synodic-client 0.0.1.dev63__tar.gz → 0.0.1.dev65__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.dev63 → synodic_client-0.0.1.dev65}/PKG-INFO +2 -2
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/pyproject.toml +2 -2
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/bootstrap.py +1 -1
- synodic_client-0.0.1.dev65/synodic_client/application/config_store.py +65 -0
- synodic_client-0.0.1.dev65/synodic_client/application/package_state.py +125 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/qt.py +5 -3
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/schema.py +21 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/install.py +14 -7
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/install_workers.py +0 -1
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/projects.py +13 -6
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/schema.py +0 -1
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/screen.py +81 -52
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/settings.py +15 -35
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/tool_update_controller.py +99 -87
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/tray.py +14 -28
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/update_banner.py +25 -17
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/update_controller.py +22 -27
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/workers.py +15 -14
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/resolution.py +0 -1
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/schema.py +0 -5
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/subprocess_patch.py +5 -5
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/test_gather_packages.py +37 -32
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/test_install_preview.py +6 -9
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/test_settings.py +36 -94
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/test_tray_window_show.py +24 -7
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/test_update_controller.py +30 -36
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/test_config.py +0 -2
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/test_resolution.py +0 -1
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/LICENSE.md +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/README.md +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/__init__.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/__main__.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/__init__.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/data.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/icon.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/init.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/instance.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/__init__.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/action_card.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/card.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/log_panel.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/plugin_row.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/sidebar.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/screen/spinner.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/theme.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/uri.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/cli.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/client.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/config.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/logging.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/protocol.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/py.typed +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/startup.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/updater.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/__init__.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/conftest.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/__init__.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/__init__.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/conftest.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/test_action_card.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/test_log_panel.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/test_logging.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/test_preview_model.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/test_sidebar.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/test_update_banner.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/qt/test_update_feedback.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/test_cli.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/test_client_updater.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/test_client_version.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/test_examples.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/test_init.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/test_install.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/test_updater.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/test_uri.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/test_workers.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/windows/__init__.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/windows/conftest.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/tests/unit/windows/test_protocol.py +0 -0
- {synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/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.dev65
|
|
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.dev78
|
|
12
12
|
Requires-Dist: qasync>=0.28.0
|
|
13
13
|
Requires-Dist: velopack>=0.0.1444.dev49733
|
|
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.dev78",
|
|
14
14
|
"qasync>=0.28.0",
|
|
15
15
|
"velopack>=0.0.1444.dev49733",
|
|
16
16
|
"typer>=0.24.1",
|
|
17
17
|
]
|
|
18
|
-
version = "0.0.1.
|
|
18
|
+
version = "0.0.1.dev65"
|
|
19
19
|
|
|
20
20
|
[project.license]
|
|
21
21
|
text = "LGPL-3.0-or-later"
|
{synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/bootstrap.py
RENAMED
|
@@ -17,8 +17,8 @@ import sys
|
|
|
17
17
|
|
|
18
18
|
from synodic_client.config import set_dev_mode
|
|
19
19
|
from synodic_client.logging import configure_logging
|
|
20
|
-
from synodic_client.subprocess_patch import apply as _apply_subprocess_patch
|
|
21
20
|
from synodic_client.protocol import extract_uri_from_args
|
|
21
|
+
from synodic_client.subprocess_patch import apply as _apply_subprocess_patch
|
|
22
22
|
from synodic_client.updater import initialize_velopack
|
|
23
23
|
|
|
24
24
|
# Parse flags early so logging uses the right filename and level.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Centralized configuration store.
|
|
2
|
+
|
|
3
|
+
Provides a single source of truth for :class:`ResolvedConfig` so that
|
|
4
|
+
every consumer (ToolsView, SettingsWindow, UpdateController,
|
|
5
|
+
ToolUpdateOrchestrator) always reads the same snapshot and receives
|
|
6
|
+
change notifications through a Qt signal.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from PySide6.QtCore import QObject, Signal
|
|
12
|
+
|
|
13
|
+
from synodic_client.resolution import ResolvedConfig, resolve_config, update_user_config
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfigStore(QObject):
|
|
17
|
+
"""Observable wrapper around :class:`ResolvedConfig`.
|
|
18
|
+
|
|
19
|
+
All config mutations go through :meth:`update` (which persists to
|
|
20
|
+
disk) or :meth:`set` (which replaces without persisting). Both
|
|
21
|
+
emit :attr:`changed` so every connected consumer stays in sync.
|
|
22
|
+
|
|
23
|
+
Typical usage::
|
|
24
|
+
|
|
25
|
+
store = ConfigStore(initial_config)
|
|
26
|
+
store.changed.connect(some_consumer.on_config_changed)
|
|
27
|
+
store.update(auto_apply=False) # persists + emits
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
changed = Signal(object)
|
|
31
|
+
"""Emitted with the new ``ResolvedConfig`` after every mutation."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, config: ResolvedConfig | None = None, parent: QObject | None = None) -> None:
|
|
34
|
+
"""Create a new store, optionally seeded with *config*."""
|
|
35
|
+
super().__init__(parent)
|
|
36
|
+
self._config = config if config is not None else resolve_config()
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def config(self) -> ResolvedConfig:
|
|
40
|
+
"""The current configuration snapshot."""
|
|
41
|
+
return self._config
|
|
42
|
+
|
|
43
|
+
def update(self, **changes: object) -> ResolvedConfig:
|
|
44
|
+
"""Persist *changes* to disk and broadcast the new config.
|
|
45
|
+
|
|
46
|
+
Wraps :func:`~synodic_client.resolution.update_user_config`.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
**changes: Field-name / value pairs forwarded to
|
|
50
|
+
:func:`update_user_config`.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
The fresh :class:`ResolvedConfig`.
|
|
54
|
+
"""
|
|
55
|
+
self._config = update_user_config(**changes)
|
|
56
|
+
self.changed.emit(self._config)
|
|
57
|
+
return self._config
|
|
58
|
+
|
|
59
|
+
def set(self, config: ResolvedConfig) -> None:
|
|
60
|
+
"""Replace the config without persisting and notify listeners.
|
|
61
|
+
|
|
62
|
+
Use for externally resolved configs (e.g. passed at startup).
|
|
63
|
+
"""
|
|
64
|
+
self._config = config
|
|
65
|
+
self.changed.emit(self._config)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Shared package update state registry.
|
|
2
|
+
|
|
3
|
+
Provides :class:`PackageStateStore`, a centralised record of
|
|
4
|
+
per-package update status used by both ToolsView and ProjectsView
|
|
5
|
+
so that version/update information discovered in one view is
|
|
6
|
+
immediately available to the other.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
from PySide6.QtCore import QObject, Signal
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(slots=True)
|
|
17
|
+
class PackageState:
|
|
18
|
+
"""Canonical update state for a single package.
|
|
19
|
+
|
|
20
|
+
Keyed by ``(signal_key, name)`` inside :class:`PackageStateStore`.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
name: str
|
|
24
|
+
"""Package name (e.g. ``"ruff"``)."""
|
|
25
|
+
|
|
26
|
+
installed_version: str = ''
|
|
27
|
+
"""Currently installed version, or empty if unknown."""
|
|
28
|
+
|
|
29
|
+
available_version: str = ''
|
|
30
|
+
"""Latest available version, or empty if unknown."""
|
|
31
|
+
|
|
32
|
+
has_update: bool = False
|
|
33
|
+
"""Whether the package has a newer version available."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PackageStateStore(QObject):
|
|
37
|
+
"""Shared registry of package update states across views.
|
|
38
|
+
|
|
39
|
+
Both ToolsView and ProjectsView write discovered update information
|
|
40
|
+
into the store; each view can then read the canonical state
|
|
41
|
+
regardless of which view made the discovery.
|
|
42
|
+
|
|
43
|
+
:attr:`state_changed` is emitted whenever new data arrives so
|
|
44
|
+
listeners can refresh their badges/labels without polling.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
state_changed = Signal()
|
|
48
|
+
"""Emitted whenever any package state is created or modified."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, parent: QObject | None = None) -> None:
|
|
51
|
+
"""Initialise with an empty registry."""
|
|
52
|
+
super().__init__(parent)
|
|
53
|
+
self._data: dict[str, dict[str, PackageState]] = {}
|
|
54
|
+
|
|
55
|
+
# -- Bulk write (ToolsView check_updates) ----------------------------
|
|
56
|
+
|
|
57
|
+
def set_check_results(self, available: dict[str, dict[str, str]]) -> None:
|
|
58
|
+
"""Populate from ``check_updates`` results.
|
|
59
|
+
|
|
60
|
+
*available* maps ``{signal_key: {package_name: latest_version}}``
|
|
61
|
+
— the same shape previously stored in
|
|
62
|
+
``ToolsView._updates_available``.
|
|
63
|
+
"""
|
|
64
|
+
self._data.clear()
|
|
65
|
+
for key, packages in available.items():
|
|
66
|
+
for pkg_name, latest in packages.items():
|
|
67
|
+
self._data.setdefault(key, {})[pkg_name] = PackageState(
|
|
68
|
+
name=pkg_name,
|
|
69
|
+
available_version=latest,
|
|
70
|
+
has_update=True,
|
|
71
|
+
)
|
|
72
|
+
self.state_changed.emit()
|
|
73
|
+
|
|
74
|
+
# -- Single-action write (ProjectsView dry-run) ----------------------
|
|
75
|
+
|
|
76
|
+
def record_action_result(
|
|
77
|
+
self,
|
|
78
|
+
signal_key: str,
|
|
79
|
+
pkg_name: str,
|
|
80
|
+
*,
|
|
81
|
+
installed_version: str = '',
|
|
82
|
+
available_version: str = '',
|
|
83
|
+
has_update: bool = False,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Record a single dry-run result.
|
|
86
|
+
|
|
87
|
+
Merges with any existing state so that data discovered by
|
|
88
|
+
different views accumulates rather than overwrites.
|
|
89
|
+
"""
|
|
90
|
+
existing = self._data.get(signal_key, {}).get(pkg_name)
|
|
91
|
+
state = PackageState(
|
|
92
|
+
name=pkg_name,
|
|
93
|
+
installed_version=installed_version or (existing.installed_version if existing else ''),
|
|
94
|
+
available_version=available_version or (existing.available_version if existing else ''),
|
|
95
|
+
has_update=has_update or (existing.has_update if existing else False),
|
|
96
|
+
)
|
|
97
|
+
self._data.setdefault(signal_key, {})[pkg_name] = state
|
|
98
|
+
self.state_changed.emit()
|
|
99
|
+
|
|
100
|
+
# -- Read API --------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
def get_updates(self, signal_key: str) -> dict[str, str]:
|
|
103
|
+
"""Return ``{package_name: latest_version}`` for packages with updates.
|
|
104
|
+
|
|
105
|
+
Drop-in replacement for ``_updates_available.get(key, {})``.
|
|
106
|
+
"""
|
|
107
|
+
bucket = self._data.get(signal_key, {})
|
|
108
|
+
return {name: s.available_version for name, s in bucket.items() if s.has_update}
|
|
109
|
+
|
|
110
|
+
def has_updates_for(self, signal_key: str) -> bool:
|
|
111
|
+
"""Return whether any package under *signal_key* has an update."""
|
|
112
|
+
return any(s.has_update for s in self._data.get(signal_key, {}).values())
|
|
113
|
+
|
|
114
|
+
def get(self, signal_key: str, pkg_name: str) -> PackageState | None:
|
|
115
|
+
"""Return the state for a specific package, or ``None``."""
|
|
116
|
+
return self._data.get(signal_key, {}).get(pkg_name)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def has_data(self) -> bool:
|
|
120
|
+
"""Return whether any update data has been recorded."""
|
|
121
|
+
return bool(self._data)
|
|
122
|
+
|
|
123
|
+
def clear(self) -> None:
|
|
124
|
+
"""Remove all recorded state."""
|
|
125
|
+
self._data.clear()
|
|
@@ -15,6 +15,7 @@ from porringer.schema import LocalConfiguration
|
|
|
15
15
|
from PySide6.QtCore import QEvent, QObject, Qt, QTimer
|
|
16
16
|
from PySide6.QtWidgets import QApplication, QWidget
|
|
17
17
|
|
|
18
|
+
from synodic_client.application.config_store import ConfigStore
|
|
18
19
|
from synodic_client.application.icon import app_icon
|
|
19
20
|
from synodic_client.application.init import run_startup_preamble
|
|
20
21
|
from synodic_client.application.instance import SingleInstance
|
|
@@ -216,8 +217,9 @@ def application(*, uri: str | None = None, dev_mode: bool = False, debug: bool =
|
|
|
216
217
|
sys.exit(0)
|
|
217
218
|
instance.start_server()
|
|
218
219
|
|
|
219
|
-
|
|
220
|
-
|
|
220
|
+
_store = ConfigStore(config)
|
|
221
|
+
_screen = Screen(porringer, _store)
|
|
222
|
+
_tray = TrayScreen(app, client, _screen.window, store=_store)
|
|
221
223
|
|
|
222
224
|
# Keep install preview windows alive until the app exits
|
|
223
225
|
_install_windows: list[InstallPreviewWindow] = []
|
|
@@ -227,7 +229,7 @@ def application(*, uri: str | None = None, dev_mode: bool = False, debug: bool =
|
|
|
227
229
|
window = InstallPreviewWindow(
|
|
228
230
|
porringer,
|
|
229
231
|
manifest_url,
|
|
230
|
-
config=config,
|
|
232
|
+
config=_store.config,
|
|
231
233
|
)
|
|
232
234
|
_install_windows.append(window)
|
|
233
235
|
window.show()
|
{synodic_client-0.0.1.dev63 → synodic_client-0.0.1.dev65}/synodic_client/application/schema.py
RENAMED
|
@@ -51,3 +51,24 @@ class ToolUpdateResult:
|
|
|
51
51
|
failed: int = 0
|
|
52
52
|
updated_packages: set[str] = field(default_factory=set)
|
|
53
53
|
"""Package names that were successfully upgraded."""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True, slots=True)
|
|
57
|
+
class UpdateTarget:
|
|
58
|
+
"""Identifies the scope of a manual tool update.
|
|
59
|
+
|
|
60
|
+
Passed to the shared completion handler so it can clear the correct
|
|
61
|
+
updating state and derive timestamp keys. ``None`` (the default in
|
|
62
|
+
the handler) means the update was periodic / automatic.
|
|
63
|
+
|
|
64
|
+
When *package* is empty the update targeted an entire plugin;
|
|
65
|
+
otherwise it targeted one specific package within the plugin.
|
|
66
|
+
*plugin* always carries the signal key (possibly composite
|
|
67
|
+
``"plugin:tag"``).
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
plugin: str
|
|
71
|
+
"""Signal key for the plugin (may be composite ``"name:tag"``)."""
|
|
72
|
+
|
|
73
|
+
package: str = ''
|
|
74
|
+
"""Package name, or empty when the whole plugin was updated."""
|
|
@@ -43,6 +43,7 @@ from PySide6.QtWidgets import (
|
|
|
43
43
|
QWidget,
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
+
from synodic_client.application.package_state import PackageStateStore
|
|
46
47
|
from synodic_client.application.screen import skip_reason_label
|
|
47
48
|
from synodic_client.application.screen.action_card import ActionCardList, action_key
|
|
48
49
|
from synodic_client.application.screen.card import CardFrame
|
|
@@ -113,6 +114,7 @@ class SetupPreviewWidget(QWidget):
|
|
|
113
114
|
*,
|
|
114
115
|
show_close: bool = True,
|
|
115
116
|
config: ResolvedConfig | None = None,
|
|
117
|
+
package_store: PackageStateStore | None = None,
|
|
116
118
|
) -> None:
|
|
117
119
|
"""Initialize the preview widget.
|
|
118
120
|
|
|
@@ -122,11 +124,13 @@ class SetupPreviewWidget(QWidget):
|
|
|
122
124
|
show_close: Whether to show the Close button. Set ``False``
|
|
123
125
|
when embedding inside a persistent view (e.g. a tab).
|
|
124
126
|
config: Global configuration for per-manifest pre-release state.
|
|
127
|
+
package_store: Shared package update state registry.
|
|
125
128
|
"""
|
|
126
129
|
super().__init__(parent)
|
|
127
130
|
self._porringer = porringer
|
|
128
131
|
self._show_close = show_close
|
|
129
132
|
self._config = config
|
|
133
|
+
self._package_store = package_store
|
|
130
134
|
self._discovered_plugins: DiscoveredPlugins | None = None
|
|
131
135
|
|
|
132
136
|
self._model = PreviewModel()
|
|
@@ -293,7 +297,6 @@ class SetupPreviewWidget(QWidget):
|
|
|
293
297
|
path_or_url: str,
|
|
294
298
|
*,
|
|
295
299
|
project_directory: Path | None = None,
|
|
296
|
-
detect_updates: bool = True,
|
|
297
300
|
) -> None:
|
|
298
301
|
"""Load a manifest preview, or skip if the same manifest is already showing results.
|
|
299
302
|
|
|
@@ -305,7 +308,6 @@ class SetupPreviewWidget(QWidget):
|
|
|
305
308
|
Args:
|
|
306
309
|
path_or_url: Manifest path or URL.
|
|
307
310
|
project_directory: Working directory for project sync actions.
|
|
308
|
-
detect_updates: Query package indices for newer versions.
|
|
309
311
|
"""
|
|
310
312
|
key = normalize_manifest_key(path_or_url)
|
|
311
313
|
|
|
@@ -345,7 +347,6 @@ class SetupPreviewWidget(QWidget):
|
|
|
345
347
|
self._run_preview_task(
|
|
346
348
|
path_or_url,
|
|
347
349
|
project_directory=self._model.project_directory,
|
|
348
|
-
detect_updates=detect_updates,
|
|
349
350
|
prerelease_packages=overrides,
|
|
350
351
|
),
|
|
351
352
|
)
|
|
@@ -474,7 +475,6 @@ class SetupPreviewWidget(QWidget):
|
|
|
474
475
|
path_or_url: str,
|
|
475
476
|
*,
|
|
476
477
|
project_directory: Path | None = None,
|
|
477
|
-
detect_updates: bool = True,
|
|
478
478
|
prerelease_packages: set[str] | None = None,
|
|
479
479
|
) -> None:
|
|
480
480
|
"""Run the preview coroutine and route completion/errors."""
|
|
@@ -484,7 +484,6 @@ class SetupPreviewWidget(QWidget):
|
|
|
484
484
|
path_or_url,
|
|
485
485
|
config=PreviewConfig(
|
|
486
486
|
project_directory=project_directory,
|
|
487
|
-
detect_updates=detect_updates,
|
|
488
487
|
prerelease_packages=prerelease_packages,
|
|
489
488
|
),
|
|
490
489
|
callbacks=PreviewCallbacks(
|
|
@@ -640,6 +639,16 @@ class SetupPreviewWidget(QWidget):
|
|
|
640
639
|
if card is not None:
|
|
641
640
|
card.set_check_result(result)
|
|
642
641
|
|
|
642
|
+
# Record in shared store so ToolsView can reflect the update
|
|
643
|
+
if self._package_store is not None and action.installer and action.package:
|
|
644
|
+
self._package_store.record_action_result(
|
|
645
|
+
action.installer,
|
|
646
|
+
str(action.package.name),
|
|
647
|
+
installed_version=result.installed_version or '',
|
|
648
|
+
available_version=result.available_version or '',
|
|
649
|
+
has_update=result.skip_reason == SkipReason.UPDATE_AVAILABLE,
|
|
650
|
+
)
|
|
651
|
+
|
|
643
652
|
# Update phase text
|
|
644
653
|
m.checked_count += 1
|
|
645
654
|
total = len(m.action_states)
|
|
@@ -987,11 +996,9 @@ class InstallPreviewWindow(QMainWindow):
|
|
|
987
996
|
logger.info('Starting install preview for: %s', self._manifest_url)
|
|
988
997
|
self._url_label.setText(f'<b>Manifest:</b> {self._manifest_url}')
|
|
989
998
|
|
|
990
|
-
detect = self._config.detect_updates if self._config else True
|
|
991
999
|
self._preview_widget.load(
|
|
992
1000
|
self._manifest_url,
|
|
993
1001
|
project_directory=self._project_directory,
|
|
994
|
-
detect_updates=detect,
|
|
995
1002
|
)
|
|
996
1003
|
|
|
997
1004
|
# --- Callbacks ---
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import logging
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
8
9
|
|
|
9
10
|
from porringer.api import API
|
|
10
11
|
from porringer.backend.command.core.discovery import DiscoveredPlugins
|
|
@@ -19,12 +20,15 @@ from PySide6.QtWidgets import (
|
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
from synodic_client.application.data import DataCoordinator
|
|
23
|
+
from synodic_client.application.package_state import PackageStateStore
|
|
22
24
|
from synodic_client.application.screen.install import SetupPreviewWidget
|
|
23
25
|
from synodic_client.application.screen.schema import PreviewPhase
|
|
24
26
|
from synodic_client.application.screen.sidebar import ManifestSidebar
|
|
25
27
|
from synodic_client.application.screen.spinner import LoadingIndicator
|
|
26
28
|
from synodic_client.application.theme import COMPACT_MARGINS
|
|
27
|
-
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from synodic_client.application.config_store import ConfigStore
|
|
28
32
|
|
|
29
33
|
logger = logging.getLogger(__name__)
|
|
30
34
|
|
|
@@ -41,24 +45,27 @@ class ProjectsView(QWidget):
|
|
|
41
45
|
def __init__(
|
|
42
46
|
self,
|
|
43
47
|
porringer: API,
|
|
44
|
-
|
|
48
|
+
store: ConfigStore,
|
|
45
49
|
parent: QWidget | None = None,
|
|
46
50
|
*,
|
|
47
51
|
coordinator: DataCoordinator | None = None,
|
|
52
|
+
package_store: PackageStateStore | None = None,
|
|
48
53
|
) -> None:
|
|
49
54
|
"""Initialize the projects view.
|
|
50
55
|
|
|
51
56
|
Args:
|
|
52
57
|
porringer: The porringer API instance.
|
|
53
|
-
|
|
58
|
+
store: The centralised :class:`ConfigStore`.
|
|
54
59
|
parent: Optional parent widget.
|
|
55
60
|
coordinator: Shared data coordinator for validated directory
|
|
56
61
|
data.
|
|
62
|
+
package_store: Shared package update state registry.
|
|
57
63
|
"""
|
|
58
64
|
super().__init__(parent)
|
|
59
65
|
self._porringer = porringer
|
|
60
|
-
self.
|
|
66
|
+
self._store = store
|
|
61
67
|
self._coordinator = coordinator
|
|
68
|
+
self._package_store = package_store
|
|
62
69
|
self._refresh_in_progress = False
|
|
63
70
|
self._pending_select: Path | None = None
|
|
64
71
|
self._widgets: dict[Path, SetupPreviewWidget] = {}
|
|
@@ -163,7 +170,6 @@ class ProjectsView(QWidget):
|
|
|
163
170
|
widget.load(
|
|
164
171
|
str(path),
|
|
165
172
|
project_directory=path if path.is_dir() else path.parent,
|
|
166
|
-
detect_updates=self._config.detect_updates,
|
|
167
173
|
)
|
|
168
174
|
|
|
169
175
|
except Exception:
|
|
@@ -196,7 +202,8 @@ class ProjectsView(QWidget):
|
|
|
196
202
|
self._porringer,
|
|
197
203
|
self,
|
|
198
204
|
show_close=False,
|
|
199
|
-
config=self.
|
|
205
|
+
config=self._store.config,
|
|
206
|
+
package_store=self._package_store,
|
|
200
207
|
)
|
|
201
208
|
widget._discovered_plugins = discovered
|
|
202
209
|
widget.install_finished.connect(self._on_install_finished)
|