From 9e58a9d574345e4a9fc542ffe82e10ffb9d445c9 Mon Sep 17 00:00:00 2001 From: Yuvi9587 <114073886+Yuvi9587@users.noreply.github.com> Date: Tue, 15 Jul 2025 08:49:20 -0700 Subject: [PATCH] commit --- src/core/workers.py | 41 +++++++++++---- src/ui/dialogs/SupportDialog.py | 93 +++++++++++++++++++++++++++++++++ src/ui/main_window.py | 21 +++++--- src/utils/resolution.py | 4 ++ 4 files changed, 141 insertions(+), 18 deletions(-) create mode 100644 src/ui/dialogs/SupportDialog.py diff --git a/src/core/workers.py b/src/core/workers.py index 9d2a0f3..43a4bc1 100644 --- a/src/core/workers.py +++ b/src/core/workers.py @@ -556,17 +556,34 @@ class PostProcessorWorker: if self._check_pause(f"Post-download hash check for '{api_original_filename}'"): return 0, 1, filename_to_save_in_main_path, was_original_name_kept_flag, FILE_DOWNLOAD_STATUS_SKIPPED, None + ### START OF CHANGE 1: INSERT THIS NEW BLOCK ### + with self.downloaded_file_hashes_lock: + if calculated_file_hash in self.downloaded_file_hashes: + self.logger(f" -> Skip (Content Duplicate): '{api_original_filename}' is identical to a file already downloaded. Discarding.") + # Clean up the downloaded temporary file as it's a duplicate. + if downloaded_part_file_path and os.path.exists(downloaded_part_file_path): + try: + os.remove(downloaded_part_file_path) + except OSError: + pass + return 0, 1, filename_to_save_in_main_path, was_original_name_kept_flag, FILE_DOWNLOAD_STATUS_SKIPPED, None + + # If the content is unique, we proceed to save. + # Now, handle FILENAME collisions by adding a numeric suffix if needed. effective_save_folder = target_folder_path - # ... (History check and other pre-save logic would be here) ... - - final_filename_on_disk = filename_to_save_in_main_path # This may be adjusted by collision logic - - # This is a placeholder for your collision and final filename logic - # For example: - # final_filename_on_disk = self._handle_collisions(target_folder_path, filename_to_save_in_main_path) - - + base_name, extension = os.path.splitext(filename_to_save_in_main_path) + counter = 1 + final_filename_on_disk = filename_to_save_in_main_path final_save_path = os.path.join(effective_save_folder, final_filename_on_disk) + + while os.path.exists(final_save_path): + final_filename_on_disk = f"{base_name}_{counter}{extension}" + final_save_path = os.path.join(effective_save_folder, final_filename_on_disk) + counter += 1 + + if counter > 1: + self.logger(f" ⚠️ Filename collision: Saving as '{final_filename_on_disk}' instead.") + try: if data_to_write_io: with open(final_save_path, 'wb') as f_out: @@ -584,8 +601,10 @@ class PostProcessorWorker: else: raise FileNotFoundError(f"Original .part file not found for saving: {downloaded_part_file_path}") - with self.downloaded_file_hashes_lock: self.downloaded_file_hashes.add(calculated_file_hash) - with self.downloaded_files_lock: self.downloaded_files.add(filename_to_save_in_main_path) + with self.downloaded_file_hashes_lock: + self.downloaded_file_hashes.add(calculated_file_hash) + with self.downloaded_files_lock: + self.downloaded_files.add(final_filename_on_disk) final_filename_saved_for_return = final_filename_on_disk self.logger(f"✅ Saved: '{final_filename_saved_for_return}' (from '{api_original_filename}', {downloaded_size_bytes / (1024 * 1024):.2f} MB) in '{os.path.basename(effective_save_folder)}'") diff --git a/src/ui/dialogs/SupportDialog.py b/src/ui/dialogs/SupportDialog.py new file mode 100644 index 0000000..6f219df --- /dev/null +++ b/src/ui/dialogs/SupportDialog.py @@ -0,0 +1,93 @@ +# src/ui/dialogs/SupportDialog.py + +from PyQt5.QtWidgets import ( + QDialog, QVBoxLayout, QLabel, QFrame, QDialogButtonBox +) +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QFont + +# Assuming execution from project root, so we can import from utils +from ...utils.resolution import get_dark_theme + +class SupportDialog(QDialog): + """ + A dialog to show support and donation options. + """ + def __init__(self, parent=None): + super().__init__(parent) + self.parent_app = parent + self.setWindowTitle("❤️ Support the Developer") + self.setMinimumWidth(400) + + # Main layout + layout = QVBoxLayout(self) + layout.setSpacing(15) + + # Title Label + title_label = QLabel("Thank You for Your Support!") + font = title_label.font() + font.setPointSize(14) + font.setBold(True) + title_label.setFont(font) + title_label.setAlignment(Qt.AlignCenter) + layout.addWidget(title_label) + + # Informational Text + info_label = QLabel( + "If you find this application useful, please consider supporting its development. " + "Your contribution helps cover costs and encourages future updates and features." + ) + info_label.setWordWrap(True) + info_label.setAlignment(Qt.AlignCenter) + layout.addWidget(info_label) + + # Separator + line = QFrame() + line.setFrameShape(QFrame.HLine) + line.setFrameShadow(QFrame.Sunken) + layout.addWidget(line) + + # Donation Options + options_layout = QVBoxLayout() + options_layout.setSpacing(10) + + # --- Ko-fi --- + kofi_label = QLabel( + '' + '☕ Buy me a Ko-fi' + '' + ) + kofi_label.setOpenExternalLinks(True) + kofi_label.setAlignment(Qt.AlignCenter) + font.setPointSize(12) + kofi_label.setFont(font) + options_layout.addWidget(kofi_label) + + # --- GitHub Sponsors --- + github_label = QLabel( + '' + '💜 Sponsor on GitHub' + '' + ) + github_label.setOpenExternalLinks(True) + github_label.setAlignment(Qt.AlignCenter) + github_label.setFont(font) + options_layout.addWidget(github_label) + + layout.addLayout(options_layout) + + # Close Button + self.button_box = QDialogButtonBox(QDialogButtonBox.Close) + self.button_box.rejected.connect(self.reject) + layout.addWidget(self.button_box) + + self.setLayout(layout) + self._apply_theme() + + def _apply_theme(self): + """Applies the current theme from the parent application.""" + if self.parent_app and hasattr(self.parent_app, 'current_theme') and self.parent_app.current_theme == "dark": + scale = getattr(self.parent_app, 'scale_factor', 1) + self.setStyleSheet(get_dark_theme(scale)) + else: + self.setStyleSheet("") diff --git a/src/ui/main_window.py b/src/ui/main_window.py index 8a8f4da..91768b8 100644 --- a/src/ui/main_window.py +++ b/src/ui/main_window.py @@ -56,6 +56,7 @@ from .dialogs.FavoriteArtistsDialog import FavoriteArtistsDialog from .dialogs.ConfirmAddAllDialog import ConfirmAddAllDialog from .dialogs.MoreOptionsDialog import MoreOptionsDialog from .dialogs.SinglePDF import create_single_pdf_from_content +from .dialogs.SupportDialog import SupportDialog class DynamicFilterHolder: """A thread-safe class to hold and update character filters during a download.""" @@ -735,6 +736,8 @@ class DownloaderApp (QWidget ): self .history_button .clicked .connect (self ._show_download_history_dialog ) if hasattr (self ,'error_btn'): self .error_btn .clicked .connect (self ._show_error_files_dialog ) + if hasattr(self, 'support_button'): + self.support_button.clicked.connect(self._show_support_dialog) def _on_character_input_changed_live (self ,text ): """ @@ -1311,6 +1314,11 @@ class DownloaderApp (QWidget ): dialog = FutureSettingsDialog(self) dialog.exec_() + def _show_support_dialog(self): + """Shows the support/donation dialog.""" + dialog = SupportDialog(self) + dialog.exec_() + def _check_if_all_work_is_done(self): """ Checks if the fetcher thread is done AND if all submitted tasks have been processed. @@ -1732,7 +1740,7 @@ class DownloaderApp (QWidget ): if self .log_splitter :self .log_splitter .setSizes ([self .height ()//2 ,self .height ()//2 ]) if self .main_log_output :self .main_log_output .setMinimumHeight (50 ) if self .external_log_output :self .external_log_output .setMinimumHeight (50 ) - self .log_signal .emit ("\n"+"="*40 +"\n🔗 External Links Log Enabled\n"+"="*40 ) + self.log_signal.emit("ℹ️ External Links Log Enabled") if self .external_log_output : self .external_log_output .clear () self .external_log_output .append ("🔗 External Links Found:") @@ -1743,8 +1751,7 @@ class DownloaderApp (QWidget ): if self .main_log_output :self .main_log_output .setMinimumHeight (0 ) if self .external_log_output :self .external_log_output .setMinimumHeight (0 ) if self .external_log_output :self .external_log_output .clear () - self .log_signal .emit ("\n"+"="*40 +"\n🔗 External Links Log Disabled\n"+"="*40 ) - + self.log_signal.emit("ℹ️ External Links Log Disabled") def _handle_filter_mode_change(self, button, checked): # If a button other than "More" is selected, reset the UI @@ -1833,24 +1840,24 @@ class DownloaderApp (QWidget ): self .log_signal .emit ("INTERNAL: _handle_filter_mode_change - Log cleared by _handle_filter_mode_change.") if self .main_log_output :self .main_log_output .setMinimumHeight (0 ) - self .log_signal .emit ("="*20 +" Mode changed to: Only Links "+"="*20 ) + self.log_signal.emit(f"ℹ️ Filter mode changed to: {button.text()}") self ._try_process_next_external_link () elif is_only_archives : self .progress_log_label .setText ("📜 Progress Log (Archives Only):") if self .external_log_output :self .external_log_output .hide () if self .log_splitter :self .log_splitter .setSizes ([self .height (),0 ]) if self .main_log_output :self .main_log_output .clear () - self .log_signal .emit ("="*20 +" Mode changed to: Only Archives "+"="*20 ) + self.log_signal.emit(f"ℹ️ Filter mode changed to: {button.text()}") elif is_only_audio : self .progress_log_label .setText (self ._tr ("progress_log_label_text","📜 Progress Log:")+f" ({self ._tr ('filter_audio_radio','🎧 Only Audio')})") if self .external_log_output :self .external_log_output .hide () if self .log_splitter :self .log_splitter .setSizes ([self .height (),0 ]) if self .main_log_output :self .main_log_output .clear () - self .log_signal .emit ("="*20 +f" Mode changed to: {self ._tr ('filter_audio_radio','🎧 Only Audio')} "+"="*20 ) + self.log_signal.emit(f"ℹ️ Filter mode changed to: {button.text()}") else : self .progress_log_label .setText (self ._tr ("progress_log_label_text","📜 Progress Log:")) self .update_external_links_setting (self .external_links_checkbox .isChecked ()if self .external_links_checkbox else False ) - self .log_signal .emit (f"="*20 +f" Mode changed to: {button .text ()} "+"="*20 ) + self.log_signal.emit(f"ℹ️ Filter mode changed to: {button.text()}") if is_only_links : diff --git a/src/utils/resolution.py b/src/utils/resolution.py index 949c2a6..1c07622 100644 --- a/src/utils/resolution.py +++ b/src/utils/resolution.py @@ -355,11 +355,15 @@ def setup_ui(main_app): main_app.future_settings_button = QPushButton("⚙️") main_app.future_settings_button.setFixedWidth(45 * scale) main_app.future_settings_button.clicked.connect(main_app._show_future_settings_dialog) + main_app.support_button = QPushButton("❤️ Support") + main_app.support_button.setFixedWidth(100 * scale) + main_app.support_button.setToolTip("Support the application developer.") char_manage_layout.addWidget(main_app.add_to_filter_button, 1) char_manage_layout.addWidget(main_app.delete_char_button, 1) char_manage_layout.addWidget(main_app.known_names_help_button, 0) char_manage_layout.addWidget(main_app.history_button, 0) char_manage_layout.addWidget(main_app.future_settings_button, 0) + char_manage_layout.addWidget(main_app.support_button, 0) left_layout.addLayout(char_manage_layout) left_layout.addStretch(0)