import threading import datetime import re import os from urllib.parse import urlparse, parse_qs from .downloader_core.bunkr import BunkrDownloader from .downloader_core.downloader import Downloader from .downloader_core.erome import EromeDownloader from .downloader_core.simpcity import SimpCity from .downloader_core.jpg5 import Jpg5Downloader from .samba_manager import SambaManager from .storage import SambaStorage from .config import settings # Dummy translator def tr(key, **kwargs): return key.format(**kwargs) def extract_ck_parameters(url): match = re.search(r"/(?P[^/?]+)(/user/(?P[^/?]+)(/post/(?P[^/?]+))?)?", url.path) if match: return match.group("service"), match.group("user"), match.group("post") return None, None, None def extract_ck_query(url): query = parse_qs(url.query) q = query.get("q")[0] if query.get("q") and len(query.get("q")) > 0 else "0" o = query.get("o")[0] if query.get("o") and len(query.get("o")) > 0 else "0" return q, int(o) if str.isdigit(o) else 0 class DownloaderManager: def __init__(self): self.active_downloader = None self.download_thread = None self.logs = [] self.progress = { "status": "Idle", "active": False, "downloaded": 0, "total": 0, "speed": 0, "eta": 0, "current_file": "" } self.download_folder = os.path.join(os.getcwd(), "downloads") os.makedirs(self.download_folder, exist_ok=True) self.samba_manager = None def log_callback(self, message): timestamp = datetime.datetime.now().strftime("%H:%M:%S") self.logs.append(f"[{timestamp}] {message}") if len(self.logs) > 1000: self.logs.pop(0) def update_progress_callback(self, downloaded, total, **kwargs): self.progress["downloaded"] = downloaded self.progress["total"] = total if "speed" in kwargs: self.progress["speed"] = kwargs["speed"] if "eta" in kwargs: self.progress["eta"] = kwargs["eta"] if "file_path" in kwargs: self.progress["current_file"] = os.path.basename(kwargs["file_path"]) def enable_widgets_callback(self): # Called when a single downloader finishes # We don't set active=False here because we might have more URLs pass def start_download(self, urls, options): if self.progress["active"]: raise Exception("Download already in progress") self.logs = [] self.progress["active"] = True self.progress["status"] = "Starting..." self.progress["downloaded"] = 0 self.progress["total"] = 0 self.download_thread = threading.Thread(target=self._run_download, args=(urls, options)) self.download_thread.start() def _run_download(self, urls, options): try: for i, url in enumerate(urls): # Check for cancellation between URLs if self.progress["status"] == "Cancelling...": self.log_callback("Queue processing cancelled.") break self.log_callback(f"Processing URL {i+1}/{len(urls)}: {url}") try: self._process_single_url(url, options) except Exception as e: self.log_callback(f"Error processing URL {url}: {e}") # Continue to next URL? Yes, usually desired behavior for bulk. except Exception as e: self.log_callback(f"Fatal error in download thread: {e}") self.progress["status"] = "Error" finally: self.progress["active"] = False self.progress["status"] = "Finished" if self.samba_manager: self.samba_manager.close() self.samba_manager = None self.active_downloader = None def _process_single_url(self, url, options): # Options download_images = options.get("download_images", True) download_videos = options.get("download_videos", True) max_workers = options.get("max_workers", 3) force_overwrite = options.get("force_overwrite", False) parsed_url = urlparse(url) target = None args = () # Cleanup previous samba manager if exists (though we try to clean up in finally) if self.samba_manager: self.samba_manager.close() self.samba_manager = None # Dispatch Logic if "erome.com" in url: self.log_callback("Starting Erome Download") is_profile = "/a/" not in url self.active_downloader = EromeDownloader( download_folder=self.download_folder, log_callback=self.log_callback, enable_widgets_callback=self.enable_widgets_callback, update_progress_callback=self.update_progress_callback, tr=tr ) if "/a/" in url: target = self.active_downloader.process_album_page args = (url, self.download_folder, download_images, download_videos) else: target = self.active_downloader.process_profile_page args = (url, self.download_folder, download_images, download_videos) elif re.search(r"https?://([a-z0-9-]+\.)?bunkr\.[a-z]{2,}", url): self.log_callback("Starting Bunkr Download") self.active_downloader = BunkrDownloader( download_folder=self.download_folder, log_callback=self.log_callback, enable_widgets_callback=self.enable_widgets_callback, update_progress_callback=self.update_progress_callback, tr=tr ) if any(sub in url for sub in ["/v/", "/i/", "/f/"]): target = self.active_downloader.descargar_post_bunkr args = (url,) else: target = self.active_downloader.descargar_perfil_bunkr args = (url,) elif parsed_url.netloc in ["coomer.st", "kemono.cr", "coomer.su", "kemono.su"]: self.log_callback("Starting Coomer/Kemono Download (Direct SMB)") try: self.samba_manager = SambaManager( settings.samba_server_ip, "isolation", settings.samba_username, settings.samba_password ) self.log_callback(f"Connected to SMB Share: isolation at {settings.samba_server_ip}") storage = SambaStorage(self.samba_manager, base_path="videos/coomerparty") except Exception as e: self.log_callback(f"Failed to connect to SMB: {e}") raise e self.active_downloader = Downloader( download_folder="", max_workers=max_workers, log_callback=self.log_callback, enable_widgets_callback=self.enable_widgets_callback, update_progress_callback=self.update_progress_callback, tr=tr, download_images=download_images, download_videos=download_videos, storage=storage, force_overwrite=force_overwrite ) site = parsed_url.netloc service, user, post = extract_ck_parameters(parsed_url) if not service or not user: raise Exception("Invalid Coomer/Kemono URL") if post: target = self.active_downloader.download_single_post args = (site, post, service, user) else: query, offset = extract_ck_query(parsed_url) target = self.active_downloader.download_media args = (site, user, service, query, True, offset) elif "simpcity.cr" in url: self.log_callback("Starting SimpCity Download") self.active_downloader = SimpCity( download_folder=self.download_folder, log_callback=self.log_callback, enable_widgets_callback=self.enable_widgets_callback, update_progress_callback=self.update_progress_callback, tr=tr ) target = self.active_downloader.download_images_from_simpcity args = (url,) elif "jpg5.su" in url: self.log_callback("Starting Jpg5 Download") self.active_downloader = Jpg5Downloader( url=url, carpeta_destino=self.download_folder, log_callback=self.log_callback, tr=tr ) target = self.active_downloader.descargar_imagenes args = () else: raise Exception(f"Unsupported URL: {url}") # Execute self.progress["status"] = "Running" target(*args) def cancel_download(self): self.progress["status"] = "Cancelling..." self.log_callback("Cancellation requested...") if self.active_downloader and hasattr(self.active_downloader, 'request_cancel'): self.active_downloader.request_cancel() manager = DownloaderManager()