mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
fix(download): recover stalled transfers automatically
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from types import SimpleNamespace
|
||||
@@ -8,6 +9,7 @@ from unittest.mock import AsyncMock
|
||||
import pytest
|
||||
|
||||
from py.services.download_manager import DownloadManager
|
||||
from py.services.downloader import DownloadStreamControl
|
||||
from py.services import download_manager
|
||||
from py.services.service_registry import ServiceRegistry
|
||||
from py.services.settings_manager import SettingsManager, get_settings_manager
|
||||
@@ -528,9 +530,8 @@ async def test_pause_download_updates_state():
|
||||
|
||||
download_id = "dl"
|
||||
manager._download_tasks[download_id] = object()
|
||||
pause_event = asyncio.Event()
|
||||
pause_event.set()
|
||||
manager._pause_events[download_id] = pause_event
|
||||
pause_control = DownloadStreamControl()
|
||||
manager._pause_events[download_id] = pause_control
|
||||
manager._active_downloads[download_id] = {
|
||||
"status": "downloading",
|
||||
"bytes_per_second": 42.0,
|
||||
@@ -557,8 +558,10 @@ async def test_resume_download_sets_event_and_status():
|
||||
manager = DownloadManager()
|
||||
|
||||
download_id = "dl"
|
||||
pause_event = asyncio.Event()
|
||||
manager._pause_events[download_id] = pause_event
|
||||
pause_control = DownloadStreamControl()
|
||||
pause_control.pause()
|
||||
pause_control.mark_progress()
|
||||
manager._pause_events[download_id] = pause_control
|
||||
manager._active_downloads[download_id] = {
|
||||
"status": "paused",
|
||||
"bytes_per_second": 0.0,
|
||||
@@ -571,13 +574,32 @@ async def test_resume_download_sets_event_and_status():
|
||||
assert manager._active_downloads[download_id]["status"] == "downloading"
|
||||
|
||||
|
||||
async def test_resume_download_requests_reconnect_for_stalled_stream():
|
||||
manager = DownloadManager()
|
||||
|
||||
download_id = "dl"
|
||||
pause_control = DownloadStreamControl(stall_timeout=40)
|
||||
pause_control.pause()
|
||||
pause_control.last_progress_timestamp = (datetime.now().timestamp() - 120)
|
||||
manager._pause_events[download_id] = pause_control
|
||||
manager._active_downloads[download_id] = {
|
||||
"status": "paused",
|
||||
"bytes_per_second": 0.0,
|
||||
}
|
||||
|
||||
result = await manager.resume_download(download_id)
|
||||
|
||||
assert result == {"success": True, "message": "Download resumed successfully"}
|
||||
assert pause_control.is_set() is True
|
||||
assert pause_control.has_reconnect_request() is True
|
||||
|
||||
|
||||
async def test_resume_download_rejects_when_not_paused():
|
||||
manager = DownloadManager()
|
||||
|
||||
download_id = "dl"
|
||||
pause_event = asyncio.Event()
|
||||
pause_event.set()
|
||||
manager._pause_events[download_id] = pause_event
|
||||
pause_control = DownloadStreamControl()
|
||||
manager._pause_events[download_id] = pause_control
|
||||
|
||||
result = await manager.resume_download(download_id)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Sequence
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -8,13 +9,24 @@ from py.services.downloader import Downloader
|
||||
|
||||
|
||||
class FakeStream:
|
||||
def __init__(self, chunks):
|
||||
def __init__(self, chunks: Sequence[Sequence] | Sequence[bytes]):
|
||||
self._chunks = list(chunks)
|
||||
|
||||
async def iter_chunked(self, _chunk_size):
|
||||
for chunk in self._chunks:
|
||||
async def read(self, _chunk_size: int) -> bytes:
|
||||
if not self._chunks:
|
||||
await asyncio.sleep(0)
|
||||
yield chunk
|
||||
return b""
|
||||
|
||||
item = self._chunks.pop(0)
|
||||
delay = 0.0
|
||||
payload = item
|
||||
|
||||
if isinstance(item, tuple):
|
||||
payload = item[0]
|
||||
delay = item[1]
|
||||
|
||||
await asyncio.sleep(delay)
|
||||
return payload
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
@@ -53,6 +65,12 @@ def _build_downloader(responses, *, max_retries=0):
|
||||
downloader._session = fake_session
|
||||
downloader._session_created_at = datetime.now()
|
||||
downloader._proxy_url = None
|
||||
async def _noop_create_session():
|
||||
downloader._session = fake_session
|
||||
downloader._session_created_at = datetime.now()
|
||||
downloader._proxy_url = None
|
||||
|
||||
downloader._create_session = _noop_create_session # type: ignore[assignment]
|
||||
return downloader
|
||||
|
||||
|
||||
@@ -123,3 +141,34 @@ async def test_download_file_succeeds_when_sizes_match(tmp_path):
|
||||
assert success is True
|
||||
assert Path(result_path).read_bytes() == payload
|
||||
assert not Path(str(target_path) + ".part").exists()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_download_file_recovers_from_stall(tmp_path):
|
||||
target_path = tmp_path / "model" / "file.bin"
|
||||
target_path.parent.mkdir()
|
||||
|
||||
payload = b"abcdef"
|
||||
|
||||
responses = [
|
||||
lambda: FakeResponse(
|
||||
status=200,
|
||||
headers={"content-length": str(len(payload))},
|
||||
chunks=[(b"abc", 0.0), (b"def", 0.1)],
|
||||
),
|
||||
lambda: FakeResponse(
|
||||
status=206,
|
||||
headers={"content-length": "3", "Content-Range": "bytes 3-5/6"},
|
||||
chunks=[(b"def", 0.0)],
|
||||
),
|
||||
]
|
||||
|
||||
downloader = _build_downloader(responses, max_retries=1)
|
||||
downloader.stall_timeout = 0.05
|
||||
|
||||
success, result_path = await downloader.download_file("https://example.com/file", str(target_path))
|
||||
|
||||
assert success is True
|
||||
assert Path(result_path).read_bytes() == payload
|
||||
assert downloader._session._get_calls == 2
|
||||
assert not Path(str(target_path) + ".part").exists()
|
||||
|
||||
Reference in New Issue
Block a user