Skip to content

Commit ba73e9b

Browse files
committed
update
1 parent 80e9469 commit ba73e9b

File tree

4 files changed

+222
-15
lines changed

4 files changed

+222
-15
lines changed

flaxfile/src/flaxfile/cli.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -660,11 +660,9 @@ def pull(
660660
password: 密码(可选)
661661
662662
示例:
663-
flaxfile sync pull myproject
664-
flaxfile sync pull myproject /path/to/download
665-
flaxfile sync pull myproject /path/to/download --server prod
666-
667-
注意: 当前版本暂不支持,待实现
663+
flaxfile sync pull my_project
664+
flaxfile sync pull my_project /path/to/download
665+
flaxfile sync pull my_project /path/to/download --server prod
668666
"""
669667
from .sync import pull_directory
670668

flaxfile/src/flaxfile/client.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,37 @@ async def delete_file(self, file_key: str) -> bool:
373373

374374
return frames[1] == b'OK'
375375

376+
async def list_files(self, prefix: str = "") -> list:
377+
"""
378+
列出服务器上的文件
379+
380+
Args:
381+
prefix: 文件前缀(可选,用于过滤)
382+
383+
Returns:
384+
文件列表,每个文件包含 key, size, mtime
385+
"""
386+
await self.connect()
387+
388+
await self.socket.send_multipart([b'', b'LIST', prefix.encode('utf-8')])
389+
frames = await self.socket.recv_multipart()
390+
391+
if len(frames) < 2:
392+
raise Exception("服务器响应无效")
393+
394+
if frames[1] == b'ERROR':
395+
error_msg = frames[2].decode('utf-8') if len(frames) > 2 else "Unknown error"
396+
raise Exception(f"列出文件失败: {error_msg}")
397+
398+
if frames[1] != b'OK':
399+
raise Exception(f"列出文件失败: {frames[1]}")
400+
401+
# 解析文件列表
402+
files_json = frames[2].decode('utf-8')
403+
files = json.loads(files_json)
404+
405+
return files
406+
376407
async def close(self):
377408
"""关闭连接"""
378409
if self.socket:
@@ -432,6 +463,10 @@ def delete_file(self, file_key: str) -> bool:
432463
"""删除文件 (同步)"""
433464
return asyncio.run(self.async_client.delete_file(file_key))
434465

466+
def list_files(self, prefix: str = "") -> list:
467+
"""列出文件 (同步)"""
468+
return asyncio.run(self.async_client.list_files(prefix))
469+
435470
def close(self):
436471
"""关闭连接 (同步)"""
437472
asyncio.run(self.async_client.close())

flaxfile/src/flaxfile/server.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ async def handle_command(self, identity: bytes, command: bytes, args: list):
143143
elif command == b'DELETE':
144144
await self.handle_delete(identity, args)
145145

146+
elif command == b'LIST':
147+
await self.handle_list(identity, args)
148+
146149
else:
147150
logger.warning(f"未知命令: {command}")
148151
await self.socket.send_multipart([identity, b'', b'ERROR', b'Unknown command'])
@@ -323,6 +326,44 @@ async def handle_delete(self, identity: bytes, args: list):
323326
logger.error(f"删除失败: {e}")
324327
await self.socket.send_multipart([identity, b'', b'ERROR', str(e).encode('utf-8')])
325328

329+
async def handle_list(self, identity: bytes, args: list):
330+
"""列出指定前缀下的所有文件"""
331+
# 获取前缀(可选)
332+
prefix = args[0].decode('utf-8') if args else ""
333+
334+
try:
335+
files_info = []
336+
337+
# 遍历存储目录
338+
for file_path in STORAGE_DIR.rglob('*'):
339+
if file_path.is_file():
340+
# 计算相对路径
341+
relative_path = file_path.relative_to(STORAGE_DIR)
342+
key = str(relative_path)
343+
344+
# 如果指定了前缀,只返回匹配的文件
345+
if prefix and not key.startswith(prefix):
346+
continue
347+
348+
# 获取文件信息
349+
stat = file_path.stat()
350+
files_info.append({
351+
'key': key,
352+
'size': stat.st_size,
353+
'mtime': stat.st_mtime
354+
})
355+
356+
# 序列化文件列表
357+
import json
358+
files_json = json.dumps(files_info).encode('utf-8')
359+
360+
logger.info(f"📋 列出文件: 前缀='{prefix}', 数量={len(files_info)}")
361+
await self.socket.send_multipart([identity, b'', b'OK', files_json])
362+
363+
except Exception as e:
364+
logger.error(f"列出文件失败: {e}")
365+
await self.socket.send_multipart([identity, b'', b'ERROR', str(e).encode('utf-8')])
366+
326367
async def stop(self):
327368
"""停止服务器"""
328369
# 关闭所有活跃的上传

flaxfile/src/flaxfile/sync.py

Lines changed: 143 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,12 @@ def push_directory(
179179
console.print(f"[cyan]📊 总大小: {total_bytes / (1024*1024):.2f} MB")
180180
console.print()
181181

182-
# 3. 上传所有文件
182+
# 3. 先连接到服务器(在显示进度条之前完成密码输入)
183+
console.print("[cyan]🔗 连接到服务器...")
184+
client.connect()
185+
console.print()
186+
187+
# 4. 上传所有文件
183188
uploaded = 0
184189
failed = 0
185190
failed_files = []
@@ -264,9 +269,6 @@ def pull_directory(
264269
"""
265270
从服务器下载目录到本地
266271
267-
注意:当前实现需要客户端维护远程文件列表
268-
后续可以添加服务器端 LIST 命令来优化
269-
270272
Args:
271273
client: FlaxFileClient 实例
272274
remote_dir: 远程目录名称
@@ -277,12 +279,143 @@ def pull_directory(
277279
Returns:
278280
同步结果统计
279281
"""
280-
console.print("[yellow]⚠️ pull 功能需要服务器支持文件列表功能")
281-
console.print("[yellow] 当前版本暂不支持,请等待后续更新")
282+
# 1. 先连接到服务器(在显示进度条之前完成密码输入)
283+
console.print("[cyan]🔗 连接到服务器...")
284+
client.connect()
285+
console.print()
286+
287+
# 2. 列出远程文件
288+
console.print(f"[cyan]📋 获取远程文件列表: {remote_dir}/")
289+
290+
try:
291+
files = client.list_files(prefix=remote_dir)
292+
except Exception as e:
293+
console.print(f"[red]✗ 获取文件列表失败: {e}")
294+
return {
295+
'total_files': 0,
296+
'downloaded': 0,
297+
'failed': 0,
298+
'total_bytes': 0
299+
}
300+
301+
if not files:
302+
console.print("[yellow]⚠️ 远程目录为空或不存在")
303+
return {
304+
'total_files': 0,
305+
'downloaded': 0,
306+
'failed': 0,
307+
'total_bytes': 0
308+
}
309+
310+
console.print(f"[green]✓ 发现 {len(files)} 个文件")
311+
312+
# 3. 计算总大小
313+
total_bytes = sum(f['size'] for f in files)
314+
console.print(f"[cyan]📊 总大小: {total_bytes / (1024*1024):.2f} MB")
315+
console.print()
316+
317+
# 4. 创建本地目录
318+
local_dir_path = Path(local_dir)
319+
local_dir_path.mkdir(parents=True, exist_ok=True)
320+
321+
# 5. 下载所有文件
322+
downloaded = 0
323+
failed = 0
324+
failed_files = []
325+
326+
if show_progress:
327+
with Progress(
328+
SpinnerColumn(),
329+
TextColumn("[bold blue]{task.description}"),
330+
BarColumn(),
331+
TaskProgressColumn(),
332+
TransferSpeedColumn(),
333+
TimeRemainingColumn(),
334+
console=console,
335+
) as progress:
336+
main_task = progress.add_task(
337+
f"[cyan]下载 {remote_dir}/",
338+
total=len(files)
339+
)
340+
341+
for file_info in files:
342+
remote_key = file_info['key']
343+
344+
# 计算本地路径(移除远程目录前缀)
345+
if remote_key.startswith(remote_dir + '/'):
346+
rel_path = remote_key[len(remote_dir) + 1:]
347+
elif remote_key.startswith(remote_dir):
348+
rel_path = remote_key[len(remote_dir):]
349+
else:
350+
rel_path = remote_key
351+
352+
local_path = local_dir_path / rel_path
353+
354+
try:
355+
# 更新当前文件描述
356+
progress.update(
357+
main_task,
358+
description=f"[cyan]下载: {rel_path}"
359+
)
360+
361+
# 创建父目录
362+
local_path.parent.mkdir(parents=True, exist_ok=True)
363+
364+
# 下载文件(不显示单文件进度,避免刷屏)
365+
client.download_file(remote_key, str(local_path), show_progress=False)
366+
downloaded += 1
367+
368+
except Exception as e:
369+
failed += 1
370+
failed_files.append((rel_path, str(e)))
371+
console.print(f"[red]✗ 下载失败: {rel_path} - {e}")
372+
373+
# 更新进度
374+
progress.update(main_task, advance=1)
375+
376+
else:
377+
# 无进度条模式
378+
for file_info in files:
379+
remote_key = file_info['key']
380+
381+
# 计算本地路径
382+
if remote_key.startswith(remote_dir + '/'):
383+
rel_path = remote_key[len(remote_dir) + 1:]
384+
elif remote_key.startswith(remote_dir):
385+
rel_path = remote_key[len(remote_dir):]
386+
else:
387+
rel_path = remote_key
388+
389+
local_path = local_dir_path / rel_path
390+
391+
try:
392+
console.print(f"[cyan]下载: {rel_path}")
393+
394+
# 创建父目录
395+
local_path.parent.mkdir(parents=True, exist_ok=True)
396+
397+
# 下载文件
398+
client.download_file(remote_key, str(local_path), show_progress=False)
399+
downloaded += 1
400+
401+
except Exception as e:
402+
failed += 1
403+
failed_files.append((rel_path, str(e)))
404+
console.print(f"[red]✗ 下载失败: {rel_path} - {e}")
405+
406+
# 6. 显示结果
407+
console.print()
408+
if failed == 0:
409+
console.print(f"[bold green]✓ 同步完成! 成功下载 {downloaded} 个文件到 {local_dir}")
410+
else:
411+
console.print(f"[yellow]⚠️ 同步完成,但有 {failed} 个文件失败:")
412+
for rel_path, error in failed_files:
413+
console.print(f" [red]✗ {rel_path}: {error}")
282414

283415
return {
284-
'total_files': 0,
285-
'downloaded': 0,
286-
'failed': 0,
287-
'total_bytes': 0
416+
'total_files': len(files),
417+
'downloaded': downloaded,
418+
'failed': failed,
419+
'failed_files': failed_files,
420+
'total_bytes': total_bytes
288421
}

0 commit comments

Comments
 (0)