lighty-core 26.5.10

Core utilities for Lighty Launcher
Documentation
# download — async file downloads

Two helpers built on the shared `hosts::HTTP_CLIENT`. No retry logic,
no SHA1 verification — pair with [`hash`](./hash.md) when you need
either.

## API

```rust
pub async fn download_file_untracked(
    url: &str,
    path: impl AsRef<Path>,
) -> DownloadResult<()>;

pub async fn download_file<F: Fn(u64, u64)>(
    url: &str,
    on_progress: F,
) -> DownloadResult<Vec<u8>>;
```

`download_file_untracked` writes directly to disk and discards the
response body. `download_file` streams the response chunk by chunk into
a `Vec<u8>`, calling `on_progress(current, total)` after each chunk.
`total` is `0` when the server omits `Content-Length`.

## Examples

### Save straight to disk

```rust
use lighty_core::download::download_file_untracked;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    download_file_untracked(
        "https://example.com/file.bin",
        "/tmp/file.bin",
    ).await?;
    Ok(())
}
```

### Buffer in memory with a progress callback

```rust
use lighty_core::download::download_file;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let bytes = download_file(
        "https://example.com/file.bin",
        |current, total| {
            if total > 0 {
                let pct = current as f64 * 100.0 / total as f64;
                println!("{:.1}%", pct);
            }
        },
    ).await?;
    tokio::fs::write("/tmp/file.bin", &bytes).await?;
    Ok(())
}
```

### Download + verify SHA1

```rust
use lighty_core::{download::download_file_untracked, hash::verify_file_sha1};
use std::path::Path;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let path = Path::new("/tmp/library.jar");
    download_file_untracked("https://example.com/library.jar", path).await?;

    if !verify_file_sha1(path, "expected-sha1").await? {
        tokio::fs::remove_file(path).await?;
        anyhow::bail!("library SHA1 mismatch");
    }
    Ok(())
}
```

### Concurrent downloads

```rust
use lighty_core::download::download_file_untracked;
use futures::future::try_join_all;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let jobs = vec![
        ("https://example.com/a", "/tmp/a"),
        ("https://example.com/b", "/tmp/b"),
        ("https://example.com/c", "/tmp/c"),
    ];
    try_join_all(jobs.into_iter().map(|(u, p)| download_file_untracked(u, p))).await?;
    Ok(())
}
```

## Errors

```rust
pub enum DownloadError {
    Http(reqwest::Error),   // network failure or non-2xx status
    Io(std::io::Error),     // write to disk failed
}
```

`error_for_status()` is called on the response, so 4xx/5xx surface as
`Http(_)`.

## See also

- [`hash.md`]./hash.md — verify what you downloaded
- [`how-to-use.md`]./how-to-use.md — top-level walkthrough