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:
Renamr Scaffold
2026-08-29 04:31:16 +00:00
co-authored by Claude Sonnet 5
parent aab8e28246
commit fd4e6f66f0
7 changed files with 546 additions and 127 deletions
+21 -16
View File
@@ -9,14 +9,15 @@ native file/folder loading and drag-and-drop, local filename-based season/episod
movie title/year* detection ("Match") is working, real online metadata lookups against
TMDB ("Fetch Data") are working for both TV episodes and movies — including fuzzy
title disambiguation, so a search that returns several similarly-named results doesn't
just take TMDB's top-ranked hit — and the actual on-disk rename operation is wired up
just take TMDB's top-ranked hit — and the actual on-disk operation is wired up
(skip-on-conflict, with a confirmation step showing the full old-path/new-path plan
before anything touches disk, and a per-row ✕ to drop an individual file from that plan
at the last step). Rename can also organize files into subfolders —
including ones that don't exist yet — when the naming scheme includes a `/`. Every
completed rename is logged to a history panel that can undo it, moving files back to
their original paths. A single loaded batch can freely mix TV episodes and movies —
each file only fills in the fields that apply to it. Full media-metadata bindings are
before anything touches disk, a per-row ✕ to drop an individual file from that plan at
the last step, and a Move/Copy/Symlink selector for what to actually do at each
target). It can also organize files into subfolders — including ones that don't exist
yet — when the naming scheme includes a `/`. Every completed batch is logged to a
history panel that can undo it (moving files back for Move, or just removing what was
created for Copy/Symlink). A single loaded batch can freely mix TV episodes and movies
— each file only fills in the fields that apply to it. Full media-metadata bindings are
not yet ported. See "Porting roadmap" below.
The naming scheme is fully user-editable from the app (the "Naming Scheme" button next
@@ -115,12 +116,12 @@ crates/renamr-core/ Pure Rust core: naming-expression engine (ported), local
TMDB metadata client (`web/tmdb.rs` — TV search + episode lookup
and movie search + lookup, both disambiguated via `similarity/`,
tested against a mocked HTTP server via `mockito`), the on-disk
rename engine (`rename/`) —
skip-on-conflict, can also organize into (auto-created)
subfolders when the naming scheme includes `/` — and a rename
history/undo log (`history/`, a partial `HistoryDialog` port).
Not yet ported: full media metadata bindings, other web
datasource clients (TVDB/AniDB/etc).
rename engine (`rename/` — skip-on-conflict, Move/Copy/Symlink
actions, can also organize into (auto-created) subfolders when
the naming scheme includes `/`) — and a rename history/undo log
(`history/`, a partial `HistoryDialog` port, undo-aware of which
action produced each batch). Not yet ported: full media metadata
bindings, other web datasource clients (TVDB/AniDB/etc).
No Tauri dependency, so it builds and unit-tests standalone.
src-tauri/ Tauri app shell: exposes renamr-core as commands to the frontend
(naming-scheme rendering, filename matching for both TV and
@@ -227,9 +228,13 @@ ported from, for anyone picking this up:
UI's Rename button behind a confirmation step) including the `move`-into-subfolder
`--action` variant — a naming scheme like `{n}/{s00e00}` organizes files into
subfolders, creating any missing folders automatically, always scoped to the file's
own directory tree. Not yet done: the `copy`/`symlink` `--action` variants, the
`--conflict` variants beyond skip (auto-number, override), and the CLI flag surface
itself.
own directory tree. The `copy`/`symlink` `--action` variants are done too — an action
selector in the confirmation modal picks Move/Copy/Symlink for the whole batch; Copy
and Symlink both leave the source file untouched and produce a new file/link at the
computed target, and both are undoable from the History panel the same as Move (undo
for Copy/Symlink just removes what was created, since there's nothing to move back).
Not yet done: the `--conflict` variants beyond skip (auto-number, override), and the
CLI flag surface itself.
7. **The rest of the UI** — the Rename panel (file loading, drag-drop, live preview,
local TV/movie matching, confirmed rename with per-item exclude, rename history/undo)
is built; the other five sidebar panels (Episodes/Subtitles/SFV/Filter/List) are