diff --git a/README.md b/README.md index f36f398..546665e 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Looking to **upgrade from V1 to V2**? Look [here](#upgrading-from-v1-to-v2) - [REMOVE_SLOW](#remove_slow) - [REMOVE_STALLED](#remove_stalled) - [REMOVE_UNMONITORED](#remove_unmonitored) - - [REMOVE_COMPLETED](#remove_completed) + - [REMOVE_DONE_SEEDING](#remove_done_seeding) - [SEARCH_CUTOFF_UNMET](#search_unmet_cutoff) - [SEARCH_MISSING](#search_missing) - [DETECT_DELETIONS](#detect_deletions) @@ -68,7 +68,7 @@ Feature overview: - Removing downloads that are repeatedly have been found to be slow (remove_slow) - Removing downloads that are stalled (remove_stalled) - Removing downloads belonging to movies/series/albums etc. that have been marked as "unmonitored" (remove_unmonitored) -- Removing completed downloads from your download client that match certain criteria (remove_completed) +- Removing completed downloads from your download client that match certain criteria (remove_done_seeding) - Periodically searching for better content on movies/series/albums etc. where cutoff has not been reached yet (search_cutoff_unmet) - Periodically searching for missing content that has not yet been found (search_missing) @@ -227,7 +227,7 @@ services: # As written above, these can also be set as Job Defaults so you don't have to specify them as granular as below. # REMOVE_BAD_FILES: | # keep_archives: True - # REMOVE_COMPLETED: | + # REMOVE_DONE_SEEDING: | # target_tags: # - "Obsolete" # target_categories: @@ -501,11 +501,12 @@ This is the interesting section. It defines which job you want decluttarr to run - This may be helpful if you use a tool such as [unpackerr](https://github.com/Unpackerr/unpackerr) that can handle it - However, you may also find that these packages may contain bad/malicious files (which will not removed by decluttarr) -#### REMOVE_COMPLETED +#### REMOVE_DONE_SEEDING -- Removes completed downloads from the download client's queue when they meet your selection criteria (tags and/or categories). -- What "completed" means: - - Downloads are considered completed when the seeding goals configured in your download client are met: either the ratio limit or the seeding time limit (per-torrent overrides or the global limits). +- Removes downloads that are completed and are done with seeding from the download client's queue when they meet your selection criteria (tags and/or categories). +- "Done Seeding" means the seeding goals configured in your download client are met: + - Either the ratio limit is reached + - Or the seeding time limit (per-torrent overrides or the global limits). - Type: Boolean or Dict - Permissible Values: - If Bool: True, False diff --git a/config/config_example.yaml b/config/config_example.yaml index 317d659..71ec755 100644 --- a/config/config_example.yaml +++ b/config/config_example.yaml @@ -17,7 +17,7 @@ job_defaults: jobs: remove_bad_files: # keep_archives: true - remove_completed: + remove_done_seeding: # target_tags: # - "Obsolete" # target_categories: diff --git a/src/job_manager.py b/src/job_manager.py index b050ae4..978d10b 100644 --- a/src/job_manager.py +++ b/src/job_manager.py @@ -1,6 +1,6 @@ # Cleans the download queue from src.jobs.remove_bad_files import RemoveBadFiles -from src.jobs.remove_completed import RemoveCompleted +from src.jobs.remove_done_seeding import RemoveDoneSeeding from src.jobs.remove_failed_downloads import RemoveFailedDownloads from src.jobs.remove_failed_imports import RemoveFailedImports from src.jobs.remove_metadata_missing import RemoveMetadataMissing @@ -176,7 +176,7 @@ class JobManager: Each job is included if the corresponding attribute exists and is truthy in settings.jobs. """ download_client_job_classes = { - "remove_completed": RemoveCompleted, + "remove_done_seeding": RemoveDoneSeeding, } jobs = [] diff --git a/src/jobs/remove_completed.py b/src/jobs/remove_done_seeding.py similarity index 87% rename from src/jobs/remove_completed.py rename to src/jobs/remove_done_seeding.py index d1e8ae2..e83541f 100644 --- a/src/jobs/remove_completed.py +++ b/src/jobs/remove_done_seeding.py @@ -11,7 +11,7 @@ COMPLETED_STATES = [ ] -class RemoveCompleted(DownloadClientRemovalJob): +class RemoveDoneSeeding(DownloadClientRemovalJob): """Job to remove completed torrents that match specific tags or categories.""" SUPPORTED_CLIENTS: ClassVar[list[str]] = ["qbittorrent"] @@ -19,7 +19,7 @@ class RemoveCompleted(DownloadClientRemovalJob): async def run(self) -> int: if self.download_client_type not in self.SUPPORTED_CLIENTS: logger.debug( - f"remove_completed.py/run: Skipping job '{self.job_name}' for unsupported client {self.download_client.name}.", + f"remove_done_seeding.py/run: Skipping job '{self.job_name}' for unsupported client {self.download_client.name}.", ) return 0 @@ -34,7 +34,7 @@ class RemoveCompleted(DownloadClientRemovalJob): if not target_tags and not target_categories: logger.debug( - "remove_completed.py/_get_items_to_remove: No target tags or categories specified for remove_completed job.", + "remove_done_seeding.py/_get_items_to_remove: No target tags or categories specified for remove_done_seeding job.", ) return [] @@ -47,7 +47,7 @@ class RemoveCompleted(DownloadClientRemovalJob): for item in items_to_remove: logger.debug( - f"remove_completed.py/_get_items_to_remove: Found completed item to remove: {item.get('name', 'unknown')}", + f"remove_done_seeding.py/_get_items_to_remove: Found completed item to remove: {item.get('name', 'unknown')}", ) return items_to_remove diff --git a/src/settings/_jobs.py b/src/settings/_jobs.py index e480434..41f3994 100644 --- a/src/settings/_jobs.py +++ b/src/settings/_jobs.py @@ -89,7 +89,7 @@ class Jobs: def _set_job_defaults(self): self.remove_bad_files = JobParams(keep_archives=self.job_defaults.keep_archives) - self.remove_completed = JobParams(target_tags=self.job_defaults.target_tags) + self.remove_done_seeding = JobParams(target_tags=self.job_defaults.target_tags) self.remove_failed_downloads = JobParams() self.remove_failed_imports = JobParams( message_patterns=self.job_defaults.message_patterns, diff --git a/src/settings/_user_config.py b/src/settings/_user_config.py index bb1f2b8..6cc4ac8 100644 --- a/src/settings/_user_config.py +++ b/src/settings/_user_config.py @@ -28,7 +28,7 @@ CONFIG_MAPPING = { ], "jobs": [ "REMOVE_BAD_FILES", - "REMOVE_COMPLETED", + "REMOVE_DONE_SEEDING", "REMOVE_FAILED_DOWNLOADS", "REMOVE_FAILED_IMPORTS", "REMOVE_METADATA_MISSING", diff --git a/tests/jobs/test_remove_completed.py b/tests/jobs/test_remove_completed.py index ce2f849..4e6db2b 100644 --- a/tests/jobs/test_remove_completed.py +++ b/tests/jobs/test_remove_completed.py @@ -1,19 +1,19 @@ -"""Tests for the remove_completed job.""" +"""Tests for the remove_done_seeding job.""" from unittest.mock import AsyncMock, MagicMock, patch import pytest -from src.jobs.remove_completed import COMPLETED_STATES, RemoveCompleted +from src.jobs.remove_done_seeding import COMPLETED_STATES, RemoveDoneSeeding def create_mock_settings(target_tags=None, target_categories=None): """Create mock settings for testing.""" settings = MagicMock() settings.jobs = MagicMock() - settings.jobs.remove_completed.enabled = True - settings.jobs.remove_completed.target_tags = target_tags or [] - settings.jobs.remove_completed.target_categories = target_categories or [] + settings.jobs.remove_done_seeding.enabled = True + settings.jobs.remove_done_seeding.target_tags = target_tags or [] + settings.jobs.remove_done_seeding.target_categories = target_categories or [] settings.general = MagicMock() settings.general.protected_tag = "protected" return settings @@ -119,19 +119,19 @@ ITEM_DEFAULTS = { ), ], ) -async def test_remove_completed_logic( +async def test_remove_done_seeding_logic( item_properties: dict, target_tags: list, target_categories: list, should_be_removed: bool, ): - """Test the logic of the remove_completed job with various scenarios.""" + """Test the logic of the remove_done_seeding job with various scenarios.""" item = {**ITEM_DEFAULTS, **item_properties, "name": "test_item"} settings = create_mock_settings(target_tags, target_categories) client = create_mock_download_client([item]) - job = RemoveCompleted(client, "qbittorrent", settings, "remove_completed") + job = RemoveDoneSeeding(client, "qbittorrent", settings, "remove_done_seeding") items_to_remove = await job._get_items_to_remove(await client.get_qbit_items()) @@ -143,11 +143,11 @@ async def test_remove_completed_logic( @pytest.mark.asyncio -async def test_remove_completed_skipped_for_sabnzbd(): - """Test that the remove_completed job is skipped for SABnzbd clients.""" +async def test_remove_done_seeding_skipped_for_sabnzbd(): + """Test that the remove_done_seeding job is skipped for SABnzbd clients.""" settings = create_mock_settings() client = create_mock_download_client([], client_name="mock_client_name") - job = RemoveCompleted(client, "sabnzbd", settings, "remove_completed") + job = RemoveDoneSeeding(client, "sabnzbd", settings, "remove_done_seeding") # Test that the job returns 0 for unsupported clients result = await job.run() @@ -158,7 +158,7 @@ async def test_remove_completed_skipped_for_sabnzbd(): @pytest.mark.asyncio -async def test_remove_completed_test_run_enabled(): +async def test_remove_done_seeding_test_run_enabled(): """Test that no items are actually removed when test_run is enabled.""" item = { **ITEM_DEFAULTS, @@ -170,7 +170,7 @@ async def test_remove_completed_test_run_enabled(): settings = create_mock_settings(target_tags=["tag1"]) settings.general.test_run = True client = create_mock_download_client([item]) - job = RemoveCompleted(client, "qbittorrent", settings, "remove_completed") + job = RemoveDoneSeeding(client, "qbittorrent", settings, "remove_done_seeding") with patch.object( client, @@ -188,7 +188,7 @@ async def test_remove_completed_test_run_enabled(): @pytest.mark.asyncio @pytest.mark.parametrize("protected_on", ["tag", "category"]) -async def test_remove_completed_with_protected_item(protected_on): +async def test_remove_done_seeding_with_protected_item(protected_on): """Test that items with a protected tag or category are not removed.""" item_properties = {"ratio": 2, "ratio_limit": 2, "name": "protected_item"} target_tags = ["tag1"] @@ -209,7 +209,7 @@ async def test_remove_completed_with_protected_item(protected_on): target_categories=target_categories, ) client = create_mock_download_client([item]) - job = RemoveCompleted(client, "qbittorrent", settings, "remove_completed") + job = RemoveDoneSeeding(client, "qbittorrent", settings, "remove_done_seeding") with patch.object( job, @@ -224,7 +224,9 @@ async def test_remove_completed_with_protected_item(protected_on): @pytest.mark.asyncio async def test_is_completed_logic(): """Test the internal _is_completed logic with different states and limits.""" - job = RemoveCompleted(MagicMock(), "qbittorrent", MagicMock(), "remove_completed") + job = RemoveDoneSeeding( + MagicMock(), "qbittorrent", MagicMock(), "remove_done_seeding" + ) # Completed states for state in COMPLETED_STATES: