refactor: enhance sync module with report tracking, payload parsing, and IndexMap support

This commit is contained in:
2026-06-05 17:15:18 +08:00
parent d88f0843b5
commit 8e6d732122
14 changed files with 412 additions and 215 deletions
+43 -44
View File
@@ -1,75 +1,74 @@
use crate::config::SshellConfig;
use super::{PullStrategy, build_sync_payload, merge_remote};
use super::{SyncReport, bidirectional_merge, build_sync_payload, count_synced};
use anyhow::{Context, Result, bail};
use reqwest::blocking::Client;
use reqwest::header::{ACCEPT, CONTENT_TYPE};
const FILE_NAME: &str = "sshell-config.toml";
pub fn push(cfg: &mut SshellConfig) -> Result<String> {
pub fn sync(cfg: &mut SshellConfig) -> Result<SyncReport> {
let url = webdav_file_url(cfg)?;
let user = cfg
.settings
.webdav_user
.as_deref()
.clone()
.context("webdav_user not set")?;
let password = cfg
.settings
.webdav_password
.as_deref()
.clone()
.context("webdav_password not set")?;
let client = Client::new();
// Step 1: Download remote
let remote_payload = {
let response = client
.get(&url)
.basic_auth(&user, Some(&password))
.header(ACCEPT, "*/*")
.send()?;
if response.status().is_success() {
let content = response.text()?;
Some(
toml::from_str(&content)
.with_context(|| "failed to parse remote config")?,
)
} else {
None
}
};
// Step 2: Snapshot before merge so we can rollback on upload failure
let snapshot = cfg.clone();
// Step 3: Bidirectional merge (modifies cfg in memory only)
let report = if let Some(remote) = remote_payload {
bidirectional_merge(cfg, remote)?
} else {
SyncReport {
pushed: count_synced(cfg),
..Default::default()
}
};
// Step 4: Upload merged payload
let payload = build_sync_payload(cfg, cfg.settings.sync_password.as_deref())?;
let content = toml::to_string_pretty(&payload)?;
let client = Client::new();
let response = client
.put(&url)
.basic_auth(user, Some(password))
.basic_auth(&user, Some(&password))
.header(CONTENT_TYPE, "text/plain")
.header(ACCEPT, "*/*")
.body(content)
.send()?;
if !response.status().is_success() {
bail!("sync push failed: {}", response.status());
// Rollback in-memory state
*cfg = snapshot;
bail!("sync upload failed: {}", response.status());
}
Ok(url)
}
pub fn pull_with_strategy(cfg: &mut SshellConfig, strategy: PullStrategy) -> Result<usize> {
let url = webdav_file_url(cfg)?;
let user = cfg
.settings
.webdav_user
.as_deref()
.context("webdav_user not set")?;
let password = cfg
.settings
.webdav_password
.as_deref()
.context("webdav_password not set")?;
let client = Client::new();
let response = client
.get(&url)
.basic_auth(user, Some(password))
.header(ACCEPT, "*/*")
.send()?;
if response.status() == reqwest::StatusCode::NOT_FOUND {
bail!("sync pull failed: remote file not found");
}
if !response.status().is_success() {
bail!("sync pull failed: {}", response.status());
}
let content = response.text()?;
let remote: toml::Value =
toml::from_str(&content).with_context(|| "failed to parse remote config")?;
merge_remote(cfg, remote, strategy)
cfg.save()?;
Ok(report)
}
fn webdav_file_url(cfg: &SshellConfig) -> Result<String> {