Skip to content

Commit

Permalink
search for (read-only) extensions in XDG_DATA_DIRS
Browse files Browse the repository at this point in the history
  • Loading branch information
nazarewk committed Nov 17, 2023
1 parent a9c5b37 commit 74b6cd0
Show file tree
Hide file tree
Showing 10 changed files with 60 additions and 19 deletions.
4 changes: 2 additions & 2 deletions tests/modes/apps/extensions/test_extension_finder.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import os

from ulauncher.modes.extensions.extension_finder import find_extensions
from ulauncher.modes.extensions.extension_finder import iter_extensions


def test_find_extensions__test_extension__is_found():
ext_dir = os.path.dirname(os.path.abspath(__file__))
(id, path) = next(iter(find_extensions(ext_dir)))
(id, path) = next(iter(iter_extensions(ext_dir)))
assert id == "test_extension"
assert path == f"{ext_dir}/test_extension"
3 changes: 3 additions & 0 deletions ulauncher/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
HOME = os.path.expanduser("~")
CONFIG = os.path.join(os.environ.get("XDG_CONFIG_HOME", f"{HOME}/.config"), "ulauncher")
DATA = os.path.join(os.environ.get("XDG_DATA_HOME", f"{HOME}/.local/share"), "ulauncher")
DATA_DIRS = [os.path.join(p, "ulauncher") for p in os.environ.get("XDG_DATA_DIRS", "").split(os.path.pathsep)]
STATE = os.path.join(os.environ.get("XDG_STATE_HOME", f"{HOME}/.local/state"), "ulauncher")
EXTENSIONS = os.path.join(DATA, "extensions")
EXTENSIONS_ALL = [EXTENSIONS, *(os.path.join(p, "extensions") for p in DATA_DIRS)]
EXTENSIONS_CONFIG = os.path.join(CONFIG, "ext_preferences")
USER_THEMES = os.path.join(CONFIG, "user-themes")
SYSTEM_THEMES = os.path.join(ASSETS, "themes")
Expand All @@ -28,6 +30,7 @@ class _PATHS_CLASS:
DATA = DATA
STATE = STATE
EXTENSIONS = EXTENSIONS
EXTENSIONS_ALL = EXTENSIONS_ALL
EXTENSIONS_CONFIG = EXTENSIONS_CONFIG
USER_THEMES = USER_THEMES
SYSTEM_THEMES = SYSTEM_THEMES
Expand Down
4 changes: 3 additions & 1 deletion ulauncher/modes/extensions/ExtensionController.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ulauncher.config import PATHS
from ulauncher.modes.extensions.DeferredResultRenderer import DeferredResultRenderer
from ulauncher.modes.extensions.ExtensionManifest import ExtensionManifest, ExtensionManifestError
from ulauncher.modes.extensions.extension_finder import locate_extension
from ulauncher.utils.decorator.debounce import debounce

logger = logging.getLogger()
Expand Down Expand Up @@ -80,7 +81,8 @@ def trigger_event(self, event: dict[str, Any]):
def get_normalized_icon_path(self, icon=None) -> str:
if not icon:
icon = self.manifest.icon
expanded_path = icon and f"{PATHS.EXTENSIONS}/{self.extension_id}/{icon}"
ext_path = locate_extension(self.extension_id, PATHS.EXTENSIONS, default_first=True)
expanded_path = icon and os.path.join(ext_path, icon)
return expanded_path if os.path.isfile(expanded_path) else icon

def handle_response(self, _framer, response: dict[str, Any]):
Expand Down
5 changes: 4 additions & 1 deletion ulauncher/modes/extensions/ExtensionDownloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ulauncher.config import PATHS
from ulauncher.modes.extensions.ExtensionDb import ExtensionDb, ExtensionRecord
from ulauncher.modes.extensions.ExtensionRemote import ExtensionRemote
from ulauncher.modes.extensions.extension_finder import is_extension

logger = logging.getLogger()

Expand All @@ -33,7 +34,9 @@ def download(self, url: str) -> str:
return remote.extension_id

def remove(self, ext_id: str) -> None:
rmtree(os.path.join(PATHS.EXTENSIONS, ext_id))
ext_dir = os.path.join(PATHS.EXTENSIONS, ext_id)
if is_extension(ext_dir):
rmtree(ext_dir)
if ext_id in self.ext_db:
del self.ext_db[ext_id]
self.ext_db.save()
Expand Down
5 changes: 4 additions & 1 deletion ulauncher/modes/extensions/ExtensionManifest.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from __future__ import annotations

import logging
import os
from typing import Any

from ulauncher.config import API_VERSION, PATHS
from ulauncher.modes.extensions.extension_finder import locate_extension
from ulauncher.utils.json_conf import JsonConf
from ulauncher.utils.version import satisfies

Expand Down Expand Up @@ -190,6 +192,7 @@ def apply_user_preferences(self, user_prefs: dict):

@classmethod
def load_from_extension_id(cls, ext_id: str):
manifest = super().load(f"{PATHS.EXTENSIONS}/{ext_id}/manifest.json")
ext_path = locate_extension(ext_id, PATHS.EXTENSIONS_ALL)
manifest = super().load(os.path.join(ext_path, "manifest.json"))
manifest.apply_user_preferences(JsonConf.load(f"{PATHS.EXTENSIONS_CONFIG}/{ext_id}.json"))
return manifest
3 changes: 2 additions & 1 deletion ulauncher/modes/extensions/ExtensionRemote.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from ulauncher.config import API_VERSION, PATHS
from ulauncher.modes.extensions.ExtensionDb import ExtensionDb, ExtensionRecord
from ulauncher.modes.extensions.ExtensionManifest import ExtensionIncompatibleWarning, ExtensionManifest
from ulauncher.modes.extensions.extension_finder import locate_extension
from ulauncher.utils.untar import untar
from ulauncher.utils.version import satisfies

Expand Down Expand Up @@ -80,7 +81,7 @@ def __init__(self, url: str):
*self.path.split("/"),
]
)
self._dir = f"{PATHS.EXTENSIONS}/{self.extension_id}"
self._dir = locate_extension(self.extension_id, PATHS.EXTENSIONS_ALL, default_first=True)
self._git_dir = f"{PATHS.EXTENSIONS}/.git/{self.extension_id}.git"

def _get_download_url(self, commit: str) -> str:
Expand Down
8 changes: 4 additions & 4 deletions ulauncher/modes/extensions/ExtensionRunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from gi.repository import Gio, GLib

from ulauncher.config import PATHS, get_options
from ulauncher.modes.extensions.extension_finder import find_extensions
from ulauncher.modes.extensions.extension_finder import iter_extensions, locate_extension
from ulauncher.modes.extensions.ExtensionDb import ExtensionDb
from ulauncher.modes.extensions.ExtensionManifest import ExtensionManifest
from ulauncher.modes.extensions.ProcessErrorExtractor import ProcessErrorExtractor
Expand Down Expand Up @@ -50,9 +50,9 @@ def __init__(self):

def run_all(self):
"""
Finds all extensions in `PATHS.EXTENSIONS` and runs them
Finds all extensions in `PATHS.EXTENSIONS`/`PATHS.EXTENSIONS_ALL` and runs them
"""
for ex_id, _ in find_extensions(PATHS.EXTENSIONS):
for ex_id, _ in iter_extensions(PATHS.EXTENSIONS_ALL):
ext_record = ext_db.get(ex_id)
if not ext_record or ext_record.is_enabled:
try:
Expand All @@ -72,7 +72,7 @@ def run(self, extension_id):
triggers = {id: t.keyword for id, t in manifest.triggers.items() if t.keyword}
# Preferences used to also contain keywords, so adding them back to avoid breaking v2 code
backwards_compatible_preferences = {**triggers, **manifest.get_user_preferences()}
extension_path = f"{PATHS.EXTENSIONS}/{extension_id}"
extension_path = locate_extension(extension_id, PATHS.EXTENSIONS_ALL)

cmd = [sys.executable, f"{extension_path}/main.py"]
env = {
Expand Down
37 changes: 32 additions & 5 deletions ulauncher/modes/extensions/extension_finder.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,38 @@
import os


def find_extensions(ext_dir):
def is_extension(directory):
"""
Yields `(extension_id, extension_path)` tuples found in a given extensions dir
Tells whether the argument is an extension directory
"""
if os.path.exists(ext_dir):
manifest = os.path.join(directory, "manifest.json")
manifest = os.path.realpath(manifest)
return os.path.isfile(manifest)


def locate_extension(ext_id, ext_dirs, default=None, default_first=False):
ret = default
for ext_dir in ext_dirs:
ext_path = os.path.join(ext_dir, ext_id)
if default_first and ret is None:
ret = ext_path
elif is_extension(ext_path):
ret = ext_path
break
if ret:
ret = os.path.realpath(ret)
return ret


def iter_extensions(ext_dirs, duplicates=False):
"""
Yields `(extension_id, extension_path)` tuples found in a given extensions dirs
"""
occurrences = set()
for ext_dir in ext_dirs:
if not os.path.exists(ext_dir):
continue
for entry in os.scandir(ext_dir):
if entry.is_dir() and os.path.isfile(f"{ext_dir}/{entry.name}/manifest.json"):
yield (entry.name, f"{ext_dir}/{entry.name}")
if is_extension(entry.path) and (duplicates or entry.name not in occurrences):
occurrences.add(entry.name)
yield entry.name, os.path.realpath(entry.path)
6 changes: 3 additions & 3 deletions ulauncher/ui/preferences_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from gi.repository import Gio, Gtk

from ulauncher.config import API_VERSION, PATHS, VERSION
from ulauncher.modes.extensions.extension_finder import find_extensions
from ulauncher.modes.extensions.extension_finder import iter_extensions
from ulauncher.modes.extensions.ExtensionDb import ExtensionDb
from ulauncher.modes.extensions.ExtensionDownloader import ExtensionDownloader
from ulauncher.modes.extensions.ExtensionManifest import ExtensionManifest
Expand Down Expand Up @@ -42,7 +42,7 @@ def decorator(handler):

def get_extensions():
ext_runner = ExtensionRunner.get_instance()
for ext_id, _ in find_extensions(PATHS.EXTENSIONS):
for ext_id, ext_path in iter_extensions(PATHS.EXTENSIONS_ALL):
manifest = ExtensionManifest.load_from_extension_id(ext_id)
error = None
try:
Expand All @@ -53,7 +53,7 @@ def get_extensions():

is_running = ext_runner.is_running(ext_id)
# Controller method `get_icon_path` would work, but only running extensions have controllers
icon = get_icon_path(manifest.icon, base_path=f"{PATHS.EXTENSIONS}/{ext_id}")
icon = get_icon_path(manifest.icon, base_path=ext_path)

yield {
**ExtensionDb.load().get(ext_id, {}),
Expand Down
4 changes: 3 additions & 1 deletion ulauncher/utils/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from ulauncher.config import FIRST_V6_RUN, PATHS
from ulauncher.modes.extensions.ExtensionManifest import ExtensionManifest
from ulauncher.modes.extensions.extension_finder import locate_extension
from ulauncher.utils.systemd_controller import SystemdController

_logger = logging.getLogger()
Expand Down Expand Up @@ -60,7 +61,8 @@ def _migrate_user_prefs(extension_id, user_prefs):
if sorted(user_prefs.keys()) == ["preferences", "triggers"]:
return user_prefs
new_prefs = {"preferences": {}, "triggers": {}}
manifest = ExtensionManifest.load(f"{PATHS.EXTENSIONS}/{extension_id}/manifest.json")
ext_path = locate_extension(extension_id, PATHS.EXTENSIONS_ALL)
manifest = ExtensionManifest.load(os.path.join(ext_path, "manifest.json"))
for id, pref in user_prefs.items():
if manifest.triggers.get(id):
new_prefs["triggers"][id] = {"keyword": pref}
Expand Down

0 comments on commit 74b6cd0

Please sign in to comment.