mirror of
https://github.com/Yuvi9587/Kemono-Downloader.git
synced 2025-12-29 16:14:44 +00:00
Commit
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
155
main.py
155
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 <b>Manga/Comic Mode</b> and also specified a <b>Page Range</b>.\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.")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user