mirror of
https://github.com/shy3130/tick-stock-panel.git
synced 2026-11-02 10:26:13 +00:00
fix(desktop): 迁移 skip 分支补捞 preferences.json
_migrate_legacy_data_dir 的 has_new_data skip 分支此前只捞 secrets.json
(0669204c), preferences.json 不捞 — 偏好丢失会让 onboarding_completed
回落默认 False, 升级后全屏引导重弹, 表现为「昨天同步过数据, 升级后
又被要求获取数据」(用户反馈链路)。函数泛化为 _rescue_legacy_user_data:
secrets.json + preferences.json 逐项捞回, 旧有新无才拷, 异常只告警。
验证: 新增 preferences 捞回/不覆盖 2 条用例; 原有 secrets 捞回 3 条
语义不变全绿。
Signed-off-by: shy3130 <shy3130@qq.com>
This commit is contained in:
@@ -107,30 +107,35 @@ def _last_available_rows(df: pl.DataFrame, cutoff: date) -> pl.DataFrame:
|
||||
)
|
||||
|
||||
|
||||
def _rescue_legacy_secrets(legacy_dir: Path, data_dir: Path) -> None:
|
||||
"""整体迁移被跳过时, 单独捞回新目录缺失的 user_data/secrets.json。
|
||||
def _rescue_legacy_user_data(legacy_dir: Path, data_dir: Path) -> None:
|
||||
"""整体迁移被跳过时, 单独捞回新目录缺失的关键 user_data 文件。
|
||||
|
||||
场景: macOS ≤0.3.6 用户升级后先跑过新版 (新 data 已有行情), 再从废纸篓
|
||||
把旧包数据恢复进新包 — has_new_data 守卫会跳过整目录搬迁, 但 secrets.json
|
||||
(AI/数据源密钥) 是用户重新配置成本最高的文件, 旧有新无时应单独找回。
|
||||
把旧包数据恢复进新包 — has_new_data 守卫会跳过整目录搬迁, 但两类文件
|
||||
是用户重新配置成本最高的, 旧有新无时应单独找回:
|
||||
- secrets.json (AI/数据源密钥)
|
||||
- preferences.json (onboarding_completed/界面偏好 — 丢失会让
|
||||
onboarding_completed 回落默认 False, 升级后全屏引导重弹, 表现为
|
||||
「明明同步过数据, 升级后又被要求获取数据」)
|
||||
任何异常只记警告, 绝不影响启动。
|
||||
"""
|
||||
import shutil
|
||||
|
||||
source = legacy_dir / "user_data" / "secrets.json"
|
||||
dest = data_dir / "user_data" / "secrets.json"
|
||||
if not source.is_file() or dest.exists():
|
||||
return
|
||||
try:
|
||||
data = json.loads(source.read_text(encoding="utf-8"))
|
||||
if not isinstance(data, dict) or not data:
|
||||
return
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
# copy2 保留源文件权限位 (secrets.json 为 0600)
|
||||
shutil.copy2(source, dest)
|
||||
logger.info("rescued legacy secrets.json %s -> %s", source, dest)
|
||||
except (OSError, ValueError) as e:
|
||||
logger.warning("legacy secrets.json rescue skipped: %s", e)
|
||||
for name, note in (("secrets.json", "密钥"), ("preferences.json", "偏好")):
|
||||
source = legacy_dir / "user_data" / name
|
||||
dest = data_dir / "user_data" / name
|
||||
if not source.is_file() or dest.exists():
|
||||
continue
|
||||
try:
|
||||
data = json.loads(source.read_text(encoding="utf-8"))
|
||||
if not isinstance(data, dict) or not data:
|
||||
continue
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
# copy2 保留源文件权限位 (secrets.json 为 0600)
|
||||
shutil.copy2(source, dest)
|
||||
logger.info("rescued legacy %s (%s) %s -> %s", name, note, source, dest)
|
||||
except (OSError, ValueError) as e:
|
||||
logger.warning("legacy %s rescue skipped: %s", name, e)
|
||||
|
||||
|
||||
class DataStore:
|
||||
@@ -221,8 +226,8 @@ class DataStore:
|
||||
self.data_dir.rglob("*.jsonl")
|
||||
)
|
||||
if has_new_data:
|
||||
# 整体搬迁会被守卫跳过, 但密钥文件单独捞回 (见函数 docstring)
|
||||
_rescue_legacy_secrets(legacy_dir, self.data_dir)
|
||||
# 整体搬迁会被守卫跳过, 但密钥/偏好等关键 user_data 文件单独捞回 (见函数 docstring)
|
||||
_rescue_legacy_user_data(legacy_dir, self.data_dir)
|
||||
logger.info(
|
||||
"legacy data dir %s exists but new %s already has data, skip migration",
|
||||
legacy_dir, self.data_dir,
|
||||
|
||||
@@ -205,3 +205,41 @@ def test_migration_rescue_skips_invalid_secrets(tmp_path, monkeypatch):
|
||||
_repo_with_data_dir(new_dir)._migrate_legacy_data_dir()
|
||||
|
||||
assert not (new_dir / "user_data" / "secrets.json").exists()
|
||||
|
||||
|
||||
def test_migration_rescues_preferences_when_new_has_data(tmp_path, monkeypatch):
|
||||
"""skip 分支也要捞回 preferences.json: 偏好丢失会让 onboarding_completed
|
||||
回落默认 False, 升级后全屏引导重弹 (「同步过数据却被要求获取数据」)。"""
|
||||
new_dir, bundle_data = _setup_skip_migration(tmp_path)
|
||||
(bundle_data / "user_data" / "preferences.json").write_text(
|
||||
'{"onboarding_completed": true, "nav_hidden": ["a"]}',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
monkeypatch.setattr(sys, "frozen", True, raising=False)
|
||||
monkeypatch.setattr(sys, "platform", "darwin")
|
||||
monkeypatch.setattr(sys, "executable", str(bundle_data.parent / "TSP"))
|
||||
|
||||
_repo_with_data_dir(new_dir)._migrate_legacy_data_dir()
|
||||
|
||||
rescued = new_dir / "user_data" / "preferences.json"
|
||||
assert rescued.exists()
|
||||
prefs = json.loads(rescued.read_text(encoding="utf-8"))
|
||||
assert prefs["onboarding_completed"] is True
|
||||
# 守卫语义不变: 旧目录整体保留
|
||||
assert (bundle_data / "user_data" / "preferences.json").exists()
|
||||
|
||||
|
||||
def test_migration_no_rescue_when_dest_preferences_exists(tmp_path, monkeypatch):
|
||||
"""新目录已有 preferences.json (新版已在使用) → 绝不覆盖。"""
|
||||
new_dir, bundle_data = _setup_skip_migration(tmp_path)
|
||||
(bundle_data / "user_data" / "preferences.json").write_text('{"legacy": true}', encoding="utf-8")
|
||||
(new_dir / "user_data" / "preferences.json").write_text('{"mine": true}', encoding="utf-8")
|
||||
|
||||
monkeypatch.setattr(sys, "frozen", True, raising=False)
|
||||
monkeypatch.setattr(sys, "platform", "darwin")
|
||||
monkeypatch.setattr(sys, "executable", str(bundle_data.parent / "TSP"))
|
||||
|
||||
_repo_with_data_dir(new_dir)._migrate_legacy_data_dir()
|
||||
|
||||
assert (new_dir / "user_data" / "preferences.json").read_text(encoding="utf-8") == '{"mine": true}'
|
||||
|
||||
Reference in New Issue
Block a user