feat(torrents): add global open-file limit

This commit is contained in:
NimBold
2026-08-02 10:50:30 +03:30
parent 48b4d984a2
commit 2d427b96d3
19 changed files with 332 additions and 6 deletions
+1 -1
View File
@@ -11,4 +11,4 @@ import type { SiteLogin } from "./SiteLogin";
import type { Theme } from "./Theme";
import type { WindowControlStyle } from "./WindowControlStyle";
export type PersistedSettings = { theme: Theme, fontFamily: FontFamily, windowControlStyle: WindowControlStyle, calendarPreference: CalendarPreference, language: string, baseDownloadFolder: string, categorySubfoldersEnabled: boolean, categorySubfolders: { [key in string]: string }, categoryDirectoryOverrides: { [key in string]: string }, approvedDownloadRoots: Array<string>, maxConcurrentDownloads: number, globalSpeedLimit: string, speedLimitPresetValues: Array<number>, logsEnabled: boolean, isSidebarVisible: boolean, sidebarPosition: string, activeSettingsTab: SettingsTab, scheduler: SchedulerSettings, schedulerRunning: boolean, schedulerActiveDownloadIds: Array<string>, schedulerLastStartKey: string, schedulerLastStopKey: string, lastCustomSpeedLimitKiB: number, lastCustomSpeedLimitUnit: string, perServerConnections: number, maxAutomaticRetries: number, showNotifications: boolean, playCompletionSound: boolean, autoAddClipboardLinks: boolean, appFontSize: AppFontSize, listRowDensity: ListRowDensity, showDockBadge: boolean, showMenuBarIcon: boolean, proxyMode: ProxyMode, proxyHost: string, proxyPort: number, torrentEnableDht: boolean, torrentEnableDht6: boolean, torrentEnablePex: boolean, torrentEnableLpd: boolean, customUserAgent: string, askWhereToSaveEachFile: boolean, rememberLastUsedDownloadDirectory: boolean, preventsSleepWhileDownloading: boolean, preventsDisplaySleepWhileDownloading: boolean, mediaCookieSource: MediaCookieSource, siteLogins: Array<SiteLogin>, autoCheckUpdates: boolean, keychainAccessGranted: boolean, };
export type PersistedSettings = { theme: Theme, fontFamily: FontFamily, windowControlStyle: WindowControlStyle, calendarPreference: CalendarPreference, language: string, baseDownloadFolder: string, categorySubfoldersEnabled: boolean, categorySubfolders: { [key in string]: string }, categoryDirectoryOverrides: { [key in string]: string }, approvedDownloadRoots: Array<string>, maxConcurrentDownloads: number, globalSpeedLimit: string, speedLimitPresetValues: Array<number>, logsEnabled: boolean, isSidebarVisible: boolean, sidebarPosition: string, activeSettingsTab: SettingsTab, scheduler: SchedulerSettings, schedulerRunning: boolean, schedulerActiveDownloadIds: Array<string>, schedulerLastStartKey: string, schedulerLastStopKey: string, lastCustomSpeedLimitKiB: number, lastCustomSpeedLimitUnit: string, perServerConnections: number, maxAutomaticRetries: number, showNotifications: boolean, playCompletionSound: boolean, autoAddClipboardLinks: boolean, appFontSize: AppFontSize, listRowDensity: ListRowDensity, showDockBadge: boolean, showMenuBarIcon: boolean, proxyMode: ProxyMode, proxyHost: string, proxyPort: number, torrentEnableDht: boolean, torrentEnableDht6: boolean, torrentEnablePex: boolean, torrentEnableLpd: boolean, torrentMaxOpenFiles: number, customUserAgent: string, askWhereToSaveEachFile: boolean, rememberLastUsedDownloadDirectory: boolean, preventsSleepWhileDownloading: boolean, preventsDisplaySleepWhileDownloading: boolean, mediaCookieSource: MediaCookieSource, siteLogins: Array<SiteLogin>, autoCheckUpdates: boolean, keychainAccessGranted: boolean, };
+50
View File
@@ -34,6 +34,11 @@ import {
import { usePlatformInfo } from '../utils/platform';
import { isTrustedFirelinkReleaseUrl } from '../utils/releaseUrls';
import { normalizeCustomProxy } from '../store/useDownloadStore';
import {
MAX_TORRENT_MAX_OPEN_FILES,
MIN_TORRENT_MAX_OPEN_FILES,
normalizeTorrentMaxOpenFiles
} from '../utils/downloads';
import { useTranslation } from 'react-i18next';
import { localeDirection, resolveAppLocale } from '../i18n';
@@ -318,6 +323,10 @@ const engineRunId = useRef(0);
() => String(settings.maxConcurrentDownloads)
);
const [proxyPortInput, setProxyPortInput] = useState(() => String(settings.proxyPort));
const [torrentMaxOpenFilesInput, setTorrentMaxOpenFilesInput] = useState(
() => String(settings.torrentMaxOpenFiles)
);
const torrentMaxOpenFilesCommitRef = useRef(0);
useEffect(() => {
setPerServerConnectionsInput(String(settings.perServerConnections));
@@ -331,6 +340,10 @@ const engineRunId = useRef(0);
setProxyPortInput(String(settings.proxyPort));
}, [settings.proxyPort]);
useEffect(() => {
setTorrentMaxOpenFilesInput(String(settings.torrentMaxOpenFiles));
}, [settings.torrentMaxOpenFiles]);
// Local state for adding site login
const [loginPattern, setLoginPattern] = useState('');
const [loginUser, setLoginUser] = useState('');
@@ -346,6 +359,22 @@ const engineRunId = useRef(0);
// Toast notifications
const { addToast } = useToast();
const commitTorrentMaxOpenFiles = (raw: string) => {
const next = normalizeTorrentMaxOpenFiles(raw) ?? settings.torrentMaxOpenFiles;
const requestId = ++torrentMaxOpenFilesCommitRef.current;
setTorrentMaxOpenFilesInput(String(next));
void settings.setTorrentMaxOpenFiles(next).catch(error => {
if (requestId !== torrentMaxOpenFilesCommitRef.current) return;
setTorrentMaxOpenFilesInput(String(settings.torrentMaxOpenFiles));
addToast({
message: t($ => $.settings.network.torrentMaxOpenFilesUpdateFailed, {
detail: error instanceof Error ? error.message : String(error)
}),
variant: 'error',
isActionable: true
});
});
};
const [isCheckingForUpdates, setIsCheckingForUpdates] = useState(false);
const [manualUpdateStatus, setManualUpdateStatus] = useState<ManualUpdateStatus>({ type: 'idle' });
@@ -1212,6 +1241,27 @@ runEngineChecks(false);
</p>
</div>
<h2 className="settings-section-title">{t($ => $.settings.network.torrentResourceLimits)}</h2>
<div className="mac-settings-group">
<div className="mac-settings-row settings-network-row">
<div className="settings-row-label">
<span>{t($ => $.settings.network.torrentMaxOpenFiles)}</span>
<small>{t($ => $.settings.network.torrentMaxOpenFilesDescription)}</small>
</div>
<input
type="number"
min={MIN_TORRENT_MAX_OPEN_FILES}
max={MAX_TORRENT_MAX_OPEN_FILES}
step={1}
value={torrentMaxOpenFilesInput}
onChange={(event) => setTorrentMaxOpenFilesInput(event.target.value)}
onBlur={(event) => commitTorrentMaxOpenFiles(event.target.value)}
className="app-control settings-port-input text-center"
aria-label={t($ => $.settings.network.torrentMaxOpenFiles)}
/>
</div>
</div>
<h2 className="settings-section-title">{t($ => $.settings.network.identity)}</h2>
<div className="mac-settings-group settings-popup-group">
<div className="mac-settings-row settings-network-row">
+4
View File
@@ -799,6 +799,10 @@ const common = {
torrentLpd: 'Local Peer Discovery (LPD)',
torrentLpdDescription: 'Discover compatible peers on the local network. This increases local network visibility.',
torrentPeerDiscoveryRestartNote: 'These options are global to Aria2 and take effect after Firelink restarts. Aria2 still disables peer discovery for private torrents.',
torrentResourceLimits: 'BitTorrent resource limits',
torrentMaxOpenFiles: 'Maximum open Torrent files',
torrentMaxOpenFilesDescription: 'Global Aria2 limit for files open at once in multi-file Torrents. Lower values reduce file-descriptor use; the default is 100. Changes apply to new Torrents without restarting Aria2, and this does not raise your operating system limit.',
torrentMaxOpenFilesUpdateFailed: 'Could not apply the Torrent open-file limit: {{detail}}',
identity: 'Identity',
customUserAgent: 'Custom User-Agent',
userAgentDescription: 'Applied to metadata fetches and download engines.',
+4
View File
@@ -799,6 +799,10 @@ const fa = {
torrentLpd: 'کشف همتای محلی (LPD)',
torrentLpdDescription: 'همتاهای سازگار در شبکه محلی را پیدا می‌کند و دیده‌شدن ترافیک در شبکه محلی را افزایش می‌دهد.',
torrentPeerDiscoveryRestartNote: 'این گزینه‌ها سراسری و مربوط به Aria2 هستند و پس از راه‌اندازی مجدد Firelink اعمال می‌شوند. Aria2 همچنان کشف همتا را برای تورنت‌های خصوصی خاموش می‌کند.',
torrentResourceLimits: 'محدودیت منابع بیت‌تورنت',
torrentMaxOpenFiles: 'حداکثر فایل‌های باز تورنت',
torrentMaxOpenFilesDescription: 'حداکثر سراسری Aria2 برای تعداد فایل‌های هم‌زمان باز در تورنت‌های چندفایلی. مقدار کمتر مصرف file descriptor را کم می‌کند؛ پیش‌فرض ۱۰۰ است. تغییرات برای تورنت‌های جدید و بدون راه‌اندازی مجدد Aria2 اعمال می‌شوند و محدودیت سیستم‌عامل را افزایش نمی‌دهند.',
torrentMaxOpenFilesUpdateFailed: 'اعمال محدودیت فایل‌های باز تورنت ممکن نشد: {{detail}}',
identity: 'هویت',
customUserAgent: 'User-Agent سفارشی',
userAgentDescription: 'در دریافت‌های متادیتا و موتورهای دانلود اعمال می‌شود.',
+4
View File
@@ -799,6 +799,10 @@ const he = {
torrentLpd: 'גילוי עמיתים מקומיים (LPD)',
torrentLpdDescription: 'מאתר עמיתים תואמים ברשת המקומית ומגדיל את החשיפה המקומית של התעבורה.',
torrentPeerDiscoveryRestartNote: 'האפשרויות האלה הן כלליות ל-Aria2 ונכנסות לתוקף לאחר הפעלה מחדש של Firelink. Aria2 עדיין משבית גילוי עמיתים בטורנטים פרטיים.',
torrentResourceLimits: 'מגבלות משאבי BitTorrent',
torrentMaxOpenFiles: 'מספר קובצי Torrent פתוחים מרבי',
torrentMaxOpenFilesDescription: 'מגבלה כללית של Aria2 על מספר הקבצים הפתוחים בו-זמנית בטורנטים מרובי קבצים. ערך נמוך יותר מפחית שימוש ב-file descriptors; ברירת המחדל היא 100. השינויים חלים על טורנטים חדשים ללא הפעלה מחדש של Aria2, ואינם מגדילים את מגבלת מערכת ההפעלה.',
torrentMaxOpenFilesUpdateFailed: 'לא ניתן להחיל את מגבלת הקבצים הפתוחים של Torrent: {{detail}}',
identity: 'זהות',
customUserAgent: 'User-Agent מותאם אישית',
userAgentDescription: 'מוחל על משיכות מטא נתונים ומנועי הורדה.',
+4
View File
@@ -799,6 +799,10 @@ const ru = {
torrentLpd: 'Локальное обнаружение пиров (LPD)',
torrentLpdDescription: 'Ищет подходящие пиры в локальной сети и увеличивает видимость трафика в ней.',
torrentPeerDiscoveryRestartNote: 'Эти параметры являются глобальными для Aria2 и применяются после перезапуска Firelink. Aria2 по-прежнему отключает обнаружение пиров для приватных торрентов.',
torrentResourceLimits: 'Ограничения ресурсов BitTorrent',
torrentMaxOpenFiles: 'Максимум открытых файлов Torrent',
torrentMaxOpenFilesDescription: 'Глобальный лимит Aria2 на одновременно открытые файлы в многофайловых торрентах. Меньшие значения снижают расход дескрипторов; по умолчанию 100. Изменения применяются к новым торрентам без перезапуска Aria2 и не повышают лимит операционной системы.',
torrentMaxOpenFilesUpdateFailed: 'Не удалось применить лимит открытых файлов Torrent: {{detail}}',
identity: 'Идентификация',
customUserAgent: 'Собственный User-Agent',
userAgentDescription: 'Применяется при получении метаданных и работе движков загрузки.',
+4
View File
@@ -799,6 +799,10 @@ const uk = {
torrentLpd: 'Локальний пошук пірів (LPD)',
torrentLpdDescription: 'Шукає сумісних пірів у локальній мережі та збільшує видимість трафіку в ній.',
torrentPeerDiscoveryRestartNote: 'Ці параметри є глобальними для Aria2 і застосовуються після перезапуску Firelink. Aria2 і надалі вимикає пошук пірів для приватних торрентів.',
torrentResourceLimits: 'Обмеження ресурсів BitTorrent',
torrentMaxOpenFiles: 'Максимум відкритих файлів Torrent',
torrentMaxOpenFilesDescription: 'Глобальне обмеження Aria2 на одночасно відкриті файли в багатофайлових торрентах. Менші значення зменшують використання дескрипторів; типове значення — 100. Зміни застосовуються до нових торрентів без перезапуску Aria2 і не підвищують обмеження операційної системи.',
torrentMaxOpenFilesUpdateFailed: 'Не вдалося застосувати обмеження відкритих файлів Torrent: {{detail}}',
identity: 'Ідентифікація',
customUserAgent: 'Власний User-Agent',
userAgentDescription: 'Застосовується до запитів метаданих та рушіїв завантаження.',
+4
View File
@@ -799,6 +799,10 @@ const zhCN = {
torrentLpd: '本地节点发现(LPD',
torrentLpdDescription: '在本地网络中发现兼容节点,这会增加本地网络中的流量可见性。',
torrentPeerDiscoveryRestartNote: '这些选项是 Aria2 的全局设置,需要重启 Firelink 后生效。Aria2 仍会对私有 Torrent 禁用节点发现。',
torrentResourceLimits: 'BitTorrent 资源限制',
torrentMaxOpenFiles: 'Torrent 最大打开文件数',
torrentMaxOpenFilesDescription: 'Aria2 对多文件 Torrent 同时打开文件数的全局限制。较低的值可减少文件描述符占用;默认值为 100。修改会在不重启 Aria2 的情况下应用于新 Torrent,且不会提高操作系统的限制。',
torrentMaxOpenFilesUpdateFailed: '无法应用 Torrent 打开文件数限制:{{detail}}',
identity: '身份',
customUserAgent: '自定义 User-Agent',
userAgentDescription: '应用于元数据获取和下载引擎。',
+1
View File
@@ -77,6 +77,7 @@ type CommandMap = {
result: void;
};
get_torrent_peers: { args: { id: string }; result: TorrentPeerDiagnostics };
set_torrent_max_open_files: { args: { max_open_files: number }; result: void };
set_global_speed_limit: { args: { limit: string | null }; result: void };
request_automation_permission: { args: undefined; result: void };
check_automation_permission: { args: undefined; result: void };
+64
View File
@@ -6,6 +6,10 @@ import {
} from './useSettingsStore';
import * as ipc from '../ipc';
import type { PairingTokenHydration } from '../bindings/PairingTokenHydration';
import {
DEFAULT_TORRENT_MAX_OPEN_FILES,
MAX_TORRENT_MAX_OPEN_FILES
} from '../utils/downloads';
vi.mock('../ipc', () => ({
invokeCommand: vi.fn()
@@ -57,6 +61,66 @@ describe('Torrent peer discovery preferences', () => {
});
});
describe('Torrent open-file limit preference', () => {
beforeEach(() => {
vi.clearAllMocks();
useSettingsStore.setState({ torrentMaxOpenFiles: DEFAULT_TORRENT_MAX_OPEN_FILES });
});
it('applies a bounded global limit before persisting it', async () => {
vi.mocked(ipc.invokeCommand).mockResolvedValue(undefined);
await useSettingsStore.getState().setTorrentMaxOpenFiles(256);
expect(ipc.invokeCommand).toHaveBeenCalledWith('set_torrent_max_open_files', {
max_open_files: 256
});
expect(useSettingsStore.getState().torrentMaxOpenFiles).toBe(256);
});
it('rejects unsafe values without changing the saved limit', async () => {
await expect(useSettingsStore.getState().setTorrentMaxOpenFiles(0)).rejects.toThrow();
await expect(
useSettingsStore.getState().setTorrentMaxOpenFiles(MAX_TORRENT_MAX_OPEN_FILES + 1)
).rejects.toThrow();
expect(ipc.invokeCommand).not.toHaveBeenCalledWith(
'set_torrent_max_open_files',
expect.anything()
);
expect(useSettingsStore.getState().torrentMaxOpenFiles)
.toBe(DEFAULT_TORRENT_MAX_OPEN_FILES);
});
it('serializes rapid updates so the native global option cannot reorder', async () => {
let releaseFirst!: () => void;
const firstUpdate = new Promise<void>(resolve => {
releaseFirst = resolve;
});
const events: string[] = [];
vi.mocked(ipc.invokeCommand).mockImplementation(async (command: string, args?: unknown) => {
if (command !== 'set_torrent_max_open_files') return undefined;
const value = (args as { max_open_files: number }).max_open_files;
events.push(`start:${value}`);
if (value === 256) await firstUpdate;
events.push(`finish:${value}`);
return undefined;
});
const first = useSettingsStore.getState().setTorrentMaxOpenFiles(256);
const second = useSettingsStore.getState().setTorrentMaxOpenFiles(512);
await vi.waitFor(() => expect(events).toEqual(['start:256']));
expect(useSettingsStore.getState().torrentMaxOpenFiles)
.toBe(DEFAULT_TORRENT_MAX_OPEN_FILES);
releaseFirst();
await Promise.all([first, second]);
expect(events).toEqual(['start:256', 'finish:256', 'start:512', 'finish:512']);
expect(useSettingsStore.getState().torrentMaxOpenFiles).toBe(512);
});
});
describe('calendar preference', () => {
it('keeps Gregorian as the default and persists explicit calendar choices', async () => {
vi.clearAllMocks();
+30 -1
View File
@@ -20,7 +20,13 @@ import {
DEFAULT_CATEGORY_SUBFOLDERS,
normalizeDownloadLocationSettings
} from '../utils/downloadLocations';
import { normalizeSpeedLimitForBackend } from '../utils/downloads';
import {
DEFAULT_TORRENT_MAX_OPEN_FILES,
MAX_TORRENT_MAX_OPEN_FILES,
MIN_TORRENT_MAX_OPEN_FILES,
normalizeSpeedLimitForBackend,
normalizeTorrentMaxOpenFiles
} from '../utils/downloads';
import i18n from '../i18n';
import { isAppLocalePreference, type AppLocalePreference } from '../i18n/locales';
import {
@@ -30,6 +36,7 @@ import {
} from '../utils/dateTime';
let settingsQueue: Promise<void> = Promise.resolve();
let torrentMaxOpenFilesQueue: Promise<void> = Promise.resolve();
let pairingTokenHydrationRequest: Promise<PairingTokenHydration> | null = null;
const settingsPersistenceErrorListeners = new Set<() => void>();
let settingsPersistenceFailed = false;
@@ -238,6 +245,7 @@ export interface SettingsState {
torrentEnableDht6: boolean;
torrentEnablePex: boolean;
torrentEnableLpd: boolean;
torrentMaxOpenFiles: number;
customUserAgent: string;
askWhereToSaveEachFile: boolean;
preventsSleepWhileDownloading: boolean;
@@ -292,6 +300,7 @@ export interface SettingsState {
setTorrentEnableDht6: (enabled: boolean) => void;
setTorrentEnablePex: (enabled: boolean) => void;
setTorrentEnableLpd: (enabled: boolean) => void;
setTorrentMaxOpenFiles: (value: number) => Promise<void>;
setCustomUserAgent: (userAgent: string) => void;
setAskWhereToSaveEachFile: (ask: boolean) => void;
setPreventsSleepWhileDownloading: (prevent: boolean) => void;
@@ -372,6 +381,7 @@ export const useSettingsStore = create<SettingsState>()(
torrentEnableDht6: false,
torrentEnablePex: true,
torrentEnableLpd: false,
torrentMaxOpenFiles: DEFAULT_TORRENT_MAX_OPEN_FILES,
customUserAgent: '',
askWhereToSaveEachFile: false,
preventsSleepWhileDownloading: true,
@@ -470,6 +480,22 @@ export const useSettingsStore = create<SettingsState>()(
setTorrentEnableDht6: (torrentEnableDht6) => set({ torrentEnableDht6 }),
setTorrentEnablePex: (torrentEnablePex) => set({ torrentEnablePex }),
setTorrentEnableLpd: (torrentEnableLpd) => set({ torrentEnableLpd }),
setTorrentMaxOpenFiles: (value) => {
const normalized = normalizeTorrentMaxOpenFiles(value);
if (normalized === undefined) {
return Promise.reject(new Error(
`Torrent maximum open files must be between ${MIN_TORRENT_MAX_OPEN_FILES} and ${MAX_TORRENT_MAX_OPEN_FILES}`
));
}
const apply = async () => {
await invoke('set_torrent_max_open_files', { max_open_files: normalized });
info('Settings updated: torrentMaxOpenFiles');
set({ torrentMaxOpenFiles: normalized });
};
const result = torrentMaxOpenFilesQueue.then(apply, apply);
torrentMaxOpenFilesQueue = result.then(() => undefined, () => undefined);
return result;
},
setCustomUserAgent: (customUserAgent) => set({ customUserAgent }),
setAskWhereToSaveEachFile: (askWhereToSaveEachFile) => set({ askWhereToSaveEachFile }),
setPreventsSleepWhileDownloading: (preventsSleepWhileDownloading) => {
@@ -655,6 +681,7 @@ export const useSettingsStore = create<SettingsState>()(
torrentEnableDht6: state.torrentEnableDht6,
torrentEnablePex: state.torrentEnablePex,
torrentEnableLpd: state.torrentEnableLpd,
torrentMaxOpenFiles: state.torrentMaxOpenFiles,
customUserAgent: state.customUserAgent,
askWhereToSaveEachFile: state.askWhereToSaveEachFile,
preventsSleepWhileDownloading: state.preventsSleepWhileDownloading,
@@ -704,6 +731,8 @@ export const useSettingsStore = create<SettingsState>()(
torrentEnableDht6: persistedBoolean(persisted.torrentEnableDht6, currentState.torrentEnableDht6),
torrentEnablePex: persistedBoolean(persisted.torrentEnablePex, currentState.torrentEnablePex),
torrentEnableLpd: persistedBoolean(persisted.torrentEnableLpd, currentState.torrentEnableLpd),
torrentMaxOpenFiles: normalizeTorrentMaxOpenFiles(persisted.torrentMaxOpenFiles)
?? currentState.torrentMaxOpenFiles,
sidebarPosition: isAllowedSetting(SIDEBAR_POSITION_VALUES, persisted.sidebarPosition)
? persisted.sidebarPosition
: currentState.sidebarPosition,
+14
View File
@@ -9,6 +9,7 @@ import {
isValidTorrentExcludeTrackerList,
isValidTorrentTrackerList,
normalizeTorrentEncryptionPolicy,
normalizeTorrentMaxOpenFiles,
normalizeTorrentPrioritizePiece,
normalizeTorrentTrackerInterval,
normalizeTorrentTrackerTimeout,
@@ -126,6 +127,19 @@ describe('Torrent tracker timing validation', () => {
});
});
describe('Torrent open-file limit validation', () => {
it('accepts bounded integer limits', () => {
expect(normalizeTorrentMaxOpenFiles(1)).toBe(1);
expect(normalizeTorrentMaxOpenFiles(4096)).toBe(4096);
});
it('rejects zero, fractional, and oversized limits', () => {
expect(normalizeTorrentMaxOpenFiles(0)).toBeUndefined();
expect(normalizeTorrentMaxOpenFiles('1.5')).toBeUndefined();
expect(normalizeTorrentMaxOpenFiles(4097)).toBeUndefined();
});
});
describe('download connection resolution', () => {
it('uses a clamped fallback for legacy rows without a saved value', () => {
expect(resolveDownloadConnections(undefined, 8)).toBe(8);
+12
View File
@@ -67,6 +67,9 @@ export const normalizeTorrentEncryptionPolicy = (
export const MAX_TORRENT_TRACKER_TIMEOUT = 604800;
export const MAX_TORRENT_TRACKER_INTERVAL = 604800;
export const DEFAULT_TORRENT_MAX_OPEN_FILES = 100;
export const MIN_TORRENT_MAX_OPEN_FILES = 1;
export const MAX_TORRENT_MAX_OPEN_FILES = 4096;
const parseIntegerOption = (value: unknown): number | undefined => {
if (typeof value === 'number') {
@@ -93,6 +96,15 @@ export const normalizeTorrentTrackerInterval = (value: unknown): number | undefi
: undefined;
};
export const normalizeTorrentMaxOpenFiles = (value: unknown): number | undefined => {
const parsed = parseIntegerOption(value);
return parsed !== undefined
&& parsed >= MIN_TORRENT_MAX_OPEN_FILES
&& parsed <= MAX_TORRENT_MAX_OPEN_FILES
? parsed
: undefined;
};
// Keep every filename component within the common cross-platform filesystem
// limit. Count UTF-8 bytes because POSIX filesystems enforce bytes, while this
// bound is also conservative for Windows filename components.