mirror of
https://github.com/StuffAnThings/qbit_manage.git
synced 2025-10-25 05:27:33 +08:00
commit
9e621e35d5
6 changed files with 68 additions and 32 deletions
|
|
@ -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.3.6
|
uses: dependabot/fetch-metadata@v1.4.0
|
||||||
with:
|
with:
|
||||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
# Here the PR gets approved.
|
# Here the PR gets approved.
|
||||||
|
|
|
||||||
10
CHANGELOG
10
CHANGELOG
|
|
@ -1,8 +1,8 @@
|
||||||
# Requirements Updated
|
# Requirements Updated
|
||||||
- Updates qbitorrent api to 2023.4.45
|
- Updates qbitorrent api to 2023.4.47
|
||||||
- Updates Schedule to 1.2.0
|
|
||||||
|
|
||||||
# Refactoring
|
# Bug Fixes
|
||||||
- Refactor qbit_manage to split up core functions into separate files
|
- Fixes bug in not removing empty directories (Thanks to @buthed010203 #266)
|
||||||
|
- Speed up remove_orphan by using multiprocessing (Thanks to @buthed010203 #266)
|
||||||
|
|
||||||
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v3.5.1...v3.6.0
|
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v3.6.0...v3.6.1
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
3.6.0
|
3.6.1
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
import os
|
import os
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
|
from itertools import repeat
|
||||||
|
from multiprocessing import cpu_count
|
||||||
|
from multiprocessing import Pool
|
||||||
|
|
||||||
from modules import util
|
from modules import util
|
||||||
|
|
||||||
logger = util.logger
|
logger = util.logger
|
||||||
|
_config = None
|
||||||
|
|
||||||
|
|
||||||
class RemoveOrphaned:
|
class RemoveOrphaned:
|
||||||
|
|
@ -17,7 +21,11 @@ class RemoveOrphaned:
|
||||||
self.root_dir = qbit_manager.config.root_dir
|
self.root_dir = qbit_manager.config.root_dir
|
||||||
self.orphaned_dir = qbit_manager.config.orphaned_dir
|
self.orphaned_dir = qbit_manager.config.orphaned_dir
|
||||||
|
|
||||||
|
global _config
|
||||||
|
_config = self.config
|
||||||
|
self.pool = Pool(processes=max(cpu_count() - 1, 1))
|
||||||
self.rem_orphaned()
|
self.rem_orphaned()
|
||||||
|
self.cleanup_pool()
|
||||||
|
|
||||||
def rem_orphaned(self):
|
def rem_orphaned(self):
|
||||||
"""Remove orphaned files from remote directory"""
|
"""Remove orphaned files from remote directory"""
|
||||||
|
|
@ -27,54 +35,64 @@ class RemoveOrphaned:
|
||||||
root_files = []
|
root_files = []
|
||||||
orphaned_files = []
|
orphaned_files = []
|
||||||
excluded_orphan_files = []
|
excluded_orphan_files = []
|
||||||
orphaned_parent_path = set()
|
|
||||||
|
|
||||||
if self.remote_dir != self.root_dir:
|
if self.remote_dir != self.root_dir:
|
||||||
|
local_orphaned_dir = self.orphaned_dir.replace(self.remote_dir, self.root_dir)
|
||||||
root_files = [
|
root_files = [
|
||||||
os.path.join(path.replace(self.remote_dir, self.root_dir), name)
|
os.path.join(path.replace(self.remote_dir, self.root_dir), name)
|
||||||
for path, subdirs, files in os.walk(self.remote_dir)
|
for path, subdirs, files in os.walk(self.remote_dir)
|
||||||
for name in files
|
for name in files
|
||||||
if self.orphaned_dir.replace(self.remote_dir, self.root_dir) not in path
|
if local_orphaned_dir not in path
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
root_files = [
|
root_files = [
|
||||||
os.path.join(path, name)
|
os.path.join(path, name)
|
||||||
for path, subdirs, files in os.walk(self.root_dir)
|
for path, subdirs, files in os.walk(self.root_dir)
|
||||||
for name in files
|
for name in files
|
||||||
if self.orphaned_dir.replace(self.root_dir, self.remote_dir) not in path
|
if self.orphaned_dir not in path
|
||||||
]
|
]
|
||||||
|
|
||||||
# Get an updated list of torrents
|
# Get an updated list of torrents
|
||||||
|
logger.print_line("Locating orphan files", self.config.loglevel)
|
||||||
torrent_list = self.qbt.get_torrents({"sort": "added_on"})
|
torrent_list = self.qbt.get_torrents({"sort": "added_on"})
|
||||||
|
torrent_files_and_save_path = []
|
||||||
for torrent in torrent_list:
|
for torrent in torrent_list:
|
||||||
for file in torrent.files:
|
torrent_files = []
|
||||||
fullpath = os.path.join(torrent.save_path, file.name)
|
for torrent_files_dict in torrent.files:
|
||||||
# Replace fullpath with \\ if qbm is running in docker (linux) but qbt is on windows
|
torrent_files.append(torrent_files_dict.name)
|
||||||
fullpath = fullpath.replace(r"/", "\\") if ":\\" in fullpath else fullpath
|
torrent_files_and_save_path.append((torrent_files, torrent.save_path))
|
||||||
torrent_files.append(fullpath)
|
torrent_files.extend(
|
||||||
|
[
|
||||||
|
fullpath
|
||||||
|
for fullpathlist in self.pool.starmap(get_full_path_of_torrent_files, torrent_files_and_save_path)
|
||||||
|
for fullpath in fullpathlist
|
||||||
|
if fullpath not in torrent_files
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
orphaned_files = set(root_files) - set(torrent_files)
|
orphaned_files = set(root_files) - set(torrent_files)
|
||||||
orphaned_files = sorted(orphaned_files)
|
|
||||||
|
|
||||||
if self.config.orphaned["exclude_patterns"]:
|
if self.config.orphaned["exclude_patterns"]:
|
||||||
exclude_patterns = self.config.orphaned["exclude_patterns"]
|
logger.print_line("Processing orphan exclude patterns")
|
||||||
|
exclude_patterns = [
|
||||||
|
exclude_pattern.replace(self.remote_dir, self.root_dir)
|
||||||
|
for exclude_pattern in self.config.orphaned["exclude_patterns"]
|
||||||
|
]
|
||||||
excluded_orphan_files = [
|
excluded_orphan_files = [
|
||||||
file
|
file for file in orphaned_files for exclude_pattern in exclude_patterns if fnmatch(file, exclude_pattern)
|
||||||
for file in orphaned_files
|
|
||||||
for exclude_pattern in exclude_patterns
|
|
||||||
if fnmatch(file, exclude_pattern.replace(self.remote_dir, self.root_dir))
|
|
||||||
]
|
]
|
||||||
|
|
||||||
orphaned_files = set(orphaned_files) - set(excluded_orphan_files)
|
orphaned_files = set(orphaned_files) - set(excluded_orphan_files)
|
||||||
|
|
||||||
if orphaned_files:
|
if orphaned_files:
|
||||||
|
orphaned_files = sorted(orphaned_files)
|
||||||
os.makedirs(self.orphaned_dir, exist_ok=True)
|
os.makedirs(self.orphaned_dir, exist_ok=True)
|
||||||
body = []
|
body = []
|
||||||
num_orphaned = len(orphaned_files)
|
num_orphaned = len(orphaned_files)
|
||||||
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"{'Did not move' if self.config.dry_run else 'Moved'} {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,
|
||||||
)
|
)
|
||||||
|
|
@ -89,14 +107,31 @@ class RemoveOrphaned:
|
||||||
}
|
}
|
||||||
self.config.send_notifications(attr)
|
self.config.send_notifications(attr)
|
||||||
# Delete empty directories after moving orphan files
|
# Delete empty directories after moving orphan files
|
||||||
logger.info("Cleaning up any empty directories...")
|
|
||||||
if not self.config.dry_run:
|
if not self.config.dry_run:
|
||||||
for file in orphaned_files:
|
orphaned_parent_path = set(self.pool.map(move_orphan, orphaned_files))
|
||||||
src = file.replace(self.root_dir, self.remote_dir)
|
logger.print_line("Removing newly empty directories", self.config.loglevel)
|
||||||
dest = os.path.join(self.orphaned_dir, file.replace(self.root_dir, ""))
|
self.pool.starmap(util.remove_empty_directories, zip(orphaned_parent_path, repeat("**/*")))
|
||||||
util.move_files(src, dest, True)
|
|
||||||
orphaned_parent_path.add(os.path.dirname(file).replace(self.root_dir, self.remote_dir))
|
|
||||||
for parent_path in orphaned_parent_path:
|
|
||||||
util.remove_empty_directories(parent_path, "**/*")
|
|
||||||
else:
|
else:
|
||||||
logger.print_line("No Orphaned Files found.", self.config.loglevel)
|
logger.print_line("No Orphaned Files found.", self.config.loglevel)
|
||||||
|
|
||||||
|
def cleanup_pool(self):
|
||||||
|
self.pool.close()
|
||||||
|
self.pool.join()
|
||||||
|
|
||||||
|
|
||||||
|
def get_full_path_of_torrent_files(torrent_files, save_path):
|
||||||
|
fullpath_torrent_files = []
|
||||||
|
for file in torrent_files:
|
||||||
|
fullpath = os.path.join(save_path, file)
|
||||||
|
# Replace fullpath with \\ if qbm is running in docker (linux) but qbt is on windows
|
||||||
|
fullpath = fullpath.replace(r"/", "\\") if ":\\" in fullpath else fullpath
|
||||||
|
fullpath_torrent_files.append(fullpath)
|
||||||
|
return fullpath_torrent_files
|
||||||
|
|
||||||
|
|
||||||
|
def move_orphan(file):
|
||||||
|
src = file.replace(_config.root_dir, _config.remote_dir) # Could be optimized to only run when root != remote
|
||||||
|
dest = os.path.join(_config.orphaned_dir, file.replace(_config.root_dir, ""))
|
||||||
|
util.move_files(src, dest, True)
|
||||||
|
return os.path.dirname(file).replace(_config.root_dir, _config.remote_dir) # Another candidate for micro optimizing
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,7 @@ def move_files(src, dest, mod=False):
|
||||||
dest_path = os.path.dirname(dest)
|
dest_path = os.path.dirname(dest)
|
||||||
to_delete = False
|
to_delete = False
|
||||||
if os.path.isdir(dest_path) is False:
|
if os.path.isdir(dest_path) is False:
|
||||||
os.makedirs(dest_path)
|
os.makedirs(dest_path, exist_ok=True)
|
||||||
try:
|
try:
|
||||||
if mod is True:
|
if mod is True:
|
||||||
mod_time = time.time()
|
mod_time = time.time()
|
||||||
|
|
@ -305,6 +305,7 @@ def remove_empty_directories(pathlib_root_dir, pattern):
|
||||||
key=lambda p: len(str(p)),
|
key=lambda p: len(str(p)),
|
||||||
reverse=True,
|
reverse=True,
|
||||||
)
|
)
|
||||||
|
longest.append(pathlib_root_dir)
|
||||||
for pdir in longest:
|
for pdir in longest:
|
||||||
try:
|
try:
|
||||||
pdir.rmdir() # remove directory if empty
|
pdir.rmdir() # remove directory if empty
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
flake8==6.0.0
|
flake8==6.0.0
|
||||||
pre-commit==3.2.2
|
pre-commit==3.2.2
|
||||||
qbittorrent-api==2023.4.45
|
qbittorrent-api==2023.4.47
|
||||||
requests==2.28.2
|
requests==2.28.2
|
||||||
retrying==1.3.4
|
retrying==1.3.4
|
||||||
ruamel.yaml==0.17.21
|
ruamel.yaml==0.17.21
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue