Add rename history with undo
- New crates/renamr-core/src/history module: HistoryBatch/HistoryEntry records, revert_batch() moves files back to their original paths (skip-on-conflict, same policy as rename::execute_renames; recreates a deleted destination folder defensively), push_batch() maintains a most-recent-first log capped at 50 batches. A partial port of the idea behind net.filebot.HistoryDialog, as a flat undo stack rather than a full session/element log. 6 new tests. - New src-tauri/src/history.rs: persists the log as history.json in the OS app-config directory (same location as settings.json). execute_rename now takes an AppHandle and calls history::record_batch after every rename, recording only the items that actually got renamed. Two new commands: list_rename_history and undo_rename_batch. - Frontend: the History button (previously a no-op) now opens a panel listing past rename batches with a timestamp, file count, and the old-name/new-name pairs, each with an Undo button. Undoing reconciles any currently-loaded item back to its pre-rename path/name, same pattern as post-rename reconciliation. - Verified via cargo test (45 total)/check + npm build + a full Playwright pass: loaded mocked files, renamed them, opened History, undid the batch, and confirmed both the UI and the (mocked) backend state matched at every step.
This commit is contained in:
+16
-7
@@ -1,4 +1,5 @@
|
||||
mod files;
|
||||
mod history;
|
||||
mod metadata;
|
||||
mod settings;
|
||||
|
||||
@@ -7,6 +8,7 @@ use renamr_core::matching::{self, EpisodeGuess};
|
||||
use renamr_core::rename::{self, RenamePlanItem, RenameResult};
|
||||
use renamr_core::string_bindings;
|
||||
use std::collections::HashMap;
|
||||
use tauri::AppHandle;
|
||||
|
||||
/// Renders a naming-scheme expression against a set of binding values.
|
||||
/// This is the Tauri-facing entry point into the core format engine, used by the
|
||||
@@ -28,16 +30,21 @@ fn guess_episode(filename: String) -> Option<EpisodeGuess> {
|
||||
matching::guess_episode(&filename)
|
||||
}
|
||||
|
||||
/// Executes a batch of planned on-disk renames (same-directory rename only; conflicting
|
||||
/// targets are skipped, never overwritten - see `renamr_core::rename` for the policy).
|
||||
/// The UI is expected to have already shown the user the full old-name/new-name plan
|
||||
/// and gotten explicit confirmation before calling this - there is no undo yet.
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
#[tauri::command]
|
||||
async fn execute_rename(items: Vec<RenamePlanItem>) -> Vec<RenameResult> {
|
||||
tauri::async_runtime::spawn_blocking(move || rename::execute_renames(&items))
|
||||
async fn execute_rename(app: AppHandle, items: Vec<RenamePlanItem>) -> Vec<RenameResult> {
|
||||
let results = tauri::async_runtime::spawn_blocking(move || rename::execute_renames(&items))
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default();
|
||||
history::record_batch(&app, &results);
|
||||
results
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
@@ -51,6 +58,8 @@ pub fn run() {
|
||||
metadata::fetch_metadata,
|
||||
settings::get_tmdb_token,
|
||||
settings::save_tmdb_token,
|
||||
history::list_rename_history,
|
||||
history::undo_rename_batch,
|
||||
files::pick_files,
|
||||
files::pick_folder,
|
||||
files::expand_dropped_paths,
|
||||
|
||||
Reference in New Issue
Block a user