diff --git a/src/download.rs b/src/download.rs index 83327e5..f4ae712 100644 --- a/src/download.rs +++ b/src/download.rs @@ -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, + /// SHA-1 hash of the downloaded file pub hash_sha1: omaha::Hash, + /// 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 = hash_on_disk(path, None)?; +/// println!("File hash: {}", hash); +/// Ok(()) +/// } +/// ``` pub fn hash_on_disk(path: &Path, maxlen: Option) -> Result> { let file = File::open(path).context(format!("failed to open path({:?})", path.display()))?; let mut hasher = T::hasher(); @@ -59,6 +101,10 @@ pub fn hash_on_disk(path: &Path, maxlen: Option) -> 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(client: &Client, url: U, path: &Path, expected_sha256: Option>, expected_sha1: Option>) -> Result where U: reqwest::IntoUrl + Clone, @@ -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(client: &Client, url: U, path: &Path, expected_sha256: Option>, expected_sha1: Option>) -> Result where U: reqwest::IntoUrl + Clone,