优化下载性能:移除 SHA256 计算并使用 16MB chunks

- 移除下载后的 SHA256 计算,直接使用 API 返回的 hash 值
- 将 chunk size 从 4MB 调整为 16MB,减少 75% 的 I/O 操作
- 这有助于缓解 ComfyUI 执行期间的卡顿问题
This commit is contained in:
Will Miao
2026-03-25 19:29:48 +08:00
parent c5c1b8fd2a
commit 6f2a01dc86
2 changed files with 252 additions and 151 deletions

View File

@@ -19,7 +19,6 @@ from ..utils.civitai_utils import rewrite_preview_url
from ..utils.preview_selection import select_preview_media from ..utils.preview_selection import select_preview_media
from ..utils.utils import sanitize_folder_name from ..utils.utils import sanitize_folder_name
from ..utils.exif_utils import ExifUtils from ..utils.exif_utils import ExifUtils
from ..utils.file_utils import calculate_sha256
from ..utils.metadata_manager import MetadataManager from ..utils.metadata_manager import MetadataManager
from .service_registry import ServiceRegistry from .service_registry import ServiceRegistry
from .settings_manager import get_settings_manager from .settings_manager import get_settings_manager
@@ -965,11 +964,12 @@ class DownloadManager:
for download_url in download_urls: for download_url in download_urls:
use_auth = download_url.startswith("https://civitai.com/api/download/") use_auth = download_url.startswith("https://civitai.com/api/download/")
download_kwargs = { download_kwargs = {
"progress_callback": lambda progress, "progress_callback": lambda progress, snapshot=None: (
snapshot=None: self._handle_download_progress( self._handle_download_progress(
progress, progress,
progress_callback, progress_callback,
snapshot, snapshot,
)
), ),
"use_auth": use_auth, # Only use authentication for Civitai downloads "use_auth": use_auth, # Only use authentication for Civitai downloads
} }
@@ -1238,7 +1238,8 @@ class DownloadManager:
entry.file_name = os.path.splitext(os.path.basename(file_path))[0] entry.file_name = os.path.splitext(os.path.basename(file_path))[0]
# Update size to actual downloaded file size # Update size to actual downloaded file size
entry.size = os.path.getsize(file_path) entry.size = os.path.getsize(file_path)
entry.sha256 = await calculate_sha256(file_path) # Use SHA256 from API metadata (already set in from_civitai_info)
# Do not recalculate to avoid blocking during ComfyUI execution
entries.append(entry) entries.append(entry)
return entries return entries

View File

@@ -44,7 +44,9 @@ class DownloadStreamControl:
self._event.set() self._event.set()
self._reconnect_requested = False self._reconnect_requested = False
self.last_progress_timestamp: Optional[float] = None self.last_progress_timestamp: Optional[float] = None
self.stall_timeout: float = float(stall_timeout) if stall_timeout is not None else 120.0 self.stall_timeout: float = (
float(stall_timeout) if stall_timeout is not None else 120.0
)
def is_set(self) -> bool: def is_set(self) -> bool:
return self._event.is_set() return self._event.is_set()
@@ -85,7 +87,9 @@ class DownloadStreamControl:
self.last_progress_timestamp = timestamp or datetime.now().timestamp() self.last_progress_timestamp = timestamp or datetime.now().timestamp()
self._reconnect_requested = False self._reconnect_requested = False
def time_since_last_progress(self, *, now: Optional[float] = None) -> Optional[float]: def time_since_last_progress(
self, *, now: Optional[float] = None
) -> Optional[float]:
if self.last_progress_timestamp is None: if self.last_progress_timestamp is None:
return None return None
reference = now if now is not None else datetime.now().timestamp() reference = now if now is not None else datetime.now().timestamp()
@@ -120,7 +124,7 @@ class Downloader:
def __init__(self): def __init__(self):
"""Initialize the downloader with optimal settings""" """Initialize the downloader with optimal settings"""
# Check if already initialized for singleton pattern # Check if already initialized for singleton pattern
if hasattr(self, '_initialized'): if hasattr(self, "_initialized"):
return return
self._initialized = True self._initialized = True
@@ -131,7 +135,9 @@ class Downloader:
self._session_lock = asyncio.Lock() self._session_lock = asyncio.Lock()
# Configuration # Configuration
self.chunk_size = 4 * 1024 * 1024 # 4MB chunks for better throughput self.chunk_size = (
16 * 1024 * 1024
) # 16MB chunks to balance I/O reduction and memory usage
self.max_retries = 5 self.max_retries = 5
self.base_delay = 2.0 # Base delay for exponential backoff self.base_delay = 2.0 # Base delay for exponential backoff
self.session_timeout = 300 # 5 minutes self.session_timeout = 300 # 5 minutes
@@ -139,10 +145,10 @@ class Downloader:
# Default headers # Default headers
self.default_headers = { self.default_headers = {
'User-Agent': 'ComfyUI-LoRA-Manager/1.0', "User-Agent": "ComfyUI-LoRA-Manager/1.0",
# Explicitly request uncompressed payloads so aiohttp doesn't need optional # Explicitly request uncompressed payloads so aiohttp doesn't need optional
# decoders (e.g. zstandard) that may be missing in runtime environments. # decoders (e.g. zstandard) that may be missing in runtime environments.
'Accept-Encoding': 'identity', "Accept-Encoding": "identity",
} }
@property @property
@@ -158,7 +164,7 @@ class Downloader:
@property @property
def proxy_url(self) -> Optional[str]: def proxy_url(self) -> Optional[str]:
"""Get the current proxy URL (initialize if needed)""" """Get the current proxy URL (initialize if needed)"""
if not hasattr(self, '_proxy_url'): if not hasattr(self, "_proxy_url"):
self._proxy_url = None self._proxy_url = None
return self._proxy_url return self._proxy_url
@@ -169,14 +175,14 @@ class Downloader:
try: try:
settings_manager = get_settings_manager() settings_manager = get_settings_manager()
settings_timeout = settings_manager.get('download_stall_timeout_seconds') settings_timeout = settings_manager.get("download_stall_timeout_seconds")
except Exception as exc: # pragma: no cover - defensive guard except Exception as exc: # pragma: no cover - defensive guard
logger.debug("Failed to read stall timeout from settings: %s", exc) logger.debug("Failed to read stall timeout from settings: %s", exc)
raw_value = ( raw_value = (
settings_timeout settings_timeout
if settings_timeout not in (None, "") if settings_timeout not in (None, "")
else os.environ.get('COMFYUI_DOWNLOAD_STALL_TIMEOUT') else os.environ.get("COMFYUI_DOWNLOAD_STALL_TIMEOUT")
) )
try: try:
@@ -191,11 +197,13 @@ class Downloader:
if self._session is None: if self._session is None:
return True return True
if not hasattr(self, '_session_created_at') or self._session_created_at is None: if not hasattr(self, "_session_created_at") or self._session_created_at is None:
return True return True
# Refresh if session is older than timeout # Refresh if session is older than timeout
if (datetime.now() - self._session_created_at).total_seconds() > self.session_timeout: if (
datetime.now() - self._session_created_at
).total_seconds() > self.session_timeout:
return True return True
return False return False
@@ -217,12 +225,12 @@ class Downloader:
# Check for app-level proxy settings # Check for app-level proxy settings
proxy_url = None proxy_url = None
settings_manager = get_settings_manager() settings_manager = get_settings_manager()
if settings_manager.get('proxy_enabled', False): if settings_manager.get("proxy_enabled", False):
proxy_host = settings_manager.get('proxy_host', '').strip() proxy_host = settings_manager.get("proxy_host", "").strip()
proxy_port = settings_manager.get('proxy_port', '').strip() proxy_port = settings_manager.get("proxy_port", "").strip()
proxy_type = settings_manager.get('proxy_type', 'http').lower() proxy_type = settings_manager.get("proxy_type", "http").lower()
proxy_username = settings_manager.get('proxy_username', '').strip() proxy_username = settings_manager.get("proxy_username", "").strip()
proxy_password = settings_manager.get('proxy_password', '').strip() proxy_password = settings_manager.get("proxy_password", "").strip()
if proxy_host and proxy_port: if proxy_host and proxy_port:
# Build proxy URL # Build proxy URL
@@ -231,37 +239,46 @@ class Downloader:
else: else:
proxy_url = f"{proxy_type}://{proxy_host}:{proxy_port}" proxy_url = f"{proxy_type}://{proxy_host}:{proxy_port}"
logger.debug(f"Using app-level proxy: {proxy_type}://{proxy_host}:{proxy_port}") logger.debug(
f"Using app-level proxy: {proxy_type}://{proxy_host}:{proxy_port}"
)
logger.debug("Proxy mode: app-level proxy is active.") logger.debug("Proxy mode: app-level proxy is active.")
else: else:
logger.debug("Proxy mode: system-level proxy (trust_env) will be used if configured in environment.") logger.debug(
"Proxy mode: system-level proxy (trust_env) will be used if configured in environment."
)
# Optimize TCP connection parameters # Optimize TCP connection parameters
connector = aiohttp.TCPConnector( connector = aiohttp.TCPConnector(
ssl=True, ssl=True,
limit=8, # Concurrent connections limit=8, # Concurrent connections
ttl_dns_cache=300, # DNS cache timeout ttl_dns_cache=300, # DNS cache timeout
force_close=False, # Keep connections for reuse force_close=False, # Keep connections for reuse
enable_cleanup_closed=True enable_cleanup_closed=True,
) )
# Configure timeout parameters # Configure timeout parameters
timeout = aiohttp.ClientTimeout( timeout = aiohttp.ClientTimeout(
total=None, # No total timeout for large downloads total=None, # No total timeout for large downloads
connect=60, # Connection timeout connect=60, # Connection timeout
sock_read=300 # 5 minute socket read timeout sock_read=300, # 5 minute socket read timeout
) )
self._session = aiohttp.ClientSession( self._session = aiohttp.ClientSession(
connector=connector, connector=connector,
trust_env=proxy_url is None, # Only use system proxy if no app-level proxy is set trust_env=proxy_url
timeout=timeout is None, # Only use system proxy if no app-level proxy is set
timeout=timeout,
) )
# Store proxy URL for use in requests # Store proxy URL for use in requests
self._proxy_url = proxy_url self._proxy_url = proxy_url
self._session_created_at = datetime.now() self._session_created_at = datetime.now()
logger.debug("Created new HTTP session with proxy settings. App-level proxy: %s, System-level proxy (trust_env): %s", bool(proxy_url), proxy_url is None) logger.debug(
"Created new HTTP session with proxy settings. App-level proxy: %s, System-level proxy (trust_env): %s",
bool(proxy_url),
proxy_url is None,
)
def _get_auth_headers(self, use_auth: bool = False) -> Dict[str, str]: def _get_auth_headers(self, use_auth: bool = False) -> Dict[str, str]:
"""Get headers with optional authentication""" """Get headers with optional authentication"""
@@ -270,10 +287,10 @@ class Downloader:
if use_auth: if use_auth:
# Add CivitAI API key if available # Add CivitAI API key if available
settings_manager = get_settings_manager() settings_manager = get_settings_manager()
api_key = settings_manager.get('civitai_api_key') api_key = settings_manager.get("civitai_api_key")
if api_key: if api_key:
headers['Authorization'] = f'Bearer {api_key}' headers["Authorization"] = f"Bearer {api_key}"
headers['Content-Type'] = 'application/json' headers["Content-Type"] = "application/json"
return headers return headers
@@ -303,7 +320,7 @@ class Downloader:
Tuple[bool, str]: (success, save_path or error message) Tuple[bool, str]: (success, save_path or error message)
""" """
retry_count = 0 retry_count = 0
part_path = save_path + '.part' if allow_resume else save_path part_path = save_path + ".part" if allow_resume else save_path
# Prepare headers # Prepare headers
headers = self._get_auth_headers(use_auth) headers = self._get_auth_headers(use_auth)
@@ -323,50 +340,71 @@ class Downloader:
session = await self.session session = await self.session
# Debug log for proxy mode at request time # Debug log for proxy mode at request time
if self.proxy_url: if self.proxy_url:
logger.debug(f"[download_file] Using app-level proxy: {self.proxy_url}") logger.debug(
f"[download_file] Using app-level proxy: {self.proxy_url}"
)
else: else:
logger.debug("[download_file] Using system-level proxy (trust_env) if configured.") logger.debug(
"[download_file] Using system-level proxy (trust_env) if configured."
)
# Add Range header for resume if we have partial data # Add Range header for resume if we have partial data
request_headers = headers.copy() request_headers = headers.copy()
if allow_resume and resume_offset > 0: if allow_resume and resume_offset > 0:
request_headers['Range'] = f'bytes={resume_offset}-' request_headers["Range"] = f"bytes={resume_offset}-"
# Disable compression for better chunked downloads # Disable compression for better chunked downloads
request_headers['Accept-Encoding'] = 'identity' request_headers["Accept-Encoding"] = "identity"
logger.debug(f"Download attempt {retry_count + 1}/{self.max_retries + 1} from: {url}") logger.debug(
f"Download attempt {retry_count + 1}/{self.max_retries + 1} from: {url}"
)
if resume_offset > 0: if resume_offset > 0:
logger.debug(f"Requesting range from byte {resume_offset}") logger.debug(f"Requesting range from byte {resume_offset}")
async with session.get(url, headers=request_headers, allow_redirects=True, proxy=self.proxy_url) as response: async with session.get(
url,
headers=request_headers,
allow_redirects=True,
proxy=self.proxy_url,
) as response:
# Handle different response codes # Handle different response codes
if response.status == 200: if response.status == 200:
# Full content response # Full content response
if resume_offset > 0: if resume_offset > 0:
# Server doesn't support ranges, restart from beginning # Server doesn't support ranges, restart from beginning
logger.warning("Server doesn't support range requests, restarting download") logger.warning(
"Server doesn't support range requests, restarting download"
)
resume_offset = 0 resume_offset = 0
if os.path.exists(part_path): if os.path.exists(part_path):
os.remove(part_path) os.remove(part_path)
elif response.status == 206: elif response.status == 206:
# Partial content response (resume successful) # Partial content response (resume successful)
content_range = response.headers.get('Content-Range') content_range = response.headers.get("Content-Range")
if content_range: if content_range:
# Parse total size from Content-Range header (e.g., "bytes 1024-2047/2048") # Parse total size from Content-Range header (e.g., "bytes 1024-2047/2048")
range_parts = content_range.split('/') range_parts = content_range.split("/")
if len(range_parts) == 2: if len(range_parts) == 2:
total_size = int(range_parts[1]) total_size = int(range_parts[1])
logger.info(f"Successfully resumed download from byte {resume_offset}") logger.info(
f"Successfully resumed download from byte {resume_offset}"
)
elif response.status == 416: elif response.status == 416:
# Range not satisfiable - file might be complete or corrupted # Range not satisfiable - file might be complete or corrupted
if allow_resume and os.path.exists(part_path): if allow_resume and os.path.exists(part_path):
part_size = os.path.getsize(part_path) part_size = os.path.getsize(part_path)
logger.warning(f"Range not satisfiable. Part file size: {part_size}") logger.warning(
f"Range not satisfiable. Part file size: {part_size}"
)
# Try to get actual file size # Try to get actual file size
head_response = await session.head(url, headers=headers, proxy=self.proxy_url) head_response = await session.head(
url, headers=headers, proxy=self.proxy_url
)
if head_response.status == 200: if head_response.status == 200:
actual_size = int(head_response.headers.get('content-length', 0)) actual_size = int(
head_response.headers.get("content-length", 0)
)
if part_size == actual_size: if part_size == actual_size:
# File is complete, just rename it # File is complete, just rename it
if allow_resume: if allow_resume:
@@ -388,21 +426,36 @@ class Downloader:
resume_offset = 0 resume_offset = 0
continue continue
elif response.status == 401: elif response.status == 401:
logger.warning(f"Unauthorized access to resource: {url} (Status 401)") logger.warning(
return False, "Invalid or missing API key, or early access restriction." f"Unauthorized access to resource: {url} (Status 401)"
)
return (
False,
"Invalid or missing API key, or early access restriction.",
)
elif response.status == 403: elif response.status == 403:
logger.warning(f"Forbidden access to resource: {url} (Status 403)") logger.warning(
return False, "Access forbidden: You don't have permission to download this file." f"Forbidden access to resource: {url} (Status 403)"
)
return (
False,
"Access forbidden: You don't have permission to download this file.",
)
elif response.status == 404: elif response.status == 404:
logger.warning(f"Resource not found: {url} (Status 404)") logger.warning(f"Resource not found: {url} (Status 404)")
return False, "File not found - the download link may be invalid or expired." return (
False,
"File not found - the download link may be invalid or expired.",
)
else: else:
logger.error(f"Download failed for {url} with status {response.status}") logger.error(
f"Download failed for {url} with status {response.status}"
)
return False, f"Download failed with status {response.status}" return False, f"Download failed with status {response.status}"
# Get total file size for progress calculation (if not set from Content-Range) # Get total file size for progress calculation (if not set from Content-Range)
if total_size == 0: if total_size == 0:
total_size = int(response.headers.get('content-length', 0)) total_size = int(response.headers.get("content-length", 0))
if response.status == 206: if response.status == 206:
# For partial content, add the offset to get total file size # For partial content, add the offset to get total file size
total_size += resume_offset total_size += resume_offset
@@ -417,7 +470,7 @@ class Downloader:
# Stream download to file with progress updates # Stream download to file with progress updates
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
mode = 'ab' if (allow_resume and resume_offset > 0) else 'wb' mode = "ab" if (allow_resume and resume_offset > 0) else "wb"
control = pause_event control = pause_event
if control is not None: if control is not None:
@@ -425,7 +478,9 @@ class Downloader:
with open(part_path, mode) as f: with open(part_path, mode) as f:
while True: while True:
active_stall_timeout = control.stall_timeout if control else self.stall_timeout active_stall_timeout = (
control.stall_timeout if control else self.stall_timeout
)
if control is not None: if control is not None:
if control.is_paused(): if control.is_paused():
@@ -437,7 +492,9 @@ class Downloader:
"Reconnect requested after resume" "Reconnect requested after resume"
) )
elif control.consume_reconnect_request(): elif control.consume_reconnect_request():
raise DownloadRestartRequested("Reconnect requested") raise DownloadRestartRequested(
"Reconnect requested"
)
try: try:
chunk = await asyncio.wait_for( chunk = await asyncio.wait_for(
@@ -466,22 +523,32 @@ class Downloader:
control.mark_progress(timestamp=now.timestamp()) control.mark_progress(timestamp=now.timestamp())
# Limit progress update frequency to reduce overhead # Limit progress update frequency to reduce overhead
time_diff = (now - last_progress_report_time).total_seconds() time_diff = (
now - last_progress_report_time
).total_seconds()
if progress_callback and time_diff >= 1.0: if progress_callback and time_diff >= 1.0:
progress_samples.append((now, current_size)) progress_samples.append((now, current_size))
cutoff = now - timedelta(seconds=5) cutoff = now - timedelta(seconds=5)
while progress_samples and progress_samples[0][0] < cutoff: while (
progress_samples and progress_samples[0][0] < cutoff
):
progress_samples.popleft() progress_samples.popleft()
percent = (current_size / total_size) * 100 if total_size else 0.0 percent = (
(current_size / total_size) * 100
if total_size
else 0.0
)
bytes_per_second = 0.0 bytes_per_second = 0.0
if len(progress_samples) >= 2: if len(progress_samples) >= 2:
first_time, first_bytes = progress_samples[0] first_time, first_bytes = progress_samples[0]
last_time, last_bytes = progress_samples[-1] last_time, last_bytes = progress_samples[-1]
elapsed = (last_time - first_time).total_seconds() elapsed = (last_time - first_time).total_seconds()
if elapsed > 0: if elapsed > 0:
bytes_per_second = (last_bytes - first_bytes) / elapsed bytes_per_second = (
last_bytes - first_bytes
) / elapsed
progress_snapshot = DownloadProgress( progress_snapshot = DownloadProgress(
percent_complete=percent, percent_complete=percent,
@@ -491,21 +558,23 @@ class Downloader:
timestamp=now.timestamp(), timestamp=now.timestamp(),
) )
await self._dispatch_progress_callback(progress_callback, progress_snapshot) await self._dispatch_progress_callback(
progress_callback, progress_snapshot
)
last_progress_report_time = now last_progress_report_time = now
# Download completed successfully # Download completed successfully
# Verify file size integrity before finalizing # Verify file size integrity before finalizing
final_size = os.path.getsize(part_path) if os.path.exists(part_path) else 0 final_size = (
os.path.getsize(part_path) if os.path.exists(part_path) else 0
)
expected_size = total_size if total_size > 0 else None expected_size = total_size if total_size > 0 else None
integrity_error: Optional[str] = None integrity_error: Optional[str] = None
if final_size <= 0: if final_size <= 0:
integrity_error = "Downloaded file is empty" integrity_error = "Downloaded file is empty"
elif expected_size is not None and final_size != expected_size: elif expected_size is not None and final_size != expected_size:
integrity_error = ( integrity_error = f"File size mismatch. Expected: {expected_size}, Got: {final_size}"
f"File size mismatch. Expected: {expected_size}, Got: {final_size}"
)
if integrity_error is not None: if integrity_error is not None:
logger.error( logger.error(
@@ -555,7 +624,9 @@ class Downloader:
rename_attempt = 0 rename_attempt = 0
rename_success = False rename_success = False
while rename_attempt < max_rename_attempts and not rename_success: while (
rename_attempt < max_rename_attempts and not rename_success
):
try: try:
# If the destination file exists, remove it first (Windows safe) # If the destination file exists, remove it first (Windows safe)
if os.path.exists(save_path): if os.path.exists(save_path):
@@ -566,11 +637,18 @@ class Downloader:
except PermissionError as e: except PermissionError as e:
rename_attempt += 1 rename_attempt += 1
if rename_attempt < max_rename_attempts: if rename_attempt < max_rename_attempts:
logger.info(f"File still in use, retrying rename in 2 seconds (attempt {rename_attempt}/{max_rename_attempts})") logger.info(
f"File still in use, retrying rename in 2 seconds (attempt {rename_attempt}/{max_rename_attempts})"
)
await asyncio.sleep(2) await asyncio.sleep(2)
else: else:
logger.error(f"Failed to rename file after {max_rename_attempts} attempts: {e}") logger.error(
return False, f"Failed to finalize download: {str(e)}" f"Failed to rename file after {max_rename_attempts} attempts: {e}"
)
return (
False,
f"Failed to finalize download: {str(e)}",
)
final_size = os.path.getsize(save_path) final_size = os.path.getsize(save_path)
@@ -583,8 +661,9 @@ class Downloader:
bytes_per_second=0.0, bytes_per_second=0.0,
timestamp=datetime.now().timestamp(), timestamp=datetime.now().timestamp(),
) )
await self._dispatch_progress_callback(progress_callback, final_snapshot) await self._dispatch_progress_callback(
progress_callback, final_snapshot
)
return True, save_path return True, save_path
@@ -597,7 +676,9 @@ class Downloader:
DownloadRestartRequested, DownloadRestartRequested,
) as e: ) as e:
retry_count += 1 retry_count += 1
logger.warning(f"Network error during download (attempt {retry_count}/{self.max_retries + 1}): {e}") logger.warning(
f"Network error during download (attempt {retry_count}/{self.max_retries + 1}): {e}"
)
if retry_count <= self.max_retries: if retry_count <= self.max_retries:
# Calculate delay with exponential backoff # Calculate delay with exponential backoff
@@ -615,7 +696,10 @@ class Downloader:
continue continue
else: else:
logger.error(f"Max retries exceeded for download: {e}") logger.error(f"Max retries exceeded for download: {e}")
return False, f"Network error after {self.max_retries + 1} attempts: {str(e)}" return (
False,
f"Network error after {self.max_retries + 1} attempts: {str(e)}",
)
except Exception as e: except Exception as e:
logger.error(f"Unexpected download error: {e}") logger.error(f"Unexpected download error: {e}")
@@ -645,7 +729,7 @@ class Downloader:
url: str, url: str,
use_auth: bool = False, use_auth: bool = False,
custom_headers: Optional[Dict[str, str]] = None, custom_headers: Optional[Dict[str, str]] = None,
return_headers: bool = False return_headers: bool = False,
) -> Tuple[bool, Union[bytes, str], Optional[Dict]]: ) -> Tuple[bool, Union[bytes, str], Optional[Dict]]:
""" """
Download a file to memory (for small files like preview images) Download a file to memory (for small files like preview images)
@@ -663,16 +747,22 @@ class Downloader:
session = await self.session session = await self.session
# Debug log for proxy mode at request time # Debug log for proxy mode at request time
if self.proxy_url: if self.proxy_url:
logger.debug(f"[download_to_memory] Using app-level proxy: {self.proxy_url}") logger.debug(
f"[download_to_memory] Using app-level proxy: {self.proxy_url}"
)
else: else:
logger.debug("[download_to_memory] Using system-level proxy (trust_env) if configured.") logger.debug(
"[download_to_memory] Using system-level proxy (trust_env) if configured."
)
# Prepare headers # Prepare headers
headers = self._get_auth_headers(use_auth) headers = self._get_auth_headers(use_auth)
if custom_headers: if custom_headers:
headers.update(custom_headers) headers.update(custom_headers)
async with session.get(url, headers=headers, proxy=self.proxy_url) as response: async with session.get(
url, headers=headers, proxy=self.proxy_url
) as response:
if response.status == 200: if response.status == 200:
content = await response.read() content = await response.read()
if return_headers: if return_headers:
@@ -700,7 +790,7 @@ class Downloader:
self, self,
url: str, url: str,
use_auth: bool = False, use_auth: bool = False,
custom_headers: Optional[Dict[str, str]] = None custom_headers: Optional[Dict[str, str]] = None,
) -> Tuple[bool, Union[Dict, str]]: ) -> Tuple[bool, Union[Dict, str]]:
""" """
Get response headers without downloading the full content Get response headers without downloading the full content
@@ -717,16 +807,22 @@ class Downloader:
session = await self.session session = await self.session
# Debug log for proxy mode at request time # Debug log for proxy mode at request time
if self.proxy_url: if self.proxy_url:
logger.debug(f"[get_response_headers] Using app-level proxy: {self.proxy_url}") logger.debug(
f"[get_response_headers] Using app-level proxy: {self.proxy_url}"
)
else: else:
logger.debug("[get_response_headers] Using system-level proxy (trust_env) if configured.") logger.debug(
"[get_response_headers] Using system-level proxy (trust_env) if configured."
)
# Prepare headers # Prepare headers
headers = self._get_auth_headers(use_auth) headers = self._get_auth_headers(use_auth)
if custom_headers: if custom_headers:
headers.update(custom_headers) headers.update(custom_headers)
async with session.head(url, headers=headers, proxy=self.proxy_url) as response: async with session.head(
url, headers=headers, proxy=self.proxy_url
) as response:
if response.status == 200: if response.status == 200:
return True, dict(response.headers) return True, dict(response.headers)
else: else:
@@ -742,7 +838,7 @@ class Downloader:
url: str, url: str,
use_auth: bool = False, use_auth: bool = False,
custom_headers: Optional[Dict[str, str]] = None, custom_headers: Optional[Dict[str, str]] = None,
**kwargs **kwargs,
) -> Tuple[bool, Union[Dict, str]]: ) -> Tuple[bool, Union[Dict, str]]:
""" """
Make a generic HTTP request and return JSON response Make a generic HTTP request and return JSON response
@@ -763,7 +859,9 @@ class Downloader:
if self.proxy_url: if self.proxy_url:
logger.debug(f"[make_request] Using app-level proxy: {self.proxy_url}") logger.debug(f"[make_request] Using app-level proxy: {self.proxy_url}")
else: else:
logger.debug("[make_request] Using system-level proxy (trust_env) if configured.") logger.debug(
"[make_request] Using system-level proxy (trust_env) if configured."
)
# Prepare headers # Prepare headers
headers = self._get_auth_headers(use_auth) headers = self._get_auth_headers(use_auth)
@@ -771,10 +869,12 @@ class Downloader:
headers.update(custom_headers) headers.update(custom_headers)
# Add proxy to kwargs if not already present # Add proxy to kwargs if not already present
if 'proxy' not in kwargs: if "proxy" not in kwargs:
kwargs['proxy'] = self.proxy_url kwargs["proxy"] = self.proxy_url
async with session.request(method, url, headers=headers, **kwargs) as response: async with session.request(
method, url, headers=headers, **kwargs
) as response:
if response.status == 200: if response.status == 200:
# Try to parse as JSON, fall back to text # Try to parse as JSON, fall back to text
try: try: