winbrew_cli\commands/
update.rs1use 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}