driverclient 0.2.12__tar.gz → 0.2.13__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.12 → driverclient-0.2.13}/PKG-INFO +1 -1
- {driverclient-0.2.12 → driverclient-0.2.13}/pyproject.toml +1 -1
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/__init__.py +1 -1
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/config.py +6 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/ops/install.py +76 -3
- {driverclient-0.2.12 → driverclient-0.2.13}/.gitignore +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/README.md +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/__main__.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/config.json +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/core/__init__.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/core/hardware.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/core/http.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/core/hwid.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/core/proc.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/events.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/main.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/ops/__init__.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/ops/automate.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/ops/capture.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/ops/diagnose.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/ops/resolve.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/src/driverclient/ops/scan.py +0 -0
- {driverclient-0.2.12 → driverclient-0.2.13}/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.13
|
|
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.13"
|
|
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"
|
|
@@ -70,6 +70,12 @@ DEFAULTS: dict = {
|
|
|
70
70
|
# no new repo drivers or installs nothing.
|
|
71
71
|
"automate_rescan_passes": 3,
|
|
72
72
|
"force_resolve": False,
|
|
73
|
+
# The server now offers every driver matching each device (primary + extensions
|
|
74
|
+
# + companions). Before downloading each, skip it if its INF is already in the
|
|
75
|
+
# Windows driver store at the same-or-newer version — so a still-current big
|
|
76
|
+
# driver (e.g. graphics) is never re-downloaded, while missing/extension drivers
|
|
77
|
+
# still install. Set False to force-reinstall everything the server offers.
|
|
78
|
+
"skip_installed_drivers": True,
|
|
73
79
|
|
|
74
80
|
# ── HTTP timeouts (seconds) ───────────────────────────────────────────
|
|
75
81
|
"timeout_tiny": 10,
|
|
@@ -13,6 +13,7 @@ Download pipeline:
|
|
|
13
13
|
Public API:
|
|
14
14
|
resolve_and_install(force=False) -> InstallResult
|
|
15
15
|
"""
|
|
16
|
+
import re
|
|
16
17
|
import shutil
|
|
17
18
|
import subprocess
|
|
18
19
|
import time
|
|
@@ -86,6 +87,54 @@ class InstallResult:
|
|
|
86
87
|
return any(r.reboot_required for r in self.installed_ok)
|
|
87
88
|
|
|
88
89
|
|
|
90
|
+
def _parse_ver(s: str) -> tuple | None:
|
|
91
|
+
"""Extract an x.x.x.x version tuple from a string (e.g. a pnputil 'Driver
|
|
92
|
+
Version: 12/02/2021 1.0.3.0' line or a repo version_label). None if absent."""
|
|
93
|
+
m = re.search(r"(\d+)\.(\d+)\.(\d+)\.(\d+)", s or "")
|
|
94
|
+
return tuple(int(x) for x in m.groups()) if m else None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _installed_inf_index() -> dict:
|
|
98
|
+
"""Map original INF name (lowercase) → highest installed driver-version tuple,
|
|
99
|
+
parsed from `pnputil /enum-drivers`. Lets us skip re-downloading/installing a
|
|
100
|
+
driver the machine already has at the same-or-newer version. Best-effort: on any
|
|
101
|
+
error/parse miss it returns {} so nothing is ever wrongly skipped."""
|
|
102
|
+
idx: dict = {}
|
|
103
|
+
try:
|
|
104
|
+
r = proc.run(["pnputil", "/enum-drivers"], capture_output=True, text=True, timeout=120)
|
|
105
|
+
if r.returncode != 0:
|
|
106
|
+
return {}
|
|
107
|
+
cur = None
|
|
108
|
+
for line in r.stdout.splitlines():
|
|
109
|
+
low = line.lower()
|
|
110
|
+
if "original name" in low and ":" in line:
|
|
111
|
+
cur = line.split(":", 1)[1].strip().lower()
|
|
112
|
+
elif "driver version" in low and cur:
|
|
113
|
+
v = _parse_ver(line)
|
|
114
|
+
if v and (cur not in idx or v > idx[cur]):
|
|
115
|
+
idx[cur] = v
|
|
116
|
+
cur = None
|
|
117
|
+
except Exception:
|
|
118
|
+
return {}
|
|
119
|
+
return idx
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _already_in_store(driver: dict, idx: dict) -> bool:
|
|
123
|
+
"""True when every INF this driver ships is already in the driver store at a
|
|
124
|
+
same-or-newer version — so it needn't be downloaded/installed again. Conservative:
|
|
125
|
+
returns False (→ install) whenever the version or an INF can't be confirmed."""
|
|
126
|
+
if not idx:
|
|
127
|
+
return False
|
|
128
|
+
ver = _parse_ver(driver.get("version_label", ""))
|
|
129
|
+
if not ver:
|
|
130
|
+
return False
|
|
131
|
+
infs = [f["filename"].lower() for f in driver.get("files", [])
|
|
132
|
+
if f.get("file_role") == "inf" or f.get("filename", "").lower().endswith(".inf")]
|
|
133
|
+
if not infs:
|
|
134
|
+
return False
|
|
135
|
+
return all(idx.get(name) is not None and idx[name] >= ver for name in infs)
|
|
136
|
+
|
|
137
|
+
|
|
89
138
|
def resolve_and_install(force: bool = False) -> InstallResult:
|
|
90
139
|
"""
|
|
91
140
|
Run resolve (or load cache) then download + install all available drivers.
|
|
@@ -99,14 +148,38 @@ def resolve_and_install(force: bool = False) -> InstallResult:
|
|
|
99
148
|
total=0)
|
|
100
149
|
return InstallResult(trace_id=result.trace_id)
|
|
101
150
|
|
|
151
|
+
# Server now offers EVERY driver matching each device (primary + extensions +
|
|
152
|
+
# companions), so nothing is dropped by an assumption. Freshness is decided HERE
|
|
153
|
+
# against the real driver store: skip any package whose INF(s) are already present
|
|
154
|
+
# at the same-or-newer version — so a still-current big driver (graphics) is not
|
|
155
|
+
# re-downloaded, while missing/extension drivers still install. Toggle off with
|
|
156
|
+
# skip_installed_drivers=false to force-reinstall everything.
|
|
157
|
+
drivers = result.drivers
|
|
158
|
+
if cfg.get("skip_installed_drivers", True):
|
|
159
|
+
idx = _installed_inf_index()
|
|
160
|
+
kept = [d for d in drivers if not _already_in_store(d, idx)]
|
|
161
|
+
skipped = len(drivers) - len(kept)
|
|
162
|
+
if skipped:
|
|
163
|
+
events.progress(
|
|
164
|
+
"install",
|
|
165
|
+
f"[install] {skipped} driver(s) already current in the driver store — skipping",
|
|
166
|
+
skipped=skipped,
|
|
167
|
+
)
|
|
168
|
+
drivers = kept
|
|
169
|
+
if not drivers:
|
|
170
|
+
events.done("install",
|
|
171
|
+
"[install] Nothing to install — all resolved drivers already current",
|
|
172
|
+
total=0)
|
|
173
|
+
return InstallResult(trace_id=result.trace_id)
|
|
174
|
+
|
|
102
175
|
session_id = str(uuid.uuid4())
|
|
103
176
|
work_dir = Path(cfg.get("work_dir", "C:\\DriverServer\\client\\staging")) / session_id
|
|
104
177
|
work_dir.mkdir(parents=True, exist_ok=True)
|
|
105
178
|
|
|
106
179
|
events.start("install",
|
|
107
|
-
f"[install] {len(
|
|
108
|
-
total=len(
|
|
109
|
-
all_results = _stream_install(
|
|
180
|
+
f"[install] {len(drivers)} driver(s) — starting parallel downloads…",
|
|
181
|
+
total=len(drivers))
|
|
182
|
+
all_results = _stream_install(drivers, work_dir, cfg)
|
|
110
183
|
|
|
111
184
|
shutil.rmtree(work_dir, ignore_errors=True)
|
|
112
185
|
|
|
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
|