diff --git a/CHANGELOG b/CHANGELOG
index 54c61427b..483be2bc9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -14,4 +14,5 @@ Fixes an issue where Prime Video overlays/collections would not be built when th
Fixed the `cast` search option for the `imdb_search` builder
Fixes #2258 `imdb_list` sort was not being parsed correctly
Fixes `letterboxd_list` rating filter to use a 1-10 rating vs 1-100 to reflect how letterboxd ratings work on their website
+Fixes #2274 Enhance handling of smart collections in deletion
Fixed the `ids_to_anidb` lookup for anime movies and shows
diff --git a/docs/config/operations.md b/docs/config/operations.md
index ed06c5f5e..e30f42142 100644
--- a/docs/config/operations.md
+++ b/docs/config/operations.md
@@ -83,6 +83,8 @@ You can create individual blocks of operations by using a list under `operations
`configured: true` | Collection must be Configured to be deleted (collection is in the config file of the specific Kometa run) |
`configured: false` | Collection must be Unconfigured to be deleted (collection is not in the config file of the specific Kometa run) |
`less: ###` | Collection must contain less than the given number of items to be deleted. ### is a Number greater than 0 Optional value which if undefined means collections will be deleted regardless of how many items they have |
+ `ignore_empty_smart_collections: false` | Do not apply less check to empty smart collections This allows retaining things like smart collections that show movies without subtitles or the like. |
+
**The collection does not need to be scheduled to be considered configured and only needs to be in the config file.**
diff --git a/json-schema/config-schema.json b/json-schema/config-schema.json
index 77d07edce..3cfd1905e 100644
--- a/json-schema/config-schema.json
+++ b/json-schema/config-schema.json
@@ -2080,7 +2080,8 @@
"properties": {
"configured": { "type": "boolean" },
"managed": { "type": "boolean" },
- "less": { "type": "integer" }
+ "less": { "type": "integer" },
+ "ignore_empty_smart_collections": { "type": "boolean" }
},
"required": []
},
diff --git a/json-schema/kitchen_sink_config.yml b/json-schema/kitchen_sink_config.yml
index 4a826bbfb..71941947b 100644
--- a/json-schema/kitchen_sink_config.yml
+++ b/json-schema/kitchen_sink_config.yml
@@ -344,7 +344,8 @@ libraries:
delete_collections:
configured: false # False - Collection must be an Unconfigured Collection to be deleted (collection is not in the config file of the specific Kometa run).
managed: false # False - Collection must be an Unmanaged Collection to be deleted (the collection does not have the Kometa label)
- less: 99999 # Effectively all collections regardless of teh number of items in the collection
+ less: 99999 # Effectively all collections regardless of the number of items in the collection
+ ignore_empty_smart_collections: true # Don't do the less check on empty smart collections
mass_user_rating_update: mdb_tomatoes # Update user ratings with mdb_tomatoes
mass_critic_rating_update: imdb # Update critic ratings with imdb
mass_audience_rating_update: tmdb # Update audience ratings with tmdb
@@ -758,7 +759,8 @@ libraries:
delete_collections:
configured: false # False - Collection must be an Unconfigured Collection to be deleted (collection is not in the config file of the specific Kometa run).
managed: false # False - Collection must be an Unmanaged Collection to be deleted (the collection does not have the Kometa label)
- less: 99999 # Effectively all collections regardless of teh number of items in the collection
+ less: 99999 # Effectively all collections regardless of the number of items in the collection
+ ignore_empty_smart_collections: true # Don't do the less check on empty smart collections
mass_user_rating_update: mdb_tomatoes # Update user ratings with mdb_tomatoes
mass_critic_rating_update: imdb # Update critic ratings with imdb
mass_audience_rating_update: tmdb # Update audience ratings with tmdb
@@ -859,8 +861,8 @@ webhooks: # Can be individually specif
version: (redacted)
delete: (redacted)
plex: # Can be individually specified per library as well; REQUIRED for the script to run
- url: (redacted)
- token: (redacted)
+ url: http://10.10.10.10:1234
+ token: THIS_IS_A_FAKE_TOKEN
timeout: 60
clean_bundles: true
empty_trash: true
@@ -873,19 +875,19 @@ tmdb: # REQUIRED for the script to
region: CA # Upper case ISO 3166-1 Code
cache_expiration: 60
tautulli: # Can be individually specified per library as well
- url: (redacted)
- apikey: (redacted)
+ url: http://10.10.10.10:1234
+ apikey: THIS_IS_A_FAKE_TOKEN
omdb:
- apikey: (redacted)
+ apikey: THIS_IS_A_FAKE_TOKEN
cache_expiration: 60
mdblist:
- apikey: (redacted)
+ apikey: THIS_IS_A_FAKE_TOKEN
cache_expiration: 60
notifiarr:
- apikey: (redacted)
+ apikey: THIS_IS_A_FAKE_TOKEN
radarr: # Can be individually specified per library as well
- url: (redacted)
- token: (redacted)
+ url: http://10.10.10.10:1234
+ token: THIS_IS_A_FAKE_TOKEN
root_folder_path: /data/media/movies
monitor: true
availability: announced
@@ -900,8 +902,8 @@ radarr: # Can be individually specified
ignore_cache: false
monitor_existing: false
sonarr: # Can be individually specified per library as well
- url: (redacted)
- token: (redacted)
+ url: http://10.10.10.10:1234
+ token: THIS_IS_A_FAKE_TOKEN
root_folder_path: /data/media/tv
monitor: all
quality_profile: Any
@@ -920,7 +922,7 @@ sonarr: # Can be individually specified
monitor_existing: false
anidb:
client: (redacted)
- version: (redacted)
+ version: 1234
language: en
cache_expiration: 60
username: (redacted)
diff --git a/json-schema/prototype_config.yml b/json-schema/prototype_config.yml
index 015770a4d..91b297c18 100644
--- a/json-schema/prototype_config.yml
+++ b/json-schema/prototype_config.yml
@@ -388,6 +388,7 @@ libraries:
managed: false
configured: true
less: 123
+ ignore_empty_smart_collections: true
Anime:
collection_files:
diff --git a/modules/config.py b/modules/config.py
index 09dac1cd0..9289f8106 100644
--- a/modules/config.py
+++ b/modules/config.py
@@ -964,6 +964,7 @@ def check_for_attribute(data, attribute, parent=None, test_list=None, translatio
"managed": check_for_attribute(input_dict, "managed", var_type="bool", default_is_none=True, save=False),
"configured": check_for_attribute(input_dict, "configured", var_type="bool", default_is_none=True, save=False),
"less": check_for_attribute(input_dict, "less", var_type="int", default_is_none=True, save=False, int_min=1),
+ "ignore_empty_smart_collections": check_for_attribute(input_dict, "ignore_empty_smart_collections", var_type="bool", default=True, save=False),
}
elif op == "mass_collection_content_rating_update":
section_final[op] = {
diff --git a/modules/operations.py b/modules/operations.py
index a35a98b5f..1a681cfcb 100644
--- a/modules/operations.py
+++ b/modules/operations.py
@@ -64,14 +64,19 @@ def run_operations(self):
logger.debug(f"Item Operation: {self.library.items_library_operation}")
logger.debug("")
- def should_be_deleted(col_in, labels_in, configured_in, managed_in, less_in):
+ def should_be_deleted(col_in, labels_in, configured_in, managed_in, less_in, ignore_smart_in):
if all((x is None for x in [configured_in, managed_in, less_in])):
return False
- less_check = True
+ less_check = not ignore_smart_in if col_in.smart else True
if less_in is not None:
- less_check = col_in.childCount < less_in
- logger.trace(f"{col_in.title} - collection size: {col_in.childCount} < less: {less_in}, DELETE: {less_check}")
+ if less_check:
+ col_count = col_in.childCount if col_in.childCount is not None else 0
+ less_check = col_count < less_in
+ logger.trace(f"{col_in.title} - collection size: {col_count} < less: {less_in}, DELETE: {less_check}")
+ else:
+ logger.trace(f"{col_in.title} - skipping size check: smart - {col_in.smart}, ignore_smart - {ignore_smart_in}")
+
managed_check = True
if managed_in is not None:
@@ -1071,6 +1076,7 @@ def get_batch_info(placement, total, display_attr, total_count, display_value=No
less = self.library.delete_collections["less"] if self.library.delete_collections and self.library.delete_collections["less"] is not None else None
managed = self.library.delete_collections["managed"] if self.library.delete_collections else None
configured = self.library.delete_collections["configured"] if self.library.delete_collections else None
+ ignore_smart = self.library.delete_collections["ignore_empty_smart_collections"] if self.library.delete_collections else True
unmanaged_collections = []
unconfigured_collections = []
all_collections = self.library.get_all_collections()
@@ -1079,7 +1085,7 @@ def get_batch_info(placement, total, display_attr, total_count, display_value=No
col = self.library.reload(col, force=True)
labels = [la.tag for la in self.library.item_labels(col)]
- if should_be_deleted(col, labels, configured, managed, less):
+ if should_be_deleted(col, labels, configured, managed, less, ignore_smart):
try:
self.library.delete(col)
logger.info(f"{col.title} Deleted")