mirror of
https://github.com/nimbold/Firelink.git
synced 2026-09-03 06:27:57 +00:00
fix(properties): route live controls through native mutations
- route active normal and Torrent-safe fields to dedicated live consumers - fence unsupported statuses and mixed persisted patches at the bridge - add regression coverage for active status and media boundaries
This commit is contained in:
@@ -73,6 +73,8 @@ const isTorrentDiagnosticsStatus = (status: string) =>
|
|||||||
const isTorrentPollingStatus = isTorrentLiveStatus;
|
const isTorrentPollingStatus = isTorrentLiveStatus;
|
||||||
|
|
||||||
const isEditableStatus = (status: string) => !['downloading', 'processing', 'verifying', 'seeding', 'waitingToSeed', 'retrying', 'moving'].includes(status);
|
const isEditableStatus = (status: string) => !['downloading', 'processing', 'verifying', 'seeding', 'waitingToSeed', 'retrying', 'moving'].includes(status);
|
||||||
|
const isLiveNormalSpeedStatus = (status: string) => ['downloading', 'retrying'].includes(status);
|
||||||
|
const isLiveTorrentControlStatus = (status: string) => ['downloading', 'seeding', 'retrying'].includes(status);
|
||||||
|
|
||||||
const isTorrentFileSelectionEditable = (status: string) =>
|
const isTorrentFileSelectionEditable = (status: string) =>
|
||||||
['ready', 'staged', 'queued', 'paused', 'failed'].includes(status);
|
['ready', 'staged', 'queued', 'paused', 'failed'].includes(status);
|
||||||
@@ -932,7 +934,15 @@ export const PropertiesWindowApp = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const applyActiveTab = useCallback(async () => {
|
const applyActiveTab = useCallback(async () => {
|
||||||
if (!snapshot || !isEditableStatus(snapshot.status)) {
|
if (!snapshot) return;
|
||||||
|
const canApplyLiveNormalSpeed = snapshot.isMedia !== true
|
||||||
|
&& snapshot.isTorrent !== true
|
||||||
|
&& isLiveNormalSpeedStatus(snapshot.status);
|
||||||
|
const canApplyLiveTorrentOptions = snapshot.isTorrent === true
|
||||||
|
&& isLiveTorrentControlStatus(snapshot.status);
|
||||||
|
if (!isEditableStatus(snapshot.status)
|
||||||
|
&& !((activeTab === 'transfer' && canApplyLiveNormalSpeed)
|
||||||
|
|| (activeTab === 'options' && canApplyLiveTorrentOptions))) {
|
||||||
closeAfterSaveRef.current = false;
|
closeAfterSaveRef.current = false;
|
||||||
switchAfterSaveRef.current = null;
|
switchAfterSaveRef.current = null;
|
||||||
setErrorMessage(t($ => $.properties.editingUnavailable));
|
setErrorMessage(t($ => $.properties.editingUnavailable));
|
||||||
@@ -982,13 +992,16 @@ export const PropertiesWindowApp = () => {
|
|||||||
patch.torrentTrackerInterval = encodePropertiesPatchValue(trackerInterval.trim() ? Number(trackerInterval) : undefined);
|
patch.torrentTrackerInterval = encodePropertiesPatchValue(trackerInterval.trim() ? Number(trackerInterval) : undefined);
|
||||||
}
|
}
|
||||||
} else if (activeTab === 'options' || activeTab === 'transfer') {
|
} else if (activeTab === 'options' || activeTab === 'transfer') {
|
||||||
if (downloadLimit !== (snapshot.speedLimit ?? '')) patch.speedLimit = encodePropertiesPatchValue(downloadLimit.trim() ? downloadLimit : undefined);
|
const canApplyLiveTorrentSpeed = !isTorrent || isLiveNormalSpeedStatus(snapshot.status);
|
||||||
if (activeTab === 'transfer' && !isTorrent && connections.trim()) {
|
if (downloadLimit !== (snapshot.speedLimit ?? '') && canApplyLiveTorrentSpeed) {
|
||||||
|
patch.speedLimit = encodePropertiesPatchValue(downloadLimit.trim() ? downloadLimit : undefined);
|
||||||
|
}
|
||||||
|
if (isEditableStatus(snapshot.status) && activeTab === 'transfer' && !isTorrent && connections.trim()) {
|
||||||
const nextConnections = Number(connections);
|
const nextConnections = Number(connections);
|
||||||
if (nextConnections !== snapshot.connections) patch.connections = nextConnections;
|
if (nextConnections !== snapshot.connections) patch.connections = nextConnections;
|
||||||
}
|
}
|
||||||
if (isTorrent) {
|
if (isTorrent) {
|
||||||
if (removeUnselectedFile && (!snapshot.torrentFileIndices || snapshot.torrentFileIndices.length === 0)) {
|
if (isEditableStatus(snapshot.status) && removeUnselectedFile && (!snapshot.torrentFileIndices || snapshot.torrentFileIndices.length === 0)) {
|
||||||
setErrorMessage(t($ => $.properties.torrentRemoveUnselectedFileSelectionRequired));
|
setErrorMessage(t($ => $.properties.torrentRemoveUnselectedFileSelectionRequired));
|
||||||
closeAfterSaveRef.current = false;
|
closeAfterSaveRef.current = false;
|
||||||
switchAfterSaveRef.current = null;
|
switchAfterSaveRef.current = null;
|
||||||
@@ -999,22 +1012,24 @@ export const PropertiesWindowApp = () => {
|
|||||||
patch.torrentMaxPeers = encodePropertiesPatchValue(maxPeers.trim() ? Number(maxPeers) : undefined);
|
patch.torrentMaxPeers = encodePropertiesPatchValue(maxPeers.trim() ? Number(maxPeers) : undefined);
|
||||||
}
|
}
|
||||||
if (peerSpeedLimit !== (snapshot.torrentPeerSpeedLimit ?? '')) patch.torrentPeerSpeedLimit = encodePropertiesPatchValue(peerSpeedLimit.trim() ? peerSpeedLimit : undefined);
|
if (peerSpeedLimit !== (snapshot.torrentPeerSpeedLimit ?? '')) patch.torrentPeerSpeedLimit = encodePropertiesPatchValue(peerSpeedLimit.trim() ? peerSpeedLimit : undefined);
|
||||||
if (seedTime !== String(snapshot.torrentSeedTime ?? '')) {
|
if (isEditableStatus(snapshot.status)) {
|
||||||
patch.torrentSeedTime = encodePropertiesPatchValue(seedTime.trim() ? Number(seedTime) : undefined);
|
if (seedTime !== String(snapshot.torrentSeedTime ?? '')) {
|
||||||
|
patch.torrentSeedTime = encodePropertiesPatchValue(seedTime.trim() ? Number(seedTime) : undefined);
|
||||||
|
}
|
||||||
|
if (seedRatio !== String(snapshot.torrentSeedRatio ?? '')) {
|
||||||
|
patch.torrentSeedRatio = encodePropertiesPatchValue(seedRatio.trim() ? Number(seedRatio) : undefined);
|
||||||
|
}
|
||||||
|
if (checkIntegrity !== (snapshot.torrentCheckIntegrity === true)) patch.torrentCheckIntegrity = checkIntegrity;
|
||||||
|
if (removeUnselectedFile !== (snapshot.torrentRemoveUnselectedFile === true)) patch.torrentRemoveUnselectedFile = removeUnselectedFile;
|
||||||
|
if (stopTimeout !== String(snapshot.torrentStopTimeout ?? '')) {
|
||||||
|
patch.torrentStopTimeout = encodePropertiesPatchValue(stopTimeout.trim() ? Number(stopTimeout) : undefined);
|
||||||
|
}
|
||||||
|
if (prioritizePiece !== (snapshot.torrentPrioritizePiece ?? '')) patch.torrentPrioritizePiece = encodePropertiesPatchValue(prioritizePiece.trim() || undefined);
|
||||||
|
const nextEncryptionPolicy = encryptionPolicy || undefined;
|
||||||
|
if (nextEncryptionPolicy !== snapshot.torrentEncryptionPolicy) patch.torrentEncryptionPolicy = encodePropertiesPatchValue(nextEncryptionPolicy);
|
||||||
|
const nextFileAllocation = fileAllocation || undefined;
|
||||||
|
if (nextFileAllocation !== snapshot.torrentFileAllocation) patch.torrentFileAllocation = encodePropertiesPatchValue(nextFileAllocation);
|
||||||
}
|
}
|
||||||
if (seedRatio !== String(snapshot.torrentSeedRatio ?? '')) {
|
|
||||||
patch.torrentSeedRatio = encodePropertiesPatchValue(seedRatio.trim() ? Number(seedRatio) : undefined);
|
|
||||||
}
|
|
||||||
if (checkIntegrity !== (snapshot.torrentCheckIntegrity === true)) patch.torrentCheckIntegrity = checkIntegrity;
|
|
||||||
if (removeUnselectedFile !== (snapshot.torrentRemoveUnselectedFile === true)) patch.torrentRemoveUnselectedFile = removeUnselectedFile;
|
|
||||||
if (stopTimeout !== String(snapshot.torrentStopTimeout ?? '')) {
|
|
||||||
patch.torrentStopTimeout = encodePropertiesPatchValue(stopTimeout.trim() ? Number(stopTimeout) : undefined);
|
|
||||||
}
|
|
||||||
if (prioritizePiece !== (snapshot.torrentPrioritizePiece ?? '')) patch.torrentPrioritizePiece = encodePropertiesPatchValue(prioritizePiece.trim() || undefined);
|
|
||||||
const nextEncryptionPolicy = encryptionPolicy || undefined;
|
|
||||||
if (nextEncryptionPolicy !== snapshot.torrentEncryptionPolicy) patch.torrentEncryptionPolicy = encodePropertiesPatchValue(nextEncryptionPolicy);
|
|
||||||
const nextFileAllocation = fileAllocation || undefined;
|
|
||||||
if (nextFileAllocation !== snapshot.torrentFileAllocation) patch.torrentFileAllocation = encodePropertiesPatchValue(nextFileAllocation);
|
|
||||||
}
|
}
|
||||||
} else if (activeTab === 'advanced') {
|
} else if (activeTab === 'advanced') {
|
||||||
if (isSftp && sftpHostKeyMd !== (snapshot.sftpHostKeyMd ?? '')) {
|
if (isSftp && sftpHostKeyMd !== (snapshot.sftpHostKeyMd ?? '')) {
|
||||||
@@ -1132,6 +1147,14 @@ export const PropertiesWindowApp = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const editingEnabled = pendingAction === null && isEditableStatus(snapshot.status);
|
const editingEnabled = pendingAction === null && isEditableStatus(snapshot.status);
|
||||||
|
const liveNormalSpeedEnabled = pendingAction === null
|
||||||
|
&& snapshot.isMedia !== true
|
||||||
|
&& snapshot.isTorrent !== true
|
||||||
|
&& isLiveNormalSpeedStatus(snapshot.status);
|
||||||
|
const liveTorrentOptionsEnabled = pendingAction === null
|
||||||
|
&& snapshot.isTorrent === true
|
||||||
|
&& isLiveTorrentControlStatus(snapshot.status);
|
||||||
|
const liveTorrentSpeedEnabled = liveTorrentOptionsEnabled && isLiveNormalSpeedStatus(snapshot.status);
|
||||||
const identityEditingEnabled = editingEnabled && !isTorrent && ['ready', 'staged'].includes(snapshot.status);
|
const identityEditingEnabled = editingEnabled && !isTorrent && ['ready', 'staged'].includes(snapshot.status);
|
||||||
const torrentMoveAvailable = ['paused', 'completed', 'failed'].includes(snapshot.status);
|
const torrentMoveAvailable = ['paused', 'completed', 'failed'].includes(snapshot.status);
|
||||||
const progress = getPropertiesProgress(snapshot);
|
const progress = getPropertiesProgress(snapshot);
|
||||||
@@ -1498,12 +1521,12 @@ export const PropertiesWindowApp = () => {
|
|||||||
<PropertiesField
|
<PropertiesField
|
||||||
label={t($ => $.properties.speedCap)}
|
label={t($ => $.properties.speedCap)}
|
||||||
controlId="properties-transfer-speed-cap"
|
controlId="properties-transfer-speed-cap"
|
||||||
hint={t($ => $.properties.speedLimitHint)}
|
hint={liveNormalSpeedEnabled ? t($ => $.properties.liveSpeedLimitHint) : t($ => $.properties.speedLimitHint)}
|
||||||
meta={downloadLimit.trim() ? t($ => $.properties.customPerDownload) : t($ => $.properties.usingDefault)}
|
meta={downloadLimit.trim() ? t($ => $.properties.customPerDownload) : t($ => $.properties.usingDefault)}
|
||||||
className="max-w-sm"
|
className="max-w-sm"
|
||||||
format={t($ => $.properties.inputFormat, { format: t($ => $.properties.inputFormatSpeedLimit) })}
|
format={t($ => $.properties.inputFormat, { format: t($ => $.properties.inputFormatSpeedLimit) })}
|
||||||
>
|
>
|
||||||
<input id="properties-transfer-speed-cap" className="app-control w-full" value={downloadLimit} onChange={event => { setDownloadLimit(event.target.value); setDraftTab('transfer'); }} placeholder={t($ => $.properties.inputExampleSpeedLimit)} disabled={!editingEnabled} />
|
<input id="properties-transfer-speed-cap" className="app-control w-full" value={downloadLimit} onChange={event => { setDownloadLimit(event.target.value); setDraftTab('transfer'); }} placeholder={t($ => $.properties.inputExampleSpeedLimit)} disabled={!(editingEnabled || liveNormalSpeedEnabled)} />
|
||||||
</PropertiesField>
|
</PropertiesField>
|
||||||
<PropertiesField
|
<PropertiesField
|
||||||
label={connectionControlLabel}
|
label={connectionControlLabel}
|
||||||
@@ -1524,18 +1547,18 @@ export const PropertiesWindowApp = () => {
|
|||||||
<div className="properties-option-group-heading">
|
<div className="properties-option-group-heading">
|
||||||
<div>
|
<div>
|
||||||
<h2 id="properties-options-limits-heading">{t($ => $.properties.liveTorrentPeerOptions)}</h2>
|
<h2 id="properties-options-limits-heading">{t($ => $.properties.liveTorrentPeerOptions)}</h2>
|
||||||
<p>{t($ => $.properties.speedLimitHint)}</p>
|
<p>{t($ => $.properties.liveTorrentPeerOptionsHint)}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid max-w-4xl gap-4 sm:grid-cols-2">
|
<div className="grid max-w-4xl gap-4 sm:grid-cols-2">
|
||||||
<PropertiesField
|
<PropertiesField
|
||||||
label={t($ => $.properties.speedCap)}
|
label={t($ => $.properties.speedCap)}
|
||||||
controlId="properties-options-speed-cap"
|
controlId="properties-options-speed-cap"
|
||||||
hint={t($ => $.properties.speedLimitHint)}
|
hint={liveTorrentSpeedEnabled ? t($ => $.properties.liveSpeedLimitHint) : t($ => $.properties.speedLimitHint)}
|
||||||
meta={downloadLimit.trim() ? t($ => $.properties.customPerDownload) : t($ => $.properties.usingDefault)}
|
meta={downloadLimit.trim() ? t($ => $.properties.customPerDownload) : t($ => $.properties.usingDefault)}
|
||||||
format={t($ => $.properties.inputFormat, { format: t($ => $.properties.inputFormatSpeedLimit) })}
|
format={t($ => $.properties.inputFormat, { format: t($ => $.properties.inputFormatSpeedLimit) })}
|
||||||
>
|
>
|
||||||
<input id="properties-options-speed-cap" className="app-control w-full" value={downloadLimit} onChange={event => { setDownloadLimit(event.target.value); setDraftTab('options'); }} placeholder={t($ => $.properties.inputExampleSpeedLimit)} disabled={!editingEnabled} />
|
<input id="properties-options-speed-cap" className="app-control w-full" value={downloadLimit} onChange={event => { setDownloadLimit(event.target.value); setDraftTab('options'); }} placeholder={t($ => $.properties.inputExampleSpeedLimit)} disabled={!(editingEnabled || liveTorrentSpeedEnabled)} />
|
||||||
</PropertiesField>
|
</PropertiesField>
|
||||||
<PropertiesField
|
<PropertiesField
|
||||||
label={t($ => $.properties.liveTorrentUploadLimit)}
|
label={t($ => $.properties.liveTorrentUploadLimit)}
|
||||||
@@ -1544,7 +1567,7 @@ export const PropertiesWindowApp = () => {
|
|||||||
meta={uploadLimit.trim() ? t($ => $.properties.customPerDownload) : t($ => $.properties.usingDefault)}
|
meta={uploadLimit.trim() ? t($ => $.properties.customPerDownload) : t($ => $.properties.usingDefault)}
|
||||||
format={t($ => $.properties.inputFormat, { format: t($ => $.properties.inputFormatSpeedLimit) })}
|
format={t($ => $.properties.inputFormat, { format: t($ => $.properties.inputFormatSpeedLimit) })}
|
||||||
>
|
>
|
||||||
<input id="properties-options-upload-limit" className="app-control w-full" value={uploadLimit} onChange={event => { setUploadLimit(event.target.value); setDraftTab('options'); }} placeholder={t($ => $.properties.inputExampleSpeedLimit)} disabled={!editingEnabled} />
|
<input id="properties-options-upload-limit" className="app-control w-full" value={uploadLimit} onChange={event => { setUploadLimit(event.target.value); setDraftTab('options'); }} placeholder={t($ => $.properties.liveTorrentUploadLimitPlaceholder)} disabled={!(editingEnabled || liveTorrentOptionsEnabled)} />
|
||||||
</PropertiesField>
|
</PropertiesField>
|
||||||
<PropertiesField
|
<PropertiesField
|
||||||
label={t($ => $.properties.torrentMaxPeers)}
|
label={t($ => $.properties.torrentMaxPeers)}
|
||||||
@@ -1553,7 +1576,7 @@ export const PropertiesWindowApp = () => {
|
|||||||
meta={maxPeers.trim() ? t($ => $.properties.customPerDownload) : t($ => $.properties.usingDefault)}
|
meta={maxPeers.trim() ? t($ => $.properties.customPerDownload) : t($ => $.properties.usingDefault)}
|
||||||
format={t($ => $.properties.inputFormat, { format: t($ => $.properties.inputFormatMaxPeers) })}
|
format={t($ => $.properties.inputFormat, { format: t($ => $.properties.inputFormatMaxPeers) })}
|
||||||
>
|
>
|
||||||
<input id="properties-options-max-peers" className="app-control w-full" value={maxPeers} onChange={event => { setMaxPeers(event.target.value); setDraftTab('options'); }} inputMode="numeric" placeholder={t($ => $.properties.inputExampleMaxPeers)} disabled={!editingEnabled} />
|
<input id="properties-options-max-peers" className="app-control w-full" value={maxPeers} onChange={event => { setMaxPeers(event.target.value); setDraftTab('options'); }} inputMode="numeric" placeholder={t($ => $.properties.inputExampleMaxPeers)} disabled={!(editingEnabled || liveTorrentOptionsEnabled)} />
|
||||||
</PropertiesField>
|
</PropertiesField>
|
||||||
<PropertiesField
|
<PropertiesField
|
||||||
label={t($ => $.properties.torrentPeerSpeedLimit)}
|
label={t($ => $.properties.torrentPeerSpeedLimit)}
|
||||||
@@ -1562,7 +1585,7 @@ export const PropertiesWindowApp = () => {
|
|||||||
meta={peerSpeedLimit.trim() ? t($ => $.properties.customPerDownload) : t($ => $.properties.usingDefault)}
|
meta={peerSpeedLimit.trim() ? t($ => $.properties.customPerDownload) : t($ => $.properties.usingDefault)}
|
||||||
format={t($ => $.properties.inputFormat, { format: t($ => $.properties.inputFormatSpeedLimit) })}
|
format={t($ => $.properties.inputFormat, { format: t($ => $.properties.inputFormatSpeedLimit) })}
|
||||||
>
|
>
|
||||||
<input id="properties-options-peer-speed-limit" className="app-control w-full" value={peerSpeedLimit} onChange={event => { setPeerSpeedLimit(event.target.value); setDraftTab('options'); }} placeholder={t($ => $.properties.inputExampleSpeedLimit)} disabled={!editingEnabled} />
|
<input id="properties-options-peer-speed-limit" className="app-control w-full" value={peerSpeedLimit} onChange={event => { setPeerSpeedLimit(event.target.value); setDraftTab('options'); }} placeholder={t($ => $.properties.inputExampleSpeedLimit)} disabled={!(editingEnabled || liveTorrentOptionsEnabled)} />
|
||||||
</PropertiesField>
|
</PropertiesField>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -205,6 +205,33 @@ export const copyEditablePropertiesPatch = (
|
|||||||
return safePatch;
|
return safePatch;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LIVE_PROPERTIES_STATUSES = new Set(['downloading', 'seeding', 'retrying']);
|
||||||
|
const LIVE_PROPERTIES_KEYS = new Set<keyof PropertiesPatch>([
|
||||||
|
'speedLimit',
|
||||||
|
'torrentUploadLimit',
|
||||||
|
'torrentMaxPeers',
|
||||||
|
'torrentPeerSpeedLimit',
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active transfers may change only the controls whose native consumers expose
|
||||||
|
* an in-place Aria2 mutation. Keep this check at the main-webview boundary so
|
||||||
|
* an active Properties save cannot enter applyPropertiesInternal and detach a
|
||||||
|
* live lifecycle before being rejected by its status gate.
|
||||||
|
*/
|
||||||
|
export const isLivePropertiesPatch = (
|
||||||
|
item: Pick<DownloadItem, 'isMedia' | 'isTorrent' | 'status'>,
|
||||||
|
patch: Partial<DownloadItem>,
|
||||||
|
): boolean => {
|
||||||
|
if (!LIVE_PROPERTIES_STATUSES.has(item.status) || item.isMedia === true) return false;
|
||||||
|
const keys = Object.keys(patch) as Array<keyof PropertiesPatch>;
|
||||||
|
if (item.isTorrent !== true) {
|
||||||
|
return keys.every(key => key === 'speedLimit' && ['downloading', 'retrying'].includes(item.status));
|
||||||
|
}
|
||||||
|
return keys.every(key => key !== 'speedLimit' || ['downloading', 'retrying'].includes(item.status))
|
||||||
|
&& keys.every(key => LIVE_PROPERTIES_KEYS.has(key));
|
||||||
|
};
|
||||||
|
|
||||||
export const PropertiesWindowBridgeHost = () => {
|
export const PropertiesWindowBridgeHost = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const mainWindowTarget = propertiesWindowEventTarget(getCurrentWindow().label);
|
const mainWindowTarget = propertiesWindowEventTarget(getCurrentWindow().label);
|
||||||
@@ -435,7 +462,41 @@ export const PropertiesWindowBridgeHost = () => {
|
|||||||
throw new Error('Generic connection settings are not available for Torrent downloads');
|
throw new Error('Generic connection settings are not available for Torrent downloads');
|
||||||
}
|
}
|
||||||
await assertCurrentAction(request);
|
await assertCurrentAction(request);
|
||||||
await store.applyProperties(request.downloadId, safePatch);
|
if (LIVE_PROPERTIES_STATUSES.has(item.status)) {
|
||||||
|
if (!isLivePropertiesPatch(item, safePatch)) {
|
||||||
|
throw new Error(i18n.t($ => $.downloadTable.transferActive));
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(safePatch, 'speedLimit')) {
|
||||||
|
await store.setDownloadSpeedLimit(
|
||||||
|
request.downloadId,
|
||||||
|
safePatch.speedLimit ?? null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (item.isTorrent === true
|
||||||
|
&& Object.prototype.hasOwnProperty.call(safePatch, 'torrentUploadLimit')) {
|
||||||
|
await store.setTorrentUploadLimit(
|
||||||
|
request.downloadId,
|
||||||
|
safePatch.torrentUploadLimit ?? null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (item.isTorrent === true
|
||||||
|
&& (Object.prototype.hasOwnProperty.call(safePatch, 'torrentMaxPeers')
|
||||||
|
|| Object.prototype.hasOwnProperty.call(safePatch, 'torrentPeerSpeedLimit'))) {
|
||||||
|
const maxPeers = Object.prototype.hasOwnProperty.call(safePatch, 'torrentMaxPeers')
|
||||||
|
? safePatch.torrentMaxPeers == null ? null : String(safePatch.torrentMaxPeers)
|
||||||
|
: item.torrentMaxPeers == null ? null : String(item.torrentMaxPeers);
|
||||||
|
const peerSpeedLimit = Object.prototype.hasOwnProperty.call(safePatch, 'torrentPeerSpeedLimit')
|
||||||
|
? safePatch.torrentPeerSpeedLimit ?? null
|
||||||
|
: item.torrentPeerSpeedLimit ?? null;
|
||||||
|
await store.setTorrentPeerOptions(
|
||||||
|
request.downloadId,
|
||||||
|
maxPeers,
|
||||||
|
peerSpeedLimit,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await store.applyProperties(request.downloadId, safePatch);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'set-torrent-file-selection': {
|
case 'set-torrent-file-selection': {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
sendPropertiesSnapshot,
|
sendPropertiesSnapshot,
|
||||||
shouldAcceptPropertiesActionRequest,
|
shouldAcceptPropertiesActionRequest,
|
||||||
} from './propertiesBridge';
|
} from './propertiesBridge';
|
||||||
import { copyEditablePropertiesPatch } from './components/PropertiesWindowBridgeHost';
|
import { copyEditablePropertiesPatch, isLivePropertiesPatch } from './components/PropertiesWindowBridgeHost';
|
||||||
|
|
||||||
describe('Properties window bridge', () => {
|
describe('Properties window bridge', () => {
|
||||||
it('keeps optional override resets explicit across the JSON IPC boundary', () => {
|
it('keeps optional override resets explicit across the JSON IPC boundary', () => {
|
||||||
@@ -415,6 +415,41 @@ describe('Properties window bridge', () => {
|
|||||||
})).toThrow('read-only');
|
})).toThrow('read-only');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows only native live controls for active Properties saves', () => {
|
||||||
|
expect(isLivePropertiesPatch(
|
||||||
|
{ isMedia: false, isTorrent: false, status: 'downloading' },
|
||||||
|
{ speedLimit: '2M' },
|
||||||
|
)).toBe(true);
|
||||||
|
expect(isLivePropertiesPatch(
|
||||||
|
{ isMedia: false, isTorrent: true, status: 'seeding' },
|
||||||
|
{ torrentUploadLimit: '1M', torrentMaxPeers: 120, torrentPeerSpeedLimit: '256K' },
|
||||||
|
)).toBe(true);
|
||||||
|
expect(isLivePropertiesPatch(
|
||||||
|
{ isMedia: false, isTorrent: true, status: 'seeding' },
|
||||||
|
{ speedLimit: '2M' },
|
||||||
|
)).toBe(false);
|
||||||
|
expect(isLivePropertiesPatch(
|
||||||
|
{ isMedia: false, isTorrent: true, status: 'verifying' },
|
||||||
|
{ torrentUploadLimit: '1M' },
|
||||||
|
)).toBe(false);
|
||||||
|
expect(isLivePropertiesPatch(
|
||||||
|
{ isMedia: false, isTorrent: true, status: 'waitingToSeed' },
|
||||||
|
{ torrentMaxPeers: 120 },
|
||||||
|
)).toBe(false);
|
||||||
|
expect(isLivePropertiesPatch(
|
||||||
|
{ isMedia: false, isTorrent: true, status: 'downloading' },
|
||||||
|
{ torrentTrackers: 'https://tracker.example/announce' },
|
||||||
|
)).toBe(false);
|
||||||
|
expect(isLivePropertiesPatch(
|
||||||
|
{ isMedia: true, isTorrent: false, status: 'downloading' },
|
||||||
|
{ speedLimit: '2M' },
|
||||||
|
)).toBe(false);
|
||||||
|
expect(isLivePropertiesPatch(
|
||||||
|
{ isMedia: false, isTorrent: false, status: 'paused' },
|
||||||
|
{ speedLimit: '2M' },
|
||||||
|
)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('keeps Torrent peer-cap telemetry distinct from generic connections', () => {
|
it('keeps Torrent peer-cap telemetry distinct from generic connections', () => {
|
||||||
expect(propertiesTorrentPeerLimit(undefined)).toBe(55);
|
expect(propertiesTorrentPeerLimit(undefined)).toBe(55);
|
||||||
expect(propertiesTorrentPeerLimit(120)).toBe(120);
|
expect(propertiesTorrentPeerLimit(120)).toBe(120);
|
||||||
|
|||||||
Reference in New Issue
Block a user