Skip to content

Commit

Permalink
Merge pull request #344 from StuffAnThings/develop
Browse files Browse the repository at this point in the history
4.0.2
  • Loading branch information
bobokun authored Jun 28, 2023
2 parents ee871b4 + 993b218 commit 16d5450
Show file tree
Hide file tree
Showing 14 changed files with 121 additions and 54 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
29 changes: 8 additions & 21 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.0.1
4.0.2
7 changes: 6 additions & 1 deletion config/config.yml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand Down Expand Up @@ -237,7 +242,7 @@ orphaned:
- "**/@eaDir"
- "/data/torrents/temp/**"
- "**/*.!qB"
- "**/_unpackerred"
- "**/*_unpackerred"

apprise:
# Apprise integration with webhooks
Expand Down
11 changes: 11 additions & 0 deletions modules/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,17 @@ def _sort_share_limits(share_limits):
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",
Expand Down
2 changes: 1 addition & 1 deletion modules/core/cross_seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def cross_seed(self):
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
):
Expand Down
2 changes: 1 addition & 1 deletion modules/core/remove_orphaned.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def rem_orphaned(self):
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)
Expand Down
9 changes: 3 additions & 6 deletions modules/core/remove_unregistered.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,6 @@ def process_torrent_issues(self):
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(
Expand All @@ -125,6 +119,9 @@ def process_torrent_issues(self):
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
Expand Down
75 changes: 61 additions & 14 deletions modules/core/share_limits.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
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):
Expand Down Expand Up @@ -179,7 +183,9 @@ def update_share_limits_for_group(self, group_name, group_config, torrents):
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}")
Expand All @@ -201,7 +207,9 @@ def update_share_limits_for_group(self, group_name, group_config, torrents):
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:
Expand All @@ -217,6 +225,7 @@ def update_share_limits_for_group(self, group_name, group_config, torrents):
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"],
)
Expand All @@ -232,10 +241,9 @@ 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
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(
Expand Down Expand Up @@ -345,7 +353,7 @@ def set_tags_and_limits(self, torrent, max_ratio, max_seeding_time, limit_upload
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:
Expand All @@ -357,24 +365,28 @@ def set_tags_and_limits(self, torrent, max_ratio, max_seeding_time, limit_upload
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(
Expand All @@ -389,15 +401,47 @@ def _has_reached_min_seeding_time_limit():
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
Expand All @@ -421,6 +465,9 @@ def _has_reached_seeding_time_limit():
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():
Expand Down
6 changes: 3 additions & 3 deletions modules/core/tag_nohardlinks.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def check_previous_nohardlinks_tagged_torrents(self, has_nohardlinks, torrent, t
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(
Expand Down Expand Up @@ -102,7 +102,7 @@ def tag_nohardlinks(self):
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:
Expand All @@ -111,7 +111,7 @@ def tag_nohardlinks(self):
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,
Expand Down
8 changes: 7 additions & 1 deletion modules/qbittorrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ def __init__(self, config, params):
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}")
Expand Down
12 changes: 12 additions & 0 deletions modules/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""

Expand Down
2 changes: 2 additions & 0 deletions qbit_manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ def my_except_hook(exctype, value, tbi):
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])


Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 16d5450

Please sign in to comment.