|
|
@@ -29,6 +29,26 @@ pub fn cache_base() -> PathBuf {
|
|
|
.join(".cache/sm121/recipe-repos")
|
|
|
}
|
|
|
|
|
|
+/// Every cloned recipe repo currently on disk (`<cache>/<sha>/`), sorted.
|
|
|
+///
|
|
|
+/// The recipe search path must not depend on *when* a clone happened: the
|
|
|
+/// dashboard is a long-lived process, and a repo cloned after startup (first
|
|
|
+/// boot's sync failed, a URL changed, a manual ⟳ sync) would otherwise stay
|
|
|
+/// invisible until a restart. Callers merge these into the recipe sources.
|
|
|
+pub fn cached_roots() -> Vec<PathBuf> {
|
|
|
+ let base = cache_base();
|
|
|
+ let Ok(entries) = std::fs::read_dir(&base) else {
|
|
|
+ return Vec::new();
|
|
|
+ };
|
|
|
+ let mut roots: Vec<PathBuf> = entries
|
|
|
+ .flatten()
|
|
|
+ .map(|e| e.path())
|
|
|
+ .filter(|p| p.join("recipes").is_dir())
|
|
|
+ .collect();
|
|
|
+ roots.sort();
|
|
|
+ roots
|
|
|
+}
|
|
|
+
|
|
|
/// `sha256(url)` hex (full; callers truncate for dir names). Stable per
|
|
|
/// URL — the cache dir is keyed on the repo URL, not the hostname.
|
|
|
pub(crate) fn url_hash(url: &str) -> String {
|
|
|
@@ -208,13 +228,47 @@ pub fn sync_repos(
|
|
|
registry: &ReposRegistry,
|
|
|
force: bool,
|
|
|
) -> Vec<PathBuf> {
|
|
|
- let mut roots = Vec::new();
|
|
|
+ sync_repos_report(repos, base, registry, force)
|
|
|
+ .into_iter()
|
|
|
+ .filter_map(|o| o.root)
|
|
|
+ .collect()
|
|
|
+}
|
|
|
+
|
|
|
+/// Why one configured repo did or did not land in the cache — the dashboard
|
|
|
+/// shows this verbatim, because a silent "nothing appeared" is
|
|
|
+/// indistinguishable from a bug in the recipes list.
|
|
|
+#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
+pub struct RepoOutcome {
|
|
|
+ pub url: String,
|
|
|
+ /// `synced` | `archived` | `failed`
|
|
|
+ pub state: &'static str,
|
|
|
+ pub root: Option<PathBuf>,
|
|
|
+ pub recipes: Option<usize>,
|
|
|
+ pub error: Option<String>,
|
|
|
+}
|
|
|
+
|
|
|
+/// Like [`sync_repos`], but keeps the per-repo outcome instead of only the
|
|
|
+/// reachable roots.
|
|
|
+pub fn sync_repos_report(
|
|
|
+ repos: &[RecipeRepo],
|
|
|
+ base: &Path,
|
|
|
+ registry: &ReposRegistry,
|
|
|
+ force: bool,
|
|
|
+) -> Vec<RepoOutcome> {
|
|
|
+ let mut out = Vec::new();
|
|
|
for repo in repos {
|
|
|
if registry.is_archived(&repo.url) {
|
|
|
info!(
|
|
|
"{}",
|
|
|
fluent_i18n::t!("recipe-repo-archived-skip", { "url" => &repo.url })
|
|
|
);
|
|
|
+ out.push(RepoOutcome {
|
|
|
+ url: repo.url.clone(),
|
|
|
+ state: "archived",
|
|
|
+ root: None,
|
|
|
+ recipes: None,
|
|
|
+ error: None,
|
|
|
+ });
|
|
|
continue;
|
|
|
}
|
|
|
let dir = repo_cache_dir(base, &repo.url);
|
|
|
@@ -224,23 +278,45 @@ pub fn sync_repos(
|
|
|
"{}",
|
|
|
fluent_i18n::t!("recipe-repo-synced", { "url" => &repo.url, "dir" => dir.display().to_string() })
|
|
|
);
|
|
|
- roots.push(dir);
|
|
|
+ out.push(RepoOutcome {
|
|
|
+ url: repo.url.clone(),
|
|
|
+ state: "synced",
|
|
|
+ recipes: recipe_count(&dir),
|
|
|
+ root: Some(dir),
|
|
|
+ error: None,
|
|
|
+ });
|
|
|
}
|
|
|
Err(e) => {
|
|
|
warn!(
|
|
|
"{}",
|
|
|
fluent_i18n::t!("recipe-repo-unreachable", { "url" => &repo.url, "err" => e.to_string() })
|
|
|
);
|
|
|
+ out.push(RepoOutcome {
|
|
|
+ url: repo.url.clone(),
|
|
|
+ state: "failed",
|
|
|
+ root: None,
|
|
|
+ recipes: None,
|
|
|
+ error: Some(e.to_string()),
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- roots
|
|
|
+ out
|
|
|
}
|
|
|
|
|
|
/// Clone/pull the `Config.recipe_repos` entries into the default cache
|
|
|
/// base (blocking git work off the async runtime). Best-effort: per-repo
|
|
|
/// failures are warnings, never errors — returns the reachable roots.
|
|
|
pub async fn sync_recipe_repos(config: &crate::config::Config) -> Vec<PathBuf> {
|
|
|
+ sync_recipe_repos_report(config)
|
|
|
+ .await
|
|
|
+ .into_iter()
|
|
|
+ .filter_map(|o| o.root)
|
|
|
+ .collect()
|
|
|
+}
|
|
|
+
|
|
|
+/// [`sync_recipe_repos`] with per-repo detail (the dashboard's ⟳ result).
|
|
|
+pub async fn sync_recipe_repos_report(config: &crate::config::Config) -> Vec<RepoOutcome> {
|
|
|
let repos = config.recipe_repos.clone();
|
|
|
let reg_path = registry_path();
|
|
|
let base = cache_base();
|
|
|
@@ -248,7 +324,7 @@ pub async fn sync_recipe_repos(config: &crate::config::Config) -> Vec<PathBuf> {
|
|
|
tokio::task::spawn_blocking(move || {
|
|
|
crate::locale::apply_global_locale();
|
|
|
let _ = std::fs::create_dir_all(&base);
|
|
|
- sync_repos(&repos, &base, ®istry, false)
|
|
|
+ sync_repos_report(&repos, &base, ®istry, false)
|
|
|
})
|
|
|
.await
|
|
|
.unwrap_or_default()
|