diff --git a/server/api/externalapi.ts b/server/api/externalapi.ts index 136e9c21..bf903892 100644 --- a/server/api/externalapi.ts +++ b/server/api/externalapi.ts @@ -1,4 +1,6 @@ +import { getSettings } from '@server/lib/settings'; import rateLimit from '@server/utils/rateLimit'; +import retry from '@server/utils/retry'; import type NodeCache from 'node-cache'; // 5 minute default TTL (in seconds) @@ -37,6 +39,11 @@ class ExternalAPI { this.fetch = fetch; } + const settings = getSettings(); + if (settings.main.retryCount) { + this.fetch = retry(this.fetch, settings.main.retryCount); + } + this.baseUrl = baseUrl; this.params = params; this.defaultHeaders = { diff --git a/server/index.ts b/server/index.ts index c0d1866d..d61a3b95 100644 --- a/server/index.ts +++ b/server/index.ts @@ -56,7 +56,7 @@ app } // Load Settings - const settings = getSettings().load(); + const settings = getSettings(); restartFlag.initializeSettings(settings.main); // Migrate library types diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index ad613cc3..e3bbf135 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -118,6 +118,7 @@ export interface MainSettings { mediaServerType: number; partialRequestsEnabled: boolean; locale: string; + retryCount: number; } interface PublicSettings { @@ -324,6 +325,7 @@ class Settings { mediaServerType: MediaServerType.NOT_CONFIGURED, partialRequestsEnabled: true, locale: 'en', + retryCount: 0, }, plex: { name: '', @@ -656,6 +658,7 @@ class Settings { } } +let loaded = false; let settings: Settings | undefined; export const getSettings = (initialSettings?: AllSettings): Settings => { @@ -663,6 +666,11 @@ export const getSettings = (initialSettings?: AllSettings): Settings => { settings = new Settings(initialSettings); } + if (!loaded) { + settings.load(); + loaded = true; + } + return settings; }; diff --git a/server/lib/settings/migrations/0002_add_retry_count.ts b/server/lib/settings/migrations/0002_add_retry_count.ts new file mode 100644 index 00000000..b73860f5 --- /dev/null +++ b/server/lib/settings/migrations/0002_add_retry_count.ts @@ -0,0 +1,13 @@ +import type { AllSettings } from '@server/lib/settings'; + +const migrateRetryCount = (settings: any): AllSettings => { + return { + ...settings, + main: { + ...settings.main, + retryCount: settings.main.retryCount ?? 0, + }, + }; +}; + +export default migrateRetryCount; diff --git a/server/utils/retry.ts b/server/utils/retry.ts new file mode 100644 index 00000000..08696e77 --- /dev/null +++ b/server/utils/retry.ts @@ -0,0 +1,23 @@ +export default function retry< + T extends (...args: Parameters) => Promise, + U +>(fn: T, retryCount: number): (...args: Parameters) => Promise { + const fnWithRetries = async ( + retryCount: number, + ...args: Parameters + ): Promise => { + try { + return await fn(...args); + } catch (e) { + if (retryCount > 1) { + return fnWithRetries(retryCount - 1, ...args); + } else { + throw e; + } + } + }; + + return (...args: Parameters): Promise => { + return fnWithRetries(retryCount, ...args); + }; +} diff --git a/src/components/Settings/SettingsMain/index.tsx b/src/components/Settings/SettingsMain/index.tsx index f7aac0d9..127b97b2 100644 --- a/src/components/Settings/SettingsMain/index.tsx +++ b/src/components/Settings/SettingsMain/index.tsx @@ -47,6 +47,11 @@ const messages = defineMessages('components.Settings.SettingsMain', { cacheImages: 'Enable Image Caching', cacheImagesTip: 'Cache externally sourced images (requires a significant amount of disk space)', + retryCount: 'Retry Count', + retryCountTip: + 'Number of retry when a network request to an external service fails', + retryCountHoverTip: + 'Do NOT enable this setting unless you understand what you are doing!', trustProxy: 'Enable Proxy Support', trustProxyTip: 'Allow Jellyseerr to correctly register client IP addresses behind a proxy', @@ -137,6 +142,7 @@ const SettingsMain = () => { partialRequestsEnabled: data?.partialRequestsEnabled, trustProxy: data?.trustProxy, cacheImages: data?.cacheImages, + retryCount: data?.retryCount, }} enableReinitialize validationSchema={MainSettingsSchema} @@ -158,6 +164,7 @@ const SettingsMain = () => { partialRequestsEnabled: values.partialRequestsEnabled, trustProxy: values.trustProxy, cacheImages: values.cacheImages, + retryCount: values.retryCount, }), }); if (!res.ok) throw new Error(); @@ -339,6 +346,32 @@ const SettingsMain = () => { /> +
+ +
+
+ +
+ {errors.retryCount && + touched.retryCount && + typeof errors.retryCount === 'string' && ( +
{errors.retryCount}
+ )} +
+