Merge pull request #372 from StuffAnThings/develop

4.0.3
This commit is contained in:
bobokun 2023-08-14 20:02:38 -04:00 committed by GitHub
commit d01afe9603
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 79 additions and 99 deletions

View file

@ -19,7 +19,7 @@ jobs:
# will not occur.
- name: Dependabot metadata
id: dependabot-metadata
uses: dependabot/fetch-metadata@v1.5.1
uses: dependabot/fetch-metadata@v1.6.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
# Here the PR gets approved.

View file

@ -27,7 +27,7 @@ repos:
args: [--format, parsable, --strict]
exclude: ^.github/
- repo: https://github.com/lyz-code/yamlfix
rev: 1.11.0
rev: 1.13.0
hooks:
- id: yamlfix
exclude: ^.github/
@ -36,18 +36,18 @@ repos:
hooks:
- id: reorder-python-imports
- repo: https://github.com/asottile/pyupgrade
rev: v3.7.0
rev: v3.10.1
hooks:
- id: pyupgrade
args: [--py3-plus]
- repo: https://github.com/psf/black
rev: 23.3.0
rev: 23.7.0
hooks:
- id: black
language_version: python3
args: [--line-length, '130', --preview]
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
rev: 6.1.0
hooks:
- id: flake8
args: [--config=.flake8]

View file

@ -1,13 +1,12 @@
# Requirements Updated
- qbitorrent-api updated to 2023.6.50
- ruamel.yaml updated to 0.17.32
# New Features
- Adds min_num_seeds condition to share_limits (Closes [#321](https://github.com/StuffAnThings/qbit_manage/issues/321))
- qbitorrent-api updated to 2023.7.52
# Bug Fixes
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
- Fixes a few webhook bugs with Notifiarr
- Fixes [#355](https://github.com/StuffAnThings/qbit_manage/issues/355)
- Fixes [#356](https://github.com/StuffAnThings/qbit_manage/issues/356)
- Fixes [#363](https://github.com/StuffAnThings/qbit_manage/issues/363)
- Fixes [#370](https://github.com/StuffAnThings/qbit_manage/issues/370)
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.0.1...v4.0.2
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.0.2...v4.0.3

View file

@ -1 +1 @@
4.0.2
4.0.3

View file

@ -663,10 +663,8 @@ class Config:
if empty_after_x_days <= days:
num_del += 1
body += logger.print_line(
(
f"{'Did not delete' if self.dry_run else 'Deleted'} "
f"{filename} from {folder} (Last modified {round(days)} days ago)."
),
f"{'Did not delete' if self.dry_run else 'Deleted'} "
f"{filename} from {folder} (Last modified {round(days)} days ago).",
self.loglevel,
)
files += [str(filename)]
@ -679,10 +677,8 @@ class Config:
for path in location_path_list:
util.remove_empty_directories(path, "**/*")
body += logger.print_line(
(
f"{'Did not delete' if self.dry_run else 'Deleted'} {num_del} files "
f"({util.human_readable_size(size_bytes)}) from the {location}."
),
f"{'Did not delete' if self.dry_run else 'Deleted'} {num_del} files "
f"({util.human_readable_size(size_bytes)}) from the {location}.",
self.loglevel,
)
attr = {

View file

@ -128,7 +128,7 @@ class CrossSeed:
"torrents": [t_name],
"torrent_category": t_cat,
"torrent_tag": "cross-seed",
"torrent_tracker": tracker,
"torrent_tracker": tracker["url"],
}
self.notify_attr.append(attr)
self.torrents_updated.append(t_name)

View file

@ -46,7 +46,7 @@ class ReCheck:
"title": "Resuming Torrent",
"body": body,
"torrents": [t_name],
"torrent_tag": tracker["tag"],
"torrent_tag": ", ".join(tracker["tag"]),
"torrent_category": t_category,
"torrent_tracker": tracker["url"],
"notifiarr_indexer": tracker["notifiarr"],
@ -64,10 +64,8 @@ class ReCheck:
)
logger.debug(
logger.insert_space(
(
f"-- Seeding Time vs Max Seed Time: {timedelta(seconds=torrent.seeding_time)} < "
f"{timedelta(minutes=torrent.max_seeding_time)}"
),
f"-- Seeding Time vs Max Seed Time: {timedelta(seconds=torrent.seeding_time)} < "
f"{timedelta(minutes=torrent.max_seeding_time)}",
4,
)
)
@ -95,7 +93,7 @@ class ReCheck:
"title": "Resuming Torrent",
"body": body,
"torrents": [t_name],
"torrent_tag": tracker["tag"],
"torrent_tag": ", ".join(tracker["tag"]),
"torrent_category": t_category,
"torrent_tracker": tracker["url"],
"notifiarr_indexer": tracker["notifiarr"],
@ -120,7 +118,7 @@ class ReCheck:
"title": "Rechecking Torrent",
"body": body,
"torrents": [t_name],
"torrent_tag": tracker["tag"],
"torrent_tag": ", ".join(tracker["tag"]),
"torrent_category": t_category,
"torrent_tracker": tracker["url"],
"notifiarr_indexer": tracker["notifiarr"],

View file

@ -67,10 +67,8 @@ class RemoveOrphaned:
logger.print_line(f"{num_orphaned} Orphaned files found", self.config.loglevel)
body += logger.print_line("\n".join(orphaned_files), self.config.loglevel)
body += logger.print_line(
(
f"{'Not moving' if self.config.dry_run else 'Moving'} {num_orphaned} Orphaned files "
f"to {self.orphaned_dir.replace(self.remote_dir,self.root_dir)}"
),
f"{'Not moving' if self.config.dry_run else 'Moving'} {num_orphaned} Orphaned files "
f"to {self.orphaned_dir.replace(self.remote_dir,self.root_dir)}",
self.config.loglevel,
)

View file

@ -142,28 +142,22 @@ class RemoveUnregistered:
if self.stats_deleted >= 1 or self.stats_deleted_contents >= 1:
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."
),
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."
),
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,
)
else:
logger.print_line("No unregistered torrents found.", self.config.loglevel)
if self.stats_untagged >= 1:
logger.print_line(
(
f"{'Did not delete' if self.config.dry_run else 'Deleted'} {self.tag_error} tags for {self.stats_untagged} "
f".torrent{'s.' if self.stats_untagged > 1 else '.'}"
),
f"{'Did not delete' if self.config.dry_run else 'Deleted'} {self.tag_error} tags for {self.stats_untagged} "
f".torrent{'s.' if self.stats_untagged > 1 else '.'}",
self.config.loglevel,
)
if self.stats_tagged >= 1:

View file

@ -55,6 +55,7 @@ class ShareLimits:
"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_min_num_seeds": group_config["min_num_seeds"],
"torrent_limit_upload_speed": group_config["limit_upload_speed"],
}
if len(self.torrents_updated) > 0:
@ -196,6 +197,7 @@ class ShareLimits:
"Config Max Seeding Time vs Torrent Max Seeding Time: "
f"{group_config['max_seeding_time']} vs {torrent.max_seeding_time}"
)
logger.trace(f"Config Min Num Seeds vs Torrent Num Seeds: {group_config['min_num_seeds']} vs {torrent.num_complete}")
logger.trace(f"check_max_seeding_time: {check_max_seeding_time}")
logger.trace(
"Config Limit Upload Speed vs Torrent Limit Upload Speed: "
@ -218,17 +220,17 @@ class ShareLimits:
self.stats_tagged += 1
self.torrents_updated.append(t_name)
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"],
min_num_seeds=group_config["min_num_seeds"],
resume_torrent=group_config["resume_torrent_after_change"],
tracker=tracker["url"],
)
# 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"],
min_num_seeds=group_config["min_num_seeds"],
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] = {}
@ -391,11 +393,9 @@ class ShareLimits:
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."
),
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,
@ -423,11 +423,9 @@ class ShareLimits:
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."
),
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,
@ -456,10 +454,8 @@ class ShareLimits:
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)}"
),
f"Seeding Time vs Max Seed Time: {timedelta(seconds=torrent.seeding_time)} >= "
f"{timedelta(minutes=seeding_time_limit)}",
8,
)
return True

View file

@ -120,20 +120,16 @@ class TagNoHardLinks:
self.check_previous_nohardlinks_tagged_torrents(has_nohardlinks, torrent, tracker, category)
if self.stats_tagged >= 1:
logger.print_line(
(
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 '.'}"
),
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,
)
else:
logger.print_line("No torrents to tag with no hardlinks.", self.config.loglevel)
if self.stats_untagged >= 1:
logger.print_line(
(
f"{'Did not delete' if self.config.dry_run else 'Deleted'} "
f"{self.nohardlinks_tag} tags for {self.stats_untagged} "
f".torrent{'s.' if self.stats_untagged > 1 else '.'}"
),
f"{'Did not delete' if self.config.dry_run else 'Deleted'} "
f"{self.nohardlinks_tag} tags for {self.stats_untagged} "
f".torrent{'s.' if self.stats_untagged > 1 else '.'}",
self.config.loglevel,
)

View file

@ -2,7 +2,6 @@
import os
import sys
from qbittorrentapi import APIConnectionError
from qbittorrentapi import Client
from qbittorrentapi import LoginFailed
from qbittorrentapi import NotFound404Error
@ -74,15 +73,10 @@ class Qbt:
except LoginFailed as exc:
ex = "Qbittorrent Error: Failed to login. Invalid username/password."
self.config.notify(ex, "Qbittorrent")
raise Failed(ex) from exc
except APIConnectionError as exc:
ex = "Qbittorrent Error: Unable to connect to the client."
self.config.notify(ex, "Qbittorrent")
raise Failed(ex) from exc
raise Failed(exc) from exc
except Exception as exc:
ex = "Qbittorrent Error: Unable to connect to the client."
self.config.notify(ex, "Qbittorrent")
raise Failed(ex) from exc
self.config.notify(exc, "Qbittorrent")
raise Failed(exc) from exc
logger.separator("Getting Torrent List", space=False, border=False)
self.torrent_list = self.get_torrents({"sort": "added_on"})

View file

@ -384,7 +384,18 @@ def move_files(src, dest, mod=False):
shutil.move(src, dest)
except PermissionError as perm:
logger.warning(f"{perm} : Copying files instead.")
shutil.copyfile(src, dest)
try:
shutil.copyfile(src, dest)
except Exception as ex:
logger.stacktrace()
logger.error(ex)
return to_delete
if os.path.isfile(src):
logger.warning(f"Removing original file: {src}")
try:
os.remove(src)
except OSError as e:
logger.warning(f"Error: {e.filename} - {e.strerror}.")
to_delete = True
except FileNotFoundError as file:
logger.warning(f"{file} : source: {src} -> destination: {dest}")
@ -501,12 +512,12 @@ class CheckHardLinks:
continue
file_size = os.stat(files).st_size
file_no_hardlinks = os.stat(files).st_nlink
logger.trace(f"Checking file: {file}")
logger.trace(f"Checking file inum: {os.stat(file).st_ino}")
logger.trace(f"Checking file: {files}")
logger.trace(f"Checking file inum: {os.stat(files).st_ino}")
logger.trace(f"Checking file size: {file_size}")
logger.trace(f"Checking no of hard links: {file_no_hardlinks}")
logger.trace(f"Checking inode_count dict: {self.inode_count.get(os.stat(file).st_ino)}")
if file_no_hardlinks - self.inode_count.get(os.stat(file).st_ino, 1) > 0 and file_size >= (
logger.trace(f"Checking inode_count dict: {self.inode_count.get(os.stat(files).st_ino)}")
if file_no_hardlinks - self.inode_count.get(os.stat(files).st_ino, 1) > 0 and file_size >= (
largest_file_size * threshold
):
check_for_hl = False

View file

@ -171,10 +171,8 @@ class Webhooks:
def notify(self, torrents_updated=[], payload={}, group_by=""):
if len(torrents_updated) > GROUP_NOTIFICATION_LIMIT:
logger.trace(
(
f"Number of torrents updated > {GROUP_NOTIFICATION_LIMIT}, grouping notifications"
f"{f' by {group_by}' if group_by else ''}"
),
f"Number of torrents updated > {GROUP_NOTIFICATION_LIMIT}, grouping notifications"
f"{f' by {group_by}' if group_by else ''}",
)
if group_by == "category":
group_attr = group_notifications_by_key(payload, "torrent_category")

View file

@ -1,2 +1,2 @@
flake8==6.0.0
flake8==6.1.0
pre-commit==3.3.3

View file

@ -1,6 +1,6 @@
bencodepy==0.9.5
GitPython==3.1.32
qbittorrent-api==2023.6.50
qbittorrent-api==2023.7.52
requests==2.31.0
retrying==1.3.4
ruamel.yaml==0.17.32