diff --git a/apps/tauri/src-tauri/src/lib.rs b/apps/tauri/src-tauri/src/lib.rs index fc7d3da..75bf467 100644 --- a/apps/tauri/src-tauri/src/lib.rs +++ b/apps/tauri/src-tauri/src/lib.rs @@ -2,6 +2,8 @@ use std::path::PathBuf; use std::sync::Mutex; use std::time::Instant; +use chrono::Utc; + use notify_debouncer_mini::{new_debouncer, DebouncedEventKind}; use serde::{Deserialize, Serialize}; use tauri::{Emitter, Manager, State}; @@ -422,21 +424,40 @@ async fn test_webdav_connection( #[tauri::command] async fn sync_workspace( + workspace_name: String, workspace_path: String, webdav_url: String, username: String, password: String, + mode: String, + state: State<'_, Mutex>, ) -> Result { + let sync_mode = match mode.as_str() { + "push" => SyncMode::Push, + "pull" => SyncMode::Pull, + _ => SyncMode::Full, + }; let result = sync::sync_workspace( - &PathBuf::from(workspace_path), + &PathBuf::from(&workspace_path), &webdav_url, &username, &password, - SyncMode::Full, + sync_mode, None, ) .await .map_err(|e| e.to_string())?; + + // Persist last_sync timestamp to config + { + let mut s = state.lock().unwrap(); + if let Some(ws) = s.config.workspaces.get_mut(&workspace_name) { + ws.last_sync = Some(Utc::now()); + } + let config_path = AppConfig::get_config_path(); + s.config.save_to_file(&config_path).map_err(|e| e.to_string())?; + } + Ok(result.into()) } diff --git a/apps/tauri/src/lib/screens/SettingsScreen.svelte b/apps/tauri/src/lib/screens/SettingsScreen.svelte index b21840b..c27dde6 100644 --- a/apps/tauri/src/lib/screens/SettingsScreen.svelte +++ b/apps/tauri/src/lib/screens/SettingsScreen.svelte @@ -122,13 +122,35 @@ {#if app.config?.current_workspace} - +
+ + +
+ {#if app.config.workspaces[app.config.current_workspace]?.last_sync} + {@const lastSync = new Date(app.config.workspaces[app.config.current_workspace].last_sync!)} + {@const secsAgo = Math.floor((Date.now() - lastSync.getTime()) / 1000)} + {@const relTime = secsAgo < 60 ? "just now" : secsAgo < 3600 ? `${Math.floor(secsAgo / 60)}m ago` : `${Math.floor(secsAgo / 3600)}h ago`} +

+ Last sync: {relTime} + {#if app.lastSyncResult} +  · ↑{app.lastSyncResult.uploaded} ↓{app.lastSyncResult.downloaded} + {/if} +

+ {/if} {/if} diff --git a/apps/tauri/src/lib/screens/TasksScreen.svelte b/apps/tauri/src/lib/screens/TasksScreen.svelte index 92793e4..9e16216 100644 --- a/apps/tauri/src/lib/screens/TasksScreen.svelte +++ b/apps/tauri/src/lib/screens/TasksScreen.svelte @@ -550,9 +550,14 @@ - + {#if app.syncing}
+ {:else if app.lastSyncResult} +
+ ↑{app.lastSyncResult.uploaded} + ↓{app.lastSyncResult.downloaded} +
{/if} diff --git a/apps/tauri/src/lib/stores/app.svelte.ts b/apps/tauri/src/lib/stores/app.svelte.ts index 85ae8c8..f928efd 100644 --- a/apps/tauri/src/lib/stores/app.svelte.ts +++ b/apps/tauri/src/lib/stores/app.svelte.ts @@ -24,6 +24,8 @@ let darkMode = $state( globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false, ); let syncing = $state(false); +let syncMode = $state<"full" | "push" | "pull">("full"); +let lastSyncResult = $state(null); let error = $state(null); // ── Derived ────────────────────────────────────────────────────────── @@ -254,7 +256,8 @@ async function setGroupByDueDate(listId: string, enabled: boolean) { async function triggerSync() { if (!config?.current_workspace) return; - const ws = config.workspaces[config.current_workspace]; + const workspaceName = config.current_workspace; + const ws = config.workspaces[workspaceName]; if (!ws?.webdav_url) { error = "No WebDAV URL configured"; return; @@ -265,14 +268,19 @@ async function triggerSync() { const domain = new URL(ws.webdav_url).hostname; const [username, password] = await invoke<[string, string]>("load_credentials", { domain }); const result = await invoke("sync_workspace", { + workspaceName, workspacePath: ws.path, webdavUrl: ws.webdav_url, username, password, + mode: syncMode, }); + lastSyncResult = result; if (result.errors.length > 0) { error = result.errors.join("; "); } + // Reload config to pick up updated last_sync timestamp + config = await invoke("get_config"); await loadLists(); } catch (e) { error = String(e); @@ -281,6 +289,10 @@ async function triggerSync() { } } +function setSyncMode(mode: "full" | "push" | "pull") { + syncMode = mode; +} + function toggleDarkMode() { darkMode = !darkMode; } @@ -326,6 +338,12 @@ export const app = { get syncing() { return syncing; }, + get syncMode() { + return syncMode; + }, + get lastSyncResult() { + return lastSyncResult; + }, get error() { return error; }, @@ -350,6 +368,7 @@ export const app = { renameList, setGroupByDueDate, triggerSync, + setSyncMode, toggleDarkMode, setScreen, clearError,