diff --git a/internal_filesystem/builtin/apps/com.micropythonos.appstore/assets/appstore.py b/internal_filesystem/builtin/apps/com.micropythonos.appstore/assets/appstore.py index cf52ac09..8bceae6c 100644 --- a/internal_filesystem/builtin/apps/com.micropythonos.appstore/assets/appstore.py +++ b/internal_filesystem/builtin/apps/com.micropythonos.appstore/assets/appstore.py @@ -312,7 +312,7 @@ class AppDetail(Activity): print("Uninstalling app....") try: _thread.stack_size(mpos.apps.good_stack_size()) - _thread.start_new_thread(self.uninstall_app, (fullname)) + _thread.start_new_thread(self.uninstall_app, (fullname,)) except Exception as e: print("Could not start uninstall_app thread: ", e) @@ -349,7 +349,7 @@ class AppDetail(Activity): try: # Step 1: Download the .mpk file print(f"Downloading .mpk file from: {zip_url}") - response = requests.get(zip_url, timeout=10) + response = requests.get(zip_url, timeout=10) # TODO: use stream=True and do it in chunks like in OSUpdate if response.status_code != 200: print("Download failed: Status code", response.status_code) response.close() diff --git a/internal_filesystem/lib/mpos/package_manager.py b/internal_filesystem/lib/mpos/package_manager.py index 06365584..fe217fe8 100644 --- a/internal_filesystem/lib/mpos/package_manager.py +++ b/internal_filesystem/lib/mpos/package_manager.py @@ -28,50 +28,105 @@ Question: does it make sense to cache the database? ''' +# PackageManager.py (MicroPython) -class PackageManager(): +import os - app_list = [] # list of App objects, sorted alphabetically by app.name, unique by full_name (com.example.appname) +class PackageManager: + """Registry of all discovered apps. + * PackageManager.get_app_list() -> list of App objects (sorted by name) + * PackageManager[fullname] -> App (raises KeyError if missing) + * PackageManager.get(fullname) -> App or None + """ + + # --------------------------------------------------------------------- # + # internal storage + # --------------------------------------------------------------------- # + _app_list = [] # sorted by app.name + _by_fullname = {} # fullname -> App + + # --------------------------------------------------------------------- # + # public list access (kept for backward compatibility) + # --------------------------------------------------------------------- # @classmethod def get_app_list(cls): - if len(cls.app_list) == 0: + if not cls._app_list: cls.find_apps() - return cls.app_list + return cls._app_list + # --------------------------------------------------------------------- # + # dict-style lookup: PackageManager["com.example.myapp"] + # --------------------------------------------------------------------- # + def __class_getitem__(cls, fullname): + try: + return cls._by_fullname[fullname] + except KeyError: + raise KeyError("No app with fullname='{}'".format(fullname)) + + # --------------------------------------------------------------------- # + # safe lookup: PackageManager.get("com.example.myapp") -> App or None + # --------------------------------------------------------------------- # + @classmethod + def get(cls, fullname): + return cls._by_fullname.get(fullname) + + # --------------------------------------------------------------------- # + # discovery & population + # --------------------------------------------------------------------- # @classmethod def find_apps(cls): print("\n\n\nPackageManager finding apps...") - seen_fullnames = set() - # Check and collect subdirectories from existing directories - apps_dir = "apps" + + seen = set() # avoid processing the same fullname twice + apps_dir = "apps" apps_dir_builtin = "builtin/apps" - # Check and collect unique subdirectories - for dir_path in [apps_dir, apps_dir_builtin]: + for base in (apps_dir, apps_dir_builtin): try: - if os.stat(dir_path)[0] & 0x4000: # Verify directory exists + # ---- does the directory exist? -------------------------------- + st = os.stat(base) + if not (st[0] & 0x4000): # 0x4000 = directory bit + continue + + # ---- iterate over immediate children ------------------------- + for name in os.listdir(base): + full_path = "{}/{}".format(base, name) + + # ---- is it a directory? --------------------------------- try: - for d in os.listdir(dir_path): - full_path = f"{dir_path}/{d}" - print(f"full_path: {full_path}") - try: - if os.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}") + st = os.stat(full_path) + if not (st[0] & 0x4000): + continue except Exception as e: - print(f"PackageManager: listdir of {dir_path} got exception: {e}") + print("PackageManager: stat of {} got exception: {}".format(full_path, e)) + continue + + fullname = name + + # ---- skip duplicates ------------------------------------ + if fullname in seen: + continue + seen.add(fullname) + + # ---- parse the manifest --------------------------------- + try: + app = mpos.apps.parse_manifest(full_path) + except Exception as e: + print("PackageManager: parsing {} failed: {}".format(full_path, e)) + continue + + # ---- store in both containers --------------------------- + cls._app_list.append(app) + cls._by_fullname[fullname] = app + print("added app {}".format(app)) + except Exception as e: - print(f"PackageManager: stat of {dir_path} got exception: {e}") + print("PackageManager: handling {} got exception: {}".format(base, e)) + + # ---- sort the list by display name (case-insensitive) ------------ + cls._app_list.sort(key=lambda a: a.name.lower()) - # Sort apps alphabetically by app.name - cls.app_list.sort(key=lambda x: x.name.lower()) # Case-insensitive sorting by name @staticmethod def uninstall_app(app_fullname):