Add Move/Copy/Symlink rename actions, with undo support for all three
- renamr-core::rename: new RenameAction enum (Move/Copy/Symlink, default Move, mirroring net.filebot.cli.CmdlineOperations's --action modes). execute_renames() now takes an action applied to the whole batch. Copy uses fs::copy (source untouched); Symlink uses std::os::unix::fs::symlink / std::os::windows::fs::symlink_file behind a cfg(unix)/cfg(windows) split. Same conflict policy (skip, never overwrite) and auto-created destination folders apply to all three actions. 8 new tests. - renamr-core::history: HistoryBatch now records which action produced it (#[serde(default)] so old history.json entries - all implicitly Move - still deserialize). revert_one() branches on it: Move still moves the file back; Copy/ Symlink undo by deleting the copy/symlink (using symlink_metadata so a broken link is still recognized as present and removable) - the original was never touched by those actions, so there's nothing to move back. 3 new tests. - src-tauri: execute_rename command takes the chosen action and threads it through to both execute_renames and record_batch. - Frontend: a Move/Copy/Symlink segmented selector in the "Confirm Rename" modal (remembered across modal opens, defaults to Move), action-aware subtitle/button copy, and a status message using the right verb (Renamed/Copied/Symlinked). Only Move reconciles a loaded item's id/name/bindings to the new path afterward, since Copy/ Symlink leave the source file exactly where it was. History panel now shows a Copy/ Symlink badge on non-Move batches and explains what Undo does for each. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
aab8e28246
commit
fd4e6f66f0
@@ -4,7 +4,7 @@
|
||||
//! list the log, and undo one batch from it.
|
||||
|
||||
use renamr_core::history::{self, HistoryBatch, HistoryEntry, RevertResult};
|
||||
use renamr_core::rename::{RenameOutcome, RenameResult};
|
||||
use renamr_core::rename::{RenameAction, RenameOutcome, RenameResult};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
@@ -43,12 +43,14 @@ fn now_millis() -> u64 {
|
||||
}
|
||||
|
||||
/// Called right after a rename batch executes (see `execute_rename` in `lib.rs`) -
|
||||
/// records only the entries that were actually renamed; a file that was unchanged,
|
||||
/// skipped, or failed to rename has nothing to undo. Best-effort: if persisting the
|
||||
/// history fails for some reason, the rename itself already succeeded and shouldn't be
|
||||
/// reported as failed just because its undo record couldn't be saved, so errors here
|
||||
/// records only the entries that were actually renamed/copied/linked, and the action
|
||||
/// that was used to do it (so `undo_rename_batch` later knows whether to move the
|
||||
/// result back or just delete it - see `renamr_core::history::revert_one`); a file that
|
||||
/// was unchanged, skipped, or failed has nothing to undo. Best-effort: if persisting the
|
||||
/// history fails for some reason, the operation itself already succeeded and shouldn't
|
||||
/// be reported as failed just because its undo record couldn't be saved, so errors here
|
||||
/// are swallowed rather than surfaced to the caller.
|
||||
pub fn record_batch(app: &AppHandle, results: &[RenameResult]) {
|
||||
pub fn record_batch(app: &AppHandle, results: &[RenameResult], action: RenameAction) {
|
||||
let entries: Vec<HistoryEntry> = results
|
||||
.iter()
|
||||
.filter_map(|r| match &r.outcome {
|
||||
@@ -67,6 +69,7 @@ pub fn record_batch(app: &AppHandle, results: &[RenameResult]) {
|
||||
let batch = HistoryBatch {
|
||||
id: format!("batch-{}", now_millis()),
|
||||
timestamp: now_millis(),
|
||||
action,
|
||||
entries,
|
||||
};
|
||||
|
||||
|
||||
+14
-11
@@ -5,7 +5,7 @@ mod settings;
|
||||
|
||||
use renamr_core::format::ExpressionFormat;
|
||||
use renamr_core::matching::{self, EpisodeGuess, MovieGuess};
|
||||
use renamr_core::rename::{self, RenamePlanItem, RenameResult};
|
||||
use renamr_core::rename::{self, RenameAction, RenamePlanItem, RenameResult};
|
||||
use renamr_core::string_bindings;
|
||||
use std::collections::HashMap;
|
||||
use tauri::AppHandle;
|
||||
@@ -39,20 +39,23 @@ fn guess_movie(filename: String) -> Option<MovieGuess> {
|
||||
matching::guess_movie(&filename)
|
||||
}
|
||||
|
||||
/// Executes a batch of planned on-disk renames (optionally moving files into
|
||||
/// subfolders - see `renamr_core::rename`; conflicting targets are always skipped,
|
||||
/// never overwritten). The UI is expected to have already shown the user the full
|
||||
/// old-path/new-path plan and gotten explicit confirmation before calling this.
|
||||
/// Runs off the main thread since real filesystem I/O shouldn't block the UI.
|
||||
/// Executes a batch of planned on-disk rename/copy/symlink operations (optionally
|
||||
/// moving/copying files into subfolders - see `renamr_core::rename`; conflicting
|
||||
/// targets are always skipped, never overwritten). `action` picks which of the three
|
||||
/// happens - the UI is expected to have already shown the user the full old-path/
|
||||
/// new-path plan, the chosen action, and gotten explicit confirmation before calling
|
||||
/// this. Runs off the main thread since real filesystem I/O shouldn't block the UI.
|
||||
///
|
||||
/// Every successfully-renamed item is recorded to the rename history log afterward
|
||||
/// (see `history::record_batch`) so it can be undone later from the History panel.
|
||||
/// Every successfully-produced item is recorded to the rename history log afterward
|
||||
/// (see `history::record_batch`), tagged with `action`, so it can be undone later from
|
||||
/// the History panel - a Move undo moves the file back, a Copy/Symlink undo just
|
||||
/// removes what was created.
|
||||
#[tauri::command]
|
||||
async fn execute_rename(app: AppHandle, items: Vec<RenamePlanItem>) -> Vec<RenameResult> {
|
||||
let results = tauri::async_runtime::spawn_blocking(move || rename::execute_renames(&items))
|
||||
async fn execute_rename(app: AppHandle, items: Vec<RenamePlanItem>, action: RenameAction) -> Vec<RenameResult> {
|
||||
let results = tauri::async_runtime::spawn_blocking(move || rename::execute_renames(&items, action))
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
history::record_batch(&app, &results);
|
||||
history::record_batch(&app, &results, action);
|
||||
results
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user