mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-19 07:36:17 +00:00
feat(downloads): add adaptive mirror reliability
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { TorrentWebSeed } from "./TorrentWebSeed";
|
||||
|
||||
export type EnqueueItem = { id: string, queue_id: string, url: string, destination: string, filename: string, connections: number | null, speed_limit: string | null, username: string | null, password: string | null, sftp_host_key_md?: string, headers: string | null, checksum: string | null, cookies: string | null, mirrors: string | null, user_agent: string | null, max_tries: number | null, proxy: string | null, format_selector: string | null, cookie_source: string | null, is_media: boolean | null, is_torrent?: boolean, torrent_path?: string, torrent_file_indices?: Array<number>, torrent_info_hash?: string, torrent_seed_time?: number, torrent_seed_ratio?: number, torrent_seed_remaining?: number, torrent_web_seeds?: Array<TorrentWebSeed>, torrent_upload_limit?: string, torrent_max_peers?: number, torrent_peer_speed_limit?: string, torrent_check_integrity?: boolean, torrent_trackers?: string, torrent_exclude_trackers?: string, torrent_tracker_connect_timeout?: number, torrent_tracker_timeout?: number, torrent_tracker_interval?: number, torrent_stop_timeout?: number, torrent_prioritize_piece?: string, torrent_remove_unselected_file?: boolean, torrent_encryption_policy?: string, torrent_file_allocation?: string, torrent_verify_only?: boolean, torrent_verify_restore_status?: string, lifecycle_generation?: string, };
|
||||
export type EnqueueItem = { id: string, queue_id: string, url: string, destination: string, filename: string, connections: number | null, speed_limit: string | null, username: string | null, password: string | null, sftp_host_key_md?: string, headers: string | null, checksum: string | null, cookies: string | null, mirrors: string | null, user_agent: string | null, max_tries: number | null, minimum_normal_download_speed_kib?: number, retry_not_found_errors?: boolean, adaptive_mirror_selection?: boolean, proxy: string | null, format_selector: string | null, cookie_source: string | null, is_media: boolean | null, is_torrent?: boolean, torrent_path?: string, torrent_file_indices?: Array<number>, torrent_info_hash?: string, torrent_seed_time?: number, torrent_seed_ratio?: number, torrent_seed_remaining?: number, torrent_web_seeds?: Array<TorrentWebSeed>, torrent_upload_limit?: string, torrent_max_peers?: number, torrent_peer_speed_limit?: string, torrent_check_integrity?: boolean, torrent_trackers?: string, torrent_exclude_trackers?: string, torrent_tracker_connect_timeout?: number, torrent_tracker_timeout?: number, torrent_tracker_interval?: number, torrent_stop_timeout?: number, torrent_prioritize_piece?: string, torrent_remove_unselected_file?: boolean, torrent_encryption_policy?: string, torrent_file_allocation?: string, torrent_verify_only?: boolean, torrent_verify_restore_status?: string, lifecycle_generation?: string, };
|
||||
|
||||
@@ -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, torrentOverallUploadLimit: 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, torrentDhtMessageTimeout: number, torrentSeparateSeedSlots: boolean, torrentMaxConcurrentSeeds: number, torrentIpv6Enabled: boolean, torrentListenPort: string, torrentDhtListenPort: string, torrentExternalIp: string, torrentDhtEntryPoint: string, torrentDhtEntryPoint6: string, torrentDhtListenAddr6: string, torrentLpdInterface: string, torrentPeerIdPrefix: string, torrentPeerAgent: string, torrentBindAddress: string, aria2DiskCache: string, 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, torrentOverallUploadLimit: 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, minimumNormalDownloadSpeedKiB: number, retryNotFoundErrors: boolean, adaptiveMirrorSelection: boolean, 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, torrentDhtMessageTimeout: number, torrentSeparateSeedSlots: boolean, torrentMaxConcurrentSeeds: number, torrentIpv6Enabled: boolean, torrentListenPort: string, torrentDhtListenPort: string, torrentExternalIp: string, torrentDhtEntryPoint: string, torrentDhtEntryPoint6: string, torrentDhtListenAddr6: string, torrentLpdInterface: string, torrentPeerIdPrefix: string, torrentPeerAgent: string, torrentBindAddress: string, aria2DiskCache: string, customUserAgent: string, askWhereToSaveEachFile: boolean, rememberLastUsedDownloadDirectory: boolean, preventsSleepWhileDownloading: boolean, preventsDisplaySleepWhileDownloading: boolean, mediaCookieSource: MediaCookieSource, siteLogins: Array<SiteLogin>, autoCheckUpdates: boolean, keychainAccessGranted: boolean, };
|
||||
|
||||
@@ -963,6 +963,43 @@ runEngineChecks(false);
|
||||
className="app-control w-24 text-center"
|
||||
/>
|
||||
</div>
|
||||
<div className="mac-settings-row">
|
||||
<div className="settings-row-label">
|
||||
<span>{t($ => $.settings.downloads.minimumNormalDownloadSpeed)}</span>
|
||||
<small>{t($ => $.settings.downloads.minimumNormalDownloadSpeedDescription)}</small>
|
||||
</div>
|
||||
<input
|
||||
type="number" min="0" max="1048576"
|
||||
value={settings.minimumNormalDownloadSpeedKiB}
|
||||
onChange={(event) => settings.setMinimumNormalDownloadSpeedKiB(Number(event.target.value))}
|
||||
className="app-control w-24 text-center"
|
||||
aria-label={t($ => $.settings.downloads.minimumNormalDownloadSpeed)}
|
||||
/>
|
||||
</div>
|
||||
<label className="mac-settings-row cursor-default">
|
||||
<div className="settings-row-label">
|
||||
<span>{t($ => $.settings.downloads.retryNotFoundErrors)}</span>
|
||||
<small>{t($ => $.settings.downloads.retryNotFoundErrorsDescription)}</small>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.retryNotFoundErrors}
|
||||
onChange={(event) => settings.setRetryNotFoundErrors(event.target.checked)}
|
||||
className="mac-switch"
|
||||
/>
|
||||
</label>
|
||||
<label className="mac-settings-row cursor-default">
|
||||
<div className="settings-row-label">
|
||||
<span>{t($ => $.settings.downloads.adaptiveMirrorSelection)}</span>
|
||||
<small>{t($ => $.settings.downloads.adaptiveMirrorSelectionDescription)}</small>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.adaptiveMirrorSelection}
|
||||
onChange={(event) => settings.setAdaptiveMirrorSelection(event.target.checked)}
|
||||
className="mac-switch"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="mac-settings-group">
|
||||
|
||||
@@ -843,6 +843,12 @@ const common = {
|
||||
parallelDownloadsDescription: 'Max simultaneous active files',
|
||||
automaticRetries: 'Automatic retries:',
|
||||
automaticRetriesDescription: 'If a connection fails',
|
||||
minimumNormalDownloadSpeed: 'Minimum normal-download speed (KiB/s):',
|
||||
minimumNormalDownloadSpeedDescription: 'Retry HTTP, FTP, and SFTP transfers that remain below this speed. Use 0 to disable.',
|
||||
retryNotFoundErrors: 'Retry temporary not-found errors',
|
||||
retryNotFoundErrorsDescription: 'Treat HTTP/FTP resource-not-found responses as retryable within the automatic retry limit. Off by default.',
|
||||
adaptiveMirrorSelection: 'Adaptive mirror selection',
|
||||
adaptiveMirrorSelectionDescription: 'Use recent transfer performance to choose among multiple mirrors. Server statistics remain private on this device.',
|
||||
systemNotification: 'Show system notification when download completes',
|
||||
systemNotificationDescription: 'Uses your operating system notification settings',
|
||||
completionChime: 'Play in-app completion chime',
|
||||
|
||||
@@ -843,6 +843,12 @@ const fa = {
|
||||
parallelDownloadsDescription: 'حداکثر فایلهای فعال همزمان',
|
||||
automaticRetries: 'تلاشهای مجدد خودکار:',
|
||||
automaticRetriesDescription: 'اگر اتصالی ناموفق باشد',
|
||||
minimumNormalDownloadSpeed: 'حداقل سرعت دانلود عادی (KiB/s):',
|
||||
minimumNormalDownloadSpeedDescription: 'دانلودهای HTTP، FTP و SFTP که سرعتشان پایینتر از این مقدار میماند دوباره تلاش میشوند. برای غیرفعالکردن ۰ را وارد کنید.',
|
||||
retryNotFoundErrors: 'تلاش دوباره برای خطای موقت «پیدا نشد»',
|
||||
retryNotFoundErrorsDescription: 'پاسخهای «منبع پیدا نشد» در HTTP/FTP را تا سقف تلاشهای خودکار دوباره امتحان میکند. بهطور پیشفرض خاموش است.',
|
||||
adaptiveMirrorSelection: 'انتخاب هوشمند میرور',
|
||||
adaptiveMirrorSelectionDescription: 'برای انتخاب بین چند میرور از عملکرد دانلودهای اخیر استفاده میکند. آمار سرورها فقط روی همین دستگاه نگهداری میشود.',
|
||||
systemNotification: 'نمایش اعلان سیستم هنگام تکمیل دانلود',
|
||||
systemNotificationDescription: 'از تنظیمات اعلان سیستمعامل شما استفاده میکند',
|
||||
completionChime: 'پخش صدای تکمیل درون برنامهای',
|
||||
|
||||
@@ -843,6 +843,12 @@ const he = {
|
||||
parallelDownloadsDescription: 'מקסימום קבצים פעילים בו-זמנית',
|
||||
automaticRetries: 'ניסיונות חוזרים אוטומטיים:',
|
||||
automaticRetriesDescription: 'אם חיבור נכשל',
|
||||
minimumNormalDownloadSpeed: 'מהירות מזערית להורדה רגילה (KiB/s):',
|
||||
minimumNormalDownloadSpeedDescription: 'ניסיון חוזר להורדות HTTP, FTP ו-SFTP שנשארות מתחת למהירות זו. 0 משבית את האפשרות.',
|
||||
retryNotFoundErrors: 'ניסיון חוזר לשגיאות זמניות של משאב שלא נמצא',
|
||||
retryNotFoundErrorsDescription: 'מתייחס לתשובות HTTP/FTP של משאב שלא נמצא כניתנות לניסיון חוזר, עד למגבלת הניסיונות האוטומטיים. כבוי כברירת מחדל.',
|
||||
adaptiveMirrorSelection: 'בחירה מסתגלת של שרת מראה',
|
||||
adaptiveMirrorSelectionDescription: 'משתמש בביצועי העברות אחרונות כדי לבחור בין כמה שרתי מראה. סטטיסטיקת השרתים נשמרת באופן פרטי במכשיר זה.',
|
||||
systemNotification: 'הצג התראת מערכת כאשר ההורדה מסתיימת',
|
||||
systemNotificationDescription: 'משתמש בהגדרות ההתראות של מערכת ההפעלה שלך',
|
||||
completionChime: 'השמע צליל סיום בתוך האפליקציה',
|
||||
|
||||
@@ -843,6 +843,12 @@ const ru = {
|
||||
parallelDownloadsDescription: 'Максимальное число одновременно активных файлов',
|
||||
automaticRetries: 'Автоматические повторы:',
|
||||
automaticRetriesDescription: 'При сбое соединения',
|
||||
minimumNormalDownloadSpeed: 'Минимальная скорость обычной загрузки (КиБ/с):',
|
||||
minimumNormalDownloadSpeedDescription: 'Повторять HTTP-, FTP- и SFTP-загрузки, если скорость остаётся ниже указанной. Значение 0 отключает эту функцию.',
|
||||
retryNotFoundErrors: 'Повторять временные ошибки «не найдено»',
|
||||
retryNotFoundErrorsDescription: 'Считать ответы HTTP/FTP «ресурс не найден» повторяемыми в пределах лимита автоматических попыток. По умолчанию выключено.',
|
||||
adaptiveMirrorSelection: 'Адаптивный выбор зеркала',
|
||||
adaptiveMirrorSelectionDescription: 'Выбирать из нескольких зеркал по недавней скорости. Статистика серверов хранится конфиденциально на этом устройстве.',
|
||||
systemNotification: 'Показывать системное уведомление по завершении загрузки',
|
||||
systemNotificationDescription: 'Использует настройки уведомлений вашей операционной системы',
|
||||
completionChime: 'Воспроизводить звуковой сигнал завершения в приложении',
|
||||
|
||||
@@ -843,6 +843,12 @@ const uk = {
|
||||
parallelDownloadsDescription: 'Макс. одночасних активних файлів',
|
||||
automaticRetries: 'Автоматичні повторні спроби:',
|
||||
automaticRetriesDescription: 'Якщо з\'єднання перерветься',
|
||||
minimumNormalDownloadSpeed: 'Мінімальна швидкість звичайного завантаження (КіБ/с):',
|
||||
minimumNormalDownloadSpeedDescription: 'Повторювати HTTP-, FTP- і SFTP-завантаження, якщо швидкість залишається нижчою за вказану. Значення 0 вимикає цю функцію.',
|
||||
retryNotFoundErrors: 'Повторювати тимчасові помилки «не знайдено»',
|
||||
retryNotFoundErrorsDescription: 'Вважати відповіді HTTP/FTP «ресурс не знайдено» придатними до повтору в межах ліміту автоматичних спроб. Типово вимкнено.',
|
||||
adaptiveMirrorSelection: 'Адаптивний вибір дзеркала',
|
||||
adaptiveMirrorSelectionDescription: 'Вибирати з кількох дзеркал за нещодавньою швидкістю. Статистика серверів приватно зберігається на цьому пристрої.',
|
||||
systemNotification: 'Показувати системне сповіщення по завершенні завантаження',
|
||||
systemNotificationDescription: 'Використовує налаштування сповіщень вашої операційної системи',
|
||||
completionChime: 'Відтворювати звуковий сигнал по завершенні в програмі',
|
||||
|
||||
@@ -843,6 +843,12 @@ const zhCN = {
|
||||
parallelDownloadsDescription: '最大同时活动文件数',
|
||||
automaticRetries: '自动重试:',
|
||||
automaticRetriesDescription: '如果连接失败',
|
||||
minimumNormalDownloadSpeed: '普通下载最低速度(KiB/s):',
|
||||
minimumNormalDownloadSpeedDescription: 'HTTP、FTP 或 SFTP 下载持续低于此速度时重试。设为 0 可关闭。',
|
||||
retryNotFoundErrors: '重试临时“未找到”错误',
|
||||
retryNotFoundErrorsDescription: '在自动重试次数限制内,将 HTTP/FTP 的“资源未找到”响应视为可重试错误。默认关闭。',
|
||||
adaptiveMirrorSelection: '自适应镜像选择',
|
||||
adaptiveMirrorSelectionDescription: '根据近期传输性能在多个镜像之间选择。服务器统计信息仅私密保存在此设备上。',
|
||||
systemNotification: '下载完成时显示系统通知',
|
||||
systemNotificationDescription: '使用操作系统的通知设置',
|
||||
completionChime: '播放应用内完成提示音',
|
||||
|
||||
@@ -36,6 +36,9 @@ vi.mock('./useSettingsStore', () => ({
|
||||
perServerConnections: 16,
|
||||
customUserAgent: '',
|
||||
maxAutomaticRetries: 3,
|
||||
minimumNormalDownloadSpeedKiB: 0,
|
||||
retryNotFoundErrors: false,
|
||||
adaptiveMirrorSelection: true,
|
||||
mediaCookieSource: 'none',
|
||||
baseDownloadFolder: '~/Downloads',
|
||||
categorySubfoldersEnabled: true,
|
||||
@@ -68,6 +71,9 @@ describe('useDownloadStore', () => {
|
||||
perServerConnections: 16,
|
||||
customUserAgent: '',
|
||||
maxAutomaticRetries: 3,
|
||||
minimumNormalDownloadSpeedKiB: 0,
|
||||
retryNotFoundErrors: false,
|
||||
adaptiveMirrorSelection: true,
|
||||
mediaCookieSource: 'none',
|
||||
baseDownloadFolder: '~/Downloads',
|
||||
categorySubfoldersEnabled: true,
|
||||
@@ -1147,6 +1153,38 @@ describe('useDownloadStore', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches normal reliability and adaptive mirror settings to the native queue', async () => {
|
||||
vi.mocked(useSettingsStore.getState).mockReturnValue({
|
||||
...useSettingsStore.getState(),
|
||||
minimumNormalDownloadSpeedKiB: 64,
|
||||
retryNotFoundErrors: true,
|
||||
adaptiveMirrorSelection: false,
|
||||
} as ReturnType<typeof useSettingsStore.getState>);
|
||||
useDownloadStore.setState({
|
||||
downloads: [
|
||||
{ id: 'reliable', url: 'https://example.test/file', fileName: 'file.bin', destination: '/tmp', status: 'queued', category: 'Other', dateAdded: '', queueId: 'MAIN', hasBeenDispatched: false },
|
||||
] as any[],
|
||||
});
|
||||
vi.mocked(ipc.invokeCommand).mockImplementation(async (command: string) => {
|
||||
if (command === 'enqueue_download') return { id: 'reliable', filename: 'file.bin' } as never;
|
||||
if (command === 'get_pending_order') return ['reliable'] as never;
|
||||
return undefined;
|
||||
});
|
||||
|
||||
await useDownloadStore.getState().startQueue('MAIN');
|
||||
|
||||
expect(ipc.invokeCommand).toHaveBeenCalledWith(
|
||||
'enqueue_download',
|
||||
expect.objectContaining({
|
||||
item: expect.objectContaining({
|
||||
minimum_normal_download_speed_kib: 64,
|
||||
retry_not_found_errors: true,
|
||||
adaptive_mirror_selection: false,
|
||||
})
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('does not resurrect a row removed while its backend enqueue is in flight', async () => {
|
||||
useDownloadStore.setState({
|
||||
downloads: [
|
||||
|
||||
@@ -370,6 +370,9 @@ async function dispatchItemInternal(id: string, proxyOverride?: string | null):
|
||||
mirrors: item.mirrors || null,
|
||||
user_agent: settings.customUserAgent.trim() || null,
|
||||
max_tries: settings.maxAutomaticRetries,
|
||||
minimum_normal_download_speed_kib: settings.minimumNormalDownloadSpeedKiB,
|
||||
retry_not_found_errors: settings.retryNotFoundErrors,
|
||||
adaptive_mirror_selection: settings.adaptiveMirrorSelection,
|
||||
proxy,
|
||||
format_selector: item.mediaFormatSelector || null,
|
||||
cookie_source: settings.mediaCookieSource !== 'none' ? settings.mediaCookieSource : null,
|
||||
@@ -2441,6 +2444,9 @@ export const useDownloadStore = create<DownloadState>((set, get) => {
|
||||
mirrors: item.mirrors || null,
|
||||
user_agent: settings.customUserAgent.trim() || null,
|
||||
max_tries: settings.maxAutomaticRetries,
|
||||
minimum_normal_download_speed_kib: settings.minimumNormalDownloadSpeedKiB,
|
||||
retry_not_found_errors: settings.retryNotFoundErrors,
|
||||
adaptive_mirror_selection: settings.adaptiveMirrorSelection,
|
||||
proxy,
|
||||
format_selector: item.mediaFormatSelector || null,
|
||||
cookie_source: settings.mediaCookieSource !== 'none' ? settings.mediaCookieSource : null,
|
||||
|
||||
@@ -25,6 +25,43 @@ describe('last used download directory preference', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('normal download reliability preferences', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
useSettingsStore.setState({
|
||||
minimumNormalDownloadSpeedKiB: 0,
|
||||
retryNotFoundErrors: false,
|
||||
adaptiveMirrorSelection: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('uses migration-safe defaults and persists bounded changes', async () => {
|
||||
expect(useSettingsStore.getState()).toMatchObject({
|
||||
minimumNormalDownloadSpeedKiB: 0,
|
||||
retryNotFoundErrors: false,
|
||||
adaptiveMirrorSelection: true,
|
||||
});
|
||||
|
||||
useSettingsStore.getState().setMinimumNormalDownloadSpeedKiB(64);
|
||||
useSettingsStore.getState().setRetryNotFoundErrors(true);
|
||||
useSettingsStore.getState().setAdaptiveMirrorSelection(false);
|
||||
await vi.waitFor(() => {
|
||||
const save = vi.mocked(ipc.invokeCommand).mock.calls
|
||||
.filter(([command]) => command === 'db_save_settings')
|
||||
.slice(-1)[0];
|
||||
expect(save).toBeDefined();
|
||||
expect(JSON.parse((save?.[1] as { data: string }).data).state).toMatchObject({
|
||||
minimumNormalDownloadSpeedKiB: 64,
|
||||
retryNotFoundErrors: true,
|
||||
adaptiveMirrorSelection: false,
|
||||
});
|
||||
});
|
||||
|
||||
useSettingsStore.getState().setMinimumNormalDownloadSpeedKiB(2_000_000);
|
||||
expect(useSettingsStore.getState().minimumNormalDownloadSpeedKiB).toBe(1_048_576);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Torrent peer discovery preferences', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -234,6 +234,9 @@ export interface SettingsState {
|
||||
// Replicated SwiftUI App Settings
|
||||
perServerConnections: number;
|
||||
maxAutomaticRetries: number;
|
||||
minimumNormalDownloadSpeedKiB: number;
|
||||
retryNotFoundErrors: boolean;
|
||||
adaptiveMirrorSelection: boolean;
|
||||
showNotifications: boolean;
|
||||
playCompletionSound: boolean;
|
||||
autoAddClipboardLinks: boolean;
|
||||
@@ -307,6 +310,9 @@ export interface SettingsState {
|
||||
|
||||
setPerServerConnections: (count: number) => void;
|
||||
setMaxAutomaticRetries: (count: number) => void;
|
||||
setMinimumNormalDownloadSpeedKiB: (speed: number) => void;
|
||||
setRetryNotFoundErrors: (enabled: boolean) => void;
|
||||
setAdaptiveMirrorSelection: (enabled: boolean) => void;
|
||||
setShowNotifications: (show: boolean) => void;
|
||||
setPlayCompletionSound: (play: boolean) => void;
|
||||
setAutoAddClipboardLinks: (enabled: boolean) => void;
|
||||
@@ -403,6 +409,9 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
// Replicated SwiftUI defaults
|
||||
perServerConnections: 16,
|
||||
maxAutomaticRetries: 3,
|
||||
minimumNormalDownloadSpeedKiB: 0,
|
||||
retryNotFoundErrors: false,
|
||||
adaptiveMirrorSelection: true,
|
||||
showNotifications: true,
|
||||
playCompletionSound: false,
|
||||
autoAddClipboardLinks: false,
|
||||
@@ -526,6 +535,16 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
setMaxAutomaticRetries: (maxAutomaticRetries) => set({
|
||||
maxAutomaticRetries: clampSettingInteger(maxAutomaticRetries, 0, 10, 3)
|
||||
}),
|
||||
setMinimumNormalDownloadSpeedKiB: (minimumNormalDownloadSpeedKiB) => set({
|
||||
minimumNormalDownloadSpeedKiB: clampSettingInteger(
|
||||
minimumNormalDownloadSpeedKiB,
|
||||
0,
|
||||
1_048_576,
|
||||
0
|
||||
)
|
||||
}),
|
||||
setRetryNotFoundErrors: (retryNotFoundErrors) => set({ retryNotFoundErrors }),
|
||||
setAdaptiveMirrorSelection: (adaptiveMirrorSelection) => set({ adaptiveMirrorSelection }),
|
||||
setShowNotifications: (showNotifications) => set({ showNotifications }),
|
||||
setPlayCompletionSound: (playCompletionSound) => set({ playCompletionSound }),
|
||||
setAutoAddClipboardLinks: (autoAddClipboardLinks) => set({ autoAddClipboardLinks }),
|
||||
@@ -762,6 +781,9 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
|
||||
perServerConnections: state.perServerConnections,
|
||||
maxAutomaticRetries: state.maxAutomaticRetries,
|
||||
minimumNormalDownloadSpeedKiB: state.minimumNormalDownloadSpeedKiB,
|
||||
retryNotFoundErrors: state.retryNotFoundErrors,
|
||||
adaptiveMirrorSelection: state.adaptiveMirrorSelection,
|
||||
showNotifications: state.showNotifications,
|
||||
playCompletionSound: state.playCompletionSound,
|
||||
autoAddClipboardLinks: state.autoAddClipboardLinks,
|
||||
@@ -961,6 +983,20 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
10,
|
||||
currentState.maxAutomaticRetries
|
||||
),
|
||||
minimumNormalDownloadSpeedKiB: clampSettingInteger(
|
||||
persisted.minimumNormalDownloadSpeedKiB,
|
||||
0,
|
||||
1_048_576,
|
||||
currentState.minimumNormalDownloadSpeedKiB
|
||||
),
|
||||
retryNotFoundErrors: persistedBoolean(
|
||||
persisted.retryNotFoundErrors,
|
||||
currentState.retryNotFoundErrors
|
||||
),
|
||||
adaptiveMirrorSelection: persistedBoolean(
|
||||
persisted.adaptiveMirrorSelection,
|
||||
currentState.adaptiveMirrorSelection
|
||||
),
|
||||
speedLimitPresetValues: Array.isArray(persisted.speedLimitPresetValues)
|
||||
? persisted.speedLimitPresetValues
|
||||
: currentState.speedLimitPresetValues,
|
||||
|
||||
Reference in New Issue
Block a user