Some checks failed
Windows project source guards / test (push) Failing after 25s
Замена файлов, порядок шагов, проверка запуска и откат теперь в общем ядре (байт-в-байт копия). Своё у Zapret KVN — только прослойка runner: план прежнего вида (его пишет ещё прежняя версия), журнал update.log, хвост startup.log упавшего запуска и сообщение об ошибке. Журнал замены теперь пишется на диск: замену, оборванную выключением, можно откатить. Замок окна обновления — общая переменная ZAPRET_UPDATE_HOLD. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
75 lines
3.1 KiB
Python
75 lines
3.1 KiB
Python
"""Передача экрана от окна обновления новой версии программы (из Zapret KVN).
|
||
|
||
Новая версия ставит себя сама и запускает программу раньше, чем закрывает
|
||
своё окно: иначе не узнать, способна ли она вообще стартовать. Чтобы два окна
|
||
не оказались на экране одновременно, программа стартует «за кулисами»:
|
||
|
||
* установщик создаёт файл-замок и передаёт его путь в переменной окружения;
|
||
* программа, собрав окно, создаёт рядом файл готовности и не показывает окно,
|
||
пока замок существует;
|
||
* установщик закрывает окно обновления и удаляет замок.
|
||
|
||
Переменная окружения, а не аргумент командной строки: при откате запускается
|
||
прежняя версия, и незнакомый аргумент она бы не пережила. Имя переменной одно
|
||
на обе программы.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
from pathlib import Path
|
||
|
||
HOLD_ENV = "ZAPRET_UPDATE_HOLD"
|
||
# Если установщик исчез, не сняв замок, программа показывается сама.
|
||
MAX_HOLD_S = 20.0
|
||
|
||
|
||
def ready_marker(hold: Path) -> Path:
|
||
return hold.with_name(hold.name + ".ready")
|
||
|
||
|
||
# ── сторона установщика ─────────────────────────────────────────
|
||
|
||
|
||
def arm(hold: Path) -> dict[str, str]:
|
||
"""Поставить замок; вернуть окружение для запускаемой программы."""
|
||
hold.parent.mkdir(parents=True, exist_ok=True)
|
||
ready_marker(hold).unlink(missing_ok=True)
|
||
hold.write_text("hold", encoding="utf-8")
|
||
return {HOLD_ENV: str(hold)}
|
||
|
||
|
||
def is_ready(hold: Path) -> bool:
|
||
return ready_marker(hold).exists()
|
||
|
||
|
||
def release(hold: Path) -> None:
|
||
for path in (hold, ready_marker(hold)):
|
||
try:
|
||
path.unlink(missing_ok=True)
|
||
except OSError:
|
||
pass
|
||
|
||
|
||
# ── сторона программы ───────────────────────────────────────────
|
||
|
||
|
||
def claim() -> Path | None:
|
||
"""Замок, под которым запущена программа (None — обычный запуск)."""
|
||
return claim_from(os.environ)
|
||
|
||
|
||
def claim_from(environment) -> Path | None:
|
||
# pop: переменная не должна достаться дочерним процессам.
|
||
value = environment.pop(HOLD_ENV, "")
|
||
if not value:
|
||
return None
|
||
hold = Path(value)
|
||
return hold if hold.exists() else None
|
||
|
||
|
||
def confirm_ready(hold: Path) -> None:
|
||
try:
|
||
ready_marker(hold).write_text("ready", encoding="utf-8")
|
||
except OSError:
|
||
pass
|