synodic-client 0.0.1.dev52__tar.gz → 0.0.1.dev54__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/PKG-INFO +1 -1
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/pyproject.toml +1 -1
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/bootstrap.py +4 -3
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/qt.py +10 -4
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/screen.py +2 -3
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/settings.py +30 -2
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/tray.py +8 -5
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/update_controller.py +25 -11
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/cli.py +5 -1
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/logging.py +33 -2
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/resolution.py +2 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/schema.py +5 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/updater.py +12 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_gather_packages.py +1 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_settings.py +1 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_update_controller.py +111 -12
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/test_cli.py +3 -3
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/test_config.py +1 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/test_resolution.py +1 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/test_updater.py +10 -2
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/LICENSE.md +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/README.md +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/__init__.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/__main__.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/__init__.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/data.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/icon.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/init.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/instance.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/schema.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/__init__.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/action_card.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/card.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/install.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/install_workers.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/log_panel.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/plugin_row.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/projects.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/schema.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/sidebar.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/spinner.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/tool_update_controller.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/update_banner.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/theme.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/uri.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/workers.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/client.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/config.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/protocol.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/py.typed +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/startup.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/__init__.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/conftest.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/__init__.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/__init__.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/conftest.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_action_card.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_install_preview.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_log_panel.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_logging.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_preview_model.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_sidebar.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_tray_window_show.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_update_banner.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_update_feedback.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/test_client_updater.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/test_client_version.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/test_examples.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/test_init.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/test_install.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/test_uri.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/test_workers.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/windows/__init__.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/windows/conftest.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/windows/test_protocol.py +0 -0
- {synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/windows/test_startup.py +0 -0
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/bootstrap.py
RENAMED
|
@@ -20,11 +20,12 @@ from synodic_client.logging import configure_logging
|
|
|
20
20
|
from synodic_client.protocol import extract_uri_from_args
|
|
21
21
|
from synodic_client.updater import initialize_velopack
|
|
22
22
|
|
|
23
|
-
# Parse
|
|
23
|
+
# Parse flags early so logging uses the right filename and level.
|
|
24
24
|
_dev_mode = '--dev' in sys.argv[1:]
|
|
25
|
+
_debug = '--debug' in sys.argv[1:]
|
|
25
26
|
set_dev_mode(_dev_mode)
|
|
26
27
|
|
|
27
|
-
configure_logging()
|
|
28
|
+
configure_logging(debug=_debug)
|
|
28
29
|
initialize_velopack()
|
|
29
30
|
|
|
30
31
|
if not _dev_mode:
|
|
@@ -35,4 +36,4 @@ if not _dev_mode:
|
|
|
35
36
|
# Heavy imports happen here — PySide6, porringer, etc.
|
|
36
37
|
from synodic_client.application.qt import application
|
|
37
38
|
|
|
38
|
-
application(uri=extract_uri_from_args(), dev_mode=_dev_mode)
|
|
39
|
+
application(uri=extract_uri_from_args(), dev_mode=_dev_mode, debug=_debug)
|
|
@@ -24,7 +24,7 @@ from synodic_client.application.screen.tray import TrayScreen
|
|
|
24
24
|
from synodic_client.application.uri import parse_uri
|
|
25
25
|
from synodic_client.client import Client
|
|
26
26
|
from synodic_client.config import set_dev_mode
|
|
27
|
-
from synodic_client.logging import configure_logging
|
|
27
|
+
from synodic_client.logging import configure_logging, set_debug_level
|
|
28
28
|
from synodic_client.protocol import extract_uri_from_args
|
|
29
29
|
from synodic_client.resolution import (
|
|
30
30
|
ResolvedConfig,
|
|
@@ -104,7 +104,7 @@ class _TopLevelShowFilter(QObject):
|
|
|
104
104
|
):
|
|
105
105
|
geo = obj.geometry()
|
|
106
106
|
stack = ''.join(traceback.format_stack(limit=12))
|
|
107
|
-
self._diag_logger.
|
|
107
|
+
self._diag_logger.debug(
|
|
108
108
|
'[DIAG] Top-level window %s: class=%s title=%r geo=(%d,%d %dx%d) visible=%s\n%s',
|
|
109
109
|
event.type().name,
|
|
110
110
|
type(obj).__qualname__,
|
|
@@ -149,7 +149,7 @@ def _init_app() -> QApplication:
|
|
|
149
149
|
return app
|
|
150
150
|
|
|
151
151
|
|
|
152
|
-
def application(*, uri: str | None = None, dev_mode: bool = False) -> None:
|
|
152
|
+
def application(*, uri: str | None = None, dev_mode: bool = False, debug: bool = False) -> None:
|
|
153
153
|
"""Application entry point.
|
|
154
154
|
|
|
155
155
|
Args:
|
|
@@ -159,13 +159,14 @@ def application(*, uri: str | None = None, dev_mode: bool = False) -> None:
|
|
|
159
159
|
log files, or single-instance locks with the user-installed
|
|
160
160
|
application. Velopack initialisation and protocol
|
|
161
161
|
registration are skipped.
|
|
162
|
+
debug: When ``True``, enable DEBUG-level file logging.
|
|
162
163
|
"""
|
|
163
164
|
# Activate dev-mode namespacing before anything reads config paths.
|
|
164
165
|
set_dev_mode(dev_mode)
|
|
165
166
|
|
|
166
167
|
# Configure logging before Velopack so install/uninstall hooks and
|
|
167
168
|
# first-run diagnostics are captured in the log file.
|
|
168
|
-
configure_logging()
|
|
169
|
+
configure_logging(debug=debug)
|
|
169
170
|
logger = logging.getLogger('synodic_client')
|
|
170
171
|
_install_exception_hook(logger)
|
|
171
172
|
|
|
@@ -180,6 +181,11 @@ def application(*, uri: str | None = None, dev_mode: bool = False) -> None:
|
|
|
180
181
|
|
|
181
182
|
client, porringer, config = _init_services(logger)
|
|
182
183
|
|
|
184
|
+
# Honour the persisted debug_logging preference unless the --debug
|
|
185
|
+
# flag already activated it.
|
|
186
|
+
if not debug and config.debug_logging:
|
|
187
|
+
set_debug_level(enabled=True)
|
|
188
|
+
|
|
183
189
|
app = _init_app()
|
|
184
190
|
|
|
185
191
|
loop = qasync.QEventLoop(app)
|
|
@@ -199,8 +199,7 @@ class ToolsView(QWidget):
|
|
|
199
199
|
"""Schedule an asynchronous rebuild of the tool list."""
|
|
200
200
|
if self._refresh_in_progress:
|
|
201
201
|
return
|
|
202
|
-
|
|
203
|
-
logger.info('[DIAG] ToolsView.refresh() called, parent_visible=%s\n%s', self.isVisible(), caller)
|
|
202
|
+
logger.debug('ToolsView.refresh() called (visible=%s)', self.isVisible())
|
|
204
203
|
asyncio.create_task(self._async_refresh())
|
|
205
204
|
|
|
206
205
|
async def _async_refresh(self) -> None:
|
|
@@ -1112,7 +1111,7 @@ class MainWindow(QMainWindow):
|
|
|
1112
1111
|
"""[DIAG] Log every show event with a stack trace."""
|
|
1113
1112
|
geo = self.geometry()
|
|
1114
1113
|
stack = ''.join(traceback.format_stack(limit=10))
|
|
1115
|
-
logger.
|
|
1114
|
+
logger.debug(
|
|
1116
1115
|
'[DIAG] MainWindow.showEvent: geo=(%d,%d %dx%d) visible=%s\n%s',
|
|
1117
1116
|
geo.x(),
|
|
1118
1117
|
geo.y(),
|
|
@@ -32,7 +32,7 @@ from synodic_client.application.icon import app_icon
|
|
|
32
32
|
from synodic_client.application.screen import _format_relative_time
|
|
33
33
|
from synodic_client.application.screen.card import CardFrame
|
|
34
34
|
from synodic_client.application.theme import SETTINGS_WINDOW_MIN_SIZE, UPDATE_STATUS_CHECKING_STYLE
|
|
35
|
-
from synodic_client.logging import log_path
|
|
35
|
+
from synodic_client.logging import log_path, set_debug_level
|
|
36
36
|
from synodic_client.resolution import ResolvedConfig, update_user_config
|
|
37
37
|
from synodic_client.schema import GITHUB_REPO_URL
|
|
38
38
|
from synodic_client.startup import is_startup_registered, register_startup, remove_startup
|
|
@@ -54,11 +54,14 @@ class SettingsWindow(QMainWindow):
|
|
|
54
54
|
check_updates_requested = Signal()
|
|
55
55
|
"""Emitted when the user clicks the *Check for Updates* button."""
|
|
56
56
|
|
|
57
|
+
restart_requested = Signal()
|
|
58
|
+
"""Emitted when the user clicks the *Restart & Update* button."""
|
|
59
|
+
|
|
57
60
|
def showEvent(self, event: QShowEvent) -> None: # noqa: N802
|
|
58
61
|
"""[DIAG] Log every show event with a stack trace."""
|
|
59
62
|
geo = self.geometry()
|
|
60
63
|
stack = ''.join(traceback.format_stack(limit=10))
|
|
61
|
-
logger.
|
|
64
|
+
logger.debug(
|
|
62
65
|
'[DIAG] SettingsWindow.showEvent: geo=(%d,%d %dx%d) visible=%s\n%s',
|
|
63
66
|
geo.x(),
|
|
64
67
|
geo.y(),
|
|
@@ -199,6 +202,12 @@ class SettingsWindow(QMainWindow):
|
|
|
199
202
|
self._update_status_label = QLabel('')
|
|
200
203
|
self._update_status_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
|
|
201
204
|
row.addWidget(self._update_status_label)
|
|
205
|
+
|
|
206
|
+
self._restart_btn = QPushButton('Restart \u0026 Update')
|
|
207
|
+
self._restart_btn.clicked.connect(self.restart_requested.emit)
|
|
208
|
+
self._restart_btn.hide()
|
|
209
|
+
row.addWidget(self._restart_btn)
|
|
210
|
+
|
|
202
211
|
row.addStretch()
|
|
203
212
|
content.addLayout(row)
|
|
204
213
|
|
|
@@ -218,6 +227,12 @@ class SettingsWindow(QMainWindow):
|
|
|
218
227
|
def _build_advanced_section(self) -> CardFrame:
|
|
219
228
|
"""Construct the *Advanced* settings card."""
|
|
220
229
|
card = CardFrame('Advanced')
|
|
230
|
+
|
|
231
|
+
self._debug_logging_check = QCheckBox('Debug logging')
|
|
232
|
+
self._debug_logging_check.setToolTip('Write DEBUG-level messages to the log file')
|
|
233
|
+
self._debug_logging_check.toggled.connect(self._on_debug_logging_changed)
|
|
234
|
+
card.content_layout.addWidget(self._debug_logging_check)
|
|
235
|
+
|
|
221
236
|
row = QHBoxLayout()
|
|
222
237
|
open_log_btn = QPushButton('Open Log\u2026')
|
|
223
238
|
open_log_btn.clicked.connect(self._open_log)
|
|
@@ -254,6 +269,9 @@ class SettingsWindow(QMainWindow):
|
|
|
254
269
|
self._auto_apply_check.setChecked(config.auto_apply)
|
|
255
270
|
self._auto_start_check.setChecked(is_startup_registered())
|
|
256
271
|
|
|
272
|
+
# Debug logging
|
|
273
|
+
self._debug_logging_check.setChecked(config.debug_logging)
|
|
274
|
+
|
|
257
275
|
# Last client update timestamp
|
|
258
276
|
if config.last_client_update:
|
|
259
277
|
relative = _format_relative_time(config.last_client_update)
|
|
@@ -275,6 +293,7 @@ class SettingsWindow(QMainWindow):
|
|
|
275
293
|
def set_checking(self) -> None:
|
|
276
294
|
"""Enter the *checking* state — disable button and show status."""
|
|
277
295
|
self._check_updates_btn.setEnabled(False)
|
|
296
|
+
self._restart_btn.hide()
|
|
278
297
|
self._update_status_label.setText('Checking\u2026')
|
|
279
298
|
self._update_status_label.setStyleSheet(UPDATE_STATUS_CHECKING_STYLE)
|
|
280
299
|
|
|
@@ -282,6 +301,10 @@ class SettingsWindow(QMainWindow):
|
|
|
282
301
|
"""Re-enable the *Check for Updates* button after a check completes."""
|
|
283
302
|
self._check_updates_btn.setEnabled(True)
|
|
284
303
|
|
|
304
|
+
def show_restart_button(self) -> None:
|
|
305
|
+
"""Show the *Restart & Update* button."""
|
|
306
|
+
self._restart_btn.show()
|
|
307
|
+
|
|
285
308
|
def show(self) -> None:
|
|
286
309
|
"""Sync controls from config, then show the window."""
|
|
287
310
|
self.sync_from_config()
|
|
@@ -313,6 +336,7 @@ class SettingsWindow(QMainWindow):
|
|
|
313
336
|
self._detect_updates_check,
|
|
314
337
|
self._auto_apply_check,
|
|
315
338
|
self._auto_start_check,
|
|
339
|
+
self._debug_logging_check,
|
|
316
340
|
self._check_updates_btn,
|
|
317
341
|
)
|
|
318
342
|
for w in widgets:
|
|
@@ -362,6 +386,10 @@ class SettingsWindow(QMainWindow):
|
|
|
362
386
|
remove_startup()
|
|
363
387
|
self.settings_changed.emit(self._config)
|
|
364
388
|
|
|
389
|
+
def _on_debug_logging_changed(self, checked: bool) -> None:
|
|
390
|
+
set_debug_level(enabled=checked)
|
|
391
|
+
self._persist(debug_logging=checked)
|
|
392
|
+
|
|
365
393
|
@staticmethod
|
|
366
394
|
def _open_log() -> None:
|
|
367
395
|
"""Open the log file in the system's default editor."""
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/tray.py
RENAMED
|
@@ -5,6 +5,7 @@ import logging
|
|
|
5
5
|
from PySide6.QtGui import QAction
|
|
6
6
|
from PySide6.QtWidgets import (
|
|
7
7
|
QApplication,
|
|
8
|
+
QMainWindow,
|
|
8
9
|
QMenu,
|
|
9
10
|
QSystemTrayIcon,
|
|
10
11
|
)
|
|
@@ -130,13 +131,15 @@ class TrayScreen:
|
|
|
130
131
|
"""Show the settings window."""
|
|
131
132
|
self._settings_window.show()
|
|
132
133
|
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
@staticmethod
|
|
135
|
+
def _is_user_active() -> bool:
|
|
136
|
+
"""Return ``True`` when the user has a visible application window.
|
|
135
137
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
Checks all top-level ``QMainWindow`` instances (main window,
|
|
139
|
+
settings, install previews) so that auto-apply is deferred
|
|
140
|
+
whenever *any* window is open.
|
|
138
141
|
"""
|
|
139
|
-
return
|
|
142
|
+
return any(w.isVisible() for w in QApplication.topLevelWidgets() if isinstance(w, QMainWindow))
|
|
140
143
|
|
|
141
144
|
def _on_settings_changed(self, config: ResolvedConfig) -> None:
|
|
142
145
|
"""React to a change made in the settings window."""
|
|
@@ -56,7 +56,7 @@ class UpdateController:
|
|
|
56
56
|
Optional pre-resolved configuration. ``None`` resolves from disk.
|
|
57
57
|
is_user_active:
|
|
58
58
|
Predicate returning ``True`` when the user has a visible window.
|
|
59
|
-
|
|
59
|
+
Auto-apply is deferred while active; checks still run normally.
|
|
60
60
|
"""
|
|
61
61
|
|
|
62
62
|
def __init__(
|
|
@@ -84,6 +84,7 @@ class UpdateController:
|
|
|
84
84
|
self._config = config
|
|
85
85
|
self._is_user_active: Callable[[], bool] = lambda: False
|
|
86
86
|
self._update_task: asyncio.Task[None] | None = None
|
|
87
|
+
self._pending_version: str | None = None
|
|
87
88
|
|
|
88
89
|
# Derive auto-apply preference from config
|
|
89
90
|
resolved = self._resolve_config()
|
|
@@ -99,9 +100,10 @@ class UpdateController:
|
|
|
99
100
|
|
|
100
101
|
# Wire settings check-updates button
|
|
101
102
|
self._settings_window.check_updates_requested.connect(self._on_manual_check)
|
|
103
|
+
self._settings_window.restart_requested.connect(self._apply_update)
|
|
102
104
|
|
|
103
105
|
def set_user_active_predicate(self, predicate: Callable[[], bool]) -> None:
|
|
104
|
-
"""Set the predicate used to defer
|
|
106
|
+
"""Set the predicate used to defer auto-apply when the user is active.
|
|
105
107
|
|
|
106
108
|
Args:
|
|
107
109
|
predicate: Returns ``True`` when the user has a visible window.
|
|
@@ -200,12 +202,10 @@ class UpdateController:
|
|
|
200
202
|
def _on_auto_check(self) -> None:
|
|
201
203
|
"""Handle automatic (periodic) check — silent.
|
|
202
204
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
+
The check always runs so the settings window can show the
|
|
206
|
+
latest status and the *last updated* timestamp stays current.
|
|
207
|
+
Auto-apply is gated separately by :meth:`_can_auto_apply`.
|
|
205
208
|
"""
|
|
206
|
-
if self._is_user_active():
|
|
207
|
-
logger.debug('Automatic update check deferred — user is active')
|
|
208
|
-
return
|
|
209
209
|
self._do_check(silent=True)
|
|
210
210
|
|
|
211
211
|
def _do_check(self, *, silent: bool) -> None:
|
|
@@ -215,8 +215,9 @@ class UpdateController:
|
|
|
215
215
|
self._banner.show_error('Updater is not initialized.')
|
|
216
216
|
return
|
|
217
217
|
|
|
218
|
-
#
|
|
219
|
-
self.
|
|
218
|
+
# Preserve the restart button when an update is already pending
|
|
219
|
+
if self._pending_version is None:
|
|
220
|
+
self._settings_window.set_checking()
|
|
220
221
|
|
|
221
222
|
self._update_task = asyncio.create_task(self._async_check(silent=silent))
|
|
222
223
|
|
|
@@ -259,8 +260,14 @@ class UpdateController:
|
|
|
259
260
|
logger.debug('Automatic update check: no update available')
|
|
260
261
|
return
|
|
261
262
|
|
|
262
|
-
# Update available — show status and start download
|
|
263
263
|
version = str(result.latest_version)
|
|
264
|
+
|
|
265
|
+
# Already downloaded — restore the ready state without re-downloading
|
|
266
|
+
if version == self._pending_version:
|
|
267
|
+
self._show_ready(version)
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
# New update available — download it
|
|
264
271
|
self._settings_window.set_update_status(
|
|
265
272
|
f'v{version} available',
|
|
266
273
|
UPDATE_STATUS_AVAILABLE_STYLE,
|
|
@@ -308,6 +315,8 @@ class UpdateController:
|
|
|
308
315
|
# Persist the client update timestamp
|
|
309
316
|
update_user_config(last_client_update=datetime.now(UTC).isoformat())
|
|
310
317
|
|
|
318
|
+
self._pending_version = version
|
|
319
|
+
|
|
311
320
|
if self._can_auto_apply():
|
|
312
321
|
# Silently apply and restart — no banner, no user interaction
|
|
313
322
|
logger.info('Auto-applying update v%s', version)
|
|
@@ -318,12 +327,16 @@ class UpdateController:
|
|
|
318
327
|
self._apply_update(silent=True)
|
|
319
328
|
return
|
|
320
329
|
|
|
321
|
-
|
|
330
|
+
self._show_ready(version)
|
|
331
|
+
|
|
332
|
+
def _show_ready(self, version: str) -> None:
|
|
333
|
+
"""Present the *ready to restart* state in both UIs."""
|
|
322
334
|
self._banner.show_ready(version)
|
|
323
335
|
self._settings_window.set_update_status(
|
|
324
336
|
f'v{version} ready',
|
|
325
337
|
UPDATE_STATUS_UP_TO_DATE_STYLE,
|
|
326
338
|
)
|
|
339
|
+
self._settings_window.show_restart_button()
|
|
327
340
|
|
|
328
341
|
def _on_download_error(self, error: str) -> None:
|
|
329
342
|
"""Handle download error — show error banner."""
|
|
@@ -345,6 +358,7 @@ class UpdateController:
|
|
|
345
358
|
return
|
|
346
359
|
|
|
347
360
|
try:
|
|
361
|
+
self._pending_version = None
|
|
348
362
|
self._client.apply_update_on_exit(restart=True, silent=silent)
|
|
349
363
|
logger.info('Update scheduled — restarting application')
|
|
350
364
|
self._app.quit()
|
|
@@ -35,8 +35,12 @@ def main(
|
|
|
35
35
|
bool,
|
|
36
36
|
typer.Option('--dev', help='Run in dev mode with isolated config, logs, and instance lock.'),
|
|
37
37
|
] = False,
|
|
38
|
+
debug: Annotated[
|
|
39
|
+
bool,
|
|
40
|
+
typer.Option('--debug', help='Enable DEBUG-level file logging for this session.'),
|
|
41
|
+
] = False,
|
|
38
42
|
) -> None:
|
|
39
43
|
"""Launch the Synodic Client GUI application."""
|
|
40
44
|
from synodic_client.application.qt import application
|
|
41
45
|
|
|
42
|
-
application(uri=uri, dev_mode=dev)
|
|
46
|
+
application(uri=uri, dev_mode=dev, debug=debug)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Centralised logging configuration for the Synodic Client.
|
|
2
2
|
|
|
3
|
-
Provides a rotating file handler with eager flushing
|
|
3
|
+
Provides a rotating file handler with eager flushing and runtime
|
|
4
|
+
log-level switching via :func:`set_debug_level`.
|
|
4
5
|
"""
|
|
5
6
|
|
|
6
7
|
import logging
|
|
@@ -17,6 +18,8 @@ _MAX_BYTES = 5_242_880 # 5 MB
|
|
|
17
18
|
_BACKUP_COUNT = 3
|
|
18
19
|
_FORMAT = '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
|
|
19
20
|
|
|
21
|
+
_debug_active: bool = False
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
def log_path() -> Path:
|
|
22
25
|
"""Return the path to the application log file.
|
|
@@ -43,13 +46,18 @@ class EagerRotatingFileHandler(RotatingFileHandler):
|
|
|
43
46
|
self.flush()
|
|
44
47
|
|
|
45
48
|
|
|
46
|
-
def configure_logging() -> None:
|
|
49
|
+
def configure_logging(*, debug: bool = False) -> None:
|
|
47
50
|
"""Set up application-wide logging.
|
|
48
51
|
|
|
49
52
|
Attaches a :class:`EagerRotatingFileHandler` to the ``synodic_client``
|
|
50
53
|
and ``porringer`` loggers and configures :func:`logging.basicConfig`
|
|
51
54
|
for ``INFO`` level output on *stderr*.
|
|
52
55
|
|
|
56
|
+
Args:
|
|
57
|
+
debug: When ``True``, set the file handler and app logger to
|
|
58
|
+
``DEBUG`` level immediately. Equivalent to calling
|
|
59
|
+
:func:`set_debug_level` after configuration.
|
|
60
|
+
|
|
53
61
|
Safe to call more than once — subsequent calls are no-ops.
|
|
54
62
|
"""
|
|
55
63
|
app_logger = logging.getLogger('synodic_client')
|
|
@@ -80,3 +88,26 @@ def configure_logging() -> None:
|
|
|
80
88
|
porringer_logger.setLevel(logging.DEBUG)
|
|
81
89
|
else:
|
|
82
90
|
porringer_logger.setLevel(logging.INFO)
|
|
91
|
+
|
|
92
|
+
if debug:
|
|
93
|
+
set_debug_level(enabled=True)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def set_debug_level(*, enabled: bool) -> None:
|
|
97
|
+
"""Switch the app logger and file handler between DEBUG and INFO at runtime.
|
|
98
|
+
|
|
99
|
+
Safe to call at any time. Has no effect if logging has not been
|
|
100
|
+
configured yet.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
enabled: ``True`` for DEBUG, ``False`` for INFO.
|
|
104
|
+
"""
|
|
105
|
+
global _debug_active # noqa: PLW0603
|
|
106
|
+
_debug_active = enabled
|
|
107
|
+
level = logging.DEBUG if enabled else logging.INFO
|
|
108
|
+
|
|
109
|
+
app_logger = logging.getLogger('synodic_client')
|
|
110
|
+
app_logger.setLevel(level)
|
|
111
|
+
for h in app_logger.handlers:
|
|
112
|
+
if isinstance(h, EagerRotatingFileHandler):
|
|
113
|
+
h.setLevel(level)
|
|
@@ -114,6 +114,7 @@ def _resolve_from_user(user: UserConfig) -> ResolvedConfig:
|
|
|
114
114
|
|
|
115
115
|
auto_apply = user.auto_apply if user.auto_apply is not None else True
|
|
116
116
|
auto_start = user.auto_start if user.auto_start is not None else True
|
|
117
|
+
debug_logging = user.debug_logging if user.debug_logging is not None else False
|
|
117
118
|
|
|
118
119
|
return ResolvedConfig(
|
|
119
120
|
update_source=user.update_source,
|
|
@@ -125,6 +126,7 @@ def _resolve_from_user(user: UserConfig) -> ResolvedConfig:
|
|
|
125
126
|
prerelease_packages=user.prerelease_packages,
|
|
126
127
|
auto_apply=auto_apply,
|
|
127
128
|
auto_start=auto_start,
|
|
129
|
+
debug_logging=debug_logging,
|
|
128
130
|
last_client_update=user.last_client_update,
|
|
129
131
|
last_tool_updates=user.last_tool_updates,
|
|
130
132
|
)
|
|
@@ -100,6 +100,10 @@ class UserConfig(BaseModel):
|
|
|
100
100
|
# auto-startup.
|
|
101
101
|
auto_start: bool | None = None
|
|
102
102
|
|
|
103
|
+
# Enable verbose DEBUG-level logging to the log file.
|
|
104
|
+
# None resolves to False (INFO level).
|
|
105
|
+
debug_logging: bool | None = None
|
|
106
|
+
|
|
103
107
|
# ISO 8601 timestamp of the last successful client self-update.
|
|
104
108
|
# None means no update has been recorded.
|
|
105
109
|
last_client_update: str | None = None
|
|
@@ -231,5 +235,6 @@ class ResolvedConfig:
|
|
|
231
235
|
prerelease_packages: dict[str, list[str]] | None
|
|
232
236
|
auto_apply: bool
|
|
233
237
|
auto_start: bool
|
|
238
|
+
debug_logging: bool
|
|
234
239
|
last_client_update: str | None
|
|
235
240
|
last_tool_updates: dict[str, str] | None
|
|
@@ -7,6 +7,7 @@ and installation.
|
|
|
7
7
|
For non-installed (development) environments, updates are not supported.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
import contextlib
|
|
10
11
|
import logging
|
|
11
12
|
import sys
|
|
12
13
|
from collections.abc import Callable
|
|
@@ -114,6 +115,12 @@ class Updater:
|
|
|
114
115
|
self._velopack_manager: Any = None
|
|
115
116
|
self._velopack_not_installed: bool = False
|
|
116
117
|
|
|
118
|
+
# Eagerly resolve the Velopack manager so that
|
|
119
|
+
# _current_version reflects the installed binary version
|
|
120
|
+
# rather than the (potentially stale) Python package metadata.
|
|
121
|
+
with contextlib.suppress(Exception):
|
|
122
|
+
self._get_velopack_manager()
|
|
123
|
+
|
|
117
124
|
logger.info(
|
|
118
125
|
'Updater created: version=%s, channel=%s, repo=%s',
|
|
119
126
|
self._current_version,
|
|
@@ -406,6 +413,11 @@ def initialize_velopack() -> None:
|
|
|
406
413
|
return
|
|
407
414
|
_VelopackState.initialized = True
|
|
408
415
|
|
|
416
|
+
# During post-update restarts Velopack's App.run() may exit the
|
|
417
|
+
# current process (to apply the update and relaunch). Each
|
|
418
|
+
# short-lived process writes "Initializing Velopack" to the shared
|
|
419
|
+
# log file before being replaced, so multiple entries followed by a
|
|
420
|
+
# single "initialized successfully" is expected behaviour.
|
|
409
421
|
logger.info('Initializing Velopack (exe=%s)', sys.executable)
|
|
410
422
|
try:
|
|
411
423
|
app = velopack.App()
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_update_controller.py
RENAMED
|
@@ -38,6 +38,7 @@ def _make_config(**overrides: Any) -> ResolvedConfig:
|
|
|
38
38
|
'prerelease_packages': None,
|
|
39
39
|
'auto_apply': True,
|
|
40
40
|
'auto_start': True,
|
|
41
|
+
'debug_logging': False,
|
|
41
42
|
'last_client_update': None,
|
|
42
43
|
'last_tool_updates': None,
|
|
43
44
|
}
|
|
@@ -197,6 +198,27 @@ class TestDownloadFinished:
|
|
|
197
198
|
UPDATE_STATUS_UP_TO_DATE_STYLE,
|
|
198
199
|
)
|
|
199
200
|
|
|
201
|
+
@staticmethod
|
|
202
|
+
def test_no_auto_apply_shows_restart_button() -> None:
|
|
203
|
+
"""When auto_apply=False, the restart button should be shown in settings."""
|
|
204
|
+
ctrl, app, client, banner, settings = _make_controller(auto_apply=False)
|
|
205
|
+
ctrl._on_download_finished(True, '2.0.0')
|
|
206
|
+
|
|
207
|
+
settings.show_restart_button.assert_called_once()
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
def test_user_active_shows_restart_button() -> None:
|
|
211
|
+
"""When user is active, the restart button should be shown in settings."""
|
|
212
|
+
ctrl, app, client, banner, settings = _make_controller(
|
|
213
|
+
auto_apply=True,
|
|
214
|
+
is_user_active=True,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
with patch.object(ctrl, '_apply_update'):
|
|
218
|
+
ctrl._on_download_finished(True, '2.0.0')
|
|
219
|
+
|
|
220
|
+
settings.show_restart_button.assert_called_once()
|
|
221
|
+
|
|
200
222
|
@staticmethod
|
|
201
223
|
def test_download_failure_shows_error() -> None:
|
|
202
224
|
"""A failed download should show an error banner."""
|
|
@@ -206,29 +228,98 @@ class TestDownloadFinished:
|
|
|
206
228
|
assert banner.state.name == 'ERROR'
|
|
207
229
|
settings.set_update_status.assert_called_with('Download failed', UPDATE_STATUS_ERROR_STYLE)
|
|
208
230
|
|
|
231
|
+
@staticmethod
|
|
232
|
+
def test_download_sets_pending_version() -> None:
|
|
233
|
+
"""A successful download should set _pending_version."""
|
|
234
|
+
ctrl, app, client, banner, settings = _make_controller(auto_apply=False)
|
|
235
|
+
ctrl._on_download_finished(True, '2.0.0')
|
|
236
|
+
|
|
237
|
+
assert ctrl._pending_version == '2.0.0'
|
|
238
|
+
|
|
209
239
|
|
|
210
240
|
# ---------------------------------------------------------------------------
|
|
211
|
-
#
|
|
241
|
+
# Pending version — skip redundant downloads
|
|
212
242
|
# ---------------------------------------------------------------------------
|
|
213
243
|
|
|
214
244
|
|
|
215
|
-
class
|
|
216
|
-
"""Verify
|
|
245
|
+
class TestPendingVersion:
|
|
246
|
+
"""Verify behaviour when an update is already downloaded and pending."""
|
|
217
247
|
|
|
218
248
|
@staticmethod
|
|
219
|
-
def
|
|
220
|
-
"""
|
|
221
|
-
ctrl, _app, _client, banner, settings = _make_controller(
|
|
249
|
+
def test_check_skips_download_when_version_already_pending() -> None:
|
|
250
|
+
"""Re-checking the same version should restore ready state, not re-download."""
|
|
251
|
+
ctrl, _app, _client, banner, settings = _make_controller(auto_apply=False)
|
|
252
|
+
ctrl._pending_version = '2.0.0'
|
|
222
253
|
|
|
223
|
-
|
|
224
|
-
ctrl._on_auto_check()
|
|
254
|
+
result = UpdateInfo(available=True, current_version=Version('1.0.0'), latest_version=Version('2.0.0'))
|
|
225
255
|
|
|
226
|
-
|
|
256
|
+
with patch.object(ctrl, '_start_download') as mock_dl:
|
|
257
|
+
ctrl._on_check_finished(result, silent=True)
|
|
258
|
+
|
|
259
|
+
mock_dl.assert_not_called()
|
|
260
|
+
assert banner.state.name == 'READY'
|
|
261
|
+
settings.show_restart_button.assert_called_once()
|
|
227
262
|
|
|
228
263
|
@staticmethod
|
|
229
|
-
def
|
|
230
|
-
"""
|
|
231
|
-
ctrl, _app, _client, banner, settings = _make_controller(
|
|
264
|
+
def test_check_downloads_when_newer_version() -> None:
|
|
265
|
+
"""A different version should trigger a fresh download."""
|
|
266
|
+
ctrl, _app, _client, banner, settings = _make_controller(auto_apply=False)
|
|
267
|
+
ctrl._pending_version = '1.5.0'
|
|
268
|
+
|
|
269
|
+
result = UpdateInfo(available=True, current_version=Version('1.0.0'), latest_version=Version('2.0.0'))
|
|
270
|
+
|
|
271
|
+
with patch.object(ctrl, '_start_download') as mock_dl:
|
|
272
|
+
ctrl._on_check_finished(result, silent=True)
|
|
273
|
+
|
|
274
|
+
mock_dl.assert_called_once_with('2.0.0')
|
|
275
|
+
|
|
276
|
+
@staticmethod
|
|
277
|
+
def test_do_check_preserves_settings_ui_when_pending() -> None:
|
|
278
|
+
"""set_checking should NOT be called when an update is already pending."""
|
|
279
|
+
ctrl, _app, _client, banner, settings = _make_controller()
|
|
280
|
+
ctrl._pending_version = '2.0.0'
|
|
281
|
+
|
|
282
|
+
with patch('asyncio.create_task'):
|
|
283
|
+
ctrl._do_check(silent=True)
|
|
284
|
+
|
|
285
|
+
settings.set_checking.assert_not_called()
|
|
286
|
+
|
|
287
|
+
@staticmethod
|
|
288
|
+
def test_do_check_shows_checking_when_no_pending() -> None:
|
|
289
|
+
"""set_checking should be called when there is no pending update."""
|
|
290
|
+
ctrl, _app, _client, banner, settings = _make_controller()
|
|
291
|
+
|
|
292
|
+
with patch('asyncio.create_task'):
|
|
293
|
+
ctrl._do_check(silent=True)
|
|
294
|
+
|
|
295
|
+
settings.set_checking.assert_called_once()
|
|
296
|
+
|
|
297
|
+
@staticmethod
|
|
298
|
+
def test_apply_clears_pending_version() -> None:
|
|
299
|
+
"""_apply_update should clear _pending_version."""
|
|
300
|
+
ctrl, app, client, banner, settings = _make_controller()
|
|
301
|
+
ctrl._pending_version = '2.0.0'
|
|
302
|
+
ctrl._apply_update()
|
|
303
|
+
|
|
304
|
+
assert ctrl._pending_version is None
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# ---------------------------------------------------------------------------
|
|
308
|
+
# User-active gating
|
|
309
|
+
# ---------------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class TestUserActiveGating:
|
|
313
|
+
"""Verify that auto-apply is deferred when the user is active.
|
|
314
|
+
|
|
315
|
+
Automatic checks always run so the settings window stays current.
|
|
316
|
+
Only the silent apply-and-restart is gated by ``_is_user_active``.
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
@staticmethod
|
|
320
|
+
def test_auto_check_always_runs() -> None:
|
|
321
|
+
"""_on_auto_check should call _do_check even when user is active."""
|
|
322
|
+
ctrl, _app, _client, banner, settings = _make_controller(is_user_active=True)
|
|
232
323
|
|
|
233
324
|
with patch.object(ctrl, '_do_check') as mock_check:
|
|
234
325
|
ctrl._on_auto_check()
|
|
@@ -318,6 +409,14 @@ class TestApplyUpdate:
|
|
|
318
409
|
client.apply_update_on_exit.assert_not_called()
|
|
319
410
|
app.quit.assert_not_called()
|
|
320
411
|
|
|
412
|
+
@staticmethod
|
|
413
|
+
def test_restart_requested_signal_triggers_apply() -> None:
|
|
414
|
+
"""The settings restart_requested signal should be connected to _apply_update."""
|
|
415
|
+
ctrl, app, client, banner, settings = _make_controller()
|
|
416
|
+
|
|
417
|
+
# Verify the signal was connected
|
|
418
|
+
settings.restart_requested.connect.assert_called_once_with(ctrl._apply_update)
|
|
419
|
+
|
|
321
420
|
|
|
322
421
|
# ---------------------------------------------------------------------------
|
|
323
422
|
# Settings changed → immediate check
|
|
@@ -36,7 +36,7 @@ class TestCli:
|
|
|
36
36
|
with patch('synodic_client.application.qt.application') as mock_app:
|
|
37
37
|
result = runner.invoke(app, [])
|
|
38
38
|
assert result.exit_code == 0
|
|
39
|
-
mock_app.assert_called_once_with(uri=None, dev_mode=False)
|
|
39
|
+
mock_app.assert_called_once_with(uri=None, dev_mode=False, debug=False)
|
|
40
40
|
|
|
41
41
|
@staticmethod
|
|
42
42
|
def test_launches_application_with_uri() -> None:
|
|
@@ -45,7 +45,7 @@ class TestCli:
|
|
|
45
45
|
with patch('synodic_client.application.qt.application') as mock_app:
|
|
46
46
|
result = runner.invoke(app, [test_uri])
|
|
47
47
|
assert result.exit_code == 0
|
|
48
|
-
mock_app.assert_called_once_with(uri=test_uri, dev_mode=False)
|
|
48
|
+
mock_app.assert_called_once_with(uri=test_uri, dev_mode=False, debug=False)
|
|
49
49
|
|
|
50
50
|
@staticmethod
|
|
51
51
|
def test_launches_application_with_dev_flag() -> None:
|
|
@@ -53,4 +53,4 @@ class TestCli:
|
|
|
53
53
|
with patch('synodic_client.application.qt.application') as mock_app:
|
|
54
54
|
result = runner.invoke(app, ['--dev'])
|
|
55
55
|
assert result.exit_code == 0
|
|
56
|
-
mock_app.assert_called_once_with(uri=None, dev_mode=True)
|
|
56
|
+
mock_app.assert_called_once_with(uri=None, dev_mode=True, debug=False)
|
|
@@ -70,7 +70,12 @@ class TestGithubReleaseAssetUrl:
|
|
|
70
70
|
@pytest.fixture
|
|
71
71
|
def updater() -> Updater:
|
|
72
72
|
"""Create an Updater instance for testing."""
|
|
73
|
-
|
|
73
|
+
u = Updater(current_version=Version('1.0.0'))
|
|
74
|
+
# Reset state cached by the eager _get_velopack_manager() call
|
|
75
|
+
# so each test can independently mock the Velopack SDK.
|
|
76
|
+
u._velopack_manager = None
|
|
77
|
+
u._velopack_not_installed = False
|
|
78
|
+
return u
|
|
74
79
|
|
|
75
80
|
|
|
76
81
|
@pytest.fixture
|
|
@@ -80,7 +85,10 @@ def updater_with_config() -> Updater:
|
|
|
80
85
|
repo_url='https://github.com/test/repo',
|
|
81
86
|
channel=UpdateChannel.DEVELOPMENT,
|
|
82
87
|
)
|
|
83
|
-
|
|
88
|
+
u = Updater(current_version=Version('1.0.0'), config=config)
|
|
89
|
+
u._velopack_manager = None
|
|
90
|
+
u._velopack_not_installed = False
|
|
91
|
+
return u
|
|
84
92
|
|
|
85
93
|
|
|
86
94
|
class TestUpdater:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/__init__.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/data.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/icon.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/init.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/instance.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/schema.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/card.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/theme.py
RENAMED
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/synodic_client/application/workers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_install_preview.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_preview_model.py
RENAMED
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_tray_window_show.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_update_banner.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_update_feedback.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/windows/test_protocol.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev52 → synodic_client-0.0.1.dev54}/tests/unit/windows/test_startup.py
RENAMED
|
File without changes
|