mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00

- Launcher can now be accessed via Tools>Upgrade/Downgrade - Anki closes automatically on update - When launcher not available, show update link like in the past - It appears that access to the modern console host requires an app to be built with the windows console subsystem, so we introduce an extra anki-console.exe binary to relaunch ourselves with. Solves https://forums.ankiweb.net/t/new-online-installer-launcher/62745/50 - Windows now requires you to close the terminal like on a Mac, as I couldn't figure out how to have it automatically close. Suggestions welcome! - Reduce the amount of duplicate/near-duplicate code in the various platform files, and improve readability - Add a helper to install the current code into the launcher env - Fix cargo test failing to build on ARM64 Windows
150 lines
4.6 KiB
Python
150 lines
4.6 KiB
Python
# Copyright: Ankitects Pty Ltd and contributors
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import aqt
|
|
from anki.buildinfo import buildhash
|
|
from anki.collection import CheckForUpdateResponse, Collection
|
|
from anki.utils import dev_mode, int_time, int_version, is_mac, is_win, plat_desc
|
|
from aqt.operations import QueryOp
|
|
from aqt.qt import *
|
|
from aqt.utils import openLink, show_warning, showText, tr
|
|
|
|
|
|
def check_for_update() -> None:
|
|
from aqt import mw
|
|
|
|
def do_check(_col: Collection) -> CheckForUpdateResponse:
|
|
return mw.backend.check_for_update(
|
|
version=int_version(),
|
|
buildhash=buildhash,
|
|
os=plat_desc(),
|
|
install_id=mw.pm.meta["id"],
|
|
last_message_id=max(0, mw.pm.meta["lastMsg"]),
|
|
)
|
|
|
|
def on_done(resp: CheckForUpdateResponse) -> None:
|
|
# is clock off?
|
|
if not dev_mode:
|
|
diff = abs(resp.current_time - int_time())
|
|
if diff > 300:
|
|
diff_text = tr.qt_misc_second(count=diff)
|
|
warn = (
|
|
tr.qt_misc_in_order_to_ensure_your_collection(val="%s") % diff_text
|
|
)
|
|
show_warning(
|
|
warn,
|
|
parent=mw,
|
|
textFormat=Qt.TextFormat.RichText,
|
|
callback=mw.app.closeAllWindows,
|
|
)
|
|
return
|
|
# should we show a message?
|
|
if msg := resp.message:
|
|
showText(msg, parent=mw, type="html")
|
|
mw.pm.meta["lastMsg"] = resp.last_message_id
|
|
# has Anki been updated?
|
|
if ver := resp.new_version:
|
|
if mw.pm.meta.get("suppressUpdate", None) != ver:
|
|
prompt_to_update(mw, ver)
|
|
|
|
def on_fail(exc: Exception) -> None:
|
|
print(f"update check failed: {exc}")
|
|
|
|
QueryOp(parent=mw, op=do_check, success=on_done).failure(
|
|
on_fail
|
|
).without_collection().run_in_background()
|
|
|
|
|
|
def prompt_to_update(mw: aqt.AnkiQt, ver: str) -> None:
|
|
msg = (
|
|
tr.qt_misc_anki_updatedanki_has_been_released(val=ver)
|
|
+ tr.qt_misc_would_you_like_to_download_it()
|
|
)
|
|
|
|
msgbox = QMessageBox(mw)
|
|
msgbox.setStandardButtons(
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
)
|
|
msgbox.setIcon(QMessageBox.Icon.Information)
|
|
msgbox.setText(msg)
|
|
|
|
button = QPushButton(tr.qt_misc_ignore_this_update())
|
|
msgbox.addButton(button, QMessageBox.ButtonRole.RejectRole)
|
|
msgbox.setDefaultButton(QMessageBox.StandardButton.Yes)
|
|
ret = msgbox.exec()
|
|
|
|
if msgbox.clickedButton() == button:
|
|
# ignore this update
|
|
mw.pm.meta["suppressUpdate"] = ver
|
|
elif ret == QMessageBox.StandardButton.Yes:
|
|
if have_launcher():
|
|
update_and_restart()
|
|
else:
|
|
openLink(aqt.appWebsiteDownloadSection)
|
|
|
|
|
|
def _anki_launcher_path() -> str | None:
|
|
return os.getenv("ANKI_LAUNCHER")
|
|
|
|
|
|
def have_launcher() -> bool:
|
|
return _anki_launcher_path() is not None
|
|
|
|
|
|
def update_and_restart() -> None:
|
|
from aqt import mw
|
|
|
|
launcher = _anki_launcher_path()
|
|
assert launcher
|
|
|
|
_trigger_launcher_run()
|
|
|
|
with contextlib.suppress(ResourceWarning):
|
|
env = os.environ.copy()
|
|
creationflags = 0
|
|
if sys.platform == "win32":
|
|
creationflags = (
|
|
subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
|
|
)
|
|
subprocess.Popen(
|
|
[launcher],
|
|
start_new_session=True,
|
|
stdin=subprocess.DEVNULL,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
env=env,
|
|
creationflags=creationflags,
|
|
)
|
|
|
|
mw.app.quit()
|
|
|
|
|
|
def _trigger_launcher_run() -> None:
|
|
"""Bump the mtime on pyproject.toml in the local data directory to trigger an update on next run."""
|
|
try:
|
|
# Get the local data directory equivalent to Rust's dirs::data_local_dir()
|
|
if is_win:
|
|
from .winpaths import get_local_appdata
|
|
|
|
data_dir = Path(get_local_appdata())
|
|
elif is_mac:
|
|
data_dir = Path.home() / "Library" / "Application Support"
|
|
else: # Linux
|
|
data_dir = Path(
|
|
os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share")
|
|
)
|
|
|
|
pyproject_path = data_dir / "AnkiProgramFiles" / "pyproject.toml"
|
|
|
|
if pyproject_path.exists():
|
|
# Touch the file to update its mtime
|
|
pyproject_path.touch()
|
|
except Exception as e:
|
|
print(e)
|