feat(synctool): 增加同步功能
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import pathlib
|
||||
import time
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Horizontal, ScrollableContainer
|
||||
@@ -18,22 +19,304 @@ class SyncScreen(Screen):
|
||||
|
||||
def __init__(self, nucleons: list = [], desc: str = ""):
|
||||
super().__init__(name=None, id=None, classes=None)
|
||||
self.sync_service = None
|
||||
self.sync_config = {}
|
||||
self.is_syncing = False
|
||||
self.is_paused = False
|
||||
self.log_messages = []
|
||||
self.max_log_lines = 50
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header(show_clock=True)
|
||||
with ScrollableContainer(id="sync_container"):
|
||||
pass
|
||||
# 标题和连接状态
|
||||
yield Static("WebDAV 同步工具", classes="title")
|
||||
yield Static("", id="status_label", classes="status")
|
||||
|
||||
# 配置信息
|
||||
yield Static("服务器配置", classes="section_title")
|
||||
with Horizontal(classes="config_info"):
|
||||
yield Static("URL:", classes="config_label")
|
||||
yield Static("", id="server_url", classes="config_value")
|
||||
with Horizontal(classes="config_info"):
|
||||
yield Static("远程路径:", classes="config_label")
|
||||
yield Static("", id="remote_path", classes="config_value")
|
||||
with Horizontal(classes="config_info"):
|
||||
yield Static("同步模式:", classes="config_label")
|
||||
yield Static("", id="sync_mode", classes="config_value")
|
||||
|
||||
# 控制按钮
|
||||
yield Static("控制面板", classes="section_title")
|
||||
with Horizontal(classes="control_buttons"):
|
||||
yield Button("测试连接", id="test_connection", variant="primary")
|
||||
yield Button("开始同步", id="start_sync", variant="success")
|
||||
yield Button("暂停", id="pause_sync", variant="warning", disabled=True)
|
||||
yield Button("取消", id="cancel_sync", variant="error", disabled=True)
|
||||
|
||||
# 进度显示
|
||||
yield Static("同步进度", classes="section_title")
|
||||
yield ProgressBar(id="progress_bar", show_percentage=True, total=100)
|
||||
yield Static("", id="progress_label", classes="progress_text")
|
||||
|
||||
# 日志输出
|
||||
yield Static("同步日志", classes="section_title")
|
||||
yield Static("", id="log_output", classes="log_output")
|
||||
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self):
|
||||
"""挂载时初始化状态"""
|
||||
self.load_config()
|
||||
self.update_ui_from_config()
|
||||
self.log_message("同步工具已启动")
|
||||
|
||||
def load_config(self):
|
||||
"""从配置文件加载同步设置"""
|
||||
try:
|
||||
from heurams.context import config_var
|
||||
config_data = config_var.get().data
|
||||
self.sync_config = config_data.get('sync', {}).get('webdav', {})
|
||||
|
||||
# 创建同步服务实例
|
||||
from heurams.services.sync_service import create_sync_service_from_config
|
||||
self.sync_service = create_sync_service_from_config()
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"加载配置失败: {e}", is_error=True)
|
||||
self.sync_config = {}
|
||||
|
||||
def update_ui_from_config(self):
|
||||
"""更新 UI 显示配置信息"""
|
||||
try:
|
||||
# 更新服务器 URL
|
||||
url = self.sync_config.get('url', '未配置')
|
||||
url_widget = self.query_one("#server_url")
|
||||
url_widget.update(url if url else '未配置') # type: ignore
|
||||
|
||||
# 更新远程路径
|
||||
remote_path = self.sync_config.get('remote_path', '/heurams/')
|
||||
path_widget = self.query_one("#remote_path")
|
||||
path_widget.update(remote_path) # type: ignore
|
||||
|
||||
# 更新同步模式
|
||||
sync_mode = self.sync_config.get('sync_mode', 'bidirectional')
|
||||
mode_widget = self.query_one("#sync_mode")
|
||||
mode_map = {
|
||||
'bidirectional': '双向同步',
|
||||
'upload_only': '仅上传',
|
||||
'download_only': '仅下载',
|
||||
}
|
||||
mode_widget.update(mode_map.get(sync_mode, sync_mode)) # type: ignore
|
||||
|
||||
# 更新状态标签
|
||||
status_widget = self.query_one("#status_label")
|
||||
if self.sync_service and self.sync_service.client:
|
||||
status_widget.update("✅ 同步服务已就绪") # type: ignore
|
||||
status_widget.add_class("ready")
|
||||
else:
|
||||
status_widget.update("❌ 同步服务未配置或未启用") # type: ignore
|
||||
status_widget.add_class("error")
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"更新 UI 失败: {e}", is_error=True)
|
||||
|
||||
def update_status(self, status, current_item="", progress=None):
|
||||
"""更新状态显示"""
|
||||
try:
|
||||
status_widget = self.query_one("#status_label")
|
||||
status_widget.update(status) # type: ignore
|
||||
|
||||
if progress is not None:
|
||||
progress_bar = self.query_one("#progress_bar")
|
||||
progress_bar.progress = progress # type: ignore
|
||||
|
||||
progress_label = self.query_one("#progress_label")
|
||||
progress_label.update(f"{progress}% - {current_item}" if current_item else f"{progress}%") # type: ignore
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"更新状态失败: {e}", is_error=True)
|
||||
|
||||
def log_message(self, message: str, is_error: bool = False):
|
||||
"""添加日志消息并更新显示"""
|
||||
timestamp = time.strftime("%H:%M:%S")
|
||||
prefix = "[ERROR]" if is_error else "[INFO]"
|
||||
log_line = f"{timestamp} {prefix} {message}"
|
||||
|
||||
self.log_messages.append(log_line)
|
||||
# 保持日志行数不超过最大值
|
||||
if len(self.log_messages) > self.max_log_lines:
|
||||
self.log_messages = self.log_messages[-self.max_log_lines:]
|
||||
|
||||
# 更新日志显示
|
||||
try:
|
||||
log_widget = self.query_one("#log_output")
|
||||
log_widget.update("\n".join(self.log_messages)) # type: ignore
|
||||
except Exception:
|
||||
pass # 如果组件未就绪,忽略错误
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
"""处理按钮点击事件"""
|
||||
button_id = event.button.id
|
||||
|
||||
if button_id == "test_connection":
|
||||
self.test_connection()
|
||||
elif button_id == "start_sync":
|
||||
self.start_sync()
|
||||
elif button_id == "pause_sync":
|
||||
self.pause_sync()
|
||||
elif button_id == "cancel_sync":
|
||||
self.cancel_sync()
|
||||
|
||||
event.stop()
|
||||
|
||||
def test_connection(self):
|
||||
"""测试 WebDAV 服务器连接"""
|
||||
if not self.sync_service:
|
||||
self.log_message("同步服务未初始化,请检查配置", is_error=True)
|
||||
self.update_status("❌ 同步服务未初始化")
|
||||
return
|
||||
|
||||
self.log_message("正在测试 WebDAV 连接...")
|
||||
self.update_status("正在测试连接...")
|
||||
|
||||
try:
|
||||
success = self.sync_service.test_connection()
|
||||
if success:
|
||||
self.log_message("连接测试成功")
|
||||
self.update_status("✅ 连接正常")
|
||||
else:
|
||||
self.log_message("连接测试失败", is_error=True)
|
||||
self.update_status("❌ 连接失败")
|
||||
except Exception as e:
|
||||
self.log_message(f"连接测试异常: {e}", is_error=True)
|
||||
self.update_status("❌ 连接异常")
|
||||
|
||||
def start_sync(self):
|
||||
"""开始同步"""
|
||||
if not self.sync_service:
|
||||
self.log_message("同步服务未初始化,无法开始同步", is_error=True)
|
||||
return
|
||||
|
||||
if self.is_syncing:
|
||||
self.log_message("同步已在进行中", is_error=True)
|
||||
return
|
||||
|
||||
self.is_syncing = True
|
||||
self.is_paused = False
|
||||
self.update_button_states()
|
||||
|
||||
self.log_message("开始同步数据...")
|
||||
self.update_status("正在同步...", progress=0)
|
||||
|
||||
# 启动后台同步任务
|
||||
self.run_worker(self.perform_sync, thread=True)
|
||||
|
||||
def perform_sync(self):
|
||||
"""执行同步任务(在后台线程中运行)"""
|
||||
worker = get_current_worker()
|
||||
|
||||
try:
|
||||
# 获取需要同步的本地目录
|
||||
from heurams.context import config_var
|
||||
config = config_var.get()
|
||||
paths = config.get('paths', {})
|
||||
|
||||
# 同步 nucleon 目录
|
||||
nucleon_dir = pathlib.Path(paths.get('nucleon_dir', './data/nucleon'))
|
||||
if nucleon_dir.exists():
|
||||
self.log_message(f"同步 nucleon 目录: {nucleon_dir}")
|
||||
self.update_status(f"同步 nucleon 目录...", progress=10)
|
||||
|
||||
result = self.sync_service.sync_directory(nucleon_dir) # type: ignore
|
||||
if result.get('success'):
|
||||
self.log_message(f"nucleon 同步完成: 上传 {result.get('uploaded', 0)} 个, 下载 {result.get('downloaded', 0)} 个")
|
||||
else:
|
||||
self.log_message(f"nucleon 同步失败: {result.get('error', '未知错误')}", is_error=True)
|
||||
|
||||
# 同步 electron 目录
|
||||
electron_dir = pathlib.Path(paths.get('electron_dir', './data/electron'))
|
||||
if electron_dir.exists():
|
||||
self.log_message(f"同步 electron 目录: {electron_dir}")
|
||||
self.update_status(f"同步 electron 目录...", progress=60)
|
||||
|
||||
result = self.sync_service.sync_directory(electron_dir) # type: ignore
|
||||
if result.get('success'):
|
||||
self.log_message(f"electron 同步完成: 上传 {result.get('uploaded', 0)} 个, 下载 {result.get('downloaded', 0)} 个")
|
||||
else:
|
||||
self.log_message(f"electron 同步失败: {result.get('error', '未知错误')}", is_error=True)
|
||||
|
||||
# 同步 orbital 目录(如果存在)
|
||||
orbital_dir = pathlib.Path(paths.get('orbital_dir', './data/orbital'))
|
||||
if orbital_dir.exists():
|
||||
self.log_message(f"同步 orbital 目录: {orbital_dir}")
|
||||
self.update_status(f"同步 orbital 目录...", progress=80)
|
||||
|
||||
result = self.sync_service.sync_directory(orbital_dir) # type: ignore
|
||||
if result.get('success'):
|
||||
self.log_message(f"orbital 同步完成: 上传 {result.get('uploaded', 0)} 个, 下载 {result.get('downloaded', 0)} 个")
|
||||
else:
|
||||
self.log_message(f"orbital 同步失败: {result.get('error', '未知错误')}", is_error=True)
|
||||
|
||||
# 同步完成
|
||||
self.update_status("同步完成", progress=100)
|
||||
self.log_message("所有目录同步完成")
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"同步过程中发生错误: {e}", is_error=True)
|
||||
self.update_status("同步失败")
|
||||
finally:
|
||||
# 重置同步状态
|
||||
self.is_syncing = False
|
||||
self.is_paused = False
|
||||
self.update_button_states() # type: ignore
|
||||
|
||||
def pause_sync(self):
|
||||
"""暂停同步"""
|
||||
if not self.is_syncing:
|
||||
return
|
||||
|
||||
self.is_paused = not self.is_paused
|
||||
self.update_button_states()
|
||||
|
||||
if self.is_paused:
|
||||
self.log_message("同步已暂停")
|
||||
self.update_status("同步已暂停")
|
||||
else:
|
||||
self.log_message("同步已恢复")
|
||||
self.update_status("正在同步...")
|
||||
|
||||
def cancel_sync(self):
|
||||
"""取消同步"""
|
||||
if not self.is_syncing:
|
||||
return
|
||||
|
||||
self.is_syncing = False
|
||||
self.is_paused = False
|
||||
self.update_button_states()
|
||||
|
||||
self.log_message("同步已取消")
|
||||
self.update_status("同步已取消")
|
||||
|
||||
def update_button_states(self):
|
||||
"""更新按钮状态"""
|
||||
try:
|
||||
start_button = self.query_one("#start_sync")
|
||||
pause_button = self.query_one("#pause_sync")
|
||||
cancel_button = self.query_one("#cancel_sync")
|
||||
|
||||
if self.is_syncing:
|
||||
start_button.disabled = True
|
||||
pause_button.disabled = False
|
||||
cancel_button.disabled = False
|
||||
pause_button.label = "继续" if self.is_paused else "暂停" # type: ignore
|
||||
else:
|
||||
start_button.disabled = False
|
||||
pause_button.disabled = True
|
||||
cancel_button.disabled = True
|
||||
|
||||
except Exception as e:
|
||||
self.log_message(f"更新按钮状态失败: {e}", is_error=True)
|
||||
|
||||
def action_go_back(self):
|
||||
self.app.pop_screen()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user