soulsync 1.0.12 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/sync.py CHANGED
@@ -42,7 +42,7 @@ class ProfileSync:
42
42
  with open(absolute_path, 'r', encoding='utf-8') as f:
43
43
  return f.read()
44
44
  except Exception as e:
45
- print(f"[Sync] Error reading local file {file_path}: {e}")
45
+ print(f"[SoulSync] Error reading local file {file_path}: {e}")
46
46
  return None
47
47
  return None
48
48
 
@@ -57,7 +57,7 @@ class ProfileSync:
57
57
  with open(absolute_path, 'w', encoding='utf-8') as f:
58
58
  f.write(content)
59
59
  except Exception as e:
60
- print(f"[Sync] Error writing local file {file_path}: {e}")
60
+ print(f"[SoulSync] Error writing local file {file_path}: {e}")
61
61
 
62
62
  def _create_conflict_backup(self, file_path: str, local_content: str, server_content: str):
63
63
  """创建冲突备份文件"""
@@ -71,64 +71,51 @@ class ProfileSync:
71
71
  f.write(local_content or "(empty)")
72
72
  f.write("\n\n========== SERVER VERSION ==========\n")
73
73
  f.write(server_content or "(empty)")
74
- print(f"[Sync] Conflict backup created: {conflict_path}")
74
+ print(f"[SoulSync] Conflict backup created: {conflict_path}")
75
75
  except Exception as e:
76
- print(f"[Sync] Error creating conflict backup: {e}")
76
+ print(f"[SoulSync] Error creating conflict backup: {e}")
77
77
 
78
78
  def pull_all(self):
79
79
  """Pull all profiles from cloud"""
80
- print("[Sync] Pulling all profiles from cloud...")
80
+ print("[SoulSync] Pulling all profiles from cloud...")
81
81
 
82
82
  try:
83
83
  result = self.client.get_profiles()
84
- cloud_files = result.get('files', [])
84
+ cloud_content = result.get('content', {})
85
+ cloud_version = result.get('version', 0)
85
86
  except Exception as e:
86
- print(f"[Sync] Error fetching cloud profiles: {e}")
87
+ print(f"[SoulSync] Error fetching cloud profiles: {e}")
87
88
  return
88
89
 
89
- if not cloud_files:
90
- print("[Sync] No files on cloud")
90
+ if not cloud_content:
91
+ print("[SoulSync] No profiles on cloud")
91
92
  return
92
93
 
94
+ local_files = ['SOUL.md', 'USER.md', 'MEMORY.md']
93
95
  pulled_count = 0
94
- pushed_count = 0
95
96
  skipped_count = 0
96
97
 
97
- for cloud_file in cloud_files:
98
- file_path = cloud_file.get('file_path')
99
- cloud_version = cloud_file.get('version', 0)
100
- cloud_content = cloud_file.get('content', '')
101
-
102
- if not file_path:
103
- continue
104
-
105
- local_content = self._read_local_file(file_path)
106
- local_version = self.version_manager.get_version(file_path)
98
+ for file_name in local_files:
99
+ cloud_file_content = cloud_content.get(file_name, '')
100
+ local_content = self._read_local_file(file_name)
101
+ local_version = self.version_manager.get_version(file_name)
107
102
 
108
103
  if cloud_version > local_version:
109
- self._mark_syncing(file_path)
104
+ self._mark_syncing(file_name)
110
105
  try:
111
- self._write_local_file(file_path, cloud_content)
112
- self.version_manager.set_version(file_path, cloud_version)
113
- pulled_count += 1
114
- print(f"[Sync] Pulled: {file_path} (v{cloud_version})")
106
+ if cloud_file_content:
107
+ self._write_local_file(file_name, cloud_file_content)
108
+ self.version_manager.set_version(file_name, cloud_version)
109
+ pulled_count += 1
110
+ print(f"[SoulSync] Pulled: {file_name} (v{cloud_version})")
115
111
  finally:
116
- self._unmark_syncing(file_path)
117
- elif local_version > cloud_version and local_content is not None:
118
- try:
119
- result = self.client.upload_profile(file_path, local_content, local_version)
120
- self.version_manager.set_version(file_path, result.get('version', local_version))
121
- pushed_count += 1
122
- print(f"[Sync] Pushed: {file_path} (v{local_version})")
123
- except ConflictError as e:
124
- self._handle_conflict(file_path, local_content, e.server_content, e.server_version)
125
- pushed_count += 1
126
- except Exception as e:
127
- print(f"[Sync] Error pushing {file_path}: {e}")
112
+ self._unmark_syncing(file_name)
128
113
  else:
129
114
  skipped_count += 1
130
115
 
131
- print(f"[Sync] Sync complete: {pulled_count} pulled, {pushed_count} pushed, {skipped_count} skipped")
116
+ self.version_manager.set_version('__profiles__', cloud_version)
117
+
118
+ print(f"[SoulSync] Sync complete: {pulled_count} pulled, {skipped_count} skipped")
132
119
 
133
120
  def push_file(self, file_path: str):
134
121
  """Push a file to cloud"""
@@ -140,64 +127,84 @@ class ProfileSync:
140
127
  try:
141
128
  local_content = self._read_local_file(file_path)
142
129
  if local_content is None:
143
- print(f"[Sync] File not found locally: {file_path}")
130
+ print(f"[SoulSync] File not found locally: {file_path}")
144
131
  return
145
132
 
146
133
  local_version = self.version_manager.get_version(file_path)
147
134
 
135
+ profiles_version = self.version_manager.get_version('__profiles__')
136
+
137
+ current_profiles = {}
148
138
  try:
149
- result = self.client.upload_profile(file_path, local_content, local_version)
150
- new_version = result.get('version', local_version + 1)
139
+ result = self.client.get_profiles()
140
+ current_profiles = result.get('content', {})
141
+ except Exception:
142
+ pass
143
+
144
+ current_profiles[file_path] = local_content
145
+
146
+ try:
147
+ result = self.client.upload_profiles(current_profiles, profiles_version)
148
+ new_version = result.get('version', profiles_version + 1)
149
+ self.version_manager.set_version('__profiles__', new_version)
151
150
  self.version_manager.set_version(file_path, new_version)
152
- print(f"[Sync] Pushed: {file_path} (v{new_version})")
151
+ print(f"[SoulSync] Pushed: {file_path} (v{new_version})")
153
152
  except ConflictError as e:
154
153
  self._handle_conflict(file_path, local_content, e.server_content, e.server_version)
155
154
  except Exception as e:
156
- print(f"[Sync] Error pushing {file_path}: {e}")
155
+ print(f"[SoulSync] Error pushing {file_path}: {e}")
157
156
  finally:
158
157
  self._unmark_syncing(file_path)
159
158
 
160
159
  def on_remote_change(self, file_path: str, version: int):
161
160
  """Handle remote file change"""
162
- local_version = self.version_manager.get_version(file_path)
161
+ local_version = self.version_manager.get_version('__profiles__')
163
162
 
164
163
  if version <= local_version:
165
- print(f"[Sync] Remote version not newer, skipping: {file_path} (local: v{local_version}, remote: v{version})")
164
+ print(f"[SoulSync] Remote version not newer, skipping: {file_path} (local: v{local_version}, remote: v{version})")
166
165
  return
167
166
 
168
167
  self._mark_syncing(file_path)
169
168
 
170
169
  try:
171
- result = self.client.get_profiles(file_path)
172
- files = result.get('files', [])
173
- if not files:
174
- print(f"[Sync] File not found on cloud: {file_path}")
170
+ result = self.client.get_profiles()
171
+ cloud_content = result.get('content', {})
172
+ cloud_version = result.get('version', 0)
173
+
174
+ if file_path not in cloud_content:
175
+ print(f"[SoulSync] File not found on cloud: {file_path}")
175
176
  return
176
177
 
177
- cloud_file = files[0]
178
- cloud_content = cloud_file.get('content', '')
178
+ cloud_file_content = cloud_content.get(file_path, '')
179
179
 
180
180
  local_content = self._read_local_file(file_path)
181
181
 
182
- if local_content is not None and local_content != cloud_content:
183
- self._create_conflict_backup(file_path, local_content, cloud_content)
182
+ if local_content is not None and local_content != cloud_file_content:
183
+ self._create_conflict_backup(file_path, local_content, cloud_file_content)
184
184
 
185
- self._write_local_file(file_path, cloud_content)
185
+ self._write_local_file(file_path, cloud_file_content)
186
+ self.version_manager.set_version('__profiles__', cloud_version)
186
187
  self.version_manager.set_version(file_path, version)
187
- print(f"[Sync] Pulled remote change: {file_path} (v{version})")
188
+ print(f"[SoulSync] Pulled remote change: {file_path} (v{version})")
188
189
  except Exception as e:
189
- print(f"[Sync] Error handling remote change for {file_path}: {e}")
190
+ print(f"[SoulSync] Error handling remote change for {file_path}: {e}")
190
191
  finally:
191
192
  self._unmark_syncing(file_path)
192
193
 
193
194
  def _handle_conflict(self, file_path: str, local_content: str, server_content: str, server_version: int):
194
195
  """Handle conflict when pushing file"""
195
- print(f"[Sync] CONFLICT detected for {file_path}")
196
+ print(f"[SoulSync] CONFLICT detected for {file_path}")
196
197
 
197
198
  self._create_conflict_backup(file_path, local_content, server_content)
198
199
 
199
- self._write_local_file(file_path, server_content)
200
+ if isinstance(server_content, dict):
201
+ server_file_content = server_content.get(file_path, '')
202
+ else:
203
+ server_file_content = server_content
204
+
205
+ self._write_local_file(file_path, server_file_content)
206
+ self.version_manager.set_version('__profiles__', server_version)
200
207
  self.version_manager.set_version(file_path, server_version)
201
208
 
202
- print(f"[Sync] Conflict resolved: server version used for {file_path}")
203
- print(f"[Sync] Please manually merge the conflict backup file")
209
+ print(f"[SoulSync] Conflict resolved: server version used for {file_path}")
210
+ print(f"[SoulSync] Please manually merge the conflict backup file")
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 测试 SSL 修复是否有效
4
+ """
5
+ import ssl
6
+ import sys
7
+
8
+ print(f"Python 版本: {sys.version}")
9
+ print(f"SSL 版本: {ssl.OPENSSL_VERSION}")
10
+ print()
11
+
12
+ # 测试 1: 使用 ssl.create_default_context()
13
+ print("测试 1: 使用 ssl.create_default_context()")
14
+ try:
15
+ import urllib.request
16
+ ctx = ssl.create_default_context()
17
+ ctx.minimum_version = ssl.TLSVersion.TLSv1_2
18
+ req = urllib.request.Request('https://soulsync.work/api/health')
19
+ response = urllib.request.urlopen(req, context=ctx)
20
+ print(f"✅ 成功: {response.read().decode()}")
21
+ except Exception as e:
22
+ print(f"❌ 失败: {e}")
23
+
24
+ print()
25
+
26
+ # 测试 2: 使用 requests + TLSAdapter
27
+ print("测试 2: 使用 requests + 自定义 TLSAdapter")
28
+ try:
29
+ import requests
30
+ from requests.adapters import HTTPAdapter
31
+
32
+ class TLSAdapter(HTTPAdapter):
33
+ def init_poolmanager(self, *args, **kwargs):
34
+ ctx = ssl.create_default_context()
35
+ ctx.minimum_version = ssl.TLSVersion.TLSv1_2
36
+ kwargs['ssl_context'] = ctx
37
+ return super().init_poolmanager(*args, **kwargs)
38
+
39
+ session = requests.Session()
40
+ session.mount('https://', TLSAdapter())
41
+ response = session.get('https://soulsync.work/api/health')
42
+ print(f"✅ 成功: {response.text}")
43
+ except Exception as e:
44
+ print(f"❌ 失败: {e}")
45
+
46
+ print()
47
+
48
+ # 测试 3: 直接 requests(无适配器)
49
+ print("测试 3: 直接 requests(无适配器)")
50
+ try:
51
+ import requests
52
+ response = requests.get('https://soulsync.work/api/health')
53
+ print(f"✅ 成功: {response.text}")
54
+ except Exception as e:
55
+ print(f"❌ 失败: {e}")
56
+
57
+ print()
58
+ print("测试完成")
@@ -1,283 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- SoulSync 交互式认证模块
4
- 处理用户注册、登录、邮箱验证
5
- """
6
-
7
- import os
8
- import sys
9
- import json
10
- import re
11
- import getpass
12
-
13
- # 获取插件目录
14
- PLUGIN_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
15
- CONFIG_PATH = os.path.join(PLUGIN_DIR, 'config.json')
16
-
17
-
18
- def get_input(prompt):
19
- """获取用户输入"""
20
- try:
21
- return input(prompt)
22
- except KeyboardInterrupt:
23
- print("\n\nOperation cancelled / 操作已取消")
24
- sys.exit(0)
25
-
26
-
27
- def get_password(prompt):
28
- """获取密码(隐藏输入)"""
29
- try:
30
- return getpass.getpass(prompt)
31
- except KeyboardInterrupt:
32
- print("\n\nOperation cancelled / 操作已取消")
33
- sys.exit(0)
34
-
35
-
36
- def is_valid_email(email):
37
- """验证邮箱格式"""
38
- pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
39
- return re.match(pattern, email) is not None
40
-
41
-
42
- def load_config():
43
- """加载配置文件"""
44
- if os.path.exists(CONFIG_PATH):
45
- try:
46
- with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
47
- return json.load(f)
48
- except:
49
- return {}
50
- return {}
51
-
52
-
53
- def save_config(config):
54
- """保存配置文件"""
55
- try:
56
- with open(CONFIG_PATH, 'w', encoding='utf-8') as f:
57
- json.dump(config, f, indent=2, ensure_ascii=False)
58
- return True
59
- except Exception as e:
60
- print(f"Failed to save config: {e} / 保存配置失败: {e}")
61
- return False
62
-
63
-
64
- def check_existing_config():
65
- """检查是否已有配置"""
66
- config = load_config()
67
- email = config.get('email', '').strip()
68
- password = config.get('password', '').strip()
69
-
70
- if email and password:
71
- return config
72
- return None
73
-
74
-
75
- def interactive_setup(client):
76
- """
77
- 交互式设置流程
78
- 返回配置好的 config 字典
79
- """
80
- print("\n" + "=" * 50)
81
- print("Welcome to SoulSync! / 欢迎使用 SoulSync!")
82
- print("=" * 50)
83
- print()
84
-
85
- # 检查现有配置
86
- existing = check_existing_config()
87
- if existing:
88
- print(f"Detected existing account / 已检测到现有账号: {existing['email']}")
89
- choice = get_input("Use existing account? (y/n) / 是否使用现有账号?: ").lower().strip()
90
- if choice in ['y', 'yes', '是']:
91
- return existing
92
- print()
93
-
94
- # 询问是否有账号
95
- print("Please select / 请选择:")
96
- print("1. Login / 登录已有账号")
97
- print("2. Register / 注册新账号")
98
- print()
99
-
100
- while True:
101
- choice = get_input("Enter option / 输入选项 (1/2): ").strip()
102
- if choice in ['1', '2']:
103
- break
104
- print("Invalid option / 无效选项,请重新输入")
105
-
106
- if choice == '1':
107
- return interactive_login(client)
108
- else:
109
- return interactive_register(client)
110
-
111
-
112
- def interactive_login(client):
113
- """交互式登录"""
114
- print("\n--- Login / 登录 ---")
115
-
116
- # 输入邮箱
117
- while True:
118
- email = get_input("Email / 邮箱: ").strip()
119
- if is_valid_email(email):
120
- break
121
- print("Invalid email format / 邮箱格式不正确,请重新输入")
122
-
123
- # 输入密码
124
- password = get_password("Password / 密码: ")
125
-
126
- print("\nLogging in... / 正在登录...")
127
-
128
- try:
129
- # 尝试登录
130
- result = client.authenticate(email, password)
131
-
132
- if result:
133
- print("✅ Login successful! / 登录成功!")
134
-
135
- # 保存配置
136
- config = load_config()
137
- config['email'] = email
138
- config['password'] = password
139
- save_config(config)
140
-
141
- return config
142
- else:
143
- print("❌ Login failed, invalid email or password / 登录失败,邮箱或密码错误")
144
- return None
145
-
146
- except Exception as e:
147
- print(f"❌ Login error: {e} / 登录出错: {e}")
148
- return None
149
-
150
-
151
- def interactive_register(client):
152
- """交互式注册"""
153
- print("\n--- Register / 注册新账号 ---")
154
-
155
- # 输入邮箱
156
- while True:
157
- email = get_input("Email / 邮箱: ").strip()
158
- if not is_valid_email(email):
159
- print("Invalid email format / 邮箱格式不正确,请重新输入")
160
- continue
161
-
162
- # 检查邮箱是否已注册
163
- print("Checking email availability... / 检查邮箱可用性...")
164
- # TODO: 调用后端 API 检查邮箱是否已存在
165
- # 暂时假设可用
166
- break
167
-
168
- # 输入密码
169
- while True:
170
- password = get_password("Set password (min 6 characters) / 设置密码 (至少6位): ")
171
- if len(password) >= 6:
172
- break
173
- print("Password too short, minimum 6 characters / 密码太短,请至少输入6位")
174
-
175
- # 确认密码
176
- while True:
177
- password2 = get_password("Confirm password / 确认密码: ")
178
- if password == password2:
179
- break
180
- print("Passwords do not match, please try again / 两次密码不一致,请重新输入")
181
-
182
- # 发送验证码
183
- print(f"\nSending verification code to {email} / 正在发送验证码到 {email}...")
184
- try:
185
- client.send_verification_code(email)
186
- print("✅ Verification code sent! / 验证码已发送!")
187
- except Exception as e:
188
- print(f"❌ Failed to send code: {e} / 发送验证码失败: {e}")
189
- return None
190
-
191
- # 输入验证码
192
- max_attempts = 3
193
- for attempt in range(max_attempts):
194
- code = get_input(f"Enter verification code / 请输入验证码 (remaining attempts / 剩余尝试: {max_attempts - attempt}): ").strip()
195
-
196
- # Verify code format (6 digits)
197
- if len(code) == 6 and code.isdigit():
198
- # Verify with server
199
- try:
200
- result = client.register(email, password, code)
201
- print("✅ Verification successful! / 验证成功!")
202
- break
203
- except Exception as e:
204
- print(f"❌ Verification failed: {e} / 验证失败: {e}")
205
- if attempt == max_attempts - 1:
206
- print("Too many failed attempts, please re-register / 验证失败次数过多,请重新注册")
207
- return None
208
- else:
209
- print("❌ Invalid code format, please enter 6-digit code / 验证码格式错误,请输入6位数字验证码")
210
- if attempt == max_attempts - 1:
211
- print("Too many failed attempts, please re-register / 验证失败次数过多,请重新注册")
212
- return None
213
-
214
- # 完成注册
215
- print("\nCreating account... / 正在创建账号...")
216
- try:
217
- print("✅ Registration successful! / 注册成功!")
218
-
219
- # 保存配置
220
- config = load_config()
221
- config['email'] = email
222
- config['password'] = password
223
- save_config(config)
224
-
225
- return config
226
-
227
- except Exception as e:
228
- print(f"❌ Registration failed: {e} / 注册失败: {e}")
229
- return None
230
-
231
-
232
- def prompt_for_missing_config(client):
233
- """
234
- 当配置缺失时,提示用户输入
235
- 用于插件启动时自动检测
236
- """
237
- config = load_config()
238
-
239
- email = config.get('email', '').strip()
240
- password = config.get('password', '').strip()
241
- cloud_url = config.get('cloud_url', '').strip()
242
-
243
- # 检查是否需要交互式配置
244
- need_setup = not email or not password or not cloud_url
245
-
246
- if need_setup:
247
- print("\nFirst time using SoulSync, configuration required... / 首次使用 SoulSync,需要进行配置...")
248
-
249
- # 如果没有 cloud_url,设置默认值
250
- if not cloud_url:
251
- config['cloud_url'] = 'https://soulsync.work'
252
- print(f"Using default server / 使用默认服务器: {config['cloud_url']}")
253
-
254
- # 交互式登录/注册
255
- result = interactive_setup(client)
256
-
257
- if result:
258
- return result
259
- else:
260
- print("\n❌ Configuration failed, plugin cannot start / 配置失败,插件无法启动")
261
- return None
262
-
263
- return config
264
-
265
-
266
- if __name__ == '__main__':
267
- # 测试代码
268
- print("Interactive Auth Module Test / 交互式认证模块测试")
269
- print("=" * 50)
270
-
271
- # 模拟客户端
272
- class MockClient:
273
- def authenticate(self, email, password):
274
- print(f"Simulating login / 模拟登录: {email}")
275
- return True
276
-
277
- client = MockClient()
278
- result = interactive_setup(client)
279
-
280
- if result:
281
- print(f"\nConfiguration complete / 配置完成: {result}")
282
- else:
283
- print("\nConfiguration failed / 配置失败")