Skip to content

Commit 5f3ac6f

Browse files
committed
Show first-release year in candidate list with a Settings toggle
1 parent f6066db commit 5f3ac6f

5 files changed

Lines changed: 106 additions & 2 deletions

File tree

discvault/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class Config:
6767
log_to_file: bool = False
6868
blank_redundant_track_artist: bool = True
6969
dedupe_equivalent_candidates: bool = True
70+
prefer_first_release_year: bool = True
7071
manual_src_musicbrainz: bool = True
7172
manual_src_discogs: bool = True
7273
use_local_cddb_cache: bool = True
@@ -162,6 +163,10 @@ def _src_default(key: str, default: bool) -> bool:
162163
dv.get("dedupe_equivalent_candidates"),
163164
cfg.dedupe_equivalent_candidates,
164165
)
166+
cfg.prefer_first_release_year = _as_bool(
167+
dv.get("prefer_first_release_year"),
168+
cfg.prefer_first_release_year,
169+
)
165170
cfg.manual_src_musicbrainz = _as_bool(
166171
dv.get("manual_src_musicbrainz"),
167172
cfg.manual_src_musicbrainz,
@@ -230,6 +235,7 @@ def save(self) -> None:
230235
f"log_to_file = {str(self.log_to_file).lower()}",
231236
f"blank_redundant_track_artist = {str(self.blank_redundant_track_artist).lower()}",
232237
f"dedupe_equivalent_candidates = {str(self.dedupe_equivalent_candidates).lower()}",
238+
f"prefer_first_release_year = {str(self.prefer_first_release_year).lower()}",
233239
f"manual_src_musicbrainz = {str(self.manual_src_musicbrainz).lower()}",
234240
f"manual_src_discogs = {str(self.manual_src_discogs).lower()}",
235241
f"use_local_cddb_cache = {str(self.use_local_cddb_cache).lower()}",

discvault/ui/settings.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,11 @@ def _rows(self) -> list:
188188
"cfg-dedupe-equivalent",
189189
self._cfg.dedupe_equivalent_candidates,
190190
),
191+
(
192+
"Prefer first-release year",
193+
"cfg-prefer-first-release-year",
194+
self._cfg.prefer_first_release_year,
195+
),
191196
),
192197

193198
Static("Logging", classes="cfg-section-header"),
@@ -298,6 +303,7 @@ def _build_config(self) -> Config:
298303
cfg.log_to_file = self._check("cfg-log-to-file")
299304
cfg.blank_redundant_track_artist = self._check("cfg-blank-redundant-artists")
300305
cfg.dedupe_equivalent_candidates = self._check("cfg-dedupe-equivalent")
306+
cfg.prefer_first_release_year = self._check("cfg-prefer-first-release-year")
301307
completion_sound = self.query_one("#cfg-completion-sound", Select).value
302308
if completion_sound in {"bell", "chime", "both", "off"}:
303309
cfg.completion_sound = completion_sound

discvault/ui/tui.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,12 +1491,13 @@ def _enter_ready(self) -> None:
14911491
self._sync_track_selection()
14921492
table = self.query_one("#meta-table", DataTable)
14931493
table.clear(columns=True)
1494-
table.add_columns("#", "Source", "Artist", "Album", "Year", "Tracks")
1494+
table.add_columns("#", "Source", "Artist", "Album", "First release", "Year", "Tracks")
14951495
for i, m in enumerate(self._candidates, 1):
14961496
self._ensure_meta_tracks(m)
14971497
table.add_row(
14981498
str(i), m.source,
14991499
m.album_artist or "(unknown)", m.album or "(untitled)",
1500+
m.first_release_year or "—",
15001501
m.year or "—", str(m.track_count),
15011502
)
15021503
if self._candidates:
@@ -1556,6 +1557,12 @@ def _enter_ready(self) -> None:
15561557
self._auto_import_url_pending = False
15571558
self.set_timer(0, lambda: self._start_import_from_value("url", self._metadata_url))
15581559

1560+
def _year_for_input(self, m: "Metadata") -> str:
1561+
"""Pick which year populates ``#input-year`` for a candidate."""
1562+
if self._cfg.prefer_first_release_year and m.first_release_year:
1563+
return m.first_release_year
1564+
return m.year or ""
1565+
15591566
def _apply_candidate(self, idx: int) -> None:
15601567
if not self._candidates or idx >= len(self._candidates):
15611568
return
@@ -1566,7 +1573,7 @@ def _apply_candidate(self, idx: int) -> None:
15661573
if not self._args.album:
15671574
self.query_one("#input-album", Input).value = m.album or ""
15681575
if not self._args.year:
1569-
self.query_one("#input-year", Input).value = m.year or ""
1576+
self.query_one("#input-year", Input).value = self._year_for_input(m)
15701577

15711578
self._render_track_editor(m)
15721579

tests/test_config.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,36 @@ def test_dedupe_equivalent_candidates_defaults_to_true_when_missing(self) -> Non
190190

191191
self.assertTrue(cfg.dedupe_equivalent_candidates)
192192

193+
def test_prefer_first_release_year_round_trip(self) -> None:
194+
with tempfile.TemporaryDirectory() as tmp:
195+
config_path = Path(tmp) / "config.toml"
196+
old = config_mod.CONFIG_PATH
197+
config_mod.CONFIG_PATH = config_path
198+
try:
199+
cfg = config_mod.Config()
200+
cfg.prefer_first_release_year = False
201+
cfg.save()
202+
loaded = config_mod.Config.load()
203+
saved_text = config_path.read_text()
204+
finally:
205+
config_mod.CONFIG_PATH = old
206+
207+
self.assertFalse(loaded.prefer_first_release_year)
208+
self.assertIn("prefer_first_release_year = false", saved_text)
209+
210+
def test_prefer_first_release_year_defaults_to_true_when_missing(self) -> None:
211+
with tempfile.TemporaryDirectory() as tmp:
212+
config_path = Path(tmp) / "config.toml"
213+
config_path.write_text("[discvault]\n")
214+
old = config_mod.CONFIG_PATH
215+
config_mod.CONFIG_PATH = config_path
216+
try:
217+
cfg = config_mod.Config.load()
218+
finally:
219+
config_mod.CONFIG_PATH = old
220+
221+
self.assertTrue(cfg.prefer_first_release_year)
222+
193223
def test_lookup_log_timings_round_trip(self) -> None:
194224
with tempfile.TemporaryDirectory() as tmp:
195225
config_path = Path(tmp) / "config.toml"

tests/test_tui.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,5 +1106,60 @@ def test_ensure_meta_tracks_omits_trailing_extra_track_from_editor(self) -> None
11061106
self.assertEqual(app._possible_extra_tracks(meta), [13])
11071107

11081108

1109+
class YearForInputTests(unittest.TestCase):
1110+
def _app(self, *, prefer_first: bool) -> DiscvaultApp:
1111+
cfg = Config()
1112+
cfg.prefer_first_release_year = prefer_first
1113+
args = Namespace(
1114+
tracks=None, metadata_file=None, metadata_url=None,
1115+
mp3_bitrate=320, mp3_quality=2, flac_compression=8,
1116+
no_image=False, no_flac=False, no_mp3=False, ogg=False,
1117+
opus=False, alac=False, aac=False, wav=False, iso=False,
1118+
artist=None, album=None, year=None,
1119+
)
1120+
return DiscvaultApp(args, cfg)
1121+
1122+
def _meta(self, *, year: str = "", first: str = "") -> Metadata:
1123+
return Metadata(
1124+
source="X",
1125+
album_artist="Artist",
1126+
album="Album",
1127+
year=year,
1128+
first_release_year=first,
1129+
)
1130+
1131+
def test_prefer_first_with_both_years_returns_first(self) -> None:
1132+
app = self._app(prefer_first=True)
1133+
self.assertEqual(app._year_for_input(self._meta(year="2006", first="1969")), "1969")
1134+
1135+
def test_prefer_first_with_only_pressing_year_returns_pressing(self) -> None:
1136+
app = self._app(prefer_first=True)
1137+
self.assertEqual(app._year_for_input(self._meta(year="2006")), "2006")
1138+
1139+
def test_prefer_first_with_only_first_year_returns_first(self) -> None:
1140+
app = self._app(prefer_first=True)
1141+
self.assertEqual(app._year_for_input(self._meta(first="1969")), "1969")
1142+
1143+
def test_prefer_off_returns_pressing_year_even_when_first_is_set(self) -> None:
1144+
app = self._app(prefer_first=False)
1145+
self.assertEqual(app._year_for_input(self._meta(year="2006", first="1969")), "2006")
1146+
1147+
def test_prefer_off_with_no_pressing_year_returns_empty_string(self) -> None:
1148+
app = self._app(prefer_first=False)
1149+
self.assertEqual(app._year_for_input(self._meta(first="1969")), "")
1150+
1151+
1152+
class CandidateTableColumnsTests(unittest.TestCase):
1153+
def test_table_includes_first_release_column(self) -> None:
1154+
# Source-level invariant: the candidate table headers list `First release`
1155+
# between `Album` and `Year`. Verifying the source is enough; rendering
1156+
# requires an active Textual App and is exercised manually.
1157+
source = inspect.getsource(DiscvaultApp._enter_ready)
1158+
self.assertIn(
1159+
'"#", "Source", "Artist", "Album", "First release", "Year", "Tracks"',
1160+
source,
1161+
)
1162+
1163+
11091164
if __name__ == "__main__":
11101165
unittest.main()

0 commit comments

Comments
 (0)