winbrew_cli\commands/
update.rs

1//! Update command wrapper for refreshing the catalog bundle.
2//!
3//! The command owns the progress-bar wiring and the final success message
4//! while the app layer performs the download, verification, and file swap.
5
6use anyhow::Result;
7use std::io::Write;
8
9use crate::core::paths::ResolvedPaths;
10use crate::{CommandContext, app::update};
11use winbrew_ui::{ProgressHandle, Ui};
12
13pub fn run(ctx: &CommandContext) -> Result<()> {
14    let mut ui = ctx.ui();
15    ui.page_title("Update Package Catalog");
16
17    run_with_refresher(&mut ui, &ctx.app().paths, &RealCatalogRefresher)
18}
19
20fn run_with_refresher<W, R>(ui: &mut Ui<W>, paths: &ResolvedPaths, refresher: &R) -> Result<()>
21where
22    W: Write,
23    R: CatalogRefresher + ?Sized,
24{
25    {
26        let progress: ProgressHandle = ui.progress_bar();
27
28        refresher.refresh_catalog(
29            paths,
30            |total_bytes| {
31                if let Some(total_bytes) = total_bytes {
32                    progress.set_length(total_bytes);
33                }
34                progress.set_message("Downloading catalog bundle");
35            },
36            |downloaded_bytes| {
37                progress.inc(downloaded_bytes);
38            },
39        )?;
40    }
41
42    ui.success("Package catalog updated.");
43    Ok(())
44}
45
46trait CatalogRefresher {
47    fn refresh_catalog<FStart, FProgress>(
48        &self,
49        paths: &ResolvedPaths,
50        on_start: FStart,
51        on_progress: FProgress,
52    ) -> Result<()>
53    where
54        FStart: FnOnce(Option<u64>),
55        FProgress: FnMut(u64);
56}
57
58struct RealCatalogRefresher;
59
60impl CatalogRefresher for RealCatalogRefresher {
61    fn refresh_catalog<FStart, FProgress>(
62        &self,
63        paths: &ResolvedPaths,
64        on_start: FStart,
65        on_progress: FProgress,
66    ) -> Result<()>
67    where
68        FStart: FnOnce(Option<u64>),
69        FProgress: FnMut(u64),
70    {
71        update::refresh_catalog(paths, on_start, on_progress)
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::{CatalogRefresher, run_with_refresher};
78    use crate::commands::test_support::{buffer_text, buffered_ui};
79    use crate::core::paths::{ResolvedPaths, resolved_paths};
80    use anyhow::Result;
81    use std::path::PathBuf;
82    use std::sync::Arc;
83    use std::sync::atomic::{AtomicBool, Ordering};
84    use tempfile::tempdir;
85    use winbrew_ui::UiSettings;
86
87    struct FakeCatalogRefresher {
88        expected_root: PathBuf,
89        called: Arc<AtomicBool>,
90    }
91
92    impl CatalogRefresher for FakeCatalogRefresher {
93        fn refresh_catalog<FStart, FProgress>(
94            &self,
95            paths: &ResolvedPaths,
96            on_start: FStart,
97            on_progress: FProgress,
98        ) -> Result<()>
99        where
100            FStart: FnOnce(Option<u64>),
101            FProgress: FnMut(u64),
102        {
103            assert_eq!(paths.root, self.expected_root);
104
105            on_start(Some(64));
106            let mut on_progress = on_progress;
107            on_progress(64);
108
109            self.called.store(true, Ordering::Relaxed);
110            Ok(())
111        }
112    }
113
114    #[test]
115    fn update_run_reports_success_after_refresh() {
116        let temp_dir = tempdir().expect("temp dir");
117        let paths = resolved_paths(
118            temp_dir.path(),
119            "${root}/packages",
120            "${root}/data",
121            "${root}/data/logs",
122            "${root}/data/cache",
123        );
124
125        let (mut ui, _output, error_output) = buffered_ui(UiSettings::default());
126        let called = Arc::new(AtomicBool::new(false));
127        let refresher = FakeCatalogRefresher {
128            expected_root: temp_dir.path().to_path_buf(),
129            called: called.clone(),
130        };
131
132        run_with_refresher(&mut ui, &paths, &refresher).expect("update should succeed");
133
134        assert!(called.load(Ordering::Relaxed));
135
136        assert!(buffer_text(&error_output).contains("Package catalog updated."));
137    }
138}