You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Add PackageManager to reduce duplication
This commit is contained in:
@@ -66,7 +66,7 @@ class AppStore(Activity):
|
||||
applist = json.loads(response.text)
|
||||
for app in json.loads(response.text):
|
||||
try:
|
||||
self.apps.append(mpos.apps.App(**app))
|
||||
self.apps.append(mpos.apps.App(app["name"], app["publisher"], app["short_description"], app["long_description"], app["icon_url"], app["download_url"], app["fullname"], app["version"], app["category"], app["activities"]))
|
||||
except Exception as e:
|
||||
print(f"Warning: could not add app from {json_url} to apps list: {e}")
|
||||
# Remove duplicates based on app.name
|
||||
@@ -137,14 +137,19 @@ class AppStore(Activity):
|
||||
if app.image_dsc:
|
||||
print(f"Skipping icon download for {app.name} because already downloaded.")
|
||||
continue
|
||||
if not self.keep_running:
|
||||
print(f"App is stopping, aborting icon downloads.")
|
||||
break
|
||||
print(f"Downloading icon for {app.name}")
|
||||
image_dsc = self.download_icon(app.icon_url)
|
||||
app.image_dsc = image_dsc # save it for the app detail page
|
||||
if not self.keep_running:
|
||||
print(f"App is stopping, aborting all icon downloads.")
|
||||
break
|
||||
lv.async_call(lambda l: app.image.set_src(image_dsc), None)
|
||||
else:
|
||||
lv.async_call(lambda l: app.image.set_src(image_dsc), None)
|
||||
time.sleep_ms(200) # not waiting here will result in some async_calls() not being executed
|
||||
print("Finished downloading icons...")
|
||||
print("Finished downloading icons.")
|
||||
|
||||
def show_app_detail(self, app):
|
||||
intent = Intent(activity_class=AppDetail)
|
||||
@@ -439,10 +444,10 @@ class AppDetail(Activity):
|
||||
installed_app=None
|
||||
if AppDetail.is_installed_by_path(appdir):
|
||||
print(f"{appdir} found, getting version...")
|
||||
installed_app = mpos.apps.parse_manifest(f"{appdir}/META-INF/MANIFEST.JSON")
|
||||
installed_app = mpos.apps.parse_manifest(appdir)
|
||||
elif AppDetail.is_installed_by_path(builtinappdir):
|
||||
print(f"{builtinappdir} found, getting version...")
|
||||
installed_app = mpos.apps.parse_manifest(f"{builtinappdir}/META-INF/MANIFEST.JSON")
|
||||
installed_app = mpos.apps.parse_manifest(builtinappdir)
|
||||
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
|
||||
return False
|
||||
return AppDetail.compare_versions(new_version, installed_app.version)
|
||||
|
||||
@@ -14,6 +14,7 @@ import lvgl as lv
|
||||
|
||||
import mpos.apps
|
||||
import mpos.ui
|
||||
from mpos.package_manager import PackageManager
|
||||
|
||||
class Launcher(mpos.apps.Activity):
|
||||
|
||||
@@ -30,57 +31,28 @@ class Launcher(mpos.apps.Activity):
|
||||
self.setContentView(main_screen)
|
||||
|
||||
def onResume(self, screen):
|
||||
app_list = []
|
||||
seen_base_names = set()
|
||||
# Check and collect subdirectories from existing directories
|
||||
apps_dir = "apps"
|
||||
apps_dir_builtin = "builtin/apps"
|
||||
# Grid parameters
|
||||
icon_size = 64 # Adjust based on your display
|
||||
label_height = 24
|
||||
iconcont_width = icon_size + label_height
|
||||
iconcont_height = icon_size + label_height
|
||||
|
||||
|
||||
# Check and collect unique subdirectories
|
||||
for dir_path in [apps_dir, apps_dir_builtin]:
|
||||
try:
|
||||
if uos.stat(dir_path)[0] & 0x4000: # Verify directory exists
|
||||
try:
|
||||
for d in uos.listdir(dir_path):
|
||||
full_path = f"{dir_path}/{d}"
|
||||
#print(f"full_path: {full_path}")
|
||||
try:
|
||||
if uos.stat(full_path)[0] & 0x4000: # Check if it's a directory
|
||||
base_name = d
|
||||
if base_name not in seen_base_names: # Avoid duplicates
|
||||
seen_base_names.add(base_name)
|
||||
app = mpos.apps.parse_manifest(f"{full_path}/META-INF/MANIFEST.JSON")
|
||||
if app.category != "launcher": # Skip launchers
|
||||
main_launcher = mpos.apps.find_main_launcher_activity(app)
|
||||
if main_launcher:
|
||||
app_list.append((app.name, full_path))
|
||||
except Exception as e:
|
||||
print(f"launcher.py stat of {full_path} got exception: {e}")
|
||||
except Exception as e:
|
||||
print(f"launcher.py listdir of {dir_path} got exception: {e}")
|
||||
except Exception as e:
|
||||
print(f"launcher.py stat of {dir_path} got exception: {e}")
|
||||
app_list = PackageManager.app_list
|
||||
|
||||
import time
|
||||
start = time.ticks_ms()
|
||||
|
||||
screen.clean()
|
||||
|
||||
# Get the group for focusable objects
|
||||
focusgroup = lv.group_get_default()
|
||||
if not focusgroup:
|
||||
print("WARNING: could not get default focusgroup")
|
||||
|
||||
# Sort apps alphabetically by app.name
|
||||
app_list.sort(key=lambda x: x[0].lower()) # Case-insensitive sorting
|
||||
|
||||
# Create UI for each app
|
||||
for app_name, app_dir_fullpath in app_list:
|
||||
for app in app_list:
|
||||
app_name = app.name
|
||||
app_dir_fullpath = app.installed_path
|
||||
print(f"Adding app {app_name} from {app_dir_fullpath}")
|
||||
# Create container for each app (icon + label)
|
||||
app_cont = lv.obj(screen)
|
||||
|
||||
@@ -10,6 +10,7 @@ import traceback
|
||||
|
||||
import mpos.info
|
||||
import mpos.ui
|
||||
from mpos.package_manager import PackageManager
|
||||
|
||||
def good_stack_size():
|
||||
stacksize = 24*1024
|
||||
@@ -119,8 +120,7 @@ def start_app(app_dir, is_launcher=False):
|
||||
import utime
|
||||
start_time = utime.ticks_ms()
|
||||
mpos.ui.set_foreground_app(app_dir) # would be better to store only the app name...
|
||||
manifest_path = f"{app_dir}/META-INF/MANIFEST.JSON"
|
||||
app = mpos.apps.parse_manifest(manifest_path)
|
||||
app = mpos.apps.parse_manifest(app_dir)
|
||||
print(f"start_app parsed manifest and got: {str(app)}")
|
||||
main_launcher_activity = find_main_launcher_activity(app)
|
||||
if not main_launcher_activity:
|
||||
@@ -136,10 +136,16 @@ def start_app(app_dir, is_launcher=False):
|
||||
end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
|
||||
print(f"start_app() took {end_time}ms")
|
||||
|
||||
# Starts the first launcher that's found
|
||||
def restart_launcher():
|
||||
print("restart_launcher")
|
||||
mpos.ui.empty_screen_stack()
|
||||
# No need to stop the other launcher first, because it exits after building the screen
|
||||
start_app_by_name("com.micropythonos.launcher", True) # Would be better to query the PackageManager for Activities that are launchers
|
||||
for app in mpos.package_manager.PackageManager.get_app_list():
|
||||
#print(f"checking {app}")
|
||||
if app.category == "launcher" and find_main_launcher_activity(app):
|
||||
print(f"Found launcher, starting {app.fullname}")
|
||||
start_app_by_name(app.fullname, True)
|
||||
|
||||
def find_main_launcher_activity(app):
|
||||
result = None
|
||||
@@ -163,7 +169,7 @@ def is_launcher(app_name):
|
||||
|
||||
|
||||
class App:
|
||||
def __init__(self, name, publisher, short_description, long_description, icon_url, download_url, fullname, version, category, activities):
|
||||
def __init__(self, name, publisher, short_description, long_description, icon_url, download_url, fullname, version, category, activities, installed_path=None):
|
||||
self.name = name
|
||||
self.publisher = publisher
|
||||
self.short_description = short_description
|
||||
@@ -176,6 +182,7 @@ class App:
|
||||
self.image = None
|
||||
self.image_dsc = None
|
||||
self.activities = activities
|
||||
self.installed_path = installed_path
|
||||
|
||||
def __str__(self):
|
||||
return (f"App(name='{self.name}', "
|
||||
@@ -183,9 +190,12 @@ class App:
|
||||
f"short_description='{self.short_description}', "
|
||||
f"version='{self.version}', "
|
||||
f"category='{self.category}', "
|
||||
f"activities={self.activities})")
|
||||
f"activities='{self.activities}', "
|
||||
f"installed_path={self.installed_path})")
|
||||
|
||||
def parse_manifest(manifest_path):
|
||||
def parse_manifest(appdir):
|
||||
print(f"parse_manifest({appdir})")
|
||||
manifest_path = f"{appdir}/META-INF/MANIFEST.JSON"
|
||||
# Default values for App object
|
||||
default_app = App(
|
||||
name="Unknown",
|
||||
@@ -197,7 +207,8 @@ def parse_manifest(manifest_path):
|
||||
fullname="Unknown",
|
||||
version="0.0.0",
|
||||
category="",
|
||||
activities=[]
|
||||
activities=[],
|
||||
installed_path=appdir
|
||||
)
|
||||
try:
|
||||
with open(manifest_path, 'r') as f:
|
||||
@@ -214,7 +225,8 @@ def parse_manifest(manifest_path):
|
||||
fullname=app_info.get("fullname", default_app.fullname),
|
||||
version=app_info.get("version", default_app.version),
|
||||
category=app_info.get("category", default_app.category),
|
||||
activities=app_info.get("activities", default_app.activities)
|
||||
activities=app_info.get("activities", default_app.activities),
|
||||
installed_path=appdir
|
||||
)
|
||||
except OSError:
|
||||
print(f"parse_manifest: error loading manifest_path: {manifest_path}")
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import uos
|
||||
import mpos.apps
|
||||
|
||||
'''
|
||||
Initialized at boot.
|
||||
Typical users: appstore, launcher
|
||||
|
||||
Allows users to:
|
||||
- list installed apps (including all app data like icon, version, etc)
|
||||
- install app from .zip file
|
||||
- uninstall app
|
||||
- check if an app is installed + which version
|
||||
|
||||
Why this exists:
|
||||
- the launcher was listing installed apps, reading them, loading the icons, starting apps
|
||||
- the appstore was also listing installed apps, reading them, (down)loading the icons, starting apps
|
||||
- other apps might also want to do so
|
||||
Previously, some functionality was deduplicated into apps.py
|
||||
But the main issue was that the list of apps was built by both etc.
|
||||
|
||||
Question: does it make sense to cache the database?
|
||||
=> No, just read/load them at startup and keep the list in memory, and load the icons at runtime.
|
||||
|
||||
'''
|
||||
|
||||
|
||||
class PackageManager():
|
||||
|
||||
app_list = [] # list of App objects, sorted alphabetically by app.name, unique by full_name (com.example.appname)
|
||||
|
||||
@classmethod
|
||||
def get_app_list(cls):
|
||||
if len(cls.app_list) == 0:
|
||||
cls.find_apps()
|
||||
return cls.app_list
|
||||
|
||||
@classmethod
|
||||
def find_apps(cls):
|
||||
print("\n\n\nPackageManager finding apps...")
|
||||
seen_fullnames = set()
|
||||
# Check and collect subdirectories from existing directories
|
||||
apps_dir = "apps"
|
||||
apps_dir_builtin = "builtin/apps"
|
||||
|
||||
# Check and collect unique subdirectories
|
||||
for dir_path in [apps_dir, apps_dir_builtin]:
|
||||
try:
|
||||
if uos.stat(dir_path)[0] & 0x4000: # Verify directory exists
|
||||
try:
|
||||
for d in uos.listdir(dir_path):
|
||||
full_path = f"{dir_path}/{d}"
|
||||
print(f"full_path: {full_path}")
|
||||
try:
|
||||
if uos.stat(full_path)[0] & 0x4000: # Check if it's a directory
|
||||
fullname = d
|
||||
if fullname not in seen_fullnames: # Avoid duplicates
|
||||
seen_fullnames.add(fullname)
|
||||
app = mpos.apps.parse_manifest(full_path)
|
||||
cls.app_list.append(app)
|
||||
print(f"added app {app}")
|
||||
except Exception as e:
|
||||
print(f"PackageManager: stat of {full_path} got exception: {e}")
|
||||
except Exception as e:
|
||||
print(f"PackageManager: listdir of {dir_path} got exception: {e}")
|
||||
except Exception as e:
|
||||
print(f"PackageManager: stat of {dir_path} got exception: {e}")
|
||||
|
||||
# Sort apps alphabetically by app.name
|
||||
cls.app_list.sort(key=lambda x: x.name.lower()) # Case-insensitive sorting by name
|
||||
Reference in New Issue
Block a user