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
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:
+81
-24
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user