Add batch duplicate review workflow
This commit is contained in:
@@ -26,6 +26,7 @@ from serato_doctor.scanner import scan_filesystem
|
||||
from serato_doctor.repair import (
|
||||
BACKUP_FOLDER,
|
||||
apply_duplicate_repair,
|
||||
apply_duplicate_repair_batch,
|
||||
list_backups,
|
||||
plan_duplicate_repair,
|
||||
restore_backup,
|
||||
@@ -324,6 +325,53 @@ def duplicate_repair(
|
||||
return result
|
||||
|
||||
|
||||
def duplicate_repair_batch(
|
||||
serato: Path,
|
||||
music: Path,
|
||||
choices: Iterable[dict],
|
||||
backup_limit: Optional[int],
|
||||
apply: bool = False,
|
||||
) -> dict:
|
||||
"""Validate and preview or apply several keeper choices together."""
|
||||
serato = serato.expanduser().resolve()
|
||||
music = music.expanduser().resolve()
|
||||
if not serato.is_dir() or not music.is_dir():
|
||||
raise ValueError("Analyze the library again before repairing duplicates")
|
||||
groups = find_duplicate_groups(scan_filesystem(music).tracks)
|
||||
valid_groups = [
|
||||
{track.path.resolve() for track in group.tracks} for group in groups
|
||||
]
|
||||
plans = []
|
||||
selected_groups = set()
|
||||
for choice in choices:
|
||||
requested = tuple(Path(value).expanduser().resolve() for value in choice["group_files"])
|
||||
group_key = frozenset(requested)
|
||||
if set(requested) not in valid_groups:
|
||||
raise ValueError("A duplicate group changed; analyze the library again")
|
||||
if group_key in selected_groups:
|
||||
raise ValueError("A duplicate group was selected more than once")
|
||||
selected_groups.add(group_key)
|
||||
plans.append(
|
||||
plan_duplicate_repair(Path(choice["keeper"]), requested, serato)
|
||||
)
|
||||
if not plans:
|
||||
raise ValueError("Choose at least one duplicate group")
|
||||
replaced = [str(path) for plan in plans for path in plan.replaced]
|
||||
result = {
|
||||
"choice_count": len(plans),
|
||||
"replaced": replaced,
|
||||
"metadata_backups": len(plans[0].metadata_files),
|
||||
"strategy": "shortcut",
|
||||
"database_v2_modified": False,
|
||||
}
|
||||
if apply:
|
||||
receipt = apply_duplicate_repair_batch(plans, serato, backup_limit)
|
||||
result.update({"applied": True, "backup": str(receipt.backup)})
|
||||
else:
|
||||
result["applied"] = False
|
||||
return result
|
||||
|
||||
|
||||
def backup_history(serato: Path) -> dict:
|
||||
serato = serato.expanduser().resolve()
|
||||
if not serato.is_dir():
|
||||
@@ -354,6 +402,8 @@ class SeratoDoctorHandler(BaseHTTPRequestHandler):
|
||||
try:
|
||||
token = parse_qs(request.query)["token"][0]
|
||||
self._audio_response(_verified_audio(token))
|
||||
except (BrokenPipeError, ConnectionResetError):
|
||||
return
|
||||
except (KeyError, IndexError, OSError, ValueError) as error:
|
||||
self._json_response(404, {"error": str(error)})
|
||||
return
|
||||
@@ -379,6 +429,8 @@ class SeratoDoctorHandler(BaseHTTPRequestHandler):
|
||||
"/api/analyze",
|
||||
"/api/duplicates/preview",
|
||||
"/api/duplicates/apply",
|
||||
"/api/duplicates/batch/preview",
|
||||
"/api/duplicates/batch/apply",
|
||||
"/api/backups/restore",
|
||||
"/api/backups",
|
||||
"/api/reveal",
|
||||
@@ -412,6 +464,16 @@ class SeratoDoctorHandler(BaseHTTPRequestHandler):
|
||||
raise ValueError("That backup does not belong to this library")
|
||||
restored = restore_backup(backup)
|
||||
result = {"restored": [str(path) for path in restored]}
|
||||
elif self.path.startswith("/api/duplicates/batch/"):
|
||||
raw_limit = payload.get("backup_limit", 10)
|
||||
backup_limit = None if raw_limit is None else int(raw_limit)
|
||||
result = duplicate_repair_batch(
|
||||
Path(payload["serato"]),
|
||||
Path(payload["music"]),
|
||||
payload["choices"],
|
||||
backup_limit,
|
||||
apply=self.path.endswith("/apply"),
|
||||
)
|
||||
else:
|
||||
raw_limit = payload.get("backup_limit", 10)
|
||||
backup_limit = None if raw_limit is None else int(raw_limit)
|
||||
|
||||
Reference in New Issue
Block a user