Skip to content

Commit b69cdee

Browse files
committed
feat: add detailed update statistics and improve CLI output
1 parent 4653f6f commit b69cdee

File tree

3 files changed

+151
-16
lines changed

3 files changed

+151
-16
lines changed

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ pub mod update_info;
44
pub mod updater;
55

66
pub use error::Error;
7-
pub use updater::Updater;
7+
pub use updater::{UpdateStats, Updater};

src/main.rs

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,27 @@ fn main() {
3737
let cli = Cli::parse();
3838

3939
if let Err(e) = run(cli) {
40-
eprintln!("Error: {}", e);
40+
eprintln!("\nError: {}", e);
4141
std::process::exit(1);
4242
}
4343
}
4444

45+
fn format_size(bytes: u64) -> String {
46+
const KB: u64 = 1024;
47+
const MB: u64 = 1024 * KB;
48+
const GB: u64 = 1024 * MB;
49+
50+
if bytes >= GB {
51+
format!("{:.1} GB", bytes as f64 / GB as f64)
52+
} else if bytes >= MB {
53+
format!("{:.1} MB", bytes as f64 / MB as f64)
54+
} else if bytes >= KB {
55+
format!("{:.1} KB", bytes as f64 / KB as f64)
56+
} else {
57+
format!("{} B", bytes)
58+
}
59+
}
60+
4561
fn run(cli: Cli) -> Result<(), Error> {
4662
match cli.command {
4763
Commands::Update {
@@ -59,23 +75,74 @@ fn run(cli: Cli) -> Result<(), Error> {
5975
updater = updater.output_dir(dir);
6076
}
6177

62-
println!("Checking for update...");
78+
let source_path = updater.source_path();
79+
let source_size = updater.source_size();
80+
let (target_path, target_size) = updater.target_info()?;
81+
82+
println!(
83+
"Source: {} ({})",
84+
source_path.display(),
85+
format_size(source_size)
86+
);
87+
println!(
88+
"Target: {} ({})",
89+
target_path.display(),
90+
format_size(target_size)
91+
);
92+
println!();
93+
6394
if updater.check_for_update()? {
64-
println!("Update available. Performing delta update...");
65-
let new_path = updater.perform_update()?;
66-
println!("Updated AppImage: {}", new_path.display());
95+
println!("Performing delta update...");
96+
let (new_path, stats) = updater.perform_update()?;
97+
98+
if stats.blocks_reused > 0 || stats.blocks_downloaded > 0 {
99+
println!();
100+
println!(
101+
"Reused: {:>10} ({} blocks)",
102+
format_size(stats.bytes_reused()),
103+
stats.blocks_reused
104+
);
105+
println!(
106+
"Downloaded: {:>10} ({} blocks)",
107+
format_size(stats.bytes_downloaded()),
108+
stats.blocks_downloaded
109+
);
110+
println!(
111+
"Saved: {:>10} ({}%)",
112+
format_size(stats.bytes_reused()),
113+
stats.saved_percentage()
114+
);
115+
}
116+
117+
println!();
118+
println!("Updated: {}", new_path.display());
67119
} else {
68-
let output_path = updater.output_path()?;
69-
println!("Already up to date: {}", output_path.display());
120+
println!("Already up to date!");
70121
}
71122
}
72123
Commands::Check { path } => {
73124
let updater = Updater::new(&path)?;
74125

126+
let source_path = updater.source_path();
127+
let source_size = updater.source_size();
128+
let (target_path, target_size) = updater.target_info()?;
129+
130+
println!(
131+
"Source: {} ({})",
132+
source_path.display(),
133+
format_size(source_size)
134+
);
135+
println!(
136+
"Target: {} ({})",
137+
target_path.display(),
138+
format_size(target_size)
139+
);
140+
println!();
141+
75142
if updater.check_for_update()? {
76-
println!("Update available.");
143+
println!("Status: Update available");
77144
} else {
78-
println!("No update available.");
145+
println!("Status: Up to date");
79146
}
80147
}
81148
}

src/updater.rs

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,34 @@ use crate::appimage::AppImage;
77
use crate::error::{Error, Result};
88
use crate::update_info::UpdateInfo;
99

10+
#[derive(Debug)]
11+
pub struct UpdateStats {
12+
pub source_path: PathBuf,
13+
pub source_size: u64,
14+
pub target_path: PathBuf,
15+
pub target_size: u64,
16+
pub blocks_reused: usize,
17+
pub blocks_downloaded: usize,
18+
pub block_size: usize,
19+
}
20+
21+
impl UpdateStats {
22+
pub fn bytes_reused(&self) -> u64 {
23+
(self.blocks_reused * self.block_size) as u64
24+
}
25+
26+
pub fn bytes_downloaded(&self) -> u64 {
27+
(self.blocks_downloaded * self.block_size) as u64
28+
}
29+
30+
pub fn saved_percentage(&self) -> u64 {
31+
if self.target_size == 0 {
32+
return 0;
33+
}
34+
(self.bytes_reused() * 100 / self.target_size).min(100)
35+
}
36+
}
37+
1038
pub struct Updater {
1139
appimage: AppImage,
1240
update_info: UpdateInfo,
@@ -107,15 +135,40 @@ impl Updater {
107135
Ok(true)
108136
}
109137

110-
pub fn perform_update(&self) -> Result<PathBuf> {
138+
pub fn source_path(&self) -> &Path {
139+
self.appimage.path()
140+
}
141+
142+
pub fn source_size(&self) -> u64 {
143+
fs::metadata(self.appimage.path())
144+
.map(|m| m.len())
145+
.unwrap_or(0)
146+
}
147+
148+
pub fn target_info(&self) -> Result<(PathBuf, u64)> {
149+
let (control, _zsync_url) = self.fetch_control_file()?;
150+
let output_path = self.resolve_output_path(&control)?;
151+
Ok((output_path, control.length))
152+
}
153+
154+
pub fn perform_update(&self) -> Result<(PathBuf, UpdateStats)> {
111155
let (control, zsync_url) = self.fetch_control_file()?;
112156
let output_path = self.resolve_output_path(&control)?;
113157

114158
if output_path.exists() {
115159
if let Some(ref expected_sha1) = control.sha1
116160
&& self.verify_existing_file(&output_path, expected_sha1)?
117161
{
118-
return Ok(output_path);
162+
let stats = UpdateStats {
163+
source_path: self.appimage.path().to_path_buf(),
164+
source_size: self.source_size(),
165+
target_path: output_path.clone(),
166+
target_size: control.length,
167+
blocks_reused: 0,
168+
blocks_downloaded: 0,
169+
block_size: control.blocksize,
170+
};
171+
return Ok((output_path, stats));
119172
}
120173

121174
if !self.overwrite {
@@ -130,20 +183,25 @@ impl Updater {
130183
.map(|m| m.permissions())
131184
.ok();
132185

186+
let source_size = self.source_size();
187+
let target_size = control.length;
188+
let block_size = control.blocksize;
189+
133190
let assembly = ZsyncAssembly::from_url(&zsync_url, &output_path)
134191
.map_err(|e| Error::Zsync(format!("Failed to initialize zsync: {}", e)))?;
135192

136193
let mut assembly = assembly;
137194

138-
assembly
195+
let blocks_reused = assembly
139196
.submit_source_file(self.appimage.path())
140197
.map_err(|e| Error::Zsync(format!("Failed to submit source file: {}", e)))?;
141198

142-
assembly
199+
let self_blocks = assembly
143200
.submit_self_referential()
144201
.map_err(|e| Error::Zsync(format!("Self-referential scan failed: {}", e)))?;
202+
let blocks_reused = blocks_reused.saturating_add(self_blocks);
145203

146-
assembly
204+
let blocks_downloaded = assembly
147205
.download_missing_blocks()
148206
.map_err(|e| Error::Zsync(format!("Failed to download blocks: {}", e)))?;
149207

@@ -155,7 +213,17 @@ impl Updater {
155213
fs::set_permissions(&output_path, perms)?;
156214
}
157215

158-
Ok(output_path)
216+
let stats = UpdateStats {
217+
source_path: self.appimage.path().to_path_buf(),
218+
source_size,
219+
target_path: output_path.clone(),
220+
target_size,
221+
blocks_reused,
222+
blocks_downloaded,
223+
block_size,
224+
};
225+
226+
Ok((output_path, stats))
159227
}
160228

161229
pub fn output_path(&self) -> Result<PathBuf> {

0 commit comments

Comments
 (0)