knowledge 0.11.0

The launcher and updater for the Knowledge.Dev MCP binary
Documentation
use anyhow::{Context, Result};
use futures::StreamExt;
use indicatif::{ProgressBar, ProgressStyle};
use reqwest::Client;
use reqwest::header::CONTENT_LENGTH;
use std::fs::File as StdFile;
use std::path::{Path, PathBuf};
use tokio::fs;
use tokio::io::AsyncWriteExt;

pub async fn download_binary(client: &Client, url: &str, dest: &Path) -> Result<()> {
    let resp = client
        .get(url)
        .header(reqwest::header::USER_AGENT, crate::USER_AGENT)
        .send()
        .await?
        .error_for_status()?;
    let total = resp
        .headers()
        .get(CONTENT_LENGTH)
        .and_then(|v| v.to_str().ok())
        .and_then(|v| v.parse().ok())
        .unwrap_or(0);
    let mut chunks = resp.bytes_stream();
    let tmp_dest = temp_download_path(dest);
    let mut file = tokio::fs::File::from_std(StdFile::create(&tmp_dest)?);
    let bar = ProgressBar::new(total);
    bar.set_style(progress_style()?);
    while let Some(chunk) = chunks.next().await.transpose()? {
        bar.inc(chunk.len() as u64);
        file.write_all(&chunk).await?;
    }
    file.flush().await?;
    drop(file);
    fs::rename(&tmp_dest, dest).await?;
    bar.finish_and_clear();
    Ok(())
}

fn temp_download_path(dest: &Path) -> PathBuf {
    let mut file_name = dest
        .file_name()
        .map(|name| name.to_os_string())
        .unwrap_or_else(|| "binary".into());
    file_name.push(".download");
    dest.with_file_name(file_name)
}

fn progress_style() -> Result<ProgressStyle> {
    ProgressStyle::with_template(
        "{wide_bar:.cyan/blue} {percent:>3}% {binary_bytes}/{binary_total_bytes} {eta}s",
    )
    .map(|style| style.progress_chars("=> "))
    .context("Failed to configure progress bar")
}