Skip to content

Repository files navigation

File Rename Plus

A fast, lightweight, and portable file renaming tool with a native GUI. Built with Rust.

License Version Language Platform

Archive status

This project is feature-frozen for long-term personal use. Needs are met; no new modes planned.

Item Policy
Primary platform Windows 10+ (64-bit), verified
macOS / Linux Code compiles; not QA-verified. One behavioural difference is documented under Rename safety
GUI toolkit iced 0.14 — do not casual-upgrade majors
Toolchain Pinned to 1.95.0 via rust-toolchain.toml; MSRV floor is 1.89
Lockfile Cargo.lock is committed; prefer cargo build --release over free dep thrash
Gate before touching code cargo test && cargo clippy -- -D warnings && cargo build --release
Rebuilding years from now See ARCHIVE.md

Every number quoted in this file is asserted by a test. If a limit, version or MSRV here disagrees with the code, cargo test fails.

Features

  • Drag and drop — drop folders (or individual files) straight onto the window
  • Find & Replace Mode — plain text or regex; case-sensitive toggle
  • Iteration Numbering Mode — template with {n}, start number, padding (extension preserved)
  • Natural sortfile2 before file10
  • Live preview — debounced (~300 ms)
  • Conflict / invalid detection — duplicate targets, collisions with files that are staying put, and illegal basenames mark previews and block Execute
  • Dark / Light theme — persisted; colours come from the active theme's palette
  • Settings persistence — SQLite (see below; find/replace strings are not stored)
  • Keyboard shortcuts
Shortcut Action
Ctrl+O Open folder
Ctrl+Enter Execute rename (only when previews are safe)
Ctrl+↑ / Ctrl+↓ Move the selected file up / down
Delete Remove selected file from the list

Delete only acts on the list when no text field has focus, so editing a pattern never silently changes the batch. The Ctrl chords are unambiguous and work regardless of focus.

System requirements

Component Minimum
OS Windows 10+ (64-bit); macOS / Linux 64-bit (unverified)
RAM ~64 MB
Disk ~10 MB for the portable EXE
Display Window opens at 900×650 and refuses to shrink below 700×500
Input Mouse and keyboard; drag and drop from the system file manager

Platform support

  • Windows: tested and distributed via releases. This is the verified platform.
  • macOS / Linux: implemented and compiled, but not QA-verified — the project is developed on Windows. Build from source; see Build from source for the system packages each needs.

There is no platform-specific code beyond filename validation (below), and no platform-specific dependencies. Rendering is CPU-side via tiny-skia, so no GPU driver or particular graphics API is required anywhere.

Quick start

  1. Add files — drag a folder onto the window, or click Add Folder. Either way the listing is non-recursive and natural-sorted. Dropping several items at once works; so does dropping individual files rather than a whole folder.
  2. Configure — Find & Replace or Iteration
  3. Preview — red [CONFLICT] / [INVALID] must be cleared before Execute enables
  4. Execute — two-phase rename (see safety notes)

After a successful run the list is rewritten in place to show the new names, so the result is visible and a second pass needs no re-adding. Your ordering and any entries you removed by hand are preserved, and nothing is re-read from disk.

Rename safety (honest)

Renames use a two-phase scheme to avoid mid-batch target collisions:

  1. Each file is moved to .rename_temp_<pid>_<newname> in the same directory.
  2. Each temp is moved to the final name.

This is also what makes a swap work: renaming a→b and b→a in one batch is resolved correctly rather than rejected.

On failure:

  • Phase 1 error → already-moved temps are rolled back to originals (best effort).
  • Phase 2 error → remaining temps are restored to originals; files already finalized stay final (no silent invent of backups).

Hard crash mid-run may leave .rename_temp_<pid>_* files. Recover by renaming: strip the .rename_temp_<pid>_ prefix to obtain the intended final basename, or restore a known original name. This is best-effort, not a full transactional filesystem journal.

Platform difference. std::fs::rename silently replaces an existing destination on Unix, but returns an error on Windows. The batch is checked for existing targets before it starts, and each destination is checked again immediately before it is written, so the window in which another process could slip a file into place is small — but on macOS and Linux it is not zero. Do not run a batch against a directory another program is actively writing to.

Filename rules are per-platform

Names are validated against the rules of the system you are running on, checked in the preview and again before any filesystem operation.

Rejected Windows macOS / Linux
Empty, ., .. yes yes
NUL and control characters yes yes
/ yes yes
\ < > : " | ? * yes allowed
Trailing . or space yes allowed
Reserved device names (CON, NUL, COM1…) yes allowed

Applying the Windows set everywhere would reject names that are perfectly legal on Unix — Artist: Title.mp3 being the usual casualty. The trade-off is that a name created on macOS or Linux is not guaranteed to be copyable to Windows later.

Dependencies

Runtime

No separate runtime install — single portable binary (SQLite is bundled via rusqlite).

Build dependencies (direct)

Crate Role
iced 0.14 GUI (tiny-skia + tokio)
rfd 0.17 Native folder dialog
regex 1.13 Find/replace patterns
rusqlite 0.40 Settings DB (bundled SQLite)
dirs 6 Settings path
anyhow 1 Error context in domain ops
tokio 1 Async runtime, and the blocking pool that keeps FS work off the UI thread

There are no platform-specific dependencies. The windows and libc crates were removed once the administrator/root check was found to be answering a question the application never had to ask — see Security & limits.

Installation

Pre-built binary (Windows)

  1. Download the latest release from Releases
  2. Extract and run file-rename-plus.exe

Build from source

Prerequisites: Rust 1.89+ (the floor is set by font-types, pulled in via iced; verified on 1.95.0, which rust-toolchain.toml pins).

rusqlite builds SQLite from source, so every platform also needs a C compiler. Beyond that the GUI stack needs the host windowing libraries:

Platform Also needed
Windows MSVC build tools (the C compiler); nothing else
macOS Xcode Command Line Tools (xcode-select --install)
Debian / Ubuntu build-essential pkg-config libwayland-dev libxkbcommon-dev libx11-dev libxcursor-dev libxrandr-dev libxi-dev
Fedora gcc pkgconf-pkg-config wayland-devel libxkbcommon-devel libX11-devel libXcursor-devel libXrandr-devel libXi-devel

These are the target system's own libraries, which is also why cross-compiling from Windows to Linux does not work without a full sysroot — build natively on each platform.

Windows:

build_release_windows.bat

Output: build_release_windows\file-rename-plus.exe (plus copied README/LICENSE).

macOS / Linux:

./build_release_unix.sh

Output: build_release_unix/file-rename-plus (plus copied docs). Like the Windows script, it runs the gate first and refuses to package a failing build. Or by hand:

cargo test
cargo clippy --all-targets -- -D warnings
cargo build --release
# binary: target/release/file-rename-plus

The test suite is platform-aware: the Windows-only filename assertions are replaced on Unix by ones asserting those same names are accepted. Both branches are compiled and run before release.

Usage

Find & Replace

  1. Add a folder (drag it in, or Add Folder)
  2. Find pattern / replacement
  3. Optional: Regex, Case Sensitive
  4. Preview → Execute

Example: IMG_Photo_ turns IMG_001.jpg into Photo_001.jpg.

Iteration numbering

Template must contain {n}. Original extension is appended.

Example: template vacation_{n}, start 1, padding 3vacation_001.jpg.

Settings location

Platform Path
Windows %LOCALAPPDATA%\file-rename-plus\settings.db
macOS ~/Library/Application Support/file-rename-plus/settings.db
Linux ~/.local/share/file-rename-plus/settings.db

Persisted keys: dark_mode, regex_mode, case_sensitive, template, start_number, padding.

Not persisted: find pattern, replace string, file list, mode (always starts in Find & Replace).

Writes are debounced along with the preview, and a failure to save is reported in the status bar rather than discarded.

Project structure

file-rename-plus/
├── src/
│   ├── main.rs           # iced 0.14 entry + metadata consistency tests
│   ├── app/
│   │   ├── mod.rs        # state, messages, subscriptions
│   │   ├── update.rs     # every state transition
│   │   └── view.rs       # widget tree
│   ├── file_ops.rs       # scan, natural sort, two-phase rename
│   ├── rename.rs         # find/replace, iteration, filename rules, conflicts
│   ├── security.rs       # directory write access
│   ├── settings.rs       # SQLite load/save
│   ├── limits.rs         # domain limits (asserted against this README)
│   ├── theme.rs          # sizing and spacing tokens
│   └── types.rs          # FileEntry, RenamePreview, AppMode, Status
├── Cargo.toml
├── Cargo.lock
├── rust-toolchain.toml   # pinned compiler
├── ARCHIVE.md            # how to rebuild this years from now
├── CHANGELOG.md
├── LICENSE               # MIT
├── README.md
├── PROJECT_VERSION
├── build_release_windows.bat
└── build_release_unix.sh

Security & limits

  • Access checks test the directory, not the file. Renaming rewrites a directory entry, so directory write access is what matters. The previous check opened each file for writing, which wrongly reported "Access denied" for read-only files and for files merely open in another application — both of which rename fine. Each directory is probed once per batch, not once per file.
  • No UAC elevation is ever requested.
  • Pattern and replacement length cap 1024 characters; template 256; padding 10 digits; max files 2000.
  • The file cap is set by what the list renderer sustains, not by any filesystem limit. The list is not virtualised; raising the cap would require that first.
  • Regex patterns are compiled with a 1 MiB compiled-program size limit, which bounds how large a pattern may expand to. Rust's regex crate matches in linear time and has no catastrophic backtracking, so there is no ReDoS exposure to mitigate beyond this.
  • Illegal filenames blocked in preview and at execute.
  • Conflicts / invalid names disable Execute (and Ctrl+Enter is a no-op with status error).
  • Settings values are written with parameterised SQL and truncated on both read and write.

License

MIT — see LICENSE.

Leonard Walujan's Public Projects

About

File Rename Plus Desktop App. Made with Rust.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages