mirror of
https://github.com/Yuvi9587/Kemono-Downloader.git
synced 2025-12-29 16:14:44 +00:00
Compare commits
No commits in common. "4bf57eb7526fbac6f30b06492927d3f28ef6b39f" and "5a8c151c9746837c5b28e0a63057e1250c453d62" have entirely different histories.
4bf57eb752
...
5a8c151c97
@ -75,7 +75,6 @@ PROXY_HOST_KEY = "proxy/host"
|
|||||||
PROXY_PORT_KEY = "proxy/port"
|
PROXY_PORT_KEY = "proxy/port"
|
||||||
PROXY_USERNAME_KEY = "proxy/username"
|
PROXY_USERNAME_KEY = "proxy/username"
|
||||||
PROXY_PASSWORD_KEY = "proxy/password"
|
PROXY_PASSWORD_KEY = "proxy/password"
|
||||||
PROXY_TYPE_KEY = "proxy_type"
|
|
||||||
|
|
||||||
# --- UI Constants and Identifiers ---
|
# --- UI Constants and Identifiers ---
|
||||||
HTML_PREFIX = "<!HTML!>"
|
HTML_PREFIX = "<!HTML!>"
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import time
|
|||||||
import requests
|
import requests
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
# REMOVED: ThreadPoolExecutor, wait (Not needed for sequential speed)
|
from concurrent.futures import ThreadPoolExecutor, wait
|
||||||
from PyQt5.QtCore import QThread, pyqtSignal
|
from PyQt5.QtCore import QThread, pyqtSignal
|
||||||
from ...core.deviantart_client import DeviantArtClient
|
from ...core.deviantart_client import DeviantArtClient
|
||||||
from ...utils.file_utils import clean_folder_name
|
from ...utils.file_utils import clean_folder_name
|
||||||
@ -21,17 +21,21 @@ class DeviantArtDownloadThread(QThread):
|
|||||||
self.pause_event = pause_event
|
self.pause_event = pause_event
|
||||||
self.cancellation_event = cancellation_event
|
self.cancellation_event = cancellation_event
|
||||||
|
|
||||||
# Pass logger to client
|
# Pass logger to client so we see "Rate Limit" messages in the UI
|
||||||
self.client = DeviantArtClient(logger_func=self.progress_signal.emit)
|
self.client = DeviantArtClient(logger_func=self.progress_signal.emit)
|
||||||
|
|
||||||
self.parent_app = parent
|
self.parent_app = parent
|
||||||
self.download_count = 0
|
self.download_count = 0
|
||||||
self.skip_count = 0
|
self.skip_count = 0
|
||||||
|
|
||||||
|
# --- THREAD SETTINGS ---
|
||||||
|
# STRICTLY 1 THREAD (Sequential) to match 1.py and avoid Rate Limits
|
||||||
|
self.max_threads = 1
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.progress_signal.emit("=" * 40)
|
self.progress_signal.emit("=" * 40)
|
||||||
self.progress_signal.emit(f"🚀 Starting DeviantArt download for: {self.url}")
|
self.progress_signal.emit(f"🚀 Starting DeviantArt download for: {self.url}")
|
||||||
self.progress_signal.emit(f" ℹ️ Mode: High-Speed Sequential (Matches 1.py)")
|
self.progress_signal.emit(f" ℹ️ Mode: Sequential (1 thread) to prevent 429 errors.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not self.client.authenticate():
|
if not self.client.authenticate():
|
||||||
@ -87,7 +91,7 @@ class DeviantArtDownloadThread(QThread):
|
|||||||
if not os.path.exists(base_folder):
|
if not os.path.exists(base_folder):
|
||||||
os.makedirs(base_folder, exist_ok=True)
|
os.makedirs(base_folder, exist_ok=True)
|
||||||
|
|
||||||
# --- OPTIMIZED LOOP (Matches 1.py structure) ---
|
with ThreadPoolExecutor(max_workers=self.max_threads) as executor:
|
||||||
while has_more:
|
while has_more:
|
||||||
if self._check_pause_cancel(): break
|
if self._check_pause_cancel(): break
|
||||||
|
|
||||||
@ -98,12 +102,16 @@ class DeviantArtDownloadThread(QThread):
|
|||||||
|
|
||||||
if not results: break
|
if not results: break
|
||||||
|
|
||||||
# DIRECT LOOP - No ThreadPoolExecutor overhead
|
futures = []
|
||||||
for deviation in results:
|
for deviation in results:
|
||||||
if self._check_pause_cancel(): break
|
if self._check_pause_cancel(): break
|
||||||
self._process_deviation_task(deviation, base_folder)
|
future = executor.submit(self._process_deviation_task, deviation, base_folder)
|
||||||
|
futures.append(future)
|
||||||
|
|
||||||
# Be nice to API (1 second sleep per batch of 24)
|
# Wait for this batch to finish before getting the next page
|
||||||
|
wait(futures)
|
||||||
|
|
||||||
|
# Match 1.py: Sleep 1s between pages to be nice to API
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
def _process_deviation_task(self, deviation, base_folder):
|
def _process_deviation_task(self, deviation, base_folder):
|
||||||
@ -113,7 +121,7 @@ class DeviantArtDownloadThread(QThread):
|
|||||||
title = deviation.get('title', 'Unknown')
|
title = deviation.get('title', 'Unknown')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Try to get content (Handles fallback internally now)
|
# This handles the fallback logic internally
|
||||||
content = self.client.get_deviation_content(dev_id)
|
content = self.client.get_deviation_content(dev_id)
|
||||||
if content:
|
if content:
|
||||||
self._download_file(content['src'], deviation, override_dir=base_folder)
|
self._download_file(content['src'], deviation, override_dir=base_folder)
|
||||||
@ -147,6 +155,7 @@ class DeviantArtDownloadThread(QThread):
|
|||||||
|
|
||||||
final_filename = f"{safe_title}{ext}"
|
final_filename = f"{safe_title}{ext}"
|
||||||
|
|
||||||
|
# Naming logic
|
||||||
if self.parent_app and self.parent_app.manga_mode_checkbox.isChecked():
|
if self.parent_app and self.parent_app.manga_mode_checkbox.isChecked():
|
||||||
try:
|
try:
|
||||||
creator_name = metadata.get('author', {}).get('username', 'Unknown')
|
creator_name = metadata.get('author', {}).get('username', 'Unknown')
|
||||||
@ -168,8 +177,7 @@ class DeviantArtDownloadThread(QThread):
|
|||||||
final_filename = f"{clean_folder_name(new_name)}{ext}"
|
final_filename = f"{clean_folder_name(new_name)}{ext}"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Reduced logging verbosity slightly for speed
|
self.progress_signal.emit(f" ⚠️ Renaming failed ({e}), using default.")
|
||||||
pass
|
|
||||||
|
|
||||||
save_dir = override_dir if override_dir else self.output_dir
|
save_dir = override_dir if override_dir else self.output_dir
|
||||||
if not os.path.exists(save_dir):
|
if not os.path.exists(save_dir):
|
||||||
|
|||||||
@ -249,22 +249,13 @@ class FutureSettingsDialog(QDialog):
|
|||||||
self.proxy_enabled_checkbox.stateChanged.connect(self._proxy_setting_changed)
|
self.proxy_enabled_checkbox.stateChanged.connect(self._proxy_setting_changed)
|
||||||
proxy_layout.addWidget(self.proxy_enabled_checkbox, 0, 0, 1, 2)
|
proxy_layout.addWidget(self.proxy_enabled_checkbox, 0, 0, 1, 2)
|
||||||
|
|
||||||
# Proxy Type Dropdown
|
|
||||||
self.proxy_type_label = QLabel("Proxy Type:")
|
|
||||||
self.proxy_type_combo = QComboBox()
|
|
||||||
self.proxy_type_combo.addItems(["HTTP", "SOCKS4", "SOCKS5"])
|
|
||||||
self.proxy_type_combo.currentIndexChanged.connect(self._proxy_setting_changed)
|
|
||||||
proxy_layout.addWidget(self.proxy_type_label, 1, 0)
|
|
||||||
proxy_layout.addWidget(self.proxy_type_combo, 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
# Host / IP
|
# Host / IP
|
||||||
self.proxy_host_label = QLabel()
|
self.proxy_host_label = QLabel()
|
||||||
self.proxy_host_input = QLineEdit()
|
self.proxy_host_input = QLineEdit()
|
||||||
self.proxy_host_input.setPlaceholderText("127.0.0.1")
|
self.proxy_host_input.setPlaceholderText("127.0.0.1")
|
||||||
self.proxy_host_input.editingFinished.connect(self._proxy_setting_changed)
|
self.proxy_host_input.editingFinished.connect(self._proxy_setting_changed)
|
||||||
proxy_layout.addWidget(self.proxy_host_label, 2, 0) # Changed row to 2
|
proxy_layout.addWidget(self.proxy_host_label, 1, 0)
|
||||||
proxy_layout.addWidget(self.proxy_host_input, 2, 1)
|
proxy_layout.addWidget(self.proxy_host_input, 1, 1)
|
||||||
|
|
||||||
# Port
|
# Port
|
||||||
self.proxy_port_label = QLabel()
|
self.proxy_port_label = QLabel()
|
||||||
@ -272,16 +263,16 @@ class FutureSettingsDialog(QDialog):
|
|||||||
self.proxy_port_input.setPlaceholderText("8080")
|
self.proxy_port_input.setPlaceholderText("8080")
|
||||||
self.proxy_port_input.setValidator(QIntValidator(1, 65535, self)) # Only numbers
|
self.proxy_port_input.setValidator(QIntValidator(1, 65535, self)) # Only numbers
|
||||||
self.proxy_port_input.editingFinished.connect(self._proxy_setting_changed)
|
self.proxy_port_input.editingFinished.connect(self._proxy_setting_changed)
|
||||||
proxy_layout.addWidget(self.proxy_port_label, 3, 0)
|
proxy_layout.addWidget(self.proxy_port_label, 2, 0)
|
||||||
proxy_layout.addWidget(self.proxy_port_input, 3, 1)
|
proxy_layout.addWidget(self.proxy_port_input, 2, 1)
|
||||||
|
|
||||||
# Username
|
# Username
|
||||||
self.proxy_user_label = QLabel()
|
self.proxy_user_label = QLabel()
|
||||||
self.proxy_user_input = QLineEdit()
|
self.proxy_user_input = QLineEdit()
|
||||||
self.proxy_user_input.setPlaceholderText("(Optional)")
|
self.proxy_user_input.setPlaceholderText("(Optional)")
|
||||||
self.proxy_user_input.editingFinished.connect(self._proxy_setting_changed)
|
self.proxy_user_input.editingFinished.connect(self._proxy_setting_changed)
|
||||||
proxy_layout.addWidget(self.proxy_user_label, 4, 0)
|
proxy_layout.addWidget(self.proxy_user_label, 3, 0)
|
||||||
proxy_layout.addWidget(self.proxy_user_input, 4, 1)
|
proxy_layout.addWidget(self.proxy_user_input, 3, 1)
|
||||||
|
|
||||||
# Password
|
# Password
|
||||||
self.proxy_pass_label = QLabel()
|
self.proxy_pass_label = QLabel()
|
||||||
@ -289,8 +280,8 @@ class FutureSettingsDialog(QDialog):
|
|||||||
self.proxy_pass_input.setPlaceholderText("(Optional)")
|
self.proxy_pass_input.setPlaceholderText("(Optional)")
|
||||||
self.proxy_pass_input.setEchoMode(QLineEdit.Password) # Mask input
|
self.proxy_pass_input.setEchoMode(QLineEdit.Password) # Mask input
|
||||||
self.proxy_pass_input.editingFinished.connect(self._proxy_setting_changed)
|
self.proxy_pass_input.editingFinished.connect(self._proxy_setting_changed)
|
||||||
proxy_layout.addWidget(self.proxy_pass_label, 5, 0)
|
proxy_layout.addWidget(self.proxy_pass_label, 4, 0)
|
||||||
proxy_layout.addWidget(self.proxy_pass_input, 5, 1)
|
proxy_layout.addWidget(self.proxy_pass_input, 4, 1)
|
||||||
|
|
||||||
network_tab_layout.addWidget(self.proxy_group_box)
|
network_tab_layout.addWidget(self.proxy_group_box)
|
||||||
network_tab_layout.addStretch(1)
|
network_tab_layout.addStretch(1)
|
||||||
@ -388,32 +379,19 @@ class FutureSettingsDialog(QDialog):
|
|||||||
# --- START: New Proxy Logic ---
|
# --- START: New Proxy Logic ---
|
||||||
def _load_proxy_settings(self):
|
def _load_proxy_settings(self):
|
||||||
"""Loads proxy settings from QSettings into the UI."""
|
"""Loads proxy settings from QSettings into the UI."""
|
||||||
# Block signals to prevent triggering auto-save while loading
|
|
||||||
self.proxy_enabled_checkbox.blockSignals(True)
|
self.proxy_enabled_checkbox.blockSignals(True)
|
||||||
self.proxy_type_combo.blockSignals(True) # <--- NEW
|
|
||||||
self.proxy_host_input.blockSignals(True)
|
self.proxy_host_input.blockSignals(True)
|
||||||
self.proxy_port_input.blockSignals(True)
|
self.proxy_port_input.blockSignals(True)
|
||||||
self.proxy_user_input.blockSignals(True)
|
self.proxy_user_input.blockSignals(True)
|
||||||
self.proxy_pass_input.blockSignals(True)
|
self.proxy_pass_input.blockSignals(True)
|
||||||
|
|
||||||
# Load values
|
|
||||||
enabled = self.parent_app.settings.value(PROXY_ENABLED_KEY, False, type=bool)
|
enabled = self.parent_app.settings.value(PROXY_ENABLED_KEY, False, type=bool)
|
||||||
proxy_type = self.parent_app.settings.value("proxy_type", "HTTP", type=str) # <--- NEW
|
|
||||||
host = self.parent_app.settings.value(PROXY_HOST_KEY, "", type=str)
|
host = self.parent_app.settings.value(PROXY_HOST_KEY, "", type=str)
|
||||||
port = self.parent_app.settings.value(PROXY_PORT_KEY, "", type=str)
|
port = self.parent_app.settings.value(PROXY_PORT_KEY, "", type=str)
|
||||||
user = self.parent_app.settings.value(PROXY_USERNAME_KEY, "", type=str)
|
user = self.parent_app.settings.value(PROXY_USERNAME_KEY, "", type=str)
|
||||||
password = self.parent_app.settings.value(PROXY_PASSWORD_KEY, "", type=str)
|
password = self.parent_app.settings.value(PROXY_PASSWORD_KEY, "", type=str)
|
||||||
|
|
||||||
# Apply values to UI
|
|
||||||
self.proxy_enabled_checkbox.setChecked(enabled)
|
self.proxy_enabled_checkbox.setChecked(enabled)
|
||||||
|
|
||||||
# <--- NEW: Set the dropdown selection
|
|
||||||
index = self.proxy_type_combo.findText(proxy_type)
|
|
||||||
if index >= 0:
|
|
||||||
self.proxy_type_combo.setCurrentIndex(index)
|
|
||||||
else:
|
|
||||||
self.proxy_type_combo.setCurrentIndex(0) # Default to first item if not found
|
|
||||||
|
|
||||||
self.proxy_host_input.setText(host)
|
self.proxy_host_input.setText(host)
|
||||||
self.proxy_port_input.setText(port)
|
self.proxy_port_input.setText(port)
|
||||||
self.proxy_user_input.setText(user)
|
self.proxy_user_input.setText(user)
|
||||||
@ -421,9 +399,7 @@ class FutureSettingsDialog(QDialog):
|
|||||||
|
|
||||||
self._update_proxy_fields_state(enabled)
|
self._update_proxy_fields_state(enabled)
|
||||||
|
|
||||||
# Unblock signals
|
|
||||||
self.proxy_enabled_checkbox.blockSignals(False)
|
self.proxy_enabled_checkbox.blockSignals(False)
|
||||||
self.proxy_type_combo.blockSignals(False) # <--- NEW
|
|
||||||
self.proxy_host_input.blockSignals(False)
|
self.proxy_host_input.blockSignals(False)
|
||||||
self.proxy_port_input.blockSignals(False)
|
self.proxy_port_input.blockSignals(False)
|
||||||
self.proxy_user_input.blockSignals(False)
|
self.proxy_user_input.blockSignals(False)
|
||||||
@ -432,19 +408,16 @@ class FutureSettingsDialog(QDialog):
|
|||||||
def _proxy_setting_changed(self):
|
def _proxy_setting_changed(self):
|
||||||
"""Saves the current proxy UI state to QSettings."""
|
"""Saves the current proxy UI state to QSettings."""
|
||||||
enabled = self.proxy_enabled_checkbox.isChecked()
|
enabled = self.proxy_enabled_checkbox.isChecked()
|
||||||
proxy_type = self.proxy_type_combo.currentText() # <--- NEW
|
|
||||||
host = self.proxy_host_input.text().strip()
|
host = self.proxy_host_input.text().strip()
|
||||||
port = self.proxy_port_input.text().strip()
|
port = self.proxy_port_input.text().strip()
|
||||||
user = self.proxy_user_input.text().strip()
|
user = self.proxy_user_input.text().strip()
|
||||||
password = self.proxy_pass_input.text().strip()
|
password = self.proxy_pass_input.text().strip()
|
||||||
|
|
||||||
self.parent_app.settings.setValue(PROXY_ENABLED_KEY, enabled)
|
self.parent_app.settings.setValue(PROXY_ENABLED_KEY, enabled)
|
||||||
self.parent_app.settings.setValue("proxy_type", proxy_type) # <--- NEW
|
|
||||||
self.parent_app.settings.setValue(PROXY_HOST_KEY, host)
|
self.parent_app.settings.setValue(PROXY_HOST_KEY, host)
|
||||||
self.parent_app.settings.setValue(PROXY_PORT_KEY, port)
|
self.parent_app.settings.setValue(PROXY_PORT_KEY, port)
|
||||||
self.parent_app.settings.setValue(PROXY_USERNAME_KEY, user)
|
self.parent_app.settings.setValue(PROXY_USERNAME_KEY, user)
|
||||||
self.parent_app.settings.setValue(PROXY_PASSWORD_KEY, password)
|
self.parent_app.settings.setValue(PROXY_PASSWORD_KEY, password)
|
||||||
|
|
||||||
self.parent_app.settings.sync()
|
self.parent_app.settings.sync()
|
||||||
|
|
||||||
self._update_proxy_fields_state(enabled)
|
self._update_proxy_fields_state(enabled)
|
||||||
@ -454,7 +427,6 @@ class FutureSettingsDialog(QDialog):
|
|||||||
|
|
||||||
def _update_proxy_fields_state(self, enabled):
|
def _update_proxy_fields_state(self, enabled):
|
||||||
"""Enables or disables input fields based on the checkbox."""
|
"""Enables or disables input fields based on the checkbox."""
|
||||||
self.proxy_type_combo.setEnabled(enabled)
|
|
||||||
self.proxy_host_input.setEnabled(enabled)
|
self.proxy_host_input.setEnabled(enabled)
|
||||||
self.proxy_port_input.setEnabled(enabled)
|
self.proxy_port_input.setEnabled(enabled)
|
||||||
self.proxy_user_input.setEnabled(enabled)
|
self.proxy_user_input.setEnabled(enabled)
|
||||||
|
|||||||
@ -849,24 +849,12 @@ class DownloaderApp (QWidget ):
|
|||||||
settings['proxy_port'] = self.settings.value(PROXY_PORT_KEY, "", type=str)
|
settings['proxy_port'] = self.settings.value(PROXY_PORT_KEY, "", type=str)
|
||||||
settings['proxy_username'] = self.settings.value(PROXY_USERNAME_KEY, "", type=str)
|
settings['proxy_username'] = self.settings.value(PROXY_USERNAME_KEY, "", type=str)
|
||||||
settings['proxy_password'] = self.settings.value(PROXY_PASSWORD_KEY, "", type=str)
|
settings['proxy_password'] = self.settings.value(PROXY_PASSWORD_KEY, "", type=str)
|
||||||
proxy_type_str = self.settings.value("proxy_type", "HTTP", type=str)
|
|
||||||
|
|
||||||
settings['proxies'] = None
|
settings['proxies'] = None
|
||||||
if settings['proxy_enabled'] and settings['proxy_host'] and settings['proxy_port']:
|
if settings['proxy_enabled'] and settings['proxy_host'] and settings['proxy_port']:
|
||||||
|
proxy_str = f"http://{settings['proxy_host']}:{settings['proxy_port']}"
|
||||||
# Determine correct scheme
|
|
||||||
scheme = "http"
|
|
||||||
if proxy_type_str == "SOCKS5":
|
|
||||||
scheme = "socks5h" # 'socks5h' forces remote DNS resolution (safer/better for bypassing)
|
|
||||||
elif proxy_type_str == "SOCKS4":
|
|
||||||
scheme = "socks4"
|
|
||||||
|
|
||||||
# Build URL string
|
|
||||||
if settings['proxy_username'] and settings['proxy_password']:
|
if settings['proxy_username'] and settings['proxy_password']:
|
||||||
proxy_str = f"{scheme}://{settings['proxy_username']}:{settings['proxy_password']}@{settings['proxy_host']}:{settings['proxy_port']}"
|
proxy_str = f"http://{settings['proxy_username']}:{settings['proxy_password']}@{settings['proxy_host']}:{settings['proxy_port']}"
|
||||||
else:
|
|
||||||
proxy_str = f"{scheme}://{settings['proxy_host']}:{settings['proxy_port']}"
|
|
||||||
|
|
||||||
settings['proxies'] = {'http': proxy_str, 'https': proxy_str}
|
settings['proxies'] = {'http': proxy_str, 'https': proxy_str}
|
||||||
|
|
||||||
return settings
|
return settings
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user