- Implement UpdateChecker for version comparison and update notifications - Add menu options for documentation and update checking - Enhance AboutWindow with dynamic version display - Update requirements.txt with new dependencies - Create infrastructure for opening local documentation - Improve application menu with additional help options
175 lines
7.6 KiB
Python
175 lines
7.6 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
import json
|
||
import logging
|
||
import requests
|
||
import threading
|
||
from packaging import version
|
||
|
||
class UpdateCheckError(Exception):
|
||
"""Исключение для ошибок проверки обновлений"""
|
||
pass
|
||
|
||
class UpdateChecker:
|
||
"""Класс для проверки обновлений программы"""
|
||
|
||
def __init__(self, current_version, repo_url):
|
||
self.current_version = current_version
|
||
self.repo_url = repo_url
|
||
# Формируем базовый URL API
|
||
self.api_url = repo_url.replace("gitea.filow.ru", "gitea.filow.ru/api/v1/repos/LowaSC/ComConfigCopy")
|
||
self._update_available = False
|
||
self._latest_version = None
|
||
self._latest_release = None
|
||
self._error = None
|
||
self._changelog = None
|
||
|
||
def get_changelog(self, callback=None):
|
||
"""
|
||
Получение changelog из репозитория.
|
||
:param callback: Функция обратного вызова, которая будет вызвана после получения changelog
|
||
"""
|
||
def fetch():
|
||
try:
|
||
# Пытаемся получить CHANGELOG.md из репозитория
|
||
response = requests.get(f"{self.api_url}/contents/CHANGELOG.md", timeout=10)
|
||
response.raise_for_status()
|
||
|
||
content = response.json()
|
||
if "content" in content:
|
||
import base64
|
||
changelog_content = base64.b64decode(content["content"]).decode("utf-8")
|
||
self._changelog = changelog_content
|
||
self._error = None
|
||
else:
|
||
raise UpdateCheckError("Не удалось получить содержимое CHANGELOG.md")
|
||
|
||
except requests.RequestException as e:
|
||
error_msg = f"Ошибка получения changelog: {e}"
|
||
logging.error(error_msg, exc_info=True)
|
||
self._error = error_msg
|
||
self._changelog = None
|
||
except Exception as e:
|
||
error_msg = f"Неизвестная ошибка при получении changelog: {e}"
|
||
logging.error(error_msg, exc_info=True)
|
||
self._error = error_msg
|
||
self._changelog = None
|
||
finally:
|
||
if callback:
|
||
callback(self._changelog, self._error)
|
||
|
||
# Запускаем получение в отдельном потоке
|
||
threading.Thread(target=fetch, daemon=True).start()
|
||
|
||
def check_updates(self, callback=None):
|
||
"""
|
||
Проверка наличия обновлений.
|
||
:param callback: Функция обратного вызова, которая будет вызвана после проверки
|
||
"""
|
||
def check():
|
||
try:
|
||
response = requests.get(f"{self.api_url}/releases", timeout=10)
|
||
response.raise_for_status()
|
||
|
||
releases = response.json()
|
||
if not releases:
|
||
raise UpdateCheckError("Не найдено релизов в репозитории")
|
||
|
||
latest_release = releases[0]
|
||
latest_version = latest_release.get("tag_name", "").lstrip("v")
|
||
|
||
if not latest_version:
|
||
raise UpdateCheckError("Не удалось определить версию последнего релиза")
|
||
|
||
try:
|
||
if version.parse(latest_version) > version.parse(self.current_version):
|
||
self._update_available = True
|
||
self._latest_version = latest_version
|
||
self._latest_release = latest_release
|
||
logging.info(f"Доступно обновление: {latest_version}")
|
||
else:
|
||
logging.info("Обновления не требуются")
|
||
except version.InvalidVersion as e:
|
||
raise UpdateCheckError(f"Некорректный формат версии: {e}")
|
||
|
||
self._error = None
|
||
|
||
except requests.RequestException as e:
|
||
error_msg = f"Ошибка сетевого подключения: {e}"
|
||
logging.error(error_msg, exc_info=True)
|
||
self._error = error_msg
|
||
except UpdateCheckError as e:
|
||
logging.error(str(e), exc_info=True)
|
||
self._error = str(e)
|
||
except Exception as e:
|
||
error_msg = f"Неизвестная ошибка при проверке обновлений: {e}"
|
||
logging.error(error_msg, exc_info=True)
|
||
self._error = error_msg
|
||
finally:
|
||
if callback:
|
||
callback(self._update_available, self._error)
|
||
|
||
@property
|
||
def update_available(self):
|
||
"""Доступно ли обновление"""
|
||
return self._update_available
|
||
|
||
@property
|
||
def latest_version(self):
|
||
"""Последняя доступная версия"""
|
||
return self._latest_version
|
||
|
||
@property
|
||
def error(self):
|
||
"""Последняя ошибка при проверке обновлений"""
|
||
return self._error
|
||
|
||
@property
|
||
def changelog(self):
|
||
"""Текущий changelog"""
|
||
return self._changelog
|
||
|
||
def get_release_notes(self):
|
||
"""Получение информации о последнем релизе"""
|
||
if self._latest_release:
|
||
return {
|
||
"version": self._latest_version,
|
||
"description": self._latest_release.get("body", ""),
|
||
"download_url": self._latest_release.get("assets", [{}])[0].get("browser_download_url", "")
|
||
}
|
||
return None
|
||
|
||
def get_releases(self, callback=None):
|
||
"""
|
||
Получение списка релизов из репозитория.
|
||
:param callback: Функция обратного вызова, которая будет вызвана после получения списка релизов
|
||
"""
|
||
def fetch():
|
||
try:
|
||
response = requests.get(f"{self.api_url}/releases", timeout=10)
|
||
response.raise_for_status()
|
||
releases = response.json()
|
||
|
||
if not releases:
|
||
raise UpdateCheckError("Не найдено релизов в репозитории")
|
||
|
||
self._error = None
|
||
if callback:
|
||
callback(releases, None)
|
||
|
||
except requests.RequestException as e:
|
||
error_msg = f"Ошибка сетевого подключения: {e}"
|
||
logging.error(error_msg, exc_info=True)
|
||
self._error = error_msg
|
||
if callback:
|
||
callback(None, error_msg)
|
||
except Exception as e:
|
||
error_msg = f"Ошибка при получении списка релизов: {e}"
|
||
logging.error(error_msg, exc_info=True)
|
||
self._error = error_msg
|
||
if callback:
|
||
callback(None, error_msg)
|
||
|
||
# Запускаем получение в отдельном потоке
|
||
threading.Thread(target=fetch, daemon=True).start() |