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. # will not occur.
- name: Dependabot metadata - name: Dependabot metadata
id: dependabot-metadata id: dependabot-metadata
uses: dependabot/fetch-metadata@v1.5.1 uses: dependabot/fetch-metadata@v1.6.0
with: with:
github-token: "${{ secrets.GITHUB_TOKEN }}" github-token: "${{ secrets.GITHUB_TOKEN }}"
# Here the PR gets approved. # Here the PR gets approved.

View file

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

View file

@ -1,13 +1,12 @@
# Requirements Updated # Requirements Updated
- qbitorrent-api updated to 2023.6.50 - qbitorrent-api updated to 2023.7.52
- 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))
# Bug Fixes # Bug Fixes
Fixes [#333](https://github.com/StuffAnThings/qbit_manage/issues/333) - Fixes a few webhook bugs with Notifiarr
Fixes [#340](https://github.com/StuffAnThings/qbit_manage/issues/340) - Fixes [#355](https://github.com/StuffAnThings/qbit_manage/issues/355)
Fixes [#343](https://github.com/StuffAnThings/qbit_manage/issues/343) - Fixes [#356](https://github.com/StuffAnThings/qbit_manage/issues/356)
Fixes bug when checking tags in torrents - 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: if empty_after_x_days <= days:
num_del += 1 num_del += 1
body += logger.print_line( body += logger.print_line(
(
f"{'Did not delete' if self.dry_run else 'Deleted'} " f"{'Did not delete' if self.dry_run else 'Deleted'} "
f"{filename} from {folder} (Last modified {round(days)} days ago)." f"{filename} from {folder} (Last modified {round(days)} days ago).",
),
self.loglevel, self.loglevel,
) )
files += [str(filename)] files += [str(filename)]
@ -679,10 +677,8 @@ class Config:
for path in location_path_list: for path in location_path_list:
util.remove_empty_directories(path, "**/*") util.remove_empty_directories(path, "**/*")
body += logger.print_line( body += logger.print_line(
(
f"{'Did not delete' if self.dry_run else 'Deleted'} {num_del} files " f"{'Did not delete' if self.dry_run else 'Deleted'} {num_del} files "
f"({util.human_readable_size(size_bytes)}) from the {location}." f"({util.human_readable_size(size_bytes)}) from the {location}.",
),
self.loglevel, self.loglevel,
) )
attr = { attr = {

View file

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

View file

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

View file

@ -67,10 +67,8 @@ class RemoveOrphaned:
logger.print_line(f"{num_orphaned} Orphaned files found", self.config.loglevel) 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("\n".join(orphaned_files), self.config.loglevel)
body += logger.print_line( body += logger.print_line(
(
f"{'Not moving' if self.config.dry_run else 'Moving'} {num_orphaned} Orphaned files " 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"to {self.orphaned_dir.replace(self.remote_dir,self.root_dir)}",
),
self.config.loglevel, 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 or self.stats_deleted_contents >= 1:
if self.stats_deleted >= 1: if self.stats_deleted >= 1:
logger.print_line( logger.print_line(
(
f"{'Did not delete' if self.config.dry_run else 'Deleted'} {self.stats_deleted} " 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".torrent{'s' if self.stats_deleted > 1 else ''} but not content files.",
),
self.config.loglevel, self.config.loglevel,
) )
if self.stats_deleted_contents >= 1: if self.stats_deleted_contents >= 1:
logger.print_line( logger.print_line(
(
f"{'Did not delete' if self.config.dry_run else 'Deleted'} {self.stats_deleted_contents} " 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".torrent{'s' if self.stats_deleted_contents > 1 else ''} AND content files.",
),
self.config.loglevel, self.config.loglevel,
) )
else: else:
logger.print_line("No unregistered torrents found.", self.config.loglevel) logger.print_line("No unregistered torrents found.", self.config.loglevel)
if self.stats_untagged >= 1: if self.stats_untagged >= 1:
logger.print_line( logger.print_line(
(
f"{'Did not delete' if self.config.dry_run else 'Deleted'} {self.tag_error} tags for {self.stats_untagged} " 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".torrent{'s.' if self.stats_untagged > 1 else '.'}",
),
self.config.loglevel, self.config.loglevel,
) )
if self.stats_tagged >= 1: if self.stats_tagged >= 1:

View file

@ -55,6 +55,7 @@ class ShareLimits:
"torrent_max_ratio": group_config["max_ratio"], "torrent_max_ratio": group_config["max_ratio"],
"torrent_max_seeding_time": group_config["max_seeding_time"], "torrent_max_seeding_time": group_config["max_seeding_time"],
"torrent_min_seeding_time": group_config["min_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"], "torrent_limit_upload_speed": group_config["limit_upload_speed"],
} }
if len(self.torrents_updated) > 0: if len(self.torrents_updated) > 0:
@ -196,6 +197,7 @@ class ShareLimits:
"Config Max Seeding Time vs Torrent Max Seeding Time: " "Config Max Seeding Time vs Torrent Max Seeding Time: "
f"{group_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(f"check_max_seeding_time: {check_max_seeding_time}")
logger.trace( logger.trace(
"Config Limit Upload Speed vs Torrent Limit Upload Speed: " "Config Limit Upload Speed vs Torrent Limit Upload Speed: "
@ -218,8 +220,6 @@ class ShareLimits:
self.stats_tagged += 1 self.stats_tagged += 1
self.torrents_updated.append(t_name) 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( tor_reached_seed_limit = self.has_reached_seed_limit(
torrent=torrent, torrent=torrent,
max_ratio=group_config["max_ratio"], max_ratio=group_config["max_ratio"],
@ -229,6 +229,8 @@ class ShareLimits:
resume_torrent=group_config["resume_torrent_after_change"], resume_torrent=group_config["resume_torrent_after_change"],
tracker=tracker["url"], tracker=tracker["url"],
) )
# Cleanup torrents if the torrent meets the criteria for deletion and cleanup is enabled
if group_config["cleanup"]:
if tor_reached_seed_limit: if tor_reached_seed_limit:
if t_hash not in self.tdel_dict: if t_hash not in self.tdel_dict:
self.tdel_dict[t_hash] = {} 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"Tracker: {tracker}", 8), self.config.loglevel)
print_log += logger.print_line( print_log += logger.print_line(
logger.insert_space( logger.insert_space(
(
f"Min seed time not met: {timedelta(seconds=torrent.seeding_time)} <=" f"Min seed time not met: {timedelta(seconds=torrent.seeding_time)} <="
f" {timedelta(minutes=min_seeding_time)}. Removing Share Limits so qBittorrent can continue" f" {timedelta(minutes=min_seeding_time)}. Removing Share Limits so qBittorrent can continue"
" seeding." " seeding.",
),
8, 8,
), ),
self.config.loglevel, 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"Tracker: {tracker}", 8), self.config.loglevel)
print_log += logger.print_line( print_log += logger.print_line(
logger.insert_space( logger.insert_space(
(
f"Min number of seeds not met: Total Seeds ({torrent.num_complete}) < " 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" f"min_num_seeds({min_num_seeds}). Removing Share Limits so qBittorrent can continue"
" seeding." " seeding.",
),
8, 8,
), ),
self.config.loglevel, self.config.loglevel,
@ -456,10 +454,8 @@ class ShareLimits:
if seeding_time_limit: if seeding_time_limit:
if (torrent.seeding_time >= seeding_time_limit * 60) and _has_reached_min_seeding_time_limit(): if (torrent.seeding_time >= seeding_time_limit * 60) and _has_reached_min_seeding_time_limit():
body += logger.insert_space( body += logger.insert_space(
(
f"Seeding Time vs Max Seed Time: {timedelta(seconds=torrent.seeding_time)} >= " f"Seeding Time vs Max Seed Time: {timedelta(seconds=torrent.seeding_time)} >= "
f"{timedelta(minutes=seeding_time_limit)}" f"{timedelta(minutes=seeding_time_limit)}",
),
8, 8,
) )
return True return True

View file

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

View file

@ -2,7 +2,6 @@
import os import os
import sys import sys
from qbittorrentapi import APIConnectionError
from qbittorrentapi import Client from qbittorrentapi import Client
from qbittorrentapi import LoginFailed from qbittorrentapi import LoginFailed
from qbittorrentapi import NotFound404Error from qbittorrentapi import NotFound404Error
@ -74,15 +73,10 @@ class Qbt:
except LoginFailed as exc: except LoginFailed as exc:
ex = "Qbittorrent Error: Failed to login. Invalid username/password." ex = "Qbittorrent Error: Failed to login. Invalid username/password."
self.config.notify(ex, "Qbittorrent") self.config.notify(ex, "Qbittorrent")
raise Failed(ex) from exc raise Failed(exc) 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
except Exception as exc: except Exception as exc:
ex = "Qbittorrent Error: Unable to connect to the client." self.config.notify(exc, "Qbittorrent")
self.config.notify(ex, "Qbittorrent") raise Failed(exc) from exc
raise Failed(ex) from exc
logger.separator("Getting Torrent List", space=False, border=False) logger.separator("Getting Torrent List", space=False, border=False)
self.torrent_list = self.get_torrents({"sort": "added_on"}) 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) shutil.move(src, dest)
except PermissionError as perm: except PermissionError as perm:
logger.warning(f"{perm} : Copying files instead.") logger.warning(f"{perm} : Copying files instead.")
try:
shutil.copyfile(src, dest) 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 to_delete = True
except FileNotFoundError as file: except FileNotFoundError as file:
logger.warning(f"{file} : source: {src} -> destination: {dest}") logger.warning(f"{file} : source: {src} -> destination: {dest}")
@ -501,12 +512,12 @@ class CheckHardLinks:
continue continue
file_size = os.stat(files).st_size file_size = os.stat(files).st_size
file_no_hardlinks = os.stat(files).st_nlink file_no_hardlinks = os.stat(files).st_nlink
logger.trace(f"Checking file: {file}") logger.trace(f"Checking file: {files}")
logger.trace(f"Checking file inum: {os.stat(file).st_ino}") logger.trace(f"Checking file inum: {os.stat(files).st_ino}")
logger.trace(f"Checking file size: {file_size}") logger.trace(f"Checking file size: {file_size}")
logger.trace(f"Checking no of hard links: {file_no_hardlinks}") 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)}") 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(file).st_ino, 1) > 0 and file_size >= ( if file_no_hardlinks - self.inode_count.get(os.stat(files).st_ino, 1) > 0 and file_size >= (
largest_file_size * threshold largest_file_size * threshold
): ):
check_for_hl = False check_for_hl = False

View file

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

View file

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