File: module_loader.py

package info (click to toggle)
freedombox 26.2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 82,976 kB
  • sloc: python: 48,504; javascript: 1,736; xml: 481; makefile: 290; sh: 167; php: 32
file content (156 lines) | stat: -rw-r--r-- 4,893 bytes parent folder | download | duplicates (2)
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