driverclient 0.2.18__tar.gz → 0.2.19__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.
- {driverclient-0.2.18 → driverclient-0.2.19}/PKG-INFO +1 -1
- {driverclient-0.2.18 → driverclient-0.2.19}/pyproject.toml +1 -1
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/__init__.py +1 -1
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/ops/install.py +54 -9
- {driverclient-0.2.18 → driverclient-0.2.19}/.gitignore +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/README.md +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/__main__.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/config.json +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/config.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/core/__init__.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/core/hardware.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/core/http.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/core/hwid.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/core/proc.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/events.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/main.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/ops/__init__.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/ops/automate.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/ops/capture.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/ops/diagnose.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/ops/resolve.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/ops/scan.py +0 -0
- {driverclient-0.2.18 → driverclient-0.2.19}/src/driverclient/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: driverclient
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.19
|
|
4
4
|
Summary: Driver Server client with an embeddable connector for config injection at runtime.
|
|
5
5
|
Project-URL: Homepage, https://example.com/driverclient
|
|
6
6
|
Author-email: Raja Sanaullah <sanaullah@99technologies.com>
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "driverclient"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.19"
|
|
8
8
|
description = "Driver Server client with an embeddable connector for config injection at runtime."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -68,6 +68,7 @@ class DriverResult:
|
|
|
68
68
|
error: str = ""
|
|
69
69
|
duration_ms: int = 0
|
|
70
70
|
reboot_required: bool = False # installed OK but needs a reboot (pnputil 3010)
|
|
71
|
+
skipped: bool = False # already present in the driver store — not re-fetched/installed
|
|
71
72
|
|
|
72
73
|
|
|
73
74
|
@dataclass
|
|
@@ -154,9 +155,17 @@ def resolve_and_install(force: bool = False) -> InstallResult:
|
|
|
154
155
|
# at the same-or-newer version — so a still-current big driver (graphics) is not
|
|
155
156
|
# re-downloaded, while missing/extension drivers still install. Toggle off with
|
|
156
157
|
# skip_installed_drivers=false to force-reinstall everything.
|
|
158
|
+
# Live driver-store index: INF name -> highest installed version. Built once,
|
|
159
|
+
# then UPDATED as each driver installs this pass (see _record_installed) so a
|
|
160
|
+
# later package of the same driver family (same INF) at an equal-or-older
|
|
161
|
+
# version is skipped — the driver store already has it. This is skip-if-present
|
|
162
|
+
# (idempotency), NOT deduplication: no repo package is removed; a newer repo
|
|
163
|
+
# version still installs over an older store version (store_ver >= repo_ver skips).
|
|
164
|
+
skip_installed = cfg.get("skip_installed_drivers", True)
|
|
165
|
+
idx: dict = _installed_inf_index() if skip_installed else {}
|
|
166
|
+
|
|
157
167
|
drivers = result.drivers
|
|
158
|
-
if
|
|
159
|
-
idx = _installed_inf_index()
|
|
168
|
+
if skip_installed:
|
|
160
169
|
kept = [d for d in drivers if not _already_in_store(d, idx)]
|
|
161
170
|
skipped = len(drivers) - len(kept)
|
|
162
171
|
if skipped:
|
|
@@ -179,21 +188,24 @@ def resolve_and_install(force: bool = False) -> InstallResult:
|
|
|
179
188
|
events.start("install",
|
|
180
189
|
f"[install] {len(drivers)} driver(s) — starting parallel downloads…",
|
|
181
190
|
total=len(drivers))
|
|
182
|
-
all_results = _stream_install(drivers, work_dir, cfg)
|
|
191
|
+
all_results = _stream_install(drivers, work_dir, cfg, idx, skip_installed)
|
|
183
192
|
|
|
184
193
|
shutil.rmtree(work_dir, ignore_errors=True)
|
|
185
194
|
|
|
186
|
-
installed_ok = [r for r in all_results if r.success]
|
|
195
|
+
installed_ok = [r for r in all_results if r.success and not r.skipped]
|
|
196
|
+
skipped_now = [r for r in all_results if r.skipped]
|
|
187
197
|
install_failed = [r for r in all_results if not r.success]
|
|
188
198
|
|
|
189
|
-
|
|
199
|
+
# Skipped drivers ARE satisfied on the machine — report them upstream as success.
|
|
200
|
+
_confirm(result.trace_id, installed_ok + skipped_now, install_failed, cfg)
|
|
190
201
|
|
|
191
202
|
reboot_required = any(r.reboot_required for r in installed_ok)
|
|
192
203
|
reboot_tag = " — reboot required" if reboot_required else ""
|
|
204
|
+
skip_tag = f" ({len(skipped_now)} already in store)" if skipped_now else ""
|
|
193
205
|
events.done("install",
|
|
194
|
-
f"[install] Done — {len(installed_ok)} ok {len(install_failed)} failed{reboot_tag}",
|
|
206
|
+
f"[install] Done — {len(installed_ok)} ok {len(install_failed)} failed{skip_tag}{reboot_tag}",
|
|
195
207
|
total=len(all_results), ok=len(installed_ok), failed=len(install_failed),
|
|
196
|
-
reboot_required=reboot_required)
|
|
208
|
+
skipped=len(skipped_now), reboot_required=reboot_required)
|
|
197
209
|
for r in install_failed:
|
|
198
210
|
events.error("install", f" ✗ {r.binding_id}: {r.error}",
|
|
199
211
|
binding_id=r.binding_id, error=r.error)
|
|
@@ -207,10 +219,16 @@ def resolve_and_install(force: bool = False) -> InstallResult:
|
|
|
207
219
|
|
|
208
220
|
# ── Streaming pipeline ──────────────────────────────────────────────────────────
|
|
209
221
|
|
|
210
|
-
def _stream_install(drivers: list[dict], base: Path, cfg: dict
|
|
222
|
+
def _stream_install(drivers: list[dict], base: Path, cfg: dict,
|
|
223
|
+
idx: dict, skip_installed: bool) -> list[DriverResult]:
|
|
211
224
|
"""
|
|
212
225
|
Submit ALL downloads (all levels) simultaneously, then install level-by-level.
|
|
213
226
|
Each driver installs the moment its own download Future completes.
|
|
227
|
+
|
|
228
|
+
`idx` is the live driver-store index (INF -> installed version); it is read to
|
|
229
|
+
skip a driver the store already has and updated after each successful install
|
|
230
|
+
so same-family duplicates later in the pass are skipped too. Installs run one
|
|
231
|
+
at a time here (main thread), so mutating `idx` needs no lock.
|
|
214
232
|
"""
|
|
215
233
|
repo_url = cfg["local_repo_url"]
|
|
216
234
|
parallel = cfg.get("parallel_downloads", 6)
|
|
@@ -225,7 +243,7 @@ def _stream_install(drivers: list[dict], base: Path, cfg: dict) -> list[DriverRe
|
|
|
225
243
|
f"[install] {_level_label(level)} ({len(level_futs[level])} driver(s))…",
|
|
226
244
|
category=_level_label(level), count=len(level_futs[level]))
|
|
227
245
|
for fut in as_completed(level_futs[level]):
|
|
228
|
-
all_results.append(_process_completed(fut, future_info, cfg))
|
|
246
|
+
all_results.append(_process_completed(fut, future_info, cfg, idx, skip_installed))
|
|
229
247
|
|
|
230
248
|
executor.shutdown(wait=False)
|
|
231
249
|
return all_results
|
|
@@ -266,12 +284,23 @@ def _process_completed(
|
|
|
266
284
|
fut: Future,
|
|
267
285
|
future_info: dict[Future, tuple[int, dict]],
|
|
268
286
|
cfg: dict,
|
|
287
|
+
idx: dict,
|
|
288
|
+
skip_installed: bool,
|
|
269
289
|
) -> DriverResult:
|
|
270
290
|
"""Handle one completed download Future: install if ok, return DriverResult."""
|
|
271
291
|
_, driver = future_info[fut]
|
|
272
292
|
bid = driver.get("binding_id", "")
|
|
273
293
|
cat = driver.get("category", "other")
|
|
274
294
|
|
|
295
|
+
# A driver installed earlier THIS pass may have added the same INF (another
|
|
296
|
+
# package of the same driver family), so a package that was missing at
|
|
297
|
+
# pass-start can now already be in the store at an equal-or-newer version.
|
|
298
|
+
# Re-check against the live index and skip the redundant re-install.
|
|
299
|
+
if skip_installed and _already_in_store(driver, idx):
|
|
300
|
+
events.ok("install", f" ⭑ [{cat}] {bid} already in driver store — skipped",
|
|
301
|
+
binding_id=bid, category=cat, skipped=True)
|
|
302
|
+
return DriverResult(binding_id=bid, success=True, skipped=True)
|
|
303
|
+
|
|
275
304
|
try:
|
|
276
305
|
dest, dl_ok = fut.result()
|
|
277
306
|
except Exception as e:
|
|
@@ -283,6 +312,8 @@ def _process_completed(
|
|
|
283
312
|
t0 = time.monotonic()
|
|
284
313
|
ok, err, reboot = _install_driver(driver, dest, cfg)
|
|
285
314
|
dur = int((time.monotonic() - t0) * 1000)
|
|
315
|
+
if ok:
|
|
316
|
+
_record_installed(driver, idx) # update live index so same-family dupes skip
|
|
286
317
|
tag = " (reboot required)" if reboot else ""
|
|
287
318
|
line = f" {'✓' if ok else '✗'} [{cat}] {bid} ({dur}ms){tag}"
|
|
288
319
|
emit = events.ok if ok else events.error
|
|
@@ -292,6 +323,20 @@ def _process_completed(
|
|
|
292
323
|
duration_ms=dur, reboot_required=reboot)
|
|
293
324
|
|
|
294
325
|
|
|
326
|
+
def _record_installed(driver: dict, idx: dict) -> None:
|
|
327
|
+
"""Fold a just-installed driver's INF(s)+version into the live store index, so a
|
|
328
|
+
later package of the same driver family at an equal-or-older version is skipped
|
|
329
|
+
this pass — mirrors what `pnputil /enum-drivers` will report on the next scan."""
|
|
330
|
+
ver = _parse_ver(driver.get("version_label", ""))
|
|
331
|
+
if not ver:
|
|
332
|
+
return
|
|
333
|
+
for f in driver.get("files", []):
|
|
334
|
+
name = f.get("filename", "").lower()
|
|
335
|
+
if f.get("file_role") == "inf" or name.endswith(".inf"):
|
|
336
|
+
if idx.get(name) is None or ver > idx[name]:
|
|
337
|
+
idx[name] = ver
|
|
338
|
+
|
|
339
|
+
|
|
295
340
|
def _download_driver(driver: dict, base: Path, repo_url: str,
|
|
296
341
|
cfg: dict) -> tuple[Path, bool]:
|
|
297
342
|
"""Download all files for one driver. Returns (dest_dir, success)."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|