1313
1414from mpos .apps import Activity ,Intent
1515import mpos .ui
16+ from mpos .package_manager import PackageManager
17+
1618
1719class AppStore (Activity ):
1820apps = []
@@ -250,7 +252,7 @@ def onCreate(self):
250252self .install_label = lv .label (self .install_button )
251253self .install_label .center ()
252254self .set_install_label (app .fullname )
253- if self .is_update_available (app .fullname ,app .version ):
255+ if PackageManager .is_update_available (app .fullname ,app .version ):
254256self .install_button .set_size (lv .pct (47 ),40 )# make space for update button
255257print ("Update available, adding update button." )
256258self .update_button = lv .button (buttoncont )
@@ -285,10 +287,10 @@ def set_install_label(self, app_fullname):
285287# - update is separate button, only shown if already installed and new version
286288is_installed = True
287289update_available = False
288- builtin_app = self .is_builtin_app (app_fullname )
289- overridden_builtin_app = self .is_overridden_builtin_app (app_fullname )
290+ builtin_app = PackageManager .is_builtin_app (app_fullname )
291+ overridden_builtin_app = PackageManager .is_overridden_builtin_app (app_fullname )
290292if not overridden_builtin_app :
291- is_installed = AppDetail .is_installed_by_name (app_fullname )
293+ is_installed = PackageManager .is_installed_by_name (app_fullname )
292294if is_installed :
293295if builtin_app :
294296if overridden_builtin_app :
@@ -308,49 +310,43 @@ def toggle_install(self, download_url, fullname):
308310if label_text == self .action_label_install :
309311try :
310312_thread .stack_size (mpos .apps .good_stack_size ())
311- _thread .start_new_thread (self .download_and_unzip , (download_url ,f"apps/{ fullname } " ,fullname ))
313+ _thread .start_new_thread (self .download_and_install , (download_url ,f"apps/{ fullname } " ,fullname ))
312314except Exception as e :
313- print ("Could not startdownload_and_unzip thread: " ,e )
315+ print ("Could not startdownload_and_install thread: " ,e )
314316elif label_text == self .action_label_uninstall or label_text == self .action_label_restore :
315317print ("Uninstalling app...." )
316318try :
317319_thread .stack_size (mpos .apps .good_stack_size ())
318- _thread .start_new_thread (self .uninstall_app , (f"apps/ { fullname } " , fullname ))
320+ _thread .start_new_thread (self .uninstall_app , (fullname ))
319321except Exception as e :
320- print ("Could not startdownload_and_unzip thread: " ,e )
322+ print ("Could not startuninstall_app thread: " ,e )
321323
322324def update_button_click (self ,download_url ,fullname ):
323325print (f"Update button clicked for{ download_url } and fullname{ fullname } " )
324326self .update_button .add_flag (lv .obj .FLAG .HIDDEN )
325327self .install_button .set_size (lv .pct (100 ),40 )
326328try :
327329_thread .stack_size (mpos .apps .good_stack_size ())
328- _thread .start_new_thread (self .download_and_unzip , (download_url ,f"apps/{ fullname } " ,fullname ))
330+ _thread .start_new_thread (self .download_and_install , (download_url ,f"apps/{ fullname } " ,fullname ))
329331except Exception as e :
330- print ("Could not startdownload_and_unzip thread: " ,e )
331-
332- def uninstall_app (self ,app_folder , app_fullname ):
332+ print ("Could not startdownload_and_install thread: " ,e )
333+
334+ def uninstall_app (self ,app_fullname ):
333335self .install_button .add_state (lv .STATE .DISABLED )
334336self .install_label .set_text ("Please wait..." )# TODO: Put "Cancel" if cancellation is possible
335337self .progress_bar .remove_flag (lv .obj .FLAG .HIDDEN )
336- self .progress_bar .set_value (33 ,True )
337- try :
338- import shutil
339- shutil .rmtree (app_folder )
340- self .progress_bar .set_value (66 ,True )
341- except Exception as e :
342- print (f"Removing app_folder{ app_folder } got error:{ e } " )
338+ self .progress_bar .set_value (42 ,True )
339+ PackageManager .uninstall_app (app_fullname )
343340self .progress_bar .set_value (100 ,False )
344341self .progress_bar .add_flag (lv .obj .FLAG .HIDDEN )
345342self .progress_bar .set_value (0 ,False )
346343self .set_install_label (app_fullname )
347344self .install_button .remove_state (lv .STATE .DISABLED )
348- if self .is_builtin_app (app_fullname ):
345+ if PackageManager .is_builtin_app (app_fullname ):
349346self .update_button .remove_flag (lv .obj .FLAG .HIDDEN )
350347self .install_button .set_size (lv .pct (47 ),40 )# if a builtin app was removed, then it was overridden, and a new version is available, so make space for update button
351-
352348
353- def download_and_unzip (self ,zip_url ,dest_folder ,app_fullname ):
349+ def download_and_install (self ,zip_url ,dest_folder ,app_fullname ):
354350self .install_button .add_state (lv .STATE .DISABLED )
355351self .install_label .set_text ("Please wait..." )# TODO: Put "Cancel" if cancellation is possible
356352self .progress_bar .remove_flag (lv .obj .FLAG .HIDDEN )
@@ -387,86 +383,11 @@ def download_and_unzip(self, zip_url, dest_folder, app_fullname):
387383finally :
388384if 'response' in locals ():
389385response .close ()
390- try :
391- # Step 2: Unzip the file
392- print ("Unzipping it to:" ,dest_folder )
393- with zipfile .ZipFile (temp_zip_path ,"r" )as zip_ref :
394- zip_ref .extractall (dest_folder )
395- self .progress_bar .set_value (80 ,True )
396- print ("Unzipped successfully" )
397- # Step 3: Clean up
398- os .remove (temp_zip_path )
399- print ("Removed temporary .mpk file" )
400- except Exception as e :
401- print (f"Unzip and cleanup failed:{ e } " )
402- # Would be good to show error message here if it fails...
386+ # Step 2: install it:
387+ PackageManager .install_mpk (temp_zip_path ,dest_folder )
403388# Success:
404389self .progress_bar .set_value (100 ,False )
405390self .progress_bar .add_flag (lv .obj .FLAG .HIDDEN )
406391self .progress_bar .set_value (0 ,False )
407392self .set_install_label (app_fullname )
408393self .install_button .remove_state (lv .STATE .DISABLED )
409-
410- @staticmethod
411- def compare_versions (ver1 :str ,ver2 :str )-> bool :
412- """Compare two version numbers (e.g., '1.2.3' vs '4.5.6').
413- Returns True if ver1 is greater than ver2, False otherwise."""
414- print (f"Comparing versions:{ ver1 } vs{ ver2 } " )
415- v1_parts = [int (x )for x in ver1 .split ('.' )]
416- v2_parts = [int (x )for x in ver2 .split ('.' )]
417- print (f"Version 1 parts:{ v1_parts } " )
418- print (f"Version 2 parts:{ v2_parts } " )
419- for i in range (max (len (v1_parts ),len (v2_parts ))):
420- v1 = v1_parts [i ]if i < len (v1_parts )else 0
421- v2 = v2_parts [i ]if i < len (v2_parts )else 0
422- print (f"Comparing part{ i } :{ v1 } vs{ v2 } " )
423- if v1 > v2 :
424- print (f"{ ver1 } is greater than{ ver2 } " )
425- return True
426- if v1 < v2 :
427- print (f"{ ver1 } is less than{ ver2 } " )
428- return False
429- print (f"Versions are equal or{ ver1 } is not greater than{ ver2 } " )
430- return False
431-
432- @staticmethod
433- def is_builtin_app (app_fullname ):
434- return AppDetail .is_installed_by_path (f"builtin/apps/{ app_fullname } " )
435-
436- @staticmethod
437- def is_overridden_builtin_app (app_fullname ):
438- return AppDetail .is_installed_by_path (f"apps/{ app_fullname } " )and AppDetail .is_installed_by_path (f"builtin/apps/{ app_fullname } " )
439-
440- @staticmethod
441- def is_update_available (app_fullname ,new_version ):
442- appdir = f"apps/{ app_fullname } "
443- builtinappdir = f"builtin/apps/{ app_fullname } "
444- installed_app = None
445- if AppDetail .is_installed_by_path (appdir ):
446- print (f"{ appdir } found, getting version..." )
447- installed_app = mpos .apps .parse_manifest (appdir )
448- elif AppDetail .is_installed_by_path (builtinappdir ):
449- print (f"{ builtinappdir } found, getting version..." )
450- installed_app = mpos .apps .parse_manifest (builtinappdir )
451- if not installed_app or installed_app .version == "0.0.0" :# special case, if the installed app doesn't have a version number then there's no update
452- return False
453- return AppDetail .compare_versions (new_version ,installed_app .version )
454-
455- @staticmethod
456- def is_installed_by_path (dir_path ):
457- try :
458- if os .stat (dir_path )[0 ]& 0x4000 :
459- print (f"is_installed_by_path:{ dir_path } found, checking manifest..." )
460- manifest = f"{ dir_path } /META-INF/MANIFEST.JSON"
461- if os .stat (manifest )[0 ]& 0x8000 :
462- return True
463- except OSError :
464- print (f"is_installed_by_path got OSError for{ dir_path } " )
465- pass # Skip if directory or manifest doesn't exist
466- return False
467-
468- @staticmethod
469- def is_installed_by_name (app_fullname ):
470- print (f"Checking if app{ app_fullname } is installed..." )
471- return AppDetail .is_installed_by_path (f"apps/{ app_fullname } " )or AppDetail .is_installed_by_path (f"builtin/apps/{ app_fullname } " )
472-