From bf3d896016897810f6702b50d5f7d94e4f8f6c7a Mon Sep 17 00:00:00 2001 From: Psychotoxical Date: Wed, 15 Apr 2026 23:16:06 +0200 Subject: [PATCH] fix(device-sync): auto-import manifest on mount, clear view on disconnect - Read psysonic-sync.json automatically when DeviceSync page opens and drive is already connected (no manual folder re-select needed) - Always replace sources from manifest when choosing a folder, so switching between sticks loads the correct album list - Hide source list and status badges when drive is disconnected - Reset import flag on disconnect so re-plugging triggers a fresh import - Fix unused `mut` warning in cancel_device_sync Co-Authored-By: Claude Sonnet 4.6 --- src-tauri/src/lib.rs | 2 +- src/pages/DeviceSync.tsx | 53 ++++++++++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 55008981..7442c649 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1896,7 +1896,7 @@ async fn calculate_sync_payload( /// Signals a running `sync_batch_to_device` job to stop after its current tracks finish. #[tauri::command] fn cancel_device_sync(job_id: String, app: tauri::AppHandle) { - if let Ok(mut flags) = sync_cancel_flags().lock() { + if let Ok(flags) = sync_cancel_flags().lock() { if let Some(flag) = flags.get(&job_id) { flag.store(true, Ordering::Relaxed); } diff --git a/src/pages/DeviceSync.tsx b/src/pages/DeviceSync.tsx index e44352b7..bb111d5a 100644 --- a/src/pages/DeviceSync.tsx +++ b/src/pages/DeviceSync.tsx @@ -162,6 +162,30 @@ export default function DeviceSync() { // Scan device on mount and when targetDir changes useEffect(() => { scanDevice(); }, [scanDevice]); + // Auto-import manifest when page loads and drive is already connected + const manifestImportedRef = useRef(false); + useEffect(() => { + if (!targetDir || !driveDetected || manifestImportedRef.current) return; + manifestImportedRef.current = true; + invoke<{ version: number; sources: DeviceSyncSource[] } | null>( + 'read_device_manifest', { destDir: targetDir } + ).then(manifest => { + if (manifest?.sources?.length) { + useDeviceSyncStore.getState().clearSources(); + manifest.sources.forEach(s => useDeviceSyncStore.getState().addSource(s)); + showToast(t('deviceSync.manifestImported', { count: manifest.sources.length }), 4000, 'info'); + } + }).catch(() => {}); + }, [targetDir, driveDetected, t]); + + // Clear device file list and reset import flag when stick is unplugged + useEffect(() => { + if (!driveDetected) { + setDeviceFilePaths([]); + manifestImportedRef.current = false; + } + }, [driveDetected]); + // Compute expected paths for each source (for status comparison) useEffect(() => { if (!targetDir || sources.length === 0) { @@ -353,19 +377,18 @@ export default function DeviceSync() { if (sel) { const dir = sel as string; setTargetDir(dir); - // If the device has a psysonic-sync.json and localStorage has no sources yet, - // auto-import so the list is populated when switching machines. - if (useDeviceSyncStore.getState().sources.length === 0) { - try { - const manifest = await invoke<{ version: number; sources: DeviceSyncSource[] } | null>( - 'read_device_manifest', { destDir: dir } - ); - if (manifest?.sources?.length) { - manifest.sources.forEach(s => useDeviceSyncStore.getState().addSource(s)); - showToast(t('deviceSync.manifestImported', { count: manifest.sources.length }), 4000, 'info'); - } - } catch { /* no manifest, that's fine */ } - } + // If the device has a psysonic-sync.json, always import it — replacing any + // sources from a previous device so switching sticks works correctly. + try { + const manifest = await invoke<{ version: number; sources: DeviceSyncSource[] } | null>( + 'read_device_manifest', { destDir: dir } + ); + if (manifest?.sources?.length) { + useDeviceSyncStore.getState().clearSources(); + manifest.sources.forEach(s => useDeviceSyncStore.getState().addSource(s)); + showToast(t('deviceSync.manifestImported', { count: manifest.sources.length }), 4000, 'info'); + } + } catch { /* no manifest, that's fine */ } // Trigger a device scan after folder change setTimeout(() => scanDevice(), 100); } @@ -733,7 +756,7 @@ export default function DeviceSync() { {/* Status summary badges */} - {sources.length > 0 && ( + {sources.length > 0 && driveDetected && (
{syncedCount > 0 && ( @@ -753,7 +776,7 @@ export default function DeviceSync() {
)} - {sources.length === 0 ? ( + {sources.length === 0 || !driveDetected ? (

{t('deviceSync.noSourcesSelected')}

) : ( <>