做安装包版本
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
"""
|
||||
以“计划任务(最高权限)”方式执行静默覆盖更新。
|
||||
|
||||
工作流:
|
||||
- 主程序(普通权限)下载新 exe 到可写目录(建议 ProgramData)并写入 update_request.json
|
||||
- 计划任务以 SYSTEM/管理员身份运行本程序
|
||||
- 本程序读取请求文件 -> 等待主程序退出 -> 覆盖安装目录 exe -> 重启 -> 清理请求文件
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
APP_NAME = "CleanDesktopOrganizer"
|
||||
REQUEST_FILE = "update_request.json"
|
||||
|
||||
|
||||
def _programdata_dir() -> str:
|
||||
base = os.environ.get("PROGRAMDATA") or r"C:\ProgramData"
|
||||
return os.path.join(base, APP_NAME)
|
||||
|
||||
|
||||
def _request_path() -> str:
|
||||
return os.path.join(_programdata_dir(), REQUEST_FILE)
|
||||
|
||||
|
||||
def _read_request() -> dict:
|
||||
p = _request_path()
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def _pid_exists(pid: int) -> bool:
|
||||
if pid <= 0:
|
||||
return False
|
||||
try:
|
||||
# tasklist 不需要管理员权限,SYSTEM 下也可用
|
||||
out = subprocess.run(
|
||||
["tasklist", "/FI", f"PID eq {pid}"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
).stdout
|
||||
return str(pid) in out
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _copy_with_retries(src: str, dst: str, retries: int = 10) -> bool:
|
||||
for _ in range(max(1, retries)):
|
||||
try:
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
if os.path.exists(dst):
|
||||
try:
|
||||
os.chmod(dst, 0o666)
|
||||
except Exception:
|
||||
pass
|
||||
# 用二进制流复制,避免权限/锁问题时直接抛错
|
||||
with open(src, "rb") as rf:
|
||||
data = rf.read()
|
||||
with open(dst, "wb") as wf:
|
||||
wf.write(data)
|
||||
return True
|
||||
except Exception:
|
||||
time.sleep(0.8)
|
||||
return False
|
||||
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
req = _read_request()
|
||||
except FileNotFoundError:
|
||||
return 0
|
||||
except Exception:
|
||||
return 2
|
||||
|
||||
src = str(req.get("src") or "")
|
||||
dst = str(req.get("dst") or "")
|
||||
pid = int(req.get("pid") or 0)
|
||||
restart = bool(req.get("restart", True))
|
||||
|
||||
if not src or not dst:
|
||||
return 3
|
||||
|
||||
# 等待主程序退出,最多 60 秒
|
||||
waited = 0.0
|
||||
while _pid_exists(pid) and waited < 60.0:
|
||||
time.sleep(0.5)
|
||||
waited += 0.5
|
||||
|
||||
ok = _copy_with_retries(src, dst, retries=15)
|
||||
try:
|
||||
os.remove(src)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 清理请求文件(成功/失败都尽量清掉,避免重复执行)
|
||||
try:
|
||||
os.remove(_request_path())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if ok and restart:
|
||||
try:
|
||||
subprocess.Popen([dst], creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NO_WINDOW)
|
||||
except Exception:
|
||||
pass
|
||||
return 0
|
||||
|
||||
return 4
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
Reference in New Issue
Block a user