diff --git a/downloader_utils.py b/downloader_utils.py index cde814b..ff26c5a 100644 --- a/downloader_utils.py +++ b/downloader_utils.py @@ -31,6 +31,7 @@ from io import BytesIO STYLE_POST_TITLE = "post_title" STYLE_ORIGINAL_NAME = "original_name" STYLE_DATE_BASED = "date_based" # For manga date-based sequential naming +STYLE_POST_TITLE_GLOBAL_NUMBERING = "post_title_global_numbering" # For manga post title + global counter SKIP_SCOPE_FILES = "files" SKIP_SCOPE_POSTS = "posts" @@ -276,7 +277,7 @@ def prepare_cookies_for_request(use_cookie_flag, cookie_text_input, selected_coo return None def fetch_posts_paginated(api_url_base, headers, offset, logger, cancellation_event=None, pause_event=None, cookies_dict=None): - if cancellation_event and cancellation_event.is_set(): + if cancellation_event and cancellation_event.is_set(): # type: ignore logger(" Fetch cancelled before request.") raise RuntimeError("Fetch operation cancelled by user.") @@ -284,7 +285,7 @@ def fetch_posts_paginated(api_url_base, headers, offset, logger, cancellation_ev logger(" Post fetching paused...") while pause_event.is_set(): if cancellation_event and cancellation_event.is_set(): - logger(" Post fetching cancelled while paused.") + logger(" Post fetching cancelled while paused.") # type: ignore raise RuntimeError("Fetch operation cancelled by user.") time.sleep(0.5) logger(" Post fetching resumed.") @@ -379,21 +380,37 @@ def download_from_api(api_url_input, logger=print, start_page=None, end_page=Non page_size = 50 if is_creator_feed_for_manga: - logger(" Manga Mode: Fetching all posts to reverse order (oldest posts processed first)...") + logger(" Manga Mode: Fetching posts to sort by date (oldest processed first)...") all_posts_for_manga_mode = [] + current_offset_manga = 0 + # Determine starting page and offset for manga mode + if start_page and start_page > 1: + current_offset_manga = (start_page - 1) * page_size + logger(f" Manga Mode: Starting fetch from page {start_page} (offset {current_offset_manga}).") + elif start_page: # start_page is 1 + logger(f" Manga Mode: Starting fetch from page 1 (offset 0).") + + if end_page: + logger(f" Manga Mode: Will fetch up to page {end_page}.") + while True: if pause_event and pause_event.is_set(): - logger(" Manga mode post fetching paused...") # type: ignor + logger(" Manga mode post fetching paused...") # type: ignore while pause_event.is_set(): if cancellation_event and cancellation_event.is_set(): - logger(" Manga mode post fetching cancelled while paused.") + logger(" Manga mode post fetching cancelled while paused.") # type: ignore break time.sleep(0.5) if not (cancellation_event and cancellation_event.is_set()): logger(" Manga mode post fetching resumed.") if cancellation_event and cancellation_event.is_set(): logger(" Manga mode post fetching cancelled.") break + + current_page_num_manga = (current_offset_manga // page_size) + 1 + if end_page and current_page_num_manga > end_page: + logger(f" Manga Mode: Reached specified end page ({end_page}). Stopping post fetch.") + break try: posts_batch_manga = fetch_posts_paginated(api_base_url, headers, current_offset_manga, logger, cancellation_event, pause_event, cookies_dict=cookies_for_api) if not isinstance(posts_batch_manga, list): @@ -401,7 +418,11 @@ def download_from_api(api_url_input, logger=print, start_page=None, end_page=Non break if not posts_batch_manga: logger("✅ Reached end of posts (Manga Mode fetch all).") - break + if start_page and not end_page and current_page_num_manga < start_page: # Started on a page with no posts + logger(f" Manga Mode: No posts found on or after specified start page {start_page}.") + elif end_page and current_page_num_manga <= end_page and not all_posts_for_manga_mode: # Range specified but no posts in it + logger(f" Manga Mode: No posts found within the specified page range ({start_page or 1}-{end_page}).") + break # No more posts from API all_posts_for_manga_mode.extend(posts_batch_manga) current_offset_manga += page_size # Increment by page_size for the next API call's 'o' parameter time.sleep(0.6) @@ -420,7 +441,7 @@ def download_from_api(api_url_input, logger=print, start_page=None, end_page=Non if all_posts_for_manga_mode: logger(f" Manga Mode: Fetched {len(all_posts_for_manga_mode)} total posts. Sorting by publication date (oldest first)...") - + # ... (rest of sorting and yielding logic for manga mode remains the same) ... def sort_key_tuple(post): published_date_str = post.get('published') added_date_str = post.get('added') @@ -584,7 +605,8 @@ class PostProcessorWorker: selected_cookie_file=None, # Added missing parameter app_base_dir=None, # New parameter for app's base directory manga_date_file_counter_ref=None, # New parameter for date-based manga naming - ): + manga_global_file_counter_ref=None, # New parameter for global numbering + ): # type: ignore self.post = post_data self.download_root = download_root self.known_names = known_names @@ -630,6 +652,7 @@ class PostProcessorWorker: self.selected_cookie_file = selected_cookie_file # Store selected cookie file path self.app_base_dir = app_base_dir # Store app base dir self.cookie_text = cookie_text # Store cookie text + self.manga_global_file_counter_ref = manga_global_file_counter_ref # Store global counter self.use_cookie = use_cookie # Store cookie setting if self.compress_images and Image is None: @@ -666,7 +689,8 @@ class PostProcessorWorker: def _download_single_file(self, file_info, target_folder_path, headers, original_post_id_for_log, skip_event, # skip_event is threading.Event post_title="", file_index_in_post=0, num_files_in_this_post=1, manga_date_file_counter_ref=None): # Added manga_date_file_counter_ref - was_original_name_kept_flag = False + was_original_name_kept_flag = False + manga_global_file_counter_ref = None # Placeholder, will be passed from process() final_filename_saved_for_return = "" def _get_current_character_filters(self): @@ -677,7 +701,8 @@ class PostProcessorWorker: def _download_single_file(self, file_info, target_folder_path, headers, original_post_id_for_log, skip_event, post_title="", file_index_in_post=0, num_files_in_this_post=1, # Added manga_date_file_counter_ref manga_date_file_counter_ref=None, - forced_filename_override=None): # New for retries + forced_filename_override=None, # New for retries + manga_global_file_counter_ref=None): # New for global numbering was_original_name_kept_flag = False final_filename_saved_for_return = "" retry_later_details = None # For storing info if retryable failure @@ -739,6 +764,19 @@ class PostProcessorWorker: self.logger(f"⚠️ Manga Date Mode: Counter ref not provided or malformed for '{api_original_filename}'. Using original. Ref: {manga_date_file_counter_ref}") filename_to_save_in_main_path = clean_filename(api_original_filename) self.logger(f"⚠️ Manga mode (Date Based Style Fallback): Using cleaned original filename '{filename_to_save_in_main_path}' for post {original_post_id_for_log}.") + elif self.manga_filename_style == STYLE_POST_TITLE_GLOBAL_NUMBERING: + if manga_global_file_counter_ref is not None and len(manga_global_file_counter_ref) == 2: + counter_val_for_filename = -1 + counter_lock = manga_global_file_counter_ref[1] + with counter_lock: + counter_val_for_filename = manga_global_file_counter_ref[0] + manga_global_file_counter_ref[0] += 1 + + cleaned_post_title_base_for_global = clean_filename(post_title.strip() if post_title and post_title.strip() else "post") + filename_to_save_in_main_path = f"{cleaned_post_title_base_for_global}_{counter_val_for_filename:03d}{original_ext}" + else: + self.logger(f"⚠️ Manga Title+GlobalNum Mode: Counter ref not provided or malformed for '{api_original_filename}'. Using original. Ref: {manga_global_file_counter_ref}") + self.logger(f"⚠️ Manga mode (Date Based Style Fallback): Using cleaned original filename '{filename_to_save_in_main_path}' for post {original_post_id_for_log}.") else: self.logger(f"⚠️ Manga mode: Unknown filename style '{self.manga_filename_style}'. Defaulting to original filename for '{api_original_filename}'.") filename_to_save_in_main_path = clean_filename(api_original_filename) @@ -1429,6 +1467,14 @@ class PostProcessorWorker: target_folder_path_for_this_file = current_path_for_file + manga_date_counter_to_pass = None + manga_global_counter_to_pass = None + if self.manga_mode_active: + if self.manga_filename_style == STYLE_DATE_BASED: + manga_date_counter_to_pass = self.manga_date_file_counter_ref + elif self.manga_filename_style == STYLE_POST_TITLE_GLOBAL_NUMBERING: + manga_global_counter_to_pass = self.manga_global_file_counter_ref if self.manga_global_file_counter_ref is not None else self.manga_date_file_counter_ref + futures_list.append(file_pool.submit( self._download_single_file, file_info_to_dl, @@ -1436,8 +1482,9 @@ class PostProcessorWorker: headers, post_id, self.skip_current_file_flag, - post_title=post_title, # Keyword argument - manga_date_file_counter_ref=self.manga_date_file_counter_ref if self.manga_mode_active and self.manga_filename_style == STYLE_DATE_BASED else None, + post_title=post_title, + manga_date_file_counter_ref=manga_date_counter_to_pass, + manga_global_file_counter_ref=manga_global_counter_to_pass, file_index_in_post=file_idx, # Changed to keyword argument num_files_in_this_post=num_files_in_this_post_for_naming # Changed to keyword argument )) @@ -1505,6 +1552,7 @@ class DownloadThread(QThread): selected_cookie_file=None, # New parameter for selected cookie file app_base_dir=None, # New parameter manga_date_file_counter_ref=None, # New parameter + manga_global_file_counter_ref=None, # New parameter for global numbering use_cookie=False, # Added: Expected by main.py cookie_text="", # Added: Expected by main.py ): @@ -1555,6 +1603,7 @@ class DownloadThread(QThread): self.cookie_text = cookie_text # Store cookie text self.use_cookie = use_cookie # Store cookie setting self.manga_date_file_counter_ref = manga_date_file_counter_ref # Store for passing to worker by DownloadThread + self.manga_global_file_counter_ref = manga_global_file_counter_ref # Store for global numbering if self.compress_images and Image is None: self.logger("⚠️ Image compression disabled: Pillow library not found (DownloadThread).") self.compress_images = False @@ -1607,9 +1656,16 @@ class DownloadThread(QThread): for filename_to_check in filenames_in_dir: base_name_no_ext = os.path.splitext(filename_to_check)[0] match = re.match(r"(\d{3,})", base_name_no_ext) - if match: highest_num = max(highest_num, int(match.group(1))) + if match: highest_num = max(highest_num, int(match.group(1))) # Corrected indentation current_manga_date_file_counter_ref = [highest_num + 1, threading.Lock()] self.logger(f"ℹ️ [Thread] Manga Date Mode: Initialized counter at {current_manga_date_file_counter_ref[0]}.") + elif self.manga_mode_active and self.manga_filename_style == STYLE_POST_TITLE_GLOBAL_NUMBERING and not self.extract_links_only and current_manga_date_file_counter_ref is None: # Use current_manga_date_file_counter_ref for STYLE_POST_TITLE_GLOBAL_NUMBERING as well + # For global numbering, we always start from 1 for the session unless a ref is passed. + # If you need to resume global numbering across sessions, similar scanning logic would be needed. + # For now, it starts at 1 per session if no ref is provided. + current_manga_date_file_counter_ref = [1, threading.Lock()] # Start global numbering at 1 + self.logger(f"ℹ️ [Thread] Manga Title+GlobalNum Mode: Initialized counter at {current_manga_date_file_counter_ref[0]}.") + worker_signals_obj = PostProcessorSignals() try: worker_signals_obj.progress_signal.connect(self.progress_signal) @@ -1676,6 +1732,7 @@ class DownloadThread(QThread): selected_cookie_file=self.selected_cookie_file, # Pass selected cookie file app_base_dir=self.app_base_dir, # Pass app_base_dir cookie_text=self.cookie_text, # Pass cookie text + manga_global_file_counter_ref=self.manga_global_file_counter_ref, # Pass the ref use_cookie=self.use_cookie, # Pass cookie setting to worker manga_date_file_counter_ref=current_manga_date_file_counter_ref, # Pass the calculated or passed-in ref ) diff --git a/main.py b/main.py index 29387c2..77320c6 100644 --- a/main.py +++ b/main.py @@ -54,20 +54,33 @@ try: CHAR_SCOPE_FILES, # Ensure this is imported CHAR_SCOPE_BOTH, CHAR_SCOPE_COMMENTS, - FILE_DOWNLOAD_STATUS_FAILED_RETRYABLE_LATER # Import the new status + FILE_DOWNLOAD_STATUS_FAILED_RETRYABLE_LATER, # Import the new status + STYLE_POST_TITLE_GLOBAL_NUMBERING # Import new manga style ) print("Successfully imported names from downloader_utils.") except ImportError as e: print(f"--- IMPORT ERROR ---") print(f"Failed to import from 'downloader_utils.py': {e}") + print(f"--- Check downloader_utils.py for syntax errors or missing dependencies. ---") KNOWN_NAMES = [] - PostProcessorSignals = QObject PostProcessorWorker = object + # Create a mock PostProcessorSignals class with the expected signals + class _MockPostProcessorSignals(QObject): + progress_signal = pyqtSignal(str) + file_download_status_signal = pyqtSignal(bool) + external_link_signal = pyqtSignal(str, str, str, str) + file_progress_signal = pyqtSignal(str, object) + missed_character_post_signal = pyqtSignal(str, str) + # Add any other signals that might be expected if the real class is extended + def __init__(self, parent=None): + super().__init__(parent) + print("WARNING: Using MOCK PostProcessorSignals due to import error from downloader_utils.py. Some functionalities might be impaired.") + PostProcessorSignals = _MockPostProcessorSignals # Use the mock class BackendDownloadThread = QThread def clean_folder_name(n): return str(n) def extract_post_info(u): return None, None, None def download_from_api(*a, **k): yield [] - SKIP_SCOPE_FILES = "files" + SKIP_SCOPE_FILES = "files" # type: ignore SKIP_SCOPE_POSTS = "posts" SKIP_SCOPE_BOTH = "both" CHAR_SCOPE_TITLE = "title" @@ -75,6 +88,7 @@ except ImportError as e: CHAR_SCOPE_BOTH = "both" CHAR_SCOPE_COMMENTS = "comments" FILE_DOWNLOAD_STATUS_FAILED_RETRYABLE_LATER = "failed_retry_later" + STYLE_POST_TITLE_GLOBAL_NUMBERING = "post_title_global_numbering" # Mock for safety except Exception as e: print(f"--- UNEXPECTED IMPORT ERROR ---") @@ -101,6 +115,7 @@ MANGA_FILENAME_STYLE_KEY = "mangaFilenameStyleV1" STYLE_POST_TITLE = "post_title" STYLE_ORIGINAL_NAME = "original_name" STYLE_DATE_BASED = "date_based" # New style for date-based naming +STYLE_POST_TITLE_GLOBAL_NUMBERING = STYLE_POST_TITLE_GLOBAL_NUMBERING # Use imported or mocked SKIP_WORDS_SCOPE_KEY = "skipWordsScopeV1" ALLOW_MULTIPART_DOWNLOAD_KEY = "allowMultipartDownloadV1" @@ -2460,18 +2475,18 @@ class DownloaderApp(QWidget): if self.custom_folder_input: self.custom_folder_input.clear() - def update_ui_for_subfolders(self, checked): + def update_ui_for_subfolders(self, separate_folders_by_name_title_checked: bool): is_only_links = self.radio_only_links and self.radio_only_links.isChecked() is_only_archives = self.radio_only_archives and self.radio_only_archives.isChecked() + can_enable_subfolder_per_post_checkbox = not is_only_links and not is_only_archives + if self.use_subfolder_per_post_checkbox: - can_enable_subfolder_per_post = checked and not is_only_links and not is_only_archives - self.use_subfolder_per_post_checkbox.setEnabled(can_enable_subfolder_per_post) - if not can_enable_subfolder_per_post: # If it's disabled, also uncheck it + self.use_subfolder_per_post_checkbox.setEnabled(can_enable_subfolder_per_post_checkbox) + + if not can_enable_subfolder_per_post_checkbox: self.use_subfolder_per_post_checkbox.setChecked(False) - # Visibility and enabled state of character filter widgets are now primarily handled - # by _handle_filter_mode_change to decouple from the subfolder checkbox. self.update_custom_folder_visibility() @@ -2508,12 +2523,12 @@ class DownloaderApp(QWidget): _, _, post_id = extract_post_info(url_text) is_creator_feed = not post_id if url_text else False - manga_mode_active = self.manga_mode_checkbox.isChecked() if self.manga_mode_checkbox else False - - enable_page_range = is_creator_feed and not manga_mode_active + # Manga mode no longer directly dictates page range enabled state. + # Page range is enabled if it's a creator feed. + enable_page_range = is_creator_feed for widget in [self.page_range_label, self.start_page_input, self.to_label, self.end_page_input]: - if widget: widget.setEnabled(enable_page_range) + if widget: widget.setEnabled(enable_page_range) # Enable/disable based on whether it's a creator feed if not enable_page_range: if self.start_page_input: self.start_page_input.clear() @@ -2546,6 +2561,18 @@ class DownloaderApp(QWidget): " Downloads as: \"001.jpg\", \"002.jpg\".\n\n" "Click to change to: Post Title" ) + elif self.manga_filename_style == STYLE_POST_TITLE_GLOBAL_NUMBERING: + self.manga_rename_toggle_button.setText("Name: Title+G.Num") + self.manga_rename_toggle_button.setToolTip( + "Manga Filename Style: Post Title + Global Numbering\n\n" + "When Manga/Comic Mode is active for a creator feed:\n" + "- All files across all posts in the current download session are named sequentially using the post's title as a prefix.\n" + "- Example: Post 'Chapter 1' (2 files) -> 'Chapter 1_001.jpg', 'Chapter 1_002.png'.\n" + " Next Post 'Chapter 2' (1 file) -> 'Chapter 2_003.jpg'.\n" + "- Multithreading for post processing is automatically disabled for this style.\n\n" + "Click to change to: Post Title" + ) + elif self.manga_filename_style == STYLE_DATE_BASED: self.manga_rename_toggle_button.setText("Name: Date Based") self.manga_rename_toggle_button.setToolTip( @@ -2572,8 +2599,10 @@ class DownloaderApp(QWidget): if current_style == STYLE_POST_TITLE: # Title -> Original new_style = STYLE_ORIGINAL_NAME elif current_style == STYLE_ORIGINAL_NAME: # Original -> Date + new_style = STYLE_POST_TITLE_GLOBAL_NUMBERING # Original -> Title+GlobalNum + elif current_style == STYLE_POST_TITLE_GLOBAL_NUMBERING: # Title+GlobalNum -> Date Based new_style = STYLE_DATE_BASED - elif current_style == STYLE_DATE_BASED: # Date -> Title + elif current_style == STYLE_DATE_BASED: # Date Based -> Title new_style = STYLE_POST_TITLE else: self.log_signal.emit(f"⚠️ Unknown current manga filename style: {current_style}. Resetting to default ('{STYLE_POST_TITLE}').") @@ -2606,14 +2635,8 @@ class DownloaderApp(QWidget): if self.manga_rename_toggle_button: self.manga_rename_toggle_button.setVisible(manga_mode_effectively_on and not (is_only_links_mode or is_only_archives_mode)) - - if manga_mode_effectively_on: - if self.page_range_label: self.page_range_label.setEnabled(False) - if self.start_page_input: self.start_page_input.setEnabled(False); self.start_page_input.clear() - if self.to_label: self.to_label.setEnabled(False) - if self.end_page_input: self.end_page_input.setEnabled(False); self.end_page_input.clear() - else: - self.update_page_range_enabled_state() + # Always update page range enabled state, as it depends on URL type, not directly manga mode. + self.update_page_range_enabled_state() file_download_mode_active = not (self.radio_only_links and self.radio_only_links.isChecked()) # Character filter widgets should be enabled if it's a file download mode @@ -2666,10 +2689,12 @@ class DownloaderApp(QWidget): if not hasattr(self, 'manga_mode_checkbox') or not hasattr(self, 'use_multithreading_checkbox'): return # UI elements not ready - manga_on = self.manga_mode_checkbox.isChecked() - is_date_style = (self.manga_filename_style == STYLE_DATE_BASED) - - if manga_on and is_date_style: + manga_on = self.manga_mode_checkbox.isChecked() # type: ignore + is_sequential_style_requiring_single_thread = ( + self.manga_filename_style == STYLE_DATE_BASED or + self.manga_filename_style == STYLE_POST_TITLE_GLOBAL_NUMBERING + ) + if manga_on and is_sequential_style_requiring_single_thread: if self.use_multithreading_checkbox.isChecked() or self.use_multithreading_checkbox.isEnabled(): if self.use_multithreading_checkbox.isChecked(): self.log_signal.emit("ℹ️ Manga Date Mode: Multithreading for post processing has been disabled to ensure correct sequential file numbering.") @@ -2810,16 +2835,42 @@ class DownloaderApp(QWidget): start_page_str, end_page_str = self.start_page_input.text().strip(), self.end_page_input.text().strip() start_page, end_page = None, None is_creator_feed = bool(not post_id_from_url) - if is_creator_feed and not manga_mode: + + if is_creator_feed: # Page range is only relevant and parsed for creator feeds try: if start_page_str: start_page = int(start_page_str) if end_page_str: end_page = int(end_page_str) + + # Validate parsed page numbers if start_page is not None and start_page <= 0: raise ValueError("Start page must be positive.") if end_page is not None and end_page <= 0: raise ValueError("End page must be positive.") if start_page and end_page and start_page > end_page: raise ValueError("Start page cannot be greater than end page.") - except ValueError as e: QMessageBox.critical(self, "Page Range Error", f"Invalid page range: {e}"); return - elif manga_mode: - start_page, end_page = None, None + + # If it's a creator feed, and manga mode is on, and both page fields were filled, show warning + if manga_mode and start_page and end_page: + msg_box = QMessageBox(self) + msg_box.setIcon(QMessageBox.Warning) + msg_box.setWindowTitle("Manga Mode & Page Range Warning") + msg_box.setText( + "You have enabled Manga/Comic Mode and also specified a Page Range.\n\n" + "Manga Mode processes posts from oldest to newest across all available pages by default.\n" + "If you use a page range, you might miss parts of the manga/comic if it starts before your 'Start Page' or continues after your 'End Page'.\n\n" + "However, if you are certain the content you want is entirely within this page range (e.g., a short series, or you know the specific pages for a volume), then proceeding is okay.\n\n" + "Do you want to proceed with this page range in Manga Mode?" + ) + proceed_button = msg_box.addButton("Proceed Anyway", QMessageBox.AcceptRole) + cancel_button = msg_box.addButton("Cancel Download", QMessageBox.RejectRole) + msg_box.setDefaultButton(proceed_button) + msg_box.setEscapeButton(cancel_button) + msg_box.exec_() + + if msg_box.clickedButton() == cancel_button: + self.log_signal.emit("❌ Download cancelled by user due to Manga Mode & Page Range warning.") + self.set_ui_enabled(True); return # Re-enable UI and stop + except ValueError as e: + QMessageBox.critical(self, "Page Range Error", f"Invalid page range: {e}") + self.set_ui_enabled(True); return # Re-enable UI and stop + # If not a creator_feed, start_page and end_page remain None. self.external_link_queue.clear(); self.extracted_links_cache = []; self._is_processing_external_link_queue = False; self._current_link_post_title = None raw_character_filters_text = self.character_input.text().strip() # Get current text @@ -2954,17 +3005,27 @@ class DownloaderApp(QWidget): self.retryable_failed_files_info.clear() # Clear previous retryable failures before new session manga_date_file_counter_ref_for_thread = None if manga_mode and self.manga_filename_style == STYLE_DATE_BASED and not extract_links_only: - manga_date_file_counter_ref_for_thread = None + # Initialization for STYLE_DATE_BASED (scanning existing files) happens in DownloadThread.run + manga_date_file_counter_ref_for_thread = None # Placeholder, actual init in thread self.log_signal.emit(f"ℹ️ Manga Date Mode: File counter will be initialized by the download thread.") + + manga_global_file_counter_ref_for_thread = None + if manga_mode and self.manga_filename_style == STYLE_POST_TITLE_GLOBAL_NUMBERING and not extract_links_only: + manga_global_file_counter_ref_for_thread = None # Placeholder, actual init in thread + self.log_signal.emit(f"ℹ️ Manga Title+GlobalNum Mode: File counter will be initialized by the download thread (starts at 1).") + effective_num_post_workers = 1 + effective_num_file_threads_per_worker = 1 # Default to 1 for all cases initially if post_id_from_url: if use_multithreading_enabled_by_checkbox: effective_num_file_threads_per_worker = max(1, min(num_threads_from_gui, MAX_FILE_THREADS_PER_POST_OR_WORKER)) - else: + else: # This is the outer else block if manga_mode and self.manga_filename_style == STYLE_DATE_BASED: effective_num_post_workers = 1 + elif manga_mode and self.manga_filename_style == STYLE_POST_TITLE_GLOBAL_NUMBERING: # Correctly indented elif + effective_num_post_workers = 1 effective_num_file_threads_per_worker = 1 # Files are sequential for this worker too elif use_multithreading_enabled_by_checkbox: # Standard creator feed with multithreading enabled effective_num_post_workers = max(1, min(num_threads_from_gui, MAX_THREADS)) # For posts @@ -2980,13 +3041,15 @@ class DownloaderApp(QWidget): log_messages.append(f" Mode: Creator Feed") log_messages.append(f" Post Processing: {'Multi-threaded (' + str(effective_num_post_workers) + ' workers)' if effective_num_post_workers > 1 else 'Single-threaded (1 worker)'}") log_messages.append(f" ↳ File Downloads per Worker: Up to {effective_num_file_threads_per_worker} concurrent file(s)") - if is_creator_feed: - if manga_mode: log_messages.append(" Page Range: All (Manga Mode - Oldest Posts Processed First)") - else: - pr_log = "All" - if start_page or end_page: - pr_log = f"{f'From {start_page} ' if start_page else ''}{'to ' if start_page and end_page else ''}{f'{end_page}' if end_page else (f'Up to {end_page}' if end_page else (f'From {start_page}' if start_page else 'Specific Range'))}".strip() - log_messages.append(f" Page Range: {pr_log if pr_log else 'All'}") + # Logging for page range (applies if is_creator_feed is true) + pr_log = "All" + if start_page or end_page: # Construct pr_log if start_page or end_page have values + pr_log = f"{f'From {start_page} ' if start_page else ''}{'to ' if start_page and end_page else ''}{f'{end_page}' if end_page else (f'Up to {end_page}' if end_page else (f'From {start_page}' if start_page else 'Specific Range'))}".strip() + + if manga_mode: + log_messages.append(f" Page Range: {pr_log if pr_log else 'All'} (Manga Mode - Oldest Posts Processed First within range)") + else: # Not manga mode, but still a creator feed + log_messages.append(f" Page Range: {pr_log if pr_log else 'All'}") if not extract_links_only: @@ -3027,8 +3090,9 @@ class DownloaderApp(QWidget): elif use_cookie_from_checkbox and selected_cookie_file_path_for_backend: log_messages.append(f" ↳ Cookie File Selected: {os.path.basename(selected_cookie_file_path_for_backend)}") should_use_multithreading_for_posts = use_multithreading_enabled_by_checkbox and not post_id_from_url - if manga_mode and self.manga_filename_style == STYLE_DATE_BASED and not post_id_from_url: - log_messages.append(f" Threading: Single-threaded (posts) - Enforced by Manga Date Mode") + if manga_mode and (self.manga_filename_style == STYLE_DATE_BASED or self.manga_filename_style == STYLE_POST_TITLE_GLOBAL_NUMBERING) and not post_id_from_url: + enforced_by_style = "Date Mode" if self.manga_filename_style == STYLE_DATE_BASED else "Title+GlobalNum Mode" + log_messages.append(f" Threading: Single-threaded (posts) - Enforced by Manga {enforced_by_style}") should_use_multithreading_for_posts = False # Ensure this reflects the forced state else: log_messages.append(f" Threading: {'Multi-threaded (posts)' if should_use_multithreading_for_posts else 'Single-threaded (posts)'}") @@ -3082,6 +3146,7 @@ class DownloaderApp(QWidget): 'allow_multipart_download': allow_multipart, 'cookie_text': cookie_text_from_input, # Pass cookie text 'selected_cookie_file': selected_cookie_file_path_for_backend, # Pass selected cookie file + 'manga_global_file_counter_ref': manga_global_file_counter_ref_for_thread, # Pass new counter 'app_base_dir': app_base_dir_for_cookies, # Pass app base dir 'use_cookie': use_cookie_from_checkbox, # Pass cookie setting } @@ -3103,7 +3168,8 @@ class DownloaderApp(QWidget): 'skip_words_list', 'skip_words_scope', 'char_filter_scope', 'show_external_links', 'extract_links_only', 'num_file_threads_for_worker', 'start_page', 'end_page', 'target_post_id_from_initial_url', - 'manga_date_file_counter_ref', # Ensure this is passed for single thread mode + 'manga_date_file_counter_ref', + 'manga_global_file_counter_ref', # Pass new counter for single thread mode 'manga_mode_active', 'unwanted_keywords', 'manga_filename_style', 'allow_multipart_download', 'use_cookie', 'cookie_text', 'app_base_dir', 'selected_cookie_file' # Added selected_cookie_file ] @@ -3300,7 +3366,8 @@ class DownloaderApp(QWidget): 'skip_words_list', 'skip_words_scope', 'char_filter_scope', 'show_external_links', 'extract_links_only', 'allow_multipart_download', 'use_cookie', 'cookie_text', 'app_base_dir', 'selected_cookie_file', # Added selected_cookie_file 'num_file_threads', 'skip_current_file_flag', 'manga_date_file_counter_ref', - 'manga_mode_active', 'manga_filename_style' + 'manga_mode_active', 'manga_filename_style', + 'manga_global_file_counter_ref' # Add new counter here ] ppw_optional_keys_with_defaults = { 'skip_words_list', 'skip_words_scope', 'char_filter_scope', 'remove_from_filename_words_list', @@ -3308,6 +3375,8 @@ class DownloaderApp(QWidget): 'num_file_threads', 'skip_current_file_flag', 'manga_mode_active', 'manga_filename_style', 'manga_date_file_counter_ref', 'use_cookie', 'cookie_text', 'app_base_dir', 'selected_cookie_file' # Added selected_cookie_file } + # Batching is generally for high worker counts. + # If num_post_workers is low (e.g., 1), the num_post_workers > POST_WORKER_BATCH_THRESHOLD condition will prevent batching. if num_post_workers > POST_WORKER_BATCH_THRESHOLD and self.total_posts_to_process > POST_WORKER_NUM_BATCHES : self.log_signal.emit(f" High thread count ({num_post_workers}) detected. Batching post submissions into {POST_WORKER_NUM_BATCHES} parts.")