Let the naming scheme organize files into subfolders
- rename::rename_one now resolves new_name relative to the source file's
own directory, allowing / as a subdirectory separator (e.g. a naming
scheme like {show name}/{episode name}) instead of rejecting it. Missing
intermediate directories are created automatically via create_dir_all.
- New validate_relative_target() replaces the old is_plain_filename():
still rejects anything that could escape the source directory (empty
path segments - which also catches a leading-slash absolute path -
and . / .. segments) plus backslashes (Renamr always treats / as the
scheme's folder separator regardless of OS), but now allows a
legitimate multi-segment relative path through.
- The "Unchanged" check now compares the full resolved target path to the
source, not just the filename, since a computed name can now include
directory components.
- 6 new tests: organizing into a new subfolder, nested subfolders that
don't exist yet, conflict detection still works for organized targets,
and rejection of backslashes / doubled / leading / trailing slashes.
- Frontend: naming-scheme editor hint explains the / convention, and the
rename confirmation modal notes when a plan will create new subfolders.
- No changes needed in the naming-expression engine itself - it already
treats / between two {} scriptlets as ordinary literal text.
This commit is contained in:
@@ -9,10 +9,11 @@ native file/folder loading and drag-and-drop, local filename-based season/episod
|
||||
detection ("Match") is working, real online metadata lookups against TMDB ("Fetch
|
||||
Data") are working — including fuzzy show-name disambiguation, so a search that
|
||||
returns several similarly-named shows doesn't just take TMDB's top-ranked hit — and the
|
||||
actual on-disk rename operation is wired up (same-directory rename, skip-on-conflict,
|
||||
with a confirmation step showing the full old-name/new-name plan before anything
|
||||
touches disk — there is no undo yet). Full media-metadata bindings are not yet ported.
|
||||
See "Porting roadmap" below.
|
||||
actual on-disk rename operation is wired up (skip-on-conflict, with a confirmation step
|
||||
showing the full old-name/new-name plan before anything touches disk — there is no undo
|
||||
yet). Rename can also organize files into subfolders — including ones that don't exist
|
||||
yet — when the naming scheme includes a `/`. 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
|
||||
to the New Names list) — the default is `{show name} - {season number}x{episode
|
||||
@@ -78,6 +79,28 @@ An undefined binding contributes an empty string rather than failing outright (s
|
||||
`{n}{missing}` still renders); the overall expression only errors if the *total*
|
||||
rendered output would be empty.
|
||||
|
||||
`/` between two `{ }` scriptlets (or anywhere in the literal text) needs no special
|
||||
handling in the expression engine at all — it's just ordinary literal text, the same as
|
||||
a space or a dash. It only becomes meaningful one layer up, at rename time (see
|
||||
"Organizing into subfolders" below): a scheme like `{show name}/{episode name}` renders
|
||||
to a name containing a real `/`, and the rename engine treats that as "put this file in
|
||||
a subfolder" rather than rejecting it.
|
||||
|
||||
### Organizing into subfolders
|
||||
|
||||
A naming scheme that includes `/` (e.g. `{show name}/Season {season number}/{episode
|
||||
number} - {episode name}`) moves the file into that subfolder as part of renaming,
|
||||
creating any missing intermediate folders automatically
|
||||
(`crates/renamr-core/src/rename/mod.rs::rename_one` → `fs::create_dir_all`). This is
|
||||
still always scoped to the file's own directory tree — the target is resolved relative
|
||||
to the source file's parent directory, never as an absolute path, and every path
|
||||
component is validated (`validate_relative_target`) to reject empty segments (a
|
||||
leading, trailing, or doubled `/` — which also catches an absolute-looking
|
||||
`/etc/passwd`-style path), `.`/`..` segments, and backslashes (Renamr always uses `/`
|
||||
for the naming scheme's folder separator regardless of OS). Conflict policy is
|
||||
unchanged: if the resolved target already exists and isn't the same underlying file,
|
||||
that item is skipped rather than overwritten.
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
@@ -87,10 +110,11 @@ crates/renamr-core/ Pure Rust core: naming-expression engine (ported), local
|
||||
(`similarity/`, partial `EpisodeMetrics` port), a TMDB metadata
|
||||
client (`web/tmdb.rs` — TV search + episode lookup, disambiguated
|
||||
via `similarity/`, tested against a mocked HTTP server via
|
||||
`mockito`), and the on-disk rename engine (ported for the
|
||||
same-directory case, `rename/`) — skip-on-conflict, no
|
||||
move/organize-into-folders yet. Not yet ported: full media
|
||||
metadata bindings, other web datasource clients (TVDB/AniDB/etc).
|
||||
`mockito`), and the on-disk rename engine (`rename/`) —
|
||||
skip-on-conflict, can also organize into (auto-created)
|
||||
subfolders when the naming scheme includes `/`. 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, TMDB metadata fetch,
|
||||
@@ -184,13 +208,14 @@ ported from, for anyone picking this up:
|
||||
OMDb, TVmaze, OpenSubtitles) are not yet started.
|
||||
6. **Rename engine + CLI** — `net.filebot.cli.CmdlineOperations` (1235 lines) and
|
||||
`ArgumentBean` (415 lines, the full flag surface: `-rename`, `--db`, `--format`,
|
||||
`--action`, `--conflict`, `-get-subtitles`, `-script`, etc.). The core same-directory
|
||||
rename operation is done (`crates/renamr-core/src/rename`, fixed skip-on-conflict
|
||||
policy, wired to the UI's Rename button behind a confirmation step). Not yet done:
|
||||
the `--action` variants beyond rename-in-place (move/copy/symlink to a different
|
||||
folder, which is what lets a naming scheme like `{n}/{s00e00}` organize files into
|
||||
subfolders), the `--conflict` variants beyond skip (auto-number, override), and the
|
||||
CLI flag surface itself.
|
||||
`--action`, `--conflict`, `-get-subtitles`, `-script`, etc.). The rename operation is
|
||||
done (`crates/renamr-core/src/rename`, fixed skip-on-conflict policy, wired to the
|
||||
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.
|
||||
7. **The rest of the UI** — the Rename panel (file loading, drag-drop, live preview,
|
||||
local matching, confirmed rename) is built; the other five sidebar panels
|
||||
(Episodes/Subtitles/SFV/Filter/List) are still stub screens, plus Align up/down
|
||||
|
||||
Reference in New Issue
Block a user