Merge pull request #344 from StuffAnThings/develop

4.0.2
This commit is contained in:
bobokun 2023-06-27 20:03:54 -04:00 committed by GitHub
commit 16d545067f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 121 additions and 54 deletions

View file

@ -27,16 +27,16 @@ repos:
args: [--format, parsable, --strict]
exclude: ^.github/
- repo: https://github.com/lyz-code/yamlfix
rev: 1.10.0
rev: 1.11.0
hooks:
- id: yamlfix
exclude: ^.github/
- repo: https://github.com/asottile/reorder-python-imports
rev: v3.9.0
rev: v3.10.0
hooks:
- id: reorder-python-imports
- repo: https://github.com/asottile/pyupgrade
rev: v3.6.0
rev: v3.7.0
hooks:
- id: pyupgrade
args: [--py3-plus]

View file

@ -1,26 +1,13 @@
# Requirements Updated
- qbitorrent-api updated to 2023.6.49
- qbitorrent-api updated to 2023.6.50
- ruamel.yaml updated to 0.17.32
# New Features
- cross-seed will move torrent to an error folder if it fails to inject torrent
- Define multiple announce urls for one tracker (#328 Thanks to @buthed010203 for the PR)
- Adds min_num_seeds condition to share_limits (Closes [#321](https://github.com/StuffAnThings/qbit_manage/issues/321))
# Bug Fixes
- Fixes #329 (Updates missing share_limits tag even when share_limits are satisfied)
- Fixes #327 (Zero value share limits not being applied correctly)
Fixes [#333](https://github.com/StuffAnThings/qbit_manage/issues/333)
Fixes [#340](https://github.com/StuffAnThings/qbit_manage/issues/340)
Fixes [#343](https://github.com/StuffAnThings/qbit_manage/issues/343)
Fixes bug when checking tags in torrents
# Enhancements
- Logic for `share_limits_suffix_tag` changed to become a prefix tag instead along with adding the priority of the group. The reason for this change is so it's easier to see the share limit groups togethered in qbitorrent ordered by priority.
- `share_limits_suffix_tag` key is now `share_limits_tag`
- No config changes are required as the qbm will automatically change the previous `share_limits_suffix_tag` key to `share_limits_tag`
- Changes the default value of `share_limits_tag` to `~share_limit`. The `~` is used to sort the `share_limits_tag` in the qbt webUI to the bottom for less clutter
Example based on config.sample:
| old tag (v4.0.0) | new tag (v4.0.1) |
| ----------- | ----------- |
| noHL.share_limit | ~share_limit_1.noHL |
| cross-seed.share_limit | ~share_limit_2.cross-seed |
| PTP.share_limit | ~share_limit_3.PTP |
| default.share_limit | ~share_limit_999.default |
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.0.0...v4.0.1
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.0.1...v4.0.2

View file

@ -1 +1 @@
4.0.1
4.0.2

View file

@ -176,6 +176,7 @@ share_limits:
# Will default to -1 (no limit) 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).
# If the torrent has not yet reached this minimum seeding time, it will change the share limits back to no limits and resume the torrent to continue seeding.
# Will default to 0 if not specified for the group.
min_seeding_time: 43200
# <OPTIONAL> Limit Upload Speed <int>: Will limit the upload speed KiB/s (KiloBytes/second) (`-1` : No Limit)
@ -187,6 +188,10 @@ share_limits:
# <OPTIONAL> add_group_to_tag <bool>: This adds your grouping as a tag with a prefix defined in settings . Default is true
# Example: A grouping defined as noHL will have a tag set to ~share_limit.noHL (if using the default prefix)
add_group_to_tag: true
# <OPTIONAL> min_num_seeds <int>: This will prevent torrent deletion by cleanup variable if the number of seeds is less than the value set here.
# If the torrent has less number of seeds than the min_num_seeds, the share limits will be changed back to no limits and resume the torrent to continue seeding.
# Will default to 0 if not specified for the group.
min_num_seeds: 0
cross-seed:
priority: 2
include_all_tags:
@ -237,7 +242,7 @@ orphaned:
- "**/@eaDir"
- "/data/torrents/temp/**"
- "**/*.!qB"
- "**/_unpackerred"
- "**/*_unpackerred"
apprise:
# Apprise integration with webhooks

View file

@ -432,6 +432,17 @@ class Config:
do_print=False,
save=False,
)
self.share_limits[group]["min_num_seeds"] = self.util.check_for_attribute(
self.data,
"min_num_seeds",
parent="share_limits",
subparent=group,
var_type="int",
min_int=0,
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",

View file

@ -112,7 +112,7 @@ class CrossSeed:
t_name = torrent.name
t_cat = torrent.category
if (
"cross-seed" not in torrent.tags
not util.is_tag_in_torrent("cross-seed", torrent.tags)
and self.qbt.torrentinfo[t_name]["count"] > 1
and self.qbt.torrentinfo[t_name]["first_hash"] != torrent.hash
):

View file

@ -31,7 +31,7 @@ class RemoveOrphaned:
orphaned_files = []
excluded_orphan_files = []
root_files = self.executor.submit(util.get_root_files, self.remote_dir, self.root_dir, self.orphaned_dir)
root_files = self.executor.submit(util.get_root_files, self.root_dir, self.remote_dir, self.orphaned_dir)
# Get an updated list of torrents
logger.print_line("Locating orphan files", self.config.loglevel)

View file

@ -108,12 +108,6 @@ class RemoveUnregistered:
msg_up = trk.msg.upper()
msg = trk.msg
if TrackerStatus(trk.status) == TrackerStatus.NOT_WORKING:
# Tag any error torrents
if self.cfg_tag_error and self.tag_error not in check_tags:
if not list_in_text(msg_up, TorrentMessages.IGNORE_MSGS) and not list_in_text(
msg_up, TorrentMessages.UNREGISTERED_MSGS
):
self.tag_tracker_error(msg, tracker, torrent)
# Check for unregistered torrents
if self.cfg_rem_unregistered:
if list_in_text(msg_up, TorrentMessages.UNREGISTERED_MSGS) and not list_in_text(
@ -125,6 +119,9 @@ class RemoveUnregistered:
if self.check_for_unregistered_torrents_using_bhd_api(tracker, msg_up, torrent.hash):
self.del_unregistered(msg, tracker, torrent)
break
# Tag any error torrents
if self.cfg_tag_error and self.tag_error not in check_tags:
self.tag_tracker_error(msg, tracker, torrent)
except NotFound404Error:
continue

View file

@ -2,10 +2,14 @@ import os
from datetime import timedelta
from modules import util
from modules.util import is_tag_in_torrent
from modules.webhooks import GROUP_NOTIFICATION_LIMIT
logger = util.logger
MIN_SEEDING_TIME_TAG = "MinSeedTimeNotReached"
MIN_NUM_SEEDS_TAG = "MinSeedsNotMet"
class ShareLimits:
def __init__(self, qbit_manager):
@ -179,7 +183,9 @@ class ShareLimits:
group_config["limit_upload_speed"] = -1
check_limit_upload_speed = group_config["limit_upload_speed"] != torrent_upload_limit
hash_not_prev_checked = t_hash not in self.torrent_hash_checked
share_limits_not_yet_tagged = True if self.group_tag and self.group_tag not in torrent.tags else False
share_limits_not_yet_tagged = (
True if self.group_tag and not is_tag_in_torrent(self.group_tag, torrent.tags) else False
)
logger.trace(f"Torrent: {t_name} [Hash: {t_hash}]")
logger.trace(f"Torrent Category: {torrent.category}")
logger.trace(f"Torrent Tags: {torrent.tags}")
@ -201,7 +207,9 @@ class ShareLimits:
if (
check_max_ratio or check_max_seeding_time or check_limit_upload_speed or share_limits_not_yet_tagged
) and hash_not_prev_checked:
if "MinSeedTimeNotReached" not in torrent.tags:
if not is_tag_in_torrent(MIN_SEEDING_TIME_TAG, torrent.tags) and not is_tag_in_torrent(
MIN_NUM_SEEDS_TAG, torrent.tags
):
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)
if self.group_tag:
@ -217,6 +225,7 @@ class ShareLimits:
max_ratio=group_config["max_ratio"],
max_seeding_time=group_config["max_seeding_time"],
min_seeding_time=group_config["min_seeding_time"],
min_num_seeds=group_config["min_num_seeds"],
resume_torrent=group_config["resume_torrent_after_change"],
tracker=tracker["url"],
)
@ -232,10 +241,9 @@ class ShareLimits:
"""Removes previous share limits tag, updates tag and share limits for a torrent, and resumes the torrent"""
# Remove previous share_limits tag
if not self.config.dry_run:
tags = util.get_list(torrent.tags)
for tag in tags:
if self.share_limits_tag in tag:
torrent.remove_tags(tag)
tag = is_tag_in_torrent(self.share_limits_tag, torrent.tags, exact=False)
if tag:
torrent.remove_tags(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(
@ -345,7 +353,7 @@ class ShareLimits:
body.append(msg)
# Update Torrents
if not self.config.dry_run:
if tags and tags not in torrent.tags:
if tags and not is_tag_in_torrent(tags, torrent.tags):
torrent.add_tags(tags)
torrent_upload_limit = -1 if round(torrent.up_limit / 1024) == 0 else round(torrent.up_limit / 1024)
if limit_upload_speed is not None and limit_upload_speed != torrent_upload_limit:
@ -357,24 +365,28 @@ class ShareLimits:
max_ratio = torrent.max_ratio
if max_seeding_time is None:
max_seeding_time = torrent.max_seeding_time
if "MinSeedTimeNotReached" in torrent.tags:
if is_tag_in_torrent(MIN_SEEDING_TIME_TAG, torrent.tags):
return []
if is_tag_in_torrent(MIN_NUM_SEEDS_TAG, 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):
def has_reached_seed_limit(
self, torrent, max_ratio, max_seeding_time, min_seeding_time, min_num_seeds, 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:
if is_tag_in_torrent(MIN_SEEDING_TIME_TAG, torrent.tags):
if not self.config.dry_run:
torrent.remove_tags(tags="MinSeedTimeNotReached")
torrent.remove_tags(tags=MIN_SEEDING_TIME_TAG)
return True
else:
if "MinSeedTimeNotReached" not in torrent.tags:
if not is_tag_in_torrent(MIN_SEEDING_TIME_TAG, 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(
@ -389,15 +401,47 @@ class ShareLimits:
self.config.loglevel,
)
print_log += logger.print_line(
logger.insert_space("Adding Tag: MinSeedTimeNotReached", 8), self.config.loglevel
logger.insert_space(f"Adding Tag: {MIN_SEEDING_TIME_TAG}", 8), self.config.loglevel
)
if not self.config.dry_run:
torrent.add_tags("MinSeedTimeNotReached")
torrent.add_tags(MIN_SEEDING_TIME_TAG)
torrent.set_share_limits(-1, -1)
if resume_torrent:
torrent.resume()
return False
def _is_less_than_min_num_seeds():
print_log = []
if min_num_seeds == 0 or torrent.num_complete >= min_num_seeds:
if is_tag_in_torrent(MIN_NUM_SEEDS_TAG, torrent.tags):
if not self.config.dry_run:
torrent.remove_tags(tags=MIN_NUM_SEEDS_TAG)
return False
else:
if not is_tag_in_torrent(MIN_NUM_SEEDS_TAG, 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 number of seeds not met: Total Seeds ({torrent.num_complete}) <"
f"min_num_seeds({min_num_seeds}). Removing Share Limits so qBittorrent can continue"
" seeding."
),
8,
),
self.config.loglevel,
)
print_log += logger.print_line(
logger.insert_space(f"Adding Tag: {MIN_NUM_SEEDS_TAG}", 8), self.config.loglevel
)
if not self.config.dry_run:
torrent.add_tags(MIN_NUM_SEEDS_TAG)
torrent.set_share_limits(-1, -1)
if resume_torrent:
torrent.resume()
return True
def _has_reached_seeding_time_limit():
nonlocal body
seeding_time_limit = None
@ -421,6 +465,9 @@ class ShareLimits:
return True
return False
if min_num_seeds is not None:
if _is_less_than_min_num_seeds():
return body
if max_ratio is not None:
if max_ratio >= 0:
if torrent.ratio >= max_ratio and _has_reached_min_seeding_time_limit():

View file

@ -57,7 +57,7 @@ class TagNoHardLinks:
Checks for any previous torrents that were tagged with the nohardlinks tag and have since had hardlinks added.
If any are found, the nohardlinks tag is removed
"""
if not (has_nohardlinks) and (self.nohardlinks_tag in torrent.tags):
if not (has_nohardlinks) and (util.is_tag_in_torrent(self.nohardlinks_tag, torrent.tags)):
self.stats_untagged += 1
body = []
body += logger.print_line(
@ -102,7 +102,7 @@ class TagNoHardLinks:
has_nohardlinks = check_hardlinks.nohardlink(
torrent["content_path"].replace(self.root_dir, self.remote_dir), self.config.notify
)
if any(tag in torrent.tags for tag in nohardlinks[category]["exclude_tags"]):
if any(util.is_tag_in_torrent(tag, torrent.tags) for tag in nohardlinks[category]["exclude_tags"]):
# Skip to the next torrent if we find any torrents that are in the exclude tag
continue
else:
@ -111,7 +111,7 @@ class TagNoHardLinks:
if has_nohardlinks:
tracker = self.qbt.get_tags(torrent.trackers)
# Will only tag new torrents that don't have nohardlinks_tag tag
if self.nohardlinks_tag not in torrent.tags:
if not util.is_tag_in_torrent(self.nohardlinks_tag, torrent.tags):
self.add_tag_no_hl(
torrent=torrent,
tracker=tracker,

View file

@ -36,7 +36,13 @@ class Qbt:
logger.debug(f"Host: {self.host}, Username: {self.username}, Password: {self.password}")
ex = ""
try:
self.client = Client(host=self.host, username=self.username, password=self.password, VERIFY_WEBUI_CERTIFICATE=False)
self.client = Client(
host=self.host,
username=self.username,
password=self.password,
VERIFY_WEBUI_CERTIFICATE=False,
REQUESTS_ARGS={"timeout": (45, 60)},
)
self.client.auth_log_in()
self.current_version = self.client.app.version
logger.debug(f"qBittorrent: {self.current_version}")

View file

@ -34,6 +34,18 @@ def get_list(data, lower=False, split=True, int_list=False):
return [d.strip() for d in str(data).split(",")]
def is_tag_in_torrent(check_tag, torrent_tags, exact=True):
"""Check if tag is in torrent_tags"""
tags = get_list(torrent_tags)
if exact:
return check_tag in tags
else:
for t in tags:
if check_tag in t:
return t
return False
class TorrentMessages:
"""Contains list of messages to check against a status of a torrent"""

View file

@ -370,6 +370,8 @@ with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "VERSION")) a
version = util.parse_version(line)
break
branch = util.guess_branch(version, env_version, git_branch)
if branch is None:
branch = "Unknown"
version = (version[0].replace("develop", branch), version[1].replace("develop", branch), version[2])

View file

@ -1,7 +1,7 @@
bencodepy==0.9.5
GitPython==3.1.31
qbittorrent-api==2023.6.49
qbittorrent-api==2023.6.50
requests==2.31.0
retrying==1.3.4
ruamel.yaml==0.17.31
ruamel.yaml==0.17.32
schedule==1.2.0