Skip to content
This repository has been archived by the owner on Dec 6, 2023. It is now read-only.

Rework of the whole folder architecture #26

Merged
merged 6 commits into from
Aug 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
278 changes: 161 additions & 117 deletions configurator
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,99 @@
import logging
from pathlib import Path
import os
import stat
import subprocess
import sys

# TODO Review the architecture
# Currently, the packaging and the execution folder architectures
# are mixed together.
# Try to decouple them.

class Helpers:
@staticmethod
def get_logger(name: str = 'default'):
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)

# Avoid messages duplication after multiple Jupyter Lab runs
handler_console = None
handlers = logger.handlers
for h in handlers:
if isinstance(h, logging.StreamHandler):
handler_console = h
break
if not handler_console:
logger.addHandler(logging.StreamHandler())

return logger

@staticmethod
def list_dirs(from_dir: Path):
return [entry for entry in os.scandir(from_dir) if entry.is_dir()]

@staticmethod
def list_dirnames(from_dir: Path):
return [entry.name for entry in os.scandir(from_dir) if entry.is_dir()]

@staticmethod
def check_dir(directory: Path, contains:dict = {}, logger=get_logger()) -> bool:
all_subpaths_present = True
for relative_subpath, check_method in contains.items():
sub_path = directory / relative_subpath
all_subpaths_present = sub_path.exists() and check_method(sub_path)
# For debug purposes
if not sub_path.exists():
logger.debug(f'{sub_path} does not exist')
return all_subpaths_present

@staticmethod
def check_is_file(pathname: Path, logger=get_logger()) -> bool:
ret = pathname.is_file()
if not ret:
logger.error(f'{pathname} is not a file')
return ret

# TODO Factorize this by adding the permissions to check
# as an argument, and bind these arguments when using
# these methods as 'function pointers'.
@staticmethod
def check_is_readable_file(pathname: Path, logger=get_logger()) -> bool:
if not Helpers.check_is_file(pathname):
return False

if not ((os.stat(pathname).st_mode & stat.S_IRUSR)):
logger.error(f'{pathname} is not readable by its owner')
return False

@staticmethod
def check_is_exe_file(pathname: Path, logger=get_logger()) -> bool:
if not Helpers.check_is_file(pathname):
return False

if not ((os.stat(pathname).st_mode & stat.S_IXUSR) != 0):
logger.error(f'{pathname} is not executable by its owner')
return False

return True

class Configurator:
# FIXME Parse these information from configuration files
MODULES_PATH=Path('/usr/share/armbian/configurator/modules')
REQUIRED_FILES_PATHS=['module.cli', 'DESC']
REQUIRED_FILES_PATHS=['module', 'DESC']

# TODO Put such functions and setup into libraries

def __init__(self):
def __init__(self, modules_dirpath):
main_logger = logging.getLogger('armbian-configurator')
main_logger.setLevel(logging.DEBUG)
main_logger.addHandler(logging.StreamHandler())
self.main_logger = main_logger

def list_dirs(self, from_dir=".", containing_files=[]) -> list:
entries = None
try:
entries = os.scandir(from_dir)
except:
return []
dirs = []
for entry in entries:
if entry.is_dir():
directory = entry.name
all_files_present = True
for file_to_search in containing_files:
checked_file_path = Path(from_dir) / directory / file_to_search
self.main_logger.debug(f'Does {checked_file_path} exist ? {checked_file_path.exists()}')
all_files_present &= checked_file_path.exists()

if all_files_present:
dirs.append(directory)
return dirs
self.logger = main_logger
self.modules_dirpath = Path(modules_dirpath)
self.list_modules()

def module_path(self, module_name: str) -> Path:
return self.MODULES_PATH / module_name

def available_modes_for_module(self, module_name: str) -> list:
entries = None
modes = []
try:
entries = os.scandir(self.module_path(module_name))
for entry in entries:
if entry.is_file() and entry.name.startswith('module.'):
modes.append(entry.name.split('.')[1])
except Exception as e:
self.main_logger.error(f'Could not access module {module_name}')
self.main_logger.error(e)

return modes

def best_locale_desc_path_for(self, module_name: str) -> Path:
def best_desc_filename(self, module_name: str) -> Path:
module_root_path = self.module_path(module_name)

# LANG is generally something like
# C
# en_US.utf8
Expand All @@ -71,104 +109,110 @@ class Configurator:
# element
simple_locale = locale.split('_')[0]

potential_paths = [
module_root_path / f'DESC.{locale}',
module_root_path / f'DESC.{simple_locale}',
module_root_path / f'DESC']

for desc_path in potential_paths:
if desc_path.exists():
return desc_path

self.main_logger.error('No Description found')

return None

desc_filenames = [f'DESC.{locale}', f'DESC.{simple_locale}', f'DESC']

for desc_filename in desc_filenames:
desc_filepath = module_root_path / desc_filename
if desc_filepath.exists():
return desc_filepath

def list_modules(self) -> list:
return sorted(self.list_dirs(
from_dir=self.MODULES_PATH,
containing_files=self.REQUIRED_FILES_PATHS))
# There IS ZERO reason for this message to appear.
# Hence the 'CRITICAL' level of the error.
# We're supposed to list the descriptions of available modules.
# The availability of a module is checked beforehand
# and requires a 'DESC' to be in the module folder.
self.logger.critical('No Description found')

def module_exist(self, module_name: str) -> bool:
module_path = self.module_path(module_name)
all_files_present = True

for file_path in self.REQUIRED_FILES_PATHS:
all_files_present &= (module_path / file_path).exists()
return ''

return all_files_present
def list_modules(self):
# TODO Bind the logger in the check methods
required_files = { 'DESC': Helpers.check_is_readable_file, 'cli/module': Helpers.check_is_exe_file }
modules_dirpath = self.modules_dirpath

def module_description(self, module_name: str) -> dict:
info = {}

if not self.module_exist(module_name):
self.main_logger.error(f'Module {module_name} does not exist')
return info
modules = {}
for name in Helpers.list_dirnames(modules_dirpath):
sub_path = modules_dirpath / name
is_module = Helpers.check_dir(directory = sub_path, contains = required_files)
if not is_module:
continue

desc_file_path = self.best_locale_desc_path_for(module_name)
if not desc_file_path:
self.main_logger.error(f'No description found for module {module_name} !')
return info
modes = ['cli']
for additional_mode in ['tui', 'gui']:
mode_files = { f'{additional_mode}/module': Helpers.check_is_exe_file }
has_required_files = Helpers.check_dir(directory = sub_path, contains = mode_files)
if not has_required_files:
continue
modes.append(additional_mode)

modes = self.available_modes_for_module(module_name)
# 'module.cli' is mandatory.
# No modes returned means that 'module.cli' was not found.
if not modes:
self.main_logger.error(f'module.cli was not for module {module_name}')
return info

# FIXME Deal with errors here
with open(desc_file_path) as desc_file:
info['description'] = desc_file.readline()
modules[name] = {'modes': modes, 'desc': sub_path / self.best_desc_filename(name)}
self.modules = modules

info['modes'] = modes
def module_path(self, module_name: str) -> Path:
return self.modules_dirpath / module_name

return info
def module_exist(self, module_name: str) -> bool:
return module_name in self.modules

def list_modules_with_descriptions(self) -> tuple:
# FIXME Create an appropriate structure or object
# Make an array of it and output it
modules_info = {}
modules = self.list_modules()
def module_modes(self, module_name: str) -> list:
return self.modules[module_name]['modes'] if self.module_exist(module_name) else []

for module_name in modules:
info = self.module_description(module_name)
if not info:
self.main_logger.error(f'Could not get accurate info about module {module_name}. Skipping...')
def module_desc(self, module_name: str) -> str:
with open(self.modules[module_name]['desc']) as desc_file:
return desc_file.readline()

modules_info[module_name] = info
def print_modules(self):
for module_name in self.modules:
modes = self.module_modes(module_name)
desc = self.module_desc(module_name)
self.logger.info(f'\t{module_name}\t{modes}\t{desc}')

return modules_info
def system_can_handle_gui(self):
return "DISPLAY" in os.environ

def print_modules(self, modules_info: dict = None):
if not modules_info:
modules_info = self.list_modules_with_descriptions()
def execute_module(self, module_name: str, arguments: list, mode: str = '') -> int:
if not self.module_exist(module_name):
self.logger.critical(f'{module_name} does not exist !')

for module_name in modules_info:
info = modules_info[module_name]
print(f'\t{module_name}\t{info["modes"]}\t{info["description"]}')
modes = self.module_modes(module_name)

if not mode:
mode = 'cli'
if not arguments:
if 'gui' in modes and self.system_can_handle_gui():
mode = 'gui'
elif 'tui' in modes:
mode = 'tui'
else:
mode = 'cli'
else:
if not mode in modes:
self.logger.critical(f'{module_name} has no {mode} mode')
sys.exit(1)

def execute_module(self, module_name: str, arguments: list):
info = self.module_description(module_name)
# FIXME Check if TUI or GUI can actually be run !
# If no display is available, and the console is not
# interactive, there's no way to execute GUI or TUI !
mode = 'cli'
if not arguments:
if 'gui' in info['modes']:
mode = 'gui'
elif 'tui' in info['modes']:
mode = 'tui'
subprocess.run([self.module_path(module_name) / mode / 'module'] + arguments)

subprocess.run([self.module_path(module_name) / f'module.{mode}'] + arguments)

if __name__ == '__main__':

# TODO Make it configurable through config files
# AND command line arguments
default_modules_path = '/usr/share/armbian/configurator/modules'
modules_dirpath = default_modules_path
if 'ARMBIAN_MODULES_DIRPATH' in os.environ:
modules_dirpath = os.environ['ARMBIAN_MODULES_DIRPATH']

configurator = Configurator(modules_dirpath=modules_dirpath)

# Not using ArgParser at the moment since I need to keep
# track of the position of the arguments.
# All the arguments passed after the module name are passed
# to the module 'as-is' and should not be interpreted in any
# way.
# Parsing the arguments before with ArgParser should not
# be THAT hard... still, this will be for the next time.
arguments = sys.argv
configurator = Configurator()
n_args = len(arguments)
if n_args < 2:
configurator.print_modules()
Expand Down
1 change: 1 addition & 0 deletions modules/cpufreq/armbian/all-archs/tui/DEPS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ncurses
1 change: 1 addition & 0 deletions modules/cpufreq/armbian/all-archs/tui/module/DESC
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Determine the CPU frequency
1 change: 0 additions & 1 deletion modules/cpufreq/armbian/tui/DEPS

This file was deleted.

3 changes: 3 additions & 0 deletions modules/docker/armbian/BUILD.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

./install.sh
1 change: 0 additions & 1 deletion modules/docker/armbian/cli/DEPS

This file was deleted.

1 change: 0 additions & 1 deletion modules/docker/armbian/gui/DEPS

This file was deleted.

35 changes: 35 additions & 0 deletions modules/docker/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/sh

abort()
{
local message=${1}

echo $message
exit 1
}

MAIN_FILES="configuration library softwares tools README.md"
DESC_FILES="DESC DESC.fr DESC.ja DESC.zh_CN"
MODES="cli gui"
UI_FILES="ui"
PACKAGING_FILES="build/DEPS.main build/POSTINST.sh"

cp ${DESC_FILES} "armbian" &&
mkdir -p "armbian/all-archs" &&
cp ${PACKAGING_FILES} "armbian"

[[ $? -eq 0 ]] || abort "Could copy the basic armbian dependency files"

for mode in ${MODES}
do
mkdir -p "armbian/all-archs/${mode}/module" &&
cp -r ${MAIN_FILES} "armbian/all-archs/${mode}/module" &&
cp "module.${mode}" "armbian/all-archs/${mode}/module/module" &&
cp "build/DEPS.${mode}" "armbian/all-archs/${mode}"
done

[[ $? -eq 0 ]] || abort "Could not copy the modules content"

cp -r ${UI_FILES} "armbian/all-archs/gui/module"

[[ $? -eq 0 ]] || abort "Could not copy the UI files"