Give movies their own naming scheme, fixing the "- x -" blank-preview bug
The Rename panel used a single shared naming-scheme expression for
every item regardless of media kind. A movie item never gets
"show name"/"season number"/"episode number"/"episode name" bindings
(guess_movie sets "n"/"y"/"movie name"/"year" instead) - so rendering
a movie against a TV-oriented scheme like
"{show name} - {season number}x{episode number} - {episode name}"
left nothing but the scheme's own literal text: "- x -", exactly as
reported (screenshot of a real movie file after Match).
TV and movie items now render against two independent naming
schemes (`expression` for TV, a new `movieExpression` for movies),
each persisted separately (new `last_movie_naming_scheme` settings
field/commands, mirroring the existing TV ones) and each editable
through a "TV Shows" / "Movies" toggle added to the Naming Scheme
editor - presets, field chips, and the 1-9 hotkeys all act on
whichever one is currently selected. The movie scheme needs no
onboarding auto-upgrade chain (unlike TV's sample -> matched ->
fetched staging): a movie item only ever has a title and year, both
already available right after local Match, so it just starts at a
sensible default ("{movie name} ({year})") instead.
A mixed batch of TV episodes and movies now renders both kinds
correctly at the same time, each against its own scheme. Verified
with a new Playwright script reproducing the exact reported scenario
(load a movie, click Match) plus the toggle/independent-persistence
behavior; all prior naming-scheme/match-fetch/hash-files suites,
`cargo test --workspace` (162 tests), and `npm run build` still pass
clean.
This commit is contained in:
@@ -121,6 +121,10 @@ pub fn run() {
|
||||
settings::save_opensubtitles_login,
|
||||
settings::get_last_naming_scheme,
|
||||
settings::save_last_naming_scheme,
|
||||
settings::get_last_movie_naming_scheme,
|
||||
settings::save_last_movie_naming_scheme,
|
||||
settings::get_theme,
|
||||
settings::save_theme,
|
||||
subtitles::search_subtitles,
|
||||
subtitles::download_subtitle,
|
||||
episodes::browse_episodes,
|
||||
|
||||
@@ -88,8 +88,27 @@ struct StoredSettings {
|
||||
/// scheme survives closing and reopening the app instead of resetting to the
|
||||
/// built-in sample expression every time. Distinct from `presets.rs`'s 1-9 saved
|
||||
/// slots: this is the *current* scheme, not an explicitly named save.
|
||||
///
|
||||
/// Used for TV episodes specifically - see [`last_movie_naming_scheme`] for the
|
||||
/// separate scheme movies use. The two are independent because TV and movie items
|
||||
/// carry entirely different binding vocabularies (season/episode numbers and an
|
||||
/// episode title vs. just a movie title and year); a single shared scheme text
|
||||
/// can only ever render one of the two kinds correctly; a batch that mixes TV and
|
||||
/// movies would leave the other kind's fields blank.
|
||||
#[serde(default)]
|
||||
last_naming_scheme: Option<String>,
|
||||
/// The naming-scheme expression the Rename panel's editor last held while in
|
||||
/// "Movies" mode - same persistence behavior as [`last_naming_scheme`], just for
|
||||
/// movie items. See that field's doc for why these are two separate settings.
|
||||
#[serde(default)]
|
||||
last_movie_naming_scheme: Option<String>,
|
||||
/// Which visual theme the app renders with - a plain string id ("aurora"/"light"/
|
||||
/// "ocean"/"sunset") matching a `[data-theme="..."]` block in `src/theme.css` on
|
||||
/// the frontend. Kept as an opaque string here rather than a Rust enum so a new
|
||||
/// theme can be added purely in CSS/TS without touching this file too; an
|
||||
/// unrecognized or missing value just falls back to the frontend's own default.
|
||||
#[serde(default)]
|
||||
theme: Option<String>,
|
||||
}
|
||||
|
||||
fn settings_path(app: &AppHandle) -> Result<PathBuf, String> {
|
||||
@@ -327,6 +346,49 @@ pub fn save_last_naming_scheme(app: AppHandle, expression: String) -> Result<(),
|
||||
save(&app, &settings)
|
||||
}
|
||||
|
||||
/// Returns the last-saved *movie* naming-scheme expression, or `None` if nothing (or
|
||||
/// only blank text) has been saved yet - the frontend falls back to its own built-in
|
||||
/// movie default in that case. See [`last_naming_scheme`] for why movies get their
|
||||
/// own separate saved scheme instead of sharing the TV one.
|
||||
pub fn last_movie_naming_scheme(app: &AppHandle) -> Option<String> {
|
||||
load(app).last_movie_naming_scheme.filter(|t| !t.trim().is_empty())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_last_movie_naming_scheme(app: AppHandle) -> Option<String> {
|
||||
last_movie_naming_scheme(&app)
|
||||
}
|
||||
|
||||
/// Saves the movie naming-scheme editor's current text so it survives an app restart.
|
||||
/// Mirrors [`save_last_naming_scheme`] exactly, just for the separate movie scheme.
|
||||
#[tauri::command]
|
||||
pub fn save_last_movie_naming_scheme(app: AppHandle, expression: String) -> Result<(), String> {
|
||||
let mut settings = load(&app);
|
||||
let trimmed = expression.trim().to_string();
|
||||
settings.last_movie_naming_scheme = if trimmed.is_empty() { None } else { Some(expression) };
|
||||
save(&app, &settings)
|
||||
}
|
||||
|
||||
/// Returns the stored theme id, or `None` if nothing's been saved yet (the frontend
|
||||
/// then falls back to its own built-in default theme).
|
||||
pub fn theme(app: &AppHandle) -> Option<String> {
|
||||
load(app).theme.filter(|t| !t.trim().is_empty())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_theme(app: AppHandle) -> Option<String> {
|
||||
theme(&app)
|
||||
}
|
||||
|
||||
/// Saves which theme id the user picked in Settings.
|
||||
#[tauri::command]
|
||||
pub fn save_theme(app: AppHandle, theme: String) -> Result<(), String> {
|
||||
let mut settings = load(&app);
|
||||
let trimmed = theme.trim().to_string();
|
||||
settings.theme = if trimmed.is_empty() { None } else { Some(trimmed) };
|
||||
save(&app, &settings)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -414,6 +476,45 @@ mod tests {
|
||||
assert_eq!(parsed.last_naming_scheme, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stored_settings_without_movie_scheme_or_theme_fields_still_deserializes() {
|
||||
// Same schema-evolution guarantee, for the two fields the movie-naming-scheme
|
||||
// split and the theming feature added.
|
||||
let old_json = r#"{"tmdb_token":"abc123","last_naming_scheme":"{show name}"}"#;
|
||||
let parsed: StoredSettings = serde_json::from_str(old_json).unwrap();
|
||||
assert_eq!(parsed.last_naming_scheme, Some("{show name}".to_string()));
|
||||
assert_eq!(parsed.last_movie_naming_scheme, None);
|
||||
assert_eq!(parsed.theme, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_movie_naming_scheme_round_trips_and_ignores_pure_whitespace() {
|
||||
// Mirrors last_naming_scheme_round_trips_and_ignores_pure_whitespace below,
|
||||
// for the separate movie scheme.
|
||||
let mut settings = StoredSettings::default();
|
||||
settings.last_movie_naming_scheme = Some("{movie name} ({year})".to_string());
|
||||
assert_eq!(
|
||||
settings.last_movie_naming_scheme.as_deref().filter(|t| !t.trim().is_empty()),
|
||||
Some("{movie name} ({year})")
|
||||
);
|
||||
|
||||
settings.last_movie_naming_scheme = Some(" ".to_string());
|
||||
assert_eq!(
|
||||
settings.last_movie_naming_scheme.as_deref().filter(|t| !t.trim().is_empty()),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn theme_round_trips_and_ignores_pure_whitespace() {
|
||||
let mut settings = StoredSettings::default();
|
||||
settings.theme = Some("ocean".to_string());
|
||||
assert_eq!(settings.theme.as_deref().filter(|t| !t.trim().is_empty()), Some("ocean"));
|
||||
|
||||
settings.theme = Some(" ".to_string());
|
||||
assert_eq!(settings.theme.as_deref().filter(|t| !t.trim().is_empty()), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_naming_scheme_round_trips_and_ignores_pure_whitespace() {
|
||||
// Pure merge logic (load/save both need a real AppHandle, exercised end to end
|
||||
|
||||
Reference in New Issue
Block a user