synodic-client 0.0.1.dev53__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.dev53 → synodic_client-0.0.1.dev54}/PKG-INFO +1 -1
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/pyproject.toml +1 -1
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/update_controller.py +18 -4
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_update_controller.py +75 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/LICENSE.md +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/README.md +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/__init__.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/__main__.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/__init__.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/bootstrap.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/data.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/icon.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/init.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/instance.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/qt.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/schema.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/__init__.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/action_card.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/card.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/install.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/install_workers.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/log_panel.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/plugin_row.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/projects.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/schema.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/screen.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/settings.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/sidebar.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/spinner.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/tool_update_controller.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/tray.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/update_banner.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/theme.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/uri.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/workers.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/cli.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/client.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/config.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/logging.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/protocol.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/py.typed +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/resolution.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/schema.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/startup.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/updater.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/__init__.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/conftest.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/__init__.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/__init__.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/conftest.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_action_card.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_gather_packages.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_install_preview.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_log_panel.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_logging.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_preview_model.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_settings.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_sidebar.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_tray_window_show.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_update_banner.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_update_feedback.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/test_cli.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/test_client_updater.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/test_client_version.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/test_config.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/test_examples.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/test_init.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/test_install.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/test_resolution.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/test_updater.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/test_uri.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/test_workers.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/windows/__init__.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/windows/conftest.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/windows/test_protocol.py +0 -0
- {synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/windows/test_startup.py +0 -0
|
@@ -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()
|
|
@@ -214,8 +215,9 @@ class UpdateController:
|
|
|
214
215
|
self._banner.show_error('Updater is not initialized.')
|
|
215
216
|
return
|
|
216
217
|
|
|
217
|
-
#
|
|
218
|
-
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()
|
|
219
221
|
|
|
220
222
|
self._update_task = asyncio.create_task(self._async_check(silent=silent))
|
|
221
223
|
|
|
@@ -258,8 +260,14 @@ class UpdateController:
|
|
|
258
260
|
logger.debug('Automatic update check: no update available')
|
|
259
261
|
return
|
|
260
262
|
|
|
261
|
-
# Update available — show status and start download
|
|
262
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
|
|
263
271
|
self._settings_window.set_update_status(
|
|
264
272
|
f'v{version} available',
|
|
265
273
|
UPDATE_STATUS_AVAILABLE_STYLE,
|
|
@@ -307,6 +315,8 @@ class UpdateController:
|
|
|
307
315
|
# Persist the client update timestamp
|
|
308
316
|
update_user_config(last_client_update=datetime.now(UTC).isoformat())
|
|
309
317
|
|
|
318
|
+
self._pending_version = version
|
|
319
|
+
|
|
310
320
|
if self._can_auto_apply():
|
|
311
321
|
# Silently apply and restart — no banner, no user interaction
|
|
312
322
|
logger.info('Auto-applying update v%s', version)
|
|
@@ -317,7 +327,10 @@ class UpdateController:
|
|
|
317
327
|
self._apply_update(silent=True)
|
|
318
328
|
return
|
|
319
329
|
|
|
320
|
-
|
|
330
|
+
self._show_ready(version)
|
|
331
|
+
|
|
332
|
+
def _show_ready(self, version: str) -> None:
|
|
333
|
+
"""Present the *ready to restart* state in both UIs."""
|
|
321
334
|
self._banner.show_ready(version)
|
|
322
335
|
self._settings_window.set_update_status(
|
|
323
336
|
f'v{version} ready',
|
|
@@ -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()
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_update_controller.py
RENAMED
|
@@ -228,6 +228,81 @@ class TestDownloadFinished:
|
|
|
228
228
|
assert banner.state.name == 'ERROR'
|
|
229
229
|
settings.set_update_status.assert_called_with('Download failed', UPDATE_STATUS_ERROR_STYLE)
|
|
230
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
|
+
|
|
239
|
+
|
|
240
|
+
# ---------------------------------------------------------------------------
|
|
241
|
+
# Pending version — skip redundant downloads
|
|
242
|
+
# ---------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class TestPendingVersion:
|
|
246
|
+
"""Verify behaviour when an update is already downloaded and pending."""
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
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'
|
|
253
|
+
|
|
254
|
+
result = UpdateInfo(available=True, current_version=Version('1.0.0'), latest_version=Version('2.0.0'))
|
|
255
|
+
|
|
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()
|
|
262
|
+
|
|
263
|
+
@staticmethod
|
|
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
|
+
|
|
231
306
|
|
|
232
307
|
# ---------------------------------------------------------------------------
|
|
233
308
|
# User-active gating
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/__init__.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/bootstrap.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/data.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/icon.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/init.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/instance.py
RENAMED
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev53 → 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.dev53 → 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
|
|
File without changes
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/screen/tray.py
RENAMED
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/synodic_client/application/theme.py
RENAMED
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev53 → 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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_gather_packages.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev53 → 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.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_preview_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_tray_window_show.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/qt/test_update_banner.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev53 → 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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/windows/test_protocol.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev53 → synodic_client-0.0.1.dev54}/tests/unit/windows/test_startup.py
RENAMED
|
File without changes
|