synodic-client 0.0.1.dev39__tar.gz → 0.0.1.dev40__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.dev39 → synodic_client-0.0.1.dev40}/PKG-INFO +2 -2
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/pyproject.toml +4 -4
- synodic_client-0.0.1.dev40/synodic_client/_version.py +1 -0
- synodic_client-0.0.1.dev40/synodic_client/application/data.py +192 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/screen/__init__.py +1 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/screen/install.py +15 -5
- synodic_client-0.0.1.dev40/synodic_client/application/screen/screen.py +2115 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/screen/spinner.py +35 -8
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/screen/tray.py +224 -19
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/theme.py +159 -13
- synodic_client-0.0.1.dev40/synodic_client/application/workers.py +164 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/config.py +12 -4
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/resolution.py +90 -2
- synodic_client-0.0.1.dev40/tests/unit/qt/test_gather_packages.py +755 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/qt/test_install_preview.py +19 -18
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/qt/test_logging.py +2 -2
- synodic_client-0.0.1.dev40/tests/unit/qt/test_tray_window_show.py +69 -0
- synodic_client-0.0.1.dev40/tests/unit/qt/test_update_feedback.py +393 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/test_config.py +12 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/test_resolution.py +86 -0
- synodic_client-0.0.1.dev40/tests/unit/test_workers.py +55 -0
- synodic_client-0.0.1.dev39/synodic_client/_version.py +0 -1
- synodic_client-0.0.1.dev39/synodic_client/application/screen/screen.py +0 -810
- synodic_client-0.0.1.dev39/synodic_client/application/workers.py +0 -100
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/LICENSE.md +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/README.md +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/__init__.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/__main__.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/__init__.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/bootstrap.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/icon.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/instance.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/qt.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/screen/action_card.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/screen/card.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/screen/log_panel.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/screen/settings.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/screen/sidebar.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/screen/update_banner.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/application/uri.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/cli.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/client.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/logging.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/protocol.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/py.typed +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/startup.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/synodic_client/updater.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/__init__.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/conftest.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/__init__.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/qt/__init__.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/qt/conftest.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/qt/test_action_card.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/qt/test_log_panel.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/qt/test_preview_model.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/qt/test_settings.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/qt/test_sidebar.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/qt/test_update_banner.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/test_cli.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/test_client_updater.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/test_client_version.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/test_examples.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/test_install.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/test_updater.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/test_uri.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/windows/__init__.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/windows/conftest.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/tests/unit/windows/test_protocol.py +0 -0
- {synodic_client-0.0.1.dev39 → synodic_client-0.0.1.dev40}/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.dev40
|
|
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.dev71
|
|
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.dev71",
|
|
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.dev40"
|
|
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.4",
|
|
39
|
-
"pyrefly>=0.
|
|
39
|
+
"pyrefly>=0.55.0",
|
|
40
40
|
]
|
|
41
41
|
test = [
|
|
42
42
|
"pytest>=9.0.2",
|
|
@@ -109,7 +109,7 @@ write_template = "__version__ = '{}'\n"
|
|
|
109
109
|
allow-prereleases = true
|
|
110
110
|
|
|
111
111
|
[tool.pdm.scripts]
|
|
112
|
-
analyze = "ruff check
|
|
112
|
+
analyze = "ruff check"
|
|
113
113
|
format = "ruff format"
|
|
114
114
|
test = "pytest --cov=synodic_client --verbose tests"
|
|
115
115
|
type-check = "pyrefly check"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.0.1.dev40'
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Shared data coordinator for the Synodic Client application.
|
|
2
|
+
|
|
3
|
+
Centralises porringer API calls so that plugin discovery, directory
|
|
4
|
+
listing, and runtime context resolution happen once per refresh cycle
|
|
5
|
+
and the results are reused by every consumer (ToolsView, ProjectsView,
|
|
6
|
+
TrayScreen, install workers).
|
|
7
|
+
|
|
8
|
+
The coordinator follows an *invalidate-on-mutation* strategy: callers
|
|
9
|
+
that modify state (install, uninstall, add/remove directory) call
|
|
10
|
+
:meth:`invalidate` to force the next :meth:`refresh` to re-fetch.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import logging
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
|
|
19
|
+
from porringer.api import API
|
|
20
|
+
from porringer.backend.command.core.discovery import DiscoveredPlugins
|
|
21
|
+
from porringer.core.plugin_schema.plugin_manager import PluginManager
|
|
22
|
+
from porringer.schema import (
|
|
23
|
+
CheckParameters,
|
|
24
|
+
CheckResult,
|
|
25
|
+
DirectoryValidationResult,
|
|
26
|
+
ManifestDirectory,
|
|
27
|
+
PluginInfo,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(slots=True)
|
|
34
|
+
class Snapshot:
|
|
35
|
+
"""Immutable bundle of data produced by a single refresh cycle.
|
|
36
|
+
|
|
37
|
+
All fields are populated by :meth:`DataCoordinator.refresh` and
|
|
38
|
+
remain stable until the next refresh.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
plugins: list[PluginInfo] = field(default_factory=list)
|
|
42
|
+
"""All discovered plugins with install status and version info."""
|
|
43
|
+
|
|
44
|
+
directories: list[ManifestDirectory] = field(default_factory=list)
|
|
45
|
+
"""Cached project directories (un-validated)."""
|
|
46
|
+
|
|
47
|
+
validated_directories: list[DirectoryValidationResult] = field(default_factory=list)
|
|
48
|
+
"""Cached directories with ``exists`` / ``has_manifest`` validation."""
|
|
49
|
+
|
|
50
|
+
discovered: DiscoveredPlugins | None = None
|
|
51
|
+
"""Full plugin discovery result including runtime context."""
|
|
52
|
+
|
|
53
|
+
plugin_managers: dict[str, PluginManager] = field(default_factory=dict)
|
|
54
|
+
"""Project-environment plugins implementing the ``PluginManager`` protocol."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class DataCoordinator:
|
|
58
|
+
"""Single source of truth for porringer data across the application.
|
|
59
|
+
|
|
60
|
+
Usage::
|
|
61
|
+
|
|
62
|
+
coordinator = DataCoordinator(porringer)
|
|
63
|
+
snapshot = await coordinator.refresh() # first load
|
|
64
|
+
# … later, after an install …
|
|
65
|
+
coordinator.invalidate()
|
|
66
|
+
snapshot = await coordinator.refresh() # re-fetches everything
|
|
67
|
+
|
|
68
|
+
The coordinator caches the most recent :class:`Snapshot` so that
|
|
69
|
+
synchronous property access (``coordinator.snapshot``) is available
|
|
70
|
+
between refresh cycles.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, porringer: API) -> None:
|
|
74
|
+
"""Initialize the coordinator with a porringer API instance."""
|
|
75
|
+
self._porringer = porringer
|
|
76
|
+
self._snapshot: Snapshot = Snapshot()
|
|
77
|
+
self._stale = True
|
|
78
|
+
self._refresh_lock = asyncio.Lock()
|
|
79
|
+
|
|
80
|
+
# -- Public API --------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def snapshot(self) -> Snapshot:
|
|
84
|
+
"""Return the most recent snapshot (may be empty before first refresh)."""
|
|
85
|
+
return self._snapshot
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def discovered_plugins(self) -> DiscoveredPlugins | None:
|
|
89
|
+
"""Shortcut to the current ``DiscoveredPlugins`` instance."""
|
|
90
|
+
return self._snapshot.discovered
|
|
91
|
+
|
|
92
|
+
def invalidate(self) -> None:
|
|
93
|
+
"""Mark the cached data as stale.
|
|
94
|
+
|
|
95
|
+
The next call to :meth:`refresh` will re-fetch everything from
|
|
96
|
+
porringer. This is a lightweight O(1) flag-flip.
|
|
97
|
+
"""
|
|
98
|
+
self._stale = True
|
|
99
|
+
|
|
100
|
+
async def refresh(self, *, force: bool = False) -> Snapshot:
|
|
101
|
+
"""Fetch fresh data from porringer if stale (or *force* is set).
|
|
102
|
+
|
|
103
|
+
Multiple concurrent callers are coalesced via an ``asyncio.Lock``
|
|
104
|
+
so that only one discovery + listing round-trip runs at a time.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
The populated :class:`Snapshot`.
|
|
108
|
+
"""
|
|
109
|
+
if not self._stale and not force:
|
|
110
|
+
return self._snapshot
|
|
111
|
+
|
|
112
|
+
async with self._refresh_lock:
|
|
113
|
+
# Double-check after acquiring the lock — another coroutine
|
|
114
|
+
# may have already refreshed while we were waiting.
|
|
115
|
+
if not self._stale and not force:
|
|
116
|
+
return self._snapshot
|
|
117
|
+
|
|
118
|
+
self._snapshot = await self._fetch()
|
|
119
|
+
self._stale = False
|
|
120
|
+
return self._snapshot
|
|
121
|
+
|
|
122
|
+
async def check_updates(
|
|
123
|
+
self,
|
|
124
|
+
plugins: list[str] | None = None,
|
|
125
|
+
) -> list[CheckResult]:
|
|
126
|
+
"""Run update detection using the cached ``DiscoveredPlugins``.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
plugins: Optional include-set of plugin names. ``None``
|
|
130
|
+
means all plugins.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
A list of :class:`CheckResult` per plugin.
|
|
134
|
+
"""
|
|
135
|
+
params = CheckParameters(plugins=plugins)
|
|
136
|
+
return await self._porringer.sync.check_updates(
|
|
137
|
+
params,
|
|
138
|
+
plugins=self._snapshot.discovered,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# -- Internals ---------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
async def _fetch(self) -> Snapshot:
|
|
144
|
+
"""Run the full discovery + listing pipeline.
|
|
145
|
+
|
|
146
|
+
1. ``API.discover_plugins()`` — plugin entry-points + runtime
|
|
147
|
+
context in one shot.
|
|
148
|
+
2. ``PluginCommands.list()`` — installed status + versions,
|
|
149
|
+
passing the already-discovered ``DiscoveredPlugins``.
|
|
150
|
+
3. ``cache.list_directories(validate=True, check_manifest=True)``
|
|
151
|
+
— directory listing with validation baked in.
|
|
152
|
+
4. Filter ``project_environments`` for ``PluginManager`` instances.
|
|
153
|
+
|
|
154
|
+
All blocking calls are dispatched via ``asyncio.to_thread``.
|
|
155
|
+
"""
|
|
156
|
+
loop = asyncio.get_running_loop()
|
|
157
|
+
|
|
158
|
+
# Step 1: discover all plugins + resolve runtime context
|
|
159
|
+
discovered = await API.discover_plugins()
|
|
160
|
+
|
|
161
|
+
# Step 2 + 3 in parallel: plugin list + validated directories
|
|
162
|
+
plugins_task = asyncio.create_task(
|
|
163
|
+
self._porringer.plugin.list(plugins=discovered),
|
|
164
|
+
)
|
|
165
|
+
dirs_future = loop.run_in_executor(
|
|
166
|
+
None,
|
|
167
|
+
lambda: self._porringer.cache.list_directories(
|
|
168
|
+
validate=True,
|
|
169
|
+
check_manifest=True,
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
plugins = await plugins_task
|
|
174
|
+
validated = await dirs_future
|
|
175
|
+
|
|
176
|
+
# Step 4: extract PluginManager instances from project_environments
|
|
177
|
+
managers: dict[str, PluginManager] = {}
|
|
178
|
+
for _name, env in discovered.project_environments.items():
|
|
179
|
+
if isinstance(env, PluginManager) and env.is_available():
|
|
180
|
+
managers[env.tool_name()] = env
|
|
181
|
+
|
|
182
|
+
# Derive the un-validated directory list for callers that only
|
|
183
|
+
# need path + name (e.g. _gather_packages).
|
|
184
|
+
directories = [r.directory for r in validated]
|
|
185
|
+
|
|
186
|
+
return Snapshot(
|
|
187
|
+
plugins=plugins,
|
|
188
|
+
directories=directories,
|
|
189
|
+
validated_directories=validated,
|
|
190
|
+
discovered=discovered,
|
|
191
|
+
plugin_managers=managers,
|
|
192
|
+
)
|
|
@@ -43,6 +43,7 @@ def plugin_kind_group_label(kind: PluginKind) -> str:
|
|
|
43
43
|
|
|
44
44
|
SKIP_REASON_LABELS: dict[SkipReason, str] = {
|
|
45
45
|
SkipReason.ALREADY_INSTALLED: 'Already installed',
|
|
46
|
+
SkipReason.NOT_INSTALLED: 'Not installed',
|
|
46
47
|
SkipReason.ALREADY_LATEST: 'Already latest',
|
|
47
48
|
SkipReason.NO_PROJECT_DIRECTORY: 'No project directory',
|
|
48
49
|
SkipReason.UPDATE_AVAILABLE: 'Update available',
|
|
@@ -24,6 +24,7 @@ from urllib.parse import urlparse
|
|
|
24
24
|
from urllib.request import url2pathname
|
|
25
25
|
|
|
26
26
|
from porringer.api import API
|
|
27
|
+
from porringer.backend.command.core.discovery import DiscoveredPlugins
|
|
27
28
|
from porringer.schema import (
|
|
28
29
|
DownloadParameters,
|
|
29
30
|
ProgressEvent,
|
|
@@ -230,6 +231,8 @@ async def run_install(
|
|
|
230
231
|
manifest_path: Path,
|
|
231
232
|
config: InstallConfig | None = None,
|
|
232
233
|
callbacks: InstallCallbacks | None = None,
|
|
234
|
+
*,
|
|
235
|
+
plugins: DiscoveredPlugins | None = None,
|
|
233
236
|
) -> SetupResults:
|
|
234
237
|
"""Execute setup actions via porringer and stream progress.
|
|
235
238
|
|
|
@@ -243,6 +246,8 @@ async def run_install(
|
|
|
243
246
|
config: Optional execution parameters (directory, strategy,
|
|
244
247
|
prerelease overrides).
|
|
245
248
|
callbacks: Optional progress callbacks.
|
|
249
|
+
plugins: Pre-discovered plugins to pass through to porringer,
|
|
250
|
+
avoiding redundant discovery.
|
|
246
251
|
|
|
247
252
|
Returns:
|
|
248
253
|
Aggregated :class:`SetupResults`.
|
|
@@ -259,7 +264,7 @@ async def run_install(
|
|
|
259
264
|
collected: list[SetupActionResult] = []
|
|
260
265
|
manifest_result: SetupResults | None = None
|
|
261
266
|
|
|
262
|
-
async for event in porringer.sync.execute_stream(params):
|
|
267
|
+
async for event in porringer.sync.execute_stream(params, plugins=plugins):
|
|
263
268
|
if event.kind == ProgressEventKind.MANIFEST_LOADED and event.manifest:
|
|
264
269
|
manifest_result = event.manifest
|
|
265
270
|
actions = list(event.manifest.actions)
|
|
@@ -275,7 +280,7 @@ async def run_install(
|
|
|
275
280
|
):
|
|
276
281
|
cb.on_sub_progress(event.action, event.sub_action)
|
|
277
282
|
|
|
278
|
-
if event.kind == ProgressEventKind.ACTION_COMPLETED and event.result:
|
|
283
|
+
if event.kind == ProgressEventKind.ACTION_COMPLETED and event.result and event.action:
|
|
279
284
|
collected.append(event.result)
|
|
280
285
|
if cb.on_progress is not None:
|
|
281
286
|
cb.on_progress(event.action, event.result)
|
|
@@ -340,6 +345,7 @@ class SetupPreviewWidget(QWidget):
|
|
|
340
345
|
self._porringer = porringer
|
|
341
346
|
self._show_close = show_close
|
|
342
347
|
self._config = config
|
|
348
|
+
self._discovered_plugins: DiscoveredPlugins | None = None
|
|
343
349
|
|
|
344
350
|
self._model = PreviewModel()
|
|
345
351
|
self._task: asyncio.Task[None] | None = None
|
|
@@ -705,6 +711,7 @@ class SetupPreviewWidget(QWidget):
|
|
|
705
711
|
on_preview_ready=self._on_preview_resolved,
|
|
706
712
|
on_action_checked=self._on_action_checked,
|
|
707
713
|
),
|
|
714
|
+
plugins=self._discovered_plugins,
|
|
708
715
|
)
|
|
709
716
|
self._on_preview_finished()
|
|
710
717
|
except asyncio.CancelledError:
|
|
@@ -729,6 +736,7 @@ class SetupPreviewWidget(QWidget):
|
|
|
729
736
|
on_sub_progress=self._on_sub_progress,
|
|
730
737
|
on_progress=self._on_action_progress,
|
|
731
738
|
),
|
|
739
|
+
plugins=self._discovered_plugins,
|
|
732
740
|
)
|
|
733
741
|
self._on_install_finished(results)
|
|
734
742
|
except asyncio.CancelledError:
|
|
@@ -1259,8 +1267,7 @@ async def _resolve_manifest_path(url: str) -> tuple[Path, str | None]:
|
|
|
1259
1267
|
dest = Path(temp_dir) / 'porringer.json'
|
|
1260
1268
|
|
|
1261
1269
|
params = DownloadParameters(url=url, destination=dest, timeout=3)
|
|
1262
|
-
|
|
1263
|
-
result = await loop.run_in_executor(None, API.download, params)
|
|
1270
|
+
result = await API.download(params)
|
|
1264
1271
|
|
|
1265
1272
|
if not result.success:
|
|
1266
1273
|
_safe_rmtree(temp_dir)
|
|
@@ -1314,6 +1321,7 @@ async def run_preview(
|
|
|
1314
1321
|
*,
|
|
1315
1322
|
config: PreviewConfig | None = None,
|
|
1316
1323
|
callbacks: PreviewCallbacks | None = None,
|
|
1324
|
+
plugins: DiscoveredPlugins | None = None,
|
|
1317
1325
|
) -> None:
|
|
1318
1326
|
"""Download a manifest and perform a dry-run preview.
|
|
1319
1327
|
|
|
@@ -1331,6 +1339,8 @@ async def run_preview(
|
|
|
1331
1339
|
url: Manifest URL or local path.
|
|
1332
1340
|
config: Optional preview configuration.
|
|
1333
1341
|
callbacks: Optional preview callbacks.
|
|
1342
|
+
plugins: Pre-discovered plugins to pass through to porringer,
|
|
1343
|
+
avoiding redundant discovery.
|
|
1334
1344
|
"""
|
|
1335
1345
|
logger.info('run_preview starting for: %s', url)
|
|
1336
1346
|
temp_dir: str | None = None
|
|
@@ -1351,7 +1361,7 @@ async def run_preview(
|
|
|
1351
1361
|
temp_dir_str = temp_dir or ''
|
|
1352
1362
|
manifest_path_str = str(manifest_path)
|
|
1353
1363
|
|
|
1354
|
-
async for event in porringer.sync.execute_stream(setup_params):
|
|
1364
|
+
async for event in porringer.sync.execute_stream(setup_params, plugins=plugins):
|
|
1355
1365
|
_dispatch_preview_event(
|
|
1356
1366
|
event,
|
|
1357
1367
|
manifest_path_str,
|