synodic-client 0.0.1.dev33__tar.gz → 0.0.1.dev35__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.dev33 → synodic_client-0.0.1.dev35}/PKG-INFO +2 -2
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/pyproject.toml +2 -2
- synodic_client-0.0.1.dev35/synodic_client/_version.py +1 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/bootstrap.py +5 -2
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/qt.py +15 -7
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/screen/action_card.py +17 -1
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/screen/install.py +13 -10
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/screen/screen.py +14 -19
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/screen/settings.py +34 -38
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/screen/tray.py +16 -10
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/config.py +67 -60
- synodic_client-0.0.1.dev35/synodic_client/resolution.py +265 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/updater.py +17 -1
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/qt/test_settings.py +47 -32
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/test_config.py +45 -52
- synodic_client-0.0.1.dev35/tests/unit/test_resolution.py +407 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/test_updater.py +20 -0
- synodic_client-0.0.1.dev33/synodic_client/_version.py +0 -1
- synodic_client-0.0.1.dev33/synodic_client/resolution.py +0 -166
- synodic_client-0.0.1.dev33/tests/unit/test_resolution.py +0 -414
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/LICENSE.md +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/README.md +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/__init__.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/__main__.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/__init__.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/icon.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/instance.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/screen/__init__.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/screen/card.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/screen/log_panel.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/screen/spinner.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/theme.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/uri.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/workers.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/cli.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/client.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/logging.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/protocol.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/py.typed +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/startup.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/__init__.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/conftest.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/__init__.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/qt/__init__.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/qt/conftest.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/qt/test_action_card.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/qt/test_install_preview.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/qt/test_log_panel.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/qt/test_logging.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/test_cli.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/test_client_updater.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/test_client_version.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/test_examples.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/test_install.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/test_uri.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/windows/__init__.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/windows/conftest.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/windows/test_protocol.py +0 -0
- {synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/tests/unit/windows/test_startup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: synodic_client
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev35
|
|
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
|
|
@@ -8,7 +8,7 @@ Project-URL: repository, https://github.com/synodic/synodic-client
|
|
|
8
8
|
Requires-Python: <3.15,>=3.14
|
|
9
9
|
Requires-Dist: pyside6>=6.10.2
|
|
10
10
|
Requires-Dist: packaging>=26.0
|
|
11
|
-
Requires-Dist: porringer>=0.2.1.
|
|
11
|
+
Requires-Dist: porringer>=0.2.1.dev52
|
|
12
12
|
Requires-Dist: qasync>=0.28.0
|
|
13
13
|
Requires-Dist: velopack>=0.0.1442.dev64255
|
|
14
14
|
Requires-Dist: typer>=0.24.1
|
|
@@ -10,12 +10,12 @@ requires-python = ">=3.14, <3.15"
|
|
|
10
10
|
dependencies = [
|
|
11
11
|
"pyside6>=6.10.2",
|
|
12
12
|
"packaging>=26.0",
|
|
13
|
-
"porringer>=0.2.1.
|
|
13
|
+
"porringer>=0.2.1.dev52",
|
|
14
14
|
"qasync>=0.28.0",
|
|
15
15
|
"velopack>=0.0.1442.dev64255",
|
|
16
16
|
"typer>=0.24.1",
|
|
17
17
|
]
|
|
18
|
-
version = "0.0.1.
|
|
18
|
+
version = "0.0.1.dev35"
|
|
19
19
|
|
|
20
20
|
[project.license]
|
|
21
21
|
text = "LGPL-3.0-or-later"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.0.1.dev35'
|
{synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/bootstrap.py
RENAMED
|
@@ -18,7 +18,7 @@ import sys
|
|
|
18
18
|
from synodic_client.config import set_dev_mode
|
|
19
19
|
from synodic_client.logging import configure_logging
|
|
20
20
|
from synodic_client.protocol import register_protocol
|
|
21
|
-
from synodic_client.resolution import
|
|
21
|
+
from synodic_client.resolution import resolve_config, seed_user_config_from_build
|
|
22
22
|
from synodic_client.startup import register_startup, remove_startup
|
|
23
23
|
from synodic_client.updater import initialize_velopack
|
|
24
24
|
|
|
@@ -32,10 +32,13 @@ configure_logging()
|
|
|
32
32
|
initialize_velopack()
|
|
33
33
|
|
|
34
34
|
if not _dev_mode:
|
|
35
|
+
# Seed user config from the build config (one-time propagation).
|
|
36
|
+
seed_user_config_from_build()
|
|
37
|
+
|
|
35
38
|
register_protocol(sys.executable)
|
|
36
39
|
|
|
37
40
|
_config = resolve_config()
|
|
38
|
-
if
|
|
41
|
+
if _config.auto_start:
|
|
39
42
|
register_startup(sys.executable)
|
|
40
43
|
else:
|
|
41
44
|
remove_startup()
|
|
@@ -21,15 +21,21 @@ from synodic_client.application.screen.screen import Screen
|
|
|
21
21
|
from synodic_client.application.screen.tray import TrayScreen
|
|
22
22
|
from synodic_client.application.uri import parse_uri
|
|
23
23
|
from synodic_client.client import Client
|
|
24
|
-
from synodic_client.config import
|
|
24
|
+
from synodic_client.config import set_dev_mode
|
|
25
25
|
from synodic_client.logging import configure_logging
|
|
26
26
|
from synodic_client.protocol import register_protocol
|
|
27
|
-
from synodic_client.resolution import
|
|
27
|
+
from synodic_client.resolution import (
|
|
28
|
+
ResolvedConfig,
|
|
29
|
+
resolve_config,
|
|
30
|
+
resolve_update_config,
|
|
31
|
+
resolve_version,
|
|
32
|
+
seed_user_config_from_build,
|
|
33
|
+
)
|
|
28
34
|
from synodic_client.startup import register_startup, remove_startup
|
|
29
35
|
from synodic_client.updater import initialize_velopack
|
|
30
36
|
|
|
31
37
|
|
|
32
|
-
def _init_services(logger: logging.Logger) -> tuple[Client, API,
|
|
38
|
+
def _init_services(logger: logging.Logger) -> tuple[Client, API, ResolvedConfig]:
|
|
33
39
|
"""Create and configure core services.
|
|
34
40
|
|
|
35
41
|
Returns:
|
|
@@ -47,11 +53,10 @@ def _init_services(logger: logging.Logger) -> tuple[Client, API, GlobalConfigura
|
|
|
47
53
|
cached_dirs = porringer.cache.list_directories()
|
|
48
54
|
|
|
49
55
|
logger.info(
|
|
50
|
-
'Synodic Client v%s started (channel: %s, source: %s,
|
|
51
|
-
client
|
|
56
|
+
'Synodic Client v%s started (channel: %s, source: %s, cached_projects: %d)',
|
|
57
|
+
resolve_version(client),
|
|
52
58
|
update_config.channel.name,
|
|
53
59
|
update_config.repo_url,
|
|
54
|
-
sorted(config.model_fields_set),
|
|
55
60
|
len(cached_dirs),
|
|
56
61
|
)
|
|
57
62
|
|
|
@@ -140,8 +145,11 @@ def application(*, uri: str | None = None, dev_mode: bool = False) -> None:
|
|
|
140
145
|
initialize_velopack()
|
|
141
146
|
register_protocol(sys.executable)
|
|
142
147
|
|
|
148
|
+
# Seed user config from build config (one-time propagation).
|
|
149
|
+
seed_user_config_from_build()
|
|
150
|
+
|
|
143
151
|
startup_config = resolve_config()
|
|
144
|
-
if
|
|
152
|
+
if startup_config.auto_start:
|
|
145
153
|
register_startup(sys.executable)
|
|
146
154
|
else:
|
|
147
155
|
remove_startup()
|
|
@@ -297,6 +297,9 @@ class ActionCard(QFrame):
|
|
|
297
297
|
|
|
298
298
|
self._package_label = QLabel()
|
|
299
299
|
self._package_label.setStyleSheet(ACTION_CARD_PACKAGE_STYLE)
|
|
300
|
+
self._package_label.setTextInteractionFlags(
|
|
301
|
+
Qt.TextInteractionFlag.TextSelectableByMouse,
|
|
302
|
+
)
|
|
300
303
|
top.addWidget(self._package_label)
|
|
301
304
|
|
|
302
305
|
top.addStretch()
|
|
@@ -327,6 +330,9 @@ class ActionCard(QFrame):
|
|
|
327
330
|
self._desc_label = QLabel()
|
|
328
331
|
self._desc_label.setStyleSheet(ACTION_CARD_DESC_STYLE)
|
|
329
332
|
self._desc_label.setWordWrap(True)
|
|
333
|
+
self._desc_label.setTextInteractionFlags(
|
|
334
|
+
Qt.TextInteractionFlag.TextSelectableByMouse,
|
|
335
|
+
)
|
|
330
336
|
return self._desc_label
|
|
331
337
|
|
|
332
338
|
def _build_command_row(self) -> QWidget:
|
|
@@ -377,9 +383,13 @@ class ActionCard(QFrame):
|
|
|
377
383
|
"""Toggle the inline log body on click."""
|
|
378
384
|
if self._is_skeleton or not hasattr(self, '_log_output'):
|
|
379
385
|
return
|
|
380
|
-
# Don't toggle the log when clicking
|
|
386
|
+
# Don't toggle the log when clicking interactive child widgets
|
|
381
387
|
if hasattr(self, '_copy_btn') and self._copy_btn.underMouse():
|
|
382
388
|
return
|
|
389
|
+
if hasattr(self, '_package_label') and self._package_label.underMouse():
|
|
390
|
+
return
|
|
391
|
+
if hasattr(self, '_desc_label') and self._desc_label.underMouse():
|
|
392
|
+
return
|
|
383
393
|
self._toggle_log()
|
|
384
394
|
|
|
385
395
|
def _toggle_log(self) -> None:
|
|
@@ -560,6 +570,12 @@ class ActionCard(QFrame):
|
|
|
560
570
|
self._status_label.setText(label)
|
|
561
571
|
self._status_label.setStyleSheet(ACTION_CARD_STATUS_NEEDED)
|
|
562
572
|
|
|
573
|
+
# Surface diagnostic detail (e.g. SCM URL mismatch) as a tooltip
|
|
574
|
+
if result.message:
|
|
575
|
+
self._status_label.setToolTip(result.message)
|
|
576
|
+
else:
|
|
577
|
+
self._status_label.setToolTip('')
|
|
578
|
+
|
|
563
579
|
# Version column
|
|
564
580
|
self._check_available_version = result.available_version
|
|
565
581
|
if result.installed_version and result.available_version:
|
|
@@ -72,7 +72,7 @@ from synodic_client.application.theme import (
|
|
|
72
72
|
MUTED_STYLE,
|
|
73
73
|
NO_MARGINS,
|
|
74
74
|
)
|
|
75
|
-
from synodic_client.
|
|
75
|
+
from synodic_client.resolution import ResolvedConfig, update_user_config
|
|
76
76
|
|
|
77
77
|
logger = logging.getLogger(__name__)
|
|
78
78
|
|
|
@@ -332,7 +332,7 @@ class SetupPreviewWidget(QWidget):
|
|
|
332
332
|
parent: QWidget | None = None,
|
|
333
333
|
*,
|
|
334
334
|
show_close: bool = True,
|
|
335
|
-
config:
|
|
335
|
+
config: ResolvedConfig | None = None,
|
|
336
336
|
) -> None:
|
|
337
337
|
"""Initialize the preview widget.
|
|
338
338
|
|
|
@@ -579,14 +579,14 @@ class SetupPreviewWidget(QWidget):
|
|
|
579
579
|
if self._config is None or self._manifest_key is None:
|
|
580
580
|
return
|
|
581
581
|
|
|
582
|
-
pkgs = self._config.prerelease_packages or {}
|
|
582
|
+
pkgs = dict(self._config.prerelease_packages or {})
|
|
583
583
|
if self._prerelease_overrides:
|
|
584
584
|
pkgs[self._manifest_key] = sorted(self._prerelease_overrides)
|
|
585
585
|
else:
|
|
586
586
|
pkgs.pop(self._manifest_key, None)
|
|
587
587
|
|
|
588
|
-
|
|
589
|
-
|
|
588
|
+
new_value = pkgs if pkgs else None
|
|
589
|
+
self._config = update_user_config(prerelease_packages=new_value)
|
|
590
590
|
logger.info('Pre-release overrides for %s: %s', self._manifest_key, self._prerelease_overrides)
|
|
591
591
|
|
|
592
592
|
if not self._installing:
|
|
@@ -918,7 +918,7 @@ class InstallPreviewWindow(QMainWindow):
|
|
|
918
918
|
manifest_url: str,
|
|
919
919
|
parent: QWidget | None = None,
|
|
920
920
|
*,
|
|
921
|
-
config:
|
|
921
|
+
config: ResolvedConfig | None = None,
|
|
922
922
|
) -> None:
|
|
923
923
|
"""Initialize the install preview window.
|
|
924
924
|
|
|
@@ -926,13 +926,13 @@ class InstallPreviewWindow(QMainWindow):
|
|
|
926
926
|
porringer: The porringer API instance.
|
|
927
927
|
manifest_url: The URL of the manifest to install.
|
|
928
928
|
parent: Optional parent widget.
|
|
929
|
-
config: Resolved
|
|
929
|
+
config: Resolved configuration for per-manifest pre-release
|
|
930
930
|
state and update detection flags.
|
|
931
931
|
"""
|
|
932
932
|
super().__init__(parent)
|
|
933
933
|
self._porringer = porringer
|
|
934
934
|
self._manifest_url = manifest_url
|
|
935
|
-
self._config = config
|
|
935
|
+
self._config = config
|
|
936
936
|
self._temp_dir_path: str | None = None
|
|
937
937
|
self._runner: QThread | None = None
|
|
938
938
|
|
|
@@ -1038,13 +1038,16 @@ class InstallPreviewWindow(QMainWindow):
|
|
|
1038
1038
|
self._preview_widget.set_manifest_key(self._manifest_url)
|
|
1039
1039
|
|
|
1040
1040
|
manifest_key = normalize_manifest_key(self._manifest_url)
|
|
1041
|
-
|
|
1041
|
+
config = self._config
|
|
1042
|
+
if config is None:
|
|
1043
|
+
return
|
|
1044
|
+
overrides = set((config.prerelease_packages or {}).get(manifest_key, []))
|
|
1042
1045
|
|
|
1043
1046
|
preview_worker = PreviewWorker(
|
|
1044
1047
|
self._porringer,
|
|
1045
1048
|
self._manifest_url,
|
|
1046
1049
|
project_directory=self._project_directory,
|
|
1047
|
-
detect_updates=
|
|
1050
|
+
detect_updates=config.detect_updates,
|
|
1048
1051
|
prerelease_packages=overrides or None,
|
|
1049
1052
|
)
|
|
1050
1053
|
|
|
@@ -53,7 +53,7 @@ from synodic_client.application.theme import (
|
|
|
53
53
|
PLUGIN_UPDATE_STYLE,
|
|
54
54
|
SETTINGS_GEAR_STYLE,
|
|
55
55
|
)
|
|
56
|
-
from synodic_client.
|
|
56
|
+
from synodic_client.resolution import ResolvedConfig, update_user_config
|
|
57
57
|
|
|
58
58
|
logger = logging.getLogger(__name__)
|
|
59
59
|
|
|
@@ -306,14 +306,14 @@ class PluginsView(QWidget):
|
|
|
306
306
|
def __init__(
|
|
307
307
|
self,
|
|
308
308
|
porringer: API,
|
|
309
|
-
config:
|
|
309
|
+
config: ResolvedConfig,
|
|
310
310
|
parent: QWidget | None = None,
|
|
311
311
|
) -> None:
|
|
312
312
|
"""Initialize the plugins view.
|
|
313
313
|
|
|
314
314
|
Args:
|
|
315
315
|
porringer: The porringer API instance.
|
|
316
|
-
config: Resolved
|
|
316
|
+
config: Resolved configuration (for auto-update toggles).
|
|
317
317
|
parent: Optional parent widget.
|
|
318
318
|
"""
|
|
319
319
|
super().__init__(parent)
|
|
@@ -484,10 +484,7 @@ class PluginsView(QWidget):
|
|
|
484
484
|
|
|
485
485
|
def _on_auto_update_toggled(self, plugin_name: str, enabled: bool) -> None:
|
|
486
486
|
"""Persist the auto-update toggle change to config."""
|
|
487
|
-
mapping = self._config.plugin_auto_update
|
|
488
|
-
if mapping is None:
|
|
489
|
-
mapping = {}
|
|
490
|
-
self._config.plugin_auto_update = mapping
|
|
487
|
+
mapping = dict(self._config.plugin_auto_update or {})
|
|
491
488
|
|
|
492
489
|
if enabled:
|
|
493
490
|
mapping.pop(plugin_name, None)
|
|
@@ -495,10 +492,8 @@ class PluginsView(QWidget):
|
|
|
495
492
|
mapping[plugin_name] = False
|
|
496
493
|
|
|
497
494
|
# Clean up the dict if all plugins are enabled
|
|
498
|
-
if
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
save_config(self._config)
|
|
495
|
+
new_value = mapping if mapping else None
|
|
496
|
+
self._config = update_user_config(plugin_auto_update=new_value)
|
|
502
497
|
logger.info('Auto-update for %s set to %s', plugin_name, enabled)
|
|
503
498
|
|
|
504
499
|
|
|
@@ -510,12 +505,12 @@ class ProjectsView(QWidget):
|
|
|
510
505
|
install execution.
|
|
511
506
|
"""
|
|
512
507
|
|
|
513
|
-
def __init__(self, porringer: API, config:
|
|
508
|
+
def __init__(self, porringer: API, config: ResolvedConfig, parent: QWidget | None = None) -> None:
|
|
514
509
|
"""Initialize the projects view.
|
|
515
510
|
|
|
516
511
|
Args:
|
|
517
512
|
porringer: The porringer API instance.
|
|
518
|
-
config: Resolved
|
|
513
|
+
config: Resolved configuration.
|
|
519
514
|
parent: Optional parent widget.
|
|
520
515
|
"""
|
|
521
516
|
super().__init__(parent)
|
|
@@ -805,17 +800,17 @@ class MainWindow(QMainWindow):
|
|
|
805
800
|
def __init__(
|
|
806
801
|
self,
|
|
807
802
|
porringer: API | None = None,
|
|
808
|
-
config:
|
|
803
|
+
config: ResolvedConfig | None = None,
|
|
809
804
|
) -> None:
|
|
810
805
|
"""Initialize the main window.
|
|
811
806
|
|
|
812
807
|
Args:
|
|
813
808
|
porringer: Optional porringer API instance for manifest display.
|
|
814
|
-
config: Resolved
|
|
809
|
+
config: Resolved configuration for plugin auto-update state.
|
|
815
810
|
"""
|
|
816
811
|
super().__init__()
|
|
817
812
|
self._porringer = porringer
|
|
818
|
-
self._config = config
|
|
813
|
+
self._config = config
|
|
819
814
|
self.setWindowTitle('Synodic Client')
|
|
820
815
|
self.setMinimumSize(*MAIN_WINDOW_MIN_SIZE)
|
|
821
816
|
self.setWindowIcon(app_icon())
|
|
@@ -832,7 +827,7 @@ class MainWindow(QMainWindow):
|
|
|
832
827
|
|
|
833
828
|
def show(self) -> None:
|
|
834
829
|
"""Show the window, initializing UI lazily on first show."""
|
|
835
|
-
if self._tabs is None and self._porringer is not None:
|
|
830
|
+
if self._tabs is None and self._porringer is not None and self._config is not None:
|
|
836
831
|
self._tabs = QTabWidget(self)
|
|
837
832
|
|
|
838
833
|
self._projects_view = ProjectsView(self._porringer, self._config, self)
|
|
@@ -867,13 +862,13 @@ class Screen:
|
|
|
867
862
|
def __init__(
|
|
868
863
|
self,
|
|
869
864
|
porringer: API | None = None,
|
|
870
|
-
config:
|
|
865
|
+
config: ResolvedConfig | None = None,
|
|
871
866
|
) -> None:
|
|
872
867
|
"""Initialize the screen.
|
|
873
868
|
|
|
874
869
|
Args:
|
|
875
870
|
porringer: Optional porringer API instance.
|
|
876
|
-
config: Resolved
|
|
871
|
+
config: Resolved configuration.
|
|
877
872
|
"""
|
|
878
873
|
self._porringer = porringer
|
|
879
874
|
self._config = config
|
|
@@ -10,7 +10,7 @@ import sys
|
|
|
10
10
|
from collections.abc import Iterator
|
|
11
11
|
from contextlib import contextmanager
|
|
12
12
|
|
|
13
|
-
from PySide6.QtCore import QUrl, Signal
|
|
13
|
+
from PySide6.QtCore import Qt, QUrl, Signal
|
|
14
14
|
from PySide6.QtGui import QDesktopServices
|
|
15
15
|
from PySide6.QtWidgets import (
|
|
16
16
|
QCheckBox,
|
|
@@ -30,12 +30,10 @@ from PySide6.QtWidgets import (
|
|
|
30
30
|
from synodic_client.application.icon import app_icon
|
|
31
31
|
from synodic_client.application.screen.card import CardFrame
|
|
32
32
|
from synodic_client.application.theme import SETTINGS_WINDOW_MIN_SIZE
|
|
33
|
-
from synodic_client.config import GlobalConfiguration, save_config
|
|
34
33
|
from synodic_client.logging import log_path
|
|
34
|
+
from synodic_client.resolution import ResolvedConfig, update_user_config
|
|
35
35
|
from synodic_client.startup import is_startup_registered, register_startup, remove_startup
|
|
36
36
|
from synodic_client.updater import (
|
|
37
|
-
DEFAULT_AUTO_UPDATE_INTERVAL_MINUTES,
|
|
38
|
-
DEFAULT_TOOL_UPDATE_INTERVAL_MINUTES,
|
|
39
37
|
GITHUB_REPO_URL,
|
|
40
38
|
)
|
|
41
39
|
|
|
@@ -45,25 +43,26 @@ logger = logging.getLogger(__name__)
|
|
|
45
43
|
class SettingsWindow(QMainWindow):
|
|
46
44
|
"""Application settings window with grouped card sections.
|
|
47
45
|
|
|
48
|
-
All controls persist changes immediately via :func:`
|
|
49
|
-
emit :attr:`settings_changed` so that the tray and updater can
|
|
46
|
+
All controls persist changes immediately via :func:`update_user_config`
|
|
47
|
+
and emit :attr:`settings_changed` so that the tray and updater can
|
|
48
|
+
react. The signal carries the new :class:`ResolvedConfig`.
|
|
50
49
|
"""
|
|
51
50
|
|
|
52
|
-
settings_changed = Signal()
|
|
53
|
-
"""Emitted whenever a setting is changed and persisted."""
|
|
51
|
+
settings_changed = Signal(object)
|
|
52
|
+
"""Emitted with the new ``ResolvedConfig`` whenever a setting is changed and persisted."""
|
|
54
53
|
|
|
55
54
|
check_updates_requested = Signal()
|
|
56
55
|
"""Emitted when the user clicks the *Check for Updates* button."""
|
|
57
56
|
|
|
58
57
|
def __init__(
|
|
59
58
|
self,
|
|
60
|
-
config:
|
|
59
|
+
config: ResolvedConfig,
|
|
61
60
|
parent: QWidget | None = None,
|
|
62
61
|
) -> None:
|
|
63
62
|
"""Initialise the settings window.
|
|
64
63
|
|
|
65
64
|
Args:
|
|
66
|
-
config: The
|
|
65
|
+
config: The current resolved configuration snapshot.
|
|
67
66
|
parent: Optional parent widget.
|
|
68
67
|
"""
|
|
69
68
|
super().__init__(parent)
|
|
@@ -127,6 +126,12 @@ class SettingsWindow(QMainWindow):
|
|
|
127
126
|
row.addWidget(browse_btn)
|
|
128
127
|
content.addLayout(row)
|
|
129
128
|
|
|
129
|
+
self._add_update_controls(content)
|
|
130
|
+
|
|
131
|
+
return card
|
|
132
|
+
|
|
133
|
+
def _add_update_controls(self, content: QVBoxLayout) -> None:
|
|
134
|
+
"""Add interval spinners, detect-updates checkbox, and update button."""
|
|
130
135
|
# Auto-update interval
|
|
131
136
|
row = QHBoxLayout()
|
|
132
137
|
label = QLabel('App update interval (min)')
|
|
@@ -164,12 +169,11 @@ class SettingsWindow(QMainWindow):
|
|
|
164
169
|
self._check_updates_btn.clicked.connect(self._on_check_updates_clicked)
|
|
165
170
|
row.addWidget(self._check_updates_btn)
|
|
166
171
|
self._update_status_label = QLabel('')
|
|
172
|
+
self._update_status_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
|
|
167
173
|
row.addWidget(self._update_status_label)
|
|
168
174
|
row.addStretch()
|
|
169
175
|
content.addLayout(row)
|
|
170
176
|
|
|
171
|
-
return card
|
|
172
|
-
|
|
173
177
|
def _build_startup_section(self) -> CardFrame:
|
|
174
178
|
"""Construct the *Startup* settings card."""
|
|
175
179
|
card = CardFrame('Startup')
|
|
@@ -208,15 +212,9 @@ class SettingsWindow(QMainWindow):
|
|
|
208
212
|
# Update source
|
|
209
213
|
self._source_edit.setText(config.update_source or '')
|
|
210
214
|
|
|
211
|
-
# Intervals
|
|
212
|
-
|
|
213
|
-
self.
|
|
214
|
-
auto_interval if auto_interval is not None else DEFAULT_AUTO_UPDATE_INTERVAL_MINUTES,
|
|
215
|
-
)
|
|
216
|
-
tool_interval = config.tool_update_interval_minutes
|
|
217
|
-
self._tool_update_spin.setValue(
|
|
218
|
-
tool_interval if tool_interval is not None else DEFAULT_TOOL_UPDATE_INTERVAL_MINUTES,
|
|
219
|
-
)
|
|
215
|
+
# Intervals (already resolved to concrete ints)
|
|
216
|
+
self._auto_update_spin.setValue(config.auto_update_interval_minutes)
|
|
217
|
+
self._tool_update_spin.setValue(config.tool_update_interval_minutes)
|
|
220
218
|
|
|
221
219
|
# Checkboxes
|
|
222
220
|
self._detect_updates_check.setChecked(config.detect_updates)
|
|
@@ -241,10 +239,14 @@ class SettingsWindow(QMainWindow):
|
|
|
241
239
|
# Callbacks
|
|
242
240
|
# ------------------------------------------------------------------
|
|
243
241
|
|
|
244
|
-
def _persist(self) -> None:
|
|
245
|
-
"""Save config and notify listeners.
|
|
246
|
-
|
|
247
|
-
|
|
242
|
+
def _persist(self, **changes: object) -> None:
|
|
243
|
+
"""Save config changes and notify listeners.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
**changes: Field-name / value pairs to persist.
|
|
247
|
+
"""
|
|
248
|
+
self._config = update_user_config(**changes)
|
|
249
|
+
self.settings_changed.emit(self._config)
|
|
248
250
|
|
|
249
251
|
@contextmanager
|
|
250
252
|
def _block_signals(self) -> Iterator[None]:
|
|
@@ -273,13 +275,11 @@ class SettingsWindow(QMainWindow):
|
|
|
273
275
|
self.check_updates_requested.emit()
|
|
274
276
|
|
|
275
277
|
def _on_channel_changed(self, index: int) -> None:
|
|
276
|
-
self.
|
|
277
|
-
self._persist()
|
|
278
|
+
self._persist(update_channel='dev' if index == 1 else 'stable')
|
|
278
279
|
|
|
279
280
|
def _on_source_changed(self) -> None:
|
|
280
281
|
text = self._source_edit.text().strip()
|
|
281
|
-
self.
|
|
282
|
-
self._persist()
|
|
282
|
+
self._persist(update_source=text or None)
|
|
283
283
|
|
|
284
284
|
def _on_browse_source(self) -> None:
|
|
285
285
|
path = QFileDialog.getExistingDirectory(self, 'Select Releases Directory')
|
|
@@ -288,25 +288,21 @@ class SettingsWindow(QMainWindow):
|
|
|
288
288
|
self._on_source_changed()
|
|
289
289
|
|
|
290
290
|
def _on_auto_update_interval_changed(self, value: int) -> None:
|
|
291
|
-
self.
|
|
292
|
-
self._persist()
|
|
291
|
+
self._persist(auto_update_interval_minutes=value)
|
|
293
292
|
|
|
294
293
|
def _on_tool_update_interval_changed(self, value: int) -> None:
|
|
295
|
-
self.
|
|
296
|
-
self._persist()
|
|
294
|
+
self._persist(tool_update_interval_minutes=value)
|
|
297
295
|
|
|
298
296
|
def _on_detect_updates_changed(self, checked: bool) -> None:
|
|
299
|
-
self.
|
|
300
|
-
self._persist()
|
|
297
|
+
self._persist(detect_updates=checked)
|
|
301
298
|
|
|
302
299
|
def _on_auto_start_changed(self, checked: bool) -> None:
|
|
303
|
-
self._config
|
|
304
|
-
save_config(self._config)
|
|
300
|
+
self._config = update_user_config(auto_start=checked)
|
|
305
301
|
if checked:
|
|
306
302
|
register_startup(sys.executable)
|
|
307
303
|
else:
|
|
308
304
|
remove_startup()
|
|
309
|
-
self.settings_changed.emit()
|
|
305
|
+
self.settings_changed.emit(self._config)
|
|
310
306
|
|
|
311
307
|
@staticmethod
|
|
312
308
|
def _open_log() -> None:
|
{synodic_client-0.0.1.dev33 → synodic_client-0.0.1.dev35}/synodic_client/application/screen/tray.py
RENAMED
|
@@ -20,12 +20,11 @@ from synodic_client.application.screen.screen import MainWindow
|
|
|
20
20
|
from synodic_client.application.screen.settings import SettingsWindow
|
|
21
21
|
from synodic_client.application.workers import ToolUpdateWorker, UpdateCheckWorker, UpdateDownloadWorker
|
|
22
22
|
from synodic_client.client import Client
|
|
23
|
-
from synodic_client.config import GlobalConfiguration
|
|
24
23
|
from synodic_client.resolution import (
|
|
24
|
+
ResolvedConfig,
|
|
25
25
|
resolve_config,
|
|
26
26
|
resolve_enabled_plugins,
|
|
27
27
|
resolve_update_config,
|
|
28
|
-
update_and_resolve,
|
|
29
28
|
)
|
|
30
29
|
from synodic_client.updater import UpdateInfo
|
|
31
30
|
|
|
@@ -40,7 +39,7 @@ class TrayScreen:
|
|
|
40
39
|
app: QApplication,
|
|
41
40
|
client: Client,
|
|
42
41
|
window: MainWindow,
|
|
43
|
-
config:
|
|
42
|
+
config: ResolvedConfig | None = None,
|
|
44
43
|
) -> None:
|
|
45
44
|
"""Initialize the tray icon.
|
|
46
45
|
|
|
@@ -123,14 +122,14 @@ class TrayScreen:
|
|
|
123
122
|
|
|
124
123
|
# -- Config helpers --
|
|
125
124
|
|
|
126
|
-
def _resolve_config(self) ->
|
|
125
|
+
def _resolve_config(self) -> ResolvedConfig:
|
|
127
126
|
"""Return the injected config or resolve from disk."""
|
|
128
127
|
if self._config is not None:
|
|
129
128
|
return self._config
|
|
130
129
|
return resolve_config()
|
|
131
130
|
|
|
131
|
+
@staticmethod
|
|
132
132
|
def _restart_timer(
|
|
133
|
-
self,
|
|
134
133
|
current: QTimer | None,
|
|
135
134
|
interval_minutes: int,
|
|
136
135
|
slot: Callable[[], None],
|
|
@@ -192,14 +191,21 @@ class TrayScreen:
|
|
|
192
191
|
"""Show the settings window."""
|
|
193
192
|
self._settings_window.show()
|
|
194
193
|
|
|
195
|
-
def _on_settings_changed(self) -> None:
|
|
194
|
+
def _on_settings_changed(self, config: ResolvedConfig) -> None:
|
|
196
195
|
"""React to a change made in the settings window."""
|
|
197
|
-
|
|
196
|
+
self._config = config
|
|
198
197
|
self._reinitialize_updater(config)
|
|
199
198
|
|
|
200
|
-
def _reinitialize_updater(self, config:
|
|
201
|
-
"""Re-derive update settings and restart the updater and timers.
|
|
202
|
-
|
|
199
|
+
def _reinitialize_updater(self, config: ResolvedConfig) -> None:
|
|
200
|
+
"""Re-derive update settings and restart the updater and timers.
|
|
201
|
+
|
|
202
|
+
The new ``Updater`` starts with the ``importlib.metadata``
|
|
203
|
+
version which may be stale after a Velopack update. The
|
|
204
|
+
authoritative Velopack version is recovered automatically on
|
|
205
|
+
the first ``_get_velopack_manager()`` call (i.e. the next
|
|
206
|
+
update check), so no special handling is required here.
|
|
207
|
+
"""
|
|
208
|
+
update_cfg = resolve_update_config(config)
|
|
203
209
|
self._client.initialize_updater(update_cfg)
|
|
204
210
|
self._restart_auto_update_timer()
|
|
205
211
|
self._restart_tool_update_timer()
|