1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
|
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Discover, load and manage FreedomBox applications.
"""
import importlib
import logging
import pathlib
import re
import types
import django
from plinth import cfg
from plinth.signals import pre_module_loading
logger = logging.getLogger(__name__)
loaded_modules: dict[str, types.ModuleType] = dict()
_modules_to_load = None
def include_urls():
"""Include the URLs of the modules into main Django project."""
for module_import_path in get_modules_to_load():
module_name = module_import_path.split('.')[-1]
_include_module_urls(module_import_path, module_name)
def load_modules():
"""
Read names of enabled modules in modules/enabled directory and
import them from modules directory.
"""
if loaded_modules:
return # Modules have already been loaded
pre_module_loading.send_robust(sender="module_loader")
for module_import_path in get_modules_to_load():
module_name = module_import_path.split('.')[-1]
try:
module = importlib.import_module(module_import_path)
loaded_modules[module_name] = module
except Exception as exception:
logger.exception('Could not import %s: %s', module_import_path,
exception)
if cfg.develop:
raise
def _include_module_urls(module_import_path, module_name):
"""Include the module's URLs in global project URLs list"""
from plinth import urls
url_module = module_import_path + '.urls'
try:
urls.urlpatterns += [
django.urls.re_path(r'',
django.urls.include((url_module, module_name)))
]
except ImportError:
logger.debug('No URLs for %s', module_name)
if cfg.develop:
raise
def _get_modules_enabled_paths():
"""Return list of paths from which enabled modules list must be read."""
return [
pathlib.Path('/usr/share/freedombox/modules-enabled/'),
pathlib.Path('/etc/plinth/modules-enabled/'), # For compatibility
pathlib.Path('/etc/freedombox/modules-enabled/'),
]
def _get_modules_enabled_files_to_read():
"""Return the list of files containing modules import paths."""
module_files = {}
for path in _get_modules_enabled_paths():
directory = pathlib.Path(path)
files = list(directory.glob('*'))
for file_ in files:
# Omit hidden or backup files
if file_.name.startswith('.') or '.dpkg' in file_.name:
continue
if file_.is_symlink() and str(file_.resolve()) == '/dev/null':
module_files.pop(file_.name, None)
continue
module_files[file_.name] = file_
if module_files:
return module_files.values()
# 'make build install' has not been executed yet. Pickup files to load from
# local module directories.
directory = pathlib.Path(__file__).parent
glob_pattern = 'modules/*/data/usr/share/freedombox/modules-enabled/*'
return list(directory.glob(glob_pattern))
def get_modules_to_load():
"""Get the list of modules to be loaded"""
global _modules_to_load
if _modules_to_load is not None:
return _modules_to_load
modules = []
for file_ in _get_modules_enabled_files_to_read():
module = _read_module_import_paths_from_file(file_)
if module:
modules.append(module)
_modules_to_load = modules
return modules
def get_module_import_path(module_name: str) -> str:
"""Return the import path for a module."""
import_path_file = None
for path in _get_modules_enabled_paths():
file_ = path / module_name
if file_.exists():
import_path_file = file_
if file_.is_symlink() and str(file_.resolve()) == '/dev/null':
import_path_file = None
if not import_path_file:
# 'make build install' has not been executed yet. Pickup files to load
# from local module directories.
directory = pathlib.Path(__file__).parent
import_path_file = (directory /
f'modules/{module_name}/data/usr/share/'
f'freedombox/modules-enabled/{module_name}')
if not import_path_file:
raise ValueError('Unknown module')
import_path = _read_module_import_paths_from_file(import_path_file)
if not import_path:
raise ValueError('Module disabled')
return import_path
def _read_module_import_paths_from_file(file_path: pathlib.Path) -> str | None:
"""Read a module's import path from a file."""
with file_path.open() as file_handle:
for line in file_handle:
line = re.sub('#.*', '', line)
line = line.strip()
if line:
return line
return None
|