Téléchargements de modèles : pause et reprise

Repris d'AJEAN 9c7423bf (v0.18.1) : POST /api/models/download/pause
{"filename", "pause"} met en pause ou reprend un téléchargement en cours.
La pause coupe les connexions (rien ne reste ouvert vers le CDN), garde le
fichier partiel, et chaque morceau attend la reprise puis repart de l'octet
où il s'était arrêté par une requête de plage ; une pause ne consomme pas
d'essai. /status expose « paused », la vitesse publiée tombe à zéro.

Façade Rust (downloads.rs) : même route, même porte (watch), mêmes
réponses ; la route est ajoutée à celles que le Go remet à la façade quand
LOKI_MODELS=rust. Tests portés de part et d'autre (pause puis reprise de
bout en bout, fichier identique à la source).

UI : bouton « Mettre en pause » / « Reprendre » à côté de « Annuler le
téléchargement », « en pause » dans la ligne de progression.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-16 15:16:39 +02:00
1 parent 615e58dd2f
commit 0ef2ed72f5
11 files changed
+580 -139

No files matched your search

+59
View File
@@ -260,6 +260,13 @@ async fn wait_finished(dl: &Downloads, name: &str) -> DlState {
panic!("téléchargement {name} jamais terminé : {:?}", dl.status());
}
fn state_of(dl: &Downloads, name: &str) -> DlState {
dl.status()
.into_iter()
.find(|s| s.filename == name)
.unwrap()
}
fn models(home: &Path) -> PathBuf {
home.join("models")
}
@@ -537,6 +544,58 @@ async fn annulation_efface_le_part() {
assert_eq!(entries(&models(tmp.path())), Vec::<String>::new());
}
// `TestRunDownloadPauseResume` : la pause coupe le flux et garde ce qui est
// écrit ; la reprise repart de là par une plage et termine un fichier
// identique à la source.
#[tokio::test]
async fn pause_puis_reprise() {
let (addr, hf) = fake_hf().await;
let data = payload(8 << 20, 5);
hf.files
.lock()
.unwrap()
.insert("/m.gguf".into(), Arc::new(data.clone()));
*hf.slow.lock().unwrap() = true;
let tmp = tempfile::tempdir().unwrap();
let dl = Arc::new(Downloads::new());
let (code, _) = dl.start(
env(),
&dirs_at(tmp.path()),
&format!("http://{addr}/m.gguf"),
"",
);
assert_eq!(code, StatusCode::OK);
assert!(!dl.pause("autre.gguf", true));
tokio::time::sleep(Duration::from_millis(300)).await;
assert!(dl.pause("m.gguf", true));
tokio::time::sleep(Duration::from_millis(1500)).await;
let a = state_of(&dl, "m.gguf");
tokio::time::sleep(Duration::from_millis(1500)).await;
let b = state_of(&dl, "m.gguf");
assert!(
a.paused && !b.finished && a.done == b.done && a.done > 0 && b.speed == 0,
"pause inopérante : {a:?} puis {b:?}"
);
let before = hf.log.lock().unwrap().len();
assert!(dl.pause("m.gguf", false));
let end = wait_finished(&dl, "m.gguf").await;
assert!(
end.err.is_empty() && !end.canceled && !end.paused,
"{end:?}"
);
let got = std::fs::read(models(tmp.path()).join("m.gguf")).unwrap();
assert!(got == data, "fichier différent de la source après reprise");
// La reprise demande la suite, pas le début.
let log = hf.log.lock().unwrap().clone();
let resumed = log[before..]
.iter()
.filter_map(|l| l.strip_prefix("/m.gguf bytes="))
.any(|r| !r.starts_with("0-"));
assert!(resumed, "{log:?}");
// Terminé : plus rien à mettre en pause.
assert!(!dl.pause("m.gguf", true));
}
#[tokio::test]
async fn refus_avant_de_lancer() {
let tmp = tempfile::tempdir().unwrap();
+114 -114
View File
@@ -231,8 +231,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
},
@@ -304,7 +304,7 @@
"max_tokens": 300,
"messages": [
{
"content": "<8102 octets f67301f37ff4>",
"content": "<8144 octets 5c98ee6b6194>",
"role": "user"
}
],
@@ -335,8 +335,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -358,8 +358,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -387,8 +387,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -410,8 +410,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -433,8 +433,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -542,8 +542,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
},
@@ -615,7 +615,7 @@
"max_tokens": 300,
"messages": [
{
"content": "<8102 octets f67301f37ff4>",
"content": "<8144 octets 5c98ee6b6194>",
"role": "user"
}
],
@@ -855,8 +855,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -947,8 +947,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -993,8 +993,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
},
@@ -1066,7 +1066,7 @@
"max_tokens": 300,
"messages": [
{
"content": "<8102 octets f67301f37ff4>",
"content": "<8144 octets 5c98ee6b6194>",
"role": "user"
}
],
@@ -1103,8 +1103,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -1132,8 +1132,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -1241,8 +1241,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
},
@@ -1314,7 +1314,7 @@
"max_tokens": 300,
"messages": [
{
"content": "<8102 octets f67301f37ff4>",
"content": "<8144 octets 5c98ee6b6194>",
"role": "user"
}
],
@@ -1554,8 +1554,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -1646,8 +1646,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -1686,8 +1686,8 @@
"predicted_ms": 81920,
"predicted_n": 4096,
"predicted_per_second": 50,
"prompt_ms": 16403,
"prompt_n": 32806,
"prompt_ms": 16402,
"prompt_n": 32804,
"prompt_per_second": 2000,
"protocol": 2
},
@@ -1759,7 +1759,7 @@
"max_tokens": 4096,
"messages": [
{
"content": "<131211 octets 96987502f054>",
"content": "<131201 octets e3226a117903>",
"role": "user"
}
],
@@ -1790,8 +1790,8 @@
"predicted_ms": 81920,
"predicted_n": 4096,
"predicted_per_second": 50,
"prompt_ms": 16403,
"prompt_n": 32806,
"prompt_ms": 16402,
"prompt_n": 32804,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -1813,8 +1813,8 @@
"predicted_ms": 81920,
"predicted_n": 4096,
"predicted_per_second": 50,
"prompt_ms": 16403,
"prompt_n": 32806,
"prompt_ms": 16402,
"prompt_n": 32804,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -2020,8 +2020,8 @@
"predicted_ms": 320,
"predicted_n": 16,
"predicted_per_second": 50,
"prompt_ms": 469.5,
"prompt_n": 939,
"prompt_ms": 470,
"prompt_n": 940,
"prompt_per_second": 2000,
"protocol": 2
},
@@ -2093,7 +2093,7 @@
"max_tokens": 16,
"messages": [
{
"content": "<3743 octets 302346ae5a44>",
"content": "<3745 octets 55a9fdff5d02>",
"role": "user"
}
],
@@ -2124,8 +2124,8 @@
"predicted_ms": 320,
"predicted_n": 16,
"predicted_per_second": 50,
"prompt_ms": 469.5,
"prompt_n": 939,
"prompt_ms": 470,
"prompt_n": 940,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -2147,8 +2147,8 @@
"predicted_ms": 320,
"predicted_n": 16,
"predicted_per_second": 50,
"prompt_ms": 469.5,
"prompt_n": 939,
"prompt_ms": 470,
"prompt_n": 940,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -2187,8 +2187,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
},
@@ -2260,7 +2260,7 @@
"max_tokens": 300,
"messages": [
{
"content": "<8102 octets f67301f37ff4>",
"content": "<8144 octets 5c98ee6b6194>",
"role": "user"
}
],
@@ -2291,8 +2291,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -2314,8 +2314,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -2420,8 +2420,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -2443,8 +2443,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -2541,7 +2541,7 @@
"max_tokens": 300,
"messages": [
{
"content": "<8102 octets f67301f37ff4>",
"content": "<8144 octets 5c98ee6b6194>",
"role": "user"
}
],
@@ -2572,8 +2572,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -2595,8 +2595,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -2693,7 +2693,7 @@
"max_tokens": 300,
"messages": [
{
"content": "<8102 octets f67301f37ff4>",
"content": "<8144 octets 5c98ee6b6194>",
"role": "user"
}
],
@@ -2747,8 +2747,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -2770,8 +2770,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -2868,7 +2868,7 @@
"max_tokens": 300,
"messages": [
{
"content": "<8102 octets f67301f37ff4>",
"content": "<8144 octets 5c98ee6b6194>",
"role": "user"
}
],
@@ -2992,8 +2992,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -3015,8 +3015,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -3121,8 +3121,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -3144,8 +3144,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -3189,8 +3189,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
},
@@ -3287,7 +3287,7 @@
"max_tokens": 300,
"messages": [
{
"content": "<8102 octets f67301f37ff4>",
"content": "<8144 octets 5c98ee6b6194>",
"role": "user"
}
],
@@ -3319,8 +3319,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -3342,8 +3342,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -3383,8 +3383,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
},
@@ -3457,7 +3457,7 @@
"max_tokens": 300,
"messages": [
{
"content": "<8102 octets f67301f37ff4>",
"content": "<8144 octets 5c98ee6b6194>",
"role": "user"
}
],
@@ -3489,8 +3489,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -3512,8 +3512,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -3579,8 +3579,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
},
@@ -3652,7 +3652,7 @@
"max_tokens": 300,
"messages": [
{
"content": "<8102 octets f67301f37ff4>",
"content": "<8144 octets 5c98ee6b6194>",
"role": "user"
}
],
@@ -3737,8 +3737,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -3760,8 +3760,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -3867,7 +3867,7 @@
"max_tokens": 300,
"messages": [
{
"content": "<8102 octets f67301f37ff4>",
"content": "<8144 octets 5c98ee6b6194>",
"role": "user"
}
],
@@ -3952,8 +3952,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -3975,8 +3975,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -4034,8 +4034,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -4057,8 +4057,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -4089,8 +4089,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -4112,8 +4112,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}
@@ -4135,8 +4135,8 @@
"predicted_ms": 6000,
"predicted_n": 300,
"predicted_per_second": 50,
"prompt_ms": 1014.5,
"prompt_n": 2029,
"prompt_ms": 1020,
"prompt_n": 2040,
"prompt_per_second": 2000,
"protocol": 2
}