import os import re import sys from collections import defaultdict import logging from .samba_manager import SambaManager from .config import Settings from smb.smb_structs import OperationFailure # --- Dedicated logger for ComicsManager --- comics_logger = logging.getLogger('comics_manager') comics_logger.setLevel(logging.INFO) comics_logger.propagate = False if not comics_logger.handlers: comics_log_handler = logging.FileHandler("logs/comics_organization.log", mode='a') comics_log_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) comics_logger.addHandler(comics_log_handler) comics_logger.addHandler(logging.StreamHandler()) # --- Disable noisy pysmb logger --- # Temporarily set the pysmb logger levels to CRITICAL to silence its output. # This needs to be set *after* basicConfig if pysmb initializes its loggers earlier. try: logging.getLogger("smb").setLevel(logging.CRITICAL) logging.getLogger("smb.SMBConnection").setLevel(logging.CRITICAL) except Exception: # Handle cases where the logger might not be fully initialized yet pass # --- class ComicsManager: def __init__(self, settings: Settings): self.settings = settings self.db = None # ComicsManager doesn't use DB self.samba_manager = SambaManager( settings.samba_server_ip, "isolation", settings.samba_username, settings.samba_password, ) self.comics_root = "/comics/toberead" self.comics_dest_root = "/comics/manga" def _get_series_name(self, filename): base_name, _ = os.path.splitext(filename) # This regex is more aggressive at cleaning up chapter/volume numbers and other noise. # It looks for common patterns like 'ch.10', 'v02', '123', etc. pattern = re.compile(r'[-_\s]*(v(ol)?|c(h)?|chapter|issue|ep|episode)[-_\s]*\d+.*|[-_\s]+\d+$', re.IGNORECASE) match = pattern.search(base_name) if match: return base_name[:match.start()].strip('-_ ') return base_name.strip('-_ ') def organize_comics(self): comics_logger.info("Starting comics organization process...") try: items_in_toberead = self.samba_manager.list_path(self.comics_root) if "error" in items_in_toberead: raise Exception(f"Failed to list files in 'toberead': {items_in_toberead['error']}") series_chapters = defaultdict(list) for item in items_in_toberead: if not item["is_directory"]: series_name = self._get_series_name(item["name"]) series_chapters[series_name].append(item) comics_logger.info(f"Found {len(series_chapters)} series to process.") for series_name, chapters in series_chapters.items(): comics_logger.info(f"Processing series: {series_name}") # Ensure paths use forward slashes for Samba operations final_series_folder_path = f"{self.comics_dest_root}/{series_name}".replace("\", "/") try: self.samba_manager.create_directory(final_series_folder_path) comics_logger.info(f"Created directory: {final_series_folder_path}") except OperationFailure as e: # Specific check for FILE_OBJECT_NAME_COLLISION if directory already exists if "FILE_OBJECT_NAME_COLLISION" in str(e): comics_logger.info(f"Directory {final_series_folder_path} already exists. Skipping creation.") else: comics_logger.error(f"Could not create directory {final_series_folder_path}: {e}") for chapter_item in chapters: # Sanitize original path coming from list_path original_path = chapter_item["path"].replace("\", "/") original_filename = chapter_item["name"] path_to_move = "" filename_to_move = "" if original_filename.lower().endswith('.zip'): cbz_filename = os.path.splitext(original_filename)[0] + '.cbz' temp_cbz_path = f"{self.comics_root}/{cbz_filename}".replace("\", "/") try: self.samba_manager.rename_file(original_path, temp_cbz_path) path_to_move = temp_cbz_path filename_to_move = cbz_filename comics_logger.info(f"Renamed {original_filename} to {cbz_filename}") except OperationFailure as e: comics_logger.error(f"Failed to rename ZIP {original_filename} to CBZ: {e}") continue # Skip to next chapter if rename fails elif original_filename.lower().endswith('.cbz'): # If it's already a .cbz, it's ready to be moved path_to_move = original_path filename_to_move = original_filename else: comics_logger.warning(f"Skipping non-zip/cbz file: {original_filename}") continue # Final destination path for the CBZ file final_cbz_path = f"{final_series_folder_path}/{filename_to_move}".replace("\", "/") try: self.samba_manager.rename_file(path_to_move, final_cbz_path) comics_logger.info(f"Moved {filename_to_move} to {final_series_folder_path}") except OperationFailure as e: comics_logger.error(f"Failed to move {filename_to_move} to {final_series_folder_path}: {e}") except Exception as e: comics_logger.error(f"An unexpected error occurred during comics organization: {e}", exc_info=True) finally: comics_logger.info("Comics organization process finished.") self.samba_manager.close() return