Просмотр исходного кода

repos sync --watch: background re-sync loop (default 10 min) so the Mac recipe-repo cache never goes stale — mirrors the add-on entrypoint loop

Alexander Korolev 1 неделя назад
Родитель
Сommit
5359f6f6b7
9 измененных файлов с 50 добавлено и 11 удалено
  1. 5 0
      AGENTS.md
  2. 1 0
      locales/de/main.ftl
  3. 1 0
      locales/en/main.ftl
  4. 1 0
      locales/es/main.ftl
  5. 1 0
      locales/fr/main.ftl
  6. 1 0
      locales/it/main.ftl
  7. 1 0
      locales/ru/main.ftl
  8. 12 1
      src/cli_sub.rs
  9. 27 10
      src/recipe_repos.rs

+ 5 - 0
AGENTS.md

@@ -763,6 +763,11 @@ then appends its `recipes/` dir to `recipe_sources` and its root to
 ```
 ```
 sm121 repos                  # list: cloned? archived? last-pull ts? recipe count?
 sm121 repos                  # list: cloned? archived? last-pull ts? recipe count?
 sm121 repos sync [--force]   # clone/pull now (--force re-clones from scratch)
 sm121 repos sync [--force]   # clone/pull now (--force re-clones from scratch)
+sm121 repos sync --watch [--interval N]   # background re-sync loop (every N min,
+                             # default 10) — run detached on the Mac (nohup/launchd)
+                             # so the recipe-repo cache never goes stale between
+                             # commands (the HA add-on entrypoint runs the same
+                             # loop in-container)
 sm121 archive <url>          # skip the repo in sync (state in repos.json)
 sm121 archive <url>          # skip the repo in sync (state in repos.json)
 sm121 unarchive <url>
 sm121 unarchive <url>
 sm121 promote <recipe> --from <url> --to <url> [--apply]
 sm121 promote <recipe> --from <url> --to <url> [--apply]

+ 1 - 0
locales/de/main.ftl

@@ -262,6 +262,7 @@ report-title = Getestete Rezepte ({ $count })
 git-failed = git { $cmd } fehlgeschlagen: { $err }
 git-failed = git { $cmd } fehlgeschlagen: { $err }
 recipe-repo-archived-skip = Rezept-Repo ist archiviert — überspringe: { $url }
 recipe-repo-archived-skip = Rezept-Repo ist archiviert — überspringe: { $url }
 recipe-repo-synced = Rezept-Repo synchronisiert: { $url } → { $dir }
 recipe-repo-synced = Rezept-Repo synchronisiert: { $url } → { $dir }
+recipe-repo-watch = repos-Watch: { $n } Repo(s) synchronisiert, nächste Re-Synchronisierung in { $interval } Min (Strg-C zum Beenden)
 recipe-repo-unreachable = Rezept-Repo '{ $url }' nicht erreichbar — überspringe ({ $err }); fahre mit den erreichbaren Repos fort
 recipe-repo-unreachable = Rezept-Repo '{ $url }' nicht erreichbar — überspringe ({ $err }); fahre mit den erreichbaren Repos fort
 repos-registry-corrupt = Repos-Registry '{ $path }' ist beschädigt ({ $err }) — verschiebe sie nach '{ $bak }' und beginne neu
 repos-registry-corrupt = Repos-Registry '{ $path }' ist beschädigt ({ $err }) — verschiebe sie nach '{ $bak }' und beginne neu
 repos-registry-backup-failed = beschädigte Registry { $path } konnte nicht gesichert werden
 repos-registry-backup-failed = beschädigte Registry { $path } konnte nicht gesichert werden

+ 1 - 0
locales/en/main.ftl

@@ -243,6 +243,7 @@ power-on-off-wait-timeout = { $name }: plug did not report off within 10 s (rela
 git-failed = git { $cmd } failed: { $err }
 git-failed = git { $cmd } failed: { $err }
 recipe-repo-archived-skip = recipe repo archived — skipping: { $url }
 recipe-repo-archived-skip = recipe repo archived — skipping: { $url }
 recipe-repo-synced = recipe repo synced: { $url } → { $dir }
 recipe-repo-synced = recipe repo synced: { $url } → { $dir }
+recipe-repo-watch = repos watch: { $n } repo(s) synced, next re-sync in { $interval } min (Ctrl-C to stop)
 recipe-repo-unreachable = recipe repo '{ $url }' unreachable — skipping ({ $err }); continuing with the reachable repos
 recipe-repo-unreachable = recipe repo '{ $url }' unreachable — skipping ({ $err }); continuing with the reachable repos
 repos-registry-corrupt = repos registry '{ $path }' is corrupt ({ $err }) — moving it to '{ $bak }' and starting fresh
 repos-registry-corrupt = repos registry '{ $path }' is corrupt ({ $err }) — moving it to '{ $bak }' and starting fresh
 repos-registry-backup-failed = could not back up corrupt registry { $path }
 repos-registry-backup-failed = could not back up corrupt registry { $path }

+ 1 - 0
locales/es/main.ftl

@@ -262,6 +262,7 @@ report-title = Recetas probadas ({ $count })
 git-failed = git { $cmd } falló: { $err }
 git-failed = git { $cmd } falló: { $err }
 recipe-repo-archived-skip = repositorio de recetas archivado — omitiendo: { $url }
 recipe-repo-archived-skip = repositorio de recetas archivado — omitiendo: { $url }
 recipe-repo-synced = repositorio de recetas sincronizado: { $url } → { $dir }
 recipe-repo-synced = repositorio de recetas sincronizado: { $url } → { $dir }
+recipe-repo-watch = repos watch: { $n } repos sincronizados, próxima re-sincronización en { $interval } min (Ctrl-C para detener)
 recipe-repo-unreachable = repositorio de recetas '{ $url }' inaccesible — omitiendo ({ $err }); continuando con los repositorios accesibles
 recipe-repo-unreachable = repositorio de recetas '{ $url }' inaccesible — omitiendo ({ $err }); continuando con los repositorios accesibles
 repos-registry-corrupt = el registro de repositorios '{ $path }' está corrupto ({ $err }) — moviéndolo a '{ $bak }' y empezando de nuevo
 repos-registry-corrupt = el registro de repositorios '{ $path }' está corrupto ({ $err }) — moviéndolo a '{ $bak }' y empezando de nuevo
 repos-registry-backup-failed = no se pudo respaldar el registro corrupto { $path }
 repos-registry-backup-failed = no se pudo respaldar el registro corrupto { $path }

+ 1 - 0
locales/fr/main.ftl

@@ -262,6 +262,7 @@ report-title = Recettes testées ({ $count })
 git-failed = git { $cmd } a échoué : { $err }
 git-failed = git { $cmd } a échoué : { $err }
 recipe-repo-archived-skip = dépôt de recettes archivé — ignoré : { $url }
 recipe-repo-archived-skip = dépôt de recettes archivé — ignoré : { $url }
 recipe-repo-synced = dépôt de recettes synchronisé : { $url } → { $dir }
 recipe-repo-synced = dépôt de recettes synchronisé : { $url } → { $dir }
+recipe-repo-watch = repos watch : { $n } dépôt(s) synchronisé(s), prochaine re-synchronisation dans { $interval } min (Ctrl-C pour arrêter)
 recipe-repo-unreachable = dépôt de recettes '{ $url }' injoignable — ignoré ({ $err}) ; poursuite avec les dépôts joignables
 recipe-repo-unreachable = dépôt de recettes '{ $url }' injoignable — ignoré ({ $err}) ; poursuite avec les dépôts joignables
 repos-registry-corrupt = le registre des dépôts '{ $path }' est corrompu ({ $err }) — déplacé vers '{ $bak }' et repart de zéro
 repos-registry-corrupt = le registre des dépôts '{ $path }' est corrompu ({ $err }) — déplacé vers '{ $bak }' et repart de zéro
 repos-registry-backup-failed = impossible de sauvegarder le registre corrompu { $path }
 repos-registry-backup-failed = impossible de sauvegarder le registre corrompu { $path }

+ 1 - 0
locales/it/main.ftl

@@ -262,6 +262,7 @@ report-title = Ricette testate ({ $count })
 git-failed = git { $cmd } è fallito: { $err }
 git-failed = git { $cmd } è fallito: { $err }
 recipe-repo-archived-skip = repository di ricette archiviato — salto: { $url }
 recipe-repo-archived-skip = repository di ricette archiviato — salto: { $url }
 recipe-repo-synced = repository di ricette sincronizzato: { $url } → { $dir }
 recipe-repo-synced = repository di ricette sincronizzato: { $url } → { $dir }
+recipe-repo-watch = repos watch: { $n } repo sincronizzati, prossima re-sincronizzazione tra { $interval } min (Ctrl-C per fermare)
 recipe-repo-unreachable = repository di ricette '{ $url }' non raggiungibile — salto ({ $err }); proseguo con i repository raggiungibili
 recipe-repo-unreachable = repository di ricette '{ $url }' non raggiungibile — salto ({ $err }); proseguo con i repository raggiungibili
 repos-registry-corrupt = il registro dei repository '{ $path }' è corrotto ({ $err }) — lo sposto in '{ $bak }' e riparto
 repos-registry-corrupt = il registro dei repository '{ $path }' è corrotto ({ $err }) — lo sposto in '{ $bak }' e riparto
 repos-registry-backup-failed = impossibile salvare una copia del registro corrotto { $path }
 repos-registry-backup-failed = impossibile salvare una copia del registro corrotto { $path }

+ 1 - 0
locales/ru/main.ftl

@@ -272,6 +272,7 @@ stop-not-verified = Контейнеры не подтверждены как о
 git-failed = git { $cmd } завершился ошибкой: { $err }
 git-failed = git { $cmd } завершился ошибкой: { $err }
 recipe-repo-archived-skip = рецепт-репозиторий в архиве — пропускаю: { $url }
 recipe-repo-archived-skip = рецепт-репозиторий в архиве — пропускаю: { $url }
 recipe-repo-synced = рецепт-репозиторий синхронизирован: { $url } → { $dir }
 recipe-repo-synced = рецепт-репозиторий синхронизирован: { $url } → { $dir }
+recipe-repo-watch = repos watch: синхронизировано репозиториев: { $n }, повторная синхронизация через { $interval } мин (Ctrl-C для остановки)
 recipe-repo-unreachable = рецепт-репозиторий '{ $url }' недоступен — пропускаю ({ $err }); продолжаю с доступными репозиториями
 recipe-repo-unreachable = рецепт-репозиторий '{ $url }' недоступен — пропускаю ({ $err }); продолжаю с доступными репозиториями
 repos-registry-corrupt = реестр репозиториев '{ $path }' повреждён ({ $err }) — переношу его в '{ $bak }' и начинаю заново
 repos-registry-corrupt = реестр репозиториев '{ $path }' повреждён ({ $err }) — переношу его в '{ $bak }' и начинаю заново
 repos-registry-backup-failed = не удалось сохранить копию повреждённого реестра { $path }
 repos-registry-backup-failed = не удалось сохранить копию повреждённого реестра { $path }

+ 12 - 1
src/cli_sub.rs

@@ -165,10 +165,21 @@ pub enum ReposCommands {
     /// last-pull ts? recipe count?)
     /// last-pull ts? recipe count?)
     List,
     List,
     /// Clone/pull the configured recipe repos now (`--force` re-clones
     /// Clone/pull the configured recipe repos now (`--force` re-clones
-    /// each cached checkout from scratch)
+    /// each cached checkout from scratch). `--watch` keeps re-syncing in
+    /// the background (every `--interval` minutes) so the cache never goes
+    /// stale between commands — run it detached (nohup/launchd) the way the
+    /// HA add-on's entrypoint loop does.
     Sync {
     Sync {
         #[arg(long)]
         #[arg(long)]
         force: bool,
         force: bool,
+
+        /// Re-sync continuously (background mode; Ctrl-C to stop)
+        #[arg(long)]
+        watch: bool,
+
+        /// Re-sync interval in minutes (with --watch; default 10)
+        #[arg(long, default_value_t = 10)]
+        interval: u32,
     },
     },
 }
 }
 
 

+ 27 - 10
src/recipe_repos.rs

@@ -317,7 +317,11 @@ pub async fn run(config: &crate::config::Config, cmd: &ReposCommands, dry_run: b
                 println!("{:<52} {:<22} {:<18} {n}", repo.url, state, last);
                 println!("{:<52} {:<22} {:<18} {n}", repo.url, state, last);
             }
             }
         }
         }
-        ReposCommands::Sync { force } => {
+        ReposCommands::Sync {
+            force,
+            watch,
+            interval,
+        } => {
             let force = *force;
             let force = *force;
             if dry_run {
             if dry_run {
                 for repo in &config.recipe_repos {
                 for repo in &config.recipe_repos {
@@ -336,15 +340,28 @@ pub async fn run(config: &crate::config::Config, cmd: &ReposCommands, dry_run: b
                 }
                 }
                 return Ok(());
                 return Ok(());
             }
             }
-            let reg_path = registry_path().unwrap_or_else(|| PathBuf::from("repos.json"));
-            let reg = load_registry(&reg_path);
-            let base = cache_base();
-            let repos = config.recipe_repos.clone();
-            let roots = tokio::task::spawn_blocking(move || sync_repos(&repos, &base, &reg, force))
-                .await
-                .unwrap_or_default();
-            if roots.is_empty() {
-                warn!("{}", fluent_i18n::t!("repos-none-synced"));
+            let interval_min = *interval;
+            loop {
+                let reg_path = registry_path().unwrap_or_else(|| PathBuf::from("repos.json"));
+                let reg = load_registry(&reg_path);
+                let base = cache_base();
+                let repos = config.recipe_repos.clone();
+                let n = repos.len();
+                let roots =
+                    tokio::task::spawn_blocking(move || sync_repos(&repos, &base, &reg, force))
+                        .await
+                        .unwrap_or_default();
+                if roots.is_empty() {
+                    warn!("{}", fluent_i18n::t!("repos-none-synced"));
+                }
+                if !watch {
+                    break;
+                }
+                info!(
+                    "{}",
+                    fluent_i18n::t!("recipe-repo-watch", { "n" => n, "interval" => interval_min })
+                );
+                tokio::time::sleep(std::time::Duration::from_secs(interval_min as u64 * 60)).await;
             }
             }
         }
         }
     }
     }