mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-27 11:07:08 +00:00
feat(torrents): add integrity verification policy
This commit is contained in:
@@ -627,8 +627,9 @@ async function main() {
|
|||||||
const seedRoot = path.join(seedParent, 'firelink-torrent-runtime');
|
const seedRoot = path.join(seedParent, 'firelink-torrent-runtime');
|
||||||
const probeDir = path.join(tempRoot, 'probe');
|
const probeDir = path.join(tempRoot, 'probe');
|
||||||
const finalDir = path.join(tempRoot, 'final');
|
const finalDir = path.join(tempRoot, 'final');
|
||||||
|
const integrityDir = path.join(tempRoot, 'integrity');
|
||||||
const cancelDir = path.join(tempRoot, 'cancel');
|
const cancelDir = path.join(tempRoot, 'cancel');
|
||||||
for (const directory of [seedRoot, probeDir, finalDir, cancelDir]) fs.mkdirSync(directory, { recursive: true });
|
for (const directory of [seedRoot, probeDir, finalDir, integrityDir, cancelDir]) fs.mkdirSync(directory, { recursive: true });
|
||||||
|
|
||||||
const seederListenPort = await findAvailablePort();
|
const seederListenPort = await findAvailablePort();
|
||||||
const clientListenPort = await findAvailablePort();
|
const clientListenPort = await findAvailablePort();
|
||||||
@@ -730,6 +731,27 @@ async function main() {
|
|||||||
assert(finalStatus.files?.some(file => file.path === selectedPath || file.path.endsWith('/selected.bin')), 'terminal status omitted selected output');
|
assert(finalStatus.files?.some(file => file.path === selectedPath || file.path.endsWith('/selected.bin')), 'terminal status omitted selected output');
|
||||||
console.log('[OK] selected addTorrent output, pause/resume, and Aria2 file ownership passed');
|
console.log('[OK] selected addTorrent output, pause/resume, and Aria2 file ownership passed');
|
||||||
|
|
||||||
|
const integrityPath = path.join(integrityDir, torrent.name, 'selected.bin');
|
||||||
|
fs.mkdirSync(path.dirname(integrityPath), { recursive: true });
|
||||||
|
fs.writeFileSync(integrityPath, Buffer.alloc(torrent.files[0].data.length, 0x00));
|
||||||
|
const integrityGid = await rpc(client.rpcPort, client.secret, 'aria2.addTorrent', [
|
||||||
|
savedTorrentBytes.toString('base64'),
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
dir: integrityDir,
|
||||||
|
'select-file': '1',
|
||||||
|
'index-out': indexOut,
|
||||||
|
'check-integrity': 'true',
|
||||||
|
'bt-hash-check-seed': 'false',
|
||||||
|
'seed-time': '0',
|
||||||
|
'auto-file-renaming': 'false',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const integrityStatus = await waitForTerminal(client, integrityGid, 30000);
|
||||||
|
assert(integrityStatus.status === 'complete', 'integrity-check torrent did not complete');
|
||||||
|
assert(fs.readFileSync(integrityPath).equals(torrent.files[0].data), 'integrity check did not replace corrupted torrent data');
|
||||||
|
console.log('[OK] check-integrity detected and repaired corrupted Torrent data');
|
||||||
|
|
||||||
const cancelGid = await rpc(client.rpcPort, client.secret, 'aria2.addTorrent', [
|
const cancelGid = await rpc(client.rpcPort, client.secret, 'aria2.addTorrent', [
|
||||||
savedTorrentBytes.toString('base64'),
|
savedTorrentBytes.toString('base64'),
|
||||||
[],
|
[],
|
||||||
|
|||||||
@@ -191,6 +191,9 @@ pub struct DownloadItem {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[ts(optional)]
|
#[ts(optional)]
|
||||||
pub torrent_peer_speed_limit: Option<String>,
|
pub torrent_peer_speed_limit: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
#[ts(optional)]
|
||||||
|
pub torrent_check_integrity: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, TS)]
|
#[derive(Clone, Debug, Serialize, Deserialize, TS)]
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ pub struct SpawnPayload {
|
|||||||
pub torrent_upload_limit: Option<String>,
|
pub torrent_upload_limit: Option<String>,
|
||||||
pub torrent_max_peers: Option<u32>,
|
pub torrent_max_peers: Option<u32>,
|
||||||
pub torrent_peer_speed_limit: Option<String>,
|
pub torrent_peer_speed_limit: Option<String>,
|
||||||
|
pub torrent_check_integrity: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A sidecar spawner. In production this calls the real aria2/yt-dlp
|
/// A sidecar spawner. In production this calls the real aria2/yt-dlp
|
||||||
@@ -3163,6 +3164,22 @@ fn apply_aria2_torrent_options(
|
|||||||
serde_json::json!(normalized),
|
serde_json::json!(normalized),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if payload.torrent_check_integrity {
|
||||||
|
options.insert(
|
||||||
|
"check-integrity".to_string(),
|
||||||
|
serde_json::json!("true"),
|
||||||
|
);
|
||||||
|
options.insert(
|
||||||
|
"bt-hash-check-seed".to_string(),
|
||||||
|
serde_json::json!(torrent_seeding_requested(payload).to_string()),
|
||||||
|
);
|
||||||
|
// Do not let a daemon-wide bt-seed-unverified setting bypass the
|
||||||
|
// explicit per-download integrity request.
|
||||||
|
options.insert(
|
||||||
|
"bt-seed-unverified".to_string(),
|
||||||
|
serde_json::json!("false"),
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3752,6 +3769,9 @@ pub struct EnqueueItem {
|
|||||||
pub torrent_peer_speed_limit: Option<String>,
|
pub torrent_peer_speed_limit: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[ts(optional)]
|
#[ts(optional)]
|
||||||
|
pub torrent_check_integrity: Option<bool>,
|
||||||
|
#[serde(default)]
|
||||||
|
#[ts(optional)]
|
||||||
pub lifecycle_generation: Option<String>,
|
pub lifecycle_generation: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3799,6 +3819,7 @@ impl EnqueueItem {
|
|||||||
torrent_upload_limit: self.torrent_upload_limit,
|
torrent_upload_limit: self.torrent_upload_limit,
|
||||||
torrent_max_peers: self.torrent_max_peers,
|
torrent_max_peers: self.torrent_max_peers,
|
||||||
torrent_peer_speed_limit: self.torrent_peer_speed_limit,
|
torrent_peer_speed_limit: self.torrent_peer_speed_limit,
|
||||||
|
torrent_check_integrity: self.torrent_check_integrity.unwrap_or(false),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3913,6 +3934,85 @@ mod tests {
|
|||||||
assert!(torrent_seeding_requested(&payload));
|
assert!(torrent_seeding_requested(&payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn torrent_integrity_check_does_not_enter_seeding_without_a_seed_policy() {
|
||||||
|
let mut options = serde_json::Map::new();
|
||||||
|
let payload = SpawnPayload {
|
||||||
|
is_torrent: true,
|
||||||
|
torrent_check_integrity: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
apply_aria2_torrent_options(&mut options, &payload).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(options.get("check-integrity"), Some(&serde_json::json!("true")));
|
||||||
|
assert_eq!(
|
||||||
|
options.get("bt-hash-check-seed"),
|
||||||
|
Some(&serde_json::json!("false"))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
options.get("bt-seed-unverified"),
|
||||||
|
Some(&serde_json::json!("false"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn torrent_integrity_check_preserves_an_explicit_seeding_policy() {
|
||||||
|
let mut options = serde_json::Map::new();
|
||||||
|
let payload = SpawnPayload {
|
||||||
|
is_torrent: true,
|
||||||
|
torrent_check_integrity: true,
|
||||||
|
torrent_seed_ratio: Some(1.0),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
apply_aria2_torrent_options(&mut options, &payload).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(options.get("check-integrity"), Some(&serde_json::json!("true")));
|
||||||
|
assert_eq!(
|
||||||
|
options.get("bt-hash-check-seed"),
|
||||||
|
Some(&serde_json::json!("true"))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
options.get("bt-seed-unverified"),
|
||||||
|
Some(&serde_json::json!("false"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn torrent_integrity_options_are_not_emitted_when_disabled_or_non_torrent() {
|
||||||
|
for payload in [
|
||||||
|
SpawnPayload::default(),
|
||||||
|
SpawnPayload {
|
||||||
|
is_torrent: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
] {
|
||||||
|
let mut options = serde_json::Map::new();
|
||||||
|
apply_aria2_torrent_options(&mut options, &payload).unwrap();
|
||||||
|
assert!(!options.contains_key("check-integrity"));
|
||||||
|
assert!(!options.contains_key("bt-hash-check-seed"));
|
||||||
|
assert!(!options.contains_key("bt-seed-unverified"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enqueue_item_deserializes_integrity_policy_from_frontend_payload() {
|
||||||
|
let item: EnqueueItem = serde_json::from_value(serde_json::json!({
|
||||||
|
"id": "torrent-integrity",
|
||||||
|
"queue_id": "main",
|
||||||
|
"url": "magnet:?xt=urn:btih:0123456789012345678901234567890123456789",
|
||||||
|
"destination": "/tmp/downloads",
|
||||||
|
"filename": "payload",
|
||||||
|
"is_media": false,
|
||||||
|
"is_torrent": true,
|
||||||
|
"torrent_check_integrity": true
|
||||||
|
}))
|
||||||
|
.expect("frontend enqueue payload should deserialize");
|
||||||
|
|
||||||
|
assert!(item.into_task().payload.torrent_check_integrity);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn torrent_options_reject_invalid_seed_values() {
|
fn torrent_options_reject_invalid_seed_values() {
|
||||||
let mut options = serde_json::Map::new();
|
let mut options = serde_json::Map::new();
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
import type { DownloadCategory } from "./DownloadCategory";
|
import type { DownloadCategory } from "./DownloadCategory";
|
||||||
import type { DownloadStatus } from "./DownloadStatus";
|
import type { DownloadStatus } from "./DownloadStatus";
|
||||||
|
|
||||||
export type DownloadItem = { id: string, url: string, fileName: string, status: DownloadStatus, fraction?: number, speed?: string, eta?: string, size?: string, downloadedBytes?: number, totalBytes?: number, totalIsEstimate?: boolean, category: DownloadCategory, dateAdded: string, resumable?: boolean, connections?: number, speedLimit?: string, username?: string, password?: string, headers?: string, checksum?: string, cookies?: string, mirrors?: string, destination?: string, isMedia?: boolean, mediaFormatSelector?: string, mediaQuality?: string, queueId?: string, queuePosition?: number, hasBeenDispatched?: boolean, lastError?: string, lastTry?: string, isTorrent?: boolean, torrentPath?: string, torrentFileIndices?: Array<number>, torrentInfoHash?: string, torrentSeedTime?: number, torrentSeedRatio?: number, torrentUploadLimit?: string, torrentMaxPeers?: number, torrentPeerSpeedLimit?: string, };
|
export type DownloadItem = { id: string, url: string, fileName: string, status: DownloadStatus, fraction?: number, speed?: string, eta?: string, size?: string, downloadedBytes?: number, totalBytes?: number, totalIsEstimate?: boolean, category: DownloadCategory, dateAdded: string, resumable?: boolean, connections?: number, speedLimit?: string, username?: string, password?: string, headers?: string, checksum?: string, cookies?: string, mirrors?: string, destination?: string, isMedia?: boolean, mediaFormatSelector?: string, mediaQuality?: string, queueId?: string, queuePosition?: number, hasBeenDispatched?: boolean, lastError?: string, lastTry?: string, isTorrent?: boolean, torrentPath?: string, torrentFileIndices?: Array<number>, torrentInfoHash?: string, torrentSeedTime?: number, torrentSeedRatio?: number, torrentUploadLimit?: string, torrentMaxPeers?: number, torrentPeerSpeedLimit?: string, torrentCheckIntegrity?: boolean, };
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
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, 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_upload_limit?: string, torrent_max_peers?: number, torrent_peer_speed_limit?: 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, 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_upload_limit?: string, torrent_max_peers?: number, torrent_peer_speed_limit?: string, torrent_check_integrity?: boolean, lifecycle_generation?: string, };
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ export const AddDownloadsModal = () => {
|
|||||||
const [torrentUploadLimit, setTorrentUploadLimit] = useState('1024');
|
const [torrentUploadLimit, setTorrentUploadLimit] = useState('1024');
|
||||||
const [torrentMaxPeers, setTorrentMaxPeers] = useState('');
|
const [torrentMaxPeers, setTorrentMaxPeers] = useState('');
|
||||||
const [torrentPeerSpeedLimit, setTorrentPeerSpeedLimit] = useState('');
|
const [torrentPeerSpeedLimit, setTorrentPeerSpeedLimit] = useState('');
|
||||||
|
const [torrentCheckIntegrity, setTorrentCheckIntegrity] = useState(false);
|
||||||
const [freeSpace, setFreeSpace] = useState('Unknown');
|
const [freeSpace, setFreeSpace] = useState('Unknown');
|
||||||
const freeSpaceRequestRef = useRef(0);
|
const freeSpaceRequestRef = useRef(0);
|
||||||
|
|
||||||
@@ -372,6 +373,7 @@ export const AddDownloadsModal = () => {
|
|||||||
setTorrentUploadLimit('1024');
|
setTorrentUploadLimit('1024');
|
||||||
setTorrentMaxPeers('');
|
setTorrentMaxPeers('');
|
||||||
setTorrentPeerSpeedLimit('');
|
setTorrentPeerSpeedLimit('');
|
||||||
|
setTorrentCheckIntegrity(false);
|
||||||
setUseAuth(false);
|
setUseAuth(false);
|
||||||
setUsername('');
|
setUsername('');
|
||||||
setPassword('');
|
setPassword('');
|
||||||
@@ -1448,6 +1450,7 @@ export const AddDownloadsModal = () => {
|
|||||||
torrentPeerSpeedLimit: item.isTorrent
|
torrentPeerSpeedLimit: item.isTorrent
|
||||||
? normalizeSpeedLimitForBackend(torrentPeerSpeedLimit) || undefined
|
? normalizeSpeedLimitForBackend(torrentPeerSpeedLimit) || undefined
|
||||||
: undefined,
|
: undefined,
|
||||||
|
torrentCheckIntegrity: item.isTorrent ? torrentCheckIntegrity : undefined,
|
||||||
size: item.size || (item.sizeBytes ? formatBytes(item.sizeBytes) : undefined),
|
size: item.size || (item.sizeBytes ? formatBytes(item.sizeBytes) : undefined),
|
||||||
sizeBytes: item.sizeBytes
|
sizeBytes: item.sizeBytes
|
||||||
}, action);
|
}, action);
|
||||||
@@ -2094,6 +2097,20 @@ export const AddDownloadsModal = () => {
|
|||||||
<span className="text-text-muted">KiB/s</span>
|
<span className="text-text-muted">KiB/s</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
<label className="flex items-start gap-2 text-text-primary pt-2 border-t border-border-modal/50">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={torrentCheckIntegrity}
|
||||||
|
onChange={event => setTorrentCheckIntegrity(event.target.checked)}
|
||||||
|
className="accent-blue-500 mt-0.5"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
<span className="block">{t($ => $.addDownloads.torrentVerifyIntegrity)}</span>
|
||||||
|
<span className="block text-[10px] text-text-muted">
|
||||||
|
{t($ => $.addDownloads.torrentVerifyIntegrityHint)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
<div className="grid grid-cols-[1fr_auto] gap-2 items-center pt-2 border-t border-border-modal/50">
|
<div className="grid grid-cols-[1fr_auto] gap-2 items-center pt-2 border-t border-border-modal/50">
|
||||||
<label htmlFor="torrent-max-peers" className="text-text-muted">
|
<label htmlFor="torrent-max-peers" className="text-text-muted">
|
||||||
{t($ => $.addDownloads.torrentMaxPeers)}
|
{t($ => $.addDownloads.torrentMaxPeers)}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ export const PropertiesModal = () => {
|
|||||||
const [liveTorrentUploadLimitValue, setLiveTorrentUploadLimitValue] = useState('');
|
const [liveTorrentUploadLimitValue, setLiveTorrentUploadLimitValue] = useState('');
|
||||||
const [liveTorrentMaxPeersValue, setLiveTorrentMaxPeersValue] = useState('');
|
const [liveTorrentMaxPeersValue, setLiveTorrentMaxPeersValue] = useState('');
|
||||||
const [liveTorrentPeerSpeedLimitValue, setLiveTorrentPeerSpeedLimitValue] = useState('');
|
const [liveTorrentPeerSpeedLimitValue, setLiveTorrentPeerSpeedLimitValue] = useState('');
|
||||||
|
const [torrentCheckIntegrity, setTorrentCheckIntegrity] = useState(false);
|
||||||
const [isLiveSpeedLimitPending, setIsLiveSpeedLimitPending] = useState(false);
|
const [isLiveSpeedLimitPending, setIsLiveSpeedLimitPending] = useState(false);
|
||||||
const [isLiveTorrentUploadLimitPending, setIsLiveTorrentUploadLimitPending] = useState(false);
|
const [isLiveTorrentUploadLimitPending, setIsLiveTorrentUploadLimitPending] = useState(false);
|
||||||
const [isLiveTorrentPeerOptionsPending, setIsLiveTorrentPeerOptionsPending] = useState(false);
|
const [isLiveTorrentPeerOptionsPending, setIsLiveTorrentPeerOptionsPending] = useState(false);
|
||||||
@@ -168,6 +169,7 @@ export const PropertiesModal = () => {
|
|||||||
activeItem.torrentMaxPeers === undefined ? '' : String(activeItem.torrentMaxPeers)
|
activeItem.torrentMaxPeers === undefined ? '' : String(activeItem.torrentMaxPeers)
|
||||||
);
|
);
|
||||||
setLiveTorrentPeerSpeedLimitValue(activeItem.torrentPeerSpeedLimit || '');
|
setLiveTorrentPeerSpeedLimitValue(activeItem.torrentPeerSpeedLimit || '');
|
||||||
|
setTorrentCheckIntegrity(activeItem.torrentCheckIntegrity === true);
|
||||||
setErrorMessage('');
|
setErrorMessage('');
|
||||||
} else {
|
} else {
|
||||||
setSelectedPropertiesDownloadId(null);
|
setSelectedPropertiesDownloadId(null);
|
||||||
@@ -279,6 +281,7 @@ export const PropertiesModal = () => {
|
|||||||
? {
|
? {
|
||||||
torrentMaxPeers: normalizedMaxPeers,
|
torrentMaxPeers: normalizedMaxPeers,
|
||||||
torrentPeerSpeedLimit: normalizedPeerSpeedLimit || undefined,
|
torrentPeerSpeedLimit: normalizedPeerSpeedLimit || undefined,
|
||||||
|
torrentCheckIntegrity,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(connectionsDirty
|
...(connectionsDirty
|
||||||
@@ -701,6 +704,23 @@ export const PropertiesModal = () => {
|
|||||||
<div className="col-start-2 text-[11px] text-text-muted">
|
<div className="col-start-2 text-[11px] text-text-muted">
|
||||||
{t($ => $.properties.torrentPeerOptionsSavedHint)}
|
{t($ => $.properties.torrentPeerOptionsSavedHint)}
|
||||||
</div>
|
</div>
|
||||||
|
<label className="text-xs text-text-muted text-right" htmlFor="torrent-check-integrity">
|
||||||
|
{t($ => $.properties.torrentVerifyIntegrity)}
|
||||||
|
</label>
|
||||||
|
<label className="flex items-start gap-2 text-xs text-text-primary">
|
||||||
|
<input
|
||||||
|
id="torrent-check-integrity"
|
||||||
|
type="checkbox"
|
||||||
|
checked={torrentCheckIntegrity}
|
||||||
|
onChange={event => setTorrentCheckIntegrity(event.currentTarget.checked)}
|
||||||
|
disabled={transferLocked}
|
||||||
|
className="accent-accent mt-0.5 disabled:opacity-50"
|
||||||
|
aria-describedby="torrent-check-integrity-hint"
|
||||||
|
/>
|
||||||
|
<span id="torrent-check-integrity-hint" className="text-[11px] text-text-muted">
|
||||||
|
{t($ => $.properties.torrentVerifyIntegrityHint)}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{(liveSpeedLimitAvailable || liveSpeedLimitUnavailable) && (
|
{(liveSpeedLimitAvailable || liveSpeedLimitUnavailable) && (
|
||||||
|
|||||||
@@ -236,6 +236,8 @@ const common = {
|
|||||||
liveTorrentPeerOptionsApply: 'Apply peer controls',
|
liveTorrentPeerOptionsApply: 'Apply peer controls',
|
||||||
liveTorrentPeerOptionsHint: 'Changes apply without replacing the active Torrent. Leave blank to use Aria2 defaults.',
|
liveTorrentPeerOptionsHint: 'Changes apply without replacing the active Torrent. Leave blank to use Aria2 defaults.',
|
||||||
torrentPeerOptionsSavedHint: 'Saved per Torrent. 0 peers means unlimited; blank uses Aria2 defaults.',
|
torrentPeerOptionsSavedHint: 'Saved per Torrent. 0 peers means unlimited; blank uses Aria2 defaults.',
|
||||||
|
torrentVerifyIntegrity: 'Verify Torrent integrity',
|
||||||
|
torrentVerifyIntegrityHint: 'Applied when this Torrent starts or retries. It may recheck pieces and download damaged data; active transfers cannot change it.',
|
||||||
torrentMaxPeers: 'Maximum Torrent peers',
|
torrentMaxPeers: 'Maximum Torrent peers',
|
||||||
torrentPeerSpeedLimit: 'Peer speed threshold',
|
torrentPeerSpeedLimit: 'Peer speed threshold',
|
||||||
torrentMaxPeersInvalid: 'Torrent maximum peers must be an integer from 0 to 1000',
|
torrentMaxPeersInvalid: 'Torrent maximum peers must be an integer from 0 to 1000',
|
||||||
@@ -482,6 +484,8 @@ const common = {
|
|||||||
torrentSeedTimeInvalid: 'Torrent seed time must be greater than zero',
|
torrentSeedTimeInvalid: 'Torrent seed time must be greater than zero',
|
||||||
torrentSeedRatioInvalid: 'Torrent seed ratio must be zero or greater',
|
torrentSeedRatioInvalid: 'Torrent seed ratio must be zero or greater',
|
||||||
torrentUploadLimitInvalid: 'Torrent upload limit must be greater than zero',
|
torrentUploadLimitInvalid: 'Torrent upload limit must be greater than zero',
|
||||||
|
torrentVerifyIntegrity: 'Verify Torrent integrity',
|
||||||
|
torrentVerifyIntegrityHint: 'Recheck piece hashes when starting or retrying; damaged pieces may be downloaded again.',
|
||||||
torrentMaxPeers: 'Maximum Torrent peers',
|
torrentMaxPeers: 'Maximum Torrent peers',
|
||||||
torrentPeerSpeedLimit: 'Peer speed threshold',
|
torrentPeerSpeedLimit: 'Peer speed threshold',
|
||||||
torrentPeerOptionsHint: 'Leave blank for Aria2 defaults (55 peers and 50K). 0 peers means unlimited.',
|
torrentPeerOptionsHint: 'Leave blank for Aria2 defaults (55 peers and 50K). 0 peers means unlimited.',
|
||||||
|
|||||||
@@ -236,6 +236,8 @@ const fa = {
|
|||||||
liveTorrentPeerOptionsApply: 'اعمال کنترل همتا',
|
liveTorrentPeerOptionsApply: 'اعمال کنترل همتا',
|
||||||
liveTorrentPeerOptionsHint: 'بدون جایگزینی تورنت فعال اعمال میشود. برای استفاده از پیشفرض آریا۲ خالی بگذارید.',
|
liveTorrentPeerOptionsHint: 'بدون جایگزینی تورنت فعال اعمال میشود. برای استفاده از پیشفرض آریا۲ خالی بگذارید.',
|
||||||
torrentPeerOptionsSavedHint: 'برای هر تورنت ذخیره میشود. صفر یعنی نامحدود؛ خالی یعنی پیشفرض آریا۲.',
|
torrentPeerOptionsSavedHint: 'برای هر تورنت ذخیره میشود. صفر یعنی نامحدود؛ خالی یعنی پیشفرض آریا۲.',
|
||||||
|
torrentVerifyIntegrity: 'بررسی صحت تورنت',
|
||||||
|
torrentVerifyIntegrityHint: 'هنگام شروع یا تلاش مجدد این تورنت اعمال میشود. ممکن است قطعهها دوباره بررسی و دادههای خراب دوباره دانلود شوند؛ در انتقال فعال قابل تغییر نیست.',
|
||||||
torrentMaxPeers: 'حداکثر همتاهای تورنت',
|
torrentMaxPeers: 'حداکثر همتاهای تورنت',
|
||||||
torrentPeerSpeedLimit: 'آستانه سرعت همتا',
|
torrentPeerSpeedLimit: 'آستانه سرعت همتا',
|
||||||
torrentMaxPeersInvalid: 'حداکثر همتاهای تورنت باید عددی صحیح بین ۰ و ۱۰۰۰ باشد',
|
torrentMaxPeersInvalid: 'حداکثر همتاهای تورنت باید عددی صحیح بین ۰ و ۱۰۰۰ باشد',
|
||||||
@@ -482,6 +484,8 @@ const fa = {
|
|||||||
torrentSeedTimeInvalid: 'مدت سید تورنت باید بیشتر از صفر باشد',
|
torrentSeedTimeInvalid: 'مدت سید تورنت باید بیشتر از صفر باشد',
|
||||||
torrentSeedRatioInvalid: 'نسبت سید تورنت نمیتواند منفی باشد',
|
torrentSeedRatioInvalid: 'نسبت سید تورنت نمیتواند منفی باشد',
|
||||||
torrentUploadLimitInvalid: 'محدودیت آپلود تورنت باید بیشتر از صفر باشد',
|
torrentUploadLimitInvalid: 'محدودیت آپلود تورنت باید بیشتر از صفر باشد',
|
||||||
|
torrentVerifyIntegrity: 'بررسی صحت تورنت',
|
||||||
|
torrentVerifyIntegrityHint: 'هنگام شروع یا تلاش مجدد، هش قطعهها را بررسی میکند؛ قطعههای خراب ممکن است دوباره دانلود شوند.',
|
||||||
torrentMaxPeers: 'حداکثر همتاهای تورنت',
|
torrentMaxPeers: 'حداکثر همتاهای تورنت',
|
||||||
torrentPeerSpeedLimit: 'آستانه سرعت همتا',
|
torrentPeerSpeedLimit: 'آستانه سرعت همتا',
|
||||||
torrentPeerOptionsHint: 'برای استفاده از پیشفرضهای آریا۲ خالی بگذارید (۵۵ همتا و 50K). صفر یعنی نامحدود.',
|
torrentPeerOptionsHint: 'برای استفاده از پیشفرضهای آریا۲ خالی بگذارید (۵۵ همتا و 50K). صفر یعنی نامحدود.',
|
||||||
|
|||||||
@@ -236,6 +236,8 @@ const he = {
|
|||||||
liveTorrentPeerOptionsApply: 'החל בקרות עמיתים',
|
liveTorrentPeerOptionsApply: 'החל בקרות עמיתים',
|
||||||
liveTorrentPeerOptionsHint: 'השינוי חל בלי להחליף את הטורנט הפעיל. השאר ריק כדי להשתמש בברירות המחדל של Aria2.',
|
liveTorrentPeerOptionsHint: 'השינוי חל בלי להחליף את הטורנט הפעיל. השאר ריק כדי להשתמש בברירות המחדל של Aria2.',
|
||||||
torrentPeerOptionsSavedHint: 'נשמר לכל טורנט. אפס עמיתים פירושו ללא הגבלה; ריק משתמש בברירות המחדל של Aria2.',
|
torrentPeerOptionsSavedHint: 'נשמר לכל טורנט. אפס עמיתים פירושו ללא הגבלה; ריק משתמש בברירות המחדל של Aria2.',
|
||||||
|
torrentVerifyIntegrity: 'אימות תקינות הטורנט',
|
||||||
|
torrentVerifyIntegrityHint: 'מוחל כשהטורנט מתחיל או מנסה שוב. ייתכן שהחלקים ייבדקו מחדש ונתונים פגומים יורדו שוב; אי אפשר לשנות זאת בהעברה פעילה.',
|
||||||
torrentMaxPeers: 'מספר העמיתים המרבי בטורנט',
|
torrentMaxPeers: 'מספר העמיתים המרבי בטורנט',
|
||||||
torrentPeerSpeedLimit: 'סף מהירות עמיתים',
|
torrentPeerSpeedLimit: 'סף מהירות עמיתים',
|
||||||
torrentMaxPeersInvalid: 'מספר העמיתים המרבי חייב להיות מספר שלם בין 0 ל-1000',
|
torrentMaxPeersInvalid: 'מספר העמיתים המרבי חייב להיות מספר שלם בין 0 ל-1000',
|
||||||
@@ -482,6 +484,8 @@ const he = {
|
|||||||
torrentSeedTimeInvalid: 'זמן שיתוף הטורנט חייב להיות גדול מאפס',
|
torrentSeedTimeInvalid: 'זמן שיתוף הטורנט חייב להיות גדול מאפס',
|
||||||
torrentSeedRatioInvalid: 'יחס שיתוף הטורנט חייב להיות אפס או יותר',
|
torrentSeedRatioInvalid: 'יחס שיתוף הטורנט חייב להיות אפס או יותר',
|
||||||
torrentUploadLimitInvalid: 'מגבלת העלאת הטורנט חייבת להיות גדולה מאפס',
|
torrentUploadLimitInvalid: 'מגבלת העלאת הטורנט חייבת להיות גדולה מאפס',
|
||||||
|
torrentVerifyIntegrity: 'אימות תקינות הטורנט',
|
||||||
|
torrentVerifyIntegrityHint: 'בדיקת גיבובי החלקים בעת התחלה או ניסיון חוזר; חלקים פגומים עשויים להיות מורדים מחדש.',
|
||||||
torrentMaxPeers: 'מספר העמיתים המרבי בטורנט',
|
torrentMaxPeers: 'מספר העמיתים המרבי בטורנט',
|
||||||
torrentPeerSpeedLimit: 'סף מהירות עמיתים',
|
torrentPeerSpeedLimit: 'סף מהירות עמיתים',
|
||||||
torrentPeerOptionsHint: 'השאר ריק כדי להשתמש בברירות המחדל של Aria2 (55 עמיתים ו-50K). אפס עמיתים פירושו ללא הגבלה.',
|
torrentPeerOptionsHint: 'השאר ריק כדי להשתמש בברירות המחדל של Aria2 (55 עמיתים ו-50K). אפס עמיתים פירושו ללא הגבלה.',
|
||||||
|
|||||||
@@ -236,6 +236,8 @@ const ru = {
|
|||||||
liveTorrentPeerOptionsApply: 'Применить настройки пиров',
|
liveTorrentPeerOptionsApply: 'Применить настройки пиров',
|
||||||
liveTorrentPeerOptionsHint: 'Применяется без замены активного торрента. Оставьте пустым для параметров Aria2 по умолчанию.',
|
liveTorrentPeerOptionsHint: 'Применяется без замены активного торрента. Оставьте пустым для параметров Aria2 по умолчанию.',
|
||||||
torrentPeerOptionsSavedHint: 'Сохраняется для этого торрента. 0 пиров означает без ограничений; пустое поле использует настройки Aria2 по умолчанию.',
|
torrentPeerOptionsSavedHint: 'Сохраняется для этого торрента. 0 пиров означает без ограничений; пустое поле использует настройки Aria2 по умолчанию.',
|
||||||
|
torrentVerifyIntegrity: 'Проверять целостность торрента',
|
||||||
|
torrentVerifyIntegrityHint: 'Применяется при запуске или повторной попытке. Может повторно проверить части и скачать повреждённые данные; во время активной передачи изменить нельзя.',
|
||||||
torrentMaxPeers: 'Максимум пиров торрента',
|
torrentMaxPeers: 'Максимум пиров торрента',
|
||||||
torrentPeerSpeedLimit: 'Порог скорости пиров',
|
torrentPeerSpeedLimit: 'Порог скорости пиров',
|
||||||
torrentMaxPeersInvalid: 'Максимум пиров должен быть целым числом от 0 до 1000',
|
torrentMaxPeersInvalid: 'Максимум пиров должен быть целым числом от 0 до 1000',
|
||||||
@@ -482,6 +484,8 @@ const ru = {
|
|||||||
torrentSeedTimeInvalid: 'Время раздачи торрента должно быть больше нуля',
|
torrentSeedTimeInvalid: 'Время раздачи торрента должно быть больше нуля',
|
||||||
torrentSeedRatioInvalid: 'Коэффициент раздачи торрента не может быть отрицательным',
|
torrentSeedRatioInvalid: 'Коэффициент раздачи торрента не может быть отрицательным',
|
||||||
torrentUploadLimitInvalid: 'Лимит отдачи торрента должен быть больше нуля',
|
torrentUploadLimitInvalid: 'Лимит отдачи торрента должен быть больше нуля',
|
||||||
|
torrentVerifyIntegrity: 'Проверять целостность торрента',
|
||||||
|
torrentVerifyIntegrityHint: 'Проверка хешей частей при запуске или повторной попытке; повреждённые части могут быть загружены заново.',
|
||||||
torrentMaxPeers: 'Максимум пиров торрента',
|
torrentMaxPeers: 'Максимум пиров торрента',
|
||||||
torrentPeerSpeedLimit: 'Порог скорости пиров',
|
torrentPeerSpeedLimit: 'Порог скорости пиров',
|
||||||
torrentPeerOptionsHint: 'Оставьте пустым для параметров Aria2 по умолчанию (55 пиров и 50K). 0 пиров означает без ограничений.',
|
torrentPeerOptionsHint: 'Оставьте пустым для параметров Aria2 по умолчанию (55 пиров и 50K). 0 пиров означает без ограничений.',
|
||||||
|
|||||||
@@ -236,6 +236,8 @@ const uk = {
|
|||||||
liveTorrentPeerOptionsApply: 'Застосувати налаштування пірів',
|
liveTorrentPeerOptionsApply: 'Застосувати налаштування пірів',
|
||||||
liveTorrentPeerOptionsHint: 'Застосовується без заміни активного торрента. Залиште порожнім для стандартних параметрів Aria2.',
|
liveTorrentPeerOptionsHint: 'Застосовується без заміни активного торрента. Залиште порожнім для стандартних параметрів Aria2.',
|
||||||
torrentPeerOptionsSavedHint: 'Зберігається для цього торрента. 0 пірів означає без обмежень; порожнє поле використовує стандартні параметри Aria2.',
|
torrentPeerOptionsSavedHint: 'Зберігається для цього торрента. 0 пірів означає без обмежень; порожнє поле використовує стандартні параметри Aria2.',
|
||||||
|
torrentVerifyIntegrity: 'Перевіряти цілісність торрента',
|
||||||
|
torrentVerifyIntegrityHint: 'Застосовується під час запуску або повторної спроби. Частини можуть перевірятися повторно, а пошкоджені дані — завантажуватися знову; під час активної передачі змінити не можна.',
|
||||||
torrentMaxPeers: 'Максимум пірів торрента',
|
torrentMaxPeers: 'Максимум пірів торрента',
|
||||||
torrentPeerSpeedLimit: 'Поріг швидкості пірів',
|
torrentPeerSpeedLimit: 'Поріг швидкості пірів',
|
||||||
torrentMaxPeersInvalid: 'Максимум пірів має бути цілим числом від 0 до 1000',
|
torrentMaxPeersInvalid: 'Максимум пірів має бути цілим числом від 0 до 1000',
|
||||||
@@ -482,6 +484,8 @@ const uk = {
|
|||||||
torrentSeedTimeInvalid: 'Час роздачі торрента має бути більшим за нуль',
|
torrentSeedTimeInvalid: 'Час роздачі торрента має бути більшим за нуль',
|
||||||
torrentSeedRatioInvalid: 'Коефіцієнт роздачі торрента не може бути від’ємним',
|
torrentSeedRatioInvalid: 'Коефіцієнт роздачі торрента не може бути від’ємним',
|
||||||
torrentUploadLimitInvalid: 'Ліміт віддачі торрента має бути більшим за нуль',
|
torrentUploadLimitInvalid: 'Ліміт віддачі торрента має бути більшим за нуль',
|
||||||
|
torrentVerifyIntegrity: 'Перевіряти цілісність торрента',
|
||||||
|
torrentVerifyIntegrityHint: 'Перевіряє хеші частин під час запуску або повторної спроби; пошкоджені частини можуть завантажуватися знову.',
|
||||||
torrentMaxPeers: 'Максимум пірів торрента',
|
torrentMaxPeers: 'Максимум пірів торрента',
|
||||||
torrentPeerSpeedLimit: 'Поріг швидкості пірів',
|
torrentPeerSpeedLimit: 'Поріг швидкості пірів',
|
||||||
torrentPeerOptionsHint: 'Залиште порожнім для стандартних параметрів Aria2 (55 пірів і 50K). 0 пірів означає без обмежень.',
|
torrentPeerOptionsHint: 'Залиште порожнім для стандартних параметрів Aria2 (55 пірів і 50K). 0 пірів означає без обмежень.',
|
||||||
|
|||||||
@@ -236,6 +236,8 @@ const zhCN = {
|
|||||||
liveTorrentPeerOptionsApply: '应用节点控制',
|
liveTorrentPeerOptionsApply: '应用节点控制',
|
||||||
liveTorrentPeerOptionsHint: '无需替换活动 Torrent 即可应用。留空以使用 Aria2 默认值。',
|
liveTorrentPeerOptionsHint: '无需替换活动 Torrent 即可应用。留空以使用 Aria2 默认值。',
|
||||||
torrentPeerOptionsSavedHint: '按 Torrent 保存。0 个节点表示不限制;留空使用 Aria2 默认值。',
|
torrentPeerOptionsSavedHint: '按 Torrent 保存。0 个节点表示不限制;留空使用 Aria2 默认值。',
|
||||||
|
torrentVerifyIntegrity: '验证 Torrent 完整性',
|
||||||
|
torrentVerifyIntegrityHint: '在 Torrent 启动或重试时应用。可能会重新检查分片并重新下载损坏的数据;活动传输期间无法更改。',
|
||||||
torrentMaxPeers: 'Torrent 最大对等节点数',
|
torrentMaxPeers: 'Torrent 最大对等节点数',
|
||||||
torrentPeerSpeedLimit: '对等节点速度阈值',
|
torrentPeerSpeedLimit: '对等节点速度阈值',
|
||||||
torrentMaxPeersInvalid: 'Torrent 最大对等节点数必须是 0 到 1000 之间的整数',
|
torrentMaxPeersInvalid: 'Torrent 最大对等节点数必须是 0 到 1000 之间的整数',
|
||||||
@@ -482,6 +484,8 @@ const zhCN = {
|
|||||||
torrentSeedTimeInvalid: '做种时间必须大于零',
|
torrentSeedTimeInvalid: '做种时间必须大于零',
|
||||||
torrentSeedRatioInvalid: '做种比率不能小于零',
|
torrentSeedRatioInvalid: '做种比率不能小于零',
|
||||||
torrentUploadLimitInvalid: '种子上传限速必须大于零',
|
torrentUploadLimitInvalid: '种子上传限速必须大于零',
|
||||||
|
torrentVerifyIntegrity: '验证 Torrent 完整性',
|
||||||
|
torrentVerifyIntegrityHint: '启动或重试时重新检查分片哈希;损坏的分片可能会再次下载。',
|
||||||
torrentMaxPeers: 'Torrent 最大对等节点数',
|
torrentMaxPeers: 'Torrent 最大对等节点数',
|
||||||
torrentPeerSpeedLimit: '对等节点速度阈值',
|
torrentPeerSpeedLimit: '对等节点速度阈值',
|
||||||
torrentPeerOptionsHint: '留空以使用 Aria2 默认值(55 个节点和 50K)。0 个节点表示不限制。',
|
torrentPeerOptionsHint: '留空以使用 Aria2 默认值(55 个节点和 50K)。0 个节点表示不限制。',
|
||||||
|
|||||||
@@ -852,11 +852,13 @@ describe('useDownloadStore', () => {
|
|||||||
dateAdded: '',
|
dateAdded: '',
|
||||||
isTorrent: true,
|
isTorrent: true,
|
||||||
torrentMaxPeers: 'not-a-number' as unknown as number,
|
torrentMaxPeers: 'not-a-number' as unknown as number,
|
||||||
torrentPeerSpeedLimit: 0 as unknown as string
|
torrentPeerSpeedLimit: 0 as unknown as string,
|
||||||
|
torrentCheckIntegrity: 'yes' as unknown as boolean
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(normalized.torrentMaxPeers).toBeUndefined();
|
expect(normalized.torrentMaxPeers).toBeUndefined();
|
||||||
expect(normalized.torrentPeerSpeedLimit).toBeUndefined();
|
expect(normalized.torrentPeerSpeedLimit).toBeUndefined();
|
||||||
|
expect(normalized.torrentCheckIntegrity).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('normalizes proxy settings for download dispatch', async () => {
|
it('normalizes proxy settings for download dispatch', async () => {
|
||||||
@@ -1505,7 +1507,9 @@ describe('useDownloadStore', () => {
|
|||||||
url: 'https://example.com/start.bin',
|
url: 'https://example.com/start.bin',
|
||||||
fileName: 'start.bin',
|
fileName: 'start.bin',
|
||||||
category: 'Other',
|
category: 'Other',
|
||||||
dateAdded: ''
|
dateAdded: '',
|
||||||
|
isTorrent: true,
|
||||||
|
torrentCheckIntegrity: true
|
||||||
}, { type: 'start-now' });
|
}, { type: 'start-now' });
|
||||||
|
|
||||||
const item = useDownloadStore.getState().downloads[0];
|
const item = useDownloadStore.getState().downloads[0];
|
||||||
@@ -1514,7 +1518,10 @@ describe('useDownloadStore', () => {
|
|||||||
expect(ipc.invokeCommand).toHaveBeenCalledWith(
|
expect(ipc.invokeCommand).toHaveBeenCalledWith(
|
||||||
'enqueue_download',
|
'enqueue_download',
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
item: expect.objectContaining({ id: 'start-1' })
|
item: expect.objectContaining({
|
||||||
|
id: 'start-1',
|
||||||
|
torrent_check_integrity: true
|
||||||
|
})
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -350,6 +350,7 @@ async function dispatchItemInternal(id: string, proxyOverride?: string | null):
|
|||||||
torrent_upload_limit: item.torrentUploadLimit || undefined,
|
torrent_upload_limit: item.torrentUploadLimit || undefined,
|
||||||
torrent_max_peers: item.torrentMaxPeers,
|
torrent_max_peers: item.torrentMaxPeers,
|
||||||
torrent_peer_speed_limit: item.torrentPeerSpeedLimit || undefined,
|
torrent_peer_speed_limit: item.torrentPeerSpeedLimit || undefined,
|
||||||
|
torrent_check_integrity: item.torrentCheckIntegrity,
|
||||||
lifecycle_generation: lifecycleGeneration.toString(),
|
lifecycle_generation: lifecycleGeneration.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -626,12 +627,18 @@ export const normalizePersistedDownloadProgress = (download: DownloadItem): Down
|
|||||||
const normalizedPeerSpeedLimit = typeof rawPeerSpeedLimit === 'string'
|
const normalizedPeerSpeedLimit = typeof rawPeerSpeedLimit === 'string'
|
||||||
? normalizeSpeedLimitForBackend(rawPeerSpeedLimit) || undefined
|
? normalizeSpeedLimitForBackend(rawPeerSpeedLimit) || undefined
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const rawCheckIntegrity = download.torrentCheckIntegrity as unknown;
|
||||||
|
const normalizedCheckIntegrity = typeof rawCheckIntegrity === 'boolean'
|
||||||
|
? rawCheckIntegrity
|
||||||
|
: undefined;
|
||||||
const normalizedOptions = rawMaxPeers !== normalizedMaxPeers ||
|
const normalizedOptions = rawMaxPeers !== normalizedMaxPeers ||
|
||||||
rawPeerSpeedLimit !== normalizedPeerSpeedLimit
|
rawPeerSpeedLimit !== normalizedPeerSpeedLimit ||
|
||||||
|
rawCheckIntegrity !== normalizedCheckIntegrity
|
||||||
? {
|
? {
|
||||||
...download,
|
...download,
|
||||||
torrentMaxPeers: normalizedMaxPeers,
|
torrentMaxPeers: normalizedMaxPeers,
|
||||||
torrentPeerSpeedLimit: normalizedPeerSpeedLimit
|
torrentPeerSpeedLimit: normalizedPeerSpeedLimit,
|
||||||
|
torrentCheckIntegrity: normalizedCheckIntegrity
|
||||||
}
|
}
|
||||||
: download;
|
: download;
|
||||||
|
|
||||||
@@ -2162,6 +2169,7 @@ export const useDownloadStore = create<DownloadState>((set, get) => {
|
|||||||
torrent_upload_limit: item.torrentUploadLimit || undefined,
|
torrent_upload_limit: item.torrentUploadLimit || undefined,
|
||||||
torrent_max_peers: item.torrentMaxPeers,
|
torrent_max_peers: item.torrentMaxPeers,
|
||||||
torrent_peer_speed_limit: item.torrentPeerSpeedLimit || undefined,
|
torrent_peer_speed_limit: item.torrentPeerSpeedLimit || undefined,
|
||||||
|
torrent_check_integrity: item.torrentCheckIntegrity,
|
||||||
lifecycle_generation: currentDownloadLifecycle(item.id).toString(),
|
lifecycle_generation: currentDownloadLifecycle(item.id).toString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export interface AddDownloadDraftRow {
|
|||||||
torrentUploadLimit?: string;
|
torrentUploadLimit?: string;
|
||||||
torrentMaxPeers?: number;
|
torrentMaxPeers?: number;
|
||||||
torrentPeerSpeedLimit?: string;
|
torrentPeerSpeedLimit?: string;
|
||||||
|
torrentCheckIntegrity?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user