Commit Graph
16 Commits
Author SHA1 Message Date
splitsec2 e575ff0fcb feat(activity): clearer cards, a Rejected view and Reopen for failed deliveries (#1618)
A batch of activity list changes from running it as an admin on my own
instance. They are separate commits that build on each other, so they
can be reviewed in order.

The cards read better. Every card gets a format pill (violet for audio,
orange for ebooks), the requester is named once, and finished cards say
how long a download took and how long ago it ended ("took 5m · 35m
ago"). A download that picked up work already under way shows a
Reattached pill.

Admins can tell what is automatic and what needs a person. A declined
request shows who declined it and when, anything with an `[auto]` note
reads "Declined automatically", and "Needs Review" becomes "No Match
Yet" with a line saying the search repeats on every sync.

There is a Rejected view next to History for admins, with Reopen on each
card and a way to hide the dead ones.

The part to read most carefully is the backend in `requests_service.py`
and `user_db.py`. An admin can now reopen a fulfilled request whose
download failed or was cancelled, and the reopen a failed download
triggers remembers which release failed, so the next search doesn't pick
it again.

I ran it on a scratch instance (when pulling them together to move from
my fork to upstream) as an admin with seeded requests and looked at the
pills, the durations, the wording and the Rejected view in a browser.
Reopening a fulfilled request I only exercised in the tests. The
frontend and Python suites, type check, ruff and basedpyright pass.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **New Features**
* Admins can review rejected requests in a dedicated view, hide or
restore cards, and reopen requests.
* Fulfilled requests with failed or cancelled deliveries can be
reopened; the failed release is skipped in the next automatic search.
* Activity cards now show format badges, download timing, and when a
download resumes existing work.
* **Improvements**
* Activity views clarify automatic retries and actions, and show
reviewer names for admins.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-11-09 12:02:21 -06:00
splitsec2 76d7df306f fix(requests): retrying a request after its client failed offers the same release again (#1677)
When a request's download fails in a way the queue can't resume, the
request is reopened and its Retry button opens the release list. The
release that just failed was drawn there as a red, disabled "Failed"
button for as long as the failed task stayed in the queue status, so you
couldn't pick it again even when the release was fine and the download
client was the problem. A bad qBittorrent password is the easy way to
hit it. Thanks @joshft91 for the report and the steps.

What changes:

- In the release list, a failed request download now shows Retry, the
same as a failure the queue can retry already does. A direct download
with nothing to retry from still shows Failed. The rule lives in one
small helper used by the release modal and both button-state paths in
`useDownloadTracking`.
- A client that refuses the login is no longer held against the release.
qbittorrent-api raises `LoginFailed` with no message for a wrong
password, and only "Forbidden" or "Unauthorized" for a bad session or
API key, so none of it matched the unreachable-client markers from #1655
and the release went into `failed_releases`. The qBittorrent client now
says "qBittorrent authentication failed (...)" for these.
- The request reopen and the auto-download retry used two copies of the
same marker list. They now share one in `status_messages.py`, which also
counts an explicit authentication failure as the client's fault. A bare
403 or 401 is not on it, since a dead .torrent link fails that way too
and that one is the release's. Genuine release failures (no seeders, a
rejected torrent, a dead .torrent link) are still remembered as #1655
intended.

Tested with a scratch instance against a mock Prowlarr and a fake
qBittorrent that rejects the login: before, the request's Retry opened
the list with the failed release as a red X; after, it shows Retry,
picking it fulfils the request again, and the request reopens without
the release in `failed_releases`. New tests cover the qBittorrent
messages, both reopen paths and the button state.

Fixes #1674


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Bug Fixes**
* Failed releases are no longer skipped after download-client connection
or login failures; download failures and stall cancellations remain
eligible to be skipped on the next search.
* Failed requests with an associated request can now show a retry
option, while direct downloads without retries remain marked as failed.
* qBittorrent login and authorization errors are reported more clearly.

* **Documentation**
* Clarified which failures cause a release to be skipped on a later
search.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-11-08 23:11:25 -07:00
splitsec2 e8ec6cc715 fix(requests): reopening after a failed download remembers the release that failed (#1655)
When a download fails, the terminal hook reopens its request through
reopen_failed_request straight away, with no admin involved. That path
cleared the release without noting that it had failed, so the next
search could pick the same release again. Only the admin reopen and the
status sync remembered it.

Both paths now add the release to failed_releases before the reopen
clears it, the same list the unattended retry already skips, and a
release that is already listed is not added twice. A download that
failed because its client could not be reached (the same four markers
auto_download uses) is not held against the release, since the same one
is fine once the client is back.

This is the upstream shape of something I have been running on my own
instance since mid October. ruff, basedpyright and the requests_service
tests (87) pass, with five new tests for the two paths, the dedupe and
the unreachable client.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Bug Fixes**
* Failed requests are reopened after non-retryable delivery failures,
with the failed release recorded when appropriate.
* Connection failures caused by an unreachable client no longer mark the
release as failed.
* Reopening a request does not record the same release more than once or
alter book details when no new release can be recorded.
* Queue status updates apply the same release-tracking behavior, while
requests that are not failed remain unchanged.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-11-04 00:41:03 -06:00
splitsec2andCaliBrain 72b5d04939 perf: cut repeated work on the status and activity paths (#1545)
Two small cuts to work that repeats on every poll or request.

The delivery-state sync behind /api/status and the activity snapshot
listed every fulfilled request (SELECT * plus a JSON parse of book_data
and release_data per row) before it looked at the queue. That history is
never purged, so an idle instance paid for it on every poll. It now
returns straight away when no queue bucket holds a task, since nothing
could map to a request then. Results and emitted updates are unchanged.
The activity snapshot gains less from this, because its status merges
download history rows, so it is usually not empty.

In proxy auth mode the before-request hook looked the user up by name
and by session id on every API call, cover images included, each on a
new SQLite connection. UserDB.get_user_cached answers repeat lookups
from memory for up to five seconds, bounded at 256 entries. create_user,
update_user and delete_user drop the cache as soon as they commit, and a
lookup that overlaps a write is not stored, so a role change or deletion
made in the app applies on the next request. Changes made outside that
UserDB instance (another process, or the separate instances some helpers
open) show within the five seconds. Only the middleware uses the cached
path; every other get_user caller is unchanged.

Each change has a test that failed before it: one counts list_requests
calls for an empty queue, the others count get_user calls across
repeated lookups and middleware runs, check expiry with a patched clock,
and check that writes and caller mutations do not leave a stale row
behind. The full suite passes (4432 passed, 48 skipped, e2e excluded),
as do ruff and basedpyright on the changed files.

I also looked at throttling the full status_update broadcast when only
the progress message changes during torrent and usenet polling, and left
it out. Those handlers poll every 2 seconds and
DOWNLOAD_PROGRESS_UPDATE_INTERVAL defaults to 1 second, so a throttle on
that interval would skip nothing at the default setting. A longer window
would slow the speed and ETA text in the UI, and without a trailing
flush it would hide the last message of a quick burst, such as "Waiting
for bot response" arriving just after "Requesting file from bot", for
the whole wait that follows.

---------

Co-authored-by: CaliBrain <calibrain@l4n.xyz>
2026-10-22 20:27:07 -04:00
splitsec2 9ba5816b83 fix(requests): an older failed attempt no longer undoes a later delivery (#1493)
A request that was retried after a failure has two tasks in the status
built from download history. `sync_delivery_states_from_queue_status`
walks the status buckets in `QueueStatus` order and keeps the last one
it sees for a request. Error comes after complete, so a request that was
delivered by its retry is set back to error and, when no retry is left,
reopened as pending. The next pass can then fetch the book again.

When a request has more than one task, an attempt that is still running
now decides its state, else a completed one, else the failure. Ties keep
the old order, so a request with a single failed task still reopens as
before.

Tests: an earlier failure doesn't undo a later delivery, a running retry
outranks an earlier failure, and a lone failure still reopens. It
happened on my instance to a request whose first try failed and whose
retry succeeded. Ruff and the backend suite pass.
2026-10-20 23:21:16 -04:00
splitsec2 a1ac017487 feat(requests): let an admin reopen a rejected request (#1491)
Closes #1445, where you said you were open to it.

A rejected request was final. `update_request` won't move a terminal
status and the admin routes only fulfil or reject, so an admin who
changed their mind had to ask the user to request the book again, and
the user kept seeing the old rejected one.

`POST /api/admin/requests/<id>/reopen` moves a rejected request back to
pending and clears the review fields. What the user asked for (request
level, release, note) is kept. It refuses anything that isn't rejected,
and refuses when that user already has a pending request for the same
book.

Admins get a Reopen action on rejected cards in the activity sidebar and
in History. A rejected request has usually been cleared from the list by
the time anyone wants it back, so reopening also clears the dismissal,
the way the failed-download reopen does. Without that the pending
request stays hidden.

Tests cover the service (state change, what is kept, the refusals, a
non-string admin note) and the route, and the card model for the new
action. Ruff, tsc, oxlint, vitest and the backend suite pass. It has
been running on my instance since Oct 8.
2026-10-20 23:20:26 -04:00
Alex d7b9f2e67f Backend test hardening + quality enforcement (#872)
- Reworked many tests
- Enforcing lint + type checking for test suite
- Fixed various issues surfaced by the new tests
- CI tweaks
2026-04-12 12:01:52 +01:00
Alex 9bfcf828ea Fixes: Env variable config usage, retry availability, Entrypoint permissions (#817)
- Clean up a few uses of config options that may miss the env variable
if this is set
- Add enhanced retry availability utilising the DB to persist download
errors / retries across restarts, request failures, and pass Prowlarr
detail through the download task to maintain retry data.
- Strip back entrypoint permissions for less intensive chown operations.

Fixes #796
2026-03-29 16:39:40 +01:00
Alex 80aa289a64 Misc fixes (#718)
- Update file movement to prefer copy
- Improved mirror config overwriting on app updates
- Request / user DB hardening
2026-03-07 10:30:47 +00:00
Alex cce2c10704 Download history refactor pt.2 (#703)
Two-phase download history: downloads are now recorded in the DB at
queue time (not just at terminal time), eliminating the need to
reconstruct metadata in the terminal hook and removing the
`_is_graduated_request_download()` request-scan mess
2026-03-05 13:06:34 +00:00
Alex 7d992c3918 User DB cleanup and refactor (#686)
- Refactored user and request code to avoid any database conflicts
- Fix threading behavior with custom script execution
- Harden the no_auth activity user filtering
- Add a hint to add local admin if none is created
- Added secret key to persist login states across updates / restarts
2026-03-02 15:41:15 +00:00
Alex ac36d539c8 Patch: Various fixes (#660)
Various fixes from the last couple days: 

- Add manual approval option for book/audiobook requests (#651)
- Add flagged HTTP headers 
- Add filesystem fallback - copy + delete when hardlink/move fails
across filesystems (#647)
- Dependency updates
- Tweak frontend test config (simplified tsconfig for tests)
- Fix overlapping sort scoring in release modal - duplicate scoring keys
caused incorrect release ordering (#654 )
- Fix stale search session after download - search state was not
refreshed when returning from a download (#659)
- Fix multi-format release filtering - releases with multiple formats
were incorrectly excluded by the format filter (#658)
- Fix config persistence when action button is used - clicking "Test
connection" reset unsaved settings (#657)
- Fix request grid text positioning in admin request policy panel 
- Fix rTorrent path discovery (#653)
- Fix `/login` API check (#650)
2026-02-25 18:44:46 +00:00
Alex ccb39e674e Patch: Request retry, admin-level requests, and various fixes (#620)
- Add request retry in the case of a download failure, admins will be prompted to attach a new file to the request
- Add admin-level "add to requests" button in the release modal
2026-02-16 12:27:56 +00:00
Alex 1931eb96a5 Feature: Notification support + Enhanced request management (#618)
- Added notification support via Apprise dependency
- Notifications can be configured globally or per user, with full
customization of events and notification type.
- Added expanded ActivityCard for increased detail of each request, file
info, and managing the attached file.
- Enhanced tests
2026-02-15 17:59:53 +00:00
Alex b7bee132a1 Requests: Various fixes and improvements (#617)
- Refactored activity backend for full user-level management, using the
db file
- Revamped the activity sidebar UX and categorisation
- Added download history and user filtering
- Added User Preferences modal, giving limited configuration for
non-admins - replaces the "restrict settings" config option.
- Many many bug fixes
- Many many new tests
2026-02-14 18:24:28 +00:00
Alex 68608b6162 Feature: Multi-user request system (#615)
- Adds a comprehensive multi-user request system to the existing
download flow
- Request configuration is policy based. Configure global settings for
content type, or narrow down policy for specific sources (E.g. allow
direct downloads, set prowlarr to request only, block IRC completely,
etc).
- Global policy configuration and per-user overrides for tailored
configs
- Replaced downloads sidebar with ActivitySidebar, combining active
downloads with requests. Admin management of user requests is done here,
and admins have view of downloads from all users. Sidebar can now be
pinned.
- Request either a standard book or a specific release. Release-requests
are used if you permit one source differently than the other. On
book-level requests, admins pick the specific file to be attached to the
fulfilled request.
- Users can request books with a note

This is WIP so some features are still not complete (notifications, more
automatic release selection, among others).
2026-02-14 11:08:20 +00:00