""" QThread wrapper for BookysScraper, translating callback events into Qt signals. """ from PyQt5.QtCore import QThread, pyqtSignal from .bookys_scraper import BookysScraper class BookysThread(QThread): """Runs BookysScraper off the GUI thread and emits progress signals.""" # High-level lifecycle scraping_started = pyqtSignal(dict) scraping_completed = pyqtSignal(dict) error_occurred = pyqtSignal(str) # Cloudflare handshake cloudflare_waiting = pyqtSignal(str) # message cloudflare_passed = pyqtSignal(int) # item_count cloudflare_timeout = pyqtSignal(int) # timeout seconds # Progress page_started = pyqtSignal(int, int, int, str) # page_number, index, total, url page_items_found = pyqtSignal(int, int) # page_number, item_count page_completed = pyqtSignal(int, int) # page_number, items_processed book_started = pyqtSignal(int, int, int, str) # page_number, book_index, total, url book_completed = pyqtSignal(str, int) # title, links_found link_found = pyqtSignal(str, int) # url, running_total # Catch-all textual status for the log status_update = pyqtSignal(str) def __init__(self, category="bd", start_page=1, end_page=1, output_file=None, timing_config=None): super().__init__() self.category = category self.start_page = start_page self.end_page = end_page self.output_file = output_file self.timing_config = timing_config self.scraper = None self._is_running = False def run(self): try: self._is_running = True self.scraper = BookysScraper( progress_callback=self._handle_progress, category=self.category, output_file=self.output_file, timing_config=self.timing_config, ) summary = self.scraper.scrape(self.start_page, self.end_page) self.scraping_completed.emit(summary) except Exception as e: self.error_occurred.emit(f"Unexpected error: {e}") finally: if self.scraper: self.scraper.close() self._is_running = False def _handle_progress(self, event_type, data): try: if event_type == "scraping_started": self.scraping_started.emit(data) elif event_type == "scraping_completed": # Emitted from run() as well; skip here to avoid a double signal. pass elif event_type in ("cloudflare_waiting", "cloudflare_check"): self.cloudflare_waiting.emit(data.get("message", "Waiting for page...")) elif event_type == "cloudflare_passed": self.cloudflare_passed.emit(data.get("item_count", 0)) elif event_type == "cloudflare_timeout": self.cloudflare_timeout.emit(data.get("timeout", 0)) elif event_type == "page_started": self.page_started.emit( data.get("page_number", 1), data.get("page_index", 1), data.get("total_pages", 1), data.get("url", "")) elif event_type == "page_items_found": self.page_items_found.emit(data.get("page_number", 1), data.get("item_count", 0)) elif event_type == "page_completed": self.page_completed.emit(data.get("page_number", 1), data.get("items_processed", 0)) elif event_type == "book_started": self.book_started.emit( data.get("page_number", 1), data.get("book_index", 1), data.get("total_books", 1), data.get("url", "")) elif event_type == "book_completed": self.book_completed.emit(data.get("title", ""), data.get("links_found", 0)) elif event_type == "link_found": self.link_found.emit(data.get("url", ""), data.get("total", 0)) else: self.status_update.emit(f"{event_type}: {data}") except Exception as e: self.error_occurred.emit(f"Signal emission error: {e}") def request_stop(self): if self.scraper: self.scraper.request_stop() def is_running(self): return self._is_running and self.isRunning()