Files
djk/scripts/update_from_zip.py
T

190 lines
7.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""把新版 短剧库.zip 与本地源码对比,展示变更并更新本地旧代码。
用法:
python scripts/update_from_zip.py # 从默认地址下载最新包并预览变更
python scripts/update_from_zip.py --apply # 下载并真正更新本地代码
python scripts/update_from_zip.py 某个路径/短剧库.zip # 用本地压缩包
python scripts/update_from_zip.py --url https://... # 指定其他下载地址
行为:
1. 读取压缩包(自动处理中文文件名 GBK/UTF-8、自动剥离压缩包内公共顶层目录)
2. 与本地源码逐文件比对(忽略 .git、data/、日志、压缩包等运行时文件)
3. 列出 新增 / 修改 / 删除 三类变更;加 --apply 后写入本地
4. 更新完成后用 git status / git diff 自查,再提交
"""
import argparse
import hashlib
import sys
import tempfile
import urllib.parse
import urllib.request
import zipfile
from pathlib import Path
DEFAULT_URL = "https://www.252035.xyz/短剧库.zip"
REPO_ROOT = Path(__file__).resolve().parent.parent
IGNORE_DIRS = {".git", "data", "__pycache__", "dist", "短剧下载"}
IGNORE_SUFFIX = {".log", ".zip", ".tar", ".pyc"}
IGNORE_NAMES = {".DS_Store", "Thumbs.db"}
PROTECT = {"scripts/update_from_zip.py"} # 更新工具自身,永远不删
def download(url: str) -> Path:
quoted = urllib.parse.quote(url, safe=":/?&=%")
print(f"下载最新压缩包:{url}")
request = urllib.request.Request(quoted, headers={"User-Agent": "juku-update/1.0"})
with urllib.request.urlopen(request, timeout=60) as resp:
total = int(resp.headers.get("Content-Length") or 0)
target = Path(tempfile.gettempdir()) / "短剧库-update.zip"
done = 0
with target.open("wb") as f:
while True:
chunk = resp.read(1 << 16)
if not chunk:
break
f.write(chunk)
done += len(chunk)
if total:
print(f"\r {done}/{total} 字节", end="", flush=True)
print(f"\n 已保存到 {target}")
return target
def norm_name(info: zipfile.ZipInfo) -> str:
"""修正 zip 内中文文件名。
打包工具有两种常见错误:用 UTF-8 编码但不打 0x800 标志(跨平台工具),
或用 GBK 编码且不打标志(中文 Windows 右键压缩)。依次尝试恢复:
UTF-8 优先(其字节序列极少与 GBK 混淆),失败再试 GBK。
"""
name = info.filename
if not info.flag_bits & 0x800:
try:
raw = name.encode("cp437")
except UnicodeEncodeError:
return name.replace("\\", "/")
for encoding in ("utf-8", "gbk"):
try:
name = raw.decode(encoding)
break
except UnicodeDecodeError:
continue
return name.replace("\\", "/")
def ignored(rel: str) -> bool:
parts = rel.split("/")
return (
any(p in IGNORE_DIRS for p in parts[:-1])
or Path(rel).name in IGNORE_NAMES
or Path(rel).suffix in IGNORE_SUFFIX
)
def common_prefix(names: list[str]) -> str:
"""所有条目共享同一顶层目录时返回该前缀(如 '短剧库/'),否则返回空串。"""
if not names:
return ""
tops = {n.split("/", 1)[0] for n in names}
if len(tops) == 1 and all("/" in n for n in names):
return next(iter(tops)) + "/"
return ""
def md5_of(path: Path) -> str:
h = hashlib.md5()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1 << 16), b""):
h.update(chunk)
return h.hexdigest()
def main() -> int:
parser = argparse.ArgumentParser(description="用新版 zip 更新本地短剧库源码")
parser.add_argument("zipfile", nargs="?", default=None,
help="本地压缩包路径;省略则从 --url 地址下载最新包")
parser.add_argument("--url", default=DEFAULT_URL, help=f"新版下载地址(默认 {DEFAULT_URL})")
parser.add_argument("--apply", action="store_true", help="真正写入本地(默认只预览)")
args = parser.parse_args()
if args.zipfile:
zip_path = Path(args.zipfile)
if not zip_path.exists():
print(f"找不到压缩包:{args.zipfile}")
return 1
else:
zip_path = download(args.url)
with zipfile.ZipFile(zip_path) as zf:
infos = [i for i in zf.infolist() if not i.is_dir()]
names = [norm_name(i) for i in infos]
# 先丢弃 macOS 压缩附带的资源文件,避免干扰顶层目录识别
pairs = [
(info, name)
for info, name in zip(infos, names)
if "__MACOSX" not in name.split("/") and not Path(name).name.startswith("._")
]
prefix = common_prefix([name for _, name in pairs])
if prefix:
print(f"检测到压缩包顶层目录:{prefix}(比对时已忽略)")
incoming: dict[str, bytes] = {}
for info, name in pairs:
rel = name[len(prefix):] if prefix else name
if rel and not ignored(rel):
incoming[rel] = zf.read(info)
added, modified, unchanged = [], [], []
for rel, blob in sorted(incoming.items()):
local = REPO_ROOT / rel
if not local.exists():
added.append(rel)
elif md5_of(local) != hashlib.md5(blob).hexdigest():
modified.append(rel)
else:
unchanged.append(rel)
local_files: set[str] = set()
for path in REPO_ROOT.rglob("*"):
if not path.is_file():
continue
rel = path.relative_to(REPO_ROOT).as_posix()
if ignored(rel) or rel in IGNORE_NAMES:
continue
local_files.add(rel)
deleted = sorted(local_files - set(incoming) - PROTECT)
print(f"\n对比完成:压缩包 {len(incoming)} 个文件,本地 {len(local_files)} 个文件")
print(f" 新增 {len(added)} 个")
for rel in added:
print(f" + {rel}")
print(f" 修改 {len(modified)} 个")
for rel in modified:
print(f" ~ {rel}")
print(f" 删除 {len(deleted)} 个")
for rel in deleted:
print(f" - {rel}")
print(f" 未变化 {len(unchanged)} 个")
if not (added or modified or deleted):
print("本地代码与压缩包一致,无需更新。")
return 0
if not args.apply:
print("\n当前为预览模式。确认无误后追加 --apply 执行更新,然后 git add -A && git commit。")
return 0
for rel in added + modified:
target = REPO_ROOT / rel
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(incoming[rel])
print(f"已写入 {rel}")
for rel in deleted:
(REPO_ROOT / rel).unlink(missing_ok=True)
print(f"已删除 {rel}")
print("\n更新完成。请执行 git status / git diff 复核后提交。")
return 0
if __name__ == "__main__":
sys.exit(main())