-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstore_sources.py
More file actions
179 lines (150 loc) · 5.59 KB
/
Copy pathstore_sources.py
File metadata and controls
179 lines (150 loc) · 5.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/usr/bin/env python3
"""Store source health, retry, and fallback helpers."""
import shutil
import subprocess
import re
class StoreSourceError(RuntimeError):
def __init__(self, source_name, errors):
self.source_name = source_name
self.errors = list(errors)
super().__init__(f"{source_name}: {'; '.join(self.errors)}")
def _clean_output(value):
text = " ".join(str(value or "").strip().split())
return text.encode("ascii", "replace").decode("ascii")
def _version_from_output(output):
text = _clean_output(output)
match = re.search(r"\bv?\d+(?:\.\d+){1,5}(?:[-+][A-Za-z0-9_.-]+)?", text)
if match:
return match.group(0)
return text[:120]
def _creation_flags():
return getattr(subprocess, "CREATE_NO_WINDOW", 0)
def request_with_retries(source_name, request_callable, attempts=2, retry_statuses=None):
retry_statuses = set(retry_statuses or {408, 429, 500, 502, 503, 504})
errors = []
attempts = max(1, int(attempts or 1))
for attempt in range(1, attempts + 1):
try:
response = request_callable()
status_code = getattr(response, "status_code", None)
if status_code in retry_statuses and attempt < attempts:
errors.append(f"attempt {attempt}: HTTP {status_code}")
continue
response.raise_for_status()
return response, errors
except Exception as exc:
errors.append(f"attempt {attempt}: {type(exc).__name__}: {exc}")
if attempt >= attempts:
raise StoreSourceError(source_name, errors)
raise StoreSourceError(source_name, errors)
def detect_http_source(key, name, endpoint, request_callable):
status = {
"Key": key,
"Name": name,
"Kind": "http",
"Available": False,
"Detail": "Not checked",
"Endpoint": endpoint,
}
try:
response = request_callable()
code = getattr(response, "status_code", None)
if code is None or 200 <= code < 400:
status["Available"] = True
status["Detail"] = f"HTTP {code}" if code is not None else "Responded"
else:
status["Detail"] = f"HTTP {code}"
except Exception as exc:
status["Detail"] = f"{type(exc).__name__}: {exc}"
return status
def detect_command_source(key, name, commands, version_args=None, which=shutil.which, run=subprocess.run, timeout=5):
status = {
"Key": key,
"Name": name,
"Kind": "command",
"Available": False,
"Detail": "Command not found",
"Command": "",
"Version": "",
}
version_args = version_args or ["--version"]
for command in commands:
path = which(command)
if not path:
continue
status["Available"] = True
status["Command"] = command
status["Detail"] = f"Found at {path}"
try:
result = run(
[path] + list(version_args),
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=timeout,
creationflags=_creation_flags(),
)
output = result.stdout or result.stderr
if output:
status["Version"] = _version_from_output(output)
if result.returncode != 0:
status["Detail"] = f"Found at {path}; version probe exited {result.returncode}"
except Exception as exc:
status["Detail"] = f"Found at {path}; version probe failed: {type(exc).__name__}: {exc}"
return status
return status
def detect_source_health(storeedge_request, rgadguard_request, which=shutil.which, run=subprocess.run):
return [
detect_http_source(
"storeedgefd",
"Microsoft Store Search API",
"https://storeedgefd.dsx.mp.microsoft.com/v9.0/manifestSearch",
storeedge_request,
),
detect_http_source(
"rg-adguard",
"RG-Adguard package proxy",
"https://store.rg-adguard.net/api/GetFiles",
rgadguard_request,
),
detect_command_source(
"winget",
"WinGet msstore source",
["winget"],
["--version"],
which=which,
run=run,
),
detect_command_source(
"store-cli",
"Microsoft Store CLI",
["store"],
["--help"],
which=which,
run=run,
),
]
def package_lookup_fallbacks(product_id, statuses):
available = {status.get("Key"): status for status in statuses if status.get("Available")}
fallbacks = []
if "winget" in available:
fallbacks.append({
"Source": "WinGet",
"Command": f"winget install --source msstore --id {product_id} --accept-source-agreements --accept-package-agreements",
"Detail": "Installs from the WinGet msstore source when direct package URLs are unavailable.",
})
if "store-cli" in available:
fallbacks.append({
"Source": "Store CLI",
"Command": f"store install {product_id}",
"Detail": "Requires Microsoft Store to be enabled on the PC.",
})
return fallbacks
def source_status_summary(status):
prefix = "available" if status.get("Available") else "unavailable"
detail = status.get("Detail") or ""
version = status.get("Version") or ""
if version:
detail = f"{detail}; {version}" if detail else version
return f"{status.get('Name', 'Source')}: {prefix} ({detail})"