2026-08-29 11:30:51 +00:00
|
|
|
mod cli;
|
2026-08-29 15:35:27 +00:00
|
|
|
mod episodes;
|
2026-08-29 02:06:14 +00:00
|
|
|
mod files;
|
2026-08-29 03:44:38 +00:00
|
|
|
mod history;
|
2026-08-29 03:01:19 +00:00
|
|
|
mod metadata;
|
2026-08-29 09:39:52 +00:00
|
|
|
mod presets;
|
2026-08-29 03:01:19 +00:00
|
|
|
mod settings;
|
2026-08-29 14:08:17 +00:00
|
|
|
mod subtitles;
|
2026-08-29 02:06:14 +00:00
|
|
|
|
2026-08-29 10:07:09 +00:00
|
|
|
use aurora_curator_core::format::ExpressionFormat;
|
|
|
|
|
use aurora_curator_core::matching::{self, EpisodeGuess, MovieGuess, ReleaseInfo};
|
|
|
|
|
use aurora_curator_core::rename::{self, ConflictPolicy, RenameAction, RenamePlanItem, RenameResult};
|
|
|
|
|
use aurora_curator_core::string_bindings;
|
2026-08-29 01:43:12 +00:00
|
|
|
use std::collections::HashMap;
|
2026-08-29 03:44:38 +00:00
|
|
|
use tauri::AppHandle;
|
2026-08-29 01:43:12 +00:00
|
|
|
|
|
|
|
|
/// 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
|
|
|
|
|
/// rename-preview panel in the UI so users see live results as they edit a pattern.
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn render_expression(expression: String, bindings: HashMap<String, String>) -> Result<String, String> {
|
|
|
|
|
let compiled = ExpressionFormat::compile(&expression).map_err(|e| e.to_string())?;
|
|
|
|
|
compiled
|
|
|
|
|
.format(&string_bindings(bindings))
|
|
|
|
|
.map_err(|e| e.to_string())
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-29 02:42:31 +00:00
|
|
|
/// Guesses a season/episode number pair (and series name) from a filename using local
|
|
|
|
|
/// regex patterns only - no network calls, no metadata lookup. Returns `None` when the
|
|
|
|
|
/// filename doesn't carry a recognizable season/episode marker (movies, already-clean
|
|
|
|
|
/// names, etc).
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn guess_episode(filename: String) -> Option<EpisodeGuess> {
|
|
|
|
|
matching::guess_episode(&filename)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-29 04:04:16 +00:00
|
|
|
/// Guesses a movie title (and release year, if present) from a filename using local
|
|
|
|
|
/// regex/heuristic patterns only - no network calls. Callers should try
|
|
|
|
|
/// `guess_episode` first and only fall back to this when it returns `None`; see
|
|
|
|
|
/// `matching::guess_movie` for why.
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn guess_movie(filename: String) -> Option<MovieGuess> {
|
|
|
|
|
matching::guess_movie(&filename)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-29 09:29:14 +00:00
|
|
|
/// Guesses release-tag metadata (resolution, source, video/audio codec, release
|
|
|
|
|
/// group) from a filename's own text using local regex patterns only - no network
|
2026-08-29 10:07:09 +00:00
|
|
|
/// calls, no real media probing (Aurora Curator never ships an ffprobe/MediaInfo dependency,
|
2026-08-29 09:29:14 +00:00
|
|
|
/// so filename tags are the only source for these fields). Independent of
|
|
|
|
|
/// `guess_episode`/`guess_movie`: called alongside whichever of those two applies,
|
|
|
|
|
/// since a release name carries (or doesn't carry) these tags regardless of whether
|
|
|
|
|
/// it's an episode or a movie.
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn guess_release_info(filename: String) -> Option<ReleaseInfo> {
|
|
|
|
|
matching::guess_release_info(&filename)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-29 04:31:16 +00:00
|
|
|
/// Executes a batch of planned on-disk rename/copy/symlink operations (optionally
|
2026-08-29 10:07:09 +00:00
|
|
|
/// moving/copying files into subfolders - see `aurora_curator_core::rename`). `action` picks
|
2026-08-29 09:50:36 +00:00
|
|
|
/// which of the three happens; `conflict` picks what happens when a computed target
|
|
|
|
|
/// already exists (skip it, save alongside it with an auto-numbered name, or replace
|
|
|
|
|
/// it) - the UI is expected to have already shown the user the full old-path/new-path
|
|
|
|
|
/// plan and both choices, and gotten explicit confirmation before calling this. Runs
|
|
|
|
|
/// off the main thread since real filesystem I/O shouldn't block the UI.
|
2026-08-29 03:44:38 +00:00
|
|
|
///
|
2026-08-29 04:31:16 +00:00
|
|
|
/// 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
|
2026-08-29 09:50:36 +00:00
|
|
|
/// removes what was created. `conflict` is NOT recorded - undoing always uses its own
|
|
|
|
|
/// fixed skip-on-conflict policy regardless of what the original rename used.
|
2026-08-29 02:42:31 +00:00
|
|
|
#[tauri::command]
|
2026-08-29 09:50:36 +00:00
|
|
|
async fn execute_rename(
|
|
|
|
|
app: AppHandle,
|
|
|
|
|
items: Vec<RenamePlanItem>,
|
|
|
|
|
action: RenameAction,
|
|
|
|
|
conflict: ConflictPolicy,
|
|
|
|
|
) -> Vec<RenameResult> {
|
|
|
|
|
let results = tauri::async_runtime::spawn_blocking(move || rename::execute_renames(&items, action, conflict))
|
2026-08-29 02:42:31 +00:00
|
|
|
.await
|
2026-08-29 03:44:38 +00:00
|
|
|
.unwrap_or_default();
|
2026-08-29 04:31:16 +00:00
|
|
|
history::record_batch(&app, &results, action);
|
2026-08-29 03:44:38 +00:00
|
|
|
results
|
2026-08-29 02:42:31 +00:00
|
|
|
}
|
|
|
|
|
|
2026-08-29 11:30:51 +00:00
|
|
|
/// Entry point for the `-rename` command-line mode - see `cli.rs`'s module doc. Never
|
|
|
|
|
/// builds a Tauri `App`/`Builder` at all, unlike [`run`] below. Returns the process
|
|
|
|
|
/// exit code for `main.rs` to pass to `std::process::exit`.
|
|
|
|
|
pub fn run_cli(args: Vec<String>) -> i32 {
|
|
|
|
|
cli::run(args)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-29 01:43:12 +00:00
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
|
|
|
pub fn run() {
|
|
|
|
|
tauri::Builder::default()
|
2026-08-29 02:06:14 +00:00
|
|
|
.plugin(tauri_plugin_dialog::init())
|
|
|
|
|
.invoke_handler(tauri::generate_handler![
|
|
|
|
|
render_expression,
|
2026-08-29 02:42:31 +00:00
|
|
|
guess_episode,
|
2026-08-29 04:04:16 +00:00
|
|
|
guess_movie,
|
2026-08-29 09:29:14 +00:00
|
|
|
guess_release_info,
|
2026-08-29 02:42:31 +00:00
|
|
|
execute_rename,
|
2026-08-29 03:01:19 +00:00
|
|
|
metadata::fetch_metadata,
|
2026-08-29 04:04:16 +00:00
|
|
|
metadata::fetch_movie_metadata,
|
2026-08-29 03:01:19 +00:00
|
|
|
settings::get_tmdb_token,
|
|
|
|
|
settings::save_tmdb_token,
|
2026-08-29 12:25:45 +00:00
|
|
|
settings::get_omdb_api_key,
|
|
|
|
|
settings::save_omdb_api_key,
|
2026-08-29 10:44:12 +00:00
|
|
|
settings::get_tv_metadata_source,
|
|
|
|
|
settings::save_tv_metadata_source,
|
2026-08-29 12:25:45 +00:00
|
|
|
settings::get_movie_metadata_source,
|
|
|
|
|
settings::save_movie_metadata_source,
|
2026-08-29 14:08:17 +00:00
|
|
|
settings::get_opensubtitles_api_key,
|
|
|
|
|
settings::save_opensubtitles_api_key,
|
|
|
|
|
settings::get_opensubtitles_username,
|
|
|
|
|
settings::has_opensubtitles_password,
|
|
|
|
|
settings::save_opensubtitles_login,
|
|
|
|
|
subtitles::search_subtitles,
|
|
|
|
|
subtitles::download_subtitle,
|
2026-08-29 15:35:27 +00:00
|
|
|
episodes::browse_episodes,
|
2026-08-29 03:44:38 +00:00
|
|
|
history::list_rename_history,
|
|
|
|
|
history::undo_rename_batch,
|
2026-08-29 09:39:52 +00:00
|
|
|
presets::list_presets,
|
|
|
|
|
presets::save_preset,
|
|
|
|
|
presets::delete_preset,
|
2026-08-29 02:06:14 +00:00
|
|
|
files::pick_files,
|
|
|
|
|
files::pick_folder,
|
|
|
|
|
files::expand_dropped_paths,
|
|
|
|
|
])
|
2026-08-29 01:43:12 +00:00
|
|
|
.run(tauri::generate_context!())
|
2026-08-29 10:07:09 +00:00
|
|
|
.expect("error while running Aurora Curator");
|
2026-08-29 01:43:12 +00:00
|
|
|
}
|