合并 WebDAV 上的分片文件 使用临时文件避免内存问题
(self, upload_id: str, chunk_info: UploadChunk, save_path: str)
| 1213 | ) |
| 1214 | |
| 1215 | async def merge_chunks(self, upload_id: str, chunk_info: UploadChunk, save_path: str) -> tuple[str, str]: |
| 1216 | """ |
| 1217 | 合并 WebDAV 上的分片文件 |
| 1218 | 使用临时文件避免内存问题 |
| 1219 | """ |
| 1220 | file_sha256 = hashlib.sha256() |
| 1221 | chunk_dir = str(Path(save_path).parent / "chunks" / upload_id) |
| 1222 | |
| 1223 | # 使用临时文件存储合并数据,避免内存问题 |
| 1224 | with tempfile.NamedTemporaryFile(delete=False) as temp_file: |
| 1225 | temp_path = temp_file.name |
| 1226 | |
| 1227 | try: |
| 1228 | async with aiohttp.ClientSession(auth=self.auth) as session: |
| 1229 | # 按顺序读取并验证每个分片,写入临时文件 |
| 1230 | async with aiofiles.open(temp_path, 'wb') as out_file: |
| 1231 | for i in range(chunk_info.total_chunks): |
| 1232 | chunk_path = f"{chunk_dir}/{i}.part" |
| 1233 | chunk_url = self._build_url(chunk_path) |
| 1234 | |
| 1235 | # 获取分片记录 |
| 1236 | chunk_record = await UploadChunk.filter(upload_id=upload_id, chunk_index=i).first() |
| 1237 | if not chunk_record: |
| 1238 | raise ValueError(f"分片{i}记录不存在") |
| 1239 | |
| 1240 | # 下载分片数据 |
| 1241 | async with session.get(chunk_url) as resp: |
| 1242 | if resp.status != 200: |
| 1243 | raise ValueError(f"分片{i}文件不存在或无法访问") |
| 1244 | chunk_data = await resp.read() |
| 1245 | |
| 1246 | # 验证哈希 |
| 1247 | current_hash = hashlib.sha256(chunk_data).hexdigest() |
| 1248 | if current_hash != chunk_record.chunk_hash: |
| 1249 | raise ValueError(f"分片{i}哈希不匹配: 期望 {chunk_record.chunk_hash}, 实际 {current_hash}") |
| 1250 | |
| 1251 | file_sha256.update(chunk_data) |
| 1252 | await out_file.write(chunk_data) |
| 1253 | del chunk_data # 释放内存 |
| 1254 | |
| 1255 | # 确保目标目录存在 |
| 1256 | output_dir = str(Path(save_path).parent) |
| 1257 | await self._mkdir_p(output_dir) |
| 1258 | |
| 1259 | # 流式上传合并后的文件 |
| 1260 | output_url = self._build_url(save_path) |
| 1261 | |
| 1262 | async def file_sender(): |
| 1263 | async with aiofiles.open(temp_path, 'rb') as f: |
| 1264 | while True: |
| 1265 | chunk = await f.read(256 * 1024) |
| 1266 | if not chunk: |
| 1267 | break |
| 1268 | yield chunk |
| 1269 | |
| 1270 | async with session.put(output_url, data=file_sender()) as resp: |
| 1271 | if resp.status not in (200, 201, 204): |
| 1272 | content = await resp.text() |