qBitrr2 5.6.2__py3-none-any.whl → 5.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qBitrr/arss.py +237 -88
- qBitrr/bundled_data.py +2 -2
- qBitrr/main.py +232 -10
- qBitrr/static/assets/ArrView.js +1 -1
- qBitrr/static/assets/ArrView.js.map +1 -1
- qBitrr/static/assets/ConfigView.js +5 -4
- qBitrr/static/assets/ConfigView.js.map +1 -1
- qBitrr/static/assets/LogsView.js +1 -1
- qBitrr/static/assets/LogsView.js.map +1 -1
- qBitrr/static/assets/ProcessesView.js +1 -1
- qBitrr/static/assets/ProcessesView.js.map +1 -1
- qBitrr/static/assets/app.js +2 -2
- qBitrr/static/assets/app.js.map +1 -1
- qBitrr/static/assets/vendor.js +1 -1
- qBitrr/static/assets/vendor.js.map +1 -1
- qBitrr/tables.py +7 -0
- qBitrr/webui.py +48 -1
- {qbitrr2-5.6.2.dist-info → qbitrr2-5.7.0.dist-info}/METADATA +24 -2
- {qbitrr2-5.6.2.dist-info → qbitrr2-5.7.0.dist-info}/RECORD +23 -23
- {qbitrr2-5.6.2.dist-info → qbitrr2-5.7.0.dist-info}/WHEEL +0 -0
- {qbitrr2-5.6.2.dist-info → qbitrr2-5.7.0.dist-info}/entry_points.txt +0 -0
- {qbitrr2-5.6.2.dist-info → qbitrr2-5.7.0.dist-info}/licenses/LICENSE +0 -0
- {qbitrr2-5.6.2.dist-info → qbitrr2-5.7.0.dist-info}/top_level.txt +0 -0
qBitrr/arss.py
CHANGED
|
@@ -147,36 +147,36 @@ class Arr:
|
|
|
147
147
|
self.logger = logging.getLogger(f"qBitrr.{self._name}")
|
|
148
148
|
run_logs(self.logger, self._name)
|
|
149
149
|
|
|
150
|
+
# Set completed_folder path (used for category creation and file monitoring)
|
|
150
151
|
if not QBIT_DISABLED:
|
|
151
152
|
try:
|
|
153
|
+
# Check default instance for existing category configuration
|
|
152
154
|
categories = self.manager.qbit_manager.client.torrent_categories.categories
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
path
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
else:
|
|
160
|
-
self.logger.trace("Category exists without save path")
|
|
161
|
-
self.completed_folder = pathlib.Path(COMPLETED_DOWNLOAD_FOLDER).joinpath(
|
|
162
|
-
self.category
|
|
163
|
-
)
|
|
164
|
-
except KeyError:
|
|
155
|
+
categ = categories.get(self.category)
|
|
156
|
+
if categ and categ.get("savePath"):
|
|
157
|
+
self.logger.trace("Category exists with save path [%s]", categ["savePath"])
|
|
158
|
+
self.completed_folder = pathlib.Path(categ["savePath"])
|
|
159
|
+
else:
|
|
160
|
+
self.logger.trace("Category does not exist or lacks save path")
|
|
165
161
|
self.completed_folder = pathlib.Path(COMPLETED_DOWNLOAD_FOLDER).joinpath(
|
|
166
162
|
self.category
|
|
167
163
|
)
|
|
168
|
-
self.manager.qbit_manager.client.torrent_categories.create_category(
|
|
169
|
-
self.category, save_path=self.completed_folder
|
|
170
|
-
)
|
|
171
164
|
except Exception as e:
|
|
172
165
|
self.logger.warning(
|
|
173
|
-
"Could not connect to qBittorrent during initialization for %s: %s.
|
|
166
|
+
"Could not connect to qBittorrent during initialization for %s: %s. Using default path.",
|
|
174
167
|
self._name,
|
|
175
|
-
str(e).split("\n")[0] if "\n" in str(e) else str(e),
|
|
168
|
+
str(e).split("\n")[0] if "\n" in str(e) else str(e),
|
|
176
169
|
)
|
|
177
170
|
self.completed_folder = pathlib.Path(COMPLETED_DOWNLOAD_FOLDER).joinpath(
|
|
178
171
|
self.category
|
|
179
172
|
)
|
|
173
|
+
# Ensure category exists on ALL instances (deferred to avoid __init__ failures)
|
|
174
|
+
try:
|
|
175
|
+
self._ensure_category_on_all_instances()
|
|
176
|
+
except Exception as e:
|
|
177
|
+
self.logger.warning(
|
|
178
|
+
"Could not ensure category on all instances during init: %s", e
|
|
179
|
+
)
|
|
180
180
|
else:
|
|
181
181
|
self.completed_folder = pathlib.Path(COMPLETED_DOWNLOAD_FOLDER).joinpath(self.category)
|
|
182
182
|
|
|
@@ -673,6 +673,57 @@ class Arr:
|
|
|
673
673
|
)
|
|
674
674
|
self.logger.hnotice("Starting %s monitor", self._name)
|
|
675
675
|
|
|
676
|
+
def _ensure_category_on_all_instances(self) -> None:
|
|
677
|
+
"""
|
|
678
|
+
Ensure the Arr category exists on ALL qBittorrent instances.
|
|
679
|
+
|
|
680
|
+
Creates the category with the completed_folder save path on each instance.
|
|
681
|
+
Logs errors but continues if individual instances fail.
|
|
682
|
+
"""
|
|
683
|
+
if QBIT_DISABLED:
|
|
684
|
+
return
|
|
685
|
+
|
|
686
|
+
qbit_manager = self.manager.qbit_manager
|
|
687
|
+
all_instances = qbit_manager.get_all_instances()
|
|
688
|
+
|
|
689
|
+
self.logger.debug(
|
|
690
|
+
"Ensuring category '%s' exists on %d qBit instance(s)",
|
|
691
|
+
self.category,
|
|
692
|
+
len(all_instances),
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
for instance_name in all_instances:
|
|
696
|
+
try:
|
|
697
|
+
client = qbit_manager.get_client(instance_name)
|
|
698
|
+
if client is None:
|
|
699
|
+
self.logger.warning(
|
|
700
|
+
"Skipping category creation on instance '%s' (client unavailable)",
|
|
701
|
+
instance_name,
|
|
702
|
+
)
|
|
703
|
+
continue
|
|
704
|
+
|
|
705
|
+
categories = client.torrent_categories.categories
|
|
706
|
+
if self.category not in categories:
|
|
707
|
+
client.torrent_categories.create_category(
|
|
708
|
+
self.category, save_path=str(self.completed_folder)
|
|
709
|
+
)
|
|
710
|
+
self.logger.info(
|
|
711
|
+
"Created category '%s' on instance '%s'", self.category, instance_name
|
|
712
|
+
)
|
|
713
|
+
else:
|
|
714
|
+
self.logger.debug(
|
|
715
|
+
"Category '%s' already exists on instance '%s'",
|
|
716
|
+
self.category,
|
|
717
|
+
instance_name,
|
|
718
|
+
)
|
|
719
|
+
except Exception as e:
|
|
720
|
+
self.logger.error(
|
|
721
|
+
"Failed to ensure category '%s' on instance '%s': %s",
|
|
722
|
+
self.category,
|
|
723
|
+
instance_name,
|
|
724
|
+
str(e).split("\n")[0] if "\n" in str(e) else str(e),
|
|
725
|
+
)
|
|
726
|
+
|
|
676
727
|
@staticmethod
|
|
677
728
|
def _humanize_request_tag(tag: str) -> str | None:
|
|
678
729
|
if not tag:
|
|
@@ -785,7 +836,9 @@ class Arr:
|
|
|
785
836
|
"""Returns True if the State is categorized as Downloading."""
|
|
786
837
|
return torrent.state_enum in (TorrentStates.DOWNLOADING, TorrentStates.PAUSED_DOWNLOAD)
|
|
787
838
|
|
|
788
|
-
def in_tags(
|
|
839
|
+
def in_tags(
|
|
840
|
+
self, torrent: TorrentDictionary, tag: str, instance_name: str = "default"
|
|
841
|
+
) -> bool:
|
|
789
842
|
return_value = False
|
|
790
843
|
if TAGLESS:
|
|
791
844
|
if tag == "qBitrr-ignored":
|
|
@@ -796,15 +849,20 @@ class Arr:
|
|
|
796
849
|
.where(
|
|
797
850
|
(self.torrents.Hash == torrent.hash)
|
|
798
851
|
& (self.torrents.Category == torrent.category)
|
|
852
|
+
& (self.torrents.QbitInstance == instance_name)
|
|
799
853
|
)
|
|
800
854
|
.execute()
|
|
801
855
|
)
|
|
802
856
|
if not query:
|
|
803
857
|
self.torrents.insert(
|
|
804
|
-
Hash=torrent.hash,
|
|
858
|
+
Hash=torrent.hash,
|
|
859
|
+
Category=torrent.category,
|
|
860
|
+
QbitInstance=instance_name,
|
|
805
861
|
).on_conflict_ignore().execute()
|
|
806
|
-
condition = (
|
|
807
|
-
self.torrents.
|
|
862
|
+
condition = (
|
|
863
|
+
(self.torrents.Hash == torrent.hash)
|
|
864
|
+
& (self.torrents.Category == torrent.category)
|
|
865
|
+
& (self.torrents.QbitInstance == instance_name)
|
|
808
866
|
)
|
|
809
867
|
if tag == "qBitrr-allowed_seeding":
|
|
810
868
|
condition &= self.torrents.AllowedSeeding == True
|
|
@@ -832,7 +890,9 @@ class Arr:
|
|
|
832
890
|
self.logger.trace("Tag %s not in %s", tag, torrent.name)
|
|
833
891
|
return False
|
|
834
892
|
|
|
835
|
-
def remove_tags(
|
|
893
|
+
def remove_tags(
|
|
894
|
+
self, torrent: TorrentDictionary, tags: list, instance_name: str = "default"
|
|
895
|
+
) -> None:
|
|
836
896
|
for tag in tags:
|
|
837
897
|
self.logger.trace("Removing tag %s from %s", tag, torrent.name)
|
|
838
898
|
if TAGLESS:
|
|
@@ -842,32 +902,39 @@ class Arr:
|
|
|
842
902
|
.where(
|
|
843
903
|
(self.torrents.Hash == torrent.hash)
|
|
844
904
|
& (self.torrents.Category == torrent.category)
|
|
905
|
+
& (self.torrents.QbitInstance == instance_name)
|
|
845
906
|
)
|
|
846
907
|
.execute()
|
|
847
908
|
)
|
|
848
909
|
if not query:
|
|
849
910
|
self.torrents.insert(
|
|
850
|
-
Hash=torrent.hash,
|
|
911
|
+
Hash=torrent.hash,
|
|
912
|
+
Category=torrent.category,
|
|
913
|
+
QbitInstance=instance_name,
|
|
851
914
|
).on_conflict_ignore().execute()
|
|
852
915
|
if tag == "qBitrr-allowed_seeding":
|
|
853
916
|
self.torrents.update(AllowedSeeding=False).where(
|
|
854
917
|
(self.torrents.Hash == torrent.hash)
|
|
855
918
|
& (self.torrents.Category == torrent.category)
|
|
919
|
+
& (self.torrents.QbitInstance == instance_name)
|
|
856
920
|
).execute()
|
|
857
921
|
elif tag == "qBitrr-imported":
|
|
858
922
|
self.torrents.update(Imported=False).where(
|
|
859
923
|
(self.torrents.Hash == torrent.hash)
|
|
860
924
|
& (self.torrents.Category == torrent.category)
|
|
925
|
+
& (self.torrents.QbitInstance == instance_name)
|
|
861
926
|
).execute()
|
|
862
927
|
elif tag == "qBitrr-allowed_stalled":
|
|
863
928
|
self.torrents.update(AllowedStalled=False).where(
|
|
864
929
|
(self.torrents.Hash == torrent.hash)
|
|
865
930
|
& (self.torrents.Category == torrent.category)
|
|
931
|
+
& (self.torrents.QbitInstance == instance_name)
|
|
866
932
|
).execute()
|
|
867
933
|
elif tag == "qBitrr-free_space_paused":
|
|
868
934
|
self.torrents.update(FreeSpacePaused=False).where(
|
|
869
935
|
(self.torrents.Hash == torrent.hash)
|
|
870
936
|
& (self.torrents.Category == torrent.category)
|
|
937
|
+
& (self.torrents.QbitInstance == instance_name)
|
|
871
938
|
).execute()
|
|
872
939
|
else:
|
|
873
940
|
with contextlib.suppress(Exception):
|
|
@@ -883,7 +950,9 @@ class Arr:
|
|
|
883
950
|
),
|
|
884
951
|
)
|
|
885
952
|
|
|
886
|
-
def add_tags(
|
|
953
|
+
def add_tags(
|
|
954
|
+
self, torrent: TorrentDictionary, tags: list, instance_name: str = "default"
|
|
955
|
+
) -> None:
|
|
887
956
|
for tag in tags:
|
|
888
957
|
self.logger.trace("Adding tag %s from %s", tag, torrent.name)
|
|
889
958
|
if TAGLESS:
|
|
@@ -893,32 +962,39 @@ class Arr:
|
|
|
893
962
|
.where(
|
|
894
963
|
(self.torrents.Hash == torrent.hash)
|
|
895
964
|
& (self.torrents.Category == torrent.category)
|
|
965
|
+
& (self.torrents.QbitInstance == instance_name)
|
|
896
966
|
)
|
|
897
967
|
.execute()
|
|
898
968
|
)
|
|
899
969
|
if not query:
|
|
900
970
|
self.torrents.insert(
|
|
901
|
-
Hash=torrent.hash,
|
|
971
|
+
Hash=torrent.hash,
|
|
972
|
+
Category=torrent.category,
|
|
973
|
+
QbitInstance=instance_name,
|
|
902
974
|
).on_conflict_ignore().execute()
|
|
903
975
|
if tag == "qBitrr-allowed_seeding":
|
|
904
976
|
self.torrents.update(AllowedSeeding=True).where(
|
|
905
977
|
(self.torrents.Hash == torrent.hash)
|
|
906
978
|
& (self.torrents.Category == torrent.category)
|
|
979
|
+
& (self.torrents.QbitInstance == instance_name)
|
|
907
980
|
).execute()
|
|
908
981
|
elif tag == "qBitrr-imported":
|
|
909
982
|
self.torrents.update(Imported=True).where(
|
|
910
983
|
(self.torrents.Hash == torrent.hash)
|
|
911
984
|
& (self.torrents.Category == torrent.category)
|
|
985
|
+
& (self.torrents.QbitInstance == instance_name)
|
|
912
986
|
).execute()
|
|
913
987
|
elif tag == "qBitrr-allowed_stalled":
|
|
914
988
|
self.torrents.update(AllowedStalled=True).where(
|
|
915
989
|
(self.torrents.Hash == torrent.hash)
|
|
916
990
|
& (self.torrents.Category == torrent.category)
|
|
991
|
+
& (self.torrents.QbitInstance == instance_name)
|
|
917
992
|
).execute()
|
|
918
993
|
elif tag == "qBitrr-free_space_paused":
|
|
919
994
|
self.torrents.update(FreeSpacePaused=True).where(
|
|
920
995
|
(self.torrents.Hash == torrent.hash)
|
|
921
996
|
& (self.torrents.Category == torrent.category)
|
|
997
|
+
& (self.torrents.QbitInstance == instance_name)
|
|
922
998
|
).execute()
|
|
923
999
|
else:
|
|
924
1000
|
with contextlib.suppress(Exception):
|
|
@@ -1159,7 +1235,7 @@ class Arr:
|
|
|
1159
1235
|
def _process_imports(self) -> None:
|
|
1160
1236
|
if self.import_torrents:
|
|
1161
1237
|
self.needs_cleanup = True
|
|
1162
|
-
for torrent in self.import_torrents:
|
|
1238
|
+
for torrent, instance_name in self.import_torrents:
|
|
1163
1239
|
if torrent.hash in self.sent_to_scan:
|
|
1164
1240
|
continue
|
|
1165
1241
|
path = validate_and_return_torrent_file(torrent.content_path)
|
|
@@ -1245,7 +1321,7 @@ class Arr:
|
|
|
1245
1321
|
self.import_mode,
|
|
1246
1322
|
ex,
|
|
1247
1323
|
)
|
|
1248
|
-
self.add_tags(torrent, ["qBitrr-imported"])
|
|
1324
|
+
self.add_tags(torrent, ["qBitrr-imported"], instance_name)
|
|
1249
1325
|
self.sent_to_scan.add(path)
|
|
1250
1326
|
self.import_torrents.clear()
|
|
1251
1327
|
|
|
@@ -4448,27 +4524,85 @@ class Arr:
|
|
|
4448
4524
|
|
|
4449
4525
|
return payload
|
|
4450
4526
|
|
|
4527
|
+
def _get_torrents_from_all_instances(
|
|
4528
|
+
self,
|
|
4529
|
+
) -> list[tuple[str, qbittorrentapi.TorrentDictionary]]:
|
|
4530
|
+
"""
|
|
4531
|
+
Get torrents from ALL qBittorrent instances for this Arr's category.
|
|
4532
|
+
|
|
4533
|
+
Returns:
|
|
4534
|
+
list[tuple[str, TorrentDictionary]]: List of (instance_name, torrent) tuples
|
|
4535
|
+
"""
|
|
4536
|
+
all_torrents = []
|
|
4537
|
+
qbit_manager = self.manager.qbit_manager
|
|
4538
|
+
|
|
4539
|
+
for instance_name in qbit_manager.get_all_instances():
|
|
4540
|
+
if not qbit_manager.is_instance_alive(instance_name):
|
|
4541
|
+
self.logger.debug(
|
|
4542
|
+
"Skipping unhealthy instance '%s' during torrent scan", instance_name
|
|
4543
|
+
)
|
|
4544
|
+
continue
|
|
4545
|
+
|
|
4546
|
+
client = qbit_manager.get_client(instance_name)
|
|
4547
|
+
if client is None:
|
|
4548
|
+
continue
|
|
4549
|
+
|
|
4550
|
+
try:
|
|
4551
|
+
torrents = client.torrents.info(
|
|
4552
|
+
status_filter="all",
|
|
4553
|
+
category=self.category,
|
|
4554
|
+
sort="added_on",
|
|
4555
|
+
reverse=False,
|
|
4556
|
+
)
|
|
4557
|
+
# Tag each torrent with its instance name
|
|
4558
|
+
for torrent in torrents:
|
|
4559
|
+
if hasattr(torrent, "category"):
|
|
4560
|
+
all_torrents.append((instance_name, torrent))
|
|
4561
|
+
|
|
4562
|
+
self.logger.trace(
|
|
4563
|
+
"Retrieved %d torrents from instance '%s' for category '%s'",
|
|
4564
|
+
len(torrents),
|
|
4565
|
+
instance_name,
|
|
4566
|
+
self.category,
|
|
4567
|
+
)
|
|
4568
|
+
except (qbittorrentapi.exceptions.APIError, JSONDecodeError) as e:
|
|
4569
|
+
self.logger.warning(
|
|
4570
|
+
"Failed to get torrents from instance '%s': %s", instance_name, e
|
|
4571
|
+
)
|
|
4572
|
+
continue
|
|
4573
|
+
|
|
4574
|
+
self.logger.debug(
|
|
4575
|
+
"Total torrents across %d instances: %d",
|
|
4576
|
+
len(qbit_manager.get_all_instances()),
|
|
4577
|
+
len(all_torrents),
|
|
4578
|
+
)
|
|
4579
|
+
return all_torrents
|
|
4580
|
+
|
|
4451
4581
|
def process_torrents(self):
|
|
4452
4582
|
try:
|
|
4453
4583
|
try:
|
|
4454
4584
|
while True:
|
|
4455
4585
|
try:
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
category=self.category,
|
|
4459
|
-
sort="added_on",
|
|
4460
|
-
reverse=False,
|
|
4461
|
-
)
|
|
4586
|
+
# Multi-instance: Scan all qBit instances for category-matching torrents
|
|
4587
|
+
torrents_with_instances = self._get_torrents_from_all_instances()
|
|
4462
4588
|
break
|
|
4463
4589
|
except (qbittorrentapi.exceptions.APIError, JSONDecodeError) as e:
|
|
4464
4590
|
if "JSONDecodeError" in str(e):
|
|
4465
4591
|
continue
|
|
4466
4592
|
else:
|
|
4467
4593
|
raise qbittorrentapi.exceptions.APIError
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4594
|
+
|
|
4595
|
+
# Filter torrents that have category attribute
|
|
4596
|
+
torrents_with_instances = [
|
|
4597
|
+
(instance, t)
|
|
4598
|
+
for instance, t in torrents_with_instances
|
|
4599
|
+
if hasattr(t, "category")
|
|
4600
|
+
]
|
|
4601
|
+
self.category_torrent_count = len(torrents_with_instances)
|
|
4602
|
+
if not len(torrents_with_instances):
|
|
4471
4603
|
raise DelayLoopException(length=LOOP_SLEEP_TIMER, type="no_downloads")
|
|
4604
|
+
|
|
4605
|
+
# Internet check: Use default instance for backward compatibility
|
|
4472
4606
|
if not has_internet(self.manager.qbit_manager.client):
|
|
4473
4607
|
self.manager.qbit_manager.should_delay_torrent_scan = True
|
|
4474
4608
|
raise DelayLoopException(length=NO_INTERNET_SLEEP_TIMER, type="internet")
|
|
@@ -4507,9 +4641,10 @@ class Arr:
|
|
|
4507
4641
|
|
|
4508
4642
|
self.api_calls()
|
|
4509
4643
|
self.refresh_download_queue()
|
|
4510
|
-
|
|
4644
|
+
# Multi-instance: Process torrents from all instances
|
|
4645
|
+
for instance_name, torrent in torrents_with_instances:
|
|
4511
4646
|
with contextlib.suppress(qbittorrentapi.NotFound404Error):
|
|
4512
|
-
self._process_single_torrent(torrent)
|
|
4647
|
+
self._process_single_torrent(torrent, instance_name=instance_name)
|
|
4513
4648
|
self.process()
|
|
4514
4649
|
except NoConnectionrException as e:
|
|
4515
4650
|
self.logger.error(e.message)
|
|
@@ -4977,7 +5112,10 @@ class Arr:
|
|
|
4977
5112
|
self.recheck.add(torrent.hash)
|
|
4978
5113
|
|
|
4979
5114
|
def _process_single_torrent_fully_completed_torrent(
|
|
4980
|
-
self,
|
|
5115
|
+
self,
|
|
5116
|
+
torrent: qbittorrentapi.TorrentDictionary,
|
|
5117
|
+
leave_alone: bool,
|
|
5118
|
+
instance_name: str = "default",
|
|
4981
5119
|
):
|
|
4982
5120
|
if leave_alone or torrent.state_enum == TorrentStates.FORCED_UPLOAD:
|
|
4983
5121
|
self.logger.trace(
|
|
@@ -4995,7 +5133,7 @@ class Arr:
|
|
|
4995
5133
|
torrent.name,
|
|
4996
5134
|
torrent.hash,
|
|
4997
5135
|
)
|
|
4998
|
-
elif not self.in_tags(torrent, "qBitrr-imported"):
|
|
5136
|
+
elif not self.in_tags(torrent, "qBitrr-imported", instance_name):
|
|
4999
5137
|
self.logger.info(
|
|
5000
5138
|
"Importing Completed torrent: "
|
|
5001
5139
|
"[Progress: %s%%][Added On: %s]"
|
|
@@ -5020,7 +5158,7 @@ class Arr:
|
|
|
5020
5158
|
else:
|
|
5021
5159
|
torrent_folder = content_path
|
|
5022
5160
|
self.files_to_cleanup.add((torrent.hash, torrent_folder))
|
|
5023
|
-
self.import_torrents.append(torrent)
|
|
5161
|
+
self.import_torrents.append((torrent, instance_name))
|
|
5024
5162
|
|
|
5025
5163
|
def _process_single_torrent_missing_files(self, torrent: qbittorrentapi.TorrentDictionary):
|
|
5026
5164
|
# Sometimes Sonarr/Radarr does not automatically remove the
|
|
@@ -5388,7 +5526,7 @@ class Arr:
|
|
|
5388
5526
|
return data_settings, data_torrent
|
|
5389
5527
|
|
|
5390
5528
|
def _should_leave_alone(
|
|
5391
|
-
self, torrent: qbittorrentapi.TorrentDictionary
|
|
5529
|
+
self, torrent: qbittorrentapi.TorrentDictionary, instance_name: str = "default"
|
|
5392
5530
|
) -> tuple[bool, int, bool]:
|
|
5393
5531
|
return_value = True
|
|
5394
5532
|
remove_torrent = False
|
|
@@ -5414,18 +5552,18 @@ class Arr:
|
|
|
5414
5552
|
return_value = not self.torrent_limit_check(torrent, seeding_time_limit, ratio_limit)
|
|
5415
5553
|
if data_settings.get("super_seeding", False) or data_torrent.get("super_seeding", False):
|
|
5416
5554
|
return_value = True
|
|
5417
|
-
if self.in_tags(torrent, "qBitrr-free_space_paused"):
|
|
5555
|
+
if self.in_tags(torrent, "qBitrr-free_space_paused", instance_name):
|
|
5418
5556
|
return_value = True
|
|
5419
5557
|
if (
|
|
5420
5558
|
return_value
|
|
5421
|
-
and not self.in_tags(torrent, "qBitrr-allowed_seeding")
|
|
5422
|
-
and not self.in_tags(torrent, "qBitrr-free_space_paused")
|
|
5559
|
+
and not self.in_tags(torrent, "qBitrr-allowed_seeding", instance_name)
|
|
5560
|
+
and not self.in_tags(torrent, "qBitrr-free_space_paused", instance_name)
|
|
5423
5561
|
):
|
|
5424
|
-
self.add_tags(torrent, ["qBitrr-allowed_seeding"])
|
|
5562
|
+
self.add_tags(torrent, ["qBitrr-allowed_seeding"], instance_name)
|
|
5425
5563
|
elif (
|
|
5426
|
-
not return_value and self.in_tags(torrent, "qBitrr-allowed_seeding")
|
|
5427
|
-
) or self.in_tags(torrent, "qBitrr-free_space_paused"):
|
|
5428
|
-
self.remove_tags(torrent, ["qBitrr-allowed_seeding"])
|
|
5564
|
+
not return_value and self.in_tags(torrent, "qBitrr-allowed_seeding", instance_name)
|
|
5565
|
+
) or self.in_tags(torrent, "qBitrr-free_space_paused", instance_name):
|
|
5566
|
+
self.remove_tags(torrent, ["qBitrr-allowed_seeding"], instance_name)
|
|
5429
5567
|
|
|
5430
5568
|
self.logger.trace("Config Settings returned [%s]: %r", torrent.name, data_settings)
|
|
5431
5569
|
return (
|
|
@@ -5591,9 +5729,14 @@ class Arr:
|
|
|
5591
5729
|
current_tags = set(torrent.tags.split(", "))
|
|
5592
5730
|
add_tags = unique_tags.difference(current_tags)
|
|
5593
5731
|
if add_tags:
|
|
5594
|
-
self.add_tags(torrent, add_tags)
|
|
5732
|
+
self.add_tags(torrent, add_tags, instance_name)
|
|
5595
5733
|
|
|
5596
|
-
def _stalled_check(
|
|
5734
|
+
def _stalled_check(
|
|
5735
|
+
self,
|
|
5736
|
+
torrent: qbittorrentapi.TorrentDictionary,
|
|
5737
|
+
time_now: float,
|
|
5738
|
+
instance_name: str = "default",
|
|
5739
|
+
) -> bool:
|
|
5597
5740
|
stalled_ignore = True
|
|
5598
5741
|
if not self.allowed_stalled:
|
|
5599
5742
|
self.logger.trace("Stalled check: Stalled delay disabled")
|
|
@@ -5630,15 +5773,15 @@ class Arr:
|
|
|
5630
5773
|
(
|
|
5631
5774
|
torrent.state_enum
|
|
5632
5775
|
in (TorrentStates.METADATA_DOWNLOAD, TorrentStates.STALLED_DOWNLOAD)
|
|
5633
|
-
and not self.in_tags(torrent, "qBitrr-ignored")
|
|
5634
|
-
and not self.in_tags(torrent, "qBitrr-free_space_paused")
|
|
5776
|
+
and not self.in_tags(torrent, "qBitrr-ignored", instance_name)
|
|
5777
|
+
and not self.in_tags(torrent, "qBitrr-free_space_paused", instance_name)
|
|
5635
5778
|
)
|
|
5636
5779
|
or (
|
|
5637
5780
|
torrent.availability < 1
|
|
5638
5781
|
and torrent.hash in self.cleaned_torrents
|
|
5639
5782
|
and torrent.state_enum in (TorrentStates.DOWNLOADING)
|
|
5640
|
-
and not self.in_tags(torrent, "qBitrr-ignored")
|
|
5641
|
-
and not self.in_tags(torrent, "qBitrr-free_space_paused")
|
|
5783
|
+
and not self.in_tags(torrent, "qBitrr-ignored", instance_name)
|
|
5784
|
+
and not self.in_tags(torrent, "qBitrr-free_space_paused", instance_name)
|
|
5642
5785
|
)
|
|
5643
5786
|
) and self.allowed_stalled:
|
|
5644
5787
|
if (
|
|
@@ -5648,8 +5791,8 @@ class Arr:
|
|
|
5648
5791
|
):
|
|
5649
5792
|
stalled_ignore = False
|
|
5650
5793
|
self.logger.trace("Process stalled, delay expired: %s", torrent.name)
|
|
5651
|
-
elif not self.in_tags(torrent, "qBitrr-allowed_stalled"):
|
|
5652
|
-
self.add_tags(torrent, ["qBitrr-allowed_stalled"])
|
|
5794
|
+
elif not self.in_tags(torrent, "qBitrr-allowed_stalled", instance_name):
|
|
5795
|
+
self.add_tags(torrent, ["qBitrr-allowed_stalled"], instance_name)
|
|
5653
5796
|
if self.re_search_stalled:
|
|
5654
5797
|
self.logger.trace(
|
|
5655
5798
|
"Stalled, adding tag, blocklosting and re-searching: %s", torrent.name
|
|
@@ -5666,7 +5809,7 @@ class Arr:
|
|
|
5666
5809
|
)
|
|
5667
5810
|
else:
|
|
5668
5811
|
self.logger.trace("Stalled, adding tag: %s", torrent.name)
|
|
5669
|
-
elif self.in_tags(torrent, "qBitrr-allowed_stalled"):
|
|
5812
|
+
elif self.in_tags(torrent, "qBitrr-allowed_stalled", instance_name):
|
|
5670
5813
|
self.logger.trace(
|
|
5671
5814
|
"Stalled: %s [Current:%s][Last Activity:%s][Limit:%s]",
|
|
5672
5815
|
torrent.name,
|
|
@@ -5677,8 +5820,8 @@ class Arr:
|
|
|
5677
5820
|
),
|
|
5678
5821
|
)
|
|
5679
5822
|
|
|
5680
|
-
elif self.in_tags(torrent, "qBitrr-allowed_stalled"):
|
|
5681
|
-
self.remove_tags(torrent, ["qBitrr-allowed_stalled"])
|
|
5823
|
+
elif self.in_tags(torrent, "qBitrr-allowed_stalled", instance_name):
|
|
5824
|
+
self.remove_tags(torrent, ["qBitrr-allowed_stalled"], instance_name)
|
|
5682
5825
|
stalled_ignore = False
|
|
5683
5826
|
self.logger.trace("Not stalled, removing tag: %s", torrent.name)
|
|
5684
5827
|
else:
|
|
@@ -5686,13 +5829,17 @@ class Arr:
|
|
|
5686
5829
|
self.logger.trace("Not stalled: %s", torrent.name)
|
|
5687
5830
|
return stalled_ignore
|
|
5688
5831
|
|
|
5689
|
-
def _process_single_torrent(
|
|
5832
|
+
def _process_single_torrent(
|
|
5833
|
+
self, torrent: qbittorrentapi.TorrentDictionary, instance_name: str = "default"
|
|
5834
|
+
):
|
|
5690
5835
|
if torrent.category != RECHECK_CATEGORY:
|
|
5691
5836
|
self.manager.qbit_manager.cache[torrent.hash] = torrent.category
|
|
5692
5837
|
self._process_single_torrent_trackers(torrent)
|
|
5693
5838
|
self.manager.qbit_manager.name_cache[torrent.hash] = torrent.name
|
|
5694
5839
|
time_now = time.time()
|
|
5695
|
-
leave_alone, _tracker_max_eta, remove_torrent = self._should_leave_alone(
|
|
5840
|
+
leave_alone, _tracker_max_eta, remove_torrent = self._should_leave_alone(
|
|
5841
|
+
torrent, instance_name
|
|
5842
|
+
)
|
|
5696
5843
|
self.logger.trace(
|
|
5697
5844
|
"Torrent [%s]: Leave Alone (allow seeding): %s, Max ETA: %s, State[%s]",
|
|
5698
5845
|
torrent.name,
|
|
@@ -5707,20 +5854,22 @@ class Arr:
|
|
|
5707
5854
|
TorrentStates.STALLED_DOWNLOAD,
|
|
5708
5855
|
TorrentStates.DOWNLOADING,
|
|
5709
5856
|
):
|
|
5710
|
-
stalled_ignore = self._stalled_check(torrent, time_now)
|
|
5857
|
+
stalled_ignore = self._stalled_check(torrent, time_now, instance_name)
|
|
5711
5858
|
else:
|
|
5712
5859
|
stalled_ignore = False
|
|
5713
5860
|
|
|
5714
|
-
if self.in_tags(torrent, "qBitrr-ignored"):
|
|
5715
|
-
self.remove_tags(
|
|
5861
|
+
if self.in_tags(torrent, "qBitrr-ignored", instance_name):
|
|
5862
|
+
self.remove_tags(
|
|
5863
|
+
torrent, ["qBitrr-allowed_seeding", "qBitrr-free_space_paused"], instance_name
|
|
5864
|
+
)
|
|
5716
5865
|
|
|
5717
5866
|
if (
|
|
5718
5867
|
self.custom_format_unmet_search
|
|
5719
5868
|
and self.custom_format_unmet_check(torrent)
|
|
5720
|
-
and not self.in_tags(torrent, "qBitrr-ignored")
|
|
5721
|
-
and not self.in_tags(torrent, "qBitrr-free_space_paused")
|
|
5869
|
+
and not self.in_tags(torrent, "qBitrr-ignored", instance_name)
|
|
5870
|
+
and not self.in_tags(torrent, "qBitrr-free_space_paused", instance_name)
|
|
5722
5871
|
):
|
|
5723
|
-
self._process_single_torrent_delete_cfunmet(torrent)
|
|
5872
|
+
self._process_single_torrent_delete_cfunmet(torrent, instance_name)
|
|
5724
5873
|
elif remove_torrent and not leave_alone and torrent.amount_left == 0:
|
|
5725
5874
|
self._process_single_torrent_delete_ratio_seed(torrent)
|
|
5726
5875
|
elif torrent.category == FAILED_CATEGORY:
|
|
@@ -5733,8 +5882,8 @@ class Arr:
|
|
|
5733
5882
|
self._process_single_torrent_ignored(torrent)
|
|
5734
5883
|
elif (
|
|
5735
5884
|
torrent.state_enum in (TorrentStates.METADATA_DOWNLOAD, TorrentStates.STALLED_DOWNLOAD)
|
|
5736
|
-
and not self.in_tags(torrent, "qBitrr-ignored")
|
|
5737
|
-
and not self.in_tags(torrent, "qBitrr-free_space_paused")
|
|
5885
|
+
and not self.in_tags(torrent, "qBitrr-ignored", instance_name)
|
|
5886
|
+
and not self.in_tags(torrent, "qBitrr-free_space_paused", instance_name)
|
|
5738
5887
|
and not stalled_ignore
|
|
5739
5888
|
):
|
|
5740
5889
|
self._process_single_torrent_stalled_torrent(torrent, "Stalled State")
|
|
@@ -5755,15 +5904,15 @@ class Arr:
|
|
|
5755
5904
|
elif (
|
|
5756
5905
|
torrent.state_enum == TorrentStates.PAUSED_DOWNLOAD
|
|
5757
5906
|
and torrent.amount_left != 0
|
|
5758
|
-
and not self.in_tags(torrent, "qBitrr-free_space_paused")
|
|
5759
|
-
and not self.in_tags(torrent, "qBitrr-ignored")
|
|
5907
|
+
and not self.in_tags(torrent, "qBitrr-free_space_paused", instance_name)
|
|
5908
|
+
and not self.in_tags(torrent, "qBitrr-ignored", instance_name)
|
|
5760
5909
|
):
|
|
5761
5910
|
self._process_single_torrent_paused(torrent)
|
|
5762
5911
|
elif (
|
|
5763
5912
|
torrent.progress <= self.maximum_deletable_percentage
|
|
5764
5913
|
and not self.is_complete_state(torrent)
|
|
5765
|
-
and not self.in_tags(torrent, "qBitrr-ignored")
|
|
5766
|
-
and not self.in_tags(torrent, "qBitrr-free_space_paused")
|
|
5914
|
+
and not self.in_tags(torrent, "qBitrr-ignored", instance_name)
|
|
5915
|
+
and not self.in_tags(torrent, "qBitrr-free_space_paused", instance_name)
|
|
5767
5916
|
and not stalled_ignore
|
|
5768
5917
|
) and torrent.hash in self.cleaned_torrents:
|
|
5769
5918
|
self._process_single_torrent_percentage_threshold(torrent, maximum_eta)
|
|
@@ -5810,8 +5959,8 @@ class Arr:
|
|
|
5810
5959
|
and time_now > torrent.added_on + self.ignore_torrents_younger_than
|
|
5811
5960
|
and 0 < maximum_eta < torrent.eta
|
|
5812
5961
|
and not self.do_not_remove_slow
|
|
5813
|
-
and not self.in_tags(torrent, "qBitrr-ignored")
|
|
5814
|
-
and not self.in_tags(torrent, "qBitrr-free_space_paused")
|
|
5962
|
+
and not self.in_tags(torrent, "qBitrr-ignored", instance_name)
|
|
5963
|
+
and not self.in_tags(torrent, "qBitrr-free_space_paused", instance_name)
|
|
5815
5964
|
and not stalled_ignore
|
|
5816
5965
|
):
|
|
5817
5966
|
self._process_single_torrent_delete_slow(torrent)
|
|
@@ -5826,8 +5975,8 @@ class Arr:
|
|
|
5826
5975
|
)
|
|
5827
5976
|
and torrent.hash in self.cleaned_torrents
|
|
5828
5977
|
and self.is_downloading_state(torrent)
|
|
5829
|
-
and not self.in_tags(torrent, "qBitrr-ignored")
|
|
5830
|
-
and not self.in_tags(torrent, "qBitrr-free_space_paused")
|
|
5978
|
+
and not self.in_tags(torrent, "qBitrr-ignored", instance_name)
|
|
5979
|
+
and not self.in_tags(torrent, "qBitrr-free_space_paused", instance_name)
|
|
5831
5980
|
and not stalled_ignore
|
|
5832
5981
|
):
|
|
5833
5982
|
self._process_single_torrent_stalled_torrent(torrent, "Unavailable")
|
|
@@ -7050,7 +7199,7 @@ class FreeSpaceManager(Arr):
|
|
|
7050
7199
|
)
|
|
7051
7200
|
self.pause.add(torrent.hash)
|
|
7052
7201
|
|
|
7053
|
-
def _process_single_torrent(self, torrent):
|
|
7202
|
+
def _process_single_torrent(self, torrent, instance_name: str = "default"):
|
|
7054
7203
|
if self.is_downloading_state(torrent):
|
|
7055
7204
|
free_space_test = self.current_free_space
|
|
7056
7205
|
free_space_test -= torrent["amount_left"]
|
|
@@ -7069,8 +7218,8 @@ class FreeSpaceManager(Arr):
|
|
|
7069
7218
|
format_bytes(torrent.amount_left),
|
|
7070
7219
|
format_bytes(-free_space_test),
|
|
7071
7220
|
)
|
|
7072
|
-
self.add_tags(torrent, ["qBitrr-free_space_paused"])
|
|
7073
|
-
self.remove_tags(torrent, ["qBitrr-allowed_seeding"])
|
|
7221
|
+
self.add_tags(torrent, ["qBitrr-free_space_paused"], instance_name)
|
|
7222
|
+
self.remove_tags(torrent, ["qBitrr-allowed_seeding"], instance_name)
|
|
7074
7223
|
self._process_single_torrent_pause_disk_space(torrent)
|
|
7075
7224
|
elif torrent.state_enum == TorrentStates.PAUSED_DOWNLOAD and free_space_test < 0:
|
|
7076
7225
|
self.logger.info(
|
|
@@ -7080,8 +7229,8 @@ class FreeSpaceManager(Arr):
|
|
|
7080
7229
|
format_bytes(torrent.amount_left),
|
|
7081
7230
|
format_bytes(-free_space_test),
|
|
7082
7231
|
)
|
|
7083
|
-
self.add_tags(torrent, ["qBitrr-free_space_paused"])
|
|
7084
|
-
self.remove_tags(torrent, ["qBitrr-allowed_seeding"])
|
|
7232
|
+
self.add_tags(torrent, ["qBitrr-free_space_paused"], instance_name)
|
|
7233
|
+
self.remove_tags(torrent, ["qBitrr-allowed_seeding"], instance_name)
|
|
7085
7234
|
elif torrent.state_enum != TorrentStates.PAUSED_DOWNLOAD and free_space_test > 0:
|
|
7086
7235
|
self.logger.info(
|
|
7087
7236
|
"Continuing download (sufficient space) | Torrent: %s | Available: %s | Space after: %s",
|
|
@@ -7090,7 +7239,7 @@ class FreeSpaceManager(Arr):
|
|
|
7090
7239
|
format_bytes(free_space_test + self._min_free_space_bytes),
|
|
7091
7240
|
)
|
|
7092
7241
|
self.current_free_space = free_space_test
|
|
7093
|
-
self.remove_tags(torrent, ["qBitrr-free_space_paused"])
|
|
7242
|
+
self.remove_tags(torrent, ["qBitrr-free_space_paused"], instance_name)
|
|
7094
7243
|
elif torrent.state_enum == TorrentStates.PAUSED_DOWNLOAD and free_space_test > 0:
|
|
7095
7244
|
self.logger.info(
|
|
7096
7245
|
"Resuming download (space available) | Torrent: %s | Available: %s | Space after: %s",
|
|
@@ -7099,16 +7248,16 @@ class FreeSpaceManager(Arr):
|
|
|
7099
7248
|
format_bytes(free_space_test + self._min_free_space_bytes),
|
|
7100
7249
|
)
|
|
7101
7250
|
self.current_free_space = free_space_test
|
|
7102
|
-
self.remove_tags(torrent, ["qBitrr-free_space_paused"])
|
|
7251
|
+
self.remove_tags(torrent, ["qBitrr-free_space_paused"], instance_name)
|
|
7103
7252
|
elif not self.is_downloading_state(torrent) and self.in_tags(
|
|
7104
|
-
torrent, "qBitrr-free_space_paused"
|
|
7253
|
+
torrent, "qBitrr-free_space_paused", instance_name
|
|
7105
7254
|
):
|
|
7106
7255
|
self.logger.info(
|
|
7107
7256
|
"Torrent completed, removing free space tag | Torrent: %s | Available: %s",
|
|
7108
7257
|
torrent.name,
|
|
7109
7258
|
format_bytes(self.current_free_space + self._min_free_space_bytes),
|
|
7110
7259
|
)
|
|
7111
|
-
self.remove_tags(torrent, ["qBitrr-free_space_paused"])
|
|
7260
|
+
self.remove_tags(torrent, ["qBitrr-free_space_paused"], instance_name)
|
|
7112
7261
|
|
|
7113
7262
|
def process(self):
|
|
7114
7263
|
self._process_paused()
|
|
@@ -7138,7 +7287,7 @@ class FreeSpaceManager(Arr):
|
|
|
7138
7287
|
torrents = [t for t in torrents if "qBitrr-ignored" not in t.tags]
|
|
7139
7288
|
self.category_torrent_count = len(torrents)
|
|
7140
7289
|
self.free_space_tagged_count = sum(
|
|
7141
|
-
1 for t in torrents if self.in_tags(t, "qBitrr-free_space_paused")
|
|
7290
|
+
1 for t in torrents if self.in_tags(t, "qBitrr-free_space_paused", "default")
|
|
7142
7291
|
)
|
|
7143
7292
|
if not len(torrents):
|
|
7144
7293
|
raise DelayLoopException(length=LOOP_SLEEP_TIMER, type="no_downloads")
|