Check updates in the background
This commit is contained in:
parent
9349374ab9
commit
367e974b77
7 changed files with 311 additions and 63 deletions
15
START.bat
15
START.bat
|
|
@ -178,21 +178,6 @@ if errorlevel 1 (
|
|||
)
|
||||
echo.
|
||||
|
||||
:: RPG Maker Ace tools (RV2JSON + decrypter) — downloaded from GitHub, not stored in git
|
||||
echo Checking RPG Maker Ace tools...
|
||||
python -m util.ace.update_tools
|
||||
if errorlevel 1 (
|
||||
echo WARNING: Ace tool download failed. Ace translation features may not work until fixed.
|
||||
)
|
||||
echo.
|
||||
|
||||
echo Checking Forge plugins...
|
||||
python -m util.forge.update_tools
|
||||
if errorlevel 1 (
|
||||
echo WARNING: Forge update failed. Playtest Forge features use the bundled upstream copy.
|
||||
)
|
||||
echo.
|
||||
|
||||
:: Launch the GUI
|
||||
echo ==========================================
|
||||
echo Launching DazedMTLTool GUI...
|
||||
|
|
|
|||
12
START.sh
12
START.sh
|
|
@ -220,18 +220,6 @@ else
|
|||
fi
|
||||
echo
|
||||
|
||||
echo "Checking RPG Maker Ace tools..."
|
||||
if ! python -m util.ace.update_tools; then
|
||||
echo "WARNING: Ace tool download failed. Ace translation features may not work until fixed."
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "Checking Forge plugins..."
|
||||
if ! python -m util.forge.update_tools; then
|
||||
echo "WARNING: Forge update failed. Playtest Forge features use the bundled upstream copy."
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "=========================================="
|
||||
echo " Launching DazedMTLTool GUI..."
|
||||
echo "=========================================="
|
||||
|
|
|
|||
243
gui/main.py
243
gui/main.py
|
|
@ -40,6 +40,86 @@ def load_application_icon() -> QIcon:
|
|||
return QIcon()
|
||||
|
||||
|
||||
def check_tool_update() -> str | None:
|
||||
"""Return latest commit SHA when a tool update is available, else None."""
|
||||
try:
|
||||
api = (
|
||||
f"https://gitgud.io/api/v4/projects/"
|
||||
f"{UpdateThread.REPO_USER}%2F{UpdateThread.REPO_NAME}"
|
||||
f"/repository/branches/{UpdateThread.REPO_BRANCH}"
|
||||
)
|
||||
req = urllib.request.Request(api, headers={"User-Agent": "DazedMTLTool"})
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
latest_sha = json.loads(resp.read())["commit"]["id"]
|
||||
sha_path = Path(UpdateThread.SHA_FILE)
|
||||
current_sha = sha_path.read_text().strip() if sha_path.is_file() else ""
|
||||
if latest_sha != current_sha:
|
||||
return latest_sha
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def check_all_updates() -> dict:
|
||||
"""Check tool, Ace, and Forge for available updates."""
|
||||
results = {"tool": None, "ace": False, "forge": False}
|
||||
results["tool"] = check_tool_update()
|
||||
try:
|
||||
from util.ace.update_tools import check_ace_tools_update
|
||||
results["ace"] = check_ace_tools_update()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
from util.forge.update_tools import check_forge_plugins_update
|
||||
results["forge"] = check_forge_plugins_update()
|
||||
except Exception:
|
||||
pass
|
||||
return results
|
||||
|
||||
|
||||
def any_updates_available(results: dict) -> bool:
|
||||
return bool(results.get("tool")) or results.get("ace") or results.get("forge")
|
||||
|
||||
|
||||
class BackgroundUpdateCheckThread(QThread):
|
||||
"""Checks for tool, Ace, and Forge updates without blocking the UI."""
|
||||
|
||||
finished = pyqtSignal(dict)
|
||||
|
||||
def run(self):
|
||||
self.finished.emit(check_all_updates())
|
||||
|
||||
|
||||
class AuxToolsUpdateThread(QThread):
|
||||
"""Download Ace and/or Forge updates without updating the main tool."""
|
||||
|
||||
progress = pyqtSignal(str)
|
||||
finished = pyqtSignal(bool, str)
|
||||
|
||||
def __init__(self, update_ace: bool = False, update_forge: bool = False, parent=None):
|
||||
super().__init__(parent)
|
||||
self.update_ace = update_ace
|
||||
self.update_forge = update_forge
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
if self.update_ace:
|
||||
from util.ace.update_tools import ensure_ace_tools
|
||||
self.progress.emit("Updating RPG Maker Ace tools…")
|
||||
if not ensure_ace_tools(log_fn=lambda m: self.progress.emit(m)):
|
||||
self.finished.emit(False, "Ace tool update failed.")
|
||||
return
|
||||
if self.update_forge:
|
||||
from util.forge.update_tools import ensure_forge_plugins
|
||||
self.progress.emit("Updating Forge plugins…")
|
||||
if not ensure_forge_plugins(log_fn=lambda m: self.progress.emit(m)):
|
||||
self.finished.emit(False, "Forge plugin update failed.")
|
||||
return
|
||||
self.finished.emit(True, "components_updated")
|
||||
except Exception as exc:
|
||||
self.finished.emit(False, str(exc))
|
||||
|
||||
|
||||
class UpdateThread(QThread):
|
||||
"""Downloads and applies a tool update from the GitGud repository."""
|
||||
|
||||
|
|
@ -79,18 +159,22 @@ class UpdateThread(QThread):
|
|||
except Exception as exc:
|
||||
self.finished.emit(False, str(exc))
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def _fetch_latest_sha(self):
|
||||
@classmethod
|
||||
def fetch_latest_sha(cls) -> str:
|
||||
api = (
|
||||
f"https://gitgud.io/api/v4/projects/"
|
||||
f"{self.REPO_USER}%2F{self.REPO_NAME}"
|
||||
f"/repository/branches/{self.REPO_BRANCH}"
|
||||
f"{cls.REPO_USER}%2F{cls.REPO_NAME}"
|
||||
f"/repository/branches/{cls.REPO_BRANCH}"
|
||||
)
|
||||
req = urllib.request.Request(api, headers={"User-Agent": "DazedMTLTool"})
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
return json.loads(resp.read())["commit"]["id"]
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def _fetch_latest_sha(self):
|
||||
return self.fetch_latest_sha()
|
||||
|
||||
def _read_stored_sha(self):
|
||||
p = Path(self.SHA_FILE)
|
||||
return p.read_text().strip() if p.exists() else ""
|
||||
|
|
@ -150,7 +234,7 @@ class UpdateThread(QThread):
|
|||
class UpdateDialog(QDialog):
|
||||
"""Modal dialog that shows update progress and result."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, parent=None, pending_updates: dict | None = None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Tool Update")
|
||||
self.setMinimumWidth(420)
|
||||
|
|
@ -172,13 +256,55 @@ class UpdateDialog(QDialog):
|
|||
layout.addWidget(self.close_btn)
|
||||
|
||||
self._thread = None
|
||||
self._pending = pending_updates or {}
|
||||
|
||||
def start(self, check_only=False):
|
||||
self._thread = UpdateThread(check_only=check_only, parent=self)
|
||||
self._thread.progress.connect(self.status_label.setText)
|
||||
self._thread.finished.connect(self._on_finished)
|
||||
if self._pending:
|
||||
self._show_pending_updates()
|
||||
return
|
||||
|
||||
self._thread = BackgroundUpdateCheckThread(parent=self)
|
||||
self._thread.finished.connect(self._on_check_finished)
|
||||
self._thread.start()
|
||||
|
||||
def _update_labels(self) -> list[str]:
|
||||
labels = []
|
||||
if self._pending.get("tool"):
|
||||
labels.append("DazedMTLTool")
|
||||
if self._pending.get("ace"):
|
||||
labels.append("RPG Maker Ace tools")
|
||||
if self._pending.get("forge"):
|
||||
labels.append("Forge plugins")
|
||||
return labels
|
||||
|
||||
def _show_pending_updates(self):
|
||||
self.progress_bar.setRange(0, 1)
|
||||
self.progress_bar.setValue(1)
|
||||
self.close_btn.setText("Close")
|
||||
|
||||
labels = self._update_labels()
|
||||
if not labels:
|
||||
self.status_label.setText("✅ Already up to date.")
|
||||
return
|
||||
|
||||
detail = "\n".join(f" • {label}" for label in labels)
|
||||
tool_sha = self._pending.get("tool")
|
||||
if tool_sha:
|
||||
detail = f" • DazedMTLTool ({tool_sha[:8]})\n" + "\n".join(
|
||||
f" • {label}" for label in labels if label != "DazedMTLTool"
|
||||
)
|
||||
self.status_label.setText(
|
||||
f"🆕 Updates available:\n{detail}\n\nClick 'Update Now' to install."
|
||||
)
|
||||
self.close_btn.setText("Cancel")
|
||||
update_btn = QPushButton("Update Now")
|
||||
update_btn.clicked.connect(self._do_update)
|
||||
self.layout().insertWidget(self.layout().count() - 1, update_btn)
|
||||
|
||||
def _on_check_finished(self, results: dict):
|
||||
self._pending = results
|
||||
self._show_pending_updates()
|
||||
|
||||
def _on_finished(self, success, message):
|
||||
self.progress_bar.setRange(0, 1)
|
||||
self.progress_bar.setValue(1)
|
||||
|
|
@ -190,6 +316,11 @@ class UpdateDialog(QDialog):
|
|||
|
||||
if message == "already_up_to_date":
|
||||
self.status_label.setText("✅ Already up to date.")
|
||||
elif message == "components_updated":
|
||||
self._pending = {"tool": None, "ace": False, "forge": False}
|
||||
self.status_label.setText("✅ Component updates installed.")
|
||||
if isinstance(self.parent(), DazedMTLGUI):
|
||||
self.parent().clear_update_indicator()
|
||||
elif message.startswith("update_available:"):
|
||||
sha = message.split(":", 1)[1]
|
||||
self.status_label.setText(
|
||||
|
|
@ -201,12 +332,14 @@ class UpdateDialog(QDialog):
|
|||
self.layout().insertWidget(self.layout().count() - 1, update_btn)
|
||||
elif message.startswith("updated:"):
|
||||
sha = message.split(":", 1)[1]
|
||||
self._pending = {"tool": None, "ace": False, "forge": False}
|
||||
self.status_label.setText(
|
||||
f"✅ Updated to {sha}.\n\nPlease restart the tool for changes to take effect."
|
||||
)
|
||||
if isinstance(self.parent(), DazedMTLGUI):
|
||||
self.parent().clear_update_indicator()
|
||||
|
||||
def _do_update(self):
|
||||
# Remove any extra buttons so only the close button remains
|
||||
def _clear_extra_buttons(self):
|
||||
for i in reversed(range(self.layout().count())):
|
||||
item = self.layout().itemAt(i)
|
||||
if item and item.widget() and item.widget() is not self.close_btn:
|
||||
|
|
@ -215,9 +348,37 @@ class UpdateDialog(QDialog):
|
|||
if w.text() != "Close":
|
||||
self.layout().removeWidget(w)
|
||||
w.deleteLater()
|
||||
|
||||
def _do_update(self):
|
||||
self._clear_extra_buttons()
|
||||
self.progress_bar.setRange(0, 0)
|
||||
self.status_label.setText("Downloading update…")
|
||||
self.start(check_only=False)
|
||||
self.close_btn.setText("Cancel")
|
||||
|
||||
if self._pending.get("tool"):
|
||||
self.status_label.setText("Downloading update…")
|
||||
self._thread = UpdateThread(check_only=False, parent=self)
|
||||
self._thread.progress.connect(self.status_label.setText)
|
||||
self._thread.finished.connect(self._on_finished)
|
||||
self._thread.start()
|
||||
return
|
||||
|
||||
update_ace = bool(self._pending.get("ace"))
|
||||
update_forge = bool(self._pending.get("forge"))
|
||||
if update_ace or update_forge:
|
||||
self.status_label.setText("Downloading component updates…")
|
||||
self._thread = AuxToolsUpdateThread(
|
||||
update_ace=update_ace,
|
||||
update_forge=update_forge,
|
||||
parent=self,
|
||||
)
|
||||
self._thread.progress.connect(self.status_label.setText)
|
||||
self._thread.finished.connect(self._on_finished)
|
||||
self._thread.start()
|
||||
return
|
||||
|
||||
self.progress_bar.setRange(0, 1)
|
||||
self.progress_bar.setValue(1)
|
||||
self.status_label.setText("✅ Already up to date.")
|
||||
|
||||
# Import configuration widgets
|
||||
from gui.config_tab import ConfigTab
|
||||
|
|
@ -230,10 +391,15 @@ class DazedMTLGUI(QMainWindow):
|
|||
def __init__(self):
|
||||
super().__init__()
|
||||
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
|
||||
self._pending_updates: dict = {}
|
||||
self._update_check_thread = None
|
||||
self._update_icon = "🔄"
|
||||
self.btn_update = None
|
||||
self.init_ui()
|
||||
self.setup_status_bar()
|
||||
self.setup_font_scaling()
|
||||
self.restore_window_state()
|
||||
QTimer.singleShot(500, self.start_background_update_check)
|
||||
|
||||
def restore_window_state(self):
|
||||
"""Restore window geometry and state from settings."""
|
||||
|
|
@ -427,10 +593,10 @@ class DazedMTLGUI(QMainWindow):
|
|||
sidebar_layout.addStretch()
|
||||
|
||||
# Update button at the bottom of the sidebar
|
||||
btn_update = self.create_nav_button("🔄", "Check for Updates")
|
||||
btn_update.setCheckable(False)
|
||||
btn_update.clicked.connect(self.show_update_dialog)
|
||||
sidebar_layout.addWidget(btn_update)
|
||||
self.btn_update = self.create_nav_button(self._update_icon, "Check for Updates")
|
||||
self.btn_update.setCheckable(False)
|
||||
self.btn_update.clicked.connect(self.show_update_dialog)
|
||||
sidebar_layout.addWidget(self.btn_update)
|
||||
|
||||
sidebar.setLayout(sidebar_layout)
|
||||
|
||||
|
|
@ -485,11 +651,52 @@ class DazedMTLGUI(QMainWindow):
|
|||
self.config_tab.config_changed.connect(self.on_config_changed)
|
||||
self.content_stack.addWidget(self.config_tab)
|
||||
|
||||
def start_background_update_check(self):
|
||||
"""Check for tool, Ace, and Forge updates after the GUI is visible."""
|
||||
if self._update_check_thread and self._update_check_thread.isRunning():
|
||||
return
|
||||
self._update_check_thread = BackgroundUpdateCheckThread(self)
|
||||
self._update_check_thread.finished.connect(self._on_background_update_check)
|
||||
self._update_check_thread.start()
|
||||
|
||||
def _on_background_update_check(self, results: dict):
|
||||
self._pending_updates = results
|
||||
if any_updates_available(results):
|
||||
self.set_update_indicator()
|
||||
else:
|
||||
self.clear_update_indicator()
|
||||
|
||||
def set_update_indicator(self):
|
||||
"""Highlight the update button when updates are available."""
|
||||
if not self.btn_update:
|
||||
return
|
||||
self.btn_update.setToolTip("Updates available — click to install")
|
||||
from gui.platform_glyph import configure_nav_toolbutton
|
||||
configure_nav_toolbutton(
|
||||
self.btn_update, self._update_icon, horizontal=False, update_available=True,
|
||||
)
|
||||
|
||||
def clear_update_indicator(self):
|
||||
"""Restore the default update button appearance."""
|
||||
self._pending_updates = {}
|
||||
if not self.btn_update:
|
||||
return
|
||||
self.btn_update.setToolTip("Check for Updates")
|
||||
from gui.platform_glyph import configure_nav_toolbutton
|
||||
configure_nav_toolbutton(
|
||||
self.btn_update, self._update_icon, horizontal=False, update_available=False,
|
||||
)
|
||||
|
||||
def show_update_dialog(self):
|
||||
"""Open the update dialog and check for a newer version."""
|
||||
dlg = UpdateDialog(self)
|
||||
dlg = UpdateDialog(self, pending_updates=self._pending_updates.copy())
|
||||
dlg.start(check_only=True)
|
||||
dlg.exec_()
|
||||
self._pending_updates = dlg._pending
|
||||
if any_updates_available(dlg._pending):
|
||||
self.set_update_indicator()
|
||||
else:
|
||||
self.clear_update_indicator()
|
||||
|
||||
def on_config_changed(self):
|
||||
"""Handle configuration changes."""
|
||||
|
|
|
|||
|
|
@ -84,12 +84,14 @@ def _linux_nav_pixmap(glyph: str, *, size: int, font_px: int, color: QColor) ->
|
|||
return pixmap
|
||||
|
||||
|
||||
def _linux_nav_icon(icon_text: str, *, size: int, font_px: int) -> QIcon:
|
||||
def _linux_nav_icon(icon_text: str, *, size: int, font_px: int, normal_color: QColor | None = None) -> QIcon:
|
||||
"""Render a nav glyph into a fixed-size pixmap so every tab aligns the same."""
|
||||
glyph = platform_nav_glyph(icon_text)
|
||||
gray_color = normal_color or QColor("#cccccc")
|
||||
white_color = QColor("#ffffff") if normal_color is None else normal_color.lighter(125)
|
||||
icon = QIcon()
|
||||
gray = _linux_nav_pixmap(glyph, size=size, font_px=font_px, color=QColor("#cccccc"))
|
||||
white = _linux_nav_pixmap(glyph, size=size, font_px=font_px, color=QColor("#ffffff"))
|
||||
gray = _linux_nav_pixmap(glyph, size=size, font_px=font_px, color=gray_color)
|
||||
white = _linux_nav_pixmap(glyph, size=size, font_px=font_px, color=white_color)
|
||||
icon.addPixmap(gray, QIcon.Normal, QIcon.Off)
|
||||
icon.addPixmap(gray, QIcon.Disabled, QIcon.Off)
|
||||
icon.addPixmap(white, QIcon.Active, QIcon.Off)
|
||||
|
|
@ -97,15 +99,18 @@ def _linux_nav_icon(icon_text: str, *, size: int, font_px: int) -> QIcon:
|
|||
return icon
|
||||
|
||||
|
||||
def _nav_toolbutton_stylesheet(*, horizontal: bool, icon_only: bool) -> str:
|
||||
def _nav_toolbutton_stylesheet(*, horizontal: bool, icon_only: bool, update_available: bool = False) -> str:
|
||||
border_prop = "border-bottom" if horizontal else "border-left"
|
||||
accent = "#ff5252" if update_available else "#007acc"
|
||||
text_color = "#ff5252" if update_available else "#cccccc"
|
||||
checked_color = "#ff8a80" if update_available else "#ffffff"
|
||||
if icon_only:
|
||||
return f"""
|
||||
QToolButton {{
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
{border_prop}: 3px solid transparent;
|
||||
color: #cccccc;
|
||||
color: {text_color};
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}}
|
||||
|
|
@ -114,8 +119,8 @@ def _nav_toolbutton_stylesheet(*, horizontal: bool, icon_only: bool) -> str:
|
|||
}}
|
||||
QToolButton:checked {{
|
||||
background-color: #37373d;
|
||||
{border_prop}: 3px solid #007acc;
|
||||
color: #ffffff;
|
||||
{border_prop}: 3px solid {accent};
|
||||
color: {checked_color};
|
||||
}}
|
||||
"""
|
||||
|
||||
|
|
@ -125,7 +130,7 @@ def _nav_toolbutton_stylesheet(*, horizontal: bool, icon_only: bool) -> str:
|
|||
background-color: transparent;
|
||||
border: none;
|
||||
{border_prop}: 3px solid transparent;
|
||||
color: #cccccc;
|
||||
color: {text_color};
|
||||
font-size: {font_size};
|
||||
font-family: 'DejaVu Sans Mono', monospace;
|
||||
padding: 0px;
|
||||
|
|
@ -136,26 +141,37 @@ def _nav_toolbutton_stylesheet(*, horizontal: bool, icon_only: bool) -> str:
|
|||
}}
|
||||
QToolButton:checked {{
|
||||
background-color: #37373d;
|
||||
{border_prop}: 3px solid #007acc;
|
||||
color: #ffffff;
|
||||
{border_prop}: 3px solid {accent};
|
||||
color: {checked_color};
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
def configure_nav_toolbutton(btn, icon_text: str, *, horizontal: bool = False) -> None:
|
||||
def configure_nav_toolbutton(
|
||||
btn,
|
||||
icon_text: str,
|
||||
*,
|
||||
horizontal: bool = False,
|
||||
update_available: bool = False,
|
||||
) -> None:
|
||||
"""Apply nav icon — on Linux render BMP symbols into fixed pixmaps for alignment."""
|
||||
if sys.platform.startswith("linux"):
|
||||
# Scale to the button cell (leave room for the 3px active border).
|
||||
icon_dim = max(28, min(btn.width(), btn.height()) - 8)
|
||||
font_px = max(22, int(icon_dim * 0.78))
|
||||
btn.setIcon(_linux_nav_icon(icon_text, size=icon_dim, font_px=font_px))
|
||||
normal_color = QColor("#ff5252") if update_available else None
|
||||
btn.setIcon(_linux_nav_icon(icon_text, size=icon_dim, font_px=font_px, normal_color=normal_color))
|
||||
btn.setIconSize(QSize(icon_dim, icon_dim))
|
||||
btn.setText("")
|
||||
btn.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
btn.setStyleSheet(_nav_toolbutton_stylesheet(horizontal=horizontal, icon_only=True))
|
||||
btn.setStyleSheet(_nav_toolbutton_stylesheet(
|
||||
horizontal=horizontal, icon_only=True, update_available=update_available,
|
||||
))
|
||||
return
|
||||
|
||||
btn.setIcon(QIcon())
|
||||
btn.setText(icon_text)
|
||||
btn.setToolButtonStyle(Qt.ToolButtonTextOnly)
|
||||
btn.setStyleSheet(_nav_toolbutton_stylesheet(horizontal=horizontal, icon_only=False))
|
||||
btn.setStyleSheet(_nav_toolbutton_stylesheet(
|
||||
horizontal=horizontal, icon_only=False, update_available=update_available,
|
||||
))
|
||||
|
|
|
|||
|
|
@ -45,14 +45,14 @@ def main():
|
|||
sys.exit(1)
|
||||
|
||||
try:
|
||||
from util.ace.update_tools import ensure_ace_tools
|
||||
ensure_ace_tools()
|
||||
from util.ace.update_tools import seed_ace_tools
|
||||
seed_ace_tools()
|
||||
except Exception as exc:
|
||||
print(f"Warning: Ace tool setup failed ({exc}). Ace features may be unavailable.")
|
||||
|
||||
try:
|
||||
from util.forge.update_tools import ensure_forge_plugins
|
||||
ensure_forge_plugins()
|
||||
from util.forge.update_tools import seed_forge_plugins
|
||||
seed_forge_plugins()
|
||||
except Exception as exc:
|
||||
print(f"Warning: Forge plugin setup failed ({exc}). Playtest Forge may be unavailable.")
|
||||
|
||||
|
|
|
|||
|
|
@ -183,6 +183,38 @@ def ensure_decrypter(force: bool = False, log_fn=print) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def seed_ace_tools(log_fn=None) -> None:
|
||||
"""Copy offline-bundled Ace tools if missing (no network)."""
|
||||
_seed_from_offline(RV2JSON_LOCAL, "RV2JSON.exe", log_fn)
|
||||
_seed_from_offline(DECRYPTER_LOCAL, DECRYPTER_ASSET, log_fn)
|
||||
|
||||
|
||||
def check_ace_tools_update() -> bool:
|
||||
"""Return True if upstream Ace tools differ from the local copies."""
|
||||
seed_ace_tools()
|
||||
versions = _load_versions()
|
||||
|
||||
try:
|
||||
remote_sha, _ = _rv2json_upstream()
|
||||
if not RV2JSON_LOCAL.is_file() or versions.get("rv2json_sha", "") != remote_sha:
|
||||
return True
|
||||
except Exception:
|
||||
if not RV2JSON_LOCAL.is_file():
|
||||
return True
|
||||
|
||||
try:
|
||||
tag, _ = _decrypter_upstream()
|
||||
if not (DECRYPTER_LOCAL.is_file() or DECRYPTER_LEGACY.is_file()):
|
||||
return True
|
||||
if DECRYPTER_LOCAL.is_file() and versions.get("decrypter_tag", "") != tag:
|
||||
return True
|
||||
except Exception:
|
||||
if not (DECRYPTER_LOCAL.is_file() or DECRYPTER_LEGACY.is_file()):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def ensure_ace_tools(force: bool = False, log_fn=print) -> bool:
|
||||
"""Ensure both Ace tools are present (download / update if needed)."""
|
||||
ok_rv = ensure_rv2json(force=force, log_fn=log_fn)
|
||||
|
|
|
|||
|
|
@ -151,6 +151,26 @@ def refresh_forge_plugins(log_fn=print) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def seed_forge_plugins(log_fn=None) -> None:
|
||||
"""Copy offline-bundled Forge plugins if missing (no network)."""
|
||||
for engine in PLUGIN_BY_ENGINE:
|
||||
_seed_from_offline(engine, log_fn)
|
||||
|
||||
|
||||
def check_forge_plugins_update() -> bool:
|
||||
"""Return True if upstream Forge plugins differ from the local copies."""
|
||||
seed_forge_plugins()
|
||||
missing = [e for e in PLUGIN_BY_ENGINE if not bundled_plugin_path(e).is_file()]
|
||||
if missing:
|
||||
return True
|
||||
versions = _load_versions()
|
||||
local_commit = versions.get("commit", "")
|
||||
try:
|
||||
return _upstream_commit() != local_commit
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def ensure_forge_plugins(force: bool = False, log_fn=print) -> bool:
|
||||
"""Ensure Forge plugins are present; fetch upstream when missing or stale."""
|
||||
for engine in PLUGIN_BY_ENGINE:
|
||||
|
|
|
|||
Loading…
Reference in a new issue