synodic-client 0.0.1.dev81__tar.gz → 0.0.1.dev83__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.dev81 → synodic_client-0.0.1.dev83}/PKG-INFO +2 -2
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/pyproject.toml +3 -3
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/tray.py +21 -4
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/schema.py +6 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/updater.py +92 -5
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_tray_window_show.py +30 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/test_updater.py +267 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/LICENSE.md +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/README.md +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/__init__.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/__main__.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/__init__.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/bootstrap.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/config_store.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/data.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/debug.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/icon.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/init.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/instance.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/package_state.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/qt.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/schema.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/__init__.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/action_card.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/card.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/install.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/install_workers.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/log_panel.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/plugin_row.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/projects.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/schema.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/screen.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/settings.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/sidebar.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/spinner.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/tool_update_controller.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/update_banner.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/wsl.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/theme.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/update_controller.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/update_model.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/uri.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/cli/__init__.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/cli/config.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/cli/context.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/cli/debug.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/cli/install.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/cli/output.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/cli/project.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/cli/tool.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/cli/update.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/client.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/config.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/logging.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/__init__.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/bootstrap.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/config.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/install.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/project.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/schema.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/tool.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/update.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/protocol.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/py.typed +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/resolution.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/startup.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/subprocess_patch.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/__init__.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/conftest.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/__init__.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/operations/__init__.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/operations/test_config.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/operations/test_install.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/operations/test_install_plan.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/operations/test_project.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/operations/test_tool.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/operations/test_update.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/__init__.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/conftest.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_action_card.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_gather_packages.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_install_preview.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_log_panel.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_logging.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_package_state.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_preview_model.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_settings.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_sidebar.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_update_banner.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_update_controller.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_update_feedback.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/test_bootstrap.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/test_cli.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/test_client_updater.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/test_client_version.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/test_config.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/test_examples.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/test_init.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/test_install.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/test_resolution.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/test_uri.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/test_workers.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/windows/__init__.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/windows/conftest.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/windows/test_protocol.py +0 -0
- {synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/windows/test_startup.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: synodic_client
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev83
|
|
4
4
|
Author-Email: Synodic Software <contact@synodic.software>
|
|
5
5
|
License: LGPL-3.0-or-later
|
|
6
6
|
Project-URL: homepage, https://github.com/synodic/synodic-client
|
|
7
7
|
Project-URL: repository, https://github.com/synodic/synodic-client
|
|
8
8
|
Requires-Python: <3.15,>=3.14
|
|
9
|
-
Requires-Dist: pyside6>=6.
|
|
9
|
+
Requires-Dist: pyside6>=6.11.0
|
|
10
10
|
Requires-Dist: packaging>=26.0
|
|
11
11
|
Requires-Dist: porringer>=0.2.1.dev88
|
|
12
12
|
Requires-Dist: qasync>=0.28.0
|
|
@@ -8,14 +8,14 @@ readme = "README.md"
|
|
|
8
8
|
dynamic = []
|
|
9
9
|
requires-python = ">=3.14, <3.15"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"pyside6>=6.
|
|
11
|
+
"pyside6>=6.11.0",
|
|
12
12
|
"packaging>=26.0",
|
|
13
13
|
"porringer>=0.2.1.dev88",
|
|
14
14
|
"qasync>=0.28.0",
|
|
15
15
|
"velopack>=0.0.1535.dev45597",
|
|
16
16
|
"typer>=0.24.1",
|
|
17
17
|
]
|
|
18
|
-
version = "0.0.1.
|
|
18
|
+
version = "0.0.1.dev83"
|
|
19
19
|
|
|
20
20
|
[project.license]
|
|
21
21
|
text = "LGPL-3.0-or-later"
|
|
@@ -36,7 +36,7 @@ build = [
|
|
|
36
36
|
]
|
|
37
37
|
lint = [
|
|
38
38
|
"ruff>=0.15.7",
|
|
39
|
-
"pyrefly>=0.
|
|
39
|
+
"pyrefly>=0.58.0",
|
|
40
40
|
]
|
|
41
41
|
test = [
|
|
42
42
|
"pytest>=9.0.2",
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/tray.py
RENAMED
|
@@ -4,7 +4,7 @@ import logging
|
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from PySide6.QtCore import QTimer
|
|
7
|
-
from PySide6.QtGui import QAction
|
|
7
|
+
from PySide6.QtGui import QAction, QCursor
|
|
8
8
|
from PySide6.QtWidgets import (
|
|
9
9
|
QApplication,
|
|
10
10
|
QMainWindow,
|
|
@@ -126,10 +126,10 @@ class TrayScreen:
|
|
|
126
126
|
self._menu.addSeparator()
|
|
127
127
|
|
|
128
128
|
self._quit_action = QAction('Quit', self._menu)
|
|
129
|
-
self._quit_action.triggered.connect(
|
|
129
|
+
self._quit_action.triggered.connect(self._on_quit_triggered)
|
|
130
130
|
self._menu.addAction(self._quit_action)
|
|
131
131
|
|
|
132
|
-
self.
|
|
132
|
+
self._menu.aboutToShow.connect(lambda: logger.debug('Tray context menu about to show'))
|
|
133
133
|
|
|
134
134
|
# Maximum number of tray-visibility retries at startup.
|
|
135
135
|
_TRAY_MAX_RETRIES = 5
|
|
@@ -162,12 +162,29 @@ class TrayScreen:
|
|
|
162
162
|
)
|
|
163
163
|
self.tray.setVisible(True)
|
|
164
164
|
|
|
165
|
+
# Delay before showing the context menu, in milliseconds.
|
|
166
|
+
# Absorbs residual mouse-up events from touchpad two-finger taps
|
|
167
|
+
# that would otherwise land on a menu item (typically "Quit").
|
|
168
|
+
_MENU_POPUP_DELAY_MS = 80
|
|
169
|
+
|
|
165
170
|
def _on_tray_activated(self, reason: QSystemTrayIcon.ActivationReason) -> None:
|
|
166
|
-
"""Handle tray icon activation
|
|
171
|
+
"""Handle tray icon activation."""
|
|
172
|
+
logger.debug('Tray activated: reason=%s', reason.name)
|
|
167
173
|
if reason == QSystemTrayIcon.ActivationReason.DoubleClick:
|
|
168
174
|
self._window.show()
|
|
169
175
|
self._window.raise_()
|
|
170
176
|
self._window.activateWindow()
|
|
177
|
+
elif reason == QSystemTrayIcon.ActivationReason.Context:
|
|
178
|
+
QTimer.singleShot(self._MENU_POPUP_DELAY_MS, self._show_tray_menu)
|
|
179
|
+
|
|
180
|
+
def _show_tray_menu(self) -> None:
|
|
181
|
+
"""Show the tray context menu at the current cursor position."""
|
|
182
|
+
self._menu.exec(QCursor.pos())
|
|
183
|
+
|
|
184
|
+
def _on_quit_triggered(self) -> None:
|
|
185
|
+
"""Handle the Quit menu action."""
|
|
186
|
+
logger.info('Quit requested via tray menu')
|
|
187
|
+
self._app.quit()
|
|
171
188
|
|
|
172
189
|
def _show_settings(self) -> None:
|
|
173
190
|
"""Show the settings window."""
|
|
@@ -151,6 +151,12 @@ class UpdateInfo:
|
|
|
151
151
|
# Internal: Velopack update info for download/apply
|
|
152
152
|
_velopack_info: Any = field(default=None, repr=False)
|
|
153
153
|
|
|
154
|
+
# Internal: True when the update was discovered via the manifest
|
|
155
|
+
# fallback rather than the Velopack SDK. The download path uses
|
|
156
|
+
# this to route to a direct HTTP download instead of the SDK's
|
|
157
|
+
# GithubSource (which cannot find prerelease assets).
|
|
158
|
+
_used_manifest_fallback: bool = field(default=False, repr=False)
|
|
159
|
+
|
|
154
160
|
|
|
155
161
|
# Default interval for automatic update checks (minutes)
|
|
156
162
|
DEFAULT_AUTO_UPDATE_INTERVAL_MINUTES = 5
|
|
@@ -8,11 +8,13 @@ For non-installed (development) environments, updates are not supported.
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import contextlib
|
|
11
|
+
import hashlib
|
|
11
12
|
import json
|
|
12
13
|
import logging
|
|
13
14
|
import sys
|
|
14
15
|
import urllib.request
|
|
15
16
|
from collections.abc import Callable
|
|
17
|
+
from pathlib import Path
|
|
16
18
|
from typing import Any
|
|
17
19
|
|
|
18
20
|
import velopack
|
|
@@ -175,12 +177,14 @@ class Updater:
|
|
|
175
177
|
error='Not installed via Velopack',
|
|
176
178
|
)
|
|
177
179
|
|
|
180
|
+
used_fallback = False
|
|
178
181
|
try:
|
|
179
182
|
velopack_info = manager.check_for_updates()
|
|
180
183
|
except Exception as sdk_err:
|
|
181
184
|
if '404' in str(sdk_err):
|
|
182
185
|
logger.debug('SDK check failed with 404, trying manifest fallback: %s', sdk_err)
|
|
183
186
|
velopack_info = self._check_manifest_fallback()
|
|
187
|
+
used_fallback = velopack_info is not None
|
|
184
188
|
else:
|
|
185
189
|
raise
|
|
186
190
|
|
|
@@ -188,6 +192,7 @@ class Updater:
|
|
|
188
192
|
# SDK returned no update; try the manual manifest fallback
|
|
189
193
|
# in case the SDK's GithubSource skipped prerelease entries.
|
|
190
194
|
velopack_info = self._check_manifest_fallback()
|
|
195
|
+
used_fallback = velopack_info is not None
|
|
191
196
|
|
|
192
197
|
if velopack_info is not None:
|
|
193
198
|
latest = Version(velopack_info.TargetFullRelease.Version)
|
|
@@ -197,6 +202,7 @@ class Updater:
|
|
|
197
202
|
current_version=self._current_version,
|
|
198
203
|
latest_version=latest,
|
|
199
204
|
_velopack_info=velopack_info,
|
|
205
|
+
_used_manifest_fallback=used_fallback,
|
|
200
206
|
)
|
|
201
207
|
# Only advance to UPDATE_AVAILABLE if we haven't already
|
|
202
208
|
# moved past it. A periodic re-check that discovers the
|
|
@@ -310,6 +316,85 @@ class Updater:
|
|
|
310
316
|
IsDowngrade=False,
|
|
311
317
|
)
|
|
312
318
|
|
|
319
|
+
def _download_direct(
|
|
320
|
+
self,
|
|
321
|
+
velopack_info: Any,
|
|
322
|
+
progress_callback: Callable[[int], None] | None = None,
|
|
323
|
+
) -> None:
|
|
324
|
+
"""Download the update package directly via HTTP.
|
|
325
|
+
|
|
326
|
+
Used when the update was discovered via ``_check_manifest_fallback``
|
|
327
|
+
instead of the Velopack SDK. The SDK's ``GithubSource`` cannot
|
|
328
|
+
download assets from prerelease GitHub Releases, so this method
|
|
329
|
+
fetches the ``.nupkg`` from the known GitHub Release asset URL
|
|
330
|
+
and places it in the Velopack packages directory where the SDK's
|
|
331
|
+
apply step expects to find it.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
velopack_info: A ``velopack.UpdateInfo`` whose
|
|
335
|
+
``TargetFullRelease`` describes the package to download.
|
|
336
|
+
progress_callback: Optional callback for percentage progress
|
|
337
|
+
(0–100).
|
|
338
|
+
|
|
339
|
+
Raises:
|
|
340
|
+
RuntimeError: If the packages directory cannot be determined,
|
|
341
|
+
the download fails, or the checksum does not match.
|
|
342
|
+
"""
|
|
343
|
+
asset = velopack_info.TargetFullRelease
|
|
344
|
+
asset_base = github_release_asset_url(self._config.repo_url, self._config.channel)
|
|
345
|
+
download_url = f'{asset_base}/{asset.FileName}'
|
|
346
|
+
|
|
347
|
+
# Velopack stores packages under ``{root}/packages/`` where
|
|
348
|
+
# ``{root}`` is the parent of the ``current/`` directory that
|
|
349
|
+
# contains the running executable.
|
|
350
|
+
packages_dir = Path(sys.executable).resolve().parent.parent / 'packages'
|
|
351
|
+
packages_dir.mkdir(parents=True, exist_ok=True)
|
|
352
|
+
|
|
353
|
+
target_file = packages_dir / asset.FileName
|
|
354
|
+
if target_file.exists():
|
|
355
|
+
logger.info('Package already exists, skipping download: %s', target_file)
|
|
356
|
+
return
|
|
357
|
+
|
|
358
|
+
partial_file = target_file.with_suffix('.partial')
|
|
359
|
+
|
|
360
|
+
logger.info('Direct download: %s -> %s', download_url, partial_file)
|
|
361
|
+
|
|
362
|
+
req = urllib.request.Request(download_url, headers={'User-Agent': 'synodic-client'})
|
|
363
|
+
with urllib.request.urlopen(req, timeout=300) as resp: # noqa: S310 — URL from known repo
|
|
364
|
+
total = int(resp.headers.get('Content-Length', 0))
|
|
365
|
+
sha256_hash = hashlib.sha256()
|
|
366
|
+
downloaded = 0
|
|
367
|
+
|
|
368
|
+
with partial_file.open('wb') as f:
|
|
369
|
+
while True:
|
|
370
|
+
chunk = resp.read(256 * 1024)
|
|
371
|
+
if not chunk:
|
|
372
|
+
break
|
|
373
|
+
f.write(chunk)
|
|
374
|
+
sha256_hash.update(chunk)
|
|
375
|
+
downloaded += len(chunk)
|
|
376
|
+
if progress_callback is not None and total > 0:
|
|
377
|
+
progress_callback(int(downloaded * 100 / total))
|
|
378
|
+
|
|
379
|
+
# Verify checksum — prefer SHA256, fall back to SHA1.
|
|
380
|
+
if asset.SHA256:
|
|
381
|
+
actual = sha256_hash.hexdigest()
|
|
382
|
+
if not actual.lower() == asset.SHA256.lower():
|
|
383
|
+
partial_file.unlink(missing_ok=True)
|
|
384
|
+
raise RuntimeError(
|
|
385
|
+
f'SHA256 mismatch for {asset.FileName}: expected {asset.SHA256}, got {actual}'
|
|
386
|
+
)
|
|
387
|
+
elif asset.SHA1:
|
|
388
|
+
actual_sha1 = hashlib.sha1(partial_file.read_bytes()).hexdigest() # noqa: S324 — verifying known digest
|
|
389
|
+
if not actual_sha1.lower() == asset.SHA1.lower():
|
|
390
|
+
partial_file.unlink(missing_ok=True)
|
|
391
|
+
raise RuntimeError(
|
|
392
|
+
f'SHA1 mismatch for {asset.FileName}: expected {asset.SHA1}, got {actual_sha1}'
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
partial_file.rename(target_file)
|
|
396
|
+
logger.info('Direct download complete: %s', target_file)
|
|
397
|
+
|
|
313
398
|
def download_update(self, progress_callback: Callable[[int], None] | None = None) -> bool:
|
|
314
399
|
"""Download the update.
|
|
315
400
|
|
|
@@ -334,11 +419,13 @@ class Updater:
|
|
|
334
419
|
logger.info('Starting update download for %s', self._update_info._velopack_info)
|
|
335
420
|
|
|
336
421
|
try:
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
422
|
+
if self._update_info._used_manifest_fallback:
|
|
423
|
+
self._download_direct(self._update_info._velopack_info, progress_callback)
|
|
424
|
+
else:
|
|
425
|
+
manager = self._get_velopack_manager()
|
|
426
|
+
if manager is None:
|
|
427
|
+
raise RuntimeError('Velopack manager not available')
|
|
428
|
+
manager.download_updates(self._update_info._velopack_info, progress_callback)
|
|
342
429
|
|
|
343
430
|
self._state = UpdateState.DOWNLOADED
|
|
344
431
|
logger.info('Update downloaded successfully')
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_tray_window_show.py
RENAMED
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from unittest.mock import MagicMock, patch
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
|
+
from PySide6.QtWidgets import QSystemTrayIcon
|
|
8
9
|
|
|
9
10
|
from synodic_client.application.screen.schema import UpdateTarget
|
|
10
11
|
from synodic_client.application.screen.tray import TrayScreen
|
|
@@ -69,3 +70,32 @@ class TestToolUpdateWindowShow:
|
|
|
69
70
|
result = UpdateResult(manifests_processed=1, already_latest=['pkg'])
|
|
70
71
|
tray_screen._tool_orchestrator._on_tool_update_finished(result)
|
|
71
72
|
tray_screen._window.show.assert_not_called()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TestTrayActivation:
|
|
76
|
+
"""_on_tray_activated should dispatch correctly by reason."""
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def test_double_click_shows_window(tray_screen) -> None:
|
|
80
|
+
"""Double-clicking the tray icon should show and raise the window."""
|
|
81
|
+
tray_screen._on_tray_activated(QSystemTrayIcon.ActivationReason.DoubleClick)
|
|
82
|
+
tray_screen._window.show.assert_called_once()
|
|
83
|
+
tray_screen._window.raise_.assert_called_once()
|
|
84
|
+
tray_screen._window.activateWindow.assert_called_once()
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def test_context_defers_menu_via_timer(tray_screen) -> None:
|
|
88
|
+
"""Right-clicking should defer the menu popup via a single-shot timer."""
|
|
89
|
+
with patch('synodic_client.application.screen.tray.QTimer') as mock_timer:
|
|
90
|
+
tray_screen._on_tray_activated(QSystemTrayIcon.ActivationReason.Context)
|
|
91
|
+
mock_timer.singleShot.assert_called_once()
|
|
92
|
+
delay, callback = mock_timer.singleShot.call_args[0]
|
|
93
|
+
assert delay == TrayScreen._MENU_POPUP_DELAY_MS
|
|
94
|
+
assert callback == tray_screen._show_tray_menu
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def test_context_does_not_show_window(tray_screen) -> None:
|
|
98
|
+
"""Right-clicking should not bring the main window forward."""
|
|
99
|
+
with patch('synodic_client.application.screen.tray.QTimer'):
|
|
100
|
+
tray_screen._on_tray_activated(QSystemTrayIcon.ActivationReason.Context)
|
|
101
|
+
tray_screen._window.show.assert_not_called()
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"""Tests for the self-update functionality using Velopack."""
|
|
2
2
|
|
|
3
|
+
import io
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
3
6
|
from unittest.mock import MagicMock, PropertyMock, patch
|
|
4
7
|
|
|
5
8
|
import pytest
|
|
@@ -597,3 +600,267 @@ class TestPep440ToSemver:
|
|
|
597
600
|
"""SemVer-style pre-release input is normalised via PEP 440."""
|
|
598
601
|
# packaging.version.Version normalises '0.1.0-dev.5' to '0.1.0.dev5'
|
|
599
602
|
assert pep440_to_semver('0.1.0-dev.5') == '0.1.0-dev.5'
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
# ---------------------------------------------------------------------------
|
|
606
|
+
# Realistic dev-channel manifest payload (mirrors the real GitHub Release).
|
|
607
|
+
# The ``dev`` tag on GitHub is marked ``"prerelease": true``, which
|
|
608
|
+
# Velopack's GithubSource filters out (it hard-codes prerelease=false).
|
|
609
|
+
# ---------------------------------------------------------------------------
|
|
610
|
+
|
|
611
|
+
_DEV_MANIFEST: dict[str, object] = {
|
|
612
|
+
'Assets': [
|
|
613
|
+
{
|
|
614
|
+
'PackageId': 'synodic',
|
|
615
|
+
'Version': '0.1.0-dev.83',
|
|
616
|
+
'Type': 'Full',
|
|
617
|
+
'FileName': 'synodic-0.1.0-dev.83-dev-win-full.nupkg',
|
|
618
|
+
'SHA1': 'aabbccdd',
|
|
619
|
+
'SHA256': '07badc6414dc5d87b009a7ecaa4ee446febe0d15275aa86465a9720762ceab80',
|
|
620
|
+
'Size': 66358200,
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
'PackageId': 'synodic',
|
|
624
|
+
'Version': '0.1.0-dev.83',
|
|
625
|
+
'Type': 'Delta',
|
|
626
|
+
'FileName': 'synodic-0.1.0-dev.83-dev-win-delta.nupkg',
|
|
627
|
+
'SHA1': '11223344',
|
|
628
|
+
'SHA256': '5305031a791e0de45f517eda0d2cf827951ec603380ba740fe58fbac4b6246cd',
|
|
629
|
+
'Size': 15560939,
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
'PackageId': 'synodic',
|
|
633
|
+
'Version': '0.1.0-dev.80',
|
|
634
|
+
'Type': 'Full',
|
|
635
|
+
'FileName': 'synodic-0.1.0-dev.80-dev-win-full.nupkg',
|
|
636
|
+
'SHA1': 'deadbeef',
|
|
637
|
+
'SHA256': '09bc2032a6a374e1d722cb3c42d1e586a9113c7fd6877721ad5e1ecb611dbbc3',
|
|
638
|
+
'Size': 65808335,
|
|
639
|
+
},
|
|
640
|
+
],
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def _make_urlopen_response(data: dict[str, object]) -> MagicMock:
|
|
645
|
+
"""Build a mock ``urlopen`` return value that reads as JSON."""
|
|
646
|
+
body = json.dumps(data).encode()
|
|
647
|
+
resp = MagicMock()
|
|
648
|
+
resp.read.return_value = body
|
|
649
|
+
resp.__enter__ = lambda s: s
|
|
650
|
+
resp.__exit__ = MagicMock(return_value=False)
|
|
651
|
+
resp.headers = {'Content-Length': str(len(body))}
|
|
652
|
+
return resp
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
@pytest.fixture
|
|
656
|
+
def dev_updater() -> Updater:
|
|
657
|
+
"""Create an Updater on the dev channel at version 0.1.0-dev.80."""
|
|
658
|
+
config = UpdateConfig(
|
|
659
|
+
repo_url=GITHUB_REPO_URL,
|
|
660
|
+
channel=UpdateChannel.DEVELOPMENT,
|
|
661
|
+
)
|
|
662
|
+
u = Updater(current_version=Version('0.1.0.dev80'), config=config)
|
|
663
|
+
u._velopack_manager = None
|
|
664
|
+
u._velopack_not_installed = False
|
|
665
|
+
return u
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
class TestDevChannelGithubPrerelease:
|
|
669
|
+
"""Regression tests: dev channel uses a GitHub prerelease that Velopack's
|
|
670
|
+
GithubSource silently ignores (``prerelease=false``).
|
|
671
|
+
|
|
672
|
+
The check path already has ``_check_manifest_fallback``. These tests
|
|
673
|
+
verify that the *download* path also works when the update was discovered
|
|
674
|
+
via the fallback.
|
|
675
|
+
"""
|
|
676
|
+
|
|
677
|
+
@staticmethod
|
|
678
|
+
def test_check_finds_update_via_manifest_fallback(dev_updater: Updater) -> None:
|
|
679
|
+
"""check_for_update discovers dev.83 via manifest fallback when SDK returns None."""
|
|
680
|
+
mock_manager = MagicMock(spec=velopack.UpdateManager)
|
|
681
|
+
# SDK's GithubSource filters prereleases → returns None
|
|
682
|
+
mock_manager.check_for_updates.return_value = None
|
|
683
|
+
|
|
684
|
+
manifest_resp = _make_urlopen_response(_DEV_MANIFEST)
|
|
685
|
+
|
|
686
|
+
with (
|
|
687
|
+
patch.object(dev_updater, '_get_velopack_manager', return_value=mock_manager),
|
|
688
|
+
patch('synodic_client.updater.urllib.request.urlopen', return_value=manifest_resp),
|
|
689
|
+
):
|
|
690
|
+
info = dev_updater.check_for_update()
|
|
691
|
+
|
|
692
|
+
assert info.available is True
|
|
693
|
+
assert info.latest_version == Version('0.1.0.dev83')
|
|
694
|
+
assert dev_updater.state == UpdateState.UPDATE_AVAILABLE
|
|
695
|
+
|
|
696
|
+
@staticmethod
|
|
697
|
+
def test_check_sets_manifest_fallback_flag(dev_updater: Updater) -> None:
|
|
698
|
+
"""check_for_update sets _used_manifest_fallback when fallback discovered the update."""
|
|
699
|
+
mock_manager = MagicMock(spec=velopack.UpdateManager)
|
|
700
|
+
mock_manager.check_for_updates.return_value = None
|
|
701
|
+
|
|
702
|
+
manifest_resp = _make_urlopen_response(_DEV_MANIFEST)
|
|
703
|
+
|
|
704
|
+
with (
|
|
705
|
+
patch.object(dev_updater, '_get_velopack_manager', return_value=mock_manager),
|
|
706
|
+
patch('synodic_client.updater.urllib.request.urlopen', return_value=manifest_resp),
|
|
707
|
+
):
|
|
708
|
+
info = dev_updater.check_for_update()
|
|
709
|
+
|
|
710
|
+
assert info._used_manifest_fallback is True
|
|
711
|
+
|
|
712
|
+
@staticmethod
|
|
713
|
+
def test_check_sdk_success_does_not_set_fallback_flag(dev_updater: Updater) -> None:
|
|
714
|
+
"""_used_manifest_fallback stays False when the SDK itself found the update."""
|
|
715
|
+
mock_target = MagicMock(spec=velopack.VelopackAsset)
|
|
716
|
+
mock_target.Version = '0.1.0-dev.83'
|
|
717
|
+
mock_velopack_info = MagicMock(spec=velopack.UpdateInfo)
|
|
718
|
+
mock_velopack_info.TargetFullRelease = mock_target
|
|
719
|
+
|
|
720
|
+
mock_manager = MagicMock(spec=velopack.UpdateManager)
|
|
721
|
+
mock_manager.check_for_updates.return_value = mock_velopack_info
|
|
722
|
+
|
|
723
|
+
with patch.object(dev_updater, '_get_velopack_manager', return_value=mock_manager):
|
|
724
|
+
info = dev_updater.check_for_update()
|
|
725
|
+
|
|
726
|
+
assert info.available is True
|
|
727
|
+
assert info._used_manifest_fallback is False
|
|
728
|
+
|
|
729
|
+
@staticmethod
|
|
730
|
+
def test_download_succeeds_after_manifest_fallback(dev_updater: Updater) -> None:
|
|
731
|
+
"""download_update routes to _download_direct when the manifest fallback was used."""
|
|
732
|
+
mock_manager = MagicMock(spec=velopack.UpdateManager)
|
|
733
|
+
mock_manager.check_for_updates.return_value = None
|
|
734
|
+
|
|
735
|
+
manifest_resp = _make_urlopen_response(_DEV_MANIFEST)
|
|
736
|
+
|
|
737
|
+
with (
|
|
738
|
+
patch.object(dev_updater, '_get_velopack_manager', return_value=mock_manager),
|
|
739
|
+
patch.object(Updater, 'is_installed', new_callable=PropertyMock, return_value=True),
|
|
740
|
+
patch('synodic_client.updater.urllib.request.urlopen', return_value=manifest_resp),
|
|
741
|
+
):
|
|
742
|
+
info = dev_updater.check_for_update()
|
|
743
|
+
assert info.available is True
|
|
744
|
+
assert info._used_manifest_fallback is True
|
|
745
|
+
|
|
746
|
+
# Now mock the direct download — _download_direct is called instead of
|
|
747
|
+
# manager.download_updates, so the SDK never touches GithubSource.
|
|
748
|
+
with (
|
|
749
|
+
patch.object(Updater, 'is_installed', new_callable=PropertyMock, return_value=True),
|
|
750
|
+
patch.object(dev_updater, '_download_direct') as mock_direct,
|
|
751
|
+
):
|
|
752
|
+
result = dev_updater.download_update()
|
|
753
|
+
|
|
754
|
+
assert result is True
|
|
755
|
+
assert dev_updater.state == UpdateState.DOWNLOADED
|
|
756
|
+
mock_direct.assert_called_once_with(info._velopack_info, None)
|
|
757
|
+
# The SDK's download_updates should NOT have been called
|
|
758
|
+
mock_manager.download_updates.assert_not_called()
|
|
759
|
+
|
|
760
|
+
@staticmethod
|
|
761
|
+
def test_sdk_download_used_when_sdk_found_update(dev_updater: Updater) -> None:
|
|
762
|
+
"""download_update uses the SDK when the update was found without the fallback."""
|
|
763
|
+
mock_target = MagicMock(spec=velopack.VelopackAsset)
|
|
764
|
+
mock_target.Version = '0.1.0-dev.83'
|
|
765
|
+
mock_velopack_info = MagicMock(spec=velopack.UpdateInfo)
|
|
766
|
+
mock_velopack_info.TargetFullRelease = mock_target
|
|
767
|
+
|
|
768
|
+
mock_manager = MagicMock(spec=velopack.UpdateManager)
|
|
769
|
+
mock_manager.check_for_updates.return_value = mock_velopack_info
|
|
770
|
+
|
|
771
|
+
with (
|
|
772
|
+
patch.object(dev_updater, '_get_velopack_manager', return_value=mock_manager),
|
|
773
|
+
patch.object(Updater, 'is_installed', new_callable=PropertyMock, return_value=True),
|
|
774
|
+
):
|
|
775
|
+
info = dev_updater.check_for_update()
|
|
776
|
+
assert info._used_manifest_fallback is False
|
|
777
|
+
|
|
778
|
+
result = dev_updater.download_update()
|
|
779
|
+
|
|
780
|
+
assert result is True
|
|
781
|
+
assert dev_updater.state == UpdateState.DOWNLOADED
|
|
782
|
+
mock_manager.download_updates.assert_called_once_with(mock_velopack_info, None)
|
|
783
|
+
|
|
784
|
+
@staticmethod
|
|
785
|
+
def test_download_direct_constructs_correct_url(dev_updater: Updater, tmp_path: Path) -> None:
|
|
786
|
+
"""_download_direct fetches from the correct GitHub release asset URL."""
|
|
787
|
+
mock_velopack_info = MagicMock()
|
|
788
|
+
mock_velopack_info.TargetFullRelease.FileName = 'synodic-0.1.0-dev.83-dev-win-full.nupkg'
|
|
789
|
+
mock_velopack_info.TargetFullRelease.SHA256 = ''
|
|
790
|
+
mock_velopack_info.TargetFullRelease.SHA1 = ''
|
|
791
|
+
mock_velopack_info.TargetFullRelease.Size = 0
|
|
792
|
+
|
|
793
|
+
nupkg_content = b'fake-nupkg-content'
|
|
794
|
+
resp = MagicMock()
|
|
795
|
+
resp.read.side_effect = [nupkg_content, b'']
|
|
796
|
+
resp.__enter__ = lambda s: s
|
|
797
|
+
resp.__exit__ = MagicMock(return_value=False)
|
|
798
|
+
resp.headers = {'Content-Length': str(len(nupkg_content))}
|
|
799
|
+
|
|
800
|
+
with (
|
|
801
|
+
patch('synodic_client.updater.sys') as mock_sys,
|
|
802
|
+
patch('synodic_client.updater.urllib.request.urlopen', return_value=resp) as mock_urlopen,
|
|
803
|
+
):
|
|
804
|
+
mock_sys.executable = str(tmp_path / 'current' / 'synodic.exe')
|
|
805
|
+
(tmp_path / 'current').mkdir()
|
|
806
|
+
|
|
807
|
+
dev_updater._download_direct(mock_velopack_info)
|
|
808
|
+
|
|
809
|
+
# Verify the URL
|
|
810
|
+
call_args = mock_urlopen.call_args
|
|
811
|
+
req = call_args[0][0]
|
|
812
|
+
expected_url = (
|
|
813
|
+
f'{GITHUB_REPO_URL}/releases/download/dev'
|
|
814
|
+
'/synodic-0.1.0-dev.83-dev-win-full.nupkg'
|
|
815
|
+
)
|
|
816
|
+
assert req.full_url == expected_url
|
|
817
|
+
|
|
818
|
+
# Verify the file was written
|
|
819
|
+
target_file = tmp_path / 'packages' / 'synodic-0.1.0-dev.83-dev-win-full.nupkg'
|
|
820
|
+
assert target_file.exists()
|
|
821
|
+
assert target_file.read_bytes() == nupkg_content
|
|
822
|
+
|
|
823
|
+
@staticmethod
|
|
824
|
+
def test_download_direct_sha256_mismatch(dev_updater: Updater, tmp_path: Path) -> None:
|
|
825
|
+
"""_download_direct raises on SHA256 mismatch and cleans up the partial."""
|
|
826
|
+
mock_velopack_info = MagicMock()
|
|
827
|
+
mock_velopack_info.TargetFullRelease.FileName = 'test.nupkg'
|
|
828
|
+
mock_velopack_info.TargetFullRelease.SHA256 = 'wrong_hash'
|
|
829
|
+
mock_velopack_info.TargetFullRelease.SHA1 = ''
|
|
830
|
+
mock_velopack_info.TargetFullRelease.Size = 0
|
|
831
|
+
|
|
832
|
+
resp = MagicMock()
|
|
833
|
+
resp.read.side_effect = [b'some content', b'']
|
|
834
|
+
resp.__enter__ = lambda s: s
|
|
835
|
+
resp.__exit__ = MagicMock(return_value=False)
|
|
836
|
+
resp.headers = {'Content-Length': '12'}
|
|
837
|
+
|
|
838
|
+
with (
|
|
839
|
+
patch('synodic_client.updater.sys') as mock_sys,
|
|
840
|
+
patch('synodic_client.updater.urllib.request.urlopen', return_value=resp),
|
|
841
|
+
pytest.raises(RuntimeError, match='SHA256 mismatch'),
|
|
842
|
+
):
|
|
843
|
+
mock_sys.executable = str(tmp_path / 'current' / 'synodic.exe')
|
|
844
|
+
(tmp_path / 'current').mkdir()
|
|
845
|
+
|
|
846
|
+
dev_updater._download_direct(mock_velopack_info)
|
|
847
|
+
|
|
848
|
+
# Partial file should be cleaned up
|
|
849
|
+
assert not (tmp_path / 'packages' / 'test.nupkg.partial').exists()
|
|
850
|
+
assert not (tmp_path / 'packages' / 'test.nupkg').exists()
|
|
851
|
+
|
|
852
|
+
@staticmethod
|
|
853
|
+
def test_download_direct_skips_existing(dev_updater: Updater, tmp_path: Path) -> None:
|
|
854
|
+
"""_download_direct skips download if the package already exists on disk."""
|
|
855
|
+
mock_velopack_info = MagicMock()
|
|
856
|
+
mock_velopack_info.TargetFullRelease.FileName = 'already-there.nupkg'
|
|
857
|
+
|
|
858
|
+
with patch('synodic_client.updater.sys') as mock_sys:
|
|
859
|
+
mock_sys.executable = str(tmp_path / 'current' / 'synodic.exe')
|
|
860
|
+
(tmp_path / 'current').mkdir()
|
|
861
|
+
packages = tmp_path / 'packages'
|
|
862
|
+
packages.mkdir()
|
|
863
|
+
(packages / 'already-there.nupkg').write_bytes(b'existing')
|
|
864
|
+
|
|
865
|
+
# Should not hit network at all
|
|
866
|
+
dev_updater._download_direct(mock_velopack_info)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/__init__.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/bootstrap.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/config_store.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/data.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/debug.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/icon.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/init.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/instance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/schema.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/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
|
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/screen/wsl.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/theme.py
RENAMED
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/application/update_model.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.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/__init__.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/bootstrap.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/config.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/install.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/project.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/schema.py
RENAMED
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/operations/update.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/synodic_client/subprocess_patch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/operations/test_config.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/operations/test_install.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/operations/test_install_plan.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/operations/test_project.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/operations/test_tool.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/operations/test_update.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_gather_packages.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_install_preview.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_package_state.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_preview_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_update_banner.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/qt/test_update_controller.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/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.dev81 → synodic_client-0.0.1.dev83}/tests/unit/windows/test_protocol.py
RENAMED
|
File without changes
|
{synodic_client-0.0.1.dev81 → synodic_client-0.0.1.dev83}/tests/unit/windows/test_startup.py
RENAMED
|
File without changes
|