Skip to content

Commit 0cf985d

Browse files
committed
Add cancelable scans and improve UI feedback
Enable cancellation for folder/file scans and improve front-end feedback. In Rust, reset the cancel flag at scan start and check it while enumerating folders and before spawning file tasks so long-running scans can be stopped. In React, swallow "Cancelled" errors (no alert), filter incoming hash-progress events to the active file, add filePath as a listener dependency, and track isScanning state. Disable the Scan button during scans, show a Cancel button that invokes cancel_hashing, clear previous results at scan start, and display a LinearProgress bar while scanning.
1 parent 58552a1 commit 0cf985d

2 files changed

Lines changed: 42 additions & 15 deletions

File tree

src-tauri/src/main.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,16 @@ struct FileData {
312312

313313
#[command]
314314
async fn scan_folder(window: Window, state: tauri::State<'_, AppState>, folder_path: String, include_subfolders: bool, include_hidden: bool, algorithms: Vec<String>) -> Result<Vec<FileData>, String> {
315+
state.cancel.store(false, Ordering::SeqCst);
316+
315317
// First, collect files to process
316318
let mut files_to_process: Vec<String> = Vec::new();
317319
let mut folders_to_scan = vec![folder_path];
318320

319321
while let Some(folder) = folders_to_scan.pop() {
322+
if state.cancel.load(Ordering::SeqCst) {
323+
break;
324+
}
320325
let mut entries = tokio::fs::read_dir(folder).await.map_err(|e| e.to_string())?;
321326

322327
while let Some(entry) = entries.next_entry().await.map_err(|e| e.to_string())? {
@@ -338,6 +343,9 @@ async fn scan_folder(window: Window, state: tauri::State<'_, AppState>, folder_p
338343
let mut handles = Vec::new();
339344

340345
for file_path in files_to_process {
346+
if state.cancel.load(Ordering::SeqCst) {
347+
break;
348+
}
341349
let permit = sem.clone().acquire_owned().await.unwrap();
342350
let win = window.clone();
343351
let cancel_flag = state.cancel.clone();

src/App.tsx

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,9 @@ const SingleFileTab = ({ filePath, setFilePath, selectedAlgorithms, handleAlgori
466466
applyHashResults(mergedResults);
467467
} catch (error) {
468468
console.error('Hash calculation failed:', error);
469-
showAlert('Error', 'Hash calculation failed: ' + error);
469+
if (error !== "Cancelled") {
470+
showAlert('Error', 'Hash calculation failed: ' + error);
471+
}
470472
} finally {
471473
setIsHashing(false);
472474
setProgress(null);
@@ -482,11 +484,13 @@ const SingleFileTab = ({ filePath, setFilePath, selectedAlgorithms, handleAlgori
482484
(async () => {
483485
unlisten = await listen('hash-progress', (event: any) => {
484486
const payload = event.payload as any;
485-
setProgress({
486-
percent: payload.percent || 0,
487-
bytes_read: payload.bytes_read || 0,
488-
total: payload.total || 0
489-
});
487+
if (payload.file === filePath) {
488+
setProgress({
489+
percent: payload.percent || 0,
490+
bytes_read: payload.bytes_read || 0,
491+
total: payload.total || 0
492+
});
493+
}
490494
});
491495
})();
492496

@@ -495,7 +499,7 @@ const SingleFileTab = ({ filePath, setFilePath, selectedAlgorithms, handleAlgori
495499
unlisten.then((f: any) => f());
496500
}
497501
};
498-
}, []);
502+
}, [filePath]);
499503

500504
const handleSaveReport = async () => {
501505
if (!filePath) {
@@ -885,6 +889,7 @@ const FolderScanTab = ({ folderPath, setFolderPath, selectedAlgorithms, handleAl
885889
const [files, setFiles] = useState<any[]>([]);
886890
const [includeSubfolders, setIncludeSubfolders] = useState(true);
887891
const [includeHidden, setIncludeHidden] = useState(false);
892+
const [isScanning, setIsScanning] = useState(false);
888893

889894
const handleFolderSelect = async () => {
890895
const selected = await openDialog({
@@ -905,13 +910,21 @@ const FolderScanTab = ({ folderPath, setFolderPath, selectedAlgorithms, handleAl
905910
return;
906911
}
907912

908-
const scannedFiles = await invoke("scan_folder", {
909-
folderPath,
910-
includeSubfolders,
911-
includeHidden,
912-
algorithms
913-
});
914-
setFiles(scannedFiles as any[]);
913+
setIsScanning(true);
914+
setFiles([]);
915+
try {
916+
const scannedFiles = await invoke("scan_folder", {
917+
folderPath,
918+
includeSubfolders,
919+
includeHidden,
920+
algorithms
921+
});
922+
setFiles(scannedFiles as any[]);
923+
} catch (e) {
924+
if (e !== "Cancelled") showAlert('Error', `Folder scan failed: ${e}`);
925+
} finally {
926+
setIsScanning(false);
927+
}
915928
};
916929

917930
const handleSaveReport = async () => {
@@ -995,7 +1008,13 @@ const FolderScanTab = ({ folderPath, setFolderPath, selectedAlgorithms, handleAl
9951008
<FormControlLabel control={<Checkbox checked={includeHidden} onChange={() => setIncludeHidden(!includeHidden)} />} label="Include Hidden Files" />
9961009
</Box>
9971010

998-
<Button variant="contained" onClick={handleScan} fullWidth sx={{ mt: 2 }}>Scan Folder</Button>
1011+
<Box sx={{ mt: 2, display: 'flex', gap: 1 }}>
1012+
<Button variant="contained" onClick={handleScan} fullWidth disabled={isScanning}>Scan Folder</Button>
1013+
{isScanning && (
1014+
<Button variant="outlined" color="error" onClick={() => invoke('cancel_hashing')}>Cancel</Button>
1015+
)}
1016+
</Box>
1017+
{isScanning && <LinearProgress sx={{ mt: 2 }} />}
9991018

10001019
<TableContainer component={Paper} sx={{ mt: 2 }}>
10011020
<Table>

0 commit comments

Comments
 (0)