Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources/translations/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"no_missing_games": "ما فيه ألعاب ناقصة",
"settings": "الإعدادات",
"footer_text": "المدير v{VERSION}\nME3 CLI: {me3_version}\nمن تطوير 2Pz",
"app_update_available": "<span style='color: #ffaa00;'>🔔 إصدار جديد متاح: v{latest_version}</span><br/><a href='{download_url}' style='color: #0078d4;'>تحميل التحديث</a>",
"platform_info": "معلومات النظام",
"platform_info_desc_linux": "استخدم سكريبتات تثبيت لينكس من نافذة المساعدة/عن البرنامج بدال هذا.",
"platform_info_desc_win": "استخدم تحميل مثبت ويندوز من نافذة المساعدة/عن البرنامج بدال هذا.",
Expand Down
1 change: 1 addition & 0 deletions resources/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"no_missing_games": "No Missing Games",
"settings": "Settings",
"footer_text": "Manager v{VERSION}\nME3 CLI: {me3_version}\nby 2Pz",
"app_update_available": "<span style='color: #ffaa00;'>🔔 New version available: v{latest_version}</span><br/><a href='{download_url}' style='color: #0078d4;'>Download update</a>",
"platform_info": "Platform Info",
"platform_info_desc_linux": "Use the Linux installation scripts from the Help/About dialog instead.",
"platform_info_desc_win": "Use the Windows installer download from the Help/About dialog instead.",
Expand Down
1 change: 1 addition & 0 deletions resources/translations/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"no_missing_games": "没有缺失的游戏",
"settings": "设置",
"footer_text": "管理器 v{VERSION}\nME3 CLI: {me3_version}\n作者: 2Pz",
"app_update_available": "<span style='color: #ffaa00;'>🔔 新版本可用: v{latest_version}</span><br/><a href='{download_url}' style='color: #0078d4;'>下载更新</a>",
"platform_info": "平台信息",
"platform_info_desc_linux": "请改用“帮助/关于”对话框中的 Linux 安装脚本。",
"platform_info_desc_win": "请改用“帮助/关于”对话框中的 Windows 安装程序下载。",
Expand Down
119 changes: 119 additions & 0 deletions src/me3_manager/services/app_update_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""Service for checking me3-manager app updates from GitHub releases."""

import logging
import re

import requests

log = logging.getLogger(__name__)


class AppUpdateChecker:
"""Check for me3-manager updates from GitHub releases."""

GITHUB_API_URL = "https://api.github.com/repos/2Pz/me3-manager/releases/latest"

def __init__(self, current_version: str):
"""
Initialize the update checker.

Args:
current_version: Current app version (e.g., "1.1.9" or "v1.1.9")
"""
self.current_version = self._normalize_version(current_version)

@staticmethod
def _normalize_version(version: str) -> str:
"""
Normalize version string by removing 'v' prefix.

Args:
version: Version string (e.g., "1.1.9" or "v1.1.9")

Returns:
Normalized version without 'v' prefix
"""
if not version:
return ""
return version.lstrip("v")

@staticmethod
def _parse_version(version: str) -> tuple[int, ...]:
"""
Parse version string into tuple of integers for comparison.

Args:
version: Version string (e.g., "1.1.9")

Returns:
Tuple of version numbers (e.g., (1, 1, 9))
"""
try:
# Extract version numbers using regex
match = re.match(r"^(\d+)\.(\d+)\.(\d+)", version)
if match:
return tuple(int(x) for x in match.groups())
except (ValueError, AttributeError):
pass
return (0, 0, 0)

def check_for_updates(self) -> dict:
"""
Check for available updates from GitHub releases.

Returns:
Dictionary with update information:
{
"update_available": bool,
"latest_version": str,
"current_version": str,
"download_url": str,
"error": str or None
}
"""
result = {
"update_available": False,
"latest_version": None,
"current_version": self.current_version,
"download_url": "https://www.nexusmods.com/eldenringnightreign/mods/213?tab=files",
"error": None,
}

try:
response = requests.get(self.GITHUB_API_URL, timeout=10)
response.raise_for_status()
data = response.json()

tag_name = data.get("tag_name")
if not tag_name:
result["error"] = "No tag_name found in GitHub response"
return result

# Normalize the latest version
latest_version = self._normalize_version(tag_name)
result["latest_version"] = latest_version

# Compare versions
current_tuple = self._parse_version(self.current_version)
latest_tuple = self._parse_version(latest_version)

if latest_tuple > current_tuple:
result["update_available"] = True
log.info(
"Update available: %s -> %s", self.current_version, latest_version
)
else:
log.debug(
"No update available. Current: %s, Latest: %s",
self.current_version,
latest_version,
)

except requests.RequestException as e:
result["error"] = f"Network error: {e}"
log.error("Failed to check for updates: %s", e)
except Exception as e:
result["error"] = f"Unexpected error: {e}"
log.error("Unexpected error checking for updates: %s", e)

return result
3 changes: 3 additions & 0 deletions src/me3_manager/ui/app_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ def run_startup_checks(self):

# Check for updates if enabled
self.main_window.check_for_me3_updates_if_enabled()

# Check for app updates (always runs)
self.main_window.check_for_app_updates()
51 changes: 46 additions & 5 deletions src/me3_manager/ui/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from me3_manager import __version__ as VERSION
from me3_manager.core.config_facade import ConfigFacade
from me3_manager.core.me3_version_manager import ME3VersionManager
from me3_manager.services.app_update_checker import AppUpdateChecker
from me3_manager.services.steam_service import SteamService
from me3_manager.ui.app_controller import AppController
from me3_manager.ui.dialogs.game_management_dialog import GameManagementDialog
Expand All @@ -44,6 +45,7 @@ def __init__(self):
super().__init__()
self.config_manager = ConfigFacade()
self.me3_version = self.get_me3_version()
self.app_update_info = None # Store update info for display

# Initialize the centralized version manager
self.version_manager = ME3VersionManager(
Expand All @@ -53,6 +55,9 @@ def __init__(self):
refresh_callback=self.refresh_me3_status,
)

# Initialize app update checker
self.app_update_checker = AppUpdateChecker(VERSION)

self.init_ui()

# App-level controller orchestration
Expand Down Expand Up @@ -167,6 +172,20 @@ def refresh_sidebar(self):
# Otherwise, default to the first game in the new list.
self.switch_game(all_games[0])

def check_for_app_updates(self):
"""Check for me3-manager app updates on startup."""
try:
self.app_update_info = self.app_update_checker.check_for_updates()
if self.app_update_info and self.app_update_info.get("update_available"):
self.update_footer_text()
log.info(
"App update available: %s -> %s",
self.app_update_info["current_version"],
self.app_update_info["latest_version"],
)
except Exception as e:
log.error("Failed to check for app updates: %s", e)

def check_for_me3_updates_if_enabled(self):
"""Check for ME3 updates on startup if enabled in settings."""
if not self.config_manager.get_check_for_updates():
Expand Down Expand Up @@ -304,12 +323,16 @@ def create_sidebar(self, parent):
settings_button = QPushButton(tr("settings"))
settings_button.clicked.connect(self.show_settings_dialog)
layout.addWidget(settings_button)
footer_text = tr("footer_text", VERSION=VERSION, me3_version=self.me3_version)
self.footer_label = QLabel(footer_text)

# Create footer label with initial text
self.footer_label = QLabel()
self.footer_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.footer_label.setStyleSheet(
"color: #888888; font-size: 10px; line-height: 1.4;"
)
self.footer_label.setOpenExternalLinks(True)
self.footer_label.setTextFormat(Qt.TextFormat.RichText)
self.update_footer_text() # Set initial text
layout.addWidget(self.footer_label)
parent.addWidget(sidebar)

Expand Down Expand Up @@ -349,6 +372,26 @@ def prompt_for_me3_installation(self):
dialog = HelpAboutDialog(self, initial_setup=True)
dialog.exec()

def update_footer_text(self):
"""Update footer text with version info and update notification if available."""
base_text = tr("footer_text", VERSION=VERSION, me3_version=self.me3_version)
# Convert \n to <br/> for HTML rendering
base_text = base_text.replace("\n", "<br/>")

if self.app_update_info and self.app_update_info.get("update_available"):
latest_version = self.app_update_info.get("latest_version", "Unknown")
download_url = self.app_update_info.get("download_url", "")
update_text = tr(
"app_update_available",
latest_version=latest_version,
download_url=download_url,
)
# Combine base text with update notification
full_text = f"{base_text}<br/><br/>{update_text}"
self.footer_label.setText(full_text)
else:
self.footer_label.setText(base_text)

def refresh_me3_status(self):
"""Refresh ME3 status and update UI components."""
old_version = self.me3_version
Expand All @@ -357,9 +400,7 @@ def refresh_me3_status(self):

if old_version != self.me3_version:
# Update footer label
self.footer_label.setText(
tr("footer_text", VERSION=VERSION, me3_version=self.me3_version)
)
self.update_footer_text()

# Trigger a full refresh of the application state
self.perform_global_refresh()
Expand Down