Skip to content

Commit 5691058

Browse files
Add PackageManager to reduce duplication
1 parent 3e10479 commit 5691058

File tree

4 files changed

+105
-47
lines changed

4 files changed

+105
-47
lines changed

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def download_app_index(self, json_url):
6666
applist = json.loads(response.text)
6767
for app in json.loads(response.text):
6868
try:
69-
self.apps.append(mpos.apps.App(**app))
69+
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"]))
7070
except Exception as e:
7171
print(f"Warning: could not add app from {json_url} to apps list: {e}")
7272
# Remove duplicates based on app.name
@@ -137,14 +137,19 @@ def download_icons(self):
137137
if app.image_dsc:
138138
print(f"Skipping icon download for {app.name} because already downloaded.")
139139
continue
140+
if not self.keep_running:
141+
print(f"App is stopping, aborting icon downloads.")
142+
break
140143
print(f"Downloading icon for {app.name}")
141144
image_dsc = self.download_icon(app.icon_url)
142145
app.image_dsc = image_dsc # save it for the app detail page
143146
if not self.keep_running:
147+
print(f"App is stopping, aborting all icon downloads.")
144148
break
145-
lv.async_call(lambda l: app.image.set_src(image_dsc), None)
149+
else:
150+
lv.async_call(lambda l: app.image.set_src(image_dsc), None)
146151
time.sleep_ms(200) # not waiting here will result in some async_calls() not being executed
147-
print("Finished downloading icons...")
152+
print("Finished downloading icons.")
148153

149154
def show_app_detail(self, app):
150155
intent = Intent(activity_class=AppDetail)
@@ -439,10 +444,10 @@ def is_update_available(app_fullname, new_version):
439444
installed_app=None
440445
if AppDetail.is_installed_by_path(appdir):
441446
print(f"{appdir} found, getting version...")
442-
installed_app = mpos.apps.parse_manifest(f"{appdir}/META-INF/MANIFEST.JSON")
447+
installed_app = mpos.apps.parse_manifest(appdir)
443448
elif AppDetail.is_installed_by_path(builtinappdir):
444449
print(f"{builtinappdir} found, getting version...")
445-
installed_app = mpos.apps.parse_manifest(f"{builtinappdir}/META-INF/MANIFEST.JSON")
450+
installed_app = mpos.apps.parse_manifest(builtinappdir)
446451
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
447452
return False
448453
return AppDetail.compare_versions(new_version, installed_app.version)

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

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import mpos.apps
1616
import mpos.ui
17+
from mpos.package_manager import PackageManager
1718

1819
class Launcher(mpos.apps.Activity):
1920

@@ -30,57 +31,28 @@ def onCreate(self):
3031
self.setContentView(main_screen)
3132

3233
def onResume(self, screen):
33-
app_list = []
34-
seen_base_names = set()
35-
# Check and collect subdirectories from existing directories
36-
apps_dir = "apps"
37-
apps_dir_builtin = "builtin/apps"
3834
# Grid parameters
3935
icon_size = 64 # Adjust based on your display
4036
label_height = 24
4137
iconcont_width = icon_size + label_height
4238
iconcont_height = icon_size + label_height
4339

44-
45-
# Check and collect unique subdirectories
46-
for dir_path in [apps_dir, apps_dir_builtin]:
47-
try:
48-
if uos.stat(dir_path)[0] & 0x4000: # Verify directory exists
49-
try:
50-
for d in uos.listdir(dir_path):
51-
full_path = f"{dir_path}/{d}"
52-
#print(f"full_path: {full_path}")
53-
try:
54-
if uos.stat(full_path)[0] & 0x4000: # Check if it's a directory
55-
base_name = d
56-
if base_name not in seen_base_names: # Avoid duplicates
57-
seen_base_names.add(base_name)
58-
app = mpos.apps.parse_manifest(f"{full_path}/META-INF/MANIFEST.JSON")
59-
if app.category != "launcher": # Skip launchers
60-
main_launcher = mpos.apps.find_main_launcher_activity(app)
61-
if main_launcher:
62-
app_list.append((app.name, full_path))
63-
except Exception as e:
64-
print(f"launcher.py stat of {full_path} got exception: {e}")
65-
except Exception as e:
66-
print(f"launcher.py listdir of {dir_path} got exception: {e}")
67-
except Exception as e:
68-
print(f"launcher.py stat of {dir_path} got exception: {e}")
40+
app_list = PackageManager.app_list
6941

7042
import time
7143
start = time.ticks_ms()
7244

7345
screen.clean()
46+
7447
# Get the group for focusable objects
7548
focusgroup = lv.group_get_default()
7649
if not focusgroup:
7750
print("WARNING: could not get default focusgroup")
7851

79-
# Sort apps alphabetically by app.name
80-
app_list.sort(key=lambda x: x[0].lower()) # Case-insensitive sorting
81-
8252
# Create UI for each app
83-
for app_name, app_dir_fullpath in app_list:
53+
for app in app_list:
54+
app_name = app.name
55+
app_dir_fullpath = app.installed_path
8456
print(f"Adding app {app_name} from {app_dir_fullpath}")
8557
# Create container for each app (icon + label)
8658
app_cont = lv.obj(screen)

internal_filesystem/lib/mpos/apps.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import mpos.info
1212
import mpos.ui
13+
from mpos.package_manager import PackageManager
1314

1415
def good_stack_size():
1516
stacksize = 24*1024
@@ -119,8 +120,7 @@ def start_app(app_dir, is_launcher=False):
119120
import utime
120121
start_time = utime.ticks_ms()
121122
mpos.ui.set_foreground_app(app_dir) # would be better to store only the app name...
122-
manifest_path = f"{app_dir}/META-INF/MANIFEST.JSON"
123-
app = mpos.apps.parse_manifest(manifest_path)
123+
app = mpos.apps.parse_manifest(app_dir)
124124
print(f"start_app parsed manifest and got: {str(app)}")
125125
main_launcher_activity = find_main_launcher_activity(app)
126126
if not main_launcher_activity:
@@ -136,10 +136,16 @@ def start_app(app_dir, is_launcher=False):
136136
end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
137137
print(f"start_app() took {end_time}ms")
138138

139+
# Starts the first launcher that's found
139140
def restart_launcher():
141+
print("restart_launcher")
140142
mpos.ui.empty_screen_stack()
141143
# No need to stop the other launcher first, because it exits after building the screen
142-
start_app_by_name("com.micropythonos.launcher", True) # Would be better to query the PackageManager for Activities that are launchers
144+
for app in mpos.package_manager.PackageManager.get_app_list():
145+
#print(f"checking {app}")
146+
if app.category == "launcher" and find_main_launcher_activity(app):
147+
print(f"Found launcher, starting {app.fullname}")
148+
start_app_by_name(app.fullname, True)
143149

144150
def find_main_launcher_activity(app):
145151
result = None
@@ -163,7 +169,7 @@ def is_launcher(app_name):
163169

164170

165171
class App:
166-
def __init__(self, name, publisher, short_description, long_description, icon_url, download_url, fullname, version, category, activities):
172+
def __init__(self, name, publisher, short_description, long_description, icon_url, download_url, fullname, version, category, activities, installed_path=None):
167173
self.name = name
168174
self.publisher = publisher
169175
self.short_description = short_description
@@ -176,16 +182,20 @@ def __init__(self, name, publisher, short_description, long_description, icon_ur
176182
self.image = None
177183
self.image_dsc = None
178184
self.activities = activities
185+
self.installed_path = installed_path
179186

180187
def __str__(self):
181188
return (f"App(name='{self.name}', "
182189
f"publisher='{self.publisher}', "
183190
f"short_description='{self.short_description}', "
184191
f"version='{self.version}', "
185192
f"category='{self.category}', "
186-
f"activities={self.activities})")
193+
f"activities='{self.activities}', "
194+
f"installed_path={self.installed_path})")
187195

188-
def parse_manifest(manifest_path):
196+
def parse_manifest(appdir):
197+
print(f"parse_manifest({appdir})")
198+
manifest_path = f"{appdir}/META-INF/MANIFEST.JSON"
189199
# Default values for App object
190200
default_app = App(
191201
name="Unknown",
@@ -197,7 +207,8 @@ def parse_manifest(manifest_path):
197207
fullname="Unknown",
198208
version="0.0.0",
199209
category="",
200-
activities=[]
210+
activities=[],
211+
installed_path=appdir
201212
)
202213
try:
203214
with open(manifest_path, 'r') as f:
@@ -214,7 +225,8 @@ def parse_manifest(manifest_path):
214225
fullname=app_info.get("fullname", default_app.fullname),
215226
version=app_info.get("version", default_app.version),
216227
category=app_info.get("category", default_app.category),
217-
activities=app_info.get("activities", default_app.activities)
228+
activities=app_info.get("activities", default_app.activities),
229+
installed_path=appdir
218230
)
219231
except OSError:
220232
print(f"parse_manifest: error loading manifest_path: {manifest_path}")
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import uos
2+
import mpos.apps
3+
4+
'''
5+
Initialized at boot.
6+
Typical users: appstore, launcher
7+
8+
Allows users to:
9+
- list installed apps (including all app data like icon, version, etc)
10+
- install app from .zip file
11+
- uninstall app
12+
- check if an app is installed + which version
13+
14+
Why this exists:
15+
- the launcher was listing installed apps, reading them, loading the icons, starting apps
16+
- the appstore was also listing installed apps, reading them, (down)loading the icons, starting apps
17+
- other apps might also want to do so
18+
Previously, some functionality was deduplicated into apps.py
19+
But the main issue was that the list of apps was built by both etc.
20+
21+
Question: does it make sense to cache the database?
22+
=> No, just read/load them at startup and keep the list in memory, and load the icons at runtime.
23+
24+
'''
25+
26+
27+
class PackageManager():
28+
29+
app_list = [] # list of App objects, sorted alphabetically by app.name, unique by full_name (com.example.appname)
30+
31+
@classmethod
32+
def get_app_list(cls):
33+
if len(cls.app_list) == 0:
34+
cls.find_apps()
35+
return cls.app_list
36+
37+
@classmethod
38+
def find_apps(cls):
39+
print("\n\n\nPackageManager finding apps...")
40+
seen_fullnames = set()
41+
# Check and collect subdirectories from existing directories
42+
apps_dir = "apps"
43+
apps_dir_builtin = "builtin/apps"
44+
45+
# Check and collect unique subdirectories
46+
for dir_path in [apps_dir, apps_dir_builtin]:
47+
try:
48+
if uos.stat(dir_path)[0] & 0x4000: # Verify directory exists
49+
try:
50+
for d in uos.listdir(dir_path):
51+
full_path = f"{dir_path}/{d}"
52+
print(f"full_path: {full_path}")
53+
try:
54+
if uos.stat(full_path)[0] & 0x4000: # Check if it's a directory
55+
fullname = d
56+
if fullname not in seen_fullnames: # Avoid duplicates
57+
seen_fullnames.add(fullname)
58+
app = mpos.apps.parse_manifest(full_path)
59+
cls.app_list.append(app)
60+
print(f"added app {app}")
61+
except Exception as e:
62+
print(f"PackageManager: stat of {full_path} got exception: {e}")
63+
except Exception as e:
64+
print(f"PackageManager: listdir of {dir_path} got exception: {e}")
65+
except Exception as e:
66+
print(f"PackageManager: stat of {dir_path} got exception: {e}")
67+
68+
# Sort apps alphabetically by app.name
69+
cls.app_list.sort(key=lambda x: x.name.lower()) # Case-insensitive sorting by name

0 commit comments

Comments
 (0)