Skip to content

Add paused and queued Deluge states for Download Cleaner#596

Merged
Flaminel merged 2 commits into
mainfrom
add_deluge_dc_statuses
May 4, 2026
Merged

Add paused and queued Deluge states for Download Cleaner#596
Flaminel merged 2 commits into
mainfrom
add_deluge_dc_statuses

Conversation

@Flaminel

@Flaminel Flaminel commented May 2, 2026

Copy link
Copy Markdown
Contributor

Relates to #594

Summary by Sourcery

Handle Deluge torrent states via a strongly typed enum and extend seeding detection to include finished paused/queued torrents while hardening null/edge-case handling.

Enhancements:

  • Introduce a DelugeState enum and JSON converter to represent torrent states in a type-safe, case-insensitive way with unknown-state fallback.
  • Update Deluge download item and service logic to use the new DelugeState enum, including treating finished paused/queued torrents as seeding candidates and ignoring entries without hashes.

Tests:

  • Expand and adjust Deluge-related tests to cover enum-based states, stalled/downloading logic, seeding selection rules for paused/queued/finished torrents, and DownloadStatus JSON deserialization including unknown or missing states.

@Flaminel

Flaminel commented May 2, 2026

Copy link
Copy Markdown
Contributor Author

@sourcery-ai review

@Flaminel

Flaminel commented May 2, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review

@sourcery-ai

sourcery-ai Bot commented May 2, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduces a strongly-typed DelugeState enum and JSON converter, wires it into DownloadStatus deserialization and Deluge client field selection, and updates seeding/ downloading logic and tests so that finished paused/queued torrents are treated as seeding while null/invalid hashes and unknown states are handled safely.

Sequence diagram for GetSeedingDownloads with new DelugeState handling

sequenceDiagram
    actor Cleaner
    participant DelugeServiceDC
    participant DelugeClientWrapper as ClientWrapper
    participant DownloadStatus
    participant DelugeItemWrapper

    Cleaner->>DelugeServiceDC: GetSeedingDownloads()
    DelugeServiceDC->>ClientWrapper: GetStatusForAllTorrents()
    ClientWrapper-->>DelugeServiceDC: List<DownloadStatus> downloads or null

    alt downloads is null
        DelugeServiceDC-->>Cleaner: empty List<ITorrentItemWrapper>
    else downloads not null
        loop for each DownloadStatus x in downloads
            alt x.Hash is null or empty
                DelugeServiceDC-->>DelugeServiceDC: skip x
            else if x.State == DelugeState.Seeding
                DelugeServiceDC->>DelugeItemWrapper: new DelugeItemWrapper(x)
                DelugeItemWrapper-->>DelugeServiceDC: wrapper
            else if x.IsFinished == true and (x.State == DelugeState.Paused or x.State == DelugeState.Queued)
                DelugeServiceDC->>DelugeItemWrapper: new DelugeItemWrapper(x)
                DelugeItemWrapper-->>DelugeServiceDC: wrapper
            else
                DelugeServiceDC-->>DelugeServiceDC: skip x
            end
        end
        DelugeServiceDC-->>Cleaner: List<ITorrentItemWrapper> seedingWrappers
    end
Loading

Class diagram for DelugeState enum and related download status handling

classDiagram
    class DownloadStatus {
        string Hash
        DelugeState State
        string Name
        ulong Eta
        long DownloadSpeed
        bool Private
        long Size
        long TotalDone
        bool IsFinished
        string Label
    }

    class DelugeItemWrapper {
        +DownloadStatus Info
        +bool IsDownloading()
        +bool IsStalled()
    }

    class DelugeServiceDC {
        +Task~List~<ITorrentItemWrapper> GetSeedingDownloads()
    }

    class DelugeStateConverter {
        +DelugeState ReadJson(JsonReader reader, Type objectType, DelugeState existingValue, bool hasExistingValue, JsonSerializer serializer)
        +void WriteJson(JsonWriter writer, DelugeState value, JsonSerializer serializer)
    }

    class DelugeState {
        <<enumeration>>
        Unknown
        Allocating
        Checking
        Downloading
        Seeding
        Paused
        Error
        Queued
        Moving
    }

    DownloadStatus --> DelugeState : uses
    DelugeStateConverter ..> DelugeState : converts
    DownloadStatus ..> DelugeStateConverter : JsonConverter
    DelugeItemWrapper --> DownloadStatus : wraps
    DelugeServiceDC --> DownloadStatus : filters
    DelugeServiceDC --> DelugeItemWrapper : creates
Loading

File-Level Changes

Change Details Files
Replace raw string Deluge state handling with a strongly-typed DelugeState enum and custom JSON converter, and extend DownloadStatus to include is_finished.
  • Add DelugeState enum with the set of known Deluge torrent states and an Unknown fallback, decorated with a custom JsonConverter.
  • Implement DelugeStateConverter to map wire string values to DelugeState case-insensitively and default unknown/missing/non-string values to DelugeState.Unknown.
  • Change DownloadStatus.State from nullable string to DelugeState and add an IsFinished property mapped from the is_finished field.
code/backend/Cleanuparr.Domain/Enums/DelugeState.cs
code/backend/Cleanuparr.Domain/Enums/DelugeStateConverter.cs
code/backend/Cleanuparr.Domain/Entities/Deluge/Response/DownloadStatus.cs
Update Deluge client and wrapper logic to use the new DelugeState enum for downloading, stalled, and seeding determination, and to treat finished paused/queued torrents as seeding while skipping empty hashes and null result sets.
  • Adjust DelugeServiceDC.GetSeedingDownloads filtering to accept Seeding or finished Paused/Queued torrents, while still excluding entries with empty hashes.
  • Update DelugeItemWrapper.IsDownloading and IsStalled to use pattern matching on DelugeState instead of string comparisons.
  • Ensure DelugeClient requests the is_finished field from Deluge so that seeding logic can distinguish finished paused/queued torrents.
  • Add new unit tests in DelugeServiceDCTests to cover null download lists, empty hashes, and the inclusion/exclusion of paused/queued torrents based on IsFinished, and adjust existing tests to use DelugeState values.
code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceDC.cs
code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeItemWrapper.cs
code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeClient.cs
code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/DelugeServiceDCTests.cs
Align Deluge-related tests with enum-based state handling and verify JSON deserialization behavior for known/unknown/missing/null state values.
  • Convert DelugeServiceTests and DelugeItemWrapperTests to construct DownloadStatus with DelugeState values instead of raw state strings.
  • Add DownloadStatusDeserializationTests to assert that DelugeStateConverter handles unknown, case-varied, missing, and null state fields correctly while preserving other fields.
  • Update DelugeItemWrapper IsDownloading and IsStalled theory data to use DelugeState values including Unknown.
code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/DelugeServiceTests.cs
code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/DelugeItemWrapperTests.cs
code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/DownloadStatusDeserializationTests.cs

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="code/backend/Cleanuparr.Domain/Entities/Deluge/Response/DownloadStatus.cs" line_range="10" />
<code_context>
-    public string? State { get; init; }
-    
+
+    public DelugeState State { get; init; }
+
     public string? Name { get; init; }
</code_context>
<issue_to_address>
**issue (bug_risk):** Enum-based State with StringEnumConverter may throw on unexpected Deluge values instead of defaulting to Unknown.

Changing from a nullable string to a non-nullable enum improves the model, but with `StringEnumConverter` any new/invalid state value from Deluge will now cause deserialization to fail instead of flowing through. To keep behavior resilient to unexpected/protocol-changed states, consider a custom `JsonConverter` (or handler) for `DelugeState` that maps unknown strings to `DelugeState.Unknown` instead of throwing.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@greptile-apps

greptile-apps Bot commented May 2, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds support for Paused and Queued Deluge torrent states in the Download Cleaner by introducing a typed DelugeState enum to replace previous string-based comparisons, and adds an IsFinished flag to DownloadStatus to correctly identify completed torrents that happen to be in those states. The GetSeedingDownloads filter now includes Paused/Queued torrents when is_finished is true, ensuring they are eligible for cleanup rules alongside seeding torrents.

Confidence Score: 5/5

Safe to merge — focused, well-tested change with no logic defects.

All changed files are covered by tests; no P0/P1 issues found. The enum replaces fragile string comparisons cleanly, the IsFinished guard correctly scopes Paused/Queued inclusion, and the existing JSON error-suppression in DelugeClient handles any unknown future state values safely.

No files require special attention.

Important Files Changed

Filename Overview
code/backend/Cleanuparr.Domain/Enums/DelugeState.cs New enum replacing string state values; covers all documented Deluge states with Unknown=0 as a safe default for unrecognised values.
code/backend/Cleanuparr.Domain/Entities/Deluge/Response/DownloadStatus.cs State property changed from nullable string to DelugeState enum; IsFinished property added and mapped to is_finished JSON key.
code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceDC.cs GetSeedingDownloads now includes Paused/Queued torrents with IsFinished=true alongside Seeding torrents; logic is correct and well-scoped.
code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeClient.cs is_finished field added to the fields list requested from the Deluge API; change is minimal and correct.
code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/DelugeServiceDCTests.cs Comprehensive new tests for Paused/Queued finished and not-finished cases; old case-insensitivity test correctly removed and replaced with hash-filtering test.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[GetSeedingDownloads] --> B[GetStatusForAllTorrents]
    B --> C{downloads is null?}
    C -- Yes --> D[Return empty list]
    C -- No --> E[Filter empty hashes]
    E --> F{State?}
    F -- Seeding --> G[Include torrent]
    F -- Paused --> H{IsFinished?}
    F -- Queued --> H
    F -- Other --> I[Exclude torrent]
    H -- true --> G
    H -- false --> I
    G --> J[Wrap in DelugeItemWrapper]
    J --> K[Return list]
Loading

Reviews (1): Last reviewed commit: "added paused and queued Deluge statuses ..." | Re-trigger Greptile

@codecov

codecov Bot commented May 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 69.23077% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...nd/Cleanuparr.Domain/Enums/DelugeStateConverter.cs 71.42% 2 Missing ⚠️
...ure/Features/DownloadClient/Deluge/DelugeClient.cs 0.00% 1 Missing ⚠️
...eatures/DownloadClient/Deluge/DelugeItemWrapper.cs 50.00% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@Flaminel

Flaminel commented May 2, 2026

Copy link
Copy Markdown
Contributor Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In DelugeStateConverter.WriteJson, consider emitting the exact wire format Deluge expects (e.g., lowercase strings and possibly null/omitted for Unknown) instead of value.ToString() in PascalCase, to avoid subtle integration issues if these states are ever sent back to Deluge or logged for debugging.
  • The seeding filter in DelugeServiceDC.GetSeedingDownloads has become a bit dense with the combined State and IsFinished checks; extracting this into a small helper (e.g., IsConsideredSeeding(DownloadStatus x)) would make the business rules around seeding/finished/paused/queued states easier to understand and maintain.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `DelugeStateConverter.WriteJson`, consider emitting the exact wire format Deluge expects (e.g., lowercase strings and possibly `null`/omitted for `Unknown`) instead of `value.ToString()` in PascalCase, to avoid subtle integration issues if these states are ever sent back to Deluge or logged for debugging.
- The seeding filter in `DelugeServiceDC.GetSeedingDownloads` has become a bit dense with the combined `State` and `IsFinished` checks; extracting this into a small helper (e.g., `IsConsideredSeeding(DownloadStatus x)`) would make the business rules around seeding/finished/paused/queued states easier to understand and maintain.

## Individual Comments

### Comment 1
<location path="code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceDC.cs" line_range="24" />
<code_context>
         return downloads
             .Where(x => !string.IsNullOrEmpty(x.Hash))
-            .Where(x => x.State?.Equals("seeding", StringComparison.InvariantCultureIgnoreCase) is true)
+            .Where(x => x.State is DelugeState.Seeding || x is { IsFinished: true, State: DelugeState.Paused or DelugeState.Queued })
             .Select(ITorrentItemWrapper (x) => new DelugeItemWrapper(x))
             .ToList();
</code_context>
<issue_to_address>
**suggestion:** The seeding filter mixes `State` and `IsFinished` in a single pattern; extracting this into a named predicate would reduce the risk of subtle logic mistakes.

This line now encodes three rules at once: always include `Seeding`, include `Paused` when finished, and include `Queued` when finished. While correct per the tests, it’s dense and likely to become fragile as states evolve (e.g., new states or `Unknown`). Extracting a named predicate such as `bool IsConsideredSeeding(DownloadStatus x)` (or an equivalent method on `DownloadStatus`) would make the intent clearer and centralize seeding semantics in one place.

Suggested implementation:

```csharp
            .Where(x => !string.IsNullOrEmpty(x.Hash))
            .Where(IsConsideredSeeding)
            .Select(ITorrentItemWrapper (x) => new DelugeItemWrapper(x))

```

To fully implement the suggestion, you should also introduce a dedicated predicate method that encapsulates the seeding semantics. For example, inside the `DelugeServiceDC` class (or wherever this query lives), add:

```csharp
private static bool IsConsideredSeeding(DownloadStatus x)
{
    if (x is null)
    {
        return false;
    }

    return x.State is DelugeState.Seeding ||
           x is { IsFinished: true, State: DelugeState.Paused or DelugeState.Queued };
}
```

Adjust `DownloadStatus` to the actual type of `downloads`’ elements if it differs. If nulls cannot appear in `downloads`, you can omit the null check. Centralizing the logic in this method makes the intent clearer and future changes to seeding semantics easier and safer.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@Flaminel Flaminel merged commit 48c36fa into main May 4, 2026
14 checks passed
@Flaminel Flaminel deleted the add_deluge_dc_statuses branch May 4, 2026 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant