改进同步
This commit is contained in:
244
dynanote.py
244
dynanote.py
@@ -6,6 +6,7 @@ import cmd
|
||||
import toml
|
||||
import tempfile
|
||||
import subprocess
|
||||
import requests
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from tabulate import tabulate
|
||||
|
||||
@@ -15,6 +16,7 @@ import sm2
|
||||
import pathlib
|
||||
|
||||
os.chdir(pathlib.Path(__file__).resolve().parent)
|
||||
|
||||
class MemoryUnit:
|
||||
|
||||
def __init__(self, name: str, unit_id: Optional[str] = None):
|
||||
@@ -22,6 +24,7 @@ class MemoryUnit:
|
||||
self.unit_id = unit_id or hasher.get_md5(name)
|
||||
self.attachments = set() # (字符串, 创建时间戳)
|
||||
self.created_time = timer.get_daystamp()
|
||||
self.lastmodify = timer.get_timestamp() # 新增最后修改时间字段
|
||||
self.algodata = {
|
||||
sm2.SM2Algorithm.algo_name: {
|
||||
'efactor': 2.5,
|
||||
@@ -40,6 +43,7 @@ class MemoryUnit:
|
||||
'name': self.name,
|
||||
'attachments': list(self.attachments),
|
||||
'created_time': self.created_time,
|
||||
'lastmodify': self.lastmodify, # 新增最后修改时间
|
||||
'algodata': self.algodata
|
||||
}
|
||||
|
||||
@@ -48,10 +52,54 @@ class MemoryUnit:
|
||||
unit = cls(data['name'], unit_id)
|
||||
unit.attachments = set(tuple(att) for att in data['attachments'])
|
||||
unit.created_time = data['created_time']
|
||||
unit.lastmodify = data.get('lastmodify', timer.get_timestamp()) # 兼容旧数据
|
||||
unit.algodata = data['algodata']
|
||||
return unit
|
||||
|
||||
|
||||
class WebDAVClient:
|
||||
"""WebDAV 客户端"""
|
||||
|
||||
def __init__(self, url: str, username: str, password: str):
|
||||
self.url = url.rstrip('/')
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.session = requests.Session()
|
||||
if username and password:
|
||||
self.session.auth = (username, password)
|
||||
|
||||
def download_file(self, remote_path: str) -> Optional[str]:
|
||||
"""下载文件"""
|
||||
try:
|
||||
full_url = f"{self.url}/{remote_path.lstrip('/')}"
|
||||
response = self.session.get(full_url)
|
||||
response.raise_for_status()
|
||||
return response.text
|
||||
except requests.RequestException as e:
|
||||
print(f"下载文件失败: {e}")
|
||||
return None
|
||||
|
||||
def upload_file(self, remote_path: str, content: str) -> bool:
|
||||
"""上传文件"""
|
||||
try:
|
||||
full_url = f"{self.url}/{remote_path.lstrip('/')}"
|
||||
response = self.session.put(full_url, data=content.encode('utf-8'))
|
||||
response.raise_for_status()
|
||||
return True
|
||||
except requests.RequestException as e:
|
||||
print(f"上传文件失败: {e}")
|
||||
return False
|
||||
|
||||
def file_exists(self, remote_path: str) -> bool:
|
||||
"""检查文件是否存在"""
|
||||
try:
|
||||
full_url = f"{self.url}/{remote_path.lstrip('/')}"
|
||||
response = self.session.head(full_url)
|
||||
return response.status_code == 200
|
||||
except requests.RequestException:
|
||||
return False
|
||||
|
||||
|
||||
class DynaNoteShell(cmd.Cmd):
|
||||
|
||||
intro = f'欢迎使用 DynaNote Shell. 输入 help 或 ? 查看命令列表. \n当前 UNIX 日时间戳: {timer.get_daystamp()}\n'
|
||||
@@ -60,9 +108,12 @@ class DynaNoteShell(cmd.Cmd):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.data_file = './data.toml'
|
||||
self.sync_config_file = './sync.toml'
|
||||
self.memory_units: Dict[str, MemoryUnit] = {}
|
||||
self.selected_unit: Optional[MemoryUnit] = None
|
||||
self.webdav_client: Optional[WebDAVClient] = None
|
||||
self.load_data()
|
||||
self.load_sync_config()
|
||||
|
||||
def load_data(self) -> None:
|
||||
"""从 data.toml 文件加载记忆单元"""
|
||||
@@ -93,6 +144,20 @@ class DynaNoteShell(cmd.Cmd):
|
||||
else:
|
||||
print("未找到数据文件. 从空数据库开始. ")
|
||||
|
||||
def load_sync_config(self) -> None:
|
||||
"""加载同步配置"""
|
||||
if os.path.exists(self.sync_config_file):
|
||||
try:
|
||||
with open(self.sync_config_file, 'r', encoding='utf-8') as f:
|
||||
self.sync_config = toml.load(f)
|
||||
print("同步配置已加载")
|
||||
except Exception as e:
|
||||
print(f"加载同步配置时出错: {e}")
|
||||
self.sync_config = {}
|
||||
else:
|
||||
print("未找到同步配置文件")
|
||||
self.sync_config = {}
|
||||
|
||||
def save_data(self) -> None:
|
||||
"""将记忆单元保存到 data.toml 文件"""
|
||||
try:
|
||||
@@ -107,6 +172,178 @@ class DynaNoteShell(cmd.Cmd):
|
||||
except Exception as e:
|
||||
print(f"保存数据时出错: {e}")
|
||||
|
||||
def ensure_webdav_client(self) -> bool:
|
||||
"""确保 WebDAV 客户端已初始化"""
|
||||
if self.webdav_client is not None:
|
||||
return True
|
||||
|
||||
if not self.sync_config.get('webdav'):
|
||||
print("未配置 WebDAV 同步")
|
||||
return False
|
||||
|
||||
webdav_config = self.sync_config['webdav']
|
||||
if not webdav_config.get('url'):
|
||||
print("未配置 WebDAV 服务器地址")
|
||||
return False
|
||||
|
||||
self.webdav_client = WebDAVClient(
|
||||
url=webdav_config['url'],
|
||||
username=webdav_config.get('username', ''),
|
||||
password=webdav_config.get('password', '')
|
||||
)
|
||||
return True
|
||||
|
||||
def merge_units(self, local_units: Dict[str, MemoryUnit], remote_units: Dict[str, MemoryUnit],
|
||||
strategy: str = "remote_merge") -> Dict[str, MemoryUnit]:
|
||||
"""合并本地和远程的记忆单元"""
|
||||
merged = {}
|
||||
|
||||
# 获取所有单元ID
|
||||
all_unit_ids = set(local_units.keys()) | set(remote_units.keys())
|
||||
|
||||
for unit_id in all_unit_ids:
|
||||
local_unit = local_units.get(unit_id)
|
||||
remote_unit = remote_units.get(unit_id)
|
||||
|
||||
if local_unit and remote_unit:
|
||||
# 冲突解决:取时间更为新的版本
|
||||
if local_unit.lastmodify > remote_unit.lastmodify:
|
||||
merged[unit_id] = local_unit
|
||||
else:
|
||||
merged[unit_id] = remote_unit
|
||||
elif local_unit:
|
||||
merged[unit_id] = local_unit
|
||||
elif remote_unit:
|
||||
merged[unit_id] = remote_unit
|
||||
|
||||
return merged
|
||||
|
||||
def do_pull(self, arg: str) -> None:
|
||||
"""从云端拉取数据
|
||||
用法: pull
|
||||
"""
|
||||
if not self.ensure_webdav_client():
|
||||
return
|
||||
|
||||
webdav_config = self.sync_config['webdav']
|
||||
remote_file = webdav_config.get('remote_file', 'data.toml')
|
||||
|
||||
print("正在从云端拉取数据...")
|
||||
print(f"{remote_file}")
|
||||
|
||||
# 下载远程文件
|
||||
remote_content = self.webdav_client.download_file(remote_file)
|
||||
if remote_content is None:
|
||||
print("拉取失败")
|
||||
return
|
||||
|
||||
try:
|
||||
# 解析远程数据
|
||||
remote_data = toml.loads(remote_content)
|
||||
remote_units = {}
|
||||
for unit_id, unit_data in remote_data.items():
|
||||
remote_units[unit_id] = MemoryUnit.from_dict(unit_id, unit_data)
|
||||
|
||||
# 显示预览信息
|
||||
print("\n=== 同步预览 ===")
|
||||
print("本地单元:", " ".join([unit.name for unit in self.memory_units.values()]))
|
||||
print("云端单元:", " ".join([unit.name for unit in remote_units.values()]))
|
||||
print(f"\n本地单元数: {len(self.memory_units)}, 云端单元数: {len(remote_units)}")
|
||||
|
||||
# 请求确认
|
||||
response = input("\n确认拉取并合并数据?(y/N): ")
|
||||
if response.lower() not in ['y', 'yes']:
|
||||
print("拉取已取消")
|
||||
return
|
||||
|
||||
# 获取合并策略
|
||||
sync_config = self.sync_config.get('sync', {})
|
||||
merge_strategy = sync_config.get('merge_strategy', 'remote_merge')
|
||||
|
||||
# 合并数据
|
||||
merged_units = self.merge_units(self.memory_units, remote_units, merge_strategy)
|
||||
|
||||
# 更新本地数据
|
||||
self.memory_units = merged_units
|
||||
print(f"拉取完成,合并后共有 {len(self.memory_units)} 个记忆单元")
|
||||
|
||||
# 保存合并后的数据
|
||||
self.save_data()
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理云端数据时出错: {e}")
|
||||
|
||||
def do_push(self, arg: str) -> None:
|
||||
"""推送数据到云端
|
||||
用法: push
|
||||
"""
|
||||
if not self.ensure_webdav_client():
|
||||
return
|
||||
|
||||
webdav_config = self.sync_config['webdav']
|
||||
remote_file = webdav_config.get('remote_file', 'data.toml')
|
||||
|
||||
print("正在推送数据到云端...")
|
||||
|
||||
# 首先拉取云端数据以进行合并
|
||||
remote_units = {}
|
||||
if self.webdav_client.file_exists(remote_file):
|
||||
remote_content = self.webdav_client.download_file(remote_file)
|
||||
if remote_content:
|
||||
try:
|
||||
remote_data = toml.loads(remote_content)
|
||||
for unit_id, unit_data in remote_data.items():
|
||||
remote_units[unit_id] = MemoryUnit.from_dict(unit_id, unit_data)
|
||||
except Exception as e:
|
||||
print(f"读取云端数据时出错: {e}")
|
||||
|
||||
# 获取合并策略
|
||||
sync_config = self.sync_config.get('sync', {})
|
||||
merge_strategy = sync_config.get('merge_strategy', 'remote_merge')
|
||||
|
||||
# 合并数据
|
||||
if merge_strategy == "remote_merge":
|
||||
# 与云端已有文件合并
|
||||
merged_units = self.merge_units(remote_units, self.memory_units, merge_strategy)
|
||||
else:
|
||||
# 与本地已有文件合并(实际上就是使用本地数据覆盖)
|
||||
merged_units = self.memory_units
|
||||
|
||||
# 显示预览信息
|
||||
print("\n=== 同步预览 ===")
|
||||
print("本地单元:", " ".join([unit.name for unit in self.memory_units.values()]))
|
||||
print("云端单元:", " ".join([unit.name for unit in remote_units.values()]))
|
||||
print(f"合并后单元:", " ".join([unit.name for unit in merged_units.values()]))
|
||||
print(f"\n本地单元数: {len(self.memory_units)}, 云端单元数: {len(remote_units)}, 合并后单元数: {len(merged_units)}")
|
||||
|
||||
# 请求确认
|
||||
response = input("\n确认推送并合并数据?(y/N): ")
|
||||
if response.lower() not in ['y', 'yes']:
|
||||
print("推送已取消")
|
||||
return
|
||||
|
||||
# 准备上传数据
|
||||
upload_data = {}
|
||||
for unit_id, unit in merged_units.items():
|
||||
upload_data[unit_id] = unit.to_dict()
|
||||
|
||||
# 转换为 TOML 格式
|
||||
try:
|
||||
upload_content = toml.dumps(upload_data)
|
||||
except Exception as e:
|
||||
print(f"序列化数据时出错: {e}")
|
||||
return
|
||||
|
||||
# 上传到云端
|
||||
if self.webdav_client.upload_file(remote_file, upload_content):
|
||||
print(f"推送成功,上传了 {len(merged_units)} 个记忆单元")
|
||||
|
||||
# 更新本地数据为合并后的版本
|
||||
self.memory_units = merged_units
|
||||
self.save_data()
|
||||
else:
|
||||
print("推送失败")
|
||||
|
||||
def find_unit_by_prefix(self, prefix: str) -> Optional[str]:
|
||||
matches = [unit_id for unit_id in self.memory_units.keys()
|
||||
if unit_id.startswith(prefix)]
|
||||
@@ -403,6 +640,8 @@ class DynaNoteShell(cmd.Cmd):
|
||||
print(" mark <评分> - 使用评分(0-5)更新 SM-2")
|
||||
print(" show - 显示选定单元详情")
|
||||
print(" edit - 使用 nano 编辑选定单元")
|
||||
print(" pull - 从云端拉取数据")
|
||||
print(" push - 推送数据到云端")
|
||||
print(" clear - 清屏")
|
||||
print(" help - 显示此帮助")
|
||||
print(" exit - 退出 shell")
|
||||
@@ -415,6 +654,11 @@ class DynaNoteShell(cmd.Cmd):
|
||||
print(" 1 - 错误回答; 但正确答案似乎熟悉")
|
||||
print(" 0 - 完全遗忘")
|
||||
|
||||
print("\n同步配置:")
|
||||
print(" 编辑 sync.toml 文件配置 WebDAV 同步")
|
||||
print(" 合并策略: remote_merge (与云端合并) 或 local_merge (与本地合并)")
|
||||
print(" 冲突解决: 相同单元取时间更为新的版本")
|
||||
|
||||
def do_exit(self, arg: str) -> bool:
|
||||
"""退出 shell
|
||||
用法: exit
|
||||
|
||||
Reference in New Issue
Block a user