mirror of
https://github.com/StuffAnThings/qbit_manage.git
synced 2025-10-18 17:56:01 +08:00
Adds new command share_limits to update share limits based on tags/categories specified per group
(Closes #88, Closes #306, Closes #259, Closes #308, Closes #137)
This commit is contained in:
parent
fe56320ec3
commit
fbf9cb59e9
10 changed files with 677 additions and 564 deletions
14
CHANGELOG
14
CHANGELOG
|
@ -1,8 +1,16 @@
|
|||
# Requirements Updated
|
||||
- Updates ruamel.yaml to 0.17.27
|
||||
- Updates ruamel.yaml to 0.17.30
|
||||
- Updates qbitorrent-api to 2023.5.48
|
||||
|
||||
# New Features
|
||||
- Adds new command `share_limits`, `--share-limits` , `QBT_SHARE_LIMITS=True` to update share limits based on tags/categories specified per group (Closes #88, Closes #306, Closes #259, Closes #308, Closes #137)
|
||||
- Adds new command `skip_qb_version_check`, `--skip-qb-version-check`, `QBT_SKIP_QB_VERSION_CHECK` to bypass qbitorrent compatibility check (unsupported - Thanks to @ftc2 #307)
|
||||
# Breaking Changes
|
||||
- `tag_nohardlinks` only updates/removes `noHL` tag. It does not modify or cleanup share_limits anymore.
|
||||
- `tag_update` only adds tracker tags to torrent. It does not modify or cleanup share_limits anymore.
|
||||
- Please remove any references to share_limits from your configuration in the tracker/nohardlinks section
|
||||
|
||||
# Bug Fixes
|
||||
- Fixes #302
|
||||
- Adds a way to bypass qbt version check (unsupported - Thanks to @ftc2 #307)
|
||||
|
||||
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v3.6.3...v3.6.4
|
||||
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v3.6.3...v3.7.0
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
3.6.4-develop1
|
||||
3.6.4-develop2
|
||||
|
|
|
@ -14,6 +14,8 @@ commands:
|
|||
tag_tracker_error: False
|
||||
rem_orphaned: False
|
||||
tag_nohardlinks: False
|
||||
share_limits: False
|
||||
skip_qb_version_check: False
|
||||
skip_cleanup: False
|
||||
|
||||
qbt:
|
||||
|
@ -26,6 +28,7 @@ settings:
|
|||
force_auto_tmm: False # Will force qBittorrent to enable Automatic Torrent Management for each torrent.
|
||||
tracker_error_tag: issue # Will set the tag of any torrents that do not have a working tracker.
|
||||
nohardlinks_tag: noHL # Will set the tag of any torrents with no hardlinks.
|
||||
share_limits_suffix_tag: share_limit # Will add this suffix to the grouping separated by '.' to the tag of any torrents with share limits.
|
||||
ignoreTags_OnUpdate: # When running tag-update function, it will update torrent tags for a given torrent even if the torrent has at least one or more of the tags defined here. Otherwise torrents will not be tagged if tags exist.
|
||||
- noHL
|
||||
- issue
|
||||
|
@ -68,14 +71,6 @@ tracker:
|
|||
# <Tracker URL Keyword>: # <MANDATORY> This is the keyword in the tracker url
|
||||
# <MANDATORY> Set tag name. Can be a list of tags or a single tag
|
||||
# tag: <Tag Name>
|
||||
# <OPTIONAL> Will set the torrent Maximum share ratio until torrent is stopped from seeding/uploading. -2 means the global limit should be used, -1 means no limit.
|
||||
# max_ratio: 5.0
|
||||
# <OPTIONAL> Will set the torrent Maximum seeding time (min) until torrent is stopped from seeding. -2 means the global limit should be used, -1 means no limit.
|
||||
# max_seeding_time: 129600
|
||||
# <OPTIONAL> Will ensure that noHL torrents from this tracker are not deleted by cleanup variable if torrent has not yet met the minimum seeding time (min).
|
||||
# min_seeding_time: 2000
|
||||
# <OPTIONAL> Will limit the upload speed KiB/s (KiloBytes/second) (-1 means no limit)
|
||||
# limit_upload_speed: 150
|
||||
# <OPTIONAL> Set this to the notifiarr react name. This is used to add indexer reactions to the notifications sent by Notifiarr
|
||||
# notifiarr: <notifiarr indexer>
|
||||
animebytes.tv:
|
||||
|
@ -86,10 +81,6 @@ tracker:
|
|||
- Avistaz
|
||||
- tag2
|
||||
- tag3
|
||||
max_ratio: 5.0
|
||||
max_seeding_time: 129600
|
||||
min_seeding_time: 30400
|
||||
limit_upload_speed: 150
|
||||
notifiarr: avistaz
|
||||
beyond-hd:
|
||||
tag: [Beyond-HD, tag2, tag3]
|
||||
|
@ -101,14 +92,11 @@ tracker:
|
|||
tag: CartoonChaos
|
||||
digitalcore:
|
||||
tag: DigitalCore
|
||||
max_ratio: 5.0
|
||||
notifiarr: digitalcore
|
||||
gazellegames:
|
||||
tag: GGn
|
||||
limit_upload_speed: 150
|
||||
hdts:
|
||||
tag: HDTorrents
|
||||
max_seeding_time: 129600
|
||||
landof.tv:
|
||||
tag: BroadcasTheNet
|
||||
notifiarr: broadcasthenet
|
||||
|
@ -145,48 +133,66 @@ nohardlinks:
|
|||
- Beyond-HD
|
||||
- AnimeBytes
|
||||
- MaM
|
||||
# <OPTIONAL> cleanup var: WARNING!! Setting this as true Will remove and delete contents of any torrents that have a noHL tag and meets share limits
|
||||
cleanup: false
|
||||
# <OPTIONAL> max_ratio var: Will set the torrent Maximum share ratio until torrent is stopped from seeding/uploading.
|
||||
# Delete this key from a category's config to use the tracker's configured max_ratio. Will default to -1 if not specified for the category or tracker.
|
||||
# Uses the larger value of the noHL Category or Tracker specific setting.
|
||||
max_ratio: 4.0
|
||||
# <OPTIONAL> max seeding time var: Will set the torrent Maximum seeding time (min) until torrent is stopped from seeding.
|
||||
# Delete this key from a category's config to use the tracker's configured max_seeding_time. Will default to -1 if not specified for the category or tracker.
|
||||
# Uses the larger value of the noHL Category or Tracker specific setting.
|
||||
max_seeding_time: 86400
|
||||
# <OPTIONAL> Limit Upload Speed var: Will limit the upload speed KiB/s (KiloBytes/second) (`-1` : No Limit)
|
||||
limit_upload_speed:
|
||||
# <OPTIONAL> min seeding time var: Will prevent torrent deletion by cleanup variable if torrent has not yet minimum seeding time (min).
|
||||
# Delete this key from a category's config to use the tracker's configured min_seeding_time. Will default to 0 if not specified for the category or tracker.
|
||||
# Uses the larger value of the noHL Category or Tracker specific setting.
|
||||
min_seeding_time: 43200
|
||||
# <OPTIONAL> resume_torrent_after_untagging_noHL var: If a torrent was previously tagged as NoHL and now has hardlinks, this variable will resume your torrent after changing share limits
|
||||
resume_torrent_after_untagging_noHL: false
|
||||
# Can have additional categories set with separate ratio/seeding times defined.
|
||||
series-completed:
|
||||
# <OPTIONAL> exclude_tags var: Will exclude torrents with any of the following tags when searching through the category.
|
||||
exclude_tags:
|
||||
- Beyond-HD
|
||||
- BroadcasTheNet
|
||||
# <OPTIONAL> cleanup var: WARNING!! Setting this as true Will remove and delete contents of any torrents that have a noHL tag and meets share limits
|
||||
cleanup: false
|
||||
# <OPTIONAL> max_ratio var: Will set the torrent Maximum share ratio until torrent is stopped from seeding/uploading.
|
||||
# Delete this key from a category's config to use the tracker's configured max_ratio. Will default to -1 if not specified for the category or tracker.
|
||||
# Uses the larger value of the noHL Category or Tracker specific setting.
|
||||
max_ratio: 4.0
|
||||
# <OPTIONAL> max seeding time var: Will set the torrent Maximum seeding time (min) until torrent is stopped from seeding.
|
||||
# Delete this key from a category's config to use the tracker's configured max_seeding_time. Will default to -1 if not specified for the category or tracker.
|
||||
# Uses the larger value of the noHL Category or Tracker specific setting.
|
||||
max_seeding_time: 86400
|
||||
# <OPTIONAL> Limit Upload Speed var: Will limit the upload speed KiB/s (KiloBytes/second) (`-1` : No Limit)
|
||||
limit_upload_speed:
|
||||
# <OPTIONAL> min seeding time var: Will prevent torrent deletion by cleanup variable if torrent has not yet minimum seeding time (min).
|
||||
# Delete this key from a category's config to use the tracker's configured min_seeding_time. Will default to 0 if not specified for the category or tracker.
|
||||
# Uses the larger value of the noHL Category or Tracker specific setting.
|
||||
|
||||
share_limits:
|
||||
# Control how torrent share limits are set depending on the priority of your grouping
|
||||
# This variable is mandatory and is a text defining the name of your grouping. This can be any string you want
|
||||
noHL:
|
||||
# <MANDATORY> priority: <int/float> # This is the priority of your grouping. The lower the number the higher the priority
|
||||
priority: 1
|
||||
# <OPTIONAL> tags: <list> # Filter the group based on one or more tags. Multiple tags are checked with an AND condition
|
||||
tags:
|
||||
- noHL
|
||||
# <OPTIONAL> exclude_tags: <list> # Filter by excluding one or more tags. Multiple exclude_tags are checked with an AND condition
|
||||
# This is useful to combine with the category filter to exclude one or more tags from an entire category
|
||||
exclude_tags:
|
||||
- Beyond-HD
|
||||
# <OPTIONAL> categories: <list> # Filter by excluding one or more categories. Multiple exclude_tags are checked with an OR condition
|
||||
# Since one torrent can only be associated with a single category, multiple categories are checked with an OR condition
|
||||
categories:
|
||||
- RadarrComplete
|
||||
- SonarrComplete
|
||||
# <OPTIONAL> max_ratio <float>: Will set the torrent Maximum share ratio until torrent is stopped from seeding/uploading.
|
||||
# Delete this key from a category's config to use the tracker's configured max_ratio. Will default to -1 if not specified for the group.
|
||||
max_ratio: 5.0
|
||||
# <OPTIONAL> max_seeding_time <int>: Will set the torrent Maximum seeding time (minutes) until torrent is stopped from seeding.
|
||||
# Delete this key from a category's config to use the tracker's configured max_seeding_time. Will default to -1 if not specified for the group.
|
||||
max_seeding_time: 129600
|
||||
# <OPTIONAL> min_seeding_time <int>: Will prevent torrent deletion by cleanup variable if torrent has not yet minimum seeding time (minutes).
|
||||
# Delete this key from a category's config to use the tracker's configured min_seeding_time. Will default to 0 if not specified for the group.
|
||||
min_seeding_time: 43200
|
||||
# <OPTIONAL> resume_torrent_after_untagging_noHL var: If a torrent was previously tagged as NoHL and now has hardlinks, this variable will resume your torrent after changing share limits
|
||||
resume_torrent_after_untagging_noHL: false
|
||||
# <OPTIONAL> Limit Upload Speed <int>: Will limit the upload speed KiB/s (KiloBytes/second) (`-1` : No Limit)
|
||||
limit_upload_speed: 0
|
||||
# <OPTIONAL> cleanup <bool>: WARNING!! Setting this as true Will remove and delete contents of any torrents that satisfies the share limits
|
||||
cleanup: false
|
||||
# <OPTIONAL> resume_torrent_after_change <bool>: This variable will resume your torrent after changing share limits. Default is true
|
||||
resume_torrent_after_change: true
|
||||
# <OPTIONAL> add_group_to_tag <bool>: This adds your grouping as a tag with a suffix defined in settings . Default is true
|
||||
# Example: A grouping defined as noHL will have a tag set to noHL.share_limit (if using the default suffix)
|
||||
add_group_to_tag: true
|
||||
cross-seed:
|
||||
priority: 2
|
||||
tags: cross-seed
|
||||
max_seeding_time: 10200
|
||||
cleanup: false
|
||||
PTP:
|
||||
priority: 3
|
||||
tags:
|
||||
- PassThePopcorn
|
||||
max_ratio: 2.0
|
||||
max_seeding_time: 130000
|
||||
cleanup: false
|
||||
default:
|
||||
priority: 999
|
||||
max_ratio: -1
|
||||
max_seeding_time: -1
|
||||
cleanup: false
|
||||
|
||||
recyclebin:
|
||||
# Recycle Bin method of deletion will move files into the recycle bin (Located in /root_dir/.RecycleBin) instead of directly deleting them in qbit
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Apprise notification class"""
|
||||
import time
|
||||
|
||||
from modules import util
|
||||
from modules.util import Failed
|
||||
|
||||
|
@ -14,5 +16,6 @@ class Apprise:
|
|||
logger.secret(self.api_url)
|
||||
self.notify_url = ",".join(params["notify_url"])
|
||||
response = self.config.get(self.api_url)
|
||||
time.sleep(1) # Pause for 1 second before sending the next request
|
||||
if response.status_code != 200:
|
||||
raise Failed(f"Apprise Error: Unable to connect to Apprise using {self.api_url}")
|
||||
|
|
|
@ -3,6 +3,7 @@ import os
|
|||
import re
|
||||
import stat
|
||||
import time
|
||||
from collections import OrderedDict
|
||||
|
||||
import requests
|
||||
from retrying import retry
|
||||
|
@ -28,6 +29,7 @@ COMMANDS = [
|
|||
"tag_tracker_error",
|
||||
"rem_orphaned",
|
||||
"tag_nohardlinks",
|
||||
"share_limits",
|
||||
"skip_cleanup",
|
||||
"skip_qb_version_check",
|
||||
"dry_run",
|
||||
|
@ -82,6 +84,7 @@ class Config:
|
|||
logger.debug(f" --tag-tracker-error (QBT_TAG_TRACKER_ERROR): {self.commands['tag_tracker_error']}")
|
||||
logger.debug(f" --rem-orphaned (QBT_REM_ORPHANED): {self.commands['rem_orphaned']}")
|
||||
logger.debug(f" --tag-nohardlinks (QBT_TAG_NOHARDLINKS): {self.commands['tag_nohardlinks']}")
|
||||
logger.debug(f" --share-limits (QBT_SHARE_LIMITS): {self.commands['share_limits']}")
|
||||
logger.debug(f" --skip-cleanup (QBT_SKIP_CLEANUP): {self.commands['skip_cleanup']}")
|
||||
logger.debug(f" --skip-qb-version-check (QBT_SKIP_QB_VERSION_CHECK): {self.commands['skip_qb_version_check']}")
|
||||
logger.debug(f" --dry-run (QBT_DRY_RUN): {self.commands['dry_run']}")
|
||||
|
@ -136,6 +139,9 @@ class Config:
|
|||
self.data["webhooks"] = temp
|
||||
if "bhd" in self.data:
|
||||
self.data["bhd"] = self.data.pop("bhd")
|
||||
if "share_limits" in self.data:
|
||||
self.data["share_limits"] = self.data.pop("share_limits")
|
||||
|
||||
self.dry_run = self.commands["dry_run"]
|
||||
self.loglevel = "DRYRUN" if self.dry_run else "INFO"
|
||||
self.session = requests.Session()
|
||||
|
@ -148,10 +154,14 @@ class Config:
|
|||
self.data, "tracker_error_tag", parent="settings", default="issue"
|
||||
),
|
||||
"nohardlinks_tag": self.util.check_for_attribute(self.data, "nohardlinks_tag", parent="settings", default="noHL"),
|
||||
"share_limits_suffix_tag": self.util.check_for_attribute(
|
||||
self.data, "share_limits_suffix_tag", parent="settings", default="share_limit"
|
||||
),
|
||||
}
|
||||
|
||||
self.tracker_error_tag = self.settings["tracker_error_tag"]
|
||||
self.nohardlinks_tag = self.settings["nohardlinks_tag"]
|
||||
self.share_limits_suffix_tag = "." + self.settings["share_limits_suffix_tag"]
|
||||
|
||||
default_ignore_tags = [self.nohardlinks_tag, self.tracker_error_tag, "cross-seed"]
|
||||
self.settings["ignoreTags_OnUpdate"] = self.util.check_for_attribute(
|
||||
|
@ -167,6 +177,7 @@ class Config:
|
|||
"tag_tracker_error": None,
|
||||
"rem_orphaned": None,
|
||||
"tag_nohardlinks": None,
|
||||
"share_limits": None,
|
||||
"cleanup_dirs": None,
|
||||
}
|
||||
|
||||
|
@ -333,7 +344,7 @@ class Config:
|
|||
var_type="int",
|
||||
min_int=-1,
|
||||
do_print=False,
|
||||
default=0,
|
||||
default=-1,
|
||||
save=False,
|
||||
)
|
||||
self.nohardlinks[cat]["resume_torrent_after_untagging_noHL"] = self.util.check_for_attribute(
|
||||
|
@ -357,6 +368,153 @@ class Config:
|
|||
self.notify(err, "Config")
|
||||
raise Failed(err)
|
||||
|
||||
# share limits
|
||||
self.share_limits = None
|
||||
if "share_limits" in self.data and self.commands["share_limits"]:
|
||||
|
||||
def _sort_share_limits(share_limits):
|
||||
sorted_limits = sorted(
|
||||
share_limits.items(), key=lambda x: x[1].get("priority", float("inf")) if x[1] is not None else float("inf")
|
||||
)
|
||||
priorities = set()
|
||||
for key, value in sorted_limits:
|
||||
if value is None:
|
||||
value = {}
|
||||
if "priority" in value:
|
||||
priority = value["priority"]
|
||||
if priority in priorities:
|
||||
err = (
|
||||
f"Config Error: Duplicate priority '{priority}' found in share_limits "
|
||||
f"for the grouping '{key}'. Priority must be a unique value and greater than or equal to 1"
|
||||
)
|
||||
self.notify(err, "Config")
|
||||
raise Failed(err)
|
||||
else:
|
||||
priority = max(priorities) + 1
|
||||
logger.warning(
|
||||
f"Priority not defined for the grouping '{key}' in share_limits. " f"Setting priority to {priority}"
|
||||
)
|
||||
value["priority"] = self.util.check_for_attribute(
|
||||
self.data,
|
||||
"priority",
|
||||
parent="share_limits",
|
||||
subparent=key,
|
||||
var_type="float",
|
||||
default=priority,
|
||||
save=True,
|
||||
)
|
||||
priorities.add(priority)
|
||||
return OrderedDict(sorted_limits)
|
||||
|
||||
self.share_limits = OrderedDict()
|
||||
sorted_share_limits = _sort_share_limits(self.data["share_limits"])
|
||||
for group in sorted_share_limits:
|
||||
self.share_limits[group] = {}
|
||||
self.share_limits[group]["priority"] = sorted_share_limits[group]["priority"]
|
||||
self.share_limits[group]["tags"] = self.util.check_for_attribute(
|
||||
self.data,
|
||||
"tags",
|
||||
parent="share_limits",
|
||||
subparent=group,
|
||||
var_type="list",
|
||||
default_is_none=True,
|
||||
do_print=False,
|
||||
save=False,
|
||||
)
|
||||
self.share_limits[group]["exclude_tags"] = self.util.check_for_attribute(
|
||||
self.data,
|
||||
"exclude_tags",
|
||||
parent="share_limits",
|
||||
subparent=group,
|
||||
var_type="list",
|
||||
default_is_none=True,
|
||||
do_print=False,
|
||||
save=False,
|
||||
)
|
||||
self.share_limits[group]["categories"] = self.util.check_for_attribute(
|
||||
self.data,
|
||||
"categories",
|
||||
parent="share_limits",
|
||||
subparent=group,
|
||||
var_type="list",
|
||||
default_is_none=True,
|
||||
do_print=False,
|
||||
save=False,
|
||||
)
|
||||
self.share_limits[group]["cleanup"] = self.util.check_for_attribute(
|
||||
self.data, "cleanup", parent="share_limits", subparent=group, var_type="bool", default=False, do_print=False
|
||||
)
|
||||
self.share_limits[group]["max_ratio"] = self.util.check_for_attribute(
|
||||
self.data,
|
||||
"max_ratio",
|
||||
parent="share_limits",
|
||||
subparent=group,
|
||||
var_type="float",
|
||||
min_int=-2,
|
||||
default=-1,
|
||||
do_print=False,
|
||||
save=False,
|
||||
)
|
||||
self.share_limits[group]["max_seeding_time"] = self.util.check_for_attribute(
|
||||
self.data,
|
||||
"max_seeding_time",
|
||||
parent="share_limits",
|
||||
subparent=group,
|
||||
var_type="int",
|
||||
min_int=-2,
|
||||
default=-1,
|
||||
do_print=False,
|
||||
save=False,
|
||||
)
|
||||
self.share_limits[group]["min_seeding_time"] = self.util.check_for_attribute(
|
||||
self.data,
|
||||
"min_seeding_time",
|
||||
parent="share_limits",
|
||||
subparent=group,
|
||||
var_type="int",
|
||||
min_int=0,
|
||||
default=0,
|
||||
do_print=False,
|
||||
save=False,
|
||||
)
|
||||
self.share_limits[group]["limit_upload_speed"] = self.util.check_for_attribute(
|
||||
self.data,
|
||||
"limit_upload_speed",
|
||||
parent="share_limits",
|
||||
subparent=group,
|
||||
var_type="int",
|
||||
min_int=-1,
|
||||
default=0,
|
||||
do_print=False,
|
||||
save=False,
|
||||
)
|
||||
self.share_limits[group]["resume_torrent_after_change"] = self.util.check_for_attribute(
|
||||
self.data,
|
||||
"resume_torrent_after_change",
|
||||
parent="share_limits",
|
||||
subparent=group,
|
||||
var_type="bool",
|
||||
default=True,
|
||||
do_print=False,
|
||||
save=False,
|
||||
)
|
||||
self.share_limits[group]["add_group_to_tag"] = self.util.check_for_attribute(
|
||||
self.data,
|
||||
"add_group_to_tag",
|
||||
parent="share_limits",
|
||||
subparent=group,
|
||||
var_type="bool",
|
||||
default=True,
|
||||
do_print=False,
|
||||
save=False,
|
||||
)
|
||||
self.share_limits[group]["torrents"] = []
|
||||
else:
|
||||
if self.commands["share_limits"]:
|
||||
err = "Config Error: share_limits. No valid grouping found."
|
||||
self.notify(err, "Config")
|
||||
raise Failed(err)
|
||||
|
||||
# Add RecycleBin
|
||||
self.recyclebin = {}
|
||||
self.recyclebin["enabled"] = self.util.check_for_attribute(
|
||||
|
|
399
modules/core/share_limits.py
Normal file
399
modules/core/share_limits.py
Normal file
|
@ -0,0 +1,399 @@
|
|||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
from modules import util
|
||||
|
||||
logger = util.logger
|
||||
|
||||
|
||||
class ShareLimits:
|
||||
def __init__(self, qbit_manager):
|
||||
self.qbt = qbit_manager
|
||||
self.config = qbit_manager.config
|
||||
self.client = qbit_manager.client
|
||||
self.stats_tagged = 0 # counter for the number of share limits changed
|
||||
self.stats_deleted = 0 # counter for the number of torrents that \
|
||||
# meets the criteria for ratio limit/seed limit for deletion
|
||||
self.stats_deleted_contents = 0 # counter for the number of torrents that \
|
||||
# meets the criteria for ratio limit/seed limit for deletion including contents \
|
||||
|
||||
self.tdel_dict = {} # dictionary to track the torrent names and content path that meet the deletion criteria
|
||||
self.root_dir = qbit_manager.config.root_dir # root directory of torrents
|
||||
self.remote_dir = qbit_manager.config.remote_dir # remote directory of torrents
|
||||
self.share_limits_config = qbit_manager.config.share_limits # configuration of share limits
|
||||
self.torrents_updated = [] # list of torrents that have been updated
|
||||
self.torrent_hash_checked = [] # list of torrent hashes that have been checked for share limits
|
||||
self.share_limits_suffix_tag = qbit_manager.config.share_limits_suffix_tag # suffix tag for share limits
|
||||
self.group_tag = None # tag for the share limit group
|
||||
|
||||
self.update_share_limits()
|
||||
|
||||
def update_share_limits(self):
|
||||
"""Updates share limits for torrents based on grouping"""
|
||||
logger.separator("Updating Share Limits based on priority", space=False, border=False)
|
||||
torrent_list = self.qbt.get_torrents({"status_filter": "completed"})
|
||||
self.assign_torrents_to_group(torrent_list)
|
||||
for group_name, group_config in self.share_limits_config.items():
|
||||
torrents = group_config["torrents"]
|
||||
self.torrents_updated = []
|
||||
self.tdel_dict = {}
|
||||
if torrents:
|
||||
self.update_share_limits_for_group(group_name, group_config, torrents)
|
||||
attr = {
|
||||
"function": "share_limits",
|
||||
"title": f"Updating Share Limits for {group_name}. Priority {group_config['priority']}",
|
||||
"body": f"Updated {len(self.torrents_updated)} torrents.",
|
||||
"grouping": group_name,
|
||||
"torrent_list": self.torrents_updated,
|
||||
"torrent_tag": self.group_tag,
|
||||
"torrent_max_ratio": group_config["max_ratio"],
|
||||
"torrent_max_seeding_time": group_config["max_seeding_time"],
|
||||
"torrent_min_seeding_time": group_config["min_seeding_time"],
|
||||
"torrent_limit_upload_speed": group_config["limit_upload_speed"],
|
||||
}
|
||||
self.config.send_notifications(attr)
|
||||
if group_config["cleanup"] and len(self.tdel_dict) > 0:
|
||||
self.cleanup_torrents_for_group(group_name, group_config["priority"])
|
||||
|
||||
def cleanup_torrents_for_group(self, group_name, priority):
|
||||
"""Deletes torrents that have reached the ratio/seed limit"""
|
||||
logger.separator(
|
||||
f"Cleaning up torrents that have reached ratio/seed limit for {group_name}. Priority {priority}",
|
||||
space=False,
|
||||
border=False,
|
||||
)
|
||||
group_notifications = len(self.tdel_dict) > 10
|
||||
t_deleted = set()
|
||||
t_deleted_and_contents = set()
|
||||
for torrent_hash, torrent_dict in self.tdel_dict.items():
|
||||
torrent = torrent_dict["torrent"]
|
||||
t_name = torrent.name
|
||||
t_count = self.qbt.torrentinfo[t_name]["count"]
|
||||
t_msg = self.qbt.torrentinfo[t_name]["msg"]
|
||||
t_status = self.qbt.torrentinfo[t_name]["status"]
|
||||
# Double check that the content path is the same before we delete anything
|
||||
if torrent["content_path"].replace(self.root_dir, self.remote_dir) == torrent_dict["content_path"]:
|
||||
tracker = self.qbt.get_tags(torrent.trackers)
|
||||
body = []
|
||||
body += logger.print_line(logger.insert_space(f"Torrent Name: {t_name}", 3), self.config.loglevel)
|
||||
body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel)
|
||||
body += logger.print_line(torrent_dict["body"], self.config.loglevel)
|
||||
body += logger.print_line(
|
||||
logger.insert_space("Cleanup: True [Meets Share Limits]", 8),
|
||||
self.config.loglevel,
|
||||
)
|
||||
attr = {
|
||||
"function": "cleanup_share_limits",
|
||||
"title": "Share limit removal",
|
||||
"grouping": group_name,
|
||||
"torrent_name": t_name,
|
||||
"torrent_category": torrent.category,
|
||||
"cleanup": "True",
|
||||
"torrent_tracker": tracker["url"],
|
||||
"notifiarr_indexer": tracker["notifiarr"],
|
||||
}
|
||||
if os.path.exists(torrent["content_path"].replace(self.root_dir, self.remote_dir)):
|
||||
# Checks if any of the original torrents are working
|
||||
if t_count > 1 and ("" in t_msg or 2 in t_status):
|
||||
self.stats_deleted += 1
|
||||
attr["torrents_deleted_and_contents"] = False
|
||||
t_deleted.add(t_name)
|
||||
if not self.config.dry_run:
|
||||
self.qbt.tor_delete_recycle(torrent, attr)
|
||||
body += logger.print_line(
|
||||
logger.insert_space("Deleted .torrent but NOT content files.", 8),
|
||||
self.config.loglevel,
|
||||
)
|
||||
else:
|
||||
self.stats_deleted_contents += 1
|
||||
attr["torrents_deleted_and_contents"] = True
|
||||
t_deleted_and_contents.add(t_name)
|
||||
if not self.config.dry_run:
|
||||
self.qbt.tor_delete_recycle(torrent, attr)
|
||||
body += logger.print_line(
|
||||
logger.insert_space("Deleted .torrent AND content files.", 8), self.config.loglevel
|
||||
)
|
||||
else:
|
||||
self.stats_deleted += 1
|
||||
attr["torrents_deleted_and_contents"] = False
|
||||
t_deleted.add(t_name)
|
||||
if not self.config.dry_run:
|
||||
self.qbt.tor_delete_recycle(torrent, attr)
|
||||
body += logger.print_line(
|
||||
logger.insert_space("Deleted .torrent but NOT content files.", 8), self.config.loglevel
|
||||
)
|
||||
attr["body"] = "\n".join(body)
|
||||
if not group_notifications:
|
||||
self.config.send_notifications(attr)
|
||||
self.qbt.torrentinfo[t_name]["count"] -= 1
|
||||
if group_notifications:
|
||||
if t_deleted:
|
||||
attr = {
|
||||
"function": "cleanup_share_limits",
|
||||
"title": "Share limit removal - Deleted .torrent but NOT content files.",
|
||||
"body": f"Deleted {self.stats_deleted} .torrents but NOT content files.",
|
||||
"grouping": group_name,
|
||||
"torrent_list": list(t_deleted),
|
||||
"cleanup": True,
|
||||
"torrents_deleted_and_contents": False,
|
||||
}
|
||||
self.config.send_notifications(attr)
|
||||
if t_deleted_and_contents:
|
||||
attr = {
|
||||
"function": "cleanup_share_limits",
|
||||
"title": "Share limit removal - Deleted .torrent AND content files.",
|
||||
"body": f"Deleted {self.stats_deleted_contents} .torrents AND content files.",
|
||||
"grouping": group_name,
|
||||
"torrent_list": list(t_deleted_and_contents),
|
||||
"cleanup": True,
|
||||
"torrents_deleted_and_contents": True,
|
||||
}
|
||||
self.config.send_notifications(attr)
|
||||
|
||||
def update_share_limits_for_group(self, group_name, group_config, torrents):
|
||||
"""Updates share limits for torrents in a group"""
|
||||
logger.separator(
|
||||
f"Updating Share Limits for [Group {group_name}] [Priority {group_config['priority']}]", space=False, border=False
|
||||
)
|
||||
for torrent in torrents:
|
||||
t_name = torrent.name
|
||||
t_hash = torrent.hash
|
||||
tracker = self.qbt.get_tags(torrent.trackers)
|
||||
check_max_ratio = group_config["max_ratio"] != torrent.max_ratio
|
||||
check_max_seeding_time = group_config["max_seeding_time"] != torrent.max_seeding_time
|
||||
# Treat upload limit as -1 if it is set to 0 (unlimited)
|
||||
torrent_upload_limit = -1 if torrent.up_limit == 0 else torrent.up_limit
|
||||
if group_config["limit_upload_speed"] == 0:
|
||||
group_config["limit_upload_speed"] = -1
|
||||
check_limit_upload_speed = group_config["limit_upload_speed"] != torrent_upload_limit
|
||||
if (
|
||||
check_max_ratio or check_max_seeding_time or check_limit_upload_speed
|
||||
) and t_hash not in self.torrent_hash_checked:
|
||||
if "MinSeedTimeNotReached" not in torrent.tags:
|
||||
self.group_tag = f"{group_name}{self.share_limits_suffix_tag}" if group_config["add_group_to_tag"] else None
|
||||
logger.print_line(logger.insert_space(f"Torrent Name: {t_name}", 3), self.config.loglevel)
|
||||
logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel)
|
||||
logger.trace(f"Torrent Category: {torrent.category}")
|
||||
logger.trace(f"Torrent Tags: {torrent.tags}")
|
||||
logger.trace(f"Grouping: {group_name}")
|
||||
logger.trace(f"Config Max Ratio vs Torrent Max Ratio:{group_config['max_ratio']} vs {torrent.max_ratio}")
|
||||
logger.trace(
|
||||
"Config Max Seeding Time vs Torrent Max Seeding Time: "
|
||||
f"{group_config['max_seeding_time']} vs {torrent.max_seeding_time}"
|
||||
)
|
||||
logger.trace(
|
||||
"Config Limit Upload Speed vs Torrent Limit Upload Speed: "
|
||||
f"{group_config['limit_upload_speed']} vs {torrent.up_limit}"
|
||||
)
|
||||
if self.group_tag:
|
||||
logger.print_line(logger.insert_space(f"Added Tag: {self.group_tag}", 8), self.config.loglevel)
|
||||
self.tag_and_update_share_limits_for_torrent(torrent, group_config)
|
||||
self.stats_tagged += 1
|
||||
self.torrents_updated.append(t_name)
|
||||
# Cleanup torrents if the torrent meets the criteria for deletion and cleanup is enabled
|
||||
if group_config["cleanup"]:
|
||||
tor_reached_seed_limit = self.has_reached_seed_limit(
|
||||
torrent=torrent,
|
||||
max_ratio=group_config["max_ratio"],
|
||||
max_seeding_time=group_config["max_seeding_time"],
|
||||
min_seeding_time=group_config["min_seeding_time"],
|
||||
resume_torrent=group_config["resume_torrent_after_change"],
|
||||
tracker=tracker["url"],
|
||||
)
|
||||
if tor_reached_seed_limit:
|
||||
if t_hash not in self.tdel_dict:
|
||||
self.tdel_dict[t_hash] = {}
|
||||
self.tdel_dict[t_hash]["torrent"] = torrent
|
||||
self.tdel_dict[t_hash]["content_path"] = torrent["content_path"].replace(self.root_dir, self.remote_dir)
|
||||
self.tdel_dict[t_hash]["body"] = tor_reached_seed_limit
|
||||
else:
|
||||
self.share_limits_config[group_name]["torrents"].remove(torrent)
|
||||
self.torrent_hash_checked.append(t_hash)
|
||||
|
||||
def tag_and_update_share_limits_for_torrent(self, torrent, group_config):
|
||||
"""Removes previous share limits tag, updates tag and share limits for a torrent, and resumes the torrent"""
|
||||
# Remove previous share_limits tag
|
||||
tags = util.get_list(torrent.tags)
|
||||
for tag in tags:
|
||||
if self.share_limits_suffix_tag in tag:
|
||||
tags.remove(tag)
|
||||
|
||||
# Will tag the torrent with the group name if add_group_to_tag is True and set the share limits
|
||||
self.set_tags_and_limits(
|
||||
torrent=torrent,
|
||||
max_ratio=group_config["max_ratio"],
|
||||
max_seeding_time=group_config["max_seeding_time"],
|
||||
limit_upload_speed=group_config["limit_upload_speed"],
|
||||
tags=self.group_tag,
|
||||
)
|
||||
# Resume torrent if it was paused now that the share limit has changed
|
||||
if torrent.state_enum.is_complete and group_config["resume_torrent_after_change"]:
|
||||
if not self.config.dry_run:
|
||||
torrent.resume()
|
||||
|
||||
def assign_torrents_to_group(self, torrent_list):
|
||||
"""Assign torrents to a share limit group based on its tags and category"""
|
||||
logger.info("Assigning torrents to share limit groups...")
|
||||
for torrent in torrent_list:
|
||||
tags = util.get_list(torrent.tags)
|
||||
category = torrent.category or ""
|
||||
grouping = self.get_share_limit_group(tags, category)
|
||||
if grouping:
|
||||
self.share_limits_config[grouping]["torrents"].append(torrent)
|
||||
|
||||
def get_share_limit_group(self, tags, category):
|
||||
"""Get the share limit group based on the tags and category of the torrent"""
|
||||
for group_name, group_config in self.share_limits_config.items():
|
||||
check_tags = self.check_tags(tags, group_config["tags"], group_config["exclude_tags"])
|
||||
check_category = self.check_category(category, group_config["categories"])
|
||||
|
||||
if check_tags and check_category:
|
||||
return group_name
|
||||
return None
|
||||
|
||||
def check_tags(self, tags, include_tags, exclude_tags):
|
||||
"""Check if the torrent has the required tags and does not have the excluded tags"""
|
||||
if include_tags:
|
||||
if not set(include_tags).issubset(tags):
|
||||
return False
|
||||
if exclude_tags:
|
||||
if set(exclude_tags).intersection(tags):
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_category(self, category, categories):
|
||||
"""Check if the torrent has the required category"""
|
||||
if categories:
|
||||
if category not in categories:
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_tags_and_limits(
|
||||
self, torrent, max_ratio, max_seeding_time, limit_upload_speed=None, tags=None, restore=False, do_print=True
|
||||
):
|
||||
"""Set tags and limits for a torrent"""
|
||||
body = []
|
||||
if limit_upload_speed:
|
||||
if limit_upload_speed != -1:
|
||||
msg = logger.insert_space(f"Limit UL Speed: {limit_upload_speed} kB/s", 1)
|
||||
if do_print:
|
||||
body += logger.print_line(msg, self.config.loglevel)
|
||||
else:
|
||||
body.append(msg)
|
||||
if max_ratio or max_seeding_time:
|
||||
if (max_ratio == -2 and max_seeding_time == -2) and not restore:
|
||||
msg = logger.insert_space("Share Limit: Use Global Share Limit", 4)
|
||||
if do_print:
|
||||
body += logger.print_line(msg, self.config.loglevel)
|
||||
else:
|
||||
body.append(msg)
|
||||
elif (max_ratio == -1 and max_seeding_time == -1) and not restore:
|
||||
msg = logger.insert_space("Share Limit: Set No Share Limit", 4)
|
||||
if do_print:
|
||||
body += logger.print_line(msg, self.config.loglevel)
|
||||
else:
|
||||
body.append(msg)
|
||||
else:
|
||||
if max_ratio != torrent.max_ratio and (not max_seeding_time or max_seeding_time < 0):
|
||||
msg = logger.insert_space(f"Share Limit: Max Ratio = {max_ratio}", 4)
|
||||
if do_print:
|
||||
body += logger.print_line(msg, self.config.loglevel)
|
||||
else:
|
||||
body.append(msg)
|
||||
elif max_seeding_time != torrent.max_seeding_time and (not max_ratio or max_ratio < 0):
|
||||
msg = logger.insert_space(f"Share Limit: Max Seed Time = {max_seeding_time} min", 4)
|
||||
if do_print:
|
||||
body += logger.print_line(msg, self.config.loglevel)
|
||||
else:
|
||||
body.append(msg)
|
||||
elif max_ratio != torrent.max_ratio or max_seeding_time != torrent.max_seeding_time:
|
||||
msg = logger.insert_space(f"Share Limit: Max Ratio = {max_ratio}, Max Seed Time = {max_seeding_time} min", 4)
|
||||
if do_print:
|
||||
body += logger.print_line(msg, self.config.loglevel)
|
||||
else:
|
||||
body.append(msg)
|
||||
# Update Torrents
|
||||
if not self.config.dry_run:
|
||||
if tags and tags not in torrent.tags:
|
||||
torrent.add_tags(tags)
|
||||
if limit_upload_speed:
|
||||
if limit_upload_speed == -1:
|
||||
torrent.set_upload_limit(-1)
|
||||
else:
|
||||
torrent.set_upload_limit(limit_upload_speed * 1024)
|
||||
if not max_ratio:
|
||||
max_ratio = torrent.max_ratio
|
||||
if not max_seeding_time:
|
||||
max_seeding_time = torrent.max_seeding_time
|
||||
if "MinSeedTimeNotReached" in torrent.tags:
|
||||
return []
|
||||
torrent.set_share_limits(max_ratio, max_seeding_time)
|
||||
return body
|
||||
|
||||
def has_reached_seed_limit(self, torrent, max_ratio, max_seeding_time, min_seeding_time, resume_torrent, tracker):
|
||||
"""Check if torrent has reached seed limit"""
|
||||
body = ""
|
||||
|
||||
def _has_reached_min_seeding_time_limit():
|
||||
print_log = []
|
||||
if torrent.seeding_time >= min_seeding_time * 60:
|
||||
if "MinSeedTimeNotReached" in torrent.tags:
|
||||
torrent.remove_tags(tags="MinSeedTimeNotReached")
|
||||
return True
|
||||
else:
|
||||
if "MinSeedTimeNotReached" not in torrent.tags:
|
||||
print_log += logger.print_line(logger.insert_space(f"Torrent Name: {torrent.name}", 3), self.config.loglevel)
|
||||
print_log += logger.print_line(logger.insert_space(f"Tracker: {tracker}", 8), self.config.loglevel)
|
||||
print_log += logger.print_line(
|
||||
logger.insert_space(
|
||||
f"Min seed time not met: {timedelta(seconds=torrent.seeding_time)} <= "
|
||||
f"{timedelta(minutes=min_seeding_time)}. Removing Share Limits so qBittorrent can continue seeding.",
|
||||
8,
|
||||
),
|
||||
self.config.loglevel,
|
||||
)
|
||||
print_log += logger.print_line(
|
||||
logger.insert_space("Adding Tag: MinSeedTimeNotReached", 8), self.config.loglevel
|
||||
)
|
||||
if not self.config.dry_run:
|
||||
torrent.add_tags("MinSeedTimeNotReached")
|
||||
torrent.set_share_limits(-1, -1)
|
||||
if resume_torrent:
|
||||
torrent.resume()
|
||||
return False
|
||||
|
||||
def _has_reached_seeding_time_limit():
|
||||
nonlocal body
|
||||
seeding_time_limit = None
|
||||
if not max_seeding_time:
|
||||
return False
|
||||
if max_seeding_time >= 0:
|
||||
seeding_time_limit = max_seeding_time
|
||||
elif max_seeding_time == -2 and self.global_max_seeding_time_enabled:
|
||||
seeding_time_limit = self.global_max_seeding_time
|
||||
else:
|
||||
return False
|
||||
if seeding_time_limit:
|
||||
if (torrent.seeding_time >= seeding_time_limit * 60) and _has_reached_min_seeding_time_limit():
|
||||
body += logger.insert_space(
|
||||
f"Seeding Time vs Max Seed Time: {timedelta(seconds=torrent.seeding_time)} >= "
|
||||
f"{timedelta(minutes=seeding_time_limit)}",
|
||||
8,
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
if max_ratio:
|
||||
if max_ratio >= 0:
|
||||
if torrent.ratio >= max_ratio and _has_reached_min_seeding_time_limit():
|
||||
body += logger.insert_space(f"Ratio vs Max Ratio: {torrent.ratio:.2f} >= {max_ratio:.2f}", 8)
|
||||
return body
|
||||
elif max_ratio == -2 and self.global_max_ratio_enabled and _has_reached_min_seeding_time_limit():
|
||||
if torrent.ratio >= self.global_max_ratio:
|
||||
body += logger.insert_space(
|
||||
f"Ratio vs Global Max Ratio: {torrent.ratio:.2f} >= {self.global_max_ratio:.2f}", 8
|
||||
)
|
||||
return body
|
||||
if _has_reached_seeding_time_limit():
|
||||
return body
|
||||
return False
|
|
@ -1,5 +1,3 @@
|
|||
import os
|
||||
|
||||
from modules import util
|
||||
|
||||
logger = util.logger
|
||||
|
@ -12,12 +10,7 @@ class TagNoHardLinks:
|
|||
self.client = qbit_manager.client
|
||||
self.stats_tagged = 0 # counter for the number of torrents that has no hardlinks
|
||||
self.stats_untagged = 0 # counter for number of torrents that previously had no hardlinks but now have hardlinks
|
||||
self.stats_deleted = 0 # counter for the number of torrents that has no hardlinks and \
|
||||
# meets the criteria for ratio limit/seed limit for deletion
|
||||
self.stats_deleted_contents = 0 # counter for the number of torrents that has no hardlinks and \
|
||||
# meets the criteria for ratio limit/seed limit for deletion including contents
|
||||
|
||||
self.tdel_dict = {} # dictionary to track the torrent names and content path that meet the deletion criteria
|
||||
self.root_dir = qbit_manager.config.root_dir
|
||||
self.remote_dir = qbit_manager.config.remote_dir
|
||||
self.nohardlinks = qbit_manager.config.nohardlinks
|
||||
|
@ -25,110 +18,29 @@ class TagNoHardLinks:
|
|||
|
||||
self.tag_nohardlinks()
|
||||
|
||||
def add_tag_no_hl(self, torrent, tracker, category, max_ratio, max_seeding_time, add_tag=True):
|
||||
def add_tag_no_hl(self, torrent, tracker, category):
|
||||
"""Add tag nohardlinks_tag to torrents with no hardlinks"""
|
||||
body = []
|
||||
body.append(logger.insert_space(f"Torrent Name: {torrent.name}", 3))
|
||||
if add_tag:
|
||||
body.append(logger.insert_space(f"Added Tag: {self.nohardlinks_tag}", 6))
|
||||
title = "Tagging Torrents with No Hardlinks"
|
||||
else:
|
||||
title = "Changing Share Ratio of Torrents with No Hardlinks"
|
||||
body.append(logger.insert_space(f"Added Tag: {self.nohardlinks_tag}", 6))
|
||||
title = "Tagging Torrents with No Hardlinks"
|
||||
body.append(logger.insert_space(f'Tracker: {tracker["url"]}', 8))
|
||||
body_tags_and_limits = self.qbt.set_tags_and_limits(
|
||||
torrent,
|
||||
max_ratio,
|
||||
max_seeding_time,
|
||||
self.nohardlinks[category]["limit_upload_speed"],
|
||||
tags=self.nohardlinks_tag,
|
||||
do_print=False,
|
||||
)
|
||||
if body_tags_and_limits or add_tag:
|
||||
self.stats_tagged += 1
|
||||
# Resume torrent if it was paused now that the share limit has changed
|
||||
if torrent.state_enum.is_complete and self.nohardlinks[category]["resume_torrent_after_untagging_noHL"]:
|
||||
if not self.config.dry_run:
|
||||
torrent.resume()
|
||||
body.extend(body_tags_and_limits)
|
||||
for rcd in body:
|
||||
logger.print_line(rcd, self.config.loglevel)
|
||||
attr = {
|
||||
"function": "tag_nohardlinks",
|
||||
"title": title,
|
||||
"body": "\n".join(body),
|
||||
"torrent_name": torrent.name,
|
||||
"torrent_category": torrent.category,
|
||||
"torrent_tag": self.nohardlinks_tag,
|
||||
"torrent_tracker": tracker["url"],
|
||||
"notifiarr_indexer": tracker["notifiarr"],
|
||||
"torrent_max_ratio": max_ratio,
|
||||
"torrent_max_seeding_time": max_seeding_time,
|
||||
"torrent_limit_upload_speed": self.nohardlinks[category]["limit_upload_speed"],
|
||||
}
|
||||
self.config.send_notifications(attr)
|
||||
|
||||
def cleanup_tagged_torrents_with_no_hardlinks(self, category):
|
||||
"""Delete any tagged torrents that meet noHL criteria"""
|
||||
# loop through torrent list again for cleanup purposes
|
||||
if self.nohardlinks[category]["cleanup"]:
|
||||
torrent_list = self.qbt.get_torrents({"category": category, "status_filter": "completed"})
|
||||
for torrent in torrent_list:
|
||||
t_name = torrent.name
|
||||
t_hash = torrent.hash
|
||||
if t_hash in self.tdel_dict and self.nohardlinks_tag in torrent.tags:
|
||||
t_count = self.qbt.torrentinfo[t_name]["count"]
|
||||
t_msg = self.qbt.torrentinfo[t_name]["msg"]
|
||||
t_status = self.qbt.torrentinfo[t_name]["status"]
|
||||
# Double check that the content path is the same before we delete anything
|
||||
if torrent["content_path"].replace(self.root_dir, self.remote_dir) == self.tdel_dict[t_hash]["content_path"]:
|
||||
tracker = self.qbt.get_tags(torrent.trackers)
|
||||
body = []
|
||||
body += logger.print_line(logger.insert_space(f"Torrent Name: {t_name}", 3), self.config.loglevel)
|
||||
body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel)
|
||||
body += logger.print_line(self.tdel_dict[t_hash]["body"], self.config.loglevel)
|
||||
body += logger.print_line(
|
||||
logger.insert_space("Cleanup: True [No hardlinks found and meets Share Limits.]", 8),
|
||||
self.config.loglevel,
|
||||
)
|
||||
attr = {
|
||||
"function": "cleanup_tag_nohardlinks",
|
||||
"title": "Removing NoHL Torrents and meets Share Limits",
|
||||
"torrent_name": t_name,
|
||||
"torrent_category": torrent.category,
|
||||
"cleanup": "True",
|
||||
"torrent_tracker": tracker["url"],
|
||||
"notifiarr_indexer": tracker["notifiarr"],
|
||||
}
|
||||
if os.path.exists(torrent["content_path"].replace(self.root_dir, self.remote_dir)):
|
||||
# Checks if any of the original torrents are working
|
||||
if t_count > 1 and ("" in t_msg or 2 in t_status):
|
||||
self.stats_deleted += 1
|
||||
attr["torrents_deleted_and_contents"] = False
|
||||
if not self.config.dry_run:
|
||||
self.qbt.tor_delete_recycle(torrent, attr)
|
||||
body += logger.print_line(
|
||||
logger.insert_space("Deleted .torrent but NOT content files.", 8),
|
||||
self.config.loglevel,
|
||||
)
|
||||
else:
|
||||
self.stats_deleted_contents += 1
|
||||
attr["torrents_deleted_and_contents"] = True
|
||||
if not self.config.dry_run:
|
||||
self.qbt.tor_delete_recycle(torrent, attr)
|
||||
body += logger.print_line(
|
||||
logger.insert_space("Deleted .torrent AND content files.", 8), self.config.loglevel
|
||||
)
|
||||
else:
|
||||
self.stats_deleted += 1
|
||||
attr["torrents_deleted_and_contents"] = False
|
||||
if not self.config.dry_run:
|
||||
self.qbt.tor_delete_recycle(torrent, attr)
|
||||
body += logger.print_line(
|
||||
logger.insert_space("Deleted .torrent but NOT content files.", 8), self.config.loglevel
|
||||
)
|
||||
attr["body"] = "\n".join(body)
|
||||
self.config.send_notifications(attr)
|
||||
self.qbt.torrentinfo[t_name]["count"] -= 1
|
||||
if not self.config.dry_run:
|
||||
torrent.add_tags(self.nohardlinks_tag)
|
||||
self.stats_tagged += 1
|
||||
for rcd in body:
|
||||
logger.print_line(rcd, self.config.loglevel)
|
||||
attr = {
|
||||
"function": "tag_nohardlinks",
|
||||
"title": title,
|
||||
"body": "\n".join(body),
|
||||
"torrent_name": torrent.name,
|
||||
"torrent_category": torrent.category,
|
||||
"torrent_tag": self.nohardlinks_tag,
|
||||
"torrent_tracker": tracker["url"],
|
||||
"notifiarr_indexer": tracker["notifiarr"],
|
||||
}
|
||||
self.config.send_notifications(attr)
|
||||
|
||||
def check_previous_nohardlinks_tagged_torrents(self, has_nohardlinks, torrent, tracker, category):
|
||||
"""
|
||||
|
@ -145,28 +57,8 @@ class TagNoHardLinks:
|
|||
)
|
||||
body += logger.print_line(logger.insert_space(f"Removed Tag: {self.nohardlinks_tag}", 6), self.config.loglevel)
|
||||
body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel)
|
||||
body += logger.print_line(
|
||||
f"{'Not Reverting' if self.config.dry_run else 'Reverting'} to tracker or Global share limits.",
|
||||
self.config.loglevel,
|
||||
)
|
||||
restore_max_ratio = tracker["max_ratio"]
|
||||
restore_max_seeding_time = tracker["max_seeding_time"]
|
||||
restore_limit_upload_speed = tracker["limit_upload_speed"]
|
||||
if restore_max_ratio is None:
|
||||
restore_max_ratio = -2
|
||||
if restore_max_seeding_time is None:
|
||||
restore_max_seeding_time = -2
|
||||
if restore_limit_upload_speed is None:
|
||||
restore_limit_upload_speed = -1
|
||||
if not self.config.dry_run:
|
||||
torrent.remove_tags(tags=self.nohardlinks_tag)
|
||||
body.extend(
|
||||
self.qbt.set_tags_and_limits(
|
||||
torrent, restore_max_ratio, restore_max_seeding_time, restore_limit_upload_speed, restore=True
|
||||
)
|
||||
)
|
||||
if torrent.state_enum.is_complete and self.nohardlinks[category]["resume_torrent_after_untagging_noHL"]:
|
||||
torrent.resume()
|
||||
attr = {
|
||||
"function": "untag_nohardlinks",
|
||||
"title": "Untagging Previous Torrents that now have hardlinks",
|
||||
|
@ -176,9 +68,6 @@ class TagNoHardLinks:
|
|||
"torrent_tag": self.nohardlinks_tag,
|
||||
"torrent_tracker": tracker["url"],
|
||||
"notifiarr_indexer": tracker["notifiarr"],
|
||||
"torrent_max_ratio": restore_max_ratio,
|
||||
"torrent_max_seeding_time": restore_max_seeding_time,
|
||||
"torrent_limit_upload_speed": restore_limit_upload_speed,
|
||||
}
|
||||
self.config.send_notifications(attr)
|
||||
|
||||
|
@ -211,120 +100,17 @@ class TagNoHardLinks:
|
|||
# Cleans up previously tagged nohardlinks_tag torrents that no longer have hardlinks
|
||||
if has_nohardlinks:
|
||||
tracker = self.qbt.get_tags(torrent.trackers)
|
||||
# Determine min_seeding_time.
|
||||
# If only tracker setting is set, use tracker's min_seeding_time
|
||||
# If only nohardlinks category setting is set, use nohardlinks category's min_seeding_time
|
||||
# If both tracker and nohardlinks category setting is set, use the larger of the two
|
||||
# If neither set, use 0 (no limit)
|
||||
min_seeding_time = 0
|
||||
logger.trace(f'tracker["min_seeding_time"] is {tracker["min_seeding_time"]}')
|
||||
logger.trace(f'nohardlinks[category]["min_seeding_time"] is {nohardlinks[category]["min_seeding_time"]}')
|
||||
if tracker["min_seeding_time"] is not None and nohardlinks[category]["min_seeding_time"] is not None:
|
||||
if tracker["min_seeding_time"] >= nohardlinks[category]["min_seeding_time"]:
|
||||
min_seeding_time = tracker["min_seeding_time"]
|
||||
logger.trace(f'Using tracker["min_seeding_time"] {min_seeding_time}')
|
||||
else:
|
||||
min_seeding_time = nohardlinks[category]["min_seeding_time"]
|
||||
logger.trace(f'Using nohardlinks[category]["min_seeding_time"] {min_seeding_time}')
|
||||
elif nohardlinks[category]["min_seeding_time"]:
|
||||
min_seeding_time = nohardlinks[category]["min_seeding_time"]
|
||||
logger.trace(f'Using nohardlinks[category]["min_seeding_time"] {min_seeding_time}')
|
||||
elif tracker["min_seeding_time"]:
|
||||
min_seeding_time = tracker["min_seeding_time"]
|
||||
logger.trace(f'Using tracker["min_seeding_time"] {min_seeding_time}')
|
||||
else:
|
||||
logger.trace(f"Using default min_seeding_time {min_seeding_time}")
|
||||
# Determine max_ratio.
|
||||
# If only tracker setting is set, use tracker's max_ratio
|
||||
# If only nohardlinks category setting is set, use nohardlinks category's max_ratio
|
||||
# If both tracker and nohardlinks category setting is set, use the larger of the two
|
||||
# If neither set, use -1 (no limit)
|
||||
max_ratio = -1
|
||||
logger.trace(f'tracker["max_ratio"] is {tracker["max_ratio"]}')
|
||||
logger.trace(f'nohardlinks[category]["max_ratio"] is {nohardlinks[category]["max_ratio"]}')
|
||||
if tracker["max_ratio"] is not None and nohardlinks[category]["max_ratio"] is not None:
|
||||
if tracker["max_ratio"] >= nohardlinks[category]["max_ratio"]:
|
||||
max_ratio = tracker["max_ratio"]
|
||||
logger.trace(f'Using (tracker["max_ratio"]) {max_ratio}')
|
||||
else:
|
||||
max_ratio = nohardlinks[category]["max_ratio"]
|
||||
logger.trace(f'Using (nohardlinks[category]["max_ratio"]) {max_ratio}')
|
||||
elif nohardlinks[category]["max_ratio"]:
|
||||
max_ratio = nohardlinks[category]["max_ratio"]
|
||||
logger.trace(f'Using (nohardlinks[category]["max_ratio"]) {max_ratio}')
|
||||
elif tracker["max_ratio"]:
|
||||
max_ratio = tracker["max_ratio"]
|
||||
logger.trace(f'Using (tracker["max_ratio"]) {max_ratio}')
|
||||
else:
|
||||
logger.trace(f"Using default (max_ratio) {max_ratio}")
|
||||
# Determine max_seeding_time.
|
||||
# If only tracker setting is set, use tracker's max_seeding_time
|
||||
# If only nohardlinks category setting is set, use nohardlinks category's max_seeding_time
|
||||
# If both tracker and nohardlinks category setting is set, use the larger of the two
|
||||
# If neither set, use -1 (no limit)
|
||||
max_seeding_time = -1
|
||||
logger.trace(f'tracker["max_seeding_time"] is {tracker["max_seeding_time"]}')
|
||||
logger.trace(f'nohardlinks[category]["max_seeding_time"] is {nohardlinks[category]["max_seeding_time"]}')
|
||||
if tracker["max_seeding_time"] is not None and nohardlinks[category]["max_seeding_time"] is not None:
|
||||
if tracker["max_seeding_time"] >= nohardlinks[category]["max_seeding_time"]:
|
||||
max_seeding_time = tracker["max_seeding_time"]
|
||||
logger.trace(f'Using (tracker["max_seeding_time"]) {max_seeding_time}')
|
||||
else:
|
||||
max_seeding_time = nohardlinks[category]["max_seeding_time"]
|
||||
logger.trace(f'Using (nohardlinks[category]["max_seeding_time"]) {max_seeding_time}')
|
||||
elif nohardlinks[category]["max_seeding_time"]:
|
||||
max_seeding_time = nohardlinks[category]["max_seeding_time"]
|
||||
logger.trace(f'Using (nohardlinks[category]["max_seeding_time"]) {max_seeding_time}')
|
||||
elif tracker["max_seeding_time"]:
|
||||
max_seeding_time = tracker["max_seeding_time"]
|
||||
logger.trace(f'Using (tracker["max_seeding_time"]) {max_seeding_time}')
|
||||
else:
|
||||
logger.trace(f"Using default (max_seeding_time) {max_seeding_time}")
|
||||
# Will only tag new torrents that don't have nohardlinks_tag tag
|
||||
if self.nohardlinks_tag not in torrent.tags:
|
||||
self.add_tag_no_hl(
|
||||
torrent=torrent,
|
||||
tracker=tracker,
|
||||
category=category,
|
||||
max_ratio=max_ratio,
|
||||
max_seeding_time=max_seeding_time,
|
||||
add_tag=True,
|
||||
)
|
||||
|
||||
# Deletes torrent with data if cleanup is set to true and meets the ratio/seeding requirements
|
||||
if nohardlinks[category]["cleanup"] and len(nohardlinks[category]) > 0:
|
||||
tor_reach_seed_limit = self.qbt.has_reached_seed_limit(
|
||||
torrent,
|
||||
max_ratio,
|
||||
max_seeding_time,
|
||||
min_seeding_time,
|
||||
nohardlinks[category]["resume_torrent_after_untagging_noHL"],
|
||||
tracker["url"],
|
||||
)
|
||||
if tor_reach_seed_limit:
|
||||
if torrent.hash not in self.tdel_dict:
|
||||
self.tdel_dict[torrent.hash] = {}
|
||||
self.tdel_dict[torrent.hash]["content_path"] = torrent["content_path"].replace(
|
||||
self.root_dir, self.remote_dir
|
||||
)
|
||||
self.tdel_dict[torrent.hash]["body"] = tor_reach_seed_limit
|
||||
else:
|
||||
# Updates torrent to see if "MinSeedTimeNotReached" tag has been added
|
||||
torrent = self.qbt.get_torrents({"torrent_hashes": [torrent.hash]}).data[0]
|
||||
# Checks to see if previously nohardlinks_tag share limits have changed.
|
||||
self.add_tag_no_hl(
|
||||
torrent=torrent,
|
||||
tracker=tracker,
|
||||
category=category,
|
||||
max_ratio=max_ratio,
|
||||
max_seeding_time=max_seeding_time,
|
||||
add_tag=False,
|
||||
)
|
||||
self.check_previous_nohardlinks_tagged_torrents(has_nohardlinks, torrent, tracker, category)
|
||||
self.cleanup_tagged_torrents_with_no_hardlinks(category)
|
||||
if self.stats_tagged >= 1:
|
||||
logger.print_line(
|
||||
f"{'Did not Tag/set' if self.config.dry_run else 'Tag/set'} share limits for {self.stats_tagged} "
|
||||
f"{'Did not Tag' if self.config.dry_run else 'Added Tag'} for {self.stats_tagged} "
|
||||
f".torrent{'s.' if self.stats_tagged > 1 else '.'}",
|
||||
self.config.loglevel,
|
||||
)
|
||||
|
@ -333,19 +119,7 @@ class TagNoHardLinks:
|
|||
if self.stats_untagged >= 1:
|
||||
logger.print_line(
|
||||
f"{'Did not delete' if self.config.dry_run else 'Deleted'} "
|
||||
f"{self.nohardlinks_tag} tags / share limits for {self.stats_untagged} "
|
||||
f"{self.nohardlinks_tag} tags for {self.stats_untagged} "
|
||||
f".torrent{'s.' if self.stats_untagged > 1 else '.'}",
|
||||
self.config.loglevel,
|
||||
)
|
||||
if self.stats_deleted >= 1:
|
||||
logger.print_line(
|
||||
f"{'Did not delete' if self.config.dry_run else 'Deleted'} {self.stats_deleted} "
|
||||
f".torrent{'s' if self.stats_deleted > 1 else ''} but not content files.",
|
||||
self.config.loglevel,
|
||||
)
|
||||
if self.stats_deleted_contents >= 1:
|
||||
logger.print_line(
|
||||
f"{'Did not delete' if self.config.dry_run else 'Deleted'} {self.stats_deleted_contents} "
|
||||
f".torrent{'s' if self.stats_deleted_contents > 1 else ''} AND content files.",
|
||||
self.config.loglevel,
|
||||
)
|
||||
|
|
|
@ -9,6 +9,7 @@ class Tags:
|
|||
self.config = qbit_manager.config
|
||||
self.client = qbit_manager.client
|
||||
self.stats = 0
|
||||
self.share_limits_suffix_tag = qbit_manager.config.share_limits_suffix_tag # suffix tag for share limits
|
||||
|
||||
self.tags()
|
||||
|
||||
|
@ -17,7 +18,8 @@ class Tags:
|
|||
ignore_tags = self.config.settings["ignoreTags_OnUpdate"]
|
||||
logger.separator("Updating Tags", space=False, border=False)
|
||||
for torrent in self.qbt.torrent_list:
|
||||
check_tags = util.get_list(torrent.tags)
|
||||
check_tags = [tag for tag in util.get_list(torrent.tags) if self.share_limits_suffix_tag not in tag]
|
||||
|
||||
if torrent.tags == "" or (len([trk for trk in check_tags if trk not in ignore_tags]) == 0):
|
||||
tracker = self.qbt.get_tags(torrent.trackers)
|
||||
if tracker["tag"]:
|
||||
|
@ -29,15 +31,8 @@ class Tags:
|
|||
self.config.loglevel,
|
||||
)
|
||||
body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel)
|
||||
body.extend(
|
||||
self.qbt.set_tags_and_limits(
|
||||
torrent,
|
||||
tracker["max_ratio"],
|
||||
tracker["max_seeding_time"],
|
||||
tracker["limit_upload_speed"],
|
||||
tracker["tag"],
|
||||
)
|
||||
)
|
||||
if not self.config.dry_run:
|
||||
torrent.add_tags(tracker["tag"])
|
||||
category = self.qbt.get_category(torrent.save_path) if torrent.category == "" else torrent.category
|
||||
attr = {
|
||||
"function": "tag_update",
|
||||
|
@ -48,9 +43,6 @@ class Tags:
|
|||
"torrent_tag": ", ".join(tracker["tag"]),
|
||||
"torrent_tracker": tracker["url"],
|
||||
"notifiarr_indexer": tracker["notifiarr"],
|
||||
"torrent_max_ratio": tracker["max_ratio"],
|
||||
"torrent_max_seeding_time": tracker["max_seeding_time"],
|
||||
"torrent_limit_upload_speed": tracker["limit_upload_speed"],
|
||||
}
|
||||
self.config.send_notifications(attr)
|
||||
if self.stats >= 1:
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""Qbittorrent Module"""
|
||||
import os
|
||||
import sys
|
||||
from datetime import timedelta
|
||||
|
||||
from qbittorrentapi import APIConnectionError
|
||||
from qbittorrentapi import Client
|
||||
|
@ -25,7 +24,7 @@ class Qbt:
|
|||
|
||||
SUPPORTED_VERSION = Version.latest_supported_app_version()
|
||||
MIN_SUPPORTED_VERSION = "v4.3.0"
|
||||
TORRENT_DICT_COMMANDS = ["recheck", "cross_seed", "rem_unregistered", "tag_tracker_error", "tag_nohardlinks"]
|
||||
TORRENT_DICT_COMMANDS = ["recheck", "cross_seed", "rem_unregistered", "tag_tracker_error", "tag_nohardlinks", "share_limits"]
|
||||
|
||||
def __init__(self, config, params):
|
||||
self.config = config
|
||||
|
@ -202,142 +201,11 @@ class Qbt:
|
|||
"""Get torrents from qBittorrent"""
|
||||
return self.client.torrents.info(**params)
|
||||
|
||||
def set_tags_and_limits(
|
||||
self, torrent, max_ratio, max_seeding_time, limit_upload_speed=None, tags=None, restore=False, do_print=True
|
||||
):
|
||||
"""Set tags and limits for a torrent"""
|
||||
body = []
|
||||
if limit_upload_speed:
|
||||
if limit_upload_speed != -1:
|
||||
msg = logger.insert_space(f"Limit UL Speed: {limit_upload_speed} kB/s", 1)
|
||||
if do_print:
|
||||
body += logger.print_line(msg, self.config.loglevel)
|
||||
else:
|
||||
body.append(msg)
|
||||
if max_ratio or max_seeding_time:
|
||||
if (max_ratio == -2 and max_seeding_time == -2) and not restore:
|
||||
msg = logger.insert_space("Share Limit: Use Global Share Limit", 4)
|
||||
if do_print:
|
||||
body += logger.print_line(msg, self.config.loglevel)
|
||||
else:
|
||||
body.append(msg)
|
||||
elif (max_ratio == -1 and max_seeding_time == -1) and not restore:
|
||||
msg = logger.insert_space("Share Limit: Set No Share Limit", 4)
|
||||
if do_print:
|
||||
body += logger.print_line(msg, self.config.loglevel)
|
||||
else:
|
||||
body.append(msg)
|
||||
else:
|
||||
if max_ratio != torrent.max_ratio and (not max_seeding_time or max_seeding_time < 0):
|
||||
msg = logger.insert_space(f"Share Limit: Max Ratio = {max_ratio}", 4)
|
||||
if do_print:
|
||||
body += logger.print_line(msg, self.config.loglevel)
|
||||
else:
|
||||
body.append(msg)
|
||||
elif max_seeding_time != torrent.max_seeding_time and (not max_ratio or max_ratio < 0):
|
||||
msg = logger.insert_space(f"Share Limit: Max Seed Time = {max_seeding_time} min", 4)
|
||||
if do_print:
|
||||
body += logger.print_line(msg, self.config.loglevel)
|
||||
else:
|
||||
body.append(msg)
|
||||
elif max_ratio != torrent.max_ratio or max_seeding_time != torrent.max_seeding_time:
|
||||
msg = logger.insert_space(f"Share Limit: Max Ratio = {max_ratio}, Max Seed Time = {max_seeding_time} min", 4)
|
||||
if do_print:
|
||||
body += logger.print_line(msg, self.config.loglevel)
|
||||
else:
|
||||
body.append(msg)
|
||||
# Update Torrents
|
||||
if not self.config.dry_run:
|
||||
if tags:
|
||||
torrent.add_tags(tags)
|
||||
if limit_upload_speed:
|
||||
if limit_upload_speed == -1:
|
||||
torrent.set_upload_limit(-1)
|
||||
else:
|
||||
torrent.set_upload_limit(limit_upload_speed * 1024)
|
||||
if not max_ratio:
|
||||
max_ratio = torrent.max_ratio
|
||||
if not max_seeding_time:
|
||||
max_seeding_time = torrent.max_seeding_time
|
||||
if "MinSeedTimeNotReached" in torrent.tags:
|
||||
return []
|
||||
torrent.set_share_limits(max_ratio, max_seeding_time)
|
||||
return body
|
||||
|
||||
def has_reached_seed_limit(self, torrent, max_ratio, max_seeding_time, min_seeding_time, resume_torrent, tracker):
|
||||
"""Check if torrent has reached seed limit"""
|
||||
body = ""
|
||||
|
||||
def _has_reached_min_seeding_time_limit():
|
||||
print_log = []
|
||||
if torrent.seeding_time >= min_seeding_time * 60:
|
||||
if "MinSeedTimeNotReached" in torrent.tags:
|
||||
torrent.remove_tags(tags="MinSeedTimeNotReached")
|
||||
return True
|
||||
else:
|
||||
print_log += logger.print_line(logger.insert_space(f"Torrent Name: {torrent.name}", 3), self.config.loglevel)
|
||||
print_log += logger.print_line(logger.insert_space(f"Tracker: {tracker}", 8), self.config.loglevel)
|
||||
print_log += logger.print_line(
|
||||
logger.insert_space(
|
||||
f"Min seed time not met: {timedelta(seconds=torrent.seeding_time)} <= "
|
||||
f"{timedelta(minutes=min_seeding_time)}. Removing Share Limits so qBittorrent can continue seeding.",
|
||||
8,
|
||||
),
|
||||
self.config.loglevel,
|
||||
)
|
||||
print_log += logger.print_line(logger.insert_space("Adding Tag: MinSeedTimeNotReached", 8), self.config.loglevel)
|
||||
if not self.config.dry_run:
|
||||
torrent.add_tags("MinSeedTimeNotReached")
|
||||
torrent.set_share_limits(-1, -1)
|
||||
if resume_torrent:
|
||||
torrent.resume()
|
||||
return False
|
||||
|
||||
def _has_reached_seeding_time_limit():
|
||||
nonlocal body
|
||||
seeding_time_limit = None
|
||||
if not max_seeding_time:
|
||||
return False
|
||||
if max_seeding_time >= 0:
|
||||
seeding_time_limit = max_seeding_time
|
||||
elif max_seeding_time == -2 and self.global_max_seeding_time_enabled:
|
||||
seeding_time_limit = self.global_max_seeding_time
|
||||
else:
|
||||
return False
|
||||
if seeding_time_limit:
|
||||
if (torrent.seeding_time >= seeding_time_limit * 60) and _has_reached_min_seeding_time_limit():
|
||||
body += logger.insert_space(
|
||||
f"Seeding Time vs Max Seed Time: {timedelta(seconds=torrent.seeding_time)} >= "
|
||||
f"{timedelta(minutes=seeding_time_limit)}",
|
||||
8,
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
if max_ratio:
|
||||
if max_ratio >= 0:
|
||||
if torrent.ratio >= max_ratio and _has_reached_min_seeding_time_limit():
|
||||
body += logger.insert_space(f"Ratio vs Max Ratio: {torrent.ratio:.2f} >= {max_ratio:.2f}", 8)
|
||||
return body
|
||||
elif max_ratio == -2 and self.global_max_ratio_enabled and _has_reached_min_seeding_time_limit():
|
||||
if torrent.ratio >= self.global_max_ratio:
|
||||
body += logger.insert_space(
|
||||
f"Ratio vs Global Max Ratio: {torrent.ratio:.2f} >= {self.global_max_ratio:.2f}", 8
|
||||
)
|
||||
return body
|
||||
if _has_reached_seeding_time_limit():
|
||||
return body
|
||||
return False
|
||||
|
||||
def get_tags(self, trackers):
|
||||
"""Get tags from config file based on keyword"""
|
||||
urls = [x.url for x in trackers if x.url.startswith("http")]
|
||||
tracker = {}
|
||||
tracker["tag"] = None
|
||||
tracker["max_ratio"] = None
|
||||
tracker["min_seeding_time"] = None
|
||||
tracker["max_seeding_time"] = None
|
||||
tracker["limit_upload_speed"] = None
|
||||
tracker["notifiarr"] = None
|
||||
tracker["url"] = None
|
||||
tracker_other_tag = self.config.util.check_for_attribute(
|
||||
|
@ -376,76 +244,6 @@ class Qbt:
|
|||
self.config.data["tracker"][tag_url]["tag"] = [tag_url]
|
||||
if isinstance(tracker["tag"], str):
|
||||
tracker["tag"] = [tracker["tag"]]
|
||||
is_max_ratio_defined = self.config.data["tracker"].get("max_ratio")
|
||||
is_max_seeding_time_defined = self.config.data["tracker"].get("max_seeding_time")
|
||||
if is_max_ratio_defined or is_max_seeding_time_defined:
|
||||
tracker["max_ratio"] = self.config.util.check_for_attribute(
|
||||
self.config.data,
|
||||
"max_ratio",
|
||||
parent="tracker",
|
||||
subparent=tag_url,
|
||||
var_type="float",
|
||||
min_int=-2,
|
||||
do_print=False,
|
||||
default=-1,
|
||||
save=False,
|
||||
)
|
||||
tracker["max_seeding_time"] = self.config.util.check_for_attribute(
|
||||
self.config.data,
|
||||
"max_seeding_time",
|
||||
parent="tracker",
|
||||
subparent=tag_url,
|
||||
var_type="int",
|
||||
min_int=-2,
|
||||
do_print=False,
|
||||
default=-1,
|
||||
save=False,
|
||||
)
|
||||
else:
|
||||
tracker["max_ratio"] = self.config.util.check_for_attribute(
|
||||
self.config.data,
|
||||
"max_ratio",
|
||||
parent="tracker",
|
||||
subparent=tag_url,
|
||||
var_type="float",
|
||||
min_int=-2,
|
||||
do_print=False,
|
||||
default_is_none=True,
|
||||
save=False,
|
||||
)
|
||||
tracker["max_seeding_time"] = self.config.util.check_for_attribute(
|
||||
self.config.data,
|
||||
"max_seeding_time",
|
||||
parent="tracker",
|
||||
subparent=tag_url,
|
||||
var_type="int",
|
||||
min_int=-2,
|
||||
do_print=False,
|
||||
default_is_none=True,
|
||||
save=False,
|
||||
)
|
||||
tracker["min_seeding_time"] = self.config.util.check_for_attribute(
|
||||
self.config.data,
|
||||
"min_seeding_time",
|
||||
parent="tracker",
|
||||
subparent=tag_url,
|
||||
var_type="int",
|
||||
min_int=0,
|
||||
do_print=False,
|
||||
default=0,
|
||||
save=False,
|
||||
)
|
||||
tracker["limit_upload_speed"] = self.config.util.check_for_attribute(
|
||||
self.config.data,
|
||||
"limit_upload_speed",
|
||||
parent="tracker",
|
||||
subparent=tag_url,
|
||||
var_type="int",
|
||||
min_int=-1,
|
||||
do_print=False,
|
||||
default=0,
|
||||
save=False,
|
||||
)
|
||||
tracker["notifiarr"] = self.config.util.check_for_attribute(
|
||||
self.config.data,
|
||||
"notifiarr",
|
||||
|
@ -458,50 +256,6 @@ class Qbt:
|
|||
return tracker
|
||||
if tracker_other_tag:
|
||||
tracker["tag"] = tracker_other_tag
|
||||
tracker["max_ratio"] = self.config.util.check_for_attribute(
|
||||
self.config.data,
|
||||
"max_ratio",
|
||||
parent="tracker",
|
||||
subparent="other",
|
||||
var_type="float",
|
||||
min_int=-2,
|
||||
do_print=False,
|
||||
default=-1,
|
||||
save=False,
|
||||
)
|
||||
tracker["min_seeding_time"] = self.config.util.check_for_attribute(
|
||||
self.config.data,
|
||||
"min_seeding_time",
|
||||
parent="tracker",
|
||||
subparent="other",
|
||||
var_type="int",
|
||||
min_int=0,
|
||||
do_print=False,
|
||||
default=-1,
|
||||
save=False,
|
||||
)
|
||||
tracker["max_seeding_time"] = self.config.util.check_for_attribute(
|
||||
self.config.data,
|
||||
"max_seeding_time",
|
||||
parent="tracker",
|
||||
subparent="other",
|
||||
var_type="int",
|
||||
min_int=-2,
|
||||
do_print=False,
|
||||
default=-1,
|
||||
save=False,
|
||||
)
|
||||
tracker["limit_upload_speed"] = self.config.util.check_for_attribute(
|
||||
self.config.data,
|
||||
"limit_upload_speed",
|
||||
parent="tracker",
|
||||
subparent="other",
|
||||
var_type="int",
|
||||
min_int=-1,
|
||||
do_print=False,
|
||||
default=0,
|
||||
save=False,
|
||||
)
|
||||
tracker["notifiarr"] = self.config.util.check_for_attribute(
|
||||
self.config.data,
|
||||
"notifiarr",
|
||||
|
|
|
@ -141,6 +141,16 @@ parser.add_argument(
|
|||
"When files get upgraded they no longer become linked with your media therefore will be tagged with a new tag noHL. "
|
||||
"You can then safely delete/remove these torrents to free up any extra space that is not being used by your media folder.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-sl",
|
||||
"--share-limits",
|
||||
dest="share_limits",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Use this to help apply and manage your torrent share limits based on your tags/categories."
|
||||
"This can apply a max ratio, seed time limits to your torrents or limit your torrent upload speed as well."
|
||||
"Share limits are applied in the order of priority specified.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-sc",
|
||||
"--skip-cleanup",
|
||||
|
@ -237,6 +247,7 @@ rem_unregistered = get_arg("QBT_REM_UNREGISTERED", args.rem_unregistered, arg_bo
|
|||
tag_tracker_error = get_arg("QBT_TAG_TRACKER_ERROR", args.tag_tracker_error, arg_bool=True)
|
||||
rem_orphaned = get_arg("QBT_REM_ORPHANED", args.rem_orphaned, arg_bool=True)
|
||||
tag_nohardlinks = get_arg("QBT_TAG_NOHARDLINKS", args.tag_nohardlinks, arg_bool=True)
|
||||
share_limits = get_arg("QBT_SHARE_LIMITS", args.share_limits, arg_bool=True)
|
||||
skip_cleanup = get_arg("QBT_SKIP_CLEANUP", args.skip_cleanup, arg_bool=True)
|
||||
skip_qb_version_check = get_arg("QBT_SKIP_QB_VERSION_CHECK", args.skip_qb_version_check, arg_bool=True)
|
||||
dry_run = get_arg("QBT_DRY_RUN", args.dry_run, arg_bool=True)
|
||||
|
@ -285,6 +296,7 @@ for v in [
|
|||
"tag_tracker_error",
|
||||
"rem_orphaned",
|
||||
"tag_nohardlinks",
|
||||
"share_limits",
|
||||
"skip_cleanup",
|
||||
"skip_qb_version_check",
|
||||
"dry_run",
|
||||
|
@ -329,6 +341,7 @@ from modules.core.cross_seed import CrossSeed # noqa
|
|||
from modules.core.recheck import ReCheck # noqa
|
||||
from modules.core.tag_nohardlinks import TagNoHardLinks # noqa
|
||||
from modules.core.remove_orphaned import RemoveOrphaned # noqa
|
||||
from modules.core.share_limits import ShareLimits # noqa
|
||||
|
||||
|
||||
def my_except_hook(exctype, value, tbi):
|
||||
|
@ -458,8 +471,13 @@ def start():
|
|||
stats["tagged"] += no_hardlinks.stats_tagged
|
||||
stats["tagged_noHL"] += no_hardlinks.stats_tagged
|
||||
stats["untagged_noHL"] += no_hardlinks.stats_untagged
|
||||
stats["deleted"] += no_hardlinks.stats_deleted
|
||||
stats["deleted_contents"] += no_hardlinks.stats_deleted_contents
|
||||
|
||||
# Set Share Limits
|
||||
if cfg.commands["share_limits"]:
|
||||
share_limits = ShareLimits(qbit_manager)
|
||||
stats["tagged"] += share_limits.stats_tagged
|
||||
stats["deleted"] += share_limits.stats_deleted
|
||||
stats["deleted_contents"] += share_limits.stats_deleted_contents
|
||||
|
||||
# Remove Orphaned Files
|
||||
if cfg.commands["rem_orphaned"]:
|
||||
|
@ -583,6 +601,7 @@ if __name__ == "__main__":
|
|||
logger.debug(f" --tag-tracker-error (QBT_TAG_TRACKER_ERROR): {tag_tracker_error}")
|
||||
logger.debug(f" --rem-orphaned (QBT_REM_ORPHANED): {rem_orphaned}")
|
||||
logger.debug(f" --tag-nohardlinks (QBT_TAG_NOHARDLINKS): {tag_nohardlinks}")
|
||||
logger.debug(f" --share-limits (QBT_SHARE_LIMITS): {share_limits}")
|
||||
logger.debug(f" --skip-cleanup (QBT_SKIP_CLEANUP): {skip_cleanup}")
|
||||
logger.debug(f" --skip-qb-version-check (QBT_SKIP_QB_VERSION_CHECK): {skip_qb_version_check}")
|
||||
logger.debug(f" --dry-run (QBT_DRY_RUN): {dry_run}")
|
||||
|
|
Loading…
Add table
Reference in a new issue