use anyhow::{Context, Result}; use sha2::{Digest, Sha256}; use std::{collections::HashMap, path::Path}; use klog_types::FilesMetaResponse; pub async fn upload_changed(base_url: &str, token: &str, folder: &Path) -> Result> { let base_url = base_url.trim_end_matches('/'); let client = reqwest::Client::new(); let remote: FilesMetaResponse = client .get(format!("{base_url}/files")) .bearer_auth(token) .send() .await .context("GET /files request failed")? .error_for_status() .context("GET /files returned error status")? .json() .await .context("GET /files response parse failed")?; let remote_hashes: HashMap = remote .files .into_iter() .map(|f| (f.filename, f.sha256)) .collect(); let mut uploaded = Vec::new(); let mut entries = tokio::fs::read_dir(folder) .await .with_context(|| format!("Cannot read folder: {}", folder.display()))?; while let Some(entry) = entries.next_entry().await? { if !entry.file_type().await?.is_file() { continue; } let filename = entry.file_name().to_string_lossy().to_string(); let data = tokio::fs::read(entry.path()).await?; let local_hash = hex::encode(Sha256::digest(&data)); let needs_upload = remote_hashes .get(&filename) .map_or(true, |h| h != &local_hash); if !needs_upload { continue; } let part = reqwest::multipart::Part::bytes(data).file_name(filename.clone()); let form = reqwest::multipart::Form::new().part("file", part); client .post(format!("{base_url}/files")) .bearer_auth(token) .multipart(form) .send() .await .with_context(|| format!("Upload failed for {filename}"))? .error_for_status() .with_context(|| format!("Upload error status for {filename}"))?; uploaded.push(filename); } Ok(uploaded) }