chore: satisfy CI fmt/clippy gates; make the release publish step idempotent
Docker / publish (push) Canceled after 0s
Release / crates-io (push) Canceled after 0s
Release / release (macos-14, aarch64-apple-darwin) (push) Canceled after 0s
Release / release (ubuntu-latest, x86_64-unknown-linux-gnu) (push) Canceled after 0s
Release / release (windows-latest, x86_64-pc-windows-msvc) (push) Canceled after 0s

`cargo fmt --all -- --check` and `cargo clippy --all-targets -- -D warnings` are
CI gates and both were failing on this release's code.

- rustfmt across the files touched by the release
- `.split('/').last()` walks the whole iterator on a DoubleEndedIterator; use
  `.next_back()` (4 sites in the setup/featured + delete handlers)
- `set_progress` had two identical branches; the intent is "take the first
  reading, then only move forward", which is one condition

release.yml published unconditionally on a version tag, so a version already on
crates.io (released by hand, or a re-run of the job) failed the workflow — a
version can never be re-uploaded. It now checks the registry and skips instead.
This commit is contained in:
infosave2007
2026-08-06 18:18:21 +03:00
parent 9ec8861464
commit 32ce2f7ed7
5 changed files with 193 additions and 49 deletions
+81 -24
View File
@@ -1399,7 +1399,16 @@ async fn setup_complete(
let _ = std::fs::create_dir_all(dir);
}
let lang = b.lang.unwrap_or_else(|| "en".into());
let _ = std::fs::write(&marker, format!("done lang={lang} ts={}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs()));
let _ = std::fs::write(
&marker,
format!(
"done lang={lang} ts={}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
),
);
let mut registered: Vec<String> = Vec::new();
if b.register.unwrap_or(true) {
@@ -1473,7 +1482,9 @@ async fn setup_featured(State(state): State<SharedState>) -> ApiResult {
}
}
}
if ids.is_empty() { return ok(json!({ "models": fallback })); }
if ids.is_empty() {
return ok(json!({ "models": fallback }));
}
// fetch per-variant sizes in parallel (tree API, 10s each)
let token_owned = token.map(|s| s.to_string());
let variant_lists: Vec<(String, Vec<(String, u64)>)> =
@@ -1512,7 +1523,9 @@ async fn setup_featured(State(state): State<SharedState>) -> ApiResult {
if let Some(arr) = models.as_array() {
for m in arr {
let id = m["id"].as_str().unwrap_or("").to_string();
if !id.to_ascii_lowercase().contains("cmf") { continue; }
if !id.to_ascii_lowercase().contains("cmf") {
continue;
}
let likes = m["likes"].as_u64().unwrap_or(0);
let vars = variant_map.get(&id);
// extract size token from id
@@ -1538,7 +1551,11 @@ async fn setup_featured(State(state): State<SharedState>) -> ApiResult {
.and_then(|vs| vs.first().map(|(n, _)| n.as_str()))
.unwrap_or(&id)
.to_ascii_lowercase();
let quant = if quant_src.contains("q1t") || quant_src.contains("2bit") || lower.contains("q1t") || lower.contains("2bit") {
let quant = if quant_src.contains("q1t")
|| quant_src.contains("2bit")
|| lower.contains("q1t")
|| lower.contains("2bit")
{
"Q1T"
} else if quant_src.contains("q2tp") || lower.contains("q2tp") {
"Q2TP"
@@ -1554,7 +1571,11 @@ async fn setup_featured(State(state): State<SharedState>) -> ApiResult {
""
};
let mut badge = if quant.is_empty() || size_token == "CMF" {
if size_token == "CMF" { "CMF · ready".to_string() } else { size_token.to_string() }
if size_token == "CMF" {
"CMF · ready".to_string()
} else {
size_token.to_string()
}
} else {
format!("{size_token} · {quant}")
};
@@ -1568,7 +1589,11 @@ async fn setup_featured(State(state): State<SharedState>) -> ApiResult {
if vs.len() == 1 {
let (name, sz) = &vs[0];
let s = crate::import::format_bytes(*sz);
(Some(s.clone()), Some(*sz), json!([{"name": name, "size": s, "size_bytes": sz}]))
(
Some(s.clone()),
Some(*sz),
json!([{"name": name, "size": s, "size_bytes": sz}]),
)
} else {
// dedupe same-size variants (e.g. q2tp v2/v3 both 112GB → keep latest v3)
let mut seen_sz = std::collections::HashSet::new();
@@ -1576,22 +1601,31 @@ async fn setup_featured(State(state): State<SharedState>) -> ApiResult {
// iterate reversed to keep latest (v3) over v2 when same size
for (n, sz) in vs.iter().rev() {
let s = crate::import::format_bytes(*sz);
if seen_sz.insert(s) { uniq_vs.push((n.clone(), *sz)); }
if seen_sz.insert(s) {
uniq_vs.push((n.clone(), *sz));
}
}
uniq_vs.reverse();
let parts: Vec<String> = uniq_vs.iter().map(|(_, sz)| crate::import::format_bytes(*sz)).collect();
let parts: Vec<String> = uniq_vs
.iter()
.map(|(_, sz)| crate::import::format_bytes(*sz))
.collect();
let max_sz = uniq_vs.iter().map(|(_, sz)| *sz).max();
let vars_json: Vec<Value> = uniq_vs.iter().map(|(n, sz)| json!({"name": n, "size": crate::import::format_bytes(*sz), "size_bytes": sz})).collect();
(Some(parts.join(" / ")), max_sz, json!(vars_json))
}
} else if let Some((s, sz)) = fallback_map.get(&id) {
(Some(s.clone()), Some(*sz), json!([{"name": id.split('/').last().unwrap_or(&id), "size": s, "size_bytes": sz}]))
(
Some(s.clone()),
Some(*sz),
json!([{"name": id.split('/').next_back().unwrap_or(&id), "size": s, "size_bytes": sz}]),
)
} else {
(None::<String>, None::<u64>, Value::Null)
};
// check if already downloaded locally (case-insensitive, handles sharded dirs)
let models_dir = live.cfg.cmf.models_dir.clone();
let base = crate::import::sanitize_for_id(id.split('/').last().unwrap_or(&id));
let base = crate::import::sanitize_for_id(id.split('/').next_back().unwrap_or(&id));
let mut downloaded = false;
let mut downloaded_variants: Vec<Value> = Vec::new();
if let Some(vs) = variants_json.as_array() {
@@ -1615,7 +1649,12 @@ async fn setup_featured(State(state): State<SharedState>) -> ApiResult {
if local_sz == 0 && entry.path().is_dir() {
local_sz = std::fs::read_dir(entry.path())
.ok()
.map(|r| r.filter_map(|e| e.ok()).filter_map(|e| e.metadata().ok()).map(|m| m.len()).sum())
.map(|r| {
r.filter_map(|e| e.ok())
.filter_map(|e| e.metadata().ok())
.map(|m| m.len())
.sum()
})
.unwrap_or(0);
}
break;
@@ -1627,18 +1666,23 @@ async fn setup_featured(State(state): State<SharedState>) -> ApiResult {
let p = std::path::Path::new(&models_dir).join(format!("{vbase}.cmf"));
if p.exists() || p.is_dir() {
exists = true;
local_sz = std::fs::metadata(&p)
.map(|m| m.len())
.unwrap_or_else(|_| {
local_sz =
std::fs::metadata(&p).map(|m| m.len()).unwrap_or_else(|_| {
std::fs::read_dir(&p)
.ok()
.map(|r| r.filter_map(|e| e.ok()).filter_map(|e| e.metadata().ok()).map(|m| m.len()).sum())
.map(|r| {
r.filter_map(|e| e.ok())
.filter_map(|e| e.metadata().ok())
.map(|m| m.len())
.sum()
})
.unwrap_or(0)
});
}
}
let remote_sz = v["size_bytes"].as_u64().unwrap_or(0);
let is_complete = exists && local_sz != 0 && remote_sz != 0 && local_sz == remote_sz;
let is_complete =
exists && local_sz != 0 && remote_sz != 0 && local_sz == remote_sz;
// downloaded = only when complete (not partial)
downloaded = downloaded || is_complete;
let mut vv = v.clone();
@@ -1648,8 +1692,7 @@ async fn setup_featured(State(state): State<SharedState>) -> ApiResult {
// the job that produces it — the UI passes both back verbatim so
// download / progress / delete can never disagree on the file.
vv["local_name"] = json!(vbase);
vv["job"] =
json!(crate::import::gen_id(&format!("{id}::{vname}")));
vv["job"] = json!(crate::import::gen_id(&format!("{id}::{vname}")));
if exists && !is_complete && local_sz != 0 {
// partial or outdated — show as update available if not currently downloading
// we don't have job state here, so just mark update if sizes differ
@@ -1660,11 +1703,17 @@ async fn setup_featured(State(state): State<SharedState>) -> ApiResult {
downloaded_variants.push(vv);
}
}
let final_variants = if downloaded_variants.is_empty() { variants_json } else { json!(downloaded_variants) };
let any_update = downloaded_variants.iter().any(|v| v["update_available"].as_bool().unwrap_or(false));
let final_variants = if downloaded_variants.is_empty() {
variants_json
} else {
json!(downloaded_variants)
};
let any_update = downloaded_variants
.iter()
.any(|v| v["update_available"].as_bool().unwrap_or(false));
out.push(json!({
"id": id,
"label": id.split('/').last().unwrap_or(&id),
"label": id.split('/').next_back().unwrap_or(&id),
"local_name": base,
"badge": badge,
"desc": m["pipeline_tag"].as_str().unwrap_or("text-generation"),
@@ -1678,7 +1727,11 @@ async fn setup_featured(State(state): State<SharedState>) -> ApiResult {
}));
}
}
if out.is_empty() { fallback } else { json!(out) }
if out.is_empty() {
fallback
} else {
json!(out)
}
};
ok(json!({ "models": arr }))
}
@@ -1747,8 +1800,12 @@ async fn delete_setup_file(
}
let live = state.live();
let models_dir = live.cfg.cmf.models_dir.clone();
let base = crate::import::sanitize_for_id(repo.split('/').last().unwrap_or(repo));
let variant = q.variant.as_deref().map(|s| s.trim()).filter(|s| !s.is_empty());
let base = crate::import::sanitize_for_id(repo.split('/').next_back().unwrap_or(repo));
let variant = q
.variant
.as_deref()
.map(|s| s.trim())
.filter(|s| !s.is_empty());
// Exact stem wins; sanitizing it also keeps the delete inside models_dir.
let vbase = match q.name.as_deref().map(str::trim).filter(|s| !s.is_empty()) {
Some(n) => crate::import::sanitize_for_id(n),