Skip to content

Commit 68782ab

Browse files
authored
Merge pull request #229 from pSpitzner/restore_state_candidate_search
Fixes around Candidate Search
2 parents eecdea2 + 0182998 commit 68782ab

File tree

8 files changed

+53
-19
lines changed

8 files changed

+53
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- Archive files can now be deleted [#217](https://github.com/pSpitzner/beets-flask/issues/217)
2121
- Import Bootleg Button now works as expected [#218](https://github.com/pSpitzner/beets-flask/issues/218)
2222
- Startup script was not executed correctly if placed in `/config/beets-flask/startup.sh` [#227](https://github.com/pSpitzner/beets-flask/pull/227)
23+
- Another state-related bug around Searching for Candidates [#225](https://github.com/pSpitzner/beets-flask/issues/225). We now no longer require a certaint type of state before allowing to add candidates.
2324

2425
### Added
2526

backend/beets_flask/importer/session.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
from .pipeline import AsyncPipeline
6363
from .stages import (
6464
StageOrder,
65+
finalize,
6566
group_albums,
6667
identify_duplicates,
6768
lookup_candidates,
@@ -355,6 +356,10 @@ def lookup_candidates(self, task: importer.ImportTask):
355356
f"This session should not reach this stage. {self.__class__.__name__}"
356357
)
357358

359+
def finalize(self, task: importer.ImportTask):
360+
"""Last stage called and customizable any session."""
361+
self.logger.debug(f"Finalized {self} {task}")
362+
358363
# ---------------------------------- Run --------------------------------- #
359364

360365
def run_sync(self) -> SessionState:
@@ -457,6 +462,7 @@ def stages(self) -> StageOrder:
457462
stages.append(lookup_candidates(self))
458463

459464
stages.append(identify_duplicates(self))
465+
stages.append(finalize(self))
460466

461467
return stages
462468

@@ -502,6 +508,7 @@ class AddCandidatesSession(PreviewSession):
502508
"""
503509

504510
search: TaskIdMapping[Search | Literal["skip"]]
511+
initial_task_states: dict[str, ProgressState]
505512

506513
def __init__(
507514
self,
@@ -512,16 +519,17 @@ def __init__(
512519
):
513520
super().__init__(state, config_overlay, **kwargs)
514521

515-
if state.progress != Progress.PREVIEW_COMPLETED:
516-
raise ValueError("Cannot run AddCandidatesSession on non-preview state.")
517-
518522
# None means skip search for this task
519523
self.search = parse_task_id_mapping(search, "skip")
524+
self.initial_task_states = {}
520525

521-
# Reset task progress only for tasks that have search values
522-
# other tasks are skipped
523526
for task in self.state.task_states:
527+
self.initial_task_states[task.id] = deepcopy(task.progress)
524528
if task.progress >= Progress.PREVIEW_COMPLETED:
529+
# Reset task progress only for tasks that have search values
530+
# other tasks are skipped
531+
# This should only be relevant for searches on multiple tasks
532+
# (i.e. folders)
525533
s = self.search[task.id]
526534
if s != "skip":
527535
task.set_progress(Progress.LOOKING_UP_CANDIDATES - 1)
@@ -537,13 +545,18 @@ def lookup_candidates(self, task: importer.ImportTask):
537545
log.debug(f"Skipping search for {task_state.id=}")
538546
return
539547

548+
# Beets treats empty strings like real strings, which is not what we want here.
549+
# TODO: revisit once PR merged upstream: https://github.com/beetbox/beets/pull/6117
540550
if (
541551
search["search_artist"] is not None
542552
and search["search_artist"].strip() == ""
543553
):
544554
search["search_artist"] = None
545555
if search["search_album"] is not None and search["search_album"].strip() == "":
546556
search["search_album"] = None
557+
search["search_ids"] = list(
558+
filter(lambda x: x.strip() != "", search["search_ids"])
559+
)
547560

548561
log.debug(f"Using {search=} for {task_state.id=}, {task_state.paths=}")
549562

@@ -606,6 +619,20 @@ def lookup_candidates(self, task: importer.ImportTask):
606619
)
607620
self.state.exc = None
608621

622+
def finalize(self, task: importer.ImportTask):
623+
"""Restore initial taks and session states."""
624+
625+
task_state = self.state.get_task_state_for_task_raise(task)
626+
if task_state.id in self.initial_task_states:
627+
task_state.set_progress(self.initial_task_states[task_state.id])
628+
else:
629+
log.warning(
630+
f"Task {task_state.id} not in initial task states. "
631+
+ "Cannot restore previous progress."
632+
)
633+
634+
self.logger.debug(f"Finalized {self} {task}")
635+
609636

610637
class ImportSession(BaseSession):
611638
"""
@@ -730,6 +757,7 @@ def stages(self):
730757

731758
# finally, move files
732759
stages.append(manipulate_files(self))
760+
stages.append(finalize(self))
733761

734762
return stages
735763

@@ -1062,6 +1090,7 @@ async def run_async(self) -> SessionState:
10621090

10631091
@property
10641092
def stages(self):
1093+
# This tweaked session skips the pipeline
10651094
return StageOrder()
10661095

10671096

backend/beets_flask/importer/stages.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from beets_flask import log
3939
from beets_flask.server.exceptions import (
4040
NoCandidatesFoundException,
41+
NotImportedException,
4142
)
4243

4344
from .progress import Progress, ProgressState
@@ -549,7 +550,10 @@ def plugin_stage(
549550

550551
@mutator_stage
551552
@skip_until(Progress.MATCH_THRESHOLD)
552-
@set_progress(Progress.MATCH_THRESHOLD)
553+
@set_progress(
554+
Progress.MATCH_THRESHOLD,
555+
on_error={NotImportedException: Progress.PREVIEW_COMPLETED},
556+
)
553557
def match_threshold(
554558
session: AutoImportSession,
555559
task: ImportTask,
@@ -630,6 +634,16 @@ def manipulate_files(
630634
return task
631635

632636

637+
@stage
638+
def finalize(
639+
session: BaseSession,
640+
task: ImportTask,
641+
) -> ImportTask:
642+
"""Give our custom sessions a way to do something at the very end."""
643+
session.finalize(task)
644+
return task
645+
646+
633647
def _apply_choice(session: ImportSession, task: ImportTask):
634648
# tweaked version of beets apply_choices.
635649
# we do not want to rely on the global config object

backend/beets_flask/invoker/enqueue.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
UndoSession,
3737
delete_from_beets,
3838
)
39-
from beets_flask.importer.states import Progress
4039
from beets_flask.importer.types import DuplicateAction
4140
from beets_flask.logger import log
4241
from beets_flask.redis import import_queue, preview_queue
@@ -491,12 +490,6 @@ async def run_preview_add_candidates(
491490
with db_session_factory() as db_session:
492491
s_state_live = _get_live_state_by_folder(hash, path, db_session)
493492

494-
if s_state_live.progress != Progress.PREVIEW_COMPLETED:
495-
raise InvalidUsageException(
496-
f"Session state not in preview completed state for {hash=} "
497-
+ f"Found state: {s_state_live.progress}"
498-
)
499-
500493
a_session = AddCandidatesSession(
501494
s_state_live,
502495
search=search,

backend/tests/integration/test_flows.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,13 @@
2020
SessionStateInDb,
2121
)
2222
from beets_flask.disk import Folder
23-
from beets_flask.importer.progress import FolderStatus
23+
from beets_flask.importer.progress import FolderStatus, Progress
2424
from beets_flask.importer.session import (
2525
CandidateChoice,
2626
TaskIdMappingArg,
2727
)
2828
from beets_flask.importer.types import DuplicateAction
2929
from beets_flask.invoker.enqueue import (
30-
Progress,
3130
run_import_auto,
3231
run_import_bootleg,
3332
run_import_candidate,

backend/tests/integration/test_routes/test_exceptions.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
2-
31
class TestErrorHandling:
42
"""Tests our exception handling capabilities."""
53

backend/tests/unit/test_setup.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ def test_log():
1414
assert log.name == "beets-flask"
1515

1616

17-
18-
1917
def test_config():
2018
"""Test that config is correctly set up for testing."""
2119
import tempfile

frontend/src/components/common/chips.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ export function FolderStatusChip({
230230
status_name = "Failed";
231231
if (folderStatus.exc?.type === "NoCandidatesFoundException") {
232232
status_name = "No Match";
233+
} else if (folderStatus.exc?.type === "NotImportedException") {
234+
status_name = "Threshold";
233235
}
234236
break;
235237
default:

0 commit comments

Comments
 (0)