synodic-client 0.0.1.dev32__tar.gz → 0.0.1.dev34__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.dev32 → synodic_client-0.0.1.dev34}/PKG-INFO +1 -1
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/pyproject.toml +1 -1
- synodic_client-0.0.1.dev34/synodic_client/_version.py +1 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/bootstrap.py +5 -2
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/qt.py +15 -7
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/screen/install.py +13 -10
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/screen/screen.py +14 -19
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/screen/settings.py +63 -39
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/screen/tray.py +82 -179
- synodic_client-0.0.1.dev34/synodic_client/application/workers.py +112 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/config.py +67 -60
- synodic_client-0.0.1.dev34/synodic_client/resolution.py +265 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/updater.py +18 -2
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/qt/test_settings.py +94 -31
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/test_config.py +45 -52
- synodic_client-0.0.1.dev34/tests/unit/test_resolution.py +407 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/test_updater.py +43 -20
- synodic_client-0.0.1.dev32/synodic_client/_version.py +0 -1
- synodic_client-0.0.1.dev32/synodic_client/resolution.py +0 -166
- synodic_client-0.0.1.dev32/tests/unit/test_resolution.py +0 -414
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/LICENSE.md +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/README.md +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/__init__.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/__main__.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/__init__.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/icon.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/instance.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/screen/__init__.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/screen/action_card.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/screen/card.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/screen/log_panel.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/screen/spinner.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/theme.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/application/uri.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/cli.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/client.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/logging.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/protocol.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/py.typed +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/synodic_client/startup.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/__init__.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/conftest.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/__init__.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/qt/__init__.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/qt/conftest.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/qt/test_action_card.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/qt/test_install_preview.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/qt/test_log_panel.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/qt/test_logging.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/test_cli.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/test_client_updater.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/test_client_version.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/test_examples.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/test_install.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/test_uri.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/windows/__init__.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/windows/conftest.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/windows/test_protocol.py +0 -0
- {synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/tests/unit/windows/test_startup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.0.1.dev34'
|
{synodic_client-0.0.1.dev32 → synodic_client-0.0.1.dev34}/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()
|
|
@@ -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
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""Settings window for the Synodic Client application.
|
|
2
2
|
|
|
3
3
|
Provides a single-page window with grouped sections for all application
|
|
4
|
-
settings
|
|
5
|
-
|
|
4
|
+
settings including update-channel selection and a manual *Check for
|
|
5
|
+
Updates* button with inline status feedback.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
@@ -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,22 +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."""
|
|
53
|
+
|
|
54
|
+
check_updates_requested = Signal()
|
|
55
|
+
"""Emitted when the user clicks the *Check for Updates* button."""
|
|
54
56
|
|
|
55
57
|
def __init__(
|
|
56
58
|
self,
|
|
57
|
-
config:
|
|
59
|
+
config: ResolvedConfig,
|
|
58
60
|
parent: QWidget | None = None,
|
|
59
61
|
) -> None:
|
|
60
62
|
"""Initialise the settings window.
|
|
61
63
|
|
|
62
64
|
Args:
|
|
63
|
-
config: The
|
|
65
|
+
config: The current resolved configuration snapshot.
|
|
64
66
|
parent: Optional parent widget.
|
|
65
67
|
"""
|
|
66
68
|
super().__init__(parent)
|
|
@@ -124,6 +126,12 @@ class SettingsWindow(QMainWindow):
|
|
|
124
126
|
row.addWidget(browse_btn)
|
|
125
127
|
content.addLayout(row)
|
|
126
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."""
|
|
127
135
|
# Auto-update interval
|
|
128
136
|
row = QHBoxLayout()
|
|
129
137
|
label = QLabel('App update interval (min)')
|
|
@@ -155,7 +163,16 @@ class SettingsWindow(QMainWindow):
|
|
|
155
163
|
self._detect_updates_check.toggled.connect(self._on_detect_updates_changed)
|
|
156
164
|
content.addWidget(self._detect_updates_check)
|
|
157
165
|
|
|
158
|
-
|
|
166
|
+
# Check for Updates
|
|
167
|
+
row = QHBoxLayout()
|
|
168
|
+
self._check_updates_btn = QPushButton('Check for Updates\u2026')
|
|
169
|
+
self._check_updates_btn.clicked.connect(self._on_check_updates_clicked)
|
|
170
|
+
row.addWidget(self._check_updates_btn)
|
|
171
|
+
self._update_status_label = QLabel('')
|
|
172
|
+
self._update_status_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
|
|
173
|
+
row.addWidget(self._update_status_label)
|
|
174
|
+
row.addStretch()
|
|
175
|
+
content.addLayout(row)
|
|
159
176
|
|
|
160
177
|
def _build_startup_section(self) -> CardFrame:
|
|
161
178
|
"""Construct the *Startup* settings card."""
|
|
@@ -195,20 +212,22 @@ class SettingsWindow(QMainWindow):
|
|
|
195
212
|
# Update source
|
|
196
213
|
self._source_edit.setText(config.update_source or '')
|
|
197
214
|
|
|
198
|
-
# Intervals
|
|
199
|
-
|
|
200
|
-
self.
|
|
201
|
-
auto_interval if auto_interval is not None else DEFAULT_AUTO_UPDATE_INTERVAL_MINUTES,
|
|
202
|
-
)
|
|
203
|
-
tool_interval = config.tool_update_interval_minutes
|
|
204
|
-
self._tool_update_spin.setValue(
|
|
205
|
-
tool_interval if tool_interval is not None else DEFAULT_TOOL_UPDATE_INTERVAL_MINUTES,
|
|
206
|
-
)
|
|
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)
|
|
207
218
|
|
|
208
219
|
# Checkboxes
|
|
209
220
|
self._detect_updates_check.setChecked(config.detect_updates)
|
|
210
221
|
self._auto_start_check.setChecked(is_startup_registered())
|
|
211
222
|
|
|
223
|
+
def set_update_status(self, text: str) -> None:
|
|
224
|
+
"""Set the inline status text next to the *Check for Updates* button."""
|
|
225
|
+
self._update_status_label.setText(text)
|
|
226
|
+
|
|
227
|
+
def reset_check_updates_button(self) -> None:
|
|
228
|
+
"""Re-enable the *Check for Updates* button after a check completes."""
|
|
229
|
+
self._check_updates_btn.setEnabled(True)
|
|
230
|
+
|
|
212
231
|
def show(self) -> None:
|
|
213
232
|
"""Sync controls from config, then show the window."""
|
|
214
233
|
self.sync_from_config()
|
|
@@ -220,10 +239,14 @@ class SettingsWindow(QMainWindow):
|
|
|
220
239
|
# Callbacks
|
|
221
240
|
# ------------------------------------------------------------------
|
|
222
241
|
|
|
223
|
-
def _persist(self) -> None:
|
|
224
|
-
"""Save config and notify listeners.
|
|
225
|
-
|
|
226
|
-
|
|
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)
|
|
227
250
|
|
|
228
251
|
@contextmanager
|
|
229
252
|
def _block_signals(self) -> Iterator[None]:
|
|
@@ -235,6 +258,7 @@ class SettingsWindow(QMainWindow):
|
|
|
235
258
|
self._tool_update_spin,
|
|
236
259
|
self._detect_updates_check,
|
|
237
260
|
self._auto_start_check,
|
|
261
|
+
self._check_updates_btn,
|
|
238
262
|
)
|
|
239
263
|
for w in widgets:
|
|
240
264
|
w.blockSignals(True)
|
|
@@ -244,14 +268,18 @@ class SettingsWindow(QMainWindow):
|
|
|
244
268
|
for w in widgets:
|
|
245
269
|
w.blockSignals(False)
|
|
246
270
|
|
|
271
|
+
def _on_check_updates_clicked(self) -> None:
|
|
272
|
+
"""Handle the *Check for Updates* button click."""
|
|
273
|
+
self._check_updates_btn.setEnabled(False)
|
|
274
|
+
self._update_status_label.setText('Checking\u2026')
|
|
275
|
+
self.check_updates_requested.emit()
|
|
276
|
+
|
|
247
277
|
def _on_channel_changed(self, index: int) -> None:
|
|
248
|
-
self.
|
|
249
|
-
self._persist()
|
|
278
|
+
self._persist(update_channel='dev' if index == 1 else 'stable')
|
|
250
279
|
|
|
251
280
|
def _on_source_changed(self) -> None:
|
|
252
281
|
text = self._source_edit.text().strip()
|
|
253
|
-
self.
|
|
254
|
-
self._persist()
|
|
282
|
+
self._persist(update_source=text or None)
|
|
255
283
|
|
|
256
284
|
def _on_browse_source(self) -> None:
|
|
257
285
|
path = QFileDialog.getExistingDirectory(self, 'Select Releases Directory')
|
|
@@ -260,25 +288,21 @@ class SettingsWindow(QMainWindow):
|
|
|
260
288
|
self._on_source_changed()
|
|
261
289
|
|
|
262
290
|
def _on_auto_update_interval_changed(self, value: int) -> None:
|
|
263
|
-
self.
|
|
264
|
-
self._persist()
|
|
291
|
+
self._persist(auto_update_interval_minutes=value)
|
|
265
292
|
|
|
266
293
|
def _on_tool_update_interval_changed(self, value: int) -> None:
|
|
267
|
-
self.
|
|
268
|
-
self._persist()
|
|
294
|
+
self._persist(tool_update_interval_minutes=value)
|
|
269
295
|
|
|
270
296
|
def _on_detect_updates_changed(self, checked: bool) -> None:
|
|
271
|
-
self.
|
|
272
|
-
self._persist()
|
|
297
|
+
self._persist(detect_updates=checked)
|
|
273
298
|
|
|
274
299
|
def _on_auto_start_changed(self, checked: bool) -> None:
|
|
275
|
-
self._config
|
|
276
|
-
save_config(self._config)
|
|
300
|
+
self._config = update_user_config(auto_start=checked)
|
|
277
301
|
if checked:
|
|
278
302
|
register_startup(sys.executable)
|
|
279
303
|
else:
|
|
280
304
|
remove_startup()
|
|
281
|
-
self.settings_changed.emit()
|
|
305
|
+
self.settings_changed.emit(self._config)
|
|
282
306
|
|
|
283
307
|
@staticmethod
|
|
284
308
|
def _open_log() -> None:
|