mirror of
				https://github.com/StuffAnThings/qbit_manage.git
				synced 2025-10-31 00:17:27 +08:00 
			
		
		
		
	
						commit
						00ce5c6c30
					
				
					 9 changed files with 91 additions and 23 deletions
				
			
		|  | @ -1,7 +1,7 @@ | |||
| --- | ||||
| repos: | ||||
|   - repo: https://github.com/pre-commit/pre-commit-hooks | ||||
|     rev: v4.6.0 | ||||
|     rev: v5.0.0 | ||||
|     hooks: | ||||
|       - id: trailing-whitespace | ||||
|       - id: end-of-file-fixer | ||||
|  | @ -38,12 +38,12 @@ repos: | |||
|         name: isort (python) | ||||
|         args: [--force-single-line-imports, --profile, black] | ||||
|   - repo: https://github.com/asottile/pyupgrade | ||||
|     rev: v3.17.0 | ||||
|     rev: v3.18.0 | ||||
|     hooks: | ||||
|       - id: pyupgrade | ||||
|         args: [--py3-plus] | ||||
|   - repo: https://github.com/psf/black | ||||
|     rev: 24.8.0 | ||||
|     rev: 24.10.0 | ||||
|     hooks: | ||||
|       - id: black | ||||
|         language_version: python3 | ||||
|  | @ -60,4 +60,4 @@ repos: | |||
|         entry: ./scripts/pre-commit/increase_version.sh | ||||
|         language: script | ||||
|         pass_filenames: false | ||||
|         stages: [commit] | ||||
|         stages: [pre-commit] | ||||
|  |  | |||
							
								
								
									
										14
									
								
								CHANGELOG
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								CHANGELOG
									
										
									
									
									
								
							|  | @ -1,11 +1,11 @@ | |||
| # Requirements Updated | ||||
| humanize==4.11.0 | ||||
| 
 | ||||
| # 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 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) | ||||
| - Adds new script to remove cross-seed tag (`scripts/remove_cross-seed_tag.py`) | ||||
| 
 | ||||
| # Bug Fixes | ||||
| - Truncates Recyclebin JSON filename when its too long. (Closes #604) | ||||
| - Uses Qbittorrent's torrent export to save .torrent files for qbittorrent version > 4.5.0 (Closes #650) | ||||
| - Include orphaned files and recycle bin in the list of folders to ignore when looking for noHL (Closes #660) | ||||
| - List orphaned files when reaches max threshold. (Closes #672) | ||||
| - Removing empty directories now ignores exclude patterns (Closes #624) | ||||
| 
 | ||||
| **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": { | ||||
|         "qbit": "v4.6.6", | ||||
|         "qbitapi": "2024.8.65" | ||||
|         "qbit": "v5.0.0", | ||||
|         "qbitapi": "2024.9.67" | ||||
|     }, | ||||
|     "develop": { | ||||
|         "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) | ||||
|         torrent_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) | ||||
| 
 | ||||
|  | @ -54,11 +55,13 @@ class RemoveOrphaned: | |||
|                 exclude_pattern.replace(self.remote_dir, self.root_dir) | ||||
|                 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 | ||||
|         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." | ||||
|             ) | ||||
|             self.config.notify(e, "Remove Orphaned", False) | ||||
|             logger.debug(f"Orphaned files detected: {orphaned_files}") | ||||
|             logger.warning(e) | ||||
|             return | ||||
|         elif orphaned_files: | ||||
|  | @ -104,7 +108,9 @@ class RemoveOrphaned: | |||
|                 orphaned_parent_path = set(self.executor.map(self.handle_orphaned_files, orphaned_files)) | ||||
|                 logger.print_line("Removing newly empty directories", self.config.loglevel) | ||||
|                 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, | ||||
|                 ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ import os | |||
| import shutil | ||||
| import signal | ||||
| import time | ||||
| from fnmatch import fnmatch | ||||
| from pathlib import Path | ||||
| 
 | ||||
| import requests | ||||
|  | @ -486,7 +487,7 @@ def copy_files(src, dest): | |||
|         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.""" | ||||
|     pathlib_root_dir = Path(pathlib_root_dir) | ||||
|     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: | ||||
|             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 | ||||
|         try: | ||||
|             os.rmdir(root) | ||||
|  |  | |||
|  | @ -1,2 +1,2 @@ | |||
| flake8==7.1.1 | ||||
| pre-commit==3.8.0 | ||||
| pre-commit==4.0.1 | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| bencodepy==0.9.5 | ||||
| croniter==3.0.3 | ||||
| GitPython==3.1.43 | ||||
| humanize==4.10.0 | ||||
| humanize==4.11.0 | ||||
| pytimeparse2==1.7.1 | ||||
| qbittorrent-api==2024.9.67 | ||||
| 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