This code handled the RD-specific behaviour of choosing which files to download, and only getting those links from the API, rather than what DL does: downloading all files and having the api consumer pick which links they want to download.
Uses `TorrentClientTorrent.Files` instead of `.Links` to get download links. Also adds `DownloadLink` to `Files` when `Map`ping to `TorrentClientTorrent`
`DownloadableFileFilter` is an abstraction of the filtering we do in most of the `TorrentClient`s.
It operates on a single file at a time, not the entire torrent.
Tests:
- `GetDownloadLinks`
- `IncludeRegex` filter works
- `ExcludeRegex` filter works
- `DownloadMinSize` filter works
- only `IncludeRegex` is used when both `IncludeRegex` and `ExcludeRegex` are set
- gets links for all files if none of the above filters are set
- returns null if `torrent.RdId` is null
- `GetTorrents`
- [Both full/partial sync] returns empty list when no torrents present in api response
- [full sync] overrides any previous torrents in cache, deleting those not present in the api response
- [partial sync] updates torrents in cache according to the api response
- [partial sync] adds torrents to cache according to the api response
- `Map` (private so tested via `GetTorrents`)
- Computes progress correctly from `Downloaded / Size`
- `UpdateData`
- [`torrentClientTorrent != null`] does not get from api, updates the `torrent` accordingly
- [`torrentClientTorrent == null`] gets from api, updates the `torrent` accordingly
- [`torrentClientTorrent == null`] when torrent deleted from rd, updates `RdStatusRaw = "deleted""`
- when `torrent.RdId == null`, returns the `torrent` unmodified
Tests:
- [Both overloads] uses the `download.Link` to guess the filenamem if it's not present
- [Both overloads] returns null if `download.Link` is null
- [Both overloads] returns nuyll if `download.RdName` is null
- [Both overloads] works for a file in a deeply nested subdirectory
- [overload with `downloadPath` argument only] creates the correct directory