`with sqlite3.Connection` ends the transaction but keeps the file handle open,
so every cache class leaked one handle per instance until the garbage collector
happened to run. On Windows that makes the database file undeletable while the
process lives (WinError 32), which broke 30 cache tests in teardown and would
also block moving or clearing a cache at runtime.
All eight `with <connection>` sites now go through `closing_connection()`, and a
regression test asserts the schema init leaves no usable connection behind.
- Log expected "GID not found" tellStatus probes at DEBUG, and treat a
forgotten GID as permanent so the poll loop recovers immediately
instead of burning 4 retries x 3s of ERROR lines per cycle
- cancel_download tolerates a forgotten GID and always pops the
in-memory transfer so concurrent polls cannot re-register a
cancelled download
- Restore sweep deletes aria2 state records with no resolvable target
path instead of skipping them forever
- Clearing the download queue now also cancels in-memory tasks, removes
live aria2 transfers and drops persisted state for the cleared ids
(partial files on disk are preserved)
Distinct files of the same model version queued before a backend restart
were silently collapsed by deduplicate(), which grouped rows by
(model_id, model_version_id) only. Extract the file id from file_params
via json_extract and add it to the dedup key; rows without file identity
keep the old per-version behavior (NULL matches NULL).
In add_to_queue, check download_history before INSERT OR IGNORE. Without
this check, a fire-and-forget /queue/complete failure on the extension side
would allow the same download_id to be re-inserted after complete_download()
deleted it from the queue — creating phantom queued entries for already-
finished downloads.
- retry_from_history() and retry_all_failed() now DELETE the original
history entry after re-queuing it. Previously the old entry stayed
in history causing exponential growth on repeated retry→cancel→retry
cycles.
- Add deduplicate() called once on singleton creation to clean up
existing duplicate queue/history entries left by the bug:
1. In-status dedup (keep highest id per model+version+status)
2. Cross-status dedup (prefer completed > failed > canceled)
3. Queue dedup (keep highest rowid per model+version)
4. Orphan queue cleanup (source='retry' entries obsoleted by
terminal history entries)