Skip to content

Commit e7b71cd

Browse files
Move code from AppStore to PackageManager
1 parent f044e6c commit e7b71cd

File tree

3 files changed

+115
-104
lines changed

3 files changed

+115
-104
lines changed

internal_filesystem/builtin/apps/com.micropythonos.about/assets/about.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,7 @@ def onCreate(self):
6666
print("main.py: WARNING: could not import/run freezefs_mount_builtin: ", e)
6767
label11 = lv.label(screen)
6868
label11.set_text(f"freezefs_mount_builtin exception (normal on dev builds): {e}")
69-
69+
# TODO:
70+
# - add total size, used and free space on internal storage
71+
# - add total size, used and free space on SD card
7072
self.setContentView(screen)

internal_filesystem/builtin/apps/com.micropythonos.appstore/assets/appstore.py

Lines changed: 20 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
from mpos.apps import Activity, Intent
1515
import mpos.ui
16+
from mpos.package_manager import PackageManager
17+
1618

1719
class AppStore(Activity):
1820
apps = []
@@ -250,7 +252,7 @@ def onCreate(self):
250252
self.install_label = lv.label(self.install_button)
251253
self.install_label.center()
252254
self.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):
254256
self.install_button.set_size(lv.pct(47), 40) # make space for update button
255257
print("Update available, adding update button.")
256258
self.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
286288
is_installed = True
287289
update_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)
290292
if not overridden_builtin_app:
291-
is_installed = AppDetail.is_installed_by_name(app_fullname)
293+
is_installed = PackageManager.is_installed_by_name(app_fullname)
292294
if is_installed:
293295
if builtin_app:
294296
if overridden_builtin_app:
@@ -308,49 +310,43 @@ def toggle_install(self, download_url, fullname):
308310
if label_text == self.action_label_install:
309311
try:
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))
312314
except Exception as e:
313-
print("Could not start download_and_unzip thread: ", e)
315+
print("Could not start download_and_install thread: ", e)
314316
elif label_text == self.action_label_uninstall or label_text == self.action_label_restore:
315317
print("Uninstalling app....")
316318
try:
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))
319321
except Exception as e:
320-
print("Could not start download_and_unzip thread: ", e)
322+
print("Could not start uninstall_app thread: ", e)
321323

322324
def update_button_click(self, download_url, fullname):
323325
print(f"Update button clicked for {download_url} and fullname {fullname}")
324326
self.update_button.add_flag(lv.obj.FLAG.HIDDEN)
325327
self.install_button.set_size(lv.pct(100), 40)
326328
try:
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))
329331
except Exception as e:
330-
print("Could not start download_and_unzip thread: ", e)
331-
332-
def uninstall_app(self, app_folder, app_fullname):
332+
print("Could not start download_and_install thread: ", e)
333+
334+
def uninstall_app(self, app_fullname):
333335
self.install_button.add_state(lv.STATE.DISABLED)
334336
self.install_label.set_text("Please wait...") # TODO: Put "Cancel" if cancellation is possible
335337
self.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)
343340
self.progress_bar.set_value(100, False)
344341
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
345342
self.progress_bar.set_value(0, False)
346343
self.set_install_label(app_fullname)
347344
self.install_button.remove_state(lv.STATE.DISABLED)
348-
if self.is_builtin_app(app_fullname):
345+
if PackageManager.is_builtin_app(app_fullname):
349346
self.update_button.remove_flag(lv.obj.FLAG.HIDDEN)
350347
self.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):
354350
self.install_button.add_state(lv.STATE.DISABLED)
355351
self.install_label.set_text("Please wait...") # TODO: Put "Cancel" if cancellation is possible
356352
self.progress_bar.remove_flag(lv.obj.FLAG.HIDDEN)
@@ -387,86 +383,11 @@ def download_and_unzip(self, zip_url, dest_folder, app_fullname):
387383
finally:
388384
if 'response' in locals():
389385
response.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:
404389
self.progress_bar.set_value(100, False)
405390
self.progress_bar.add_flag(lv.obj.FLAG.HIDDEN)
406391
self.progress_bar.set_value(0, False)
407392
self.set_install_label(app_fullname)
408393
self.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-

internal_filesystem/lib/mpos/package_manager.py

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import uos
1+
import os
22
import mpos.apps
33

44
'''
@@ -45,13 +45,13 @@ def find_apps(cls):
4545
# Check and collect unique subdirectories
4646
for dir_path in [apps_dir, apps_dir_builtin]:
4747
try:
48-
if uos.stat(dir_path)[0] & 0x4000: # Verify directory exists
48+
if os.stat(dir_path)[0] & 0x4000: # Verify directory exists
4949
try:
50-
for d in uos.listdir(dir_path):
50+
for d in os.listdir(dir_path):
5151
full_path = f"{dir_path}/{d}"
5252
print(f"full_path: {full_path}")
5353
try:
54-
if uos.stat(full_path)[0] & 0x4000: # Check if it's a directory
54+
if os.stat(full_path)[0] & 0x4000: # Check if it's a directory
5555
fullname = d
5656
if fullname not in seen_fullnames: # Avoid duplicates
5757
seen_fullnames.add(fullname)
@@ -67,3 +67,91 @@ def find_apps(cls):
6767

6868
# Sort apps alphabetically by app.name
6969
cls.app_list.sort(key=lambda x: x.name.lower()) # Case-insensitive sorting by name
70+
71+
@staticmethod
72+
def uninstall_app(app_fullname):
73+
try:
74+
import shutil
75+
shutil.rmtree(f"apps/{app_fullname}") # never in builtin/apps because those can't be uninstalled
76+
# TODO: also remove it from the app_list
77+
except Exception as e:
78+
print(f"Removing app_folder {app_folder} got error: {e}")
79+
80+
@staticmethod
81+
def install_mpk(temp_zip_path, dest_folder):
82+
try:
83+
# Step 2: Unzip the file
84+
print("Unzipping it to:", dest_folder)
85+
with zipfile.ZipFile(temp_zip_path, "r") as zip_ref:
86+
zip_ref.extractall(dest_folder)
87+
print("Unzipped successfully")
88+
# Step 3: Clean up
89+
os.remove(temp_zip_path)
90+
print("Removed temporary .mpk file")
91+
except Exception as e:
92+
print(f"Unzip and cleanup failed: {e}")
93+
# Would be good to show error message here if it fails...
94+
95+
@staticmethod
96+
def compare_versions(ver1: str, ver2: str) -> bool:
97+
"""Compare two version numbers (e.g., '1.2.3' vs '4.5.6').
98+
Returns True if ver1 is greater than ver2, False otherwise."""
99+
print(f"Comparing versions: {ver1} vs {ver2}")
100+
v1_parts = [int(x) for x in ver1.split('.')]
101+
v2_parts = [int(x) for x in ver2.split('.')]
102+
print(f"Version 1 parts: {v1_parts}")
103+
print(f"Version 2 parts: {v2_parts}")
104+
for i in range(max(len(v1_parts), len(v2_parts))):
105+
v1 = v1_parts[i] if i < len(v1_parts) else 0
106+
v2 = v2_parts[i] if i < len(v2_parts) else 0
107+
print(f"Comparing part {i}: {v1} vs {v2}")
108+
if v1 > v2:
109+
print(f"{ver1} is greater than {ver2}")
110+
return True
111+
if v1 < v2:
112+
print(f"{ver1} is less than {ver2}")
113+
return False
114+
print(f"Versions are equal or {ver1} is not greater than {ver2}")
115+
return False
116+
117+
@staticmethod
118+
def is_builtin_app(app_fullname):
119+
return PackageManager.is_installed_by_path(f"builtin/apps/{app_fullname}")
120+
121+
@staticmethod
122+
def is_overridden_builtin_app(app_fullname):
123+
return PackageManager.is_installed_by_path(f"apps/{app_fullname}") and PackageManager.is_installed_by_path(f"builtin/apps/{app_fullname}")
124+
125+
@staticmethod
126+
def is_update_available(app_fullname, new_version):
127+
appdir = f"apps/{app_fullname}"
128+
builtinappdir = f"builtin/apps/{app_fullname}"
129+
installed_app=None
130+
if PackageManager.is_installed_by_path(appdir):
131+
print(f"{appdir} found, getting version...")
132+
installed_app = mpos.apps.parse_manifest(appdir) # probably no need to re-parse the manifest
133+
elif PackageManager.is_installed_by_path(builtinappdir):
134+
print(f"{builtinappdir} found, getting version...")
135+
installed_app = mpos.apps.parse_manifest(builtinappdir) # probably no need to re-parse the manifest
136+
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
137+
return False
138+
return PackageManager.compare_versions(new_version, installed_app.version)
139+
140+
@staticmethod
141+
def is_installed_by_path(dir_path):
142+
try:
143+
if os.stat(dir_path)[0] & 0x4000:
144+
print(f"is_installed_by_path: {dir_path} found, checking manifest...")
145+
manifest = f"{dir_path}/META-INF/MANIFEST.JSON"
146+
if os.stat(manifest)[0] & 0x8000:
147+
return True
148+
except OSError:
149+
print(f"is_installed_by_path got OSError for {dir_path}")
150+
pass # Skip if directory or manifest doesn't exist
151+
return False
152+
153+
@staticmethod
154+
def is_installed_by_name(app_fullname):
155+
print(f"Checking if app {app_fullname} is installed...")
156+
return PackageManager.is_installed_by_path(f"apps/{app_fullname}") or PackageManager.is_installed_by_path(f"builtin/apps/{app_fullname}")
157+

0 commit comments

Comments
 (0)