Add configurable download sound and improve cancellation retries

This commit is contained in:
Dariusz L
2026-10-05 19:49:23 +02:00
parent 561b57f2e6
commit 22f4cbcdee
8 changed files with 276 additions and 14 deletions
+58
View File
@@ -0,0 +1,58 @@
import test from 'node:test';
import assert from 'node:assert/strict';
test('success sound unlocks on interaction and schedules the supplied chord', async () => {
const listeners = {};
const oscillators = [];
const gains = [];
globalThis.document = { addEventListener: (name, callback) => { listeners[name] = callback; } };
globalThis.AudioContext = class {
state = 'suspended';
currentTime = 10;
destination = {};
async resume() { this.state = 'running'; }
createOscillator() {
const oscillator = {
frequency: { setValueAtTime(value, time) { this.value = value; this.time = time; } }, connect() {}, disconnect() {},
start(time) { this.started = time; },
stop(time) { this.stopped = time; },
};
oscillators.push(oscillator);
return oscillator;
}
createGain() {
const events = [];
gains.push(events);
return { gain: {
setValueAtTime(value, time) { events.push(['set', value, time]); },
exponentialRampToValueAtTime(value, time) { events.push(['exponential', value, time]); },
}, connect() {}, disconnect() {} };
}
};
try {
const { playDownloadSuccessSound } = await import('../web/utils/download_sound.js');
playDownloadSuccessSound();
assert.equal(oscillators.length, 0);
listeners.pointerdown();
playDownloadSuccessSound();
assert.deepEqual(oscillators.map(item => item.frequency.value), [523, 659, 784, 1046]);
assert.deepEqual(oscillators.map(item => item.started), [10, 10.15, 10.30, 10.47]);
oscillators.forEach((item, index) => {
const duration = [0.27, 0.33, 0.48, 0.40][index];
assert.ok(Math.abs(item.stopped - item.started - duration - 0.02) < 1e-10);
assert.deepEqual(gains[index], [
['set', 0.0001, item.started],
['exponential', [0.055, 0.060, 0.055, 0.022][index], item.started + 0.012],
['exponential', 0.0001, item.started + duration],
]);
item.onended();
});
const { safeStorage } = await import('../web/resolver/utils/html_utils.js');
safeStorage.setItem('ModelResolver.downloadSoundEnabled', 'false');
playDownloadSuccessSound();
assert.equal(oscillators.length, 4, 'disabled sound must not schedule more notes');
} finally {
delete globalThis.document;
delete globalThis.AudioContext;
}
});
+74
View File
@@ -1,4 +1,78 @@
import importlib
import threading
from types import SimpleNamespace
from unittest.mock import Mock
import pytest
def test_cancel_does_not_wait_for_http_headers_and_closes_late_response():
orchestrator = importlib.import_module("core.download.orchestrator")
entered = threading.Event()
release = threading.Event()
closed = threading.Event()
cancelled = threading.Event()
response = SimpleNamespace(close=closed.set)
def request(*args, **kwargs):
entered.set()
assert release.wait(2)
return response, "https://example.com/model", {}
facade = SimpleNamespace(request_public_url=request, cancelled_downloads=set())
def run():
with pytest.raises(orchestrator.DownloadCancelled):
orchestrator.request_download_response(facade, "download", "https://example.com/model")
cancelled.set()
worker = threading.Thread(target=run)
worker.start()
try:
assert entered.wait(1)
facade.cancelled_downloads.add("download")
assert cancelled.wait(1), "Cancellation must finish before HTTP headers arrive"
assert not closed.is_set()
finally:
release.set()
worker.join(2)
assert closed.wait(1)
def test_download_response_preserves_result_and_network_errors():
orchestrator = importlib.import_module("core.download.orchestrator")
result = (Mock(), "https://example.com/model", {})
facade = SimpleNamespace(request_public_url=Mock(return_value=result), cancelled_downloads=set())
assert orchestrator.request_download_response(facade, "download", result[1]) == result
facade.request_public_url.side_effect = ValueError("network error")
with pytest.raises(ValueError, match="network error"):
orchestrator.request_download_response(facade, "download", result[1])
def test_cancel_is_idempotent_and_does_not_revive_terminal_downloads():
state_module = importlib.import_module("core.download.state")
progress = {"download": {"status": "starting", "speed": 5}}
state = SimpleNamespace(
download_progress=progress,
download_lock=threading.Lock(),
cancelled_downloads=set(),
aria2_lock=threading.Lock(),
aria2_desired_states={},
aria2_transfers={},
xet_transfers_lock=threading.Lock(),
xet_transfers={},
)
assert state_module.cancel_download("download", state)
assert progress["download"] == {"status": "cancelling", "speed": 0}
assert "download" in state.cancelled_downloads
for status in ("cancelled", "completed", "error"):
progress["download"]["status"] = status
state.cancelled_downloads.clear()
assert state_module.cancel_download("download", state)
assert progress["download"]["status"] == status
assert not state.cancelled_downloads
assert state_module.cancel_download("unknown", state)
assert "unknown" not in state.cancelled_downloads
def test_initial_download_progress_preserves_the_shared_contract():