Fix dedup apply lock-hang, mawk-broken tag passes; add track delete, fix button/select CSS
confirm_and_apply no longer marks candidates confirmed before their apply job actually succeeds (a stuck/skipped_lock run was silently vanishing confirmed deletes without deleting anything), switches the fuzzy pass to --apply-pairs to avoid a full rescan on every confirm, and adds a job timeout so a hung subprocess can't hold the global pipeline lock forever. dedup-library.sh's Pass 2-4 use gawk-only features (gensub, POSIX interval regex) but were running under mawk (Debian's default awk) in the deployed image, throwing silent syntax errors on every run -- switched those invocations to gawk explicitly and added it to the Dockerfile. Also: per-track delete button on the library list/detail pages, and two CSS fixes (the primary button's hover gradient was getting clobbered by the base .btn:hover rule's plain background, and the select dropdown arrow had no background-size so it rendered oversized).
This commit is contained in:
@@ -13,6 +13,14 @@ from app.services import beets_service, pipeline_runner
|
||||
EDITABLE_FIELDS = ["artist", "title", "album", "albumartist", "genres", "grouping", "track", "year"]
|
||||
|
||||
|
||||
def _run_beet_remove(item_id: int) -> subprocess.CompletedProcess:
|
||||
return subprocess.run(
|
||||
["beet", "remove", "-d", "-f", f"id:{item_id}"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
|
||||
def _run_beet_modify(item_id: int, changes: dict[str, str]) -> subprocess.CompletedProcess:
|
||||
"""subprocess with an argument list (no shell=True) -- user-submitted
|
||||
field values never pass through a shell, so there's no injection risk
|
||||
@@ -87,3 +95,32 @@ async def update_track_fields(
|
||||
)
|
||||
|
||||
return changed_fields
|
||||
|
||||
|
||||
async def delete_track(db: Session, item_id: int, deleted_by: str) -> None:
|
||||
"""Remove a single track from beets and delete its file on disk, via
|
||||
the same `beet remove -d -f` primitive the dedup passes use to delete a
|
||||
loser -- exposed directly here for "I just want this one gone" cases
|
||||
dedup doesn't cover (a bad rip, something you decided you don't want,
|
||||
etc.)."""
|
||||
item = beets_service.get_item(item_id)
|
||||
if item is None:
|
||||
raise ValueError(f"no beets item with id={item_id}")
|
||||
|
||||
result = await asyncio.to_thread(_run_beet_remove, item_id)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"beet remove failed: {result.stderr.strip()}")
|
||||
|
||||
db.add(
|
||||
ManualFixAudit(
|
||||
beets_item_id=item_id,
|
||||
file_path=item.get("path", ""),
|
||||
field="__deleted__",
|
||||
old_value=item.get("path"),
|
||||
new_value=None,
|
||||
changed_by=deleted_by,
|
||||
changed_at=time.time(),
|
||||
source="manual_delete",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
Reference in New Issue
Block a user