目录功能扩展: 文件挂目录 (上传 --dir + init dir_id), --list 显示目录+文件归属, --move/--clone

This commit is contained in:
lou
2026-08-10 22:04:17 +08:00
parent c005446ed5
commit b445b3ab30
+50 -1
View File
@@ -214,6 +214,21 @@ class TransferClient:
if not payload.get("ok"):
raise TransferError(f"删除目录失败: {payload}")
def move_file(self, file_id: str, dir_id: int) -> None:
"""PUT /api/files/{id}/move: 移动文件到目录 (0=根)"""
code, payload = self._request(
"PUT", f"/api/files/{file_id}/move", json_body={"dir_id": dir_id})
if code != 200:
raise TransferError(f"移动失败 (HTTP {code}): {payload}")
def clone_file(self, file_id: str, dir_id: int) -> dict[str, Any]:
"""POST /api/files/{id}/clone: 克隆文件到目录 (0=根)"""
code, payload = self._request(
"POST", f"/api/files/{file_id}/clone", json_body={"dir_id": dir_id})
if code != 200:
raise TransferError(f"克隆失败 (HTTP {code}): {payload}")
return payload
def download(self, file_id: str, dest: Path | None = None) -> tuple[Path, dict[str, Any]]:
"""逐卷下载密文 (零合并: 服务端只存卷, 客户端本地拼接), 返回 (密文路径, 解密参数)。
@@ -715,6 +730,9 @@ def main() -> int:
ap.add_argument("--dir-create", metavar="名称", help="创建目录 (按用户隔离)")
ap.add_argument("--dir-delete", metavar="DIR_ID", help="删除目录")
ap.add_argument("--dir-list", action="store_true", help="列出目录")
ap.add_argument("--dir", type=int, metavar="DIR_ID", help="上传到指定目录 (默认根目录)")
ap.add_argument("--move", nargs=2, metavar=("文件序号/ID", "DIR_ID|0"), help="移动文件到目录 (0=根)")
ap.add_argument("--clone", nargs=2, metavar=("文件序号/ID", "DIR_ID|0"), help="克隆文件到目录 (0=根)")
args = ap.parse_args()
server = args.server or _config_server()
@@ -796,16 +814,23 @@ def main() -> int:
if args.list:
try:
files = client.list_files()
dirs = client.list_dirs()
except TransferError as e:
print(f"[错误] {e}")
return 1
if dirs:
print(f"[目录] 共 {len(dirs)} 个:")
for d in dirs:
cnt = sum(1 for f in files if f.get("dir_id") == d["id"])
print(f" {d['id']:>2}. {d['name']} ({cnt} 个文件)")
print(f"[文件] 共 {len(files)} 个:")
for i, f in enumerate(files, 1):
size_mb = f["size"] / 1048576
name = _decrypt_name(f["file_name"]) or f["file_name"]
if name.startswith("enc:"):
name = name[:22] + "..." # 加密名截断 (keyring 无对应密钥)
print(f" {i:>2}. {name} ({size_mb:.1f} MB)")
loc = f"[{f.get('dir_name') or '/'}] "
print(f" {i:>2}. {loc}{name} ({size_mb:.1f} MB)")
return 0
if args.download:
@@ -918,10 +943,34 @@ def main() -> int:
print(f"[删除] 已删除: {args.delete}")
return 0
if args.move:
try:
src, dst = args.move
fid = _resolve_id(client, src)
client.move_file(fid, int(dst))
except (TransferError, ValueError) as e:
print(f"[错误] {e}")
return 1
print(f"[移动] {src} -> 目录 {dst}")
return 0
if args.clone:
try:
src, dst = args.clone
fid = _resolve_id(client, src)
payload = client.clone_file(fid, int(dst))
except (TransferError, ValueError) as e:
print(f"[错误] {e}")
return 1
print(f"[克隆] {src} -> 目录 {dst} (新 file_id={payload['file_id']})")
return 0
if not (args.init and args.manifest):
ap.error("需指定 --list / --quota / --download / --delete / (--init + --manifest) 之一")
init_json = json.loads(Path(args.init).read_text(encoding="utf-8"))
if args.dir is not None:
init_json["dir_id"] = args.dir # 上传到指定目录 (服务端校验归属)
manifest = json.loads(Path(args.manifest).read_text(encoding="utf-8"))
chunk_files = {
ch["index"]: Path(ch["filename"]).resolve()