Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90b3b4fc9d | |||
| 190e67c931 | |||
| 2eb4c52b81 | |||
| 3d73a504d2 | |||
| 1524271be7 | |||
| 561cf43e09 | |||
| e8930f7550 | |||
| c8da0f9191 | |||
| 37ce500fd2 | |||
| 098a84e5bd | |||
| 5aa17c1a84 | |||
| dde14f3714 | |||
| fa5d4c6993 | |||
| f9e0225243 | |||
| c42b23bea5 | |||
| b52cdea425 | |||
| b7fad78a71 |
35
.gitea/workflows/ci.yml
Normal file
35
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: Desktop CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: https://git.daemonlord.ru/actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: https://git.daemonlord.ru/actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.13"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Validate syntax
|
||||||
|
run: |
|
||||||
|
python -m py_compile app_version.py main.py build.py tests/test_auth_relogin_smoke.py
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
python -m unittest tests/test_auth_relogin_smoke.py
|
||||||
133
.gitea/workflows/release.yml
Normal file
133
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
name: Desktop Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: windows
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: https://git.daemonlord.ru/actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
tags: true
|
||||||
|
|
||||||
|
- name: Ensure Python 3.13
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
if (Get-Command python -ErrorAction SilentlyContinue) {
|
||||||
|
python --version
|
||||||
|
} elseif (Get-Command py -ErrorAction SilentlyContinue) {
|
||||||
|
$pyExe = py -3.13 -c "import sys; print(sys.executable)"
|
||||||
|
if (-not $pyExe) {
|
||||||
|
throw "Python 3.13 launcher is available, but interpreter was not found."
|
||||||
|
}
|
||||||
|
Split-Path $pyExe | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
python --version
|
||||||
|
} else {
|
||||||
|
throw "Python is not installed on runner. Install Python 3.13 and restart runner service."
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt pyinstaller
|
||||||
|
|
||||||
|
- name: Extract app version
|
||||||
|
id: extract_version
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
$version = (python -c "from app_version import APP_VERSION; print(APP_VERSION)").Trim()
|
||||||
|
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||||
|
[System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "version=$version`n", $utf8NoBom)
|
||||||
|
Write-Host "Detected version: $version"
|
||||||
|
|
||||||
|
- name: Stop if version already released
|
||||||
|
id: stop
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
$version = "${{ steps.extract_version.outputs.version }}"
|
||||||
|
$tag = "v$version"
|
||||||
|
git show-ref --tags --quiet --verify "refs/tags/$tag"
|
||||||
|
$tagExists = ($LASTEXITCODE -eq 0)
|
||||||
|
$global:LASTEXITCODE = 0
|
||||||
|
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||||
|
if ($tagExists) {
|
||||||
|
Write-Host "Version $tag already released, stopping job."
|
||||||
|
[System.IO.File]::AppendAllText($env:GITHUB_ENV, "CONTINUE=false`n", $utf8NoBom)
|
||||||
|
} else {
|
||||||
|
Write-Host "Version $tag not released yet, continuing workflow..."
|
||||||
|
[System.IO.File]::AppendAllText($env:GITHUB_ENV, "CONTINUE=true`n", $utf8NoBom)
|
||||||
|
}
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
if: env.CONTINUE == 'true'
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
python -m py_compile app_version.py main.py build.py tests/test_auth_relogin_smoke.py
|
||||||
|
python -m unittest tests/test_auth_relogin_smoke.py
|
||||||
|
|
||||||
|
- name: Build release zip
|
||||||
|
if: env.CONTINUE == 'true'
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
python build.py
|
||||||
|
|
||||||
|
- name: Ensure archive exists
|
||||||
|
if: env.CONTINUE == 'true'
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
$version = "${{ steps.extract_version.outputs.version }}"
|
||||||
|
$archivePath = "dist/AnabasisManager-$version.zip"
|
||||||
|
if (-not (Test-Path $archivePath)) {
|
||||||
|
throw "Archive not found: $archivePath"
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Generate SHA256 checksum
|
||||||
|
if: env.CONTINUE == 'true'
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
$version = "${{ steps.extract_version.outputs.version }}"
|
||||||
|
$archiveName = "AnabasisManager-$version.zip"
|
||||||
|
$archivePath = "dist/$archiveName"
|
||||||
|
$checksumPath = "dist/$archiveName.sha256"
|
||||||
|
$hash = (Get-FileHash -Path $archivePath -Algorithm SHA256).Hash.ToLower()
|
||||||
|
"$hash $archiveName" | Set-Content -Path $checksumPath -Encoding UTF8
|
||||||
|
Write-Host "Checksum created: $checksumPath"
|
||||||
|
|
||||||
|
- name: Configure git identity
|
||||||
|
if: env.CONTINUE == 'true'
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
git config user.name "gitea-actions"
|
||||||
|
git config user.email "gitea-actions@daemonlord.ru"
|
||||||
|
|
||||||
|
- name: Create git tag
|
||||||
|
if: env.CONTINUE == 'true'
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
$version = "${{ steps.extract_version.outputs.version }}"
|
||||||
|
$tag = "v$version"
|
||||||
|
git tag "$tag"
|
||||||
|
git push origin "$tag"
|
||||||
|
|
||||||
|
- name: Create Gitea Release
|
||||||
|
if: env.CONTINUE == 'true'
|
||||||
|
uses: https://git.daemonlord.ru/actions/gitea-release-action@v1
|
||||||
|
with:
|
||||||
|
server_url: https://git.daemonlord.ru
|
||||||
|
repository: ${{ gitea.repository }}
|
||||||
|
token: ${{ secrets.API_TOKEN }}
|
||||||
|
tag_name: v${{ steps.extract_version.outputs.version }}
|
||||||
|
name: Anabasis Manager ${{ steps.extract_version.outputs.version }}
|
||||||
|
body: |
|
||||||
|
Desktop release v${{ steps.extract_version.outputs.version }}
|
||||||
|
files: |
|
||||||
|
dist/AnabasisManager-${{ steps.extract_version.outputs.version }}.zip
|
||||||
|
dist/AnabasisManager-${{ steps.extract_version.outputs.version }}.zip.sha256
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ __pycache__/
|
|||||||
tests/__pycache__/
|
tests/__pycache__/
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
|
AnabasisManager.spec
|
||||||
|
|||||||
1
app_version.py
Normal file
1
app_version.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
APP_VERSION = "1.6.2"
|
||||||
3
build.py
3
build.py
@@ -2,10 +2,11 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
from app_version import APP_VERSION
|
||||||
|
|
||||||
# --- Конфигурация ---
|
# --- Конфигурация ---
|
||||||
APP_NAME = "AnabasisManager"
|
APP_NAME = "AnabasisManager"
|
||||||
VERSION = "1.5.1" # Ваша версия
|
VERSION = APP_VERSION # Единая версия приложения
|
||||||
MAIN_SCRIPT = "main.py"
|
MAIN_SCRIPT = "main.py"
|
||||||
ICON_PATH = "icon.ico"
|
ICON_PATH = "icon.ico"
|
||||||
DIST_DIR = os.path.join("dist", APP_NAME)
|
DIST_DIR = os.path.join("dist", APP_NAME)
|
||||||
|
|||||||
188
main.py
188
main.py
@@ -8,9 +8,14 @@ import time
|
|||||||
import auth_webview
|
import auth_webview
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import hashlib
|
||||||
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
import tempfile
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
import zipfile
|
||||||
|
from app_version import APP_VERSION
|
||||||
from PySide6.QtWidgets import (QApplication, QMainWindow, QLabel, QLineEdit,
|
from PySide6.QtWidgets import (QApplication, QMainWindow, QLabel, QLineEdit,
|
||||||
QPushButton, QVBoxLayout, QWidget, QMessageBox,
|
QPushButton, QVBoxLayout, QWidget, QMessageBox,
|
||||||
QTextBrowser, QScrollArea, QCheckBox, QHBoxLayout,
|
QTextBrowser, QScrollArea, QCheckBox, QHBoxLayout,
|
||||||
@@ -33,7 +38,6 @@ LOG_FILE = os.path.join(APP_DATA_DIR, "app.log")
|
|||||||
LOG_MAX_BYTES = 1024 * 1024 # 1 MB
|
LOG_MAX_BYTES = 1024 * 1024 # 1 MB
|
||||||
LOG_BACKUP_FILE = os.path.join(APP_DATA_DIR, "app.log.1")
|
LOG_BACKUP_FILE = os.path.join(APP_DATA_DIR, "app.log.1")
|
||||||
AUTH_RELOGIN_BACKOFF_SECONDS = 5.0
|
AUTH_RELOGIN_BACKOFF_SECONDS = 5.0
|
||||||
APP_VERSION = "1.5.1"
|
|
||||||
# Legacy owner/repo format for GitHub-only fallback.
|
# Legacy owner/repo format for GitHub-only fallback.
|
||||||
UPDATE_REPOSITORY = ""
|
UPDATE_REPOSITORY = ""
|
||||||
# Full repository URL is preferred (supports GitHub/Gitea).
|
# Full repository URL is preferred (supports GitHub/Gitea).
|
||||||
@@ -354,13 +358,30 @@ class UpdateChecker(QObject):
|
|||||||
html_url = release_data.get("html_url") or releases_url
|
html_url = release_data.get("html_url") or releases_url
|
||||||
assets = release_data.get("assets") or []
|
assets = release_data.get("assets") or []
|
||||||
download_url = ""
|
download_url = ""
|
||||||
|
download_name = ""
|
||||||
|
checksum_url = ""
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
url = asset.get("browser_download_url", "")
|
url = asset.get("browser_download_url", "")
|
||||||
if url.lower().endswith(".zip"):
|
if url.lower().endswith(".zip"):
|
||||||
download_url = url
|
download_url = url
|
||||||
|
download_name = asset.get("name", "")
|
||||||
break
|
break
|
||||||
if not download_url and assets:
|
if not download_url and assets:
|
||||||
download_url = assets[0].get("browser_download_url", "")
|
download_url = assets[0].get("browser_download_url", "")
|
||||||
|
download_name = assets[0].get("name", "")
|
||||||
|
|
||||||
|
for asset in assets:
|
||||||
|
name = asset.get("name", "").lower()
|
||||||
|
if not name:
|
||||||
|
continue
|
||||||
|
is_checksum_asset = name.endswith(".sha256") or name.endswith(".sha256.txt") or name in ("checksums.txt", "sha256sums.txt")
|
||||||
|
if not is_checksum_asset:
|
||||||
|
continue
|
||||||
|
if download_name and (download_name.lower() in name or name in (f"{download_name.lower()}.sha256", f"{download_name.lower()}.sha256.txt")):
|
||||||
|
checksum_url = asset.get("browser_download_url", "")
|
||||||
|
break
|
||||||
|
if not checksum_url:
|
||||||
|
checksum_url = asset.get("browser_download_url", "")
|
||||||
|
|
||||||
self.check_finished.emit(
|
self.check_finished.emit(
|
||||||
{
|
{
|
||||||
@@ -370,6 +391,8 @@ class UpdateChecker(QObject):
|
|||||||
"latest_tag": latest_tag,
|
"latest_tag": latest_tag,
|
||||||
"release_url": html_url,
|
"release_url": html_url,
|
||||||
"download_url": download_url,
|
"download_url": download_url,
|
||||||
|
"download_name": download_name,
|
||||||
|
"checksum_url": checksum_url,
|
||||||
"has_update": _is_newer_version(latest_version, self.current_version),
|
"has_update": _is_newer_version(latest_version, self.current_version),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -695,15 +718,23 @@ class VkChatManager(QMainWindow):
|
|||||||
f"Доступная версия: {latest_version}\n\n"
|
f"Доступная версия: {latest_version}\n\n"
|
||||||
"Открыть страницу загрузки?"
|
"Открыть страницу загрузки?"
|
||||||
)
|
)
|
||||||
|
update_now_button = message_box.addButton("Обновить сейчас", QMessageBox.AcceptRole)
|
||||||
download_button = message_box.addButton("Скачать", QMessageBox.AcceptRole)
|
download_button = message_box.addButton("Скачать", QMessageBox.AcceptRole)
|
||||||
releases_button = message_box.addButton("Релизы", QMessageBox.ActionRole)
|
releases_button = message_box.addButton("Релизы", QMessageBox.ActionRole)
|
||||||
cancel_button = message_box.addButton("Позже", QMessageBox.RejectRole)
|
cancel_button = message_box.addButton("Позже", QMessageBox.RejectRole)
|
||||||
message_box.setDefaultButton(download_button)
|
message_box.setDefaultButton(update_now_button)
|
||||||
message_box.exec()
|
message_box.exec()
|
||||||
|
|
||||||
clicked = message_box.clickedButton()
|
clicked = message_box.clickedButton()
|
||||||
download_url = result.get("download_url")
|
download_url = result.get("download_url")
|
||||||
|
checksum_url = result.get("checksum_url")
|
||||||
|
download_name = result.get("download_name")
|
||||||
release_url = result.get("release_url")
|
release_url = result.get("release_url")
|
||||||
|
if clicked is update_now_button and download_url:
|
||||||
|
if not self._start_auto_update(download_url, latest_version, checksum_url, download_name):
|
||||||
|
if release_url:
|
||||||
|
QDesktopServices.openUrl(QUrl(release_url))
|
||||||
|
return
|
||||||
if clicked is download_button and download_url:
|
if clicked is download_button and download_url:
|
||||||
QDesktopServices.openUrl(QUrl(download_url))
|
QDesktopServices.openUrl(QUrl(download_url))
|
||||||
elif clicked in (download_button, releases_button) and release_url:
|
elif clicked in (download_button, releases_button) and release_url:
|
||||||
@@ -736,6 +767,159 @@ class VkChatManager(QMainWindow):
|
|||||||
if not self._update_check_silent:
|
if not self._update_check_silent:
|
||||||
QMessageBox.warning(self, "Проверка обновлений", error_text)
|
QMessageBox.warning(self, "Проверка обновлений", error_text)
|
||||||
|
|
||||||
|
def _download_update_archive(self, download_url, destination_path):
|
||||||
|
request = urllib.request.Request(
|
||||||
|
download_url,
|
||||||
|
headers={"User-Agent": "AnabasisManager-Updater"},
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(request, timeout=60) as response:
|
||||||
|
with open(destination_path, "wb") as f:
|
||||||
|
shutil.copyfileobj(response, f)
|
||||||
|
|
||||||
|
def _download_update_text(self, url):
|
||||||
|
request = urllib.request.Request(
|
||||||
|
url,
|
||||||
|
headers={"User-Agent": "AnabasisManager-Updater"},
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(request, timeout=30) as response:
|
||||||
|
return response.read().decode("utf-8", errors="replace")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sha256_file(path):
|
||||||
|
digest = hashlib.sha256()
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
||||||
|
digest.update(chunk)
|
||||||
|
return digest.hexdigest().lower()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_sha256_from_text(checksum_text, target_file_name):
|
||||||
|
target = (target_file_name or "").strip().lower()
|
||||||
|
for raw_line in checksum_text.splitlines():
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
match = re.search(r"\b([A-Fa-f0-9]{64})\b", line)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
checksum = match.group(1).lower()
|
||||||
|
if not target:
|
||||||
|
return checksum
|
||||||
|
line_lower = line.lower()
|
||||||
|
if target in line_lower:
|
||||||
|
return checksum
|
||||||
|
if os.path.basename(target) in line_lower:
|
||||||
|
return checksum
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _verify_update_checksum(self, zip_path, checksum_url, download_name):
|
||||||
|
if not checksum_url:
|
||||||
|
raise RuntimeError("В релизе нет файла SHA256. Автообновление остановлено.")
|
||||||
|
checksum_text = self._download_update_text(checksum_url)
|
||||||
|
expected_hash = self._extract_sha256_from_text(checksum_text, download_name or os.path.basename(zip_path))
|
||||||
|
if not expected_hash:
|
||||||
|
raise RuntimeError("Не удалось найти SHA256 для архива обновления.")
|
||||||
|
actual_hash = self._sha256_file(zip_path)
|
||||||
|
if actual_hash != expected_hash:
|
||||||
|
raise RuntimeError("SHA256 не совпадает, обновление отменено.")
|
||||||
|
|
||||||
|
def _locate_extracted_root(self, extracted_dir):
|
||||||
|
entries = []
|
||||||
|
for name in os.listdir(extracted_dir):
|
||||||
|
full_path = os.path.join(extracted_dir, name)
|
||||||
|
if os.path.isdir(full_path):
|
||||||
|
entries.append(full_path)
|
||||||
|
if len(entries) == 1:
|
||||||
|
candidate = entries[0]
|
||||||
|
if os.path.exists(os.path.join(candidate, "AnabasisManager.exe")):
|
||||||
|
return candidate
|
||||||
|
return extracted_dir
|
||||||
|
|
||||||
|
def _build_update_script(self, app_dir, source_dir, exe_name):
|
||||||
|
script_path = os.path.join(tempfile.gettempdir(), "anabasis_apply_update.cmd")
|
||||||
|
script_lines = [
|
||||||
|
"@echo off",
|
||||||
|
"setlocal",
|
||||||
|
f"set APP_DIR={app_dir}",
|
||||||
|
f"set SRC_DIR={source_dir}",
|
||||||
|
f"set EXE_NAME={exe_name}",
|
||||||
|
"timeout /t 2 /nobreak >nul",
|
||||||
|
"robocopy \"%SRC_DIR%\" \"%APP_DIR%\" /E /NFL /NDL /NJH /NJS /NP /R:3 /W:1 >nul",
|
||||||
|
"set RC=%ERRORLEVEL%",
|
||||||
|
"if %RC% GEQ 8 goto :copy_error",
|
||||||
|
"start \"\" \"%APP_DIR%\\%EXE_NAME%\"",
|
||||||
|
"exit /b 0",
|
||||||
|
":copy_error",
|
||||||
|
"echo Auto-update failed with code %RC% > \"%APP_DIR%\\update_error.log\"",
|
||||||
|
"exit /b %RC%",
|
||||||
|
]
|
||||||
|
with open(script_path, "w", encoding="utf-8", newline="\r\n") as f:
|
||||||
|
f.write("\r\n".join(script_lines) + "\r\n")
|
||||||
|
return script_path
|
||||||
|
|
||||||
|
def _start_auto_update(self, download_url, latest_version, checksum_url="", download_name=""):
|
||||||
|
if os.name != "nt":
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Автообновление",
|
||||||
|
"Автообновление пока поддерживается только в Windows-сборке.",
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
if not getattr(sys, "frozen", False):
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Автообновление",
|
||||||
|
"Автообновление доступно в собранной версии приложения (.exe).",
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
if not download_url:
|
||||||
|
QMessageBox.warning(self, "Автообновление", "В релизе нет ссылки на файл для обновления.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.status_label.setText(f"Статус: загрузка обновления {latest_version}...")
|
||||||
|
self._set_busy(True)
|
||||||
|
work_dir = tempfile.mkdtemp(prefix="anabasis_update_")
|
||||||
|
zip_path = os.path.join(work_dir, "update.zip")
|
||||||
|
unpack_dir = os.path.join(work_dir, "extracted")
|
||||||
|
try:
|
||||||
|
self._download_update_archive(download_url, zip_path)
|
||||||
|
self._verify_update_checksum(zip_path, checksum_url, download_name)
|
||||||
|
os.makedirs(unpack_dir, exist_ok=True)
|
||||||
|
with zipfile.ZipFile(zip_path, "r") as archive:
|
||||||
|
archive.extractall(unpack_dir)
|
||||||
|
|
||||||
|
source_dir = self._locate_extracted_root(unpack_dir)
|
||||||
|
app_exe = sys.executable
|
||||||
|
app_dir = os.path.dirname(app_exe)
|
||||||
|
exe_name = os.path.basename(app_exe)
|
||||||
|
script_path = self._build_update_script(app_dir, source_dir, exe_name)
|
||||||
|
|
||||||
|
creation_flags = 0
|
||||||
|
if hasattr(subprocess, "CREATE_NEW_PROCESS_GROUP"):
|
||||||
|
creation_flags |= subprocess.CREATE_NEW_PROCESS_GROUP
|
||||||
|
if hasattr(subprocess, "DETACHED_PROCESS"):
|
||||||
|
creation_flags |= subprocess.DETACHED_PROCESS
|
||||||
|
|
||||||
|
subprocess.Popen(
|
||||||
|
["cmd.exe", "/c", script_path],
|
||||||
|
cwd=work_dir,
|
||||||
|
creationflags=creation_flags,
|
||||||
|
)
|
||||||
|
self._log_event("auto_update", f"Update {latest_version} started from {download_url}")
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Обновление запущено",
|
||||||
|
"Обновление скачано. Приложение будет перезапущено.",
|
||||||
|
)
|
||||||
|
QTimer.singleShot(150, QApplication.instance().quit)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self._log_event("auto_update_failed", str(e), level="ERROR")
|
||||||
|
QMessageBox.warning(self, "Автообновление", f"Не удалось выполнить автообновление: {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
self._set_busy(False)
|
||||||
|
|
||||||
def setup_token_timer(self):
|
def setup_token_timer(self):
|
||||||
self.token_countdown_timer = QTimer(self)
|
self.token_countdown_timer = QTimer(self)
|
||||||
self.token_countdown_timer.timeout.connect(self.update_token_timer_display)
|
self.token_countdown_timer.timeout.connect(self.update_token_timer_display)
|
||||||
|
|||||||
@@ -38,11 +38,14 @@ class AuthReloginSmokeTests(unittest.TestCase):
|
|||||||
self.assertNotIn("self.retail_coffee_checkboxes", self.source)
|
self.assertNotIn("self.retail_coffee_checkboxes", self.source)
|
||||||
|
|
||||||
def test_update_check_actions_exist(self):
|
def test_update_check_actions_exist(self):
|
||||||
self.assertIn("APP_VERSION = ", self.source)
|
self.assertIn("from app_version import APP_VERSION", self.source)
|
||||||
self.assertIn("UPDATE_REPOSITORY = ", self.source)
|
self.assertIn("UPDATE_REPOSITORY = ", self.source)
|
||||||
self.assertIn('QAction("Проверить обновления", self)', self.source)
|
self.assertIn('QAction("Проверить обновления", self)', self.source)
|
||||||
self.assertIn("def check_for_updates(self, silent_no_updates=False):", self.source)
|
self.assertIn("def check_for_updates(self, silent_no_updates=False):", self.source)
|
||||||
self.assertIn("class UpdateChecker(QObject):", self.source)
|
self.assertIn("class UpdateChecker(QObject):", self.source)
|
||||||
|
self.assertIn('message_box.addButton("Обновить сейчас", QMessageBox.AcceptRole)', self.source)
|
||||||
|
self.assertIn("def _start_auto_update(self, download_url, latest_version, checksum_url=\"\", download_name=\"\"):", self.source)
|
||||||
|
self.assertIn("def _verify_update_checksum(self, zip_path, checksum_url, download_name):", self.source)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user