diff --git a/src/i18n/catalogs/en.ts b/src/i18n/catalogs/en.ts
index 99f7b4f..336ba21 100644
--- a/src/i18n/catalogs/en.ts
+++ b/src/i18n/catalogs/en.ts
@@ -268,6 +268,12 @@ const common = {
torrentPrioritizePiece: 'Prioritize Torrent pieces',
torrentPrioritizePieceHint: 'Optional Aria2 preview policy: head, tail, or both; each may use a size such as 1M. Changes apply when the Torrent starts or retries.',
torrentPrioritizePieceInvalid: 'Torrent piece priority must use head and/or tail with optional K or M sizes between 1K and 1024M',
+ torrentEncryptionPolicy: 'Torrent encryption policy',
+ torrentEncryptionPolicyHint: 'Applied when this Torrent starts or retries. Choose one policy so the handshake and payload encryption settings stay consistent.',
+ torrentEncryptionDisabled: 'Disabled',
+ torrentEncryptionRequireCrypto: 'Require obfuscated handshake',
+ torrentEncryptionForceEncryption: 'Force encrypted payload (ARC4)',
+ torrentEncryptionPolicyInvalid: 'Choose a valid Torrent encryption policy',
torrentRemoveUnselectedFile: 'Delete unselected Torrent files after completion',
torrentRemoveUnselectedFileHint: 'Only applies when a subset of files is selected. Aria2 permanently deletes the other files after the Torrent completes.',
torrentRemoveUnselectedFileConfirm: 'Delete {{count}} unselected Torrent files after completion? This cannot be undone.',
@@ -534,6 +540,12 @@ const common = {
torrentPrioritizePiece: 'Prioritize Torrent pieces',
torrentPrioritizePieceHint: 'Saved with this Torrent and applied on its next start or retry. Use head, tail, or both with optional K or M sizes.',
torrentPrioritizePieceInvalid: 'Torrent piece priority must use head and/or tail with optional K or M sizes between 1K and 1024M',
+ torrentEncryptionPolicy: 'Torrent encryption policy',
+ torrentEncryptionPolicyHint: 'Saved with this Torrent and applied on its next start or retry. The selected policy keeps Aria2 encryption settings consistent.',
+ torrentEncryptionDisabled: 'Disabled',
+ torrentEncryptionRequireCrypto: 'Require obfuscated handshake',
+ torrentEncryptionForceEncryption: 'Force encrypted payload (ARC4)',
+ torrentEncryptionPolicyInvalid: 'Choose a valid Torrent encryption policy',
torrentRemoveUnselectedFile: 'Delete unselected Torrent files after completion',
torrentRemoveUnselectedFileHint: 'Only applies when a selected subset is configured. The unselected files are not Firelink-owned and are permanently removed when the Torrent completes.',
torrentRemoveUnselectedFileConfirm: 'Enable permanent deletion of unselected Torrent files after completion? This cannot be undone.',
diff --git a/src/i18n/catalogs/fa.ts b/src/i18n/catalogs/fa.ts
index c9d7991..63f7246 100644
--- a/src/i18n/catalogs/fa.ts
+++ b/src/i18n/catalogs/fa.ts
@@ -268,6 +268,12 @@ const fa = {
torrentPrioritizePiece: 'اولویتبندی قطعههای تورنت',
torrentPrioritizePieceHint: 'سیاست اختیاری پیشنمایش آریا۲: ابتدا، انتها یا هر دو؛ برای هرکدام میتوان اندازهای مثل 1M نوشت. تغییرات هنگام شروع یا تلاش مجدد اعمال میشوند.',
torrentPrioritizePieceInvalid: 'اولویت قطعههای تورنت باید شامل ابتدا یا انتها، با اندازه اختیاری بین 1K و 1024M باشد',
+ torrentEncryptionPolicy: 'سیاست رمزنگاری تورنت',
+ torrentEncryptionPolicyHint: 'هنگام شروع یا تلاش مجدد اعمال میشود. یک سیاست واحد انتخاب کنید تا تنظیمات handshake و رمزنگاری payload آریا۲ سازگار بمانند.',
+ torrentEncryptionDisabled: 'غیرفعال',
+ torrentEncryptionRequireCrypto: 'الزام handshake مبهمسازیشده',
+ torrentEncryptionForceEncryption: 'الزام payload رمزنگاریشده (ARC4)',
+ torrentEncryptionPolicyInvalid: 'یک سیاست معتبر برای رمزنگاری تورنت انتخاب کنید',
torrentRemoveUnselectedFile: 'حذف فایلهای انتخابنشده تورنت پس از تکمیل',
torrentRemoveUnselectedFileHint: 'فقط وقتی اعمال میشود که زیرمجموعهای از فایلها انتخاب شده باشد. آریا۲ فایلهای دیگر را پس از تکمیل تورنت برای همیشه حذف میکند.',
torrentRemoveUnselectedFileConfirm: '{{count}} فایل انتخابنشده تورنت پس از تکمیل حذف شوند؟ این کار قابل بازگشت نیست.',
@@ -534,6 +540,12 @@ const fa = {
torrentPrioritizePiece: 'اولویتبندی قطعههای تورنت',
torrentPrioritizePieceHint: 'با این تورنت ذخیره و در شروع یا تلاش مجدد بعدی اعمال میشود. ابتدا، انتها یا هر دو را با اندازه اختیاری K یا M وارد کنید.',
torrentPrioritizePieceInvalid: 'اولویت قطعههای تورنت باید شامل ابتدا یا انتها، با اندازه اختیاری بین 1K و 1024M باشد',
+ torrentEncryptionPolicy: 'سیاست رمزنگاری تورنت',
+ torrentEncryptionPolicyHint: 'با این تورنت ذخیره و در شروع یا تلاش مجدد بعدی اعمال میشود. سیاست انتخابی تنظیمات رمزنگاری آریا۲ را سازگار نگه میدارد.',
+ torrentEncryptionDisabled: 'غیرفعال',
+ torrentEncryptionRequireCrypto: 'الزام handshake مبهمسازیشده',
+ torrentEncryptionForceEncryption: 'الزام payload رمزنگاریشده (ARC4)',
+ torrentEncryptionPolicyInvalid: 'یک سیاست معتبر برای رمزنگاری تورنت انتخاب کنید',
torrentRemoveUnselectedFile: 'حذف فایلهای انتخابنشده تورنت پس از تکمیل',
torrentRemoveUnselectedFileHint: 'فقط برای زیرمجموعه انتخابشده اعمال میشود. فایلهای انتخابنشده متعلق به Firelink نیستند و هنگام تکمیل تورنت برای همیشه حذف میشوند.',
torrentRemoveUnselectedFileConfirm: 'حذف دائمی فایلهای انتخابنشده تورنت پس از تکمیل فعال شود؟ این کار قابل بازگشت نیست.',
diff --git a/src/i18n/catalogs/he.ts b/src/i18n/catalogs/he.ts
index 60e288b..ac2dc80 100644
--- a/src/i18n/catalogs/he.ts
+++ b/src/i18n/catalogs/he.ts
@@ -268,6 +268,12 @@ const he = {
torrentPrioritizePiece: 'תעדוף חלקי טורנט',
torrentPrioritizePieceHint: 'מדיניות תצוגה מקדימה אופציונלית של Aria2: התחלה, סוף או שניהם; לכל אחד אפשר לציין גודל כמו 1M. השינוי חל בהפעלה או בניסיון חוזר.',
torrentPrioritizePieceInvalid: 'תעדוף חלקי טורנט חייב לכלול התחלה ו/או סוף, עם גודל אופציונלי בין 1K ל-1024M',
+ torrentEncryptionPolicy: 'מדיניות הצפנת Torrent',
+ torrentEncryptionPolicyHint: 'מוחלת כשה-Torrent מתחיל או מנסה שוב. בחרו מדיניות אחת כדי לשמור על הגדרות handshake והצפנת payload עקביות.',
+ torrentEncryptionDisabled: 'מושבתת',
+ torrentEncryptionRequireCrypto: 'דרישת handshake מוסווה',
+ torrentEncryptionForceEncryption: 'כפיית payload מוצפן (ARC4)',
+ torrentEncryptionPolicyInvalid: 'בחרו מדיניות הצפנה תקפה ל-Torrent',
torrentRemoveUnselectedFile: 'מחיקת קבצי Torrent שלא נבחרו לאחר השלמה',
torrentRemoveUnselectedFileHint: 'חל רק כאשר נבחרה קבוצת קבצים חלקית. Aria2 מוחק לצמיתות את שאר הקבצים לאחר השלמת ה-Torrent.',
torrentRemoveUnselectedFileConfirm: 'למחוק {{count}} קבצי Torrent שלא נבחרו לאחר השלמה? אי אפשר לבטל פעולה זו.',
@@ -534,6 +540,12 @@ const he = {
torrentPrioritizePiece: 'תעדוף חלקי טורנט',
torrentPrioritizePieceHint: 'נשמר עם הטורנט ומוחל בהפעלה או בניסיון חוזר. יש להזין התחלה, סוף או שניהם עם גודל K או M אופציונלי.',
torrentPrioritizePieceInvalid: 'תעדוף חלקי טורנט חייב לכלול התחלה ו/או סוף, עם גודל אופציונלי בין 1K ל-1024M',
+ torrentEncryptionPolicy: 'מדיניות הצפנת Torrent',
+ torrentEncryptionPolicyHint: 'נשמרת עם ה-Torrent ומוחלת בהפעלה או בניסיון חוזר. המדיניות שומרת על הגדרות ההצפנה של Aria2 עקביות.',
+ torrentEncryptionDisabled: 'מושבתת',
+ torrentEncryptionRequireCrypto: 'דרישת handshake מוסווה',
+ torrentEncryptionForceEncryption: 'כפיית payload מוצפן (ARC4)',
+ torrentEncryptionPolicyInvalid: 'בחרו מדיניות הצפנה תקפה ל-Torrent',
torrentRemoveUnselectedFile: 'מחיקת קבצי Torrent שלא נבחרו לאחר השלמה',
torrentRemoveUnselectedFileHint: 'חל רק כאשר מוגדרת קבוצת קבצים חלקית. הקבצים שלא נבחרו אינם בבעלות Firelink ונמחקים לצמיתות כשה-Torrent מסתיים.',
torrentRemoveUnselectedFileConfirm: 'להפעיל מחיקה לצמיתות של קבצי Torrent שלא נבחרו לאחר השלמה? אי אפשר לבטל פעולה זו.',
diff --git a/src/i18n/catalogs/ru.ts b/src/i18n/catalogs/ru.ts
index 69429ec..35aeee3 100644
--- a/src/i18n/catalogs/ru.ts
+++ b/src/i18n/catalogs/ru.ts
@@ -268,6 +268,12 @@ const ru = {
torrentPrioritizePiece: 'Приоритет частей торрента',
torrentPrioritizePieceHint: 'Необязательная политика предпросмотра Aria2: начало, конец или оба варианта; для каждого можно указать размер, например 1M. Применяется при запуске или повторной попытке.',
torrentPrioritizePieceInvalid: 'Приоритет частей торрента должен содержать начало и/или конец с необязательным размером от 1K до 1024M',
+ torrentEncryptionPolicy: 'Политика шифрования Torrent',
+ torrentEncryptionPolicyHint: 'Применяется при запуске или повторной попытке Torrent. Выберите одну политику, чтобы параметры handshake и шифрования payload оставались согласованными.',
+ torrentEncryptionDisabled: 'Отключено',
+ torrentEncryptionRequireCrypto: 'Требовать зашифрованное рукопожатие',
+ torrentEncryptionForceEncryption: 'Принудительно шифровать payload (ARC4)',
+ torrentEncryptionPolicyInvalid: 'Выберите допустимую политику шифрования Torrent',
torrentRemoveUnselectedFile: 'Удалять невыбранные файлы Torrent после завершения',
torrentRemoveUnselectedFileHint: 'Применяется только при выборе части файлов. Aria2 навсегда удалит остальные файлы после завершения Torrent.',
torrentRemoveUnselectedFileConfirm: 'Удалить {{count}} невыбранных файлов Torrent после завершения? Это действие нельзя отменить.',
@@ -534,6 +540,12 @@ const ru = {
torrentPrioritizePiece: 'Приоритет частей торрента',
torrentPrioritizePieceHint: 'Сохраняется с торрентом и применяется при следующем запуске или повторной попытке. Укажите начало, конец или оба варианта с размером K или M.',
torrentPrioritizePieceInvalid: 'Приоритет частей торрента должен содержать начало и/или конец с необязательным размером от 1K до 1024M',
+ torrentEncryptionPolicy: 'Политика шифрования Torrent',
+ torrentEncryptionPolicyHint: 'Сохраняется вместе с Torrent и применяется при следующем запуске или повторной попытке. Выбранная политика согласует параметры шифрования Aria2.',
+ torrentEncryptionDisabled: 'Отключено',
+ torrentEncryptionRequireCrypto: 'Требовать зашифрованное рукопожатие',
+ torrentEncryptionForceEncryption: 'Принудительно шифровать payload (ARC4)',
+ torrentEncryptionPolicyInvalid: 'Выберите допустимую политику шифрования Torrent',
torrentRemoveUnselectedFile: 'Удалять невыбранные файлы Torrent после завершения',
torrentRemoveUnselectedFileHint: 'Применяется при настроенном выборе части файлов. Невыбранные файлы не принадлежат Firelink и навсегда удаляются после завершения Torrent.',
torrentRemoveUnselectedFileConfirm: 'Включить безвозвратное удаление невыбранных файлов Torrent после завершения? Это действие нельзя отменить.',
diff --git a/src/i18n/catalogs/uk.ts b/src/i18n/catalogs/uk.ts
index ffb3456..ed6a37c 100644
--- a/src/i18n/catalogs/uk.ts
+++ b/src/i18n/catalogs/uk.ts
@@ -268,6 +268,12 @@ const uk = {
torrentPrioritizePiece: 'Пріоритет частин торрента',
torrentPrioritizePieceHint: 'Необов’язкова політика попереднього перегляду Aria2: початок, кінець або обидва варіанти; для кожного можна вказати розмір, наприклад 1M. Застосовується під час запуску або повторної спроби.',
torrentPrioritizePieceInvalid: 'Пріоритет частин торрента має містити початок і/або кінець із необов’язковим розміром від 1K до 1024M',
+ torrentEncryptionPolicy: 'Політика шифрування Torrent',
+ torrentEncryptionPolicyHint: 'Застосовується під час запуску або повторної спроби Torrent. Виберіть одну політику, щоб параметри handshake і шифрування payload залишалися узгодженими.',
+ torrentEncryptionDisabled: 'Вимкнено',
+ torrentEncryptionRequireCrypto: 'Вимагати зашифроване рукостискання',
+ torrentEncryptionForceEncryption: 'Примусово шифрувати payload (ARC4)',
+ torrentEncryptionPolicyInvalid: 'Виберіть допустиму політику шифрування Torrent',
torrentRemoveUnselectedFile: 'Видаляти невибрані файли Torrent після завершення',
torrentRemoveUnselectedFileHint: 'Застосовується лише після вибору частини файлів. Aria2 назавжди видалить решту файлів після завершення Torrent.',
torrentRemoveUnselectedFileConfirm: 'Видалити {{count}} невибраних файлів Torrent після завершення? Цю дію не можна скасувати.',
@@ -534,6 +540,12 @@ const uk = {
torrentPrioritizePiece: 'Пріоритет частин торрента',
torrentPrioritizePieceHint: 'Зберігається разом із торрентом і застосовується під час наступного запуску або повторної спроби. Укажіть початок, кінець або обидва варіанти з розміром K чи M.',
torrentPrioritizePieceInvalid: 'Пріоритет частин торрента має містити початок і/або кінець із необов’язковим розміром від 1K до 1024M',
+ torrentEncryptionPolicy: 'Політика шифрування Torrent',
+ torrentEncryptionPolicyHint: 'Зберігається разом із Torrent і застосовується під час наступного запуску або повторної спроби. Вибрана політика узгоджує параметри шифрування Aria2.',
+ torrentEncryptionDisabled: 'Вимкнено',
+ torrentEncryptionRequireCrypto: 'Вимагати зашифроване рукостискання',
+ torrentEncryptionForceEncryption: 'Примусово шифрувати payload (ARC4)',
+ torrentEncryptionPolicyInvalid: 'Виберіть допустиму політику шифрування Torrent',
torrentRemoveUnselectedFile: 'Видаляти невибрані файли Torrent після завершення',
torrentRemoveUnselectedFileHint: 'Застосовується для налаштованого вибору частини файлів. Невибрані файли не належать Firelink і назавжди видаляються після завершення Torrent.',
torrentRemoveUnselectedFileConfirm: 'Увімкнути незворотне видалення невибраних файлів Torrent після завершення? Цю дію не можна скасувати.',
diff --git a/src/i18n/catalogs/zh-CN.ts b/src/i18n/catalogs/zh-CN.ts
index b1a412c..e091908 100644
--- a/src/i18n/catalogs/zh-CN.ts
+++ b/src/i18n/catalogs/zh-CN.ts
@@ -268,6 +268,12 @@ const zhCN = {
torrentPrioritizePiece: '优先下载 Torrent 片段',
torrentPrioritizePieceHint: '可选的 Aria2 预览策略:开头、结尾或两者;每项可使用 1M 等大小。Torrent 启动或重试时应用。',
torrentPrioritizePieceInvalid: 'Torrent 片段优先级必须使用开头和/或结尾,并可选 1K 到 1024M 的大小',
+ torrentEncryptionPolicy: 'Torrent 加密策略',
+ torrentEncryptionPolicyHint: '在 Torrent 启动或重试时应用。选择单一策略,确保握手和 payload 加密设置保持一致。',
+ torrentEncryptionDisabled: '已禁用',
+ torrentEncryptionRequireCrypto: '要求加密握手',
+ torrentEncryptionForceEncryption: '强制加密 payload(ARC4)',
+ torrentEncryptionPolicyInvalid: '请选择有效的 Torrent 加密策略',
torrentRemoveUnselectedFile: '完成后删除未选中的 Torrent 文件',
torrentRemoveUnselectedFileHint: '仅在选择了部分文件时生效。Torrent 完成后,Aria2 会永久删除其余文件。',
torrentRemoveUnselectedFileConfirm: '完成后删除 {{count}} 个未选中的 Torrent 文件?此操作无法撤销。',
@@ -534,6 +540,12 @@ const zhCN = {
torrentPrioritizePiece: '优先下载 Torrent 片段',
torrentPrioritizePieceHint: '随 Torrent 保存,并在下次启动或重试时应用。可使用开头、结尾或两者,并可选 K 或 M 大小。',
torrentPrioritizePieceInvalid: 'Torrent 片段优先级必须使用开头和/或结尾,并可选 1K 到 1024M 的大小',
+ torrentEncryptionPolicy: 'Torrent 加密策略',
+ torrentEncryptionPolicyHint: '随 Torrent 保存,并在下次启动或重试时应用。所选策略会保持 Aria2 加密设置一致。',
+ torrentEncryptionDisabled: '已禁用',
+ torrentEncryptionRequireCrypto: '要求加密握手',
+ torrentEncryptionForceEncryption: '强制加密 payload(ARC4)',
+ torrentEncryptionPolicyInvalid: '请选择有效的 Torrent 加密策略',
torrentRemoveUnselectedFile: '完成后删除未选中的 Torrent 文件',
torrentRemoveUnselectedFileHint: '仅适用于配置了部分文件选择的 Torrent。未选中的文件不属于 Firelink,并会在 Torrent 完成后永久删除。',
torrentRemoveUnselectedFileConfirm: '启用完成后永久删除未选中的 Torrent 文件?此操作无法撤销。',
diff --git a/src/store/useDownloadStore.test.ts b/src/store/useDownloadStore.test.ts
index 632ddf4..357eea5 100644
--- a/src/store/useDownloadStore.test.ts
+++ b/src/store/useDownloadStore.test.ts
@@ -858,7 +858,8 @@ describe('useDownloadStore', () => {
torrentExcludeTrackers: 123 as unknown as string,
torrentStopTimeout: 604801,
torrentPrioritizePiece: 'head=1G',
- torrentRemoveUnselectedFile: 'yes' as unknown as boolean
+ torrentRemoveUnselectedFile: 'yes' as unknown as boolean,
+ torrentEncryptionPolicy: 'arc4'
});
expect(normalized.torrentMaxPeers).toBeUndefined();
@@ -869,6 +870,7 @@ describe('useDownloadStore', () => {
expect(normalized.torrentStopTimeout).toBeUndefined();
expect(normalized.torrentPrioritizePiece).toBeUndefined();
expect(normalized.torrentRemoveUnselectedFile).toBeUndefined();
+ expect(normalized.torrentEncryptionPolicy).toBeUndefined();
});
it('normalizes proxy settings for download dispatch', async () => {
@@ -1524,6 +1526,7 @@ describe('useDownloadStore', () => {
torrentExcludeTrackers: '*',
torrentStopTimeout: 300,
torrentPrioritizePiece: 'head=1M,tail=1M',
+ torrentEncryptionPolicy: 'force-encryption',
torrentFileIndices: [1],
torrentRemoveUnselectedFile: true
}, { type: 'start-now' });
@@ -1541,6 +1544,7 @@ describe('useDownloadStore', () => {
torrent_exclude_trackers: '*',
torrent_stop_timeout: 300,
torrent_prioritize_piece: 'head=1M,tail=1M',
+ torrent_encryption_policy: 'force-encryption',
torrent_file_indices: [1],
torrent_remove_unselected_file: true
})
diff --git a/src/store/useDownloadStore.ts b/src/store/useDownloadStore.ts
index 84cfd6b..5f5f578 100644
--- a/src/store/useDownloadStore.ts
+++ b/src/store/useDownloadStore.ts
@@ -9,7 +9,7 @@ import type { ExtensionCookieScope } from '../bindings/ExtensionCookieScope';
import type { Queue } from '../bindings/Queue';
import { useSettingsStore } from './useSettingsStore';
import { useDownloadProgressStore } from './downloadProgressStore';
-import { canonicalizeDownloadFileName, categoryForFileName, isActiveDownloadStatus, isTransferActiveStatus, isValidTorrentExcludeTrackerList, isValidTorrentTrackerList, MAX_TORRENT_STOP_TIMEOUT, normalizeSpeedLimitForBackend, normalizeTorrentPrioritizePiece, redactDownloadForPersistence, resolveDownloadConnections } from '../utils/downloads';
+import { canonicalizeDownloadFileName, categoryForFileName, isActiveDownloadStatus, isTransferActiveStatus, isValidTorrentExcludeTrackerList, isValidTorrentTrackerList, MAX_TORRENT_STOP_TIMEOUT, normalizeSpeedLimitForBackend, normalizeTorrentEncryptionPolicy, normalizeTorrentPrioritizePiece, redactDownloadForPersistence, resolveDownloadConnections } from '../utils/downloads';
import {
resolveCategoryDestination
} from '../utils/downloadLocations';
@@ -356,6 +356,7 @@ async function dispatchItemInternal(id: string, proxyOverride?: string | null):
torrent_stop_timeout: item.torrentStopTimeout,
torrent_prioritize_piece: item.torrentPrioritizePiece || undefined,
torrent_remove_unselected_file: item.torrentRemoveUnselectedFile,
+ torrent_encryption_policy: item.torrentEncryptionPolicy || undefined,
lifecycle_generation: lifecycleGeneration.toString(),
};
@@ -659,6 +660,8 @@ export const normalizePersistedDownloadProgress = (download: DownloadItem): Down
const normalizedRemoveUnselectedFile = typeof rawRemoveUnselectedFile === 'boolean'
? rawRemoveUnselectedFile
: undefined;
+ const rawEncryptionPolicy = download.torrentEncryptionPolicy as unknown;
+ const normalizedEncryptionPolicy = normalizeTorrentEncryptionPolicy(rawEncryptionPolicy);
const normalizedOptions = rawMaxPeers !== normalizedMaxPeers ||
rawPeerSpeedLimit !== normalizedPeerSpeedLimit ||
rawCheckIntegrity !== normalizedCheckIntegrity ||
@@ -666,7 +669,8 @@ export const normalizePersistedDownloadProgress = (download: DownloadItem): Down
rawExcludeTrackers !== normalizedExcludeTrackers ||
rawStopTimeout !== normalizedStopTimeout ||
rawPrioritizePiece !== normalizedPrioritizePiece ||
- rawRemoveUnselectedFile !== normalizedRemoveUnselectedFile
+ rawRemoveUnselectedFile !== normalizedRemoveUnselectedFile ||
+ rawEncryptionPolicy !== normalizedEncryptionPolicy
? {
...download,
torrentMaxPeers: normalizedMaxPeers,
@@ -676,7 +680,8 @@ export const normalizePersistedDownloadProgress = (download: DownloadItem): Down
torrentExcludeTrackers: normalizedExcludeTrackers,
torrentStopTimeout: normalizedStopTimeout,
torrentPrioritizePiece: normalizedPrioritizePiece,
- torrentRemoveUnselectedFile: normalizedRemoveUnselectedFile
+ torrentRemoveUnselectedFile: normalizedRemoveUnselectedFile,
+ torrentEncryptionPolicy: normalizedEncryptionPolicy
}
: download;
@@ -2213,6 +2218,7 @@ export const useDownloadStore = create((set, get) => {
torrent_stop_timeout: item.torrentStopTimeout,
torrent_prioritize_piece: item.torrentPrioritizePiece || undefined,
torrent_remove_unselected_file: item.torrentRemoveUnselectedFile,
+ torrent_encryption_policy: item.torrentEncryptionPolicy || undefined,
lifecycle_generation: currentDownloadLifecycle(item.id).toString(),
});
}
diff --git a/src/utils/downloads.test.ts b/src/utils/downloads.test.ts
index 5c30056..b5abd2d 100644
--- a/src/utils/downloads.test.ts
+++ b/src/utils/downloads.test.ts
@@ -8,6 +8,7 @@ import {
canonicalizeDownloadFileName,
isValidTorrentExcludeTrackerList,
isValidTorrentTrackerList,
+ normalizeTorrentEncryptionPolicy,
normalizeTorrentPrioritizePiece,
redactDownloadForPersistence,
resolveDownloadConnections
@@ -92,6 +93,20 @@ describe('Torrent piece priority validation', () => {
});
});
+describe('Torrent encryption policy validation', () => {
+ it('accepts only the canonical policy states', () => {
+ expect(normalizeTorrentEncryptionPolicy('disabled')).toBe('disabled');
+ expect(normalizeTorrentEncryptionPolicy('require-crypto')).toBe('require-crypto');
+ expect(normalizeTorrentEncryptionPolicy('force-encryption')).toBe('force-encryption');
+ });
+
+ it('clears unknown or malformed persisted values', () => {
+ expect(normalizeTorrentEncryptionPolicy(undefined)).toBeUndefined();
+ expect(normalizeTorrentEncryptionPolicy('arc4')).toBeUndefined();
+ expect(normalizeTorrentEncryptionPolicy(true)).toBeUndefined();
+ });
+});
+
describe('download connection resolution', () => {
it('uses a clamped fallback for legacy rows without a saved value', () => {
expect(resolveDownloadConnections(undefined, 8)).toBe(8);
diff --git a/src/utils/downloads.ts b/src/utils/downloads.ts
index 98cdadd..304cb46 100644
--- a/src/utils/downloads.ts
+++ b/src/utils/downloads.ts
@@ -44,6 +44,27 @@ export const isTransferActiveStatus = (status: DownloadStatus): boolean =>
export const DOWNLOAD_CONNECTIONS_MIN = 1;
export const DOWNLOAD_CONNECTIONS_MAX = 16;
+export const TORRENT_ENCRYPTION_POLICY_DISABLED = 'disabled' as const;
+export const TORRENT_ENCRYPTION_POLICY_REQUIRE_CRYPTO = 'require-crypto' as const;
+export const TORRENT_ENCRYPTION_POLICY_FORCE_ENCRYPTION = 'force-encryption' as const;
+export type TorrentEncryptionPolicy =
+ | typeof TORRENT_ENCRYPTION_POLICY_DISABLED
+ | typeof TORRENT_ENCRYPTION_POLICY_REQUIRE_CRYPTO
+ | typeof TORRENT_ENCRYPTION_POLICY_FORCE_ENCRYPTION;
+
+export const normalizeTorrentEncryptionPolicy = (
+ value: unknown
+): TorrentEncryptionPolicy | undefined => {
+ if (
+ value === TORRENT_ENCRYPTION_POLICY_DISABLED ||
+ value === TORRENT_ENCRYPTION_POLICY_REQUIRE_CRYPTO ||
+ value === TORRENT_ENCRYPTION_POLICY_FORCE_ENCRYPTION
+ ) {
+ return value;
+ }
+ return 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.