update
This commit is contained in:
@@ -423,6 +423,7 @@ def manual_check_for_updates(parent=None) -> None:
|
||||
f"UPDATE_NOT_NEEDED mode=manual reason=already_current local_ver={APP_VERSION} "
|
||||
f"local_build={_local_build_stamp()}"
|
||||
)
|
||||
sync_office_update_hint(None)
|
||||
return
|
||||
|
||||
lvl = info.get("update_level")
|
||||
@@ -446,9 +447,8 @@ def _startup_should_show_dialog(info: dict[str, Any]) -> bool:
|
||||
return True
|
||||
if info.get("update_level") == "required":
|
||||
return True
|
||||
if info.get("update_level") == "optional":
|
||||
return False
|
||||
return True
|
||||
# optional, recommended und unbekannte Level: kein Startup-Popup (Badge stattdessen).
|
||||
return False
|
||||
|
||||
|
||||
# Verhindert zwei gleichzeitig offene Update-Dialoge (Startup-Poll + manueller Button).
|
||||
@@ -552,10 +552,9 @@ def _show_update_notification(info: dict[str, Any], parent) -> None:
|
||||
|
||||
|
||||
def maybe_show_startup_update_dialog(info: dict[str, Any] | None, parent=None) -> None:
|
||||
"""Einmaliger Startup-Dialog aus AzA Office (Owner).
|
||||
"""Startup-Dialog nur für Pflicht-/Mindestversion-Updates.
|
||||
|
||||
Verwendet das bereits vom Update-Button-Poll geladene ``info`` (kein zweiter
|
||||
Manifest-Fetch), respektiert den Session-Guard und die optional/required-Regeln.
|
||||
Optionale Updates erscheinen nur als Badge (kein störendes Popup beim Start).
|
||||
"""
|
||||
if not info:
|
||||
return
|
||||
@@ -564,17 +563,213 @@ def maybe_show_startup_update_dialog(info: dict[str, Any] | None, parent=None) -
|
||||
_show_update_notification(info, parent=parent)
|
||||
|
||||
|
||||
def show_update_dialog_from_info(info: dict[str, Any] | None, parent=None) -> None:
|
||||
"""Öffnet den bestehenden Update-Dialog aus bereits geladenem Manifest-Info."""
|
||||
if not info:
|
||||
return
|
||||
_show_update_notification(info, parent=parent)
|
||||
|
||||
|
||||
def _close_app_after_update(parent) -> None:
|
||||
"""Schliesst die App nach gestartetem Updater sauber."""
|
||||
try:
|
||||
if hasattr(parent, "shutdown_app_completely"):
|
||||
parent.shutdown_app_completely(reason="update_on_exit")
|
||||
elif hasattr(parent, "shutdown"):
|
||||
parent.shutdown()
|
||||
elif hasattr(parent, "_on_close"):
|
||||
parent._on_close()
|
||||
else:
|
||||
parent.destroy()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# --- Chat-only Update-Kanal (separates Manifest, IPC fuer Kontaktpanel) ---
|
||||
|
||||
CHAT_UPDATE_MANIFEST_URLS = (
|
||||
"https://api.aza-medwork.ch/download/chat_version.json",
|
||||
"https://api.aza-medwork.ch/release/chat_version.json",
|
||||
)
|
||||
CHAT_UPDATE_TEST_MANIFEST_URLS = (
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_data", "chat_version_test.json"),
|
||||
)
|
||||
|
||||
_CHAT_UPDATE_HINT_FILE = "chat_update_hint.json"
|
||||
_CHAT_UPDATE_REQUEST_FLAG = "chat_update_request.flag"
|
||||
_chat_update_install_inflight = False
|
||||
|
||||
|
||||
def _chat_update_ipc_path(name: str) -> str:
|
||||
return os.path.join(get_writable_data_dir(), name)
|
||||
|
||||
|
||||
def sync_chat_update_hint(info: dict[str, Any] | None) -> None:
|
||||
hint_path = _chat_update_ipc_path(_CHAT_UPDATE_HINT_FILE)
|
||||
try:
|
||||
if not info or not info.get("update_available"):
|
||||
if os.path.isfile(hint_path):
|
||||
os.remove(hint_path)
|
||||
return
|
||||
payload = {
|
||||
"available": True,
|
||||
"latest_version": str(info.get("latest_version") or "").strip(),
|
||||
"update_level": str(info.get("update_level") or "recommended").strip(),
|
||||
}
|
||||
with open(hint_path, "w", encoding="utf-8") as f:
|
||||
json.dump(payload, f, ensure_ascii=False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def read_chat_update_hint() -> dict[str, Any] | None:
|
||||
try:
|
||||
path = _chat_update_ipc_path(_CHAT_UPDATE_HINT_FILE)
|
||||
if not os.path.isfile(path):
|
||||
return None
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
if isinstance(data, dict) and data.get("available"):
|
||||
return data
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def fetch_chat_remote_manifest() -> tuple[dict[str, Any] | None, str | None]:
|
||||
test_urls = list(CHAT_UPDATE_TEST_MANIFEST_URLS) if os.getenv("AZA_CHAT_UPDATE_TEST", "").strip() == "1" else []
|
||||
urls = test_urls + list(CHAT_UPDATE_MANIFEST_URLS) + list(UPDATE_MANIFEST_URLS)
|
||||
last: str | None = None
|
||||
for url in urls:
|
||||
try:
|
||||
if url.startswith("file:"):
|
||||
from urllib.request import urlopen
|
||||
|
||||
with urlopen(url) as resp:
|
||||
data = json.loads(resp.read().decode("utf-8"))
|
||||
elif url.endswith(".json") and os.path.isfile(url):
|
||||
with open(url, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
else:
|
||||
r = requests.get(url, timeout=6)
|
||||
if r.status_code != 200:
|
||||
last = f"http_status={r.status_code} url={url}"
|
||||
continue
|
||||
data = r.json()
|
||||
if not isinstance(data, dict):
|
||||
last = f"invalid_manifest_shape url={url}"
|
||||
continue
|
||||
data = dict(data)
|
||||
data["_manifest_url"] = url
|
||||
return data, None
|
||||
except Exception as e:
|
||||
last = f"exc={type(e).__name__} url={url}"
|
||||
return None, last or "no_manifest"
|
||||
|
||||
|
||||
def check_for_chat_updates() -> dict[str, Any] | None:
|
||||
data, err = fetch_chat_remote_manifest()
|
||||
if err or not data:
|
||||
sync_chat_update_hint(None)
|
||||
return None
|
||||
if str(data.get("channel") or "stable").strip() != APP_CHANNEL:
|
||||
sync_chat_update_hint(None)
|
||||
return None
|
||||
info = _build_update_info(data)
|
||||
sync_chat_update_hint(info)
|
||||
return info
|
||||
|
||||
|
||||
def prompt_chat_update_if_required(parent=None) -> None:
|
||||
data, err = fetch_chat_remote_manifest()
|
||||
if err or not data:
|
||||
return
|
||||
if str(data.get("channel") or "stable").strip() != APP_CHANNEL:
|
||||
return
|
||||
info = _build_update_info(data)
|
||||
if not info:
|
||||
sync_chat_update_hint(None)
|
||||
return
|
||||
sync_chat_update_hint(info)
|
||||
if _startup_should_show_dialog(info):
|
||||
_show_chat_update_notification(info, parent=parent)
|
||||
|
||||
|
||||
def request_chat_update_dialog_ipc() -> None:
|
||||
try:
|
||||
path = _chat_update_ipc_path(_CHAT_UPDATE_REQUEST_FLAG)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write("1")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def consume_chat_update_request_flag() -> bool:
|
||||
try:
|
||||
path = _chat_update_ipc_path(_CHAT_UPDATE_REQUEST_FLAG)
|
||||
if not os.path.isfile(path):
|
||||
return False
|
||||
os.remove(path)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def manual_check_for_chat_updates(parent=None) -> None:
|
||||
global _chat_update_install_inflight
|
||||
if _chat_update_install_inflight:
|
||||
return
|
||||
info = check_for_chat_updates()
|
||||
if not info:
|
||||
sync_chat_update_hint(None)
|
||||
return
|
||||
_show_chat_update_notification(info, parent=parent)
|
||||
|
||||
|
||||
def _show_chat_update_notification(info: dict[str, Any], parent=None) -> None:
|
||||
global _update_dialog_open, _chat_update_install_inflight
|
||||
if _update_dialog_open or _chat_update_install_inflight:
|
||||
return
|
||||
latest = str(info.get("latest_version", "") or "")
|
||||
current = str(info.get("current_version", "") or "")
|
||||
notes = info.get("notes") or []
|
||||
mandatory = bool(info.get("below_min_required") or info.get("update_level") == "required")
|
||||
result_for_dialog = {
|
||||
"local": {"version": current},
|
||||
"latest_version": latest,
|
||||
"notes": notes,
|
||||
"mandatory": mandatory,
|
||||
"below_min_supported": bool(info.get("below_min_required")),
|
||||
}
|
||||
_update_dialog_open = True
|
||||
try:
|
||||
from aza_updater import _show_update_dialog_professional
|
||||
|
||||
choice = _show_update_dialog_professional(result_for_dialog, parent=parent)
|
||||
except Exception:
|
||||
choice = "later"
|
||||
finally:
|
||||
_update_dialog_open = False
|
||||
if choice != "now":
|
||||
return
|
||||
_chat_update_install_inflight = True
|
||||
try:
|
||||
from aza_updater import launch_external_installer
|
||||
|
||||
manifest_url = str(info.get("_manifest_url") or CHAT_UPDATE_MANIFEST_URLS[0])
|
||||
ok, _msg = launch_external_installer(
|
||||
{"remote": {"_manifest_url": manifest_url}, "latest_version": latest},
|
||||
restart_exe="AZA_Chat.exe",
|
||||
)
|
||||
if ok and parent is not None:
|
||||
try:
|
||||
parent.after(300, lambda: _close_app_after_update(parent))
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
_chat_update_install_inflight = False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
d, e = fetch_remote_manifest()
|
||||
print("fetch_err", e, "keys", list(d.keys()) if d else None)
|
||||
|
||||
Reference in New Issue
Block a user