mirror of
https://github.com/StuffAnThings/qbit_manage.git
synced 2025-10-30 07:58:16 +08:00
commit
00ce5c6c30
9 changed files with 91 additions and 23 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.6.0
|
rev: v5.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
|
|
@ -38,12 +38,12 @@ repos:
|
||||||
name: isort (python)
|
name: isort (python)
|
||||||
args: [--force-single-line-imports, --profile, black]
|
args: [--force-single-line-imports, --profile, black]
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v3.17.0
|
rev: v3.18.0
|
||||||
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: 24.8.0
|
rev: 24.10.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
language_version: python3
|
language_version: python3
|
||||||
|
|
@ -60,4 +60,4 @@ repos:
|
||||||
entry: ./scripts/pre-commit/increase_version.sh
|
entry: ./scripts/pre-commit/increase_version.sh
|
||||||
language: script
|
language: script
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
stages: [commit]
|
stages: [pre-commit]
|
||||||
|
|
|
||||||
14
CHANGELOG
14
CHANGELOG
|
|
@ -1,11 +1,11 @@
|
||||||
|
# Requirements Updated
|
||||||
|
humanize==4.11.0
|
||||||
|
|
||||||
# New Updates
|
# New Updates
|
||||||
- Adds new config option `disable_qbt_default_share_limits` to allow qbit_manage to handle share limits and disable qbittorrent's default share limits
|
- Adds new script to remove cross-seed tag (`scripts/remove_cross-seed_tag.py`)
|
||||||
- Adds new config option `max_orphaned_files_to_delete` to set default safeguards against mass deletion when running remove orphaned.
|
|
||||||
- Adds new environment variables `QBT_LOG_SIZE` and `QBT_LOG_COUNT` to customize log retention (Closes #656)
|
|
||||||
|
|
||||||
# Bug Fixes
|
# Bug Fixes
|
||||||
- Truncates Recyclebin JSON filename when its too long. (Closes #604)
|
- List orphaned files when reaches max threshold. (Closes #672)
|
||||||
- Uses Qbittorrent's torrent export to save .torrent files for qbittorrent version > 4.5.0 (Closes #650)
|
- Removing empty directories now ignores exclude patterns (Closes #624)
|
||||||
- Include orphaned files and recycle bin in the list of folders to ignore when looking for noHL (Closes #660)
|
|
||||||
|
|
||||||
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.1.10...v4.1.11
|
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.1.11...v4.1.12
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"master": {
|
"master": {
|
||||||
"qbit": "v4.6.6",
|
"qbit": "v5.0.0",
|
||||||
"qbitapi": "2024.8.65"
|
"qbitapi": "2024.9.67"
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
"qbit": "v5.0.0",
|
"qbit": "v5.0.0",
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
4.1.11
|
4.1.12
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ class RemoveOrphaned:
|
||||||
logger.separator("Checking for Orphaned Files", space=False, border=False)
|
logger.separator("Checking for Orphaned Files", space=False, border=False)
|
||||||
torrent_files = []
|
torrent_files = []
|
||||||
orphaned_files = []
|
orphaned_files = []
|
||||||
excluded_orphan_files = []
|
excluded_orphan_files = set()
|
||||||
|
exclude_patterns = []
|
||||||
|
|
||||||
root_files = self.executor.submit(util.get_root_files, self.root_dir, self.remote_dir, self.orphaned_dir)
|
root_files = self.executor.submit(util.get_root_files, self.root_dir, self.remote_dir, self.orphaned_dir)
|
||||||
|
|
||||||
|
|
@ -54,11 +55,13 @@ class RemoveOrphaned:
|
||||||
exclude_pattern.replace(self.remote_dir, self.root_dir)
|
exclude_pattern.replace(self.remote_dir, self.root_dir)
|
||||||
for exclude_pattern in self.config.orphaned["exclude_patterns"]
|
for exclude_pattern in self.config.orphaned["exclude_patterns"]
|
||||||
]
|
]
|
||||||
excluded_orphan_files = [
|
|
||||||
file for file in orphaned_files for exclude_pattern in exclude_patterns if fnmatch(file, exclude_pattern)
|
|
||||||
]
|
|
||||||
|
|
||||||
orphaned_files = set(orphaned_files) - set(excluded_orphan_files)
|
for file in orphaned_files:
|
||||||
|
for exclude_pattern in exclude_patterns:
|
||||||
|
if fnmatch(file, exclude_pattern):
|
||||||
|
excluded_orphan_files.add(file)
|
||||||
|
|
||||||
|
orphaned_files = orphaned_files - excluded_orphan_files
|
||||||
|
|
||||||
# Check the threshold before deleting orphaned files
|
# Check the threshold before deleting orphaned files
|
||||||
max_orphaned_files_to_delete = self.config.orphaned.get("max_orphaned_files_to_delete")
|
max_orphaned_files_to_delete = self.config.orphaned.get("max_orphaned_files_to_delete")
|
||||||
|
|
@ -69,6 +72,7 @@ class RemoveOrphaned:
|
||||||
"Aborting deletion to avoid accidental data loss."
|
"Aborting deletion to avoid accidental data loss."
|
||||||
)
|
)
|
||||||
self.config.notify(e, "Remove Orphaned", False)
|
self.config.notify(e, "Remove Orphaned", False)
|
||||||
|
logger.debug(f"Orphaned files detected: {orphaned_files}")
|
||||||
logger.warning(e)
|
logger.warning(e)
|
||||||
return
|
return
|
||||||
elif orphaned_files:
|
elif orphaned_files:
|
||||||
|
|
@ -104,7 +108,9 @@ class RemoveOrphaned:
|
||||||
orphaned_parent_path = set(self.executor.map(self.handle_orphaned_files, orphaned_files))
|
orphaned_parent_path = set(self.executor.map(self.handle_orphaned_files, orphaned_files))
|
||||||
logger.print_line("Removing newly empty directories", self.config.loglevel)
|
logger.print_line("Removing newly empty directories", self.config.loglevel)
|
||||||
self.executor.map(
|
self.executor.map(
|
||||||
lambda directory: util.remove_empty_directories(directory, self.qbt.get_category_save_paths()),
|
lambda directory: util.remove_empty_directories(
|
||||||
|
directory, self.qbt.get_category_save_paths(), exclude_patterns
|
||||||
|
),
|
||||||
orphaned_parent_path,
|
orphaned_parent_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
|
from fnmatch import fnmatch
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
@ -486,7 +487,7 @@ def copy_files(src, dest):
|
||||||
logger.error(ex)
|
logger.error(ex)
|
||||||
|
|
||||||
|
|
||||||
def remove_empty_directories(pathlib_root_dir, excluded_paths=None):
|
def remove_empty_directories(pathlib_root_dir, excluded_paths=None, exclude_patterns=[]):
|
||||||
"""Remove empty directories recursively, optimized version."""
|
"""Remove empty directories recursively, optimized version."""
|
||||||
pathlib_root_dir = Path(pathlib_root_dir)
|
pathlib_root_dir = Path(pathlib_root_dir)
|
||||||
if excluded_paths is not None:
|
if excluded_paths is not None:
|
||||||
|
|
@ -499,6 +500,14 @@ def remove_empty_directories(pathlib_root_dir, excluded_paths=None):
|
||||||
if excluded_paths and root_path in excluded_paths:
|
if excluded_paths and root_path in excluded_paths:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
exclude_pattern_match = False
|
||||||
|
for exclude_pattern in exclude_patterns:
|
||||||
|
if fnmatch(os.path.join(root, ""), exclude_pattern):
|
||||||
|
exclude_pattern_match = True
|
||||||
|
break
|
||||||
|
if exclude_pattern_match:
|
||||||
|
continue
|
||||||
|
|
||||||
# Attempt to remove the directory if it's empty
|
# Attempt to remove the directory if it's empty
|
||||||
try:
|
try:
|
||||||
os.rmdir(root)
|
os.rmdir(root)
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
flake8==7.1.1
|
flake8==7.1.1
|
||||||
pre-commit==3.8.0
|
pre-commit==4.0.1
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
bencodepy==0.9.5
|
bencodepy==0.9.5
|
||||||
croniter==3.0.3
|
croniter==3.0.3
|
||||||
GitPython==3.1.43
|
GitPython==3.1.43
|
||||||
humanize==4.10.0
|
humanize==4.11.0
|
||||||
pytimeparse2==1.7.1
|
pytimeparse2==1.7.1
|
||||||
qbittorrent-api==2024.9.67
|
qbittorrent-api==2024.9.67
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
|
|
|
||||||
53
scripts/remove_cross-seed_tag.py
Normal file
53
scripts/remove_cross-seed_tag.py
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
|
||||||
|
# USES ENVIRONMENTAL VARIABLES, IF NONE ARE PRESENT WILL FALLBACK TO THE SECOND STRING
|
||||||
|
QBIT_HOST = os.getenv("QBT_HOST", "http://localhost:8080")
|
||||||
|
QBIT_USERNAME = os.getenv("QBT_USERNAME", "admin")
|
||||||
|
QBIT_PASSWORD = os.getenv("QBT_PASSWORD", "YOURPASSWORD")
|
||||||
|
|
||||||
|
CRED = "\033[91m"
|
||||||
|
CGREEN = "\33[32m"
|
||||||
|
CEND = "\033[0m"
|
||||||
|
|
||||||
|
CROSS_SEED_TAG = "cross-seed"
|
||||||
|
|
||||||
|
|
||||||
|
def split(separator, data):
|
||||||
|
if data is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return [item.strip() for item in str(data).split(separator)]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from qbittorrentapi import APIConnectionError
|
||||||
|
from qbittorrentapi import Client
|
||||||
|
from qbittorrentapi import LoginFailed
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
print('Error: qbittorrent-api not installed. Please install using the command "pip install qbittorrent-api"')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
qbt_client = Client(host=QBIT_HOST, username=QBIT_USERNAME, password=QBIT_PASSWORD)
|
||||||
|
except LoginFailed:
|
||||||
|
raise "Qbittorrent Error: Failed to login. Invalid username/password."
|
||||||
|
except APIConnectionError:
|
||||||
|
raise "Qbittorrent Error: Unable to connect to the client."
|
||||||
|
except Exception:
|
||||||
|
raise "Qbittorrent Error: Unable to connect to the client."
|
||||||
|
print("qBittorrent:", qbt_client.app_version())
|
||||||
|
print("qBittorrent Web API:", qbt_client.app_web_api_version())
|
||||||
|
print()
|
||||||
|
|
||||||
|
torrents_list = qbt_client.torrents.info(sort="added_on", reverse=True)
|
||||||
|
|
||||||
|
print("Total torrents:", len(torrents_list))
|
||||||
|
print()
|
||||||
|
|
||||||
|
for torrent in torrents_list:
|
||||||
|
torrent_tags = split(",", torrent.tags)
|
||||||
|
|
||||||
|
if CROSS_SEED_TAG in torrent_tags:
|
||||||
|
print(CGREEN, "remove cross-seed tag:", torrent.name, CEND)
|
||||||
|
torrent.remove_tags(tags=CROSS_SEED_TAG)
|
||||||
Loading…
Add table
Reference in a new issue