This commit is contained in:
Yuvi9587 2025-06-19 08:25:30 +01:00
parent 3c1b361fc1
commit 191dbc8c62
3 changed files with 154 additions and 25 deletions

View File

@ -1689,7 +1689,48 @@ class PostProcessorWorker :
if not self .extract_links_only and self .use_post_subfolders :
cleaned_post_title_for_sub =clean_folder_name (post_title )
determined_post_save_path_for_history =os .path .join (determined_post_save_path_for_history ,cleaned_post_title_for_sub )
post_id_for_fallback = self.post.get('id', 'unknown_id') # Ensure post_id is available
# Fallback to a more unique name if the cleaned title is generic
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.")
original_cleaned_post_title_for_sub = f"post_{post_id_for_fallback}"
else:
original_cleaned_post_title_for_sub = cleaned_post_title_for_sub
# Path before adding the post-specific subfolder
base_path_for_post_subfolder = determined_post_save_path_for_history
suffix_counter = 0 # 0 for no suffix, 1 for _1, etc.
final_post_subfolder_name = ""
while True:
if suffix_counter == 0:
name_candidate = original_cleaned_post_title_for_sub
else:
name_candidate = f"{original_cleaned_post_title_for_sub}_{suffix_counter}"
potential_post_subfolder_path = os.path.join(base_path_for_post_subfolder, name_candidate)
try:
os.makedirs(potential_post_subfolder_path, exist_ok=False)
final_post_subfolder_name = name_candidate
if suffix_counter > 0: # Log only if a suffix was actually needed and used
self.logger(f" Post subfolder name conflict: Using '{final_post_subfolder_name}' instead of '{original_cleaned_post_title_for_sub}' to avoid mixing posts.")
break
except FileExistsError:
suffix_counter += 1
if suffix_counter > 100: # Safety break
self.logger(f" ⚠️ Exceeded 100 attempts to find unique subfolder name for '{original_cleaned_post_title_for_sub}'. Using UUID.")
final_post_subfolder_name = f"{original_cleaned_post_title_for_sub}_{uuid.uuid4().hex[:8]}"
os.makedirs(os.path.join(base_path_for_post_subfolder, final_post_subfolder_name), exist_ok=True) # Create with exist_ok=True as a last resort
break
except OSError as e_mkdir:
self.logger(f" ❌ Error creating directory '{potential_post_subfolder_path}': {e_mkdir}. Files for this post might be saved in parent or fail.")
final_post_subfolder_name = original_cleaned_post_title_for_sub # Fallback
break
determined_post_save_path_for_history = os.path.join(base_path_for_post_subfolder, final_post_subfolder_name)
if not self .extract_links_only and self .use_subfolders and self .skip_words_list :
if self ._check_pause (f"Folder keyword skip check for post {post_id }"):return 0 ,num_potential_files_in_post ,[],[],[],None
@ -1952,8 +1993,8 @@ class PostProcessorWorker :
if self .use_subfolders and target_base_folder_name_for_instance :
current_path_for_file_instance =os .path .join (current_path_for_file_instance ,target_base_folder_name_for_instance )
if self .use_post_subfolders :
cleaned_title_for_subfolder_instance =clean_folder_name (post_title )
current_path_for_file_instance =os .path .join (current_path_for_file_instance ,cleaned_title_for_subfolder_instance )
# Use the final_post_subfolder_name determined earlier, which includes suffix if needed
current_path_for_file_instance =os .path .join (current_path_for_file_instance ,final_post_subfolder_name )
manga_date_counter_to_pass =self .manga_date_file_counter_ref if self .manga_mode_active and self .manga_filename_style ==STYLE_DATE_BASED else None
manga_global_counter_to_pass =self .manga_global_file_counter_ref if self .manga_mode_active and self .manga_filename_style ==STYLE_POST_TITLE_GLOBAL_NUMBERING else None
@ -2021,6 +2062,22 @@ class PostProcessorWorker :
}
if self .check_cancel ():self .logger (f" Post {post_id } processing interrupted/cancelled.");
else :self .logger (f" Post {post_id } Summary: Downloaded={total_downloaded_this_post }, Skipped Files={total_skipped_this_post }")
# Cleanup: Remove empty post-specific subfolder if created and no files were downloaded
if not self.extract_links_only and self.use_post_subfolders and total_downloaded_this_post == 0:
# determined_post_save_path_for_history at this point holds the full path to the post-specific subfolder
# if self.use_post_subfolders was true and it was applied.
# base_path_for_post_subfolder was the path *before* the post-specific segment.
# final_post_subfolder_name was the segment itself.
# So, determined_post_save_path_for_history is the correct path to check.
path_to_check_for_emptiness = determined_post_save_path_for_history
try:
if os.path.isdir(path_to_check_for_emptiness) and not os.listdir(path_to_check_for_emptiness):
self.logger(f" 🗑️ Removing empty post-specific subfolder: '{path_to_check_for_emptiness}'")
os.rmdir(path_to_check_for_emptiness)
except OSError as e_rmdir:
self.logger(f" ⚠️ Could not remove empty post-specific subfolder '{path_to_check_for_emptiness}': {e_rmdir}")
return total_downloaded_this_post ,total_skipped_this_post ,kept_original_filenames_for_log ,retryable_failures_this_post ,permanent_failures_this_post ,history_data_for_this_post
class DownloadThread (QThread ):
progress_signal =pyqtSignal (str )

View File

@ -296,6 +296,22 @@ Output: '2023-01-15_ChapterOne.jpg', '2023-01-15_ChapterOne_1.png'""",
"download_external_links_dialog_title":"Download Selected External Links",
"select_all_button_text":"Select All",
"deselect_all_button_text":"Deselect All",
"deselect_all_button_text":"Deselect All", # Existing, but good to have for context
"settings_download_group_title": "Download Settings",
"settings_save_path_button": "Save Current Download Path",
"settings_save_path_tooltip": "Save the current 'Download Location' from the main window for future sessions.",
"settings_save_path_success_title": "Path Saved",
"settings_save_path_success_message": "Download location '{path}' saved successfully.",
"settings_save_path_invalid_title": "Invalid Path",
"settings_save_path_invalid_message": "The path '{path}' is not a valid directory. Please select a valid directory first.",
"settings_save_path_empty_title": "Empty Path",
"settings_save_path_empty_message": "Download location cannot be empty. Please select a path first.",
"settings_save_all_settings_button_text": "Save All Settings",
"settings_save_all_settings_button_tooltip": "Save all current application settings (download path, checkboxes, inputs, etc.).",
"settings_all_saved_success_title": "Settings Saved",
"settings_all_saved_success_message": "All application settings saved successfully.",
"settings_all_saved_error_title": "Save Error",
"settings_all_saved_error_message": "Could not save all application settings. Check the log for details.",
"cookie_browse_button_tooltip":"Browse for a cookie file (Netscape format, typically cookies.txt).\nThis will be used if 'Use Cookie' is checked and the text field above is empty."
,
"page_range_label_text":"Page Range:",

96
main.py
View File

@ -178,6 +178,7 @@ CHAR_FILTER_SCOPE_KEY ="charFilterScopeV1"
THEME_KEY ="currentThemeV2"
SCAN_CONTENT_IMAGES_KEY ="scanContentForImagesV1"
LANGUAGE_KEY ="currentLanguageV1"
DOWNLOAD_LOCATION_KEY ="downloadLocationV1"
CONFIRM_ADD_ALL_ACCEPTED =1
FAVORITE_SCOPE_SELECTED_LOCATION ="selected_location"
@ -719,6 +720,11 @@ class FutureSettingsDialog (QDialog ):
layout .addWidget (self .language_group_box )
self .save_path_button =QPushButton ()
self .save_path_button .clicked .connect (self ._save_download_path )
layout .addWidget (self .save_path_button )
layout .addStretch (1 )
self .ok_button =QPushButton ()
@ -740,6 +746,9 @@ class FutureSettingsDialog (QDialog ):
self .language_label .setText (self ._tr ("language_label","Language:"))
self ._update_theme_toggle_button_text ()
self ._populate_language_combo_box ()
if hasattr (self ,'save_path_button'):
self .save_path_button .setText (self ._tr ("settings_save_path_button","Save Current Download Path"))
self .save_path_button .setToolTip (self ._tr ("settings_save_path_tooltip","Save the current 'Download Location' from the main window for future sessions."))
self .ok_button .setText (self ._tr ("ok_button","OK"))
def _update_theme_toggle_button_text (self ):
if self .parent_app .current_theme =="dark":
@ -812,6 +821,29 @@ class FutureSettingsDialog (QDialog ):
if msg_box .clickedButton ()==restart_button :
self .parent_app ._request_restart_application ()
def _save_download_path (self ):
if self .parent_app and hasattr (self .parent_app ,'dir_input')and self .parent_app .dir_input :
current_path =self .parent_app .dir_input .text ().strip ()
if current_path :
if os .path .isdir (current_path ):
self .parent_app .settings .setValue (DOWNLOAD_LOCATION_KEY ,current_path )
self .parent_app .settings .sync ()
QMessageBox .information (self ,
self ._tr ("settings_save_path_success_title","Path Saved"),
self ._tr ("settings_save_path_success_message",f"Download location '{current_path }' saved successfully."))
if hasattr (self .parent_app ,'log_signal'):
self .parent_app .log_signal .emit (f"💾 Download location '{current_path }' saved.")
else :
QMessageBox .warning (self ,
self ._tr ("settings_save_path_invalid_title","Invalid Path"),
self ._tr ("settings_save_path_invalid_message",f"The path '{current_path }' is not a valid directory. Please select a valid directory first."))
else :
QMessageBox .warning (self ,
self ._tr ("settings_save_path_empty_title","Empty Path"),
self ._tr ("settings_save_path_empty_message","Download location cannot be empty. Please select a path first."))
else :
QMessageBox .critical (self ,"Error","Could not access download path input from main application.")
class EmptyPopupDialog (QDialog ):
"""A simple empty popup dialog."""
SCOPE_CHARACTERS ="Characters"
@ -1145,8 +1177,6 @@ class EmptyPopupDialog (QDialog ):
self .all_creators_data =[]
self .progress_bar .setVisible (False );QCoreApplication .processEvents ();return
self ._filter_list ()
def _populate_list_widget (self ,creators_to_display ):
@ -1219,16 +1249,8 @@ class EmptyPopupDialog (QDialog ):
norm_search_casefolded =unicodedata .normalize ('NFKC',raw_search_input ).casefold ().strip ()
scored_matches =[]
parsed_service_from_url ,parsed_user_id_from_url ,_ =extract_post_info (raw_search_input )
if parsed_service_from_url and parsed_user_id_from_url :
@ -2116,8 +2138,24 @@ class KnownNamesFilterDialog (QDialog ):
self ._retranslate_ui ()
self ._populate_list_widget ()
self .setMinimumWidth (350 )
self .setMinimumHeight (400 )
screen_geometry =QApplication .primaryScreen ().availableGeometry ()
base_width ,base_height =460 ,450
reference_screen_height =1080
scale_factor_w =screen_geometry .width ()/1920
scale_factor_h =screen_geometry .height ()/reference_screen_height
effective_scale_factor =max (0.75 ,min (scale_factor_h ,1.5 ))
self .setMinimumSize (int (base_width *effective_scale_factor ),int (base_height *effective_scale_factor ))
self .resize (int (base_width *effective_scale_factor *1.1 ),int (base_height *effective_scale_factor *1.1 ))
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 ())
self .add_button .setDefault (True )
@ -2137,6 +2175,12 @@ class KnownNamesFilterDialog (QDialog ):
self .cancel_button .setText (self ._tr ("fav_posts_cancel_button","Cancel"))
def _populate_list_widget (self ,names_to_display =None ):
if hasattr (self ,'_items_populated')and self ._items_populated and names_to_display is not None :
return
self .names_list_widget .clear ()
current_entries_source =names_to_display if names_to_display is not None else self .all_known_name_entries
for entry_obj in current_entries_source :
@ -2145,16 +2189,15 @@ class KnownNamesFilterDialog (QDialog ):
item .setCheckState (Qt .Unchecked )
item .setData (Qt .UserRole ,entry_obj )
self .names_list_widget .addItem (item )
self ._items_populated =True
def _filter_list_display (self ):
search_text =self .search_input .text ().lower ()
if not search_text :
self ._populate_list_widget ()
return
filtered_entries =[
entry_obj for entry_obj in self .all_known_name_entries if search_text in entry_obj ['name'].lower ()
]
self ._populate_list_widget (filtered_entries )
for i in range (self .names_list_widget .count ()):
item =self .names_list_widget .item (i )
entry_obj =item .data (Qt .UserRole )
matches_search =not search_text or search_text in entry_obj ['name'].lower ()
item .setHidden (not matches_search )
def _accept_selection_action (self ):
self .selected_entries_to_return =[]
@ -3747,6 +3790,7 @@ class DownloaderApp (QWidget ):
self .log_signal .emit (f" Application language loaded: '{self .current_selected_language .upper ()}' (UI may not reflect this yet).")
self ._retranslate_main_ui ()
self ._load_persistent_history ()
self ._load_saved_download_location ()
def _tr (self ,key ,default_text =""):
@ -3755,6 +3799,18 @@ class DownloaderApp (QWidget ):
return get_translation (self .current_selected_language ,key ,default_text )
return default_text
def _load_saved_download_location (self ):
saved_location =self .settings .value (DOWNLOAD_LOCATION_KEY ,"",type =str )
if saved_location and os .path .isdir (saved_location ):
if hasattr (self ,'dir_input')and self .dir_input :
self .dir_input .setText (saved_location )
self .log_signal .emit (f" Loaded saved download location: {saved_location }")
else :
self .log_signal .emit (f"⚠️ Found saved download location '{saved_location }', but dir_input not ready.")
elif saved_location :
self .log_signal .emit (f"⚠️ Found saved download location '{saved_location }', but it's not a valid directory. Ignoring.")
def _initialize_persistent_history_path (self ):
documents_path =QStandardPaths .writableLocation (QStandardPaths .DocumentsLocation )
if not documents_path :
@ -5990,7 +6046,7 @@ class DownloaderApp (QWidget ):
is_only_archives =self .radio_only_archives and self .radio_only_archives .isChecked ()
is_only_audio =hasattr (self ,'radio_only_audio')and self .radio_only_audio .isChecked ()
can_enable_subfolder_per_post_checkbox =not is_only_links and not is_only_archives
can_enable_subfolder_per_post_checkbox =not is_only_links
if self .use_subfolder_per_post_checkbox :
self .use_subfolder_per_post_checkbox .setEnabled (can_enable_subfolder_per_post_checkbox )