mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-08-28 11:36:39 +08:00
feat: enhance file upload handling and response normalization in virtual file system
This commit is contained in:
@@ -263,7 +263,17 @@ class TelegramAdapter:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await client.connect()
|
await client.connect()
|
||||||
await client.send_file(self.chat_id, file_like, caption=file_like.name)
|
sent = await client.send_file(self.chat_id, file_like, caption=file_like.name)
|
||||||
|
message = sent[0] if isinstance(sent, list) and sent else sent
|
||||||
|
actual_rel = rel
|
||||||
|
if message:
|
||||||
|
stored_name = file_like.name
|
||||||
|
file_meta = getattr(message, "file", None)
|
||||||
|
if file_meta and getattr(file_meta, "name", None):
|
||||||
|
stored_name = file_meta.name
|
||||||
|
if getattr(message, "id", None) is not None:
|
||||||
|
actual_rel = f"{message.id}_{stored_name}"
|
||||||
|
return {"rel": actual_rel, "size": len(data)}
|
||||||
finally:
|
finally:
|
||||||
if client.is_connected():
|
if client.is_connected():
|
||||||
await client.disconnect()
|
await client.disconnect()
|
||||||
@@ -285,14 +295,23 @@ class TelegramAdapter:
|
|||||||
total_size += len(chunk)
|
total_size += len(chunk)
|
||||||
|
|
||||||
await client.connect()
|
await client.connect()
|
||||||
await client.send_file(self.chat_id, temp_path, caption=filename)
|
sent = await client.send_file(self.chat_id, temp_path, caption=filename)
|
||||||
|
message = sent[0] if isinstance(sent, list) and sent else sent
|
||||||
|
actual_rel = rel
|
||||||
|
if message:
|
||||||
|
stored_name = filename
|
||||||
|
file_meta = getattr(message, "file", None)
|
||||||
|
if file_meta and getattr(file_meta, "name", None):
|
||||||
|
stored_name = file_meta.name
|
||||||
|
if getattr(message, "id", None) is not None:
|
||||||
|
actual_rel = f"{message.id}_{stored_name}"
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if os.path.exists(temp_path):
|
if os.path.exists(temp_path):
|
||||||
os.remove(temp_path)
|
os.remove(temp_path)
|
||||||
if client.is_connected():
|
if client.is_connected():
|
||||||
await client.disconnect()
|
await client.disconnect()
|
||||||
return total_size
|
return {"rel": actual_rel, "size": total_size}
|
||||||
|
|
||||||
async def mkdir(self, root: str, rel: str):
|
async def mkdir(self, root: str, rel: str):
|
||||||
raise NotImplementedError("Telegram 适配器不支持创建目录。")
|
raise NotImplementedError("Telegram 适配器不支持创建目录。")
|
||||||
|
|||||||
@@ -11,6 +11,29 @@ from .listing import VirtualFSListingMixin
|
|||||||
|
|
||||||
|
|
||||||
class VirtualFSFileOpsMixin(VirtualFSListingMixin):
|
class VirtualFSFileOpsMixin(VirtualFSListingMixin):
|
||||||
|
@classmethod
|
||||||
|
def _normalize_written_result(
|
||||||
|
cls,
|
||||||
|
original_path: str,
|
||||||
|
adapter_model: Any,
|
||||||
|
result: Any,
|
||||||
|
size_hint: int,
|
||||||
|
) -> tuple[str, int]:
|
||||||
|
final_path = original_path
|
||||||
|
size = size_hint
|
||||||
|
if isinstance(result, dict):
|
||||||
|
rel_override = result.get("rel")
|
||||||
|
if isinstance(rel_override, str) and rel_override:
|
||||||
|
final_path = cls._build_absolute_path(adapter_model.path, rel_override)
|
||||||
|
else:
|
||||||
|
path_override = result.get("path")
|
||||||
|
if isinstance(path_override, str) and path_override:
|
||||||
|
final_path = cls._normalize_path(path_override)
|
||||||
|
size_val = result.get("size")
|
||||||
|
if isinstance(size_val, int):
|
||||||
|
size = size_val
|
||||||
|
return final_path, size
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def read_file(cls, path: str) -> Union[bytes, Any]:
|
async def read_file(cls, path: str) -> Union[bytes, Any]:
|
||||||
adapter_instance, _, root, rel = await cls.resolve_adapter_and_rel(path)
|
adapter_instance, _, root, rel = await cls.resolve_adapter_and_rel(path)
|
||||||
@@ -21,16 +44,18 @@ class VirtualFSFileOpsMixin(VirtualFSListingMixin):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def write_file(cls, path: str, data: bytes):
|
async def write_file(cls, path: str, data: bytes):
|
||||||
adapter_instance, _, root, rel = await cls.resolve_adapter_and_rel(path)
|
adapter_instance, adapter_model, root, rel = await cls.resolve_adapter_and_rel(path)
|
||||||
if rel.endswith("/"):
|
if rel.endswith("/"):
|
||||||
raise HTTPException(400, detail="Invalid file path")
|
raise HTTPException(400, detail="Invalid file path")
|
||||||
write_func = await cls._ensure_method(adapter_instance, "write_file")
|
write_func = await cls._ensure_method(adapter_instance, "write_file")
|
||||||
await write_func(root, rel, data)
|
result = await write_func(root, rel, data)
|
||||||
await TaskService.trigger_tasks("file_written", path)
|
final_path, size = cls._normalize_written_result(path, adapter_model, result, len(data))
|
||||||
|
await TaskService.trigger_tasks("file_written", final_path)
|
||||||
|
return {"path": final_path, "size": size}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def write_file_stream(cls, path: str, data_iter: AsyncIterator[bytes], overwrite: bool = True):
|
async def write_file_stream(cls, path: str, data_iter: AsyncIterator[bytes], overwrite: bool = True):
|
||||||
adapter_instance, _, root, rel = await cls.resolve_adapter_and_rel(path)
|
adapter_instance, adapter_model, root, rel = await cls.resolve_adapter_and_rel(path)
|
||||||
if rel.endswith("/"):
|
if rel.endswith("/"):
|
||||||
raise HTTPException(400, detail="Invalid file path")
|
raise HTTPException(400, detail="Invalid file path")
|
||||||
exists_func = getattr(adapter_instance, "exists", None)
|
exists_func = getattr(adapter_instance, "exists", None)
|
||||||
@@ -46,18 +71,23 @@ class VirtualFSFileOpsMixin(VirtualFSListingMixin):
|
|||||||
size = 0
|
size = 0
|
||||||
stream_func = getattr(adapter_instance, "write_file_stream", None)
|
stream_func = getattr(adapter_instance, "write_file_stream", None)
|
||||||
if callable(stream_func):
|
if callable(stream_func):
|
||||||
size = await stream_func(root, rel, data_iter)
|
result = await stream_func(root, rel, data_iter)
|
||||||
|
if isinstance(result, dict):
|
||||||
|
size = int(result.get("size") or 0)
|
||||||
|
else:
|
||||||
|
size = int(result or 0)
|
||||||
else:
|
else:
|
||||||
buf = bytearray()
|
buf = bytearray()
|
||||||
async for chunk in data_iter:
|
async for chunk in data_iter:
|
||||||
if chunk:
|
if chunk:
|
||||||
buf.extend(chunk)
|
buf.extend(chunk)
|
||||||
write_func = await cls._ensure_method(adapter_instance, "write_file")
|
write_func = await cls._ensure_method(adapter_instance, "write_file")
|
||||||
await write_func(root, rel, bytes(buf))
|
result = await write_func(root, rel, bytes(buf))
|
||||||
size = len(buf)
|
size = len(buf)
|
||||||
|
|
||||||
await TaskService.trigger_tasks("file_written", path)
|
final_path, size = cls._normalize_written_result(path, adapter_model, result, size)
|
||||||
return size
|
await TaskService.trigger_tasks("file_written", final_path)
|
||||||
|
return {"path": final_path, "size": size}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def make_dir(cls, path: str):
|
async def make_dir(cls, path: str):
|
||||||
|
|||||||
@@ -225,7 +225,10 @@ class VirtualFSListingMixin(VirtualFSResolverMixin):
|
|||||||
stat_func = getattr(adapter_instance, "stat_file", None)
|
stat_func = getattr(adapter_instance, "stat_file", None)
|
||||||
if not callable(stat_func):
|
if not callable(stat_func):
|
||||||
raise HTTPException(501, detail="Adapter does not implement stat_file")
|
raise HTTPException(501, detail="Adapter does not implement stat_file")
|
||||||
info = await stat_func(root, rel)
|
try:
|
||||||
|
info = await stat_func(root, rel)
|
||||||
|
except FileNotFoundError as exc:
|
||||||
|
raise HTTPException(404, detail=str(exc))
|
||||||
|
|
||||||
if isinstance(info, dict):
|
if isinstance(info, dict):
|
||||||
info.setdefault("path", path)
|
info.setdefault("path", path)
|
||||||
|
|||||||
@@ -150,8 +150,15 @@ class VirtualFSRouteMixin(VirtualFSTempLinkMixin):
|
|||||||
@classmethod
|
@classmethod
|
||||||
async def write_uploaded_file(cls, full_path: str, data: bytes):
|
async def write_uploaded_file(cls, full_path: str, data: bytes):
|
||||||
full_path = cls._normalize_path(full_path)
|
full_path = cls._normalize_path(full_path)
|
||||||
await cls.write_file(full_path, data)
|
result = await cls.write_file(full_path, data)
|
||||||
return {"written": True, "path": full_path, "size": len(data)}
|
path = full_path
|
||||||
|
size = len(data)
|
||||||
|
if isinstance(result, dict):
|
||||||
|
path = result.get("path") or path
|
||||||
|
size_val = result.get("size")
|
||||||
|
if isinstance(size_val, int):
|
||||||
|
size = size_val
|
||||||
|
return {"written": True, "path": path, "size": size}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def mkdir(cls, path: str):
|
async def mkdir(cls, path: str):
|
||||||
@@ -227,8 +234,17 @@ class VirtualFSRouteMixin(VirtualFSTempLinkMixin):
|
|||||||
break
|
break
|
||||||
yield chunk
|
yield chunk
|
||||||
|
|
||||||
size = await cls.write_file_stream(full_path, gen(), overwrite=overwrite)
|
result = await cls.write_file_stream(full_path, gen(), overwrite=overwrite)
|
||||||
return {"uploaded": True, "path": full_path, "size": size, "overwrite": overwrite}
|
path = full_path
|
||||||
|
size = 0
|
||||||
|
if isinstance(result, dict):
|
||||||
|
path = result.get("path") or path
|
||||||
|
size_val = result.get("size")
|
||||||
|
if isinstance(size_val, int):
|
||||||
|
size = size_val
|
||||||
|
else:
|
||||||
|
size = int(result or 0)
|
||||||
|
return {"uploaded": True, "path": path, "size": size, "overwrite": overwrite}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def list_directory(cls, full_path: str, page_num: int, page_size: int, sort_by: str, sort_order: str):
|
async def list_directory(cls, full_path: str, page_num: int, page_size: int, sort_by: str, sort_order: str):
|
||||||
|
|||||||
@@ -487,7 +487,7 @@ export function useUploader(path: string, onUploadComplete: () => void) {
|
|||||||
const parentDir = task.targetPath.replace(/\/[^/]+$/, '') || '/';
|
const parentDir = task.targetPath.replace(/\/[^/]+$/, '') || '/';
|
||||||
try {
|
try {
|
||||||
await ensureDirectoryTree(parentDir);
|
await ensureDirectoryTree(parentDir);
|
||||||
await vfsApi.uploadStream(task.targetPath, task.file, shouldOverwrite, (loaded, total) => {
|
const uploadResult = await vfsApi.uploadStream(task.targetPath, task.file, shouldOverwrite, (loaded, total) => {
|
||||||
mutateFiles((prev) => prev.map((f) => {
|
mutateFiles((prev) => prev.map((f) => {
|
||||||
if (f.id !== task.id) return f;
|
if (f.id !== task.id) return f;
|
||||||
const effectiveTotal = total > 0 ? total : f.size;
|
const effectiveTotal = total > 0 ? total : f.size;
|
||||||
@@ -502,9 +502,20 @@ export function useUploader(path: string, onUploadComplete: () => void) {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
const link = await vfsApi.getTempLinkToken(task.targetPath, 60 * 60 * 24 * 365 * 10);
|
const actualPath = uploadResult?.path || task.targetPath;
|
||||||
|
const finalSize = typeof uploadResult?.size === 'number' && uploadResult.size > 0
|
||||||
|
? uploadResult.size
|
||||||
|
: task.size;
|
||||||
|
const link = await vfsApi.getTempLinkToken(actualPath, 60 * 60 * 24 * 365 * 10);
|
||||||
const permanentLink = vfsApi.getTempPublicUrl(link.token);
|
const permanentLink = vfsApi.getTempPublicUrl(link.token);
|
||||||
updateFile(task.id, { status: 'success', progress: 100, loadedBytes: task.size, permanentLink });
|
updateFile(task.id, {
|
||||||
|
status: 'success',
|
||||||
|
progress: 100,
|
||||||
|
loadedBytes: finalSize,
|
||||||
|
size: finalSize,
|
||||||
|
targetPath: actualPath,
|
||||||
|
permanentLink,
|
||||||
|
});
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const error = err instanceof Error ? err.message : t('Upload failed');
|
const error = err instanceof Error ? err.message : t('Upload failed');
|
||||||
updateFile(task.id, { status: 'error', error, progress: 0 });
|
updateFile(task.id, { status: 'error', error, progress: 0 });
|
||||||
|
|||||||
Reference in New Issue
Block a user