This commit is contained in:
Yuvi9587
2025-07-03 12:54:05 +05:30
parent 260bf8e666
commit a00d9de546
4 changed files with 437 additions and 294 deletions

View File

@@ -987,6 +987,17 @@ class PostProcessorWorker:
cleaned_post_title_for_sub =clean_folder_name (post_title )
post_id_for_fallback =self .post .get ('id','unknown_id')
if getattr(self, 'use_post_date_prefix', False):
post_date = self.post.get('published') or self.post.get('date') or self.post.get('created_at')
if post_date:
# Format date as YYYY-MM-DD (or as you prefer)
try:
date_obj = datetime.datetime.fromisoformat(post_date.replace('Z', '+00:00'))
date_str = date_obj.strftime('%Y-%m-%d')
except Exception:
date_str = str(post_date)[:10]
cleaned_post_title_for_sub = f"{date_str} {cleaned_post_title_for_sub}"
cleaned_post_title_for_sub = clean_folder_name(cleaned_post_title_for_sub)
if not cleaned_post_title_for_sub or cleaned_post_title_for_sub =="untitled_folder":
self .logger (f" ⚠️ Post title '{post_title }' resulted in a generic subfolder name. Using 'post_{post_id_for_fallback }' as base.")
@@ -1673,4 +1684,4 @@ class DownloadThread (QThread ):
class InterruptedError(Exception):
"""Custom exception for handling cancellations gracefully."""
pass
pass

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
# --- Standard Library Imports ---
import os
import time
import json
# --- PyQt5 Imports ---
from PyQt5.QtCore import Qt, QStandardPaths, QTimer
from PyQt5.QtWidgets import (
@@ -15,205 +15,207 @@ from ...i18n.translator import get_translation
from ..main_window import get_app_icon_object
class DownloadHistoryDialog(QDialog):
"""
Dialog to display download history, showing the last few downloaded files
and the first posts processed in the current session. It also allows
exporting this history to a text file.
"""
class DownloadHistoryDialog (QDialog ):
"""Dialog to display download history."""
def __init__ (self ,last_3_downloaded_entries ,first_processed_entries ,parent_app ,parent =None ):
super ().__init__ (parent )
self .parent_app =parent_app
self .last_3_downloaded_entries =last_3_downloaded_entries
self .first_processed_entries =first_processed_entries
self .setModal (True )
def __init__(self, last_downloaded_entries, first_processed_entries, parent_app, parent=None):
"""
Initializes the dialog.
# Patch missing creator_display_name and creator_name using parent_app.creator_name_cache if available
creator_name_cache = getattr(parent_app, 'creator_name_cache', None)
if creator_name_cache:
# Patch left pane (files)
for entry in self.last_3_downloaded_entries:
if not entry.get('creator_display_name'):
service = entry.get('service', '').lower()
user_id = str(entry.get('user_id', ''))
key = (service, user_id)
entry['creator_display_name'] = creator_name_cache.get(key, entry.get('folder_context_name', 'Unknown Creator/Series'))
# Patch right pane (posts)
for entry in self.first_processed_entries:
if not entry.get('creator_name'):
service = entry.get('service', '').lower()
user_id = str(entry.get('user_id', ''))
key = (service, user_id)
entry['creator_name'] = creator_name_cache.get(key, entry.get('user_id', 'Unknown'))
Args:
last_downloaded_entries (list): A list of dicts for the last few files.
first_processed_entries (list): A list of dicts for the first few posts.
parent_app (DownloaderApp): A reference to the main application window.
parent (QWidget, optional): The parent widget. Defaults to None.
"""
super().__init__(parent)
self.parent_app = parent_app
self.last_3_downloaded_entries = last_downloaded_entries
self.first_processed_entries = first_processed_entries
self.setModal(True)
app_icon =get_app_icon_object ()
if not app_icon .isNull ():
self .setWindowIcon (app_icon )
# --- Basic Window Setup ---
app_icon = get_app_icon_object()
if app_icon and not app_icon.isNull():
self.setWindowIcon(app_icon)
screen_height =QApplication .primaryScreen ().availableGeometry ().height ()if QApplication .primaryScreen ()else 768
scale_factor =screen_height /768.0
base_min_w ,base_min_h =600 ,450
# Set window size dynamically
screen_height = QApplication.primaryScreen().availableGeometry().height() if QApplication.primaryScreen() else 768
scale_factor = screen_height / 1080.0
base_min_w, base_min_h = 600, 450
scaled_min_w = int(base_min_w * 1.5 * scale_factor)
scaled_min_h = int(base_min_h * scale_factor)
self.setMinimumSize(scaled_min_w, scaled_min_h)
self.resize(scaled_min_w, scaled_min_h + 100) # Give it a bit more height
scaled_min_w =int (base_min_w *1.5 *scale_factor )
scaled_min_h =int (base_min_h *scale_factor )
self .setMinimumSize (scaled_min_w ,scaled_min_h )
# --- Initialize UI and Apply Theming ---
self._init_ui()
self._retranslate_ui()
self._apply_theme()
self .setWindowTitle (self ._tr ("download_history_dialog_title_combined","Download History"))
def _init_ui(self):
"""Initializes all UI components and layouts for the dialog."""
dialog_layout = QVBoxLayout(self)
self.setLayout(dialog_layout)
self.main_splitter = QSplitter(Qt.Horizontal)
dialog_layout.addWidget(self.main_splitter)
dialog_layout =QVBoxLayout (self )
self .setLayout (dialog_layout )
# --- Left Pane (Last Downloaded Files) ---
left_pane_widget = self._create_history_pane(
self.last_3_downloaded_entries,
"history_last_downloaded_header", "Last 3 Files Downloaded:",
self._format_last_downloaded_entry
)
self.main_splitter.addWidget(left_pane_widget)
# --- Right Pane (First Processed Posts) ---
right_pane_widget = self._create_history_pane(
self.first_processed_entries,
"first_files_processed_header", "First {count} Posts Processed This Session:",
self._format_first_processed_entry,
count=len(self.first_processed_entries)
)
self.main_splitter.addWidget(right_pane_widget)
self .main_splitter =QSplitter (Qt .Horizontal )
dialog_layout .addWidget (self .main_splitter )
# --- Bottom Buttons ---
bottom_button_layout = QHBoxLayout()
self.save_history_button = QPushButton()
self.save_history_button.clicked.connect(self._save_history_to_txt)
bottom_button_layout.addStretch(1)
bottom_button_layout.addWidget(self.save_history_button)
dialog_layout.addLayout(bottom_button_layout)
# Set splitter sizes after the dialog is shown to ensure correct proportions
QTimer.singleShot(0, lambda: self.main_splitter.setSizes([self.width() // 2, self.width() // 2]))
left_pane_widget =QWidget ()
left_layout =QVBoxLayout (left_pane_widget )
left_header_label =QLabel (self ._tr ("history_last_downloaded_header","Last 3 Files Downloaded:"))
left_header_label .setAlignment (Qt .AlignCenter )
left_layout .addWidget (left_header_label )
def _create_history_pane(self, entries, header_key, header_default, formatter_func, **kwargs):
"""Creates a generic pane for displaying a list of history entries."""
pane_widget = QWidget()
layout = QVBoxLayout(pane_widget)
header_text = self._tr(header_key, header_default).format(**kwargs)
header_label = QLabel(header_text)
header_label.setAlignment(Qt.AlignCenter)
layout.addWidget(header_label)
left_scroll_area =QScrollArea ()
left_scroll_area .setWidgetResizable (True )
left_scroll_content_widget =QWidget ()
left_scroll_layout =QVBoxLayout (left_scroll_content_widget )
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_content_widget = QWidget()
scroll_layout = QVBoxLayout(scroll_content_widget)
if not self .last_3_downloaded_entries :
no_left_history_label =QLabel (self ._tr ("no_download_history_header","No Downloads Yet"))
no_left_history_label .setAlignment (Qt .AlignCenter )
left_scroll_layout .addWidget (no_left_history_label )
else :
for entry in self .last_3_downloaded_entries :
group_box =QGroupBox (f"{self ._tr ('history_file_label','File:')} {entry .get ('disk_filename','N/A')}")
group_layout =QVBoxLayout (group_box )
details_text =(
f"<b>{self ._tr ('history_from_post_label','From Post:')}</b> {entry .get ('post_title','N/A')} (ID: {entry .get ('post_id','N/A')})<br>"
f"<b>{self ._tr ('history_creator_series_label','Creator/Series:')}</b> {entry .get ('creator_display_name','N/A')}<br>"
f"<b>{self ._tr ('history_post_uploaded_label','Post Uploaded:')}</b> {entry .get ('upload_date_str','N/A')}<br>"
f"<b>{self ._tr ('history_file_downloaded_label','File Downloaded:')}</b> {time .strftime ('%Y-%m-%d %H:%M:%S',time .localtime (entry .get ('download_timestamp',0 )))}<br>"
f"<b>{self ._tr ('history_saved_in_folder_label','Saved In Folder:')}</b> {entry .get ('download_path','N/A')}"
)
details_label =QLabel (details_text )
details_label .setWordWrap (True )
details_label .setTextFormat (Qt .RichText )
group_layout .addWidget (details_label )
left_scroll_layout .addWidget (group_box )
left_scroll_area .setWidget (left_scroll_content_widget )
left_layout .addWidget (left_scroll_area )
self .main_splitter .addWidget (left_pane_widget )
if not entries:
no_history_label = QLabel(self._tr("no_download_history_header", "No History Yet"))
no_history_label.setAlignment(Qt.AlignCenter)
scroll_layout.addWidget(no_history_label)
else:
for entry in entries:
group_box, details_label = formatter_func(entry)
group_layout = QVBoxLayout(group_box)
group_layout.addWidget(details_label)
scroll_layout.addWidget(group_box)
scroll_area.setWidget(scroll_content_widget)
layout.addWidget(scroll_area)
return pane_widget
right_pane_widget =QWidget ()
right_layout =QVBoxLayout (right_pane_widget )
right_header_label =QLabel (self ._tr ("first_files_processed_header","First {count} Posts Processed This Session:").format (count =len (self .first_processed_entries )))
right_header_label .setAlignment (Qt .AlignCenter )
right_layout .addWidget (right_header_label )
def _format_last_downloaded_entry(self, entry):
"""Formats a single entry for the 'Last Downloaded Files' pane."""
group_box = QGroupBox(f"{self._tr('history_file_label', 'File:')} {entry.get('disk_filename', 'N/A')}")
details_text = (
f"<b>{self._tr('history_from_post_label', 'From Post:')}</b> {entry.get('post_title', 'N/A')} (ID: {entry.get('post_id', 'N/A')})<br>"
f"<b>{self._tr('history_creator_series_label', 'Creator/Series:')}</b> {entry.get('creator_display_name', 'N/A')}<br>"
f"<b>{self._tr('history_post_uploaded_label', 'Post Uploaded:')}</b> {entry.get('upload_date_str', 'N/A')}<br>"
f"<b>{self._tr('history_file_downloaded_label', 'File Downloaded:')}</b> {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(entry.get('download_timestamp', 0)))}<br>"
f"<b>{self._tr('history_saved_in_folder_label', 'Saved In Folder:')}</b> {entry.get('download_path', 'N/A')}"
)
details_label = QLabel(details_text)
details_label.setWordWrap(True)
details_label.setTextFormat(Qt.RichText)
return group_box, details_label
right_scroll_area =QScrollArea ()
right_scroll_area .setWidgetResizable (True )
right_scroll_content_widget =QWidget ()
right_scroll_layout =QVBoxLayout (right_scroll_content_widget )
def _format_first_processed_entry(self, entry):
"""Formats a single entry for the 'First Processed Posts' pane."""
group_box = QGroupBox(f"{self._tr('history_post_label', 'Post:')} {entry.get('post_title', 'N/A')} (ID: {entry.get('post_id', 'N/A')})")
details_text = (
f"<b>{self._tr('history_creator_label', 'Creator:')}</b> {entry.get('creator_name', 'N/A')}<br>"
f"<b>{self._tr('history_top_file_label', 'Top File:')}</b> {entry.get('top_file_name', 'N/A')}<br>"
f"<b>{self._tr('history_num_files_label', 'Num Files in Post:')}</b> {entry.get('num_files', 0)}<br>"
f"<b>{self._tr('history_post_uploaded_label', 'Post Uploaded:')}</b> {entry.get('upload_date_str', 'N/A')}<br>"
f"<b>{self._tr('history_processed_on_label', 'Processed On:')}</b> {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(entry.get('download_date_timestamp', 0)))}<br>"
f"<b>{self._tr('history_saved_to_folder_label', 'Saved To Folder:')}</b> {entry.get('download_location', 'N/A')}"
)
details_label = QLabel(details_text)
details_label.setWordWrap(True)
details_label.setTextFormat(Qt.RichText)
return group_box, details_label
if not self .first_processed_entries :
no_right_history_label =QLabel (self ._tr ("no_processed_history_header","No Posts Processed Yet"))
no_right_history_label .setAlignment (Qt .AlignCenter )
right_scroll_layout .addWidget (no_right_history_label )
else :
for entry in self .first_processed_entries :
def _tr(self, key, default_text=""):
"""Helper to get translation based on the main application's current language."""
if callable(get_translation) and self.parent_app:
return get_translation(self.parent_app.current_selected_language, key, default_text)
return default_text
group_box =QGroupBox (f"{self ._tr ('history_post_label','Post:')} {entry .get ('post_title','N/A')} (ID: {entry .get ('post_id','N/A')})")
group_layout =QVBoxLayout (group_box )
details_text =(
f"<b>{self ._tr ('history_creator_label','Creator:')}</b> {entry .get ('creator_name','N/A')}<br>"
f"<b>{self ._tr ('history_top_file_label','Top File:')}</b> {entry .get ('top_file_name','N/A')}<br>"
f"<b>{self ._tr ('history_num_files_label','Num Files in Post:')}</b> {entry .get ('num_files',0 )}<br>"
f"<b>{self ._tr ('history_post_uploaded_label','Post Uploaded:')}</b> {entry .get ('upload_date_str','N/A')}<br>"
f"<b>{self ._tr ('history_processed_on_label','Processed On:')}</b> {time .strftime ('%Y-%m-%d %H:%M:%S',time .localtime (entry .get ('download_date_timestamp',0 )))}<br>"
f"<b>{self ._tr ('history_saved_to_folder_label','Saved To Folder:')}</b> {entry .get ('download_location','N/A')}"
)
details_label =QLabel (details_text )
details_label .setWordWrap (True )
details_label .setTextFormat (Qt .RichText )
group_layout .addWidget (details_label )
right_scroll_layout .addWidget (group_box )
right_scroll_area .setWidget (right_scroll_content_widget )
right_layout .addWidget (right_scroll_area )
self .main_splitter .addWidget (right_pane_widget )
def _retranslate_ui(self):
"""Sets the text for all translatable UI elements."""
self.setWindowTitle(self._tr("download_history_dialog_title_combined", "Download History"))
self.save_history_button.setText(self._tr("history_save_button_text", "Save History to .txt"))
def _apply_theme(self):
"""Applies the current theme from the parent application."""
if self.parent_app and hasattr(self.parent_app, 'get_dark_theme') and self.parent_app.current_theme == "dark":
self.setStyleSheet(self.parent_app.get_dark_theme())
QTimer .singleShot (0 ,lambda :self .main_splitter .setSizes ([self .width ()//2 ,self .width ()//2 ]))
def _save_history_to_txt(self):
"""Saves the displayed history content to a user-selected text file."""
if not self.last_3_downloaded_entries and not self.first_processed_entries:
QMessageBox.information(
self,
self._tr("no_download_history_header", "No History Yet"),
self._tr("history_nothing_to_save_message", "There is no history to save.")
)
return
# Suggest saving in the main download directory or Documents as a fallback
main_download_dir = self.parent_app.dir_input.text().strip()
default_save_dir = ""
if main_download_dir and os.path.isdir(main_download_dir):
default_save_dir = main_download_dir
else:
default_save_dir = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation) or self.parent_app.app_base_dir
bottom_button_layout =QHBoxLayout ()
self .save_history_button =QPushButton (self ._tr ("history_save_button_text","Save History to .txt"))
self .save_history_button .clicked .connect (self ._save_history_to_txt )
bottom_button_layout .addStretch (1 )
bottom_button_layout .addWidget (self .save_history_button )
default_filepath = os.path.join(default_save_dir, "download_history.txt")
dialog_layout .addLayout (bottom_button_layout )
filepath, _ = QFileDialog.getSaveFileName(
self,
self._tr("history_save_dialog_title", "Save Download History"),
default_filepath,
"Text Files (*.txt);;All Files (*)"
if self .parent_app and hasattr (self .parent_app ,'get_dark_theme')and self .parent_app .current_theme =="dark":
self .setStyleSheet (self .parent_app .get_dark_theme ())
def _tr (self ,key ,default_text =""):
if callable (get_translation )and self .parent_app :
return get_translation (self .parent_app .current_selected_language ,key ,default_text )
return default_text
def _save_history_to_txt (self ):
if not self .last_3_downloaded_entries and not self .first_processed_entries :
QMessageBox .information (self ,self ._tr ("no_download_history_header","No Downloads Yet"),
self ._tr ("history_nothing_to_save_message","There is no history to save."))
return
main_download_dir =self .parent_app .dir_input .text ().strip ()
default_save_dir =""
if main_download_dir and os .path .isdir (main_download_dir ):
default_save_dir =main_download_dir
else :
fallback_dir =QStandardPaths .writableLocation (QStandardPaths .DocumentsLocation )
if fallback_dir and os .path .isdir (fallback_dir ):
default_save_dir =fallback_dir
else :
default_save_dir =self .parent_app .app_base_dir
default_filepath =os .path .join (default_save_dir ,"download_history.txt")
filepath ,_ =QFileDialog .getSaveFileName (
self ,self ._tr ("history_save_dialog_title","Save Download History"),
default_filepath ,"Text Files (*.txt);;All Files (*)"
)
if not filepath:
return
if not filepath :
return
# Build the text content
history_content = []
# ... logic for formatting the text content would go here ...
history_content =[]
history_content .append (f"{self ._tr ('history_last_downloaded_header','Last 3 Files Downloaded:')}\n")
if self .last_3_downloaded_entries :
for entry in self .last_3_downloaded_entries :
history_content .append (f" {self ._tr ('history_file_label','File:')} {entry .get ('disk_filename','N/A')}")
history_content .append (f" {self ._tr ('history_from_post_label','From Post:')} {entry .get ('post_title','N/A')} (ID: {entry .get ('post_id','N/A')})")
history_content .append (f" {self ._tr ('history_creator_series_label','Creator/Series:')} {entry .get ('creator_display_name','N/A')}")
history_content .append (f" {self ._tr ('history_post_uploaded_label','Post Uploaded:')} {entry .get ('upload_date_str','N/A')}")
history_content .append (f" {self ._tr ('history_file_downloaded_label','File Downloaded:')} {time .strftime ('%Y-%m-%d %H:%M:%S',time .localtime (entry .get ('download_timestamp',0 )))}")
history_content .append (f" {self ._tr ('history_saved_in_folder_label','Saved In Folder:')} {entry .get ('download_path','N/A')}\n")
else :
history_content .append (f" ({self ._tr ('no_download_history_header','No Downloads Yet')})\n")
try:
with open(filepath, 'w', encoding='utf-8') as f:
f.write("\n".join(history_content))
QMessageBox.information(
self,
self._tr("history_export_success_title", "History Export Successful"),
self._tr("history_export_success_message", "Successfully exported to:\n{filepath}").format(filepath=filepath)
)
except Exception as e:
QMessageBox.critical(
self,
self._tr("history_export_error_title", "History Export Error"),
self._tr("history_export_error_message", "Could not export: {error}").format(error=str(e))
)
history_content .append (f"\n{self ._tr ('first_files_processed_header','First {count} Posts Processed This Session:').format (count =len (self .first_processed_entries ))}\n")
if self .first_processed_entries :
for entry in self .first_processed_entries :
history_content .append (f" {self ._tr ('history_post_label','Post:')} {entry .get ('post_title','N/A')} (ID: {entry .get ('post_id','N/A')})")
history_content .append (f" {self ._tr ('history_creator_label','Creator:')} {entry .get ('creator_name','N/A')}")
history_content .append (f" {self ._tr ('history_top_file_label','Top File:')} {entry .get ('top_file_name','N/A')}")
history_content .append (f" {self ._tr ('history_num_files_label','Num Files in Post:')} {entry .get ('num_files',0 )}")
history_content .append (f" {self ._tr ('history_post_uploaded_label','Post Uploaded:')} {entry .get ('upload_date_str','N/A')}")
history_content .append (f" {self ._tr ('history_processed_on_label','Processed On:')} {time .strftime ('%Y-%m-%d %H:%M:%S',time .localtime (entry .get ('download_date_timestamp',0 )))}")
history_content .append (f" {self ._tr ('history_saved_to_folder_label','Saved To Folder:')} {entry .get ('download_location','N/A')}\n")
else :
history_content .append (f" ({self ._tr ('no_processed_history_header','No Posts Processed Yet')})\n")
try :
with open (filepath ,'w',encoding ='utf-8')as f :
f .write ("\n".join (history_content ))
QMessageBox .information (self ,self ._tr ("history_export_success_title","History Export Successful"),
self ._tr ("history_export_success_message","Successfully exported download history to:\n{filepath}").format (filepath =filepath ))
except Exception as e :
QMessageBox .critical (self ,self ._tr ("history_export_error_title","History Export Error"),
self ._tr ("history_export_error_message","Could not export download history: {error}").format (error =str (e )))

View File

@@ -4825,124 +4825,254 @@ class DownloaderApp (QWidget ):
self .log_verbosity_toggle_button .setToolTip ("Current View: Progress Log. Click to switch to Missed Character Log.")
if self .progress_log_label :self .progress_log_label .setText (self ._tr ("progress_log_label_text","📜 Progress Log:"))
def reset_application_state (self ):
if self ._is_download_active ():QMessageBox .warning (self ,"Reset Error","Cannot reset while a download is in progress. Please cancel first.");return
self .log_signal .emit ("🔄 Resetting application state to defaults...");self ._reset_ui_to_defaults ()
self .main_log_output .clear ();self .external_log_output .clear ()
if self .missed_character_log_output :self .missed_character_log_output .clear ()
self .current_log_view ='progress'
if self .log_view_stack :self .log_view_stack .setCurrentIndex (0 )
if self .progress_log_label :self .progress_log_label .setText (self ._tr ("progress_log_label_text","📜 Progress Log:"))
if self .log_verbosity_toggle_button :
self .log_verbosity_toggle_button .setText (self .EYE_ICON )
self .log_verbosity_toggle_button .setToolTip ("Current View: Progress Log. Click to switch to Missed Character Log.")
if self .show_external_links and not (self .radio_only_links and self .radio_only_links .isChecked ()):self .external_log_output .append ("🔗 External Links Found:")
self .external_link_queue .clear ();self .extracted_links_cache =[];self ._is_processing_external_link_queue =False ;self ._current_link_post_title =None
self .progress_label .setText (self ._tr ("progress_idle_text","Progress: Idle"));self .file_progress_label .setText ("")
with self .downloaded_files_lock :count =len (self .downloaded_files );self .downloaded_files .clear ();
self .missed_title_key_terms_count .clear ()
self .missed_title_key_terms_examples .clear ()
self .logged_summary_for_key_term .clear ()
self .already_logged_bold_key_terms .clear ()
self .missed_key_terms_buffer .clear ()
self .favorite_download_queue .clear ()
self .only_links_log_display_mode =LOG_DISPLAY_LINKS
self .mega_download_log_preserved_once =False
self .permanently_failed_files_for_dialog .clear ()
self .favorite_download_scope =FAVORITE_SCOPE_SELECTED_LOCATION
self ._update_favorite_scope_button_text ()
self .retryable_failed_files_info .clear ()
self .cancellation_message_logged_this_session =False
self .is_processing_favorites_queue =False
if count >0 :self .log_signal .emit (f" Cleared {count } downloaded filename(s) from session memory.")
with self .downloaded_file_hashes_lock :count =len (self .downloaded_file_hashes );self .downloaded_file_hashes .clear ();
if count >0 :self .log_signal .emit (f" Cleared {count } downloaded file hash(es) from session memory.")
self .total_posts_to_process =0 ;self .processed_posts_count =0 ;self .download_counter =0 ;self .skip_counter =0
self .all_kept_original_filenames =[]
self .cancellation_event .clear ()
if self .pause_event :self .pause_event .clear ()
self .is_paused =False
self .manga_filename_style =STYLE_POST_TITLE
self .settings .setValue (MANGA_FILENAME_STYLE_KEY ,self .manga_filename_style )
self .skip_words_scope =SKIP_SCOPE_POSTS
self .settings .setValue (SKIP_WORDS_SCOPE_KEY ,self .skip_words_scope )
self ._update_skip_scope_button_text ()
self .char_filter_scope =CHAR_SCOPE_TITLE
self ._update_char_filter_scope_button_text ()
self .settings .sync ()
self ._update_manga_filename_style_button_text ()
self .update_ui_for_manga_mode (self .manga_mode_checkbox .isChecked ()if self .manga_mode_checkbox else False )
def _reset_ui_to_defaults (self ):
self .link_input .clear ();self .dir_input .clear ();self .custom_folder_input .clear ();self .character_input .clear ();
self .skip_words_input .clear ();self .start_page_input .clear ();self .end_page_input .clear ();self .new_char_input .clear ();
if hasattr (self ,'remove_from_filename_input'):self .remove_from_filename_input .clear ()
self .character_search_input .clear ();self .thread_count_input .setText ("4");self .radio_all .setChecked (True );
self .skip_zip_checkbox .setChecked (True );self .skip_rar_checkbox .setChecked (True );self .download_thumbnails_checkbox .setChecked (False );
self .compress_images_checkbox .setChecked (False );self .use_subfolders_checkbox .setChecked (True );
self .use_subfolder_per_post_checkbox .setChecked (False );self .use_multithreading_checkbox .setChecked (True );
if self .favorite_mode_checkbox :self .favorite_mode_checkbox .setChecked (False )
self .external_links_checkbox .setChecked (False )
if self .manga_mode_checkbox :self .manga_mode_checkbox .setChecked (False )
if hasattr (self ,'use_cookie_checkbox'):self .use_cookie_checkbox .setChecked (False )
self .selected_cookie_filepath =None
if hasattr (self ,'cookie_text_input'):self .cookie_text_input .clear ()
self .missed_title_key_terms_count .clear ()
self .missed_title_key_terms_examples .clear ()
self .logged_summary_for_key_term .clear ()
self .already_logged_bold_key_terms .clear ()
if hasattr (self ,'manga_date_prefix_input'):self .manga_date_prefix_input .clear ()
if self .pause_event :self .pause_event .clear ()
self .is_paused =False
self .missed_key_terms_buffer .clear ()
if self .download_extracted_links_button :
self .only_links_log_display_mode =LOG_DISPLAY_LINKS
self .cancellation_message_logged_this_session =False
self .mega_download_log_preserved_once =False
self .download_extracted_links_button .setEnabled (False )
if self .missed_character_log_output :self .missed_character_log_output .clear ()
self .permanently_failed_files_for_dialog .clear ()
self .allow_multipart_download_setting =False
self ._update_multipart_toggle_button_text ()
self .skip_words_scope =SKIP_SCOPE_POSTS
self ._update_skip_scope_button_text ()
self .char_filter_scope =CHAR_SCOPE_TITLE
self ._update_char_filter_scope_button_text ()
self .current_log_view ='progress'
self ._update_cookie_input_visibility (False );self ._update_cookie_input_placeholders_and_tooltips ()
if self .log_view_stack :self .log_view_stack .setCurrentIndex (0 )
if self .progress_log_label :self .progress_log_label .setText ("📜 Progress Log:")
if self .progress_log_label :self .progress_log_label .setText (self ._tr ("progress_log_label_text","📜 Progress Log:"))
self ._handle_filter_mode_change (self .radio_all ,True )
self ._handle_multithreading_toggle (self .use_multithreading_checkbox .isChecked ())
self .filter_character_list ("")
self .download_btn .setEnabled (True );self .cancel_btn .setEnabled (False )
if self .reset_button :self .reset_button .setEnabled (True );self .reset_button .setText (self ._tr ("reset_button_text","🔄 Reset"));self .reset_button .setToolTip (self ._tr ("reset_button_tooltip","Reset all inputs and logs to default state (only when idle)."))
if self .log_verbosity_toggle_button :
self .log_verbosity_toggle_button .setText (self .EYE_ICON )
self .log_verbosity_toggle_button .setToolTip ("Current View: Progress Log. Click to switch to Missed Character Log.")
self ._update_manga_filename_style_button_text ()
self .update_ui_for_manga_mode (False )
if hasattr (self ,'favorite_mode_checkbox'):
self ._handle_favorite_mode_toggle (False )
if hasattr (self ,'scan_content_images_checkbox'):
self .scan_content_images_checkbox .setChecked (False )
if hasattr (self ,'download_thumbnails_checkbox'):
self ._handle_thumbnail_mode_change (self .download_thumbnails_checkbox .isChecked ())
def reset_application_state(self):
# --- Stop all background tasks and threads ---
if self._is_download_active():
# Try to cancel download thread
if self.download_thread and self.download_thread.isRunning():
self.log_signal.emit("⚠️ Cancelling active download thread for reset...")
self.cancellation_event.set()
self.download_thread.requestInterruption()
self.download_thread.wait(3000)
if self.download_thread.isRunning():
self.log_signal.emit(" ⚠️ Download thread did not terminate gracefully.")
self.download_thread.deleteLater()
self.download_thread = None
# Try to cancel thread pool
if self.thread_pool:
self.log_signal.emit(" Shutting down thread pool for reset...")
self.thread_pool.shutdown(wait=True, cancel_futures=True)
self.thread_pool = None
self.active_futures = []
# Try to cancel external link download thread
if self.external_link_download_thread and self.external_link_download_thread.isRunning():
self.log_signal.emit(" Cancelling external link download thread for reset...")
self.external_link_download_thread.cancel()
self.external_link_download_thread.wait(3000)
self.external_link_download_thread.deleteLater()
self.external_link_download_thread = None
# Try to cancel retry thread pool
if hasattr(self, 'retry_thread_pool') and self.retry_thread_pool:
self.log_signal.emit(" Shutting down retry thread pool for reset...")
self.retry_thread_pool.shutdown(wait=True)
self.retry_thread_pool = None
if hasattr(self, 'active_retry_futures'):
self.active_retry_futures.clear()
if hasattr(self, 'active_retry_futures_map'):
self.active_retry_futures_map.clear()
self.cancellation_event.clear()
if self.pause_event:
self.pause_event.clear()
self.is_paused = False
# --- Reset UI and all state ---
self.log_signal.emit("🔄 Resetting application state to defaults...")
self._reset_ui_to_defaults()
self.main_log_output.clear()
self.external_log_output.clear()
if self.missed_character_log_output:
self.missed_character_log_output.clear()
self.current_log_view = 'progress'
if self.log_view_stack:
self.log_view_stack.setCurrentIndex(0)
if self.progress_log_label:
self.progress_log_label.setText(self._tr("progress_log_label_text", "📜 Progress Log:"))
if self.log_verbosity_toggle_button:
self.log_verbosity_toggle_button.setText(self.EYE_ICON)
self.log_verbosity_toggle_button.setToolTip("Current View: Progress Log. Click to switch to Missed Character Log.")
# Clear all download-related state
self.external_link_queue.clear()
self.extracted_links_cache = []
self._is_processing_external_link_queue = False
self._current_link_post_title = None
self.progress_label.setText(self._tr("progress_idle_text", "Progress: Idle"))
self.file_progress_label.setText("")
with self.downloaded_files_lock:
self.downloaded_files.clear()
with self.downloaded_file_hashes_lock:
self.downloaded_file_hashes.clear()
self.missed_title_key_terms_count.clear()
self.missed_title_key_terms_examples.clear()
self.logged_summary_for_key_term.clear()
self.already_logged_bold_key_terms.clear()
self.missed_key_terms_buffer.clear()
self.favorite_download_queue.clear()
self.only_links_log_display_mode = LOG_DISPLAY_LINKS
self.mega_download_log_preserved_once = False
self.permanently_failed_files_for_dialog.clear()
self.favorite_download_scope = FAVORITE_SCOPE_SELECTED_LOCATION
self._update_favorite_scope_button_text()
self.retryable_failed_files_info.clear()
self.cancellation_message_logged_this_session = False
self.is_processing_favorites_queue = False
self.total_posts_to_process = 0
self.processed_posts_count = 0
self.download_counter = 0
self.skip_counter = 0
self.all_kept_original_filenames = []
self.is_paused = False
self.is_fetcher_thread_running = False
self.interrupted_session_data = None
self.is_restore_pending = False
self.settings.setValue(MANGA_FILENAME_STYLE_KEY, self.manga_filename_style)
self.settings.setValue(SKIP_WORDS_SCOPE_KEY, self.skip_words_scope)
self.settings.sync()
self._update_manga_filename_style_button_text()
self.update_ui_for_manga_mode(self.manga_mode_checkbox.isChecked() if self.manga_mode_checkbox else False)
self.set_ui_enabled(True)
self.log_signal.emit("✅ Application fully reset. Ready for new download.")
self.is_processing_favorites_queue = False
self.current_processing_favorite_item_info = None
self.favorite_download_queue.clear()
self.interrupted_session_data = None
self.is_restore_pending = False
self.last_link_input_text_for_queue_sync = ""
# Replace your current reset_application_state with the above.
def _reset_ui_to_defaults(self):
"""Resets all UI elements and relevant state to their default values."""
# Clear all text fields
self.link_input.clear()
self.dir_input.clear()
self.custom_folder_input.clear()
self.character_input.clear()
self.skip_words_input.clear()
self.start_page_input.clear()
self.end_page_input.clear()
self.new_char_input.clear()
if hasattr(self, 'remove_from_filename_input'):
self.remove_from_filename_input.clear()
self.character_search_input.clear()
self.thread_count_input.setText("4")
if hasattr(self, 'manga_date_prefix_input'):
self.manga_date_prefix_input.clear()
# Set radio buttons and checkboxes to defaults
self.radio_all.setChecked(True)
self.skip_zip_checkbox.setChecked(True)
self.skip_rar_checkbox.setChecked(True)
self.download_thumbnails_checkbox.setChecked(False)
self.compress_images_checkbox.setChecked(False)
self.use_subfolders_checkbox.setChecked(True)
self.use_subfolder_per_post_checkbox.setChecked(False)
self.use_multithreading_checkbox.setChecked(True)
if self.favorite_mode_checkbox:
self.favorite_mode_checkbox.setChecked(False)
self.external_links_checkbox.setChecked(False)
if self.manga_mode_checkbox:
self.manga_mode_checkbox.setChecked(False)
if hasattr(self, 'use_cookie_checkbox'):
self.use_cookie_checkbox.setChecked(False)
self.selected_cookie_filepath = None
if hasattr(self, 'cookie_text_input'):
self.cookie_text_input.clear()
# Reset log and progress displays
if self.main_log_output:
self.main_log_output.clear()
if self.external_log_output:
self.external_log_output.clear()
if self.missed_character_log_output:
self.missed_character_log_output.clear()
self.progress_label.setText(self._tr("progress_idle_text", "Progress: Idle"))
self.file_progress_label.setText("")
# Reset internal state
self.missed_title_key_terms_count.clear()
self.missed_title_key_terms_examples.clear()
self.logged_summary_for_key_term.clear()
self.already_logged_bold_key_terms.clear()
self.missed_key_terms_buffer.clear()
self.permanently_failed_files_for_dialog.clear()
self.only_links_log_display_mode = LOG_DISPLAY_LINKS
self.cancellation_message_logged_this_session = False
self.mega_download_log_preserved_once = False
self.allow_multipart_download_setting = False
self.skip_words_scope = SKIP_SCOPE_POSTS
self.char_filter_scope = CHAR_SCOPE_TITLE
self.manga_filename_style = STYLE_POST_TITLE
self.favorite_download_scope = FAVORITE_SCOPE_SELECTED_LOCATION
self._update_skip_scope_button_text()
self._update_char_filter_scope_button_text()
self._update_manga_filename_style_button_text()
self._update_multipart_toggle_button_text()
self._update_favorite_scope_button_text()
self.current_log_view = 'progress'
self.is_paused = False
if self.pause_event:
self.pause_event.clear()
# Reset extracted/external links state
self.external_link_queue.clear()
self.extracted_links_cache = []
self._is_processing_external_link_queue = False
self._current_link_post_title = None
if self.download_extracted_links_button:
self.download_extracted_links_button.setEnabled(False)
# Reset favorite/queue/session state
self.favorite_download_queue.clear()
self.is_processing_favorites_queue = False
self.current_processing_favorite_item_info = None
self.interrupted_session_data = None
self.is_restore_pending = False
self.last_link_input_text_for_queue_sync = ""
self._update_button_states_and_connections()
# Reset counters and progress
self.total_posts_to_process = 0
self.processed_posts_count = 0
self.download_counter = 0
self.skip_counter = 0
self.all_kept_original_filenames = []
# Reset log view and UI state
if self.log_view_stack:
self.log_view_stack.setCurrentIndex(0)
if self.progress_log_label:
self.progress_log_label.setText(self._tr("progress_log_label_text", "📜 Progress Log:"))
if self.log_verbosity_toggle_button:
self.log_verbosity_toggle_button.setText(self.EYE_ICON)
self.log_verbosity_toggle_button.setToolTip("Current View: Progress Log. Click to switch to Missed Character Log.")
# Reset character list filter
self.filter_character_list("")
# Update UI for manga mode and multithreading
self._handle_multithreading_toggle(self.use_multithreading_checkbox.isChecked())
self.update_ui_for_manga_mode(False)
self.update_custom_folder_visibility(self.link_input.text())
self.update_page_range_enabled_state()
self._update_cookie_input_visibility(False)
self._update_cookie_input_placeholders_and_tooltips()
# Reset button states
self.download_btn.setEnabled(True)
self.cancel_btn.setEnabled(False)
if self.reset_button:
self.reset_button.setEnabled(True)
self.reset_button.setText(self._tr("reset_button_text", "🔄 Reset"))
self.reset_button.setToolTip(self._tr("reset_button_tooltip", "Reset all inputs and logs to default state (only when idle)."))
# Reset favorite mode UI
if hasattr(self, 'favorite_mode_checkbox'):
self._handle_favorite_mode_toggle(False)
if hasattr(self, 'scan_content_images_checkbox'):
self.scan_content_images_checkbox.setChecked(False)
if hasattr(self, 'download_thumbnails_checkbox'):
self._handle_thumbnail_mode_change(self.download_thumbnails_checkbox.isChecked())
self.set_ui_enabled(True)
self.log_signal.emit("✅ UI reset to defaults. Ready for new operation.")
self._update_button_states_and_connections()
def _show_feature_guide (self ):
steps_content_keys =[
("help_guide_step1_title","help_guide_step1_content"),