Skip to content

Commit 8030aca

Browse files
committed
feat(download): 链接校验和下载原子化
1 parent 3117b83 commit 8030aca

1 file changed

Lines changed: 36 additions & 12 deletions

File tree

‎src/one_dragon/utils/http_utils.py‎

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import tempfile
12
import time
3+
import urllib.parse
24
import urllib.request
35
from collections.abc import Callable
46
from pathlib import Path
@@ -28,6 +30,7 @@ def download_file(download_url: str, save_file_path: str,
2830
last_log_time = time.time()
2931
save_path = Path(save_file_path)
3032
save_path.parent.mkdir(parents=True, exist_ok=True)
33+
temp_path: Path | None = None
3134

3235
def log_download_progress(downloaded_bytes: int, total_size: int) -> None:
3336
nonlocal last_log_time
@@ -55,43 +58,64 @@ def log_download_progress(downloaded_bytes: int, total_size: int) -> None:
5558
if progress_callback is not None:
5659
progress_callback(0, msg)
5760

61+
url = urllib.parse.urlparse(download_url)
62+
if url.scheme not in ('http', 'https'):
63+
raise ValueError(f"不支持的下载协议:{download_url}")
64+
5865
request = urllib.request.Request(download_url)
59-
with opener.open(request, timeout=60) as response, save_path.open('wb') as file:
66+
with opener.open(request, timeout=60) as response:
6067
total_size = int(response.headers.get('Content-Length', '0') or 0)
6168
downloaded_bytes = 0
6269
chunk_size = 1024 * 64
6370

64-
while True:
65-
if progress_signal is not None and progress_signal.get('signal') == 'cancel':
66-
raise DownloadCancelledError("下载已取消")
71+
with tempfile.NamedTemporaryFile('wb', dir=save_path.parent, delete=False) as file:
72+
temp_path = Path(file.name)
73+
while True:
74+
if progress_signal is not None and progress_signal.get('signal') == 'cancel':
75+
raise DownloadCancelledError("下载已取消")
76+
77+
chunk = response.read(chunk_size)
78+
if not chunk:
79+
break
6780

68-
chunk = response.read(chunk_size)
69-
if not chunk:
70-
break
81+
file.write(chunk)
82+
downloaded_bytes += len(chunk)
83+
log_download_progress(downloaded_bytes, total_size)
7184

72-
file.write(chunk)
73-
downloaded_bytes += len(chunk)
74-
log_download_progress(downloaded_bytes, total_size)
85+
if total_size > 0 and downloaded_bytes != total_size:
86+
raise DownloadIncompleteError(
87+
f"下载不完整:{downloaded_bytes}/{total_size} bytes"
88+
)
89+
90+
temp_path.replace(save_path)
91+
temp_path = None
7592

7693
msg = f"{gt('下载完成')} {save_file_path}"
7794
log.info(msg)
7895
if progress_callback is not None:
7996
progress_callback(1, msg)
8097
return True
8198
except DownloadCancelledError:
82-
save_path.unlink(missing_ok=True)
99+
if temp_path is not None:
100+
temp_path.unlink(missing_ok=True)
83101
msg = f"{gt('下载已取消')}"
84102
log.info(msg)
85103
if progress_callback is not None:
86104
progress_callback(0, msg)
87105
return False
88106
except Exception as e:
89-
save_path.unlink(missing_ok=True)
107+
if temp_path is not None:
108+
temp_path.unlink(missing_ok=True)
90109
msg = f"{gt('下载失败')} {e}"
91110
if progress_callback is not None:
92111
progress_callback(0, msg)
93112
log.error(msg, exc_info=True)
94113
return False
95114

115+
96116
class DownloadCancelledError(Exception):
97117
pass
118+
119+
120+
class DownloadIncompleteError(Exception):
121+
pass

0 commit comments

Comments
 (0)