Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d6ab87ce3 | ||
|
|
bb2a0db609 | ||
|
|
30982e8a4d | ||
|
|
8eb8cc1d0d |
No files matched your search
@@ -1,5 +1,36 @@
|
|||||||
# Änderungsprotokoll
|
# Änderungsprotokoll
|
||||||
|
|
||||||
|
## 1.0.6 – 2026-09-24
|
||||||
|
|
||||||
|
### Behoben
|
||||||
|
|
||||||
|
- Dateianzeige und Fortschrittsleiste besitzen nun feste Breiten und verschieben sich beim Wechsel zwischen unterschiedlich langen Dateipfaden nicht mehr.
|
||||||
|
- Lange Dateipfade werden platzsparend gekürzt; der vollständige Text bleibt als Tooltip verfügbar.
|
||||||
|
|
||||||
|
## 1.0.5 – 2026-09-24
|
||||||
|
|
||||||
|
### Neu
|
||||||
|
|
||||||
|
- SyncVito prüft bei jedem Programmstart und während des Betriebs alle 30 Minuten automatisch auf Updates.
|
||||||
|
- Verfügbare Updates werden zusätzlich zum Hinweisfenster mit einer auffälligen „UPDATE“-Kennzeichnung am Eintrag „Einstellungen“ angezeigt.
|
||||||
|
|
||||||
|
### Behoben
|
||||||
|
|
||||||
|
- Wird eine automatische Prüfung durch eine laufende Synchronisation oder Updateaktion verhindert, wird sie nach einer Minute erneut versucht.
|
||||||
|
|
||||||
|
## 1.0.3 – 2026-09-24
|
||||||
|
|
||||||
|
### Behoben
|
||||||
|
|
||||||
|
- Der automatische Neustart nach einem internen Update setzt nun die geerbte PyInstaller-Laufzeitumgebung zurück und verhindert dadurch den Sicherheitsfehler beim Start der aktualisierten Anwendung.
|
||||||
|
|
||||||
|
## 1.0.2 – 2026-09-24
|
||||||
|
|
||||||
|
### Behoben
|
||||||
|
|
||||||
|
- Die Versionsanzeige in der Seitenleiste verwendet nun immer die zentrale Anwendungsversion.
|
||||||
|
- Unter der Versionsnummer wird jetzt „by Davito“ statt „Lokale Konfiguration“ angezeigt.
|
||||||
|
|
||||||
## 1.0.1 – 2026-09-23
|
## 1.0.1 – 2026-09-23
|
||||||
|
|
||||||
### Behoben
|
### Behoben
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "syncvito"
|
name = "syncvito"
|
||||||
version = "1.0.1"
|
version = "1.0.6"
|
||||||
description = "Windows-Desktopwerkzeug für lokale und NAS-Synchronisation"
|
description = "Windows-Desktopwerkzeug für lokale und NAS-Synchronisation"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
+84
-16
@@ -3,11 +3,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from PyQt6.QtCore import QEvent, Qt, QTimer, pyqtSignal
|
from PyQt6.QtCore import QEvent, Qt, QTimer, pyqtSignal
|
||||||
from PyQt6.QtGui import QAction, QCloseEvent, QFontDatabase
|
from PyQt6.QtGui import QAction, QCloseEvent, QFontDatabase, QResizeEvent
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QAbstractItemView,
|
QAbstractItemView,
|
||||||
QApplication,
|
QApplication,
|
||||||
@@ -48,6 +48,38 @@ from syncvito.version import __version__
|
|||||||
|
|
||||||
__all__ = ["MainWindow"]
|
__all__ = ["MainWindow"]
|
||||||
|
|
||||||
|
UPDATE_CHECK_INTERVAL_MS = 30 * 60 * 1000
|
||||||
|
UPDATE_RETRY_INTERVAL_MS = 60 * 1000
|
||||||
|
PROGRESS_TEXT_WIDTH = 300
|
||||||
|
PROGRESS_BAR_WIDTH = 450
|
||||||
|
|
||||||
|
|
||||||
|
class _ElidedLabel(QLabel):
|
||||||
|
"""Hält dynamische Texte in einer festen Breite vollständig zugänglich."""
|
||||||
|
|
||||||
|
def __init__(self, text: str = "", parent: QWidget | None = None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self._full_text = ""
|
||||||
|
self.setText(text)
|
||||||
|
|
||||||
|
def setText(self, text: str) -> None: # noqa: N802 - Qt API
|
||||||
|
self._full_text = text
|
||||||
|
self.setToolTip(text)
|
||||||
|
self.setAccessibleName(text)
|
||||||
|
self._refresh_elided_text()
|
||||||
|
|
||||||
|
def resizeEvent(self, event: QResizeEvent) -> None: # noqa: N802 - Qt API
|
||||||
|
super().resizeEvent(event)
|
||||||
|
self._refresh_elided_text()
|
||||||
|
|
||||||
|
def _refresh_elided_text(self) -> None:
|
||||||
|
visible_text = self.fontMetrics().elidedText(
|
||||||
|
self._full_text,
|
||||||
|
Qt.TextElideMode.ElideMiddle,
|
||||||
|
max(0, self.contentsRect().width()),
|
||||||
|
)
|
||||||
|
super().setText(visible_text)
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
"""Zentrale SyncVito-Benutzeroberfläche."""
|
"""Zentrale SyncVito-Benutzeroberfläche."""
|
||||||
@@ -59,9 +91,10 @@ class MainWindow(QMainWindow):
|
|||||||
self.config = config
|
self.config = config
|
||||||
self.config_store = config_store
|
self.config_store = config_store
|
||||||
self._allow_close = False
|
self._allow_close = False
|
||||||
|
self._notified_update_version: str | None = None
|
||||||
self.setWindowTitle("SyncVito")
|
self.setWindowTitle("SyncVito")
|
||||||
self.setWindowIcon(app_icon())
|
self.setWindowIcon(app_icon())
|
||||||
self.setMinimumSize(980, 680)
|
self.setMinimumSize(1040, 680)
|
||||||
self.resize(1220, 800)
|
self.resize(1220, 800)
|
||||||
|
|
||||||
self.coordinator = SyncCoordinator(config, config_store, self)
|
self.coordinator = SyncCoordinator(config, config_store, self)
|
||||||
@@ -71,6 +104,15 @@ class MainWindow(QMainWindow):
|
|||||||
self._connect_coordinator()
|
self._connect_coordinator()
|
||||||
self._refresh_tasks()
|
self._refresh_tasks()
|
||||||
self._set_global_status(*self.coordinator.current_overall_status())
|
self._set_global_status(*self.coordinator.current_overall_status())
|
||||||
|
|
||||||
|
self.update_check_timer = QTimer(self)
|
||||||
|
self.update_check_timer.setInterval(UPDATE_CHECK_INTERVAL_MS)
|
||||||
|
self.update_check_timer.timeout.connect(self._automatic_update_check)
|
||||||
|
self.update_check_timer.start()
|
||||||
|
self.update_retry_timer = QTimer(self)
|
||||||
|
self.update_retry_timer.setSingleShot(True)
|
||||||
|
self.update_retry_timer.setInterval(UPDATE_RETRY_INTERVAL_MS)
|
||||||
|
self.update_retry_timer.timeout.connect(self._automatic_update_check)
|
||||||
QTimer.singleShot(3000, self._automatic_update_check)
|
QTimer.singleShot(3000, self._automatic_update_check)
|
||||||
cleanup_attempts = 6 if "--updated" in sys.argv else 1
|
cleanup_attempts = 6 if "--updated" in sys.argv else 1
|
||||||
QTimer.singleShot(3000, lambda: self._cleanup_update_installers(cleanup_attempts))
|
QTimer.singleShot(3000, lambda: self._cleanup_update_installers(cleanup_attempts))
|
||||||
@@ -159,9 +201,10 @@ class MainWindow(QMainWindow):
|
|||||||
layout.addWidget(button)
|
layout.addWidget(button)
|
||||||
self.nav_buttons.append(button)
|
self.nav_buttons.append(button)
|
||||||
self.nav_buttons[0].setChecked(True)
|
self.nav_buttons[0].setChecked(True)
|
||||||
|
self.settings_nav_button = self.nav_buttons[2]
|
||||||
layout.addStretch(1)
|
layout.addStretch(1)
|
||||||
|
|
||||||
version = QLabel("Version 1.0.0\nLokale Konfiguration")
|
version = QLabel(f"Version {__version__}\nby Davito")
|
||||||
version.setProperty("muted", True)
|
version.setProperty("muted", True)
|
||||||
layout.addWidget(version)
|
layout.addWidget(version)
|
||||||
return sidebar
|
return sidebar
|
||||||
@@ -340,7 +383,7 @@ class MainWindow(QMainWindow):
|
|||||||
version_label = QLabel(f"Installierte Version: {__version__}")
|
version_label = QLabel(f"Installierte Version: {__version__}")
|
||||||
version_label.setProperty("muted", True)
|
version_label.setProperty("muted", True)
|
||||||
update_layout.addWidget(version_label)
|
update_layout.addWidget(version_label)
|
||||||
self.update_check_box = QCheckBox("Beim Start automatisch nach Updates suchen")
|
self.update_check_box = QCheckBox("Beim Start und danach alle 30 Minuten automatisch nach Updates suchen")
|
||||||
self.update_check_box.setChecked(self.config.settings.check_updates_on_start)
|
self.update_check_box.setChecked(self.config.settings.check_updates_on_start)
|
||||||
update_layout.addWidget(self.update_check_box)
|
update_layout.addWidget(self.update_check_box)
|
||||||
update_hint = QLabel(
|
update_hint = QLabel(
|
||||||
@@ -366,15 +409,17 @@ class MainWindow(QMainWindow):
|
|||||||
dock.setFixedHeight(66)
|
dock.setFixedHeight(66)
|
||||||
layout = QHBoxLayout(dock)
|
layout = QHBoxLayout(dock)
|
||||||
layout.setContentsMargins(28, 12, 28, 12)
|
layout.setContentsMargins(28, 12, 28, 12)
|
||||||
self.progress_text = QLabel("Bereit")
|
self.progress_text = _ElidedLabel("Bereit")
|
||||||
self.progress_text.setMinimumWidth(250)
|
self.progress_text.setFixedWidth(PROGRESS_TEXT_WIDTH)
|
||||||
self.progress_text.setProperty("muted", True)
|
self.progress_text.setProperty("muted", True)
|
||||||
self.progress_bar = QProgressBar()
|
self.progress_bar = QProgressBar()
|
||||||
|
self.progress_bar.setFixedWidth(PROGRESS_BAR_WIDTH)
|
||||||
self.progress_bar.setRange(0, 100)
|
self.progress_bar.setRange(0, 100)
|
||||||
self.progress_bar.setValue(0)
|
self.progress_bar.setValue(0)
|
||||||
self.progress_bar.setFormat("%p %")
|
self.progress_bar.setFormat("%p %")
|
||||||
layout.addWidget(self.progress_text)
|
layout.addWidget(self.progress_text)
|
||||||
layout.addWidget(self.progress_bar, 1)
|
layout.addWidget(self.progress_bar)
|
||||||
|
layout.addStretch(1)
|
||||||
return dock
|
return dock
|
||||||
|
|
||||||
def _build_tray(self) -> None:
|
def _build_tray(self) -> None:
|
||||||
@@ -511,6 +556,7 @@ class MainWindow(QMainWindow):
|
|||||||
def _save_settings(self) -> None:
|
def _save_settings(self) -> None:
|
||||||
settings = self.config.settings
|
settings = self.config.settings
|
||||||
old_autostart = settings.autostart
|
old_autostart = settings.autostart
|
||||||
|
old_update_check = settings.check_updates_on_start
|
||||||
settings.autostart = self.autostart_check.isChecked()
|
settings.autostart = self.autostart_check.isChecked()
|
||||||
settings.font_family = self.font_combo.currentText()
|
settings.font_family = self.font_combo.currentText()
|
||||||
settings.font_size = self.font_size_spin.value()
|
settings.font_size = self.font_size_spin.value()
|
||||||
@@ -528,18 +574,22 @@ class MainWindow(QMainWindow):
|
|||||||
return
|
return
|
||||||
apply_theme(QApplication.instance(), settings)
|
apply_theme(QApplication.instance(), settings)
|
||||||
self._append_log("Einstellungen", "Erfolg", "Darstellung und Systemstart wurden übernommen.")
|
self._append_log("Einstellungen", "Erfolg", "Darstellung und Systemstart wurden übernommen.")
|
||||||
|
if settings.check_updates_on_start and not old_update_check:
|
||||||
|
QTimer.singleShot(0, self._automatic_update_check)
|
||||||
|
elif not settings.check_updates_on_start:
|
||||||
|
self.update_retry_timer.stop()
|
||||||
|
|
||||||
def _automatic_update_check(self) -> None:
|
def _automatic_update_check(self) -> None:
|
||||||
settings = self.config.settings
|
settings = self.config.settings
|
||||||
if not settings.check_updates_on_start or self.coordinator.is_running:
|
if not settings.check_updates_on_start:
|
||||||
|
self.update_retry_timer.stop()
|
||||||
return
|
return
|
||||||
if settings.last_update_check:
|
if self.coordinator.is_running or self.updater.busy:
|
||||||
try:
|
if not self.update_retry_timer.isActive():
|
||||||
last_check = datetime.fromisoformat(settings.last_update_check)
|
self.update_retry_timer.start()
|
||||||
except ValueError:
|
return
|
||||||
last_check = datetime.min
|
self.update_retry_timer.stop()
|
||||||
if datetime.now() - last_check < timedelta(hours=24):
|
self.update_check_timer.start(UPDATE_CHECK_INTERVAL_MS)
|
||||||
return
|
|
||||||
self._check_updates(manual=False)
|
self._check_updates(manual=False)
|
||||||
|
|
||||||
def _check_updates(self, manual: bool) -> None:
|
def _check_updates(self, manual: bool) -> None:
|
||||||
@@ -558,12 +608,18 @@ class MainWindow(QMainWindow):
|
|||||||
self._mark_update_checked()
|
self._mark_update_checked()
|
||||||
self._update_progress(100, f"Version {info.version} ist verfügbar")
|
self._update_progress(100, f"Version {info.version} ist verfügbar")
|
||||||
self._append_log("Update", "Hinweis", f"Version {info.version} ist verfügbar.")
|
self._append_log("Update", "Hinweis", f"Version {info.version} ist verfügbar.")
|
||||||
|
self._set_update_indicator(info.version)
|
||||||
|
if not manual and self._notified_update_version == info.version:
|
||||||
|
return
|
||||||
|
self._notified_update_version = info.version
|
||||||
dialog = UpdateDialog(__version__, info, getattr(sys, "frozen", False), self)
|
dialog = UpdateDialog(__version__, info, getattr(sys, "frozen", False), self)
|
||||||
if dialog.exec() == UpdateDialog.DialogCode.Accepted:
|
if dialog.exec() == UpdateDialog.DialogCode.Accepted:
|
||||||
self.updater.download(info)
|
self.updater.download(info)
|
||||||
|
|
||||||
def _on_no_update(self, manual: bool) -> None:
|
def _on_no_update(self, manual: bool) -> None:
|
||||||
self._mark_update_checked()
|
self._mark_update_checked()
|
||||||
|
self._set_update_indicator(None)
|
||||||
|
self._notified_update_version = None
|
||||||
self._update_progress(100, "SyncVito ist aktuell")
|
self._update_progress(100, "SyncVito ist aktuell")
|
||||||
self._append_log("Update", "Erfolg", "Die installierte Version ist aktuell.")
|
self._append_log("Update", "Erfolg", "Die installierte Version ist aktuell.")
|
||||||
if manual:
|
if manual:
|
||||||
@@ -579,6 +635,18 @@ class MainWindow(QMainWindow):
|
|||||||
f"{message}\n\nBitte die Netzwerkverbindung prüfen und anschließend erneut versuchen.",
|
f"{message}\n\nBitte die Netzwerkverbindung prüfen und anschließend erneut versuchen.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _set_update_indicator(self, version: str | None) -> None:
|
||||||
|
available = bool(version)
|
||||||
|
self.settings_nav_button.setText("Einstellungen • UPDATE" if available else "Einstellungen")
|
||||||
|
self.settings_nav_button.setProperty("updateAvailable", available)
|
||||||
|
description = f"SyncVito {version} ist verfügbar" if version else ""
|
||||||
|
self.settings_nav_button.setToolTip(description)
|
||||||
|
self.settings_nav_button.setAccessibleDescription(description)
|
||||||
|
style = self.settings_nav_button.style()
|
||||||
|
style.unpolish(self.settings_nav_button)
|
||||||
|
style.polish(self.settings_nav_button)
|
||||||
|
self.settings_nav_button.update()
|
||||||
|
|
||||||
def _install_update(self, path: Path, info: UpdateInfo) -> None:
|
def _install_update(self, path: Path, info: UpdateInfo) -> None:
|
||||||
try:
|
try:
|
||||||
self.updater.service.launch_installer(path, info.version)
|
self.updater.service.launch_installer(path, info.version)
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ def _stylesheet(scale: float) -> str:
|
|||||||
padding: {round(10 * scale)}px {round(12 * scale)}px; color: #9CACBF; }}
|
padding: {round(10 * scale)}px {round(12 * scale)}px; color: #9CACBF; }}
|
||||||
QPushButton[nav="true"]:hover {{ background: #17212E; color: #E8EDF4; }}
|
QPushButton[nav="true"]:hover {{ background: #17212E; color: #E8EDF4; }}
|
||||||
QPushButton[nav="true"]:checked {{ background: #1B2A3D; color: #FFFFFF; border-color: #2A568B; }}
|
QPushButton[nav="true"]:checked {{ background: #1B2A3D; color: #FFFFFF; border-color: #2A568B; }}
|
||||||
|
QPushButton[nav="true"][updateAvailable="true"] {{ background: #3A2412; color: #FFF0DF;
|
||||||
|
border-color: #EA580C; }}
|
||||||
|
QPushButton[nav="true"][updateAvailable="true"]:hover {{ background: #4A2D14; color: #FFFFFF;
|
||||||
|
border-color: #FF7A2F; }}
|
||||||
|
QPushButton[nav="true"][updateAvailable="true"]:focus {{ border: 2px solid #FF9A5B;
|
||||||
|
padding: {round(9 * scale)}px {round(11 * scale)}px; }}
|
||||||
QLineEdit, QComboBox, QSpinBox, QListWidget, QTreeView, QTableWidget {{
|
QLineEdit, QComboBox, QSpinBox, QListWidget, QTreeView, QTableWidget {{
|
||||||
background: #0E141D; border: 1px solid #304052; border-radius: {radius}px;
|
background: #0E141D; border: 1px solid #304052; border-radius: {radius}px;
|
||||||
padding: {round(7 * scale)}px; color: #E8EDF4;
|
padding: {round(7 * scale)}px; color: #E8EDF4;
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ try {{
|
|||||||
$bootstrapLog,
|
$bootstrapLog,
|
||||||
((Get-Date).ToString('o') + ' Starte verifizierten Installer.' + [Environment]::NewLine)
|
((Get-Date).ToString('o') + ' Starte verifizierten Installer.' + [Environment]::NewLine)
|
||||||
)
|
)
|
||||||
|
$env:PYINSTALLER_RESET_ENVIRONMENT = '1'
|
||||||
$setupArguments = @({setup_arguments})
|
$setupArguments = @({setup_arguments})
|
||||||
& {_powershell_literal(installer_path)} @setupArguments
|
& {_powershell_literal(installer_path)} @setupArguments
|
||||||
$setupExitCode = $LASTEXITCODE
|
$setupExitCode = $LASTEXITCODE
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from PyQt6.QtWidgets import QApplication
|
||||||
|
|
||||||
|
from syncvito.main_window import PROGRESS_BAR_WIDTH, PROGRESS_TEXT_WIDTH, MainWindow
|
||||||
|
|
||||||
|
|
||||||
|
def test_progress_widgets_keep_fixed_widths_for_long_paths() -> None:
|
||||||
|
application = QApplication.instance() or QApplication([])
|
||||||
|
owner = SimpleNamespace()
|
||||||
|
dock = MainWindow._build_progress_dock(owner)
|
||||||
|
dock.resize(900, 66)
|
||||||
|
dock.show()
|
||||||
|
application.processEvents()
|
||||||
|
bar_x = owner.progress_bar.x()
|
||||||
|
long_path = "Kopieren: " + "sehr-langer-ordner\\" * 20 + "wichtige-datei.ini"
|
||||||
|
|
||||||
|
owner.progress_text.setText(long_path)
|
||||||
|
application.processEvents()
|
||||||
|
|
||||||
|
assert owner.progress_text.width() == PROGRESS_TEXT_WIDTH
|
||||||
|
assert owner.progress_bar.width() == PROGRESS_BAR_WIDTH
|
||||||
|
assert owner.progress_bar.x() == bar_x
|
||||||
|
assert owner.progress_text.text() != long_path
|
||||||
|
assert "…" in owner.progress_text.text()
|
||||||
|
assert owner.progress_text.toolTip() == long_path
|
||||||
|
assert owner.progress_text.accessibleName() == long_path
|
||||||
|
dock.close()
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from syncvito.main_window import UPDATE_CHECK_INTERVAL_MS, UPDATE_RETRY_INTERVAL_MS, MainWindow
|
||||||
|
|
||||||
|
|
||||||
|
class FakeTimer:
|
||||||
|
def __init__(self, *, active: bool = False) -> None:
|
||||||
|
self.active = active
|
||||||
|
self.start_calls: list[int | None] = []
|
||||||
|
self.stop_calls = 0
|
||||||
|
|
||||||
|
def isActive(self) -> bool: # noqa: N802 - Qt API
|
||||||
|
return self.active
|
||||||
|
|
||||||
|
def start(self, interval: int | None = None) -> None:
|
||||||
|
self.active = True
|
||||||
|
self.start_calls.append(interval)
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
self.active = False
|
||||||
|
self.stop_calls += 1
|
||||||
|
|
||||||
|
|
||||||
|
class FakeStyle:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.unpolished = False
|
||||||
|
self.polished = False
|
||||||
|
|
||||||
|
def unpolish(self, _button: object) -> None:
|
||||||
|
self.unpolished = True
|
||||||
|
|
||||||
|
def polish(self, _button: object) -> None:
|
||||||
|
self.polished = True
|
||||||
|
|
||||||
|
|
||||||
|
class FakeButton:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.text = ""
|
||||||
|
self.properties: dict[str, object] = {}
|
||||||
|
self.tooltip = ""
|
||||||
|
self.accessible_description = ""
|
||||||
|
self.button_style = FakeStyle()
|
||||||
|
self.updated = False
|
||||||
|
|
||||||
|
def setText(self, text: str) -> None: # noqa: N802 - Qt API
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def setProperty(self, name: str, value: object) -> None: # noqa: N802 - Qt API
|
||||||
|
self.properties[name] = value
|
||||||
|
|
||||||
|
def setToolTip(self, text: str) -> None: # noqa: N802 - Qt API
|
||||||
|
self.tooltip = text
|
||||||
|
|
||||||
|
def setAccessibleDescription(self, text: str) -> None: # noqa: N802 - Qt API
|
||||||
|
self.accessible_description = text
|
||||||
|
|
||||||
|
def style(self) -> FakeStyle:
|
||||||
|
return self.button_style
|
||||||
|
|
||||||
|
def update(self) -> None:
|
||||||
|
self.updated = True
|
||||||
|
|
||||||
|
|
||||||
|
def _window(*, sync_running: bool = False, updater_busy: bool = False) -> SimpleNamespace:
|
||||||
|
checks: list[bool] = []
|
||||||
|
return SimpleNamespace(
|
||||||
|
config=SimpleNamespace(settings=SimpleNamespace(check_updates_on_start=True)),
|
||||||
|
coordinator=SimpleNamespace(is_running=sync_running),
|
||||||
|
updater=SimpleNamespace(busy=updater_busy),
|
||||||
|
update_check_timer=FakeTimer(active=True),
|
||||||
|
update_retry_timer=FakeTimer(),
|
||||||
|
_check_updates=lambda manual: checks.append(manual),
|
||||||
|
checks=checks,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_automatic_update_check_runs_without_daily_throttle() -> None:
|
||||||
|
window = _window()
|
||||||
|
|
||||||
|
MainWindow._automatic_update_check(window)
|
||||||
|
|
||||||
|
assert window.checks == [False]
|
||||||
|
assert window.update_check_timer.start_calls == [UPDATE_CHECK_INTERVAL_MS]
|
||||||
|
assert window.update_retry_timer.stop_calls == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_automatic_update_check_retries_while_sync_is_running() -> None:
|
||||||
|
window = _window(sync_running=True)
|
||||||
|
|
||||||
|
MainWindow._automatic_update_check(window)
|
||||||
|
|
||||||
|
assert window.checks == []
|
||||||
|
assert window.update_retry_timer.start_calls == [None]
|
||||||
|
assert UPDATE_RETRY_INTERVAL_MS == 60_000
|
||||||
|
|
||||||
|
|
||||||
|
def test_periodic_update_interval_is_thirty_minutes() -> None:
|
||||||
|
assert UPDATE_CHECK_INTERVAL_MS == 30 * 60 * 1000
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_indicator_uses_text_and_accessible_description() -> None:
|
||||||
|
button = FakeButton()
|
||||||
|
window = SimpleNamespace(settings_nav_button=button)
|
||||||
|
|
||||||
|
MainWindow._set_update_indicator(window, "1.0.5")
|
||||||
|
|
||||||
|
assert button.text == "Einstellungen • UPDATE"
|
||||||
|
assert button.properties["updateAvailable"] is True
|
||||||
|
assert button.tooltip == "SyncVito 1.0.5 ist verfügbar"
|
||||||
|
assert button.accessible_description == button.tooltip
|
||||||
|
assert button.button_style.unpolished and button.button_style.polished
|
||||||
|
assert button.updated
|
||||||
@@ -162,4 +162,7 @@ def test_update_watcher_waits_before_installer(monkeypatch: pytest.MonkeyPatch,
|
|||||||
script = base64.b64decode(encoded).decode("utf-16-le")
|
script = base64.b64decode(encoded).decode("utf-16-le")
|
||||||
assert "foreach ($processId in @(111, 222))" in script
|
assert "foreach ($processId in @(111, 222))" in script
|
||||||
assert script.index("while (Get-Process") < script.index(str(installer))
|
assert script.index("while (Get-Process") < script.index(str(installer))
|
||||||
|
reset_environment = "$env:PYINSTALLER_RESET_ENVIRONMENT = '1'"
|
||||||
|
assert reset_environment in script
|
||||||
|
assert script.index(reset_environment) < script.index(f"& '{installer}'")
|
||||||
assert "'/VERYSILENT'" in script
|
assert "'/VERYSILENT'" in script
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
1.0.1
|
1.0.6
|
||||||
Reference in new issue
Block a user