docs: 新增 Windows 移植交接文档

docs/WINDOWS_PORT.md 覆盖:
- 逐文件可移植性审计(核心逻辑层约 90% 可直接复用)
- 核心难点:iOS 17+ 的 RSD 隧道无法在 Windows 复用(macOS 实现依赖 XPC +
  ObjC runtime 私有框架),改用 pymobiledevice3 的 userspace 隧道
- 已实测确认的命令与 API(--userspace 挂在 lockdown start-tunnel / mounter
  auto-mount 上,remote start-tunnel 不支持;类名是 UserspaceRsdTunnel)
- 窗口启动、数据目录、打包与分发(DMG → Inno Setup)、图标(.icns → .ico)
- macOS 侧踩过的坑:镜像挂载用 Python API、必须打包包元数据、不要把 distutils
  放进 excludes、AlreadyMountedError 当成功处理
- 设计约束(DVT 长连接、只能改经纬度、last_point 与 last_reported 分离等)
- 验收基线(复用现有测试套件)与建议的工作顺序
This commit is contained in:
jbjf6666 committed 2026-10-16 01:05:09 +08:00
commit 85e9a53750
65 files changed
+18603

No files matched your search

+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""瓦片离线可用验证:回源成功后断网,验证缓存仍能供图。
做法:用真实的 core.tilecache.TileCache,先把 urllib 换成"永远失败"的实现(模拟断网),
再请求已经缓存过的瓦片,断言仍然返回 200 + 合法图片;同时断言未缓存过的瓦片会优雅降级
(返回占位图 + status != 200,由上层回 404 让前端走重试)。
运行:./.venv/bin/python tests/offline_tiles.py
"""
from __future__ import annotations
import sys
import time
import urllib.request
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from core import config_store, tilecache # noqa: E402
PASS = 0
FAIL = 0
FAILURES: list[str] = []
def check(name: str, ok: bool, detail: str = "") -> None:
global PASS, FAIL
if ok:
PASS += 1
print(f" \033[32m✓\033[0m {name}")
else:
FAIL += 1
FAILURES.append(f"{name} {detail}")
print(f" \033[31m✗\033[0m {name} {detail}")
def main() -> int:
print("\033[1m瓦片本地缓存 / 离线可用验证\033[0m")
cache_dir = Path("/tmp/iosloc-offline-tiles")
import shutil
shutil.rmtree(cache_dir, ignore_errors=True)
source_key = "amap"
source = config_store.DEFAULT_TILE_SOURCES[source_key]
# 北京天安门附近、z12 —— 本机实测可达
z, x, y = 12, 3367, 1580
cache = tilecache.TileCache(cache_dir, max_mb=64)
print("\n1) 联网回源并落盘")
online = cache.get_tile(source_key, source, z, x, y)
check("回源返回真实图片", online.status == 200 and online.body[:4] in (b"\x89PNG", b"\xff\xd8"),
f"status={online.status} source={online.source}")
check("标记为 network", online.source == "network", online.source)
check("已写入磁盘缓存", any(cache_dir.rglob("*")), "缓存目录为空")
print("\n2) 断网(把 urllib 换成永远失败)后请求同一瓦片")
real_urlopen = urllib.request.urlopen
def offline(*_args, **_kwargs):
raise OSError("模拟断网:Network is unreachable")
urllib.request.urlopen = offline
try:
cached = cache.get_tile(source_key, source, z, x, y)
check("离线仍返回 200", cached.status == 200, f"status={cached.status}")
check("来源为 cache", cached.source == "cache", cached.source)
check("图片内容与联网时一致", cached.body == online.body,
f"{len(cached.body)}B vs {len(online.body)}B")
print("\n3) 断网 + 未缓存瓦片 → 优雅降级(不抛异常)")
t0 = time.time()
miss = cache.get_tile(source_key, source, z, x, y + 1)
elapsed = time.time() - t0
check("未命中不抛异常", True, "")
check("返回占位图且 status!=200(上层回 404 让前端重试)",
miss.status != 200 and miss.body[:4] == b"\x89PNG",
f"status={miss.status} source={miss.source}")
check("失败快速返回(<5s)", elapsed < 5.0, f"{elapsed:.2f}s")
print("\n4) 缓存统计")
stats = cache.stats
check("命中计数增加", stats["hit"] >= 1, str(stats))
check("统计到离线失败", stats["failed"] >= 1, str(stats))
finally:
urllib.request.urlopen = real_urlopen
print("\n5) 恢复网络后仍可正常回源")
cache2 = tilecache.TileCache(cache_dir, max_mb=64)
other = cache2.get_tile(source_key, source, z, x + 1, y)
check("恢复后回源成功", other.status == 200 and other.body[:4] in (b"\x89PNG", b"\xff\xd8"),
f"status={other.status} source={other.source}")
print("\n6) 容量上限淘汰不破坏可用性")
small = tilecache.TileCache(cache_dir, max_mb=1)
small._sweep()
check("淘汰后仍能读取", small.get_tile(source_key, source, z, x, y).status in (200, 404))
shutil.rmtree(cache_dir, ignore_errors=True)
print(f"\n{'=' * 56}")
print(f"通过 \033[32m{PASS}\033[0m 项,失败 \033[31m{FAIL}\033[0m 项")
for item in FAILURES:
print(" -", item)
return 0 if FAIL == 0 else 1
if __name__ == "__main__":
raise SystemExit(main())