66 lines
2.0 KiB
Rust
66 lines
2.0 KiB
Rust
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<Vec<String>> {
|
|
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<String, String> = 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)
|
|
}
|