Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions src/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,54 @@ use sha2::digest::DynDigest;

const MAX_DOWNLOAD_RETRY: u32 = 20;

/// Result of a successful download operation.
///
/// Contains the computed hashes and file handle for the downloaded content.
pub struct DownloadResult {
/// SHA-256 hash of the downloaded file
pub hash_sha256: omaha::Hash<omaha::Sha256>,
/// SHA-1 hash of the downloaded file
pub hash_sha1: omaha::Hash<omaha::Sha1>,
/// File handle to the downloaded content on disk
pub data: File,
}

/// Computes a hash of a file on disk.
///
/// Reads the file at the given path and computes its hash using the specified hash algorithm.
/// The file is read in chunks to handle large files efficiently.
///
/// # Arguments
///
/// * `path` - Path to the file to hash
/// * `maxlen` - Optional maximum number of bytes to read from the file. If `None`,
/// the entire file is hashed. If `Some(len)`, only the first `len` bytes are hashed.
///
/// # Returns
///
/// Returns the computed hash on success.
///
/// # Errors
///
/// This function will return an error if:
/// * The file cannot be opened
/// * File metadata cannot be read
/// * The file cannot be read (e.g., I/O errors)
/// * `read_exact` fails when reading file chunks
///
/// # Examples
///
/// ```no_run
/// use std::path::Path;
/// use ue_rs::hash_on_disk;
///
/// fn main() -> anyhow::Result<()> {
/// let path = Path::new("/path/to/file.dat");
/// let hash: omaha::Hash<omaha::Sha256> = hash_on_disk(path, None)?;
/// println!("File hash: {}", hash);
/// Ok(())
/// }
/// ```
pub fn hash_on_disk<T: omaha::HashAlgo>(path: &Path, maxlen: Option<usize>) -> Result<omaha::Hash<T>> {
let file = File::open(path).context(format!("failed to open path({:?})", path.display()))?;
let mut hasher = T::hasher();
Expand Down Expand Up @@ -59,6 +101,10 @@ pub fn hash_on_disk<T: omaha::HashAlgo>(path: &Path, maxlen: Option<usize>) -> R
Ok(omaha::Hash::from_bytes(Box::new(hasher).finalize()))
}

/// Internal function that performs the actual download and hash verification.
///
/// This is the core implementation that downloads a file from a URL, saves it to disk,
/// and verifies its hashes. Used internally by `download_and_hash` for retry logic.
fn do_download_and_hash<U>(client: &Client, url: U, path: &Path, expected_sha256: Option<omaha::Hash<omaha::Sha256>>, expected_sha1: Option<omaha::Hash<omaha::Sha1>>) -> Result<DownloadResult>
where
U: reqwest::IntoUrl + Clone,
Expand Down Expand Up @@ -118,6 +164,54 @@ where
})
}

/// Downloads a file from a URL and computes its hashes with retry logic.
///
/// This function downloads a file from the specified URL, saves it to the given path,
/// and computes both SHA-256 and SHA-1 hashes. It includes automatic retry logic
/// (up to 20 attempts) to handle transient network failures.
///
/// # Arguments
///
/// * `client` - HTTP client to use for the download
/// * `url` - URL to download from (must implement `IntoUrl`)
/// * `path` - Local file system path where the downloaded file will be saved
/// * `expected_sha256` - Optional expected SHA-256 hash for verification
/// * `expected_sha1` - Optional expected SHA-1 hash for verification
///
/// # Returns
///
/// Returns a `DownloadResult` containing:
/// * Computed SHA-256 hash of the downloaded file
/// * Computed SHA-1 hash of the downloaded file
/// * File handle to the downloaded content
///
/// # Errors
///
/// This function will return an error if:
/// * The HTTP request fails (network error, invalid URL, etc.)
/// * The server returns a non-success status code
/// * File I/O operations fail (cannot create/write to destination path)
/// * Hash verification fails (computed hash doesn't match expected)
/// * Maximum retry attempts (20) are exceeded
///
/// # Examples
///
/// ```no_run
/// use reqwest::blocking::Client;
/// use std::path::Path;
/// use url::Url;
/// use ue_rs::download_and_hash;
///
/// fn main() -> anyhow::Result<()> {
/// let client = Client::new();
/// let url = Url::parse("https://example.com/file.dat")?;
/// let path = Path::new("/tmp/downloaded_file.dat");
///
/// let result = download_and_hash(&client, url, path, None, None)?;
/// println!("Downloaded file with SHA-256: {}", result.hash_sha256);
/// Ok(())
/// }
/// ```
pub fn download_and_hash<U>(client: &Client, url: U, path: &Path, expected_sha256: Option<omaha::Hash<omaha::Sha256>>, expected_sha1: Option<omaha::Hash<omaha::Sha1>>) -> Result<DownloadResult>
where
U: reqwest::IntoUrl + Clone,
Expand Down