@@ -100,6 +100,338 @@ def update_java_path():
100100 print (f"Java already in PATH: { java_bin_path } " )
101101
102102
103+ async def _get_jdk_download_url ():
104+ """Get the appropriate JDK 21 LTS download URL based on platform"""
105+ system = platform .system ()
106+
107+ if system == "Windows" :
108+ return "https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.zip"
109+ elif system == "Linux" :
110+ return "https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz"
111+ elif system == "Darwin" :
112+ # macOS - use ARM64 for Apple Silicon or x64 for Intel
113+ import subprocess
114+ try :
115+ # Check if running on Apple Silicon
116+ result = subprocess .run (["uname" , "-m" ], capture_output = True , text = True )
117+ arch = result .stdout .strip ()
118+ if arch == "arm64" :
119+ return "https://download.oracle.com/java/21/latest/jdk-21_macos-aarch64_bin.tar.gz"
120+ else :
121+ return "https://download.oracle.com/java/21/latest/jdk-21_macos-x64_bin.tar.gz"
122+ except :
123+ # Default to x64 if detection fails
124+ return "https://download.oracle.com/java/21/latest/jdk-21_macos-x64_bin.tar.gz"
125+ else :
126+ raise OSError (f"Unsupported platform: { system } " )
127+
128+
129+ async def _download_jdk ():
130+ """Download JDK 21 LTS with progress reporting"""
131+ print ("[installer][android-jdk] Downloading JDK 21 LTS..." )
132+ await send_response ({
133+ "action" : "status" ,
134+ "data" : {
135+ "category" : "Android" ,
136+ "name" : "Java" ,
137+ "status" : "installing" ,
138+ "comment" : "Downloading Java 21..." ,
139+ }
140+ })
141+
142+ jdk_url = await _get_jdk_download_url ()
143+
144+ download_dir = ZEUZ_NODE_DOWNLOADS_DIR / "jdk"
145+ system = platform .system ()
146+
147+ if system == "Windows" :
148+ jdk_archive = download_dir / "jdk21.zip"
149+ elif system == "Linux" :
150+ jdk_archive = download_dir / "jdk21.tar.gz"
151+ elif system == "Darwin" :
152+ jdk_archive = download_dir / "jdk21.tar.gz"
153+ else :
154+ raise OSError (f"Unsupported platform: { system } " )
155+
156+ try :
157+ jdk_archive .parent .mkdir (parents = True , exist_ok = True )
158+
159+ async with httpx .AsyncClient (timeout = 900.0 ) as client :
160+ async with client .stream ("GET" , jdk_url ) as response :
161+ response .raise_for_status ()
162+
163+ total_size = int (response .headers .get ("content-length" , 0 ))
164+ chunk_size = 8192
165+ downloaded = 0
166+
167+ count = []
168+ with open (jdk_archive , "wb" ) as f :
169+ async for chunk in response .aiter_bytes (chunk_size ):
170+ f .write (chunk )
171+ downloaded += len (chunk )
172+
173+ if total_size > 0 :
174+ progress = (downloaded / total_size ) * 100
175+ bar_length = 50
176+ filled_length = int (bar_length * downloaded // total_size )
177+ bar = '█' * filled_length + '-' * (bar_length - filled_length )
178+
179+ mb_downloaded = downloaded / (1024 * 1024 )
180+ mb_total = total_size / (1024 * 1024 )
181+
182+ print (f"\r [installer][android-jdk] |{ bar } | { progress :.1f} % ({ mb_downloaded :.1f} /{ mb_total :.1f} MB)" , end = '' , flush = True )
183+
184+ p = round (mb_downloaded / mb_total , 1 )
185+ if p not in count :
186+ count .append (p )
187+ asyncio .create_task (send_response ({
188+ "action" : "status" ,
189+ "data" : {
190+ "category" : "Android" ,
191+ "name" : "Java" ,
192+ "status" : "installing" ,
193+ "comment" : f"Downloading Java 21... { progress :.1f} % ({ mb_downloaded :.1f} /{ mb_total :.1f} MB)" ,
194+ }
195+ }))
196+
197+ print ()
198+ print (f"[installer][android-jdk] JDK download complete: { jdk_archive } " )
199+ return jdk_archive
200+ except Exception as e :
201+ print (f"\n [installer][android-jdk] JDK download failed: { e } " )
202+ await send_response ({
203+ "action" : "status" ,
204+ "data" : {
205+ "category" : "Android" ,
206+ "name" : "Java" ,
207+ "status" : "not installed" ,
208+ "comment" : f"Java download failed: { str (e )} " ,
209+ }
210+ })
211+ return None
212+
213+
214+ async def _extract_jdk (jdk_archive ):
215+ """Extract JDK to the appropriate location"""
216+ if not jdk_archive or not jdk_archive .exists ():
217+ return None
218+
219+ print ("[installer][android-jdk] Extracting JDK..." )
220+ await send_response ({
221+ "action" : "status" ,
222+ "data" : {
223+ "category" : "Android" ,
224+ "name" : "Java" ,
225+ "status" : "installing" ,
226+ "comment" : "Extracting Java..." ,
227+ }
228+ })
229+
230+ system = platform .system ()
231+
232+ # Extract to ZEUZ downloads directory
233+ jdk_dir = ZEUZ_NODE_DOWNLOADS_DIR / "jdk" / "jdk-21"
234+ if jdk_dir .exists ():
235+ shutil .rmtree (jdk_dir )
236+ jdk_dir .mkdir (parents = True , exist_ok = True )
237+
238+ if system == "Windows" :
239+ print (f"[installer][android-jdk] Extracting JDK to { jdk_dir } " )
240+ elif system == "Linux" :
241+ print (f"[installer][android-jdk] Extracting JDK to { jdk_dir } " )
242+ elif system == "Darwin" :
243+ print (f"[installer][android-jdk] Extracting JDK to { jdk_dir } " )
244+
245+ try :
246+ if system == "Windows" :
247+ with zipfile .ZipFile (jdk_archive , 'r' ) as zip_ref :
248+ zip_ref .extractall (jdk_dir )
249+ elif system == "Linux" :
250+ with tarfile .open (jdk_archive , 'r:gz' ) as tar_ref :
251+ tar_ref .extractall (jdk_dir )
252+ elif system == "Darwin" :
253+ with tarfile .open (jdk_archive , 'r:gz' ) as tar_ref :
254+ tar_ref .extractall (jdk_dir )
255+ else :
256+ raise OSError (f"Unsupported platform: { system } " )
257+
258+ # Find the actual JDK directory (it might be nested)
259+ jdk_home = None
260+ for item in jdk_dir .iterdir ():
261+ if item .is_dir () and "jdk" in item .name .lower ():
262+ jdk_home = item
263+ break
264+
265+ if not jdk_home :
266+ print ("[installer][android-jdk] Could not find JDK directory after extraction" )
267+ await send_response ({
268+ "action" : "status" ,
269+ "data" : {
270+ "category" : "Android" ,
271+ "name" : "Java" ,
272+ "status" : "not installed" ,
273+ "comment" : "Could not find Java directory after extraction." ,
274+ }
275+ })
276+ return None
277+
278+ print (f"[installer][android-jdk] JDK extracted to { jdk_home } " )
279+ return jdk_home
280+ except Exception as e :
281+ print (f"[installer][android-jdk] JDK extraction failed: { e } " )
282+ await send_response ({
283+ "action" : "status" ,
284+ "data" : {
285+ "category" : "Android" ,
286+ "name" : "Java" ,
287+ "status" : "not installed" ,
288+ "comment" : f"Java extraction failed: { str (e )} " ,
289+ }
290+ })
291+ return None
292+
293+
294+ async def _verify_java_installation (jdk_home ):
295+ """Verify that Java is properly installed and working"""
296+ print ("[installer][android-jdk] Verifying Java installation..." )
297+ await send_response ({
298+ "action" : "status" ,
299+ "data" : {
300+ "category" : "Android" ,
301+ "name" : "Java" ,
302+ "status" : "installing" ,
303+ "comment" : "Verifying Java installation..." ,
304+ }
305+ })
306+
307+ system = platform .system ()
308+
309+ # Check if java executable exists
310+ if system == "Windows" :
311+ java_exe = jdk_home / "bin" / "java.exe"
312+ elif system == "Linux" :
313+ java_exe = jdk_home / "bin" / "java"
314+ elif system == "Darwin" :
315+ java_exe = jdk_home / "bin" / "java"
316+ else :
317+ print (f"[installer][android-jdk] Unsupported platform: { system } " )
318+ return False
319+
320+ if not java_exe .exists ():
321+ print (f"[installer][android-jdk] Java executable not found at { java_exe } " )
322+ await send_response ({
323+ "action" : "status" ,
324+ "data" : {
325+ "category" : "Android" ,
326+ "name" : "Java" ,
327+ "status" : "not installed" ,
328+ "comment" : f"Java executable not found at { java_exe } " ,
329+ }
330+ })
331+ return False
332+
333+ # Make executable on Linux and macOS
334+ if system == "Linux" :
335+ try :
336+ java_exe .chmod (java_exe .stat ().st_mode | stat .S_IEXEC )
337+ except Exception as e :
338+ print (f"[installer][android-jdk] Failed to make Java executable: { e } " )
339+ return False
340+ elif system == "Darwin" :
341+ try :
342+ java_exe .chmod (java_exe .stat ().st_mode | stat .S_IEXEC )
343+ except Exception as e :
344+ print (f"[installer][android-jdk] Failed to make Java executable: { e } " )
345+ return False
346+
347+ # Test Java version (async)
348+ try :
349+ loop = asyncio .get_event_loop ()
350+ result = await loop .run_in_executor (
351+ None ,
352+ lambda : subprocess .run (
353+ [str (java_exe ), "-version" ],
354+ capture_output = True ,
355+ text = True
356+ )
357+ )
358+ if "version \" 21" not in result .stderr :
359+ print ("[installer][android-jdk] Java version check failed" )
360+ await send_response ({
361+ "action" : "status" ,
362+ "data" : {
363+ "category" : "Android" ,
364+ "name" : "Java" ,
365+ "status" : "not installed" ,
366+ "comment" : "Java version check failed." ,
367+ }
368+ })
369+ return False
370+ print ("[installer][android-jdk] Java version verified" )
371+
372+ # Test Java compiler
373+ if system == "Windows" :
374+ javac_exe = jdk_home / "bin" / "javac.exe"
375+ elif system == "Linux" :
376+ javac_exe = jdk_home / "bin" / "javac"
377+ elif system == "Darwin" :
378+ javac_exe = jdk_home / "bin" / "javac"
379+
380+ if not javac_exe .exists ():
381+ print (f"[installer][android-jdk] Java compiler not found at { javac_exe } " )
382+ await send_response ({
383+ "action" : "status" ,
384+ "data" : {
385+ "category" : "Android" ,
386+ "name" : "Java" ,
387+ "status" : "not installed" ,
388+ "comment" : f"Java compiler not found at { javac_exe } " ,
389+ }
390+ })
391+ return False
392+
393+ if system == "Linux" :
394+ javac_exe .chmod (javac_exe .stat ().st_mode | stat .S_IEXEC )
395+ elif system == "Darwin" :
396+ javac_exe .chmod (javac_exe .stat ().st_mode | stat .S_IEXEC )
397+
398+ result = await loop .run_in_executor (
399+ None ,
400+ lambda : subprocess .run (
401+ [str (javac_exe ), "-version" ],
402+ capture_output = True ,
403+ text = True
404+ )
405+ )
406+ if "javac 21" not in (result .stdout or result .stderr ):
407+ print ("[installer][android-jdk] Java compiler version check failed" )
408+ await send_response ({
409+ "action" : "status" ,
410+ "data" : {
411+ "category" : "Android" ,
412+ "name" : "Java" ,
413+ "status" : "not installed" ,
414+ "comment" : "Java compiler version check failed." ,
415+ }
416+ })
417+ return False
418+ print ("[installer][android-jdk] Java compiler verified" )
419+
420+ return True
421+ except Exception as e :
422+ print (f"[installer][android-jdk] Java verification failed: { e } " )
423+ await send_response ({
424+ "action" : "status" ,
425+ "data" : {
426+ "category" : "Android" ,
427+ "name" : "Java" ,
428+ "status" : "not installed" ,
429+ "comment" : f"Java verification failed: { str (e )} " ,
430+ }
431+ })
432+ return False
433+
434+
103435async def install ():
104436 """Install Java by calling JDK installation function"""
105437 print ("[installer][android-java] Installing..." )
0 commit comments