mirror of
https://github.com/Yuvi9587/Kemono-Downloader.git
synced 2025-12-29 16:14:44 +00:00
commit
This commit is contained in:
@@ -556,17 +556,34 @@ class PostProcessorWorker:
|
|||||||
if self._check_pause(f"Post-download hash check for '{api_original_filename}'"):
|
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
|
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
|
effective_save_folder = target_folder_path
|
||||||
# ... (History check and other pre-save logic would be here) ...
|
base_name, extension = os.path.splitext(filename_to_save_in_main_path)
|
||||||
|
counter = 1
|
||||||
final_filename_on_disk = filename_to_save_in_main_path # This may be adjusted by collision logic
|
final_filename_on_disk = filename_to_save_in_main_path
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
|
|
||||||
final_save_path = os.path.join(effective_save_folder, final_filename_on_disk)
|
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:
|
try:
|
||||||
if data_to_write_io:
|
if data_to_write_io:
|
||||||
with open(final_save_path, 'wb') as f_out:
|
with open(final_save_path, 'wb') as f_out:
|
||||||
@@ -584,8 +601,10 @@ class PostProcessorWorker:
|
|||||||
else:
|
else:
|
||||||
raise FileNotFoundError(f"Original .part file not found for saving: {downloaded_part_file_path}")
|
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_file_hashes_lock:
|
||||||
with self.downloaded_files_lock: self.downloaded_files.add(filename_to_save_in_main_path)
|
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
|
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)}'")
|
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)}'")
|
||||||
|
|||||||
93
src/ui/dialogs/SupportDialog.py
Normal file
93
src/ui/dialogs/SupportDialog.py
Normal file
@@ -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(
|
||||||
|
'<a href="https://ko-fi.com/yuvi427183" style="color: #13C2C2; text-decoration: none;">'
|
||||||
|
'☕ Buy me a Ko-fi'
|
||||||
|
'</a>'
|
||||||
|
)
|
||||||
|
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(
|
||||||
|
'<a href="https://github.com/sponsors/Yuvi9587" style="color: #C9D1D9; text-decoration: none;">'
|
||||||
|
'💜 Sponsor on GitHub'
|
||||||
|
'</a>'
|
||||||
|
)
|
||||||
|
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("")
|
||||||
@@ -56,6 +56,7 @@ from .dialogs.FavoriteArtistsDialog import FavoriteArtistsDialog
|
|||||||
from .dialogs.ConfirmAddAllDialog import ConfirmAddAllDialog
|
from .dialogs.ConfirmAddAllDialog import ConfirmAddAllDialog
|
||||||
from .dialogs.MoreOptionsDialog import MoreOptionsDialog
|
from .dialogs.MoreOptionsDialog import MoreOptionsDialog
|
||||||
from .dialogs.SinglePDF import create_single_pdf_from_content
|
from .dialogs.SinglePDF import create_single_pdf_from_content
|
||||||
|
from .dialogs.SupportDialog import SupportDialog
|
||||||
|
|
||||||
class DynamicFilterHolder:
|
class DynamicFilterHolder:
|
||||||
"""A thread-safe class to hold and update character filters during a download."""
|
"""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 )
|
self .history_button .clicked .connect (self ._show_download_history_dialog )
|
||||||
if hasattr (self ,'error_btn'):
|
if hasattr (self ,'error_btn'):
|
||||||
self .error_btn .clicked .connect (self ._show_error_files_dialog )
|
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 ):
|
def _on_character_input_changed_live (self ,text ):
|
||||||
"""
|
"""
|
||||||
@@ -1311,6 +1314,11 @@ class DownloaderApp (QWidget ):
|
|||||||
dialog = FutureSettingsDialog(self)
|
dialog = FutureSettingsDialog(self)
|
||||||
dialog.exec_()
|
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):
|
def _check_if_all_work_is_done(self):
|
||||||
"""
|
"""
|
||||||
Checks if the fetcher thread is done AND if all submitted tasks have been processed.
|
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 .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 .main_log_output :self .main_log_output .setMinimumHeight (50 )
|
||||||
if self .external_log_output :self .external_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 :
|
if self .external_log_output :
|
||||||
self .external_log_output .clear ()
|
self .external_log_output .clear ()
|
||||||
self .external_log_output .append ("🔗 External Links Found:")
|
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 .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 .setMinimumHeight (0 )
|
||||||
if self .external_log_output :self .external_log_output .clear ()
|
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):
|
def _handle_filter_mode_change(self, button, checked):
|
||||||
# If a button other than "More" is selected, reset the UI
|
# 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.")
|
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 )
|
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 ()
|
self ._try_process_next_external_link ()
|
||||||
elif is_only_archives :
|
elif is_only_archives :
|
||||||
self .progress_log_label .setText ("📜 Progress Log (Archives Only):")
|
self .progress_log_label .setText ("📜 Progress Log (Archives Only):")
|
||||||
if self .external_log_output :self .external_log_output .hide ()
|
if self .external_log_output :self .external_log_output .hide ()
|
||||||
if self .log_splitter :self .log_splitter .setSizes ([self .height (),0 ])
|
if self .log_splitter :self .log_splitter .setSizes ([self .height (),0 ])
|
||||||
if self .main_log_output :self .main_log_output .clear ()
|
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 :
|
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')})")
|
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 .external_log_output :self .external_log_output .hide ()
|
||||||
if self .log_splitter :self .log_splitter .setSizes ([self .height (),0 ])
|
if self .log_splitter :self .log_splitter .setSizes ([self .height (),0 ])
|
||||||
if self .main_log_output :self .main_log_output .clear ()
|
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 :
|
else :
|
||||||
self .progress_log_label .setText (self ._tr ("progress_log_label_text","📜 Progress Log:"))
|
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 .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 :
|
if is_only_links :
|
||||||
|
|||||||
@@ -355,11 +355,15 @@ def setup_ui(main_app):
|
|||||||
main_app.future_settings_button = QPushButton("⚙️")
|
main_app.future_settings_button = QPushButton("⚙️")
|
||||||
main_app.future_settings_button.setFixedWidth(45 * scale)
|
main_app.future_settings_button.setFixedWidth(45 * scale)
|
||||||
main_app.future_settings_button.clicked.connect(main_app._show_future_settings_dialog)
|
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.add_to_filter_button, 1)
|
||||||
char_manage_layout.addWidget(main_app.delete_char_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.known_names_help_button, 0)
|
||||||
char_manage_layout.addWidget(main_app.history_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.future_settings_button, 0)
|
||||||
|
char_manage_layout.addWidget(main_app.support_button, 0)
|
||||||
left_layout.addLayout(char_manage_layout)
|
left_layout.addLayout(char_manage_layout)
|
||||||
left_layout.addStretch(0)
|
left_layout.addStretch(0)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user