Skip to content

[Architecture] Responsability of download client adapters#599

Open
T4g1 wants to merge 5 commits into
Listenarrs:canaryfrom
T4g1:feature/adapter-harmonization
Open

[Architecture] Responsability of download client adapters#599
T4g1 wants to merge 5 commits into
Listenarrs:canaryfrom
T4g1:feature/adapter-harmonization

Conversation

@T4g1
Copy link
Copy Markdown
Contributor

@T4g1 T4g1 commented May 17, 2026

Summary

As discussed on Discord and in #592, IDownloadClientAdapter implementations do not yet have a clear resonsability list. This PR aim at fixing that and defining a cleaner interface for future adapter integration while trying to keep it open for new features.

The main changes is the removal of FetchDownloadsAsync from Adapters, as context, they were moved from DownloadMonitor I believe and it was already a duplicate of GetQueueAsync.
I also removed unused GetRecentHistoryAsync and stubs of methods to use DownloadClientItem instead of QueueItem.

I found CombineWithBasePath was duplicated like 6 or 7 times so I removed it too.

Changes

Changed

  • Adapters can now support multiple protocoles, adapter factory is aligned with that behavior

Fixed

  • Moved SharpCompress to 0.48.1 to fix the vulnerability issue
  • No more warning on build

Removed

  • Client adapters logic to fetch updated downloads are now unified
  • Removed stub of left over refactoring

Notes

  • When the same is done on the provider side, we should probably fix the DownloadService and harden the IDownloadClientGateway so it shield the download creation better (right now, the mixing of indexer/adapter logic while adding downloads makes that very difficult to do)
  • I still need to fix Linux/Mac specific tests

@T4g1 T4g1 requested a review from a team May 17, 2026 17:13
@T4g1 T4g1 added the patch patch version bump - backward compatible bug fixes label May 17, 2026
@T4g1 T4g1 requested a review from therobbiedavis May 17, 2026 21:04
Copy link
Copy Markdown
Collaborator

@therobbiedavis therobbiedavis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some logic questions, also this needs to be rebased.


public async Task<List<Download>> FetchDownloadsAsync(DownloadClientConfiguration client, List<Download> downloads, CancellationToken ct = default)
{
var ids = GetExternalIds(downloads);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This path now only polls downloads that already have a stored external ID. If a legacy (or hopefully not a new record) is missing ClientDownloadId/TorrentHash, the adapter gets an empty ID list and the download can stay stuck without progress updates. Probably a reasonable tradeoff though, but maybe we should include a view for users to see "orphaned" records? wdyt

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe other *arr silently remove the download in case it becomes orphaned (no external ID or external ID no longer found in the download client), this seems like a good idea as in that case, automatic search will probably pick it up again later or the user can re-start a manual search (audiobook would be shown in the wanted list with no active download on it).
Also, I expect the majority of orphaned download to come from, either migration to this version or later (one time issue and the software is in canary phase anyway) or from download client manualy removing the queue item from their end (probably due to user input or configuration)
So, if we remove them directly (or even after retrying some times to get it from the adapter), by design we no longer have orphaned download and thus the view is probably not useful :p But if we dont go in that direction, then yes, we could bring a new oprhaned status meaning the download can no longer be linked with anything on the adapter side and the user could then remove only or remove and re-search them

I can see an issue with removing orphaned download: If the adapter is no longer reachable or experience technical issues, then it will send an empty list of queue items and we may delete them by error in those case. We can mitigate that by always checking if the adapter is up and running (using the TestAdapter method) first

{
var adapter = ResolveAdapter(client);
return adapter.GetRecentHistoryAsync(client, limit, ct);
var items = await adapter.GetQueueAsync(client, ids, ct);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this changes the queue endpoint fetching the client queue to fetching only downloads already tracked in our DB. That drops untracked queue items and completed external downloads before DownloadQueueService can reconcile or display them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reasoning is the following: If all adapters have to return an external ID when we add a download: It means that every Listenarr Download has an external ID. So, any Download without an external ID should be dropped and any queue item without external ID should be ignored.

By fetching the whole queue and deciding what to use or not in the application, we gather informations that are not related to Listenarr and should probably never leave the adapter layer (by design, it solves some issues I witnessed where all queue items are shown for an adapter which is not expected)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch patch version bump - backward compatible bug fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Architecture] Responsability of download client adapters Transmission: Activity shows unrelated transfers

2 participants