Skip to content

Commit 7a7a060

Browse files
committed
fix(install): use deterministic hash for package without checksum
1 parent 4781e9c commit 7a7a060

6 files changed

Lines changed: 47 additions & 53 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 39 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/soar-cli/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ miette = { workspace = true }
2525
minisign-verify = "0.2.4"
2626
nu-ansi-term = "0.50.3"
2727
once_cell = "1.21.3"
28-
rand = "0.9.2"
2928
rayon = { workspace = true }
3029
regex = { workspace = true }
3130
semver = "1.0.27"

crates/soar-cli/src/install.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use std::{
1212
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
1313
use minisign_verify::{PublicKey, Signature};
1414
use nu_ansi_term::Color::{Blue, Cyan, Green, Magenta, Red, Yellow};
15-
use rand::{distr::Alphanumeric, Rng};
1615
use soar_config::{config::get_config, utils::default_install_patterns};
1716
use soar_core::{
1817
database::{connection::DieselDatabase, models::Package},
@@ -31,7 +30,7 @@ use soar_db::repository::{
3130
};
3231
use soar_dl::types::Progress;
3332
use soar_package::integrate_package;
34-
use soar_utils::{hash::calculate_checksum, pattern::apply_sig_variants};
33+
use soar_utils::{hash::{calculate_checksum, hash_string}, pattern::apply_sig_variants};
3534
use tabled::{
3635
builder::Builder,
3736
settings::{themes::BorderCorrection, Panel, Style},
@@ -1066,11 +1065,11 @@ pub async fn install_single_package(
10661065
.filter(|s| s.len() >= 12)
10671066
.map(|s| s[..12].to_string())
10681067
.unwrap_or_else(|| {
1069-
rand::rng()
1070-
.sample_iter(&Alphanumeric)
1071-
.take(12)
1072-
.map(char::from)
1073-
.collect()
1068+
let input = format!(
1069+
"{}:{}:{}",
1070+
target.package.pkg_id, target.package.pkg_name, target.package.version
1071+
);
1072+
hash_string(&input)[..12].to_string()
10741073
});
10751074

10761075
let install_dir = get_config()

crates/soar-cli/src/progress.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ pub fn handle_progress(state: Progress, progress_bar: &ProgressBar) {
101101
} => {
102102
progress_bar.set_length(total);
103103
progress_bar.set_position(current);
104+
progress_bar.reset_elapsed();
104105
}
105106
Progress::Chunk {
106107
current, ..
@@ -143,6 +144,7 @@ pub fn handle_install_progress(
143144
if let Some(pb) = progress_bar {
144145
pb.set_length(total);
145146
pb.set_position(current);
147+
pb.reset_elapsed();
146148
}
147149
}
148150
Progress::Chunk {

crates/soar-dl/src/download.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
22
fs::{self, File, OpenOptions, Permissions},
3-
io::{Read as _, Write as _},
3+
io::{Read as _, Seek as _, SeekFrom, Write as _},
44
os::unix::fs::PermissionsExt as _,
55
path::{Path, PathBuf},
66
sync::Arc,
@@ -254,8 +254,13 @@ impl Download {
254254
if output_path.is_file() {
255255
match self.overwrite {
256256
OverwriteMode::Skip => {
257-
debug!(path = %output_path.display(), "file exists, skipping download");
258-
return Ok(output_path);
257+
// Only skip if there's no resume info (complete download)
258+
// If resume info exists, it's a partial download that should continue
259+
if resume_info.is_none() {
260+
debug!(path = %output_path.display(), "file exists, skipping download");
261+
return Ok(output_path);
262+
}
263+
debug!(path = %output_path.display(), "file exists but is partial, resuming download");
259264
}
260265
OverwriteMode::Force => {
261266
debug!(path = %output_path.display(), "file exists, forcing overwrite");
@@ -273,7 +278,7 @@ impl Download {
273278
}
274279
}
275280
}
276-
};
281+
}
277282

278283
if let Some(parent) = output_path.parent() {
279284
trace!(path = %parent.display(), "creating parent directories");
@@ -395,8 +400,14 @@ impl Download {
395400
}
396401

397402
let mut file = if is_resuming {
398-
trace!(path = %path.display(), "opening file for append (resume)");
399-
OpenOptions::new().append(true).open(path)?
403+
let resume_pos = resume_from.unwrap();
404+
trace!(path = %path.display(), resume_pos = resume_pos, "opening file for resume");
405+
// Truncate file to resume position to avoid duplicated bytes
406+
// (file may have more bytes than last checkpoint due to writes between checkpoints)
407+
let mut file = OpenOptions::new().write(true).open(path)?;
408+
file.set_len(resume_pos)?;
409+
file.seek(SeekFrom::End(0))?;
410+
file
400411
} else {
401412
trace!(path = %path.display(), "creating new file");
402413
File::create(path)?

crates/soar-utils/src/hash.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,28 @@ pub fn verify_checksum<P: AsRef<Path>>(file_path: P, expected: &str) -> HashResu
7272
Ok(actual.eq_ignore_ascii_case(expected))
7373
}
7474

75+
/// Calculates a hash from a string input.
76+
///
77+
/// Returns a hex-encoded blake3 hash string.
78+
///
79+
/// # Arguments
80+
///
81+
/// * `input` - The string to hash.
82+
///
83+
/// # Example
84+
///
85+
/// ```
86+
/// use soar_utils::hash::hash_string;
87+
///
88+
/// let hash = hash_string("hello world");
89+
/// assert_eq!(hash.len(), 64);
90+
/// ```
91+
pub fn hash_string(input: &str) -> String {
92+
let mut hasher = blake3::Hasher::new();
93+
hasher.update(input.as_bytes());
94+
hasher.finalize().to_hex().to_string()
95+
}
96+
7597
#[cfg(test)]
7698
mod tests {
7799
use std::io::Write;

0 commit comments

Comments
 (0)