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 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
- try:
154
- categ = categories[self.category]
155
- path = categ["savePath"]
156
- if path:
157
- self.logger.trace("Category exists with save path [%s]", path)
158
- self.completed_folder = pathlib.Path(path)
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. Will retry when process starts.",
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), # First line only
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(self, torrent: TorrentDictionary, tag: str) -> bool:
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, Category=torrent.category
858
+ Hash=torrent.hash,
859
+ Category=torrent.category,
860
+ QbitInstance=instance_name,
805
861
  ).on_conflict_ignore().execute()
806
- condition = (self.torrents.Hash == torrent.hash) & (
807
- self.torrents.Category == torrent.category
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(self, torrent: TorrentDictionary, tags: list) -> None:
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, Category=torrent.category
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(self, torrent: TorrentDictionary, tags: list) -> None:
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, Category=torrent.category
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
- torrents = self.manager.qbit_manager.client.torrents.info(
4457
- status_filter="all",
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
- torrents = [t for t in torrents if hasattr(t, "category")]
4469
- self.category_torrent_count = len(torrents)
4470
- if not len(torrents):
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
- for torrent in torrents:
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, torrent: qbittorrentapi.TorrentDictionary, leave_alone: bool
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(self, torrent: qbittorrentapi.TorrentDictionary, time_now: float) -> bool:
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(self, torrent: qbittorrentapi.TorrentDictionary):
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(torrent)
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(torrent, ["qBitrr-allowed_seeding", "qBitrr-free_space_paused"])
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")