Skip to content

Commit 28d376a

Browse files
committed
Update main.py
1 parent bee2eb6 commit 28d376a

1 file changed

Lines changed: 223 additions & 52 deletions

File tree

main.py

Lines changed: 223 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,260 @@
1+
import asyncio
12
import json
2-
import os
33
import re
4-
import urllib.request as request
5-
import urllib.parse as parse
4+
from pathlib import Path
5+
from urllib.parse import quote
6+
import urllib.request
67

7-
pattern = re.compile(r"(.+)-([\d.]+)-shaded\.jar")
8+
# Compile pattern once at module level
9+
ARTIFACT_PATTERN = re.compile(r"(.+)-([\d.]+)-shaded\.jar")
810

911

10-
def read_properties(path: str) -> dict:
11-
with open(path, "r") as file:
12-
return json.load(file)
12+
class JenkinsAPIError(Exception):
13+
"""Custom exception for Jenkins API related errors."""
1314

15+
pass
16+
17+
18+
async def read_properties(path):
19+
"""Read and parse a JSON properties file asynchronously.
20+
21+
Args:
22+
path: Path to the JSON file
23+
24+
Returns:
25+
Dictionary containing the properties
26+
27+
Raises:
28+
FileNotFoundError: If the file doesn't exist
29+
json.JSONDecodeError: If the file contains invalid JSON
30+
"""
31+
try:
32+
loop = asyncio.get_event_loop()
33+
content = await loop.run_in_executor(None, path.read_text, "utf-8")
34+
return json.loads(content)
35+
except FileNotFoundError:
36+
print(f"Error: File not found: {path}")
37+
raise
38+
except json.JSONDecodeError as e:
39+
print(f"Error: Invalid JSON in {path}: {e}")
40+
raise
41+
42+
43+
async def read_folder(path):
44+
"""Read all JSON property files from a folder asynchronously.
45+
46+
Args:
47+
path: Path to the folder containing property files
48+
49+
Returns:
50+
List of dictionaries containing properties from each file
51+
"""
52+
folder_path = Path(path)
53+
54+
if not folder_path.exists():
55+
print(f"Warning: Folder not found: {path}")
56+
return []
57+
58+
json_files = [
59+
f for f in folder_path.iterdir() if f.is_file() and f.suffix == ".json"
60+
]
61+
62+
# Read all files concurrently
63+
tasks = [read_properties(file) for file in json_files]
64+
results = await asyncio.gather(*tasks, return_exceptions=True)
1465

15-
def read_folder(path: str) -> list[dict]:
1666
properties_arr = []
17-
for file in os.listdir(path):
18-
properties = read_properties(os.path.join(path, file))
19-
properties_arr.append(properties)
67+
for file, result in zip(json_files, results):
68+
if isinstance(result, Exception):
69+
print(f"Error reading {file}: {result}")
70+
else:
71+
properties_arr.append(result)
72+
2073
return properties_arr
2174

2275

23-
def fetch_from_official(jenkins_name: str) -> (str, str):
24-
api_url = f"https://ci.codemc.io/job/BetterGUI-MC/job/{parse.quote(jenkins_name)}/api/json?tree=builds[url]"
76+
async def fetch_from_official(jenkins_name):
77+
"""Fetch version and download URL from Jenkins CI asynchronously.
78+
79+
Args:
80+
jenkins_name: Name of the Jenkins job
81+
82+
Returns:
83+
Tuple of (version, artifact_url)
84+
85+
Raises:
86+
JenkinsAPIError: If unable to fetch or parse Jenkins data
87+
"""
88+
api_url = f"https://ci.codemc.io/job/BetterGUI-MC/job/{quote(jenkins_name)}/api/json?tree=builds[url]"
2589
print(f"Jenkins URL: {api_url}")
26-
api_res = json.load(request.urlopen(api_url))
27-
build_urls = [build["url"] for build in api_res["builds"]]
90+
91+
try:
92+
loop = asyncio.get_event_loop()
93+
response = await loop.run_in_executor(
94+
None, lambda: urllib.request.urlopen(api_url, timeout=30)
95+
)
96+
data = response.read().decode('utf-8')
97+
api_res = json.loads(data)
98+
except urllib.error.URLError as e:
99+
raise JenkinsAPIError(f"Failed to fetch Jenkins API: {e}")
100+
except json.JSONDecodeError as e:
101+
raise JenkinsAPIError(f"Invalid JSON response from Jenkins: {e}")
102+
103+
build_urls = [build["url"] for build in api_res.get("builds", [])]
104+
105+
if not build_urls:
106+
raise JenkinsAPIError("No builds found")
107+
28108
for build_url in build_urls:
29-
normalized_build_url = build_url[0:-1] if build_url.endswith("/") else build_url
30-
build_api_url = f"{normalized_build_url}/api/json?tree=artifacts[fileName,relativePath]"
109+
normalized_build_url = build_url.rstrip("/")
110+
build_api_url = (
111+
f"{normalized_build_url}/api/json?tree=artifacts[fileName,relativePath]"
112+
)
31113
print(f"Build URL: {build_api_url}")
32-
build_res = json.load(request.urlopen(build_api_url))
33-
artifacts = build_res["artifacts"]
114+
115+
try:
116+
loop = asyncio.get_event_loop()
117+
response = await loop.run_in_executor(
118+
None, lambda: urllib.request.urlopen(build_api_url, timeout=30)
119+
)
120+
data = response.read().decode('utf-8')
121+
build_res = json.loads(data)
122+
except (urllib.error.URLError, json.JSONDecodeError) as e:
123+
print(f"Warning: Failed to fetch build {build_url}: {e}")
124+
continue
125+
126+
artifacts = build_res.get("artifacts", [])
127+
34128
for artifact in artifacts:
35-
file_name = artifact["fileName"]
36-
relative_path = artifact["relativePath"]
37-
matcher = re.search(pattern, file_name)
38-
try:
129+
file_name = artifact.get("fileName", "")
130+
relative_path = artifact.get("relativePath", "")
131+
matcher = ARTIFACT_PATTERN.search(file_name)
132+
133+
if matcher:
39134
version = matcher.group(2)
40135
artifact_url = f"{normalized_build_url}/artifact/{relative_path}"
41136
print(f"Found: {file_name}")
42137
return version, artifact_url
43-
except:
44-
continue
45-
raise Exception("No download link & version found")
46138

139+
raise JenkinsAPIError("No valid artifact found in any build")
140+
141+
142+
async def convert(
143+
properties,
144+
file_extension = ".jar",
145+
):
146+
"""Convert properties to the output format asynchronously.
147+
148+
Args:
149+
properties: Dictionary containing addon properties
150+
file_extension: File extension to append to the name
47151
48-
def convert(properties: dict, file_extension: str = ".jar") -> (str, dict):
49-
prop_name = properties["name"]
152+
Returns:
153+
Tuple of (name, values_dict)
154+
"""
155+
prop_name = properties.get("name", "Unknown")
50156
name = prop_name
51-
print(f"Adding {name}")
157+
print(f"Processing {name}")
158+
52159
values = {
53160
"file-name": name + file_extension,
54-
"description": properties["description"],
55-
"authors": properties["author"],
56-
"source-code": properties["code"],
57-
"wiki": properties["wiki"]
161+
"description": properties.get("description", ""),
162+
"authors": properties.get("author", ""),
163+
"source-code": properties.get("code", ""),
164+
"wiki": properties.get("wiki", ""),
58165
}
59166

60-
prop_type = properties["type"] if "type" in properties else ""
167+
prop_type = properties.get("type", "")
168+
61169
if prop_type == "official":
62-
jenkins_name = properties["jenkins"]
63-
version, download_link = fetch_from_official(jenkins_name)
64-
values["version"] = version
65-
values["direct-link"] = download_link
170+
jenkins_name = properties.get("jenkins")
171+
if not jenkins_name:
172+
print(f"Warning: No Jenkins name specified for {name}")
173+
values["version"] = "unknown"
174+
values["direct-link"] = ""
175+
else:
176+
try:
177+
version, download_link = await fetch_from_official(jenkins_name)
178+
values["version"] = version
179+
values["direct-link"] = download_link
180+
except JenkinsAPIError as e:
181+
print(f"Error fetching from Jenkins for {name}: {e}")
182+
values["version"] = "unknown"
183+
values["direct-link"] = ""
66184
else:
67-
values["version"] = properties["version"]
68-
values["direct-link"] = properties["download"]
185+
values["version"] = properties.get("version", "unknown")
186+
values["direct-link"] = properties.get("download", "")
69187

188+
print(f"Completed {name}")
70189
return name, values
71190

72191

73-
def write(path: str, properties_dict: dict):
74-
with open(path, "w") as file:
75-
json_str = json.dumps(properties_dict, separators=(",", ":"))
76-
file.write(json_str)
192+
async def write(path, properties_dict):
193+
"""Write properties dictionary to a JSON file asynchronously.
194+
195+
Args:
196+
path: Output file path
197+
properties_dict: Dictionary to write
198+
"""
199+
try:
200+
loop = asyncio.get_event_loop()
201+
json_str = json.dumps(
202+
properties_dict, separators=(",", ":"), ensure_ascii=False
203+
)
204+
await loop.run_in_executor(None, Path(path).write_text, json_str, "utf-8")
205+
print(f"\nSuccessfully wrote to {path}")
206+
except IOError as e:
207+
print(f"Error writing to {path}: {e}")
208+
raise
209+
77210

211+
async def process_all_addons(properties_list):
212+
"""Process all addons concurrently and merge results.
78213
79-
def main():
80-
arr = read_folder("addons")
214+
Args:
215+
properties_list: List of addon properties
216+
217+
Returns:
218+
Dictionary with all processed addons merged
219+
"""
220+
# Process all addons concurrently
221+
tasks = [convert(properties) for properties in properties_list]
222+
results = await asyncio.gather(*tasks, return_exceptions=True)
223+
224+
# Merge results
81225
converted = {}
82-
for properties in arr:
83-
name, values = convert(properties)
84-
converted[name] = values
85-
write("addons.json", converted)
226+
for result in results:
227+
if isinstance(result, Exception):
228+
print(f"Error converting addon: {result}")
229+
else:
230+
name, values = result
231+
converted[name] = values
232+
233+
return converted
234+
235+
236+
async def main():
237+
"""Main function to process addons and generate output JSON asynchronously."""
238+
print("Starting async addon processing...\n")
239+
240+
# Read all property files
241+
properties_list = await read_folder("addons")
242+
243+
if not properties_list:
244+
print("No addon properties found. Exiting.")
245+
return
246+
247+
print(f"Found {len(properties_list)} addon(s) to process\n")
248+
249+
# Process all addons concurrently
250+
converted = await process_all_addons(properties_list)
251+
252+
if converted:
253+
await write("addons.json", converted)
254+
print(f"\nProcessed {len(converted)} addon(s) successfully")
255+
else:
256+
print("No addons were successfully processed.")
86257

87258

88-
if __name__ == '__main__':
89-
main()
259+
if __name__ == "__main__":
260+
asyncio.run(main())

0 commit comments

Comments
 (0)