|
|
|
|
@@ -5,14 +5,14 @@ import sys
|
|
|
|
|
import traceback
|
|
|
|
|
from configparser import ConfigParser
|
|
|
|
|
import logging
|
|
|
|
|
from systemd import journal
|
|
|
|
|
import inspect
|
|
|
|
|
from enum import Enum, auto
|
|
|
|
|
import signal
|
|
|
|
|
from importlib import import_module
|
|
|
|
|
from threading import Thread, Lock
|
|
|
|
|
|
|
|
|
|
import inspect
|
|
|
|
|
from threading import Thread
|
|
|
|
|
from threading import Thread, Event
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_number(s):
|
|
|
|
|
@@ -48,13 +48,15 @@ class AppEngineException(Exception):
|
|
|
|
|
def __init__(self, error: AEErrs, mesg=None) -> None:
|
|
|
|
|
if mesg is None:
|
|
|
|
|
self.mesg = str(error)
|
|
|
|
|
else:
|
|
|
|
|
self.mesg = mesg
|
|
|
|
|
super().__init__(self.mesg)
|
|
|
|
|
self.value = error.value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Commands(Thread):
|
|
|
|
|
defmod = None
|
|
|
|
|
precmd = "cmd_"
|
|
|
|
|
prefcmd = "cmd_"
|
|
|
|
|
|
|
|
|
|
def __init__(self, config: ConfigParser, log: logging.Handler) -> None:
|
|
|
|
|
super().__init__()
|
|
|
|
|
@@ -62,6 +64,7 @@ class Commands(Thread):
|
|
|
|
|
self.cmods = {}
|
|
|
|
|
self.config = config
|
|
|
|
|
self.log = log
|
|
|
|
|
self.stop_all_event = None
|
|
|
|
|
self.stopped = False
|
|
|
|
|
self.nickname = None
|
|
|
|
|
if not (self.config is None):
|
|
|
|
|
@@ -103,6 +106,12 @@ class Commands(Thread):
|
|
|
|
|
def stop(self):
|
|
|
|
|
self.stopped = True
|
|
|
|
|
|
|
|
|
|
def free(self):
|
|
|
|
|
""" Virtual method used to clean resources for all Commands
|
|
|
|
|
when the application is exited.
|
|
|
|
|
"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def list_modules(self):
|
|
|
|
|
success = True
|
|
|
|
|
ret = "List of modules:\n"
|
|
|
|
|
@@ -110,15 +119,24 @@ class Commands(Thread):
|
|
|
|
|
ret = ret + " " + module + "\n"
|
|
|
|
|
return success, ret.strip()
|
|
|
|
|
|
|
|
|
|
def help_module(self, module, *args):
|
|
|
|
|
if module in self.cmods.keys():
|
|
|
|
|
if len(args) > 0:
|
|
|
|
|
return self.cmods[module].cmd_help(args[0])
|
|
|
|
|
else:
|
|
|
|
|
return self.cmods[module].cmd_help()
|
|
|
|
|
else:
|
|
|
|
|
return "No module with this name"
|
|
|
|
|
|
|
|
|
|
def _execute_command(self, method: str, *args, **kwargs) -> tuple:
|
|
|
|
|
success = False
|
|
|
|
|
ret = (AEErrs.INTERNAL_ERROR.value, "function not found")
|
|
|
|
|
if hasattr(self, self.precmd + method) and inspect.ismethod(
|
|
|
|
|
getattr(self, self.precmd + method)
|
|
|
|
|
if hasattr(self, self.prefcmd + method) and inspect.ismethod(
|
|
|
|
|
getattr(self, self.prefcmd + method)
|
|
|
|
|
):
|
|
|
|
|
self.lock.acquire()
|
|
|
|
|
try:
|
|
|
|
|
f = getattr(self, self.precmd + method)
|
|
|
|
|
f = getattr(self, self.prefcmd + method)
|
|
|
|
|
ret = f(*args, **kwargs)
|
|
|
|
|
success = True
|
|
|
|
|
self.log.info(
|
|
|
|
|
@@ -158,7 +176,28 @@ class Commands(Thread):
|
|
|
|
|
|
|
|
|
|
if module == "":
|
|
|
|
|
if method == 'help':
|
|
|
|
|
return self.list_modules()
|
|
|
|
|
# No argument was given to the help function
|
|
|
|
|
if len(args) == 0 and len(kwargs) == 0:
|
|
|
|
|
msg = "Type 'help <module>' to have the list of module's functions.\n"
|
|
|
|
|
msg += "Type 'help <module>.<function>' to documentation of a specific function.\n"
|
|
|
|
|
success, ret = self.list_modules()
|
|
|
|
|
return success, msg + ret
|
|
|
|
|
else:
|
|
|
|
|
# 1 argument has been provided
|
|
|
|
|
if len(args) > 0:
|
|
|
|
|
arg = args[0]
|
|
|
|
|
else:
|
|
|
|
|
arg = list(kwargs.keys())[0]
|
|
|
|
|
spl = arg.split(".", 1)
|
|
|
|
|
|
|
|
|
|
# help <module>
|
|
|
|
|
if len(spl) == 1:
|
|
|
|
|
module = arg
|
|
|
|
|
# help <module>.<method>
|
|
|
|
|
else:
|
|
|
|
|
module = spl[0]
|
|
|
|
|
args = [spl[1]]
|
|
|
|
|
kwargs = {}
|
|
|
|
|
else:
|
|
|
|
|
module = self.defmod
|
|
|
|
|
|
|
|
|
|
@@ -171,6 +210,10 @@ class Commands(Thread):
|
|
|
|
|
|
|
|
|
|
return self.cmods[module]._execute_command(method, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
def stop_all(self):
|
|
|
|
|
if self.stop_all_event is not None:
|
|
|
|
|
self.stop_all_event.set()
|
|
|
|
|
|
|
|
|
|
def cmd_help(self, *args, **kwargs):
|
|
|
|
|
"""Help of module commands.
|
|
|
|
|
Params:
|
|
|
|
|
@@ -181,12 +224,12 @@ class Commands(Thread):
|
|
|
|
|
|
|
|
|
|
if len(args) == 0:
|
|
|
|
|
cmds = inspect.getmembers(self, predicate=inspect.ismethod)
|
|
|
|
|
cmds = [x for x in cmds if x[0].startswith(self.precmd)]
|
|
|
|
|
cmds = [x for x in cmds if x[0].startswith(self.prefcmd)]
|
|
|
|
|
for m in cmds:
|
|
|
|
|
ret = ret + m[0][len(self.precmd):] + "\n"
|
|
|
|
|
ret = ret + m[0][len(self.prefcmd):] + "\n"
|
|
|
|
|
else:
|
|
|
|
|
if isinstance(args[0], str):
|
|
|
|
|
cmd = self.precmd + args[0]
|
|
|
|
|
cmd = self.prefcmd + args[0]
|
|
|
|
|
c = getattr(self, cmd, None)
|
|
|
|
|
if c:
|
|
|
|
|
r = inspect.getdoc(c)
|
|
|
|
|
@@ -202,20 +245,21 @@ class Commands(Thread):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CommandsLoader:
|
|
|
|
|
def __init__(self, config: ConfigParser, log: logging.Handler, modpath: str):
|
|
|
|
|
def __init__(self, stop_event: Event, config: ConfigParser, log: logging.Handler, modpath: str):
|
|
|
|
|
self.config = config
|
|
|
|
|
self.modpath = modpath
|
|
|
|
|
sys.path.append(os.path.join(modpath))
|
|
|
|
|
Commands.defmod = self.config["general"].get("default")
|
|
|
|
|
precmd = self.config["general"].get("methods_prefix")
|
|
|
|
|
if not precmd is None:
|
|
|
|
|
Commands.precmd = precmd
|
|
|
|
|
self.precmds = "cmds_"
|
|
|
|
|
precmds = self.config["general"].get("modules_prefix")
|
|
|
|
|
if not precmds is None:
|
|
|
|
|
self.precmds = precmds
|
|
|
|
|
prefcmd = self.config["general"].get("methods_prefix")
|
|
|
|
|
if not prefcmd is None:
|
|
|
|
|
Commands.prefcmd = prefcmd
|
|
|
|
|
self.prefcmds = "cmds_"
|
|
|
|
|
prefcmds = self.config["general"].get("modules_prefix")
|
|
|
|
|
if not prefcmds is None:
|
|
|
|
|
self.prefcmds = prefcmds
|
|
|
|
|
self.log = log
|
|
|
|
|
self.lock = Lock()
|
|
|
|
|
self.stop_event = stop_event
|
|
|
|
|
self._load_commands()
|
|
|
|
|
self._set_cmods()
|
|
|
|
|
|
|
|
|
|
@@ -224,7 +268,7 @@ class CommandsLoader:
|
|
|
|
|
with os.scandir(self.modpath) as it:
|
|
|
|
|
for entry in it:
|
|
|
|
|
if (
|
|
|
|
|
entry.name.startswith(self.precmds)
|
|
|
|
|
entry.name.startswith(self.prefcmds)
|
|
|
|
|
and entry.name.endswith(".py")
|
|
|
|
|
and entry.is_file()
|
|
|
|
|
):
|
|
|
|
|
@@ -232,7 +276,7 @@ class CommandsLoader:
|
|
|
|
|
if not obj is None:
|
|
|
|
|
nmod = obj.nickname
|
|
|
|
|
if nmod is None:
|
|
|
|
|
nmod = (entry.name[:-3])[len(self.precmds):]
|
|
|
|
|
nmod = (entry.name[:-3])[len(self.prefcmds):]
|
|
|
|
|
cmds.update({nmod: obj})
|
|
|
|
|
self.log.info('module "{}" loaded'.format(nmod))
|
|
|
|
|
else:
|
|
|
|
|
@@ -251,15 +295,20 @@ class CommandsLoader:
|
|
|
|
|
|
|
|
|
|
members = inspect.getmembers(module, inspect.isclass)
|
|
|
|
|
conf = None
|
|
|
|
|
if name[len(self.precmds):] in self.config.sections():
|
|
|
|
|
conf = self.config[name[len(self.precmds):]]
|
|
|
|
|
if name[len(self.prefcmds):] in self.config.sections():
|
|
|
|
|
conf = self.config[name[len(self.prefcmds):]]
|
|
|
|
|
|
|
|
|
|
obj = None
|
|
|
|
|
for n, c in members:
|
|
|
|
|
if issubclass(c, Commands) and (n != "Commands"):
|
|
|
|
|
try:
|
|
|
|
|
obj = c(conf, self.log)
|
|
|
|
|
except:
|
|
|
|
|
self.log.error(f"The object '{c.__name__}' could not be instantiated.")
|
|
|
|
|
continue
|
|
|
|
|
obj.log = self.log
|
|
|
|
|
obj.lock = self.lock
|
|
|
|
|
obj.stop_all_event = self.stop_event
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
return obj
|
|
|
|
|
@@ -271,9 +320,15 @@ class CommandsLoader:
|
|
|
|
|
def _load_dependencies(self):
|
|
|
|
|
for k, v in self.cmods.items():
|
|
|
|
|
if hasattr(v, "dependencies"):
|
|
|
|
|
for p in v.dependencies:
|
|
|
|
|
if p in self.cmods.keys():
|
|
|
|
|
setattr(v, p, self.cmods[p])
|
|
|
|
|
deps = v.dependencies
|
|
|
|
|
# dependencies can be a list or dictionary
|
|
|
|
|
if isinstance(v.dependencies, list):
|
|
|
|
|
deps = {}
|
|
|
|
|
for d in v.dependencies:
|
|
|
|
|
deps[d] = d
|
|
|
|
|
for p, pv in deps.items():
|
|
|
|
|
if pv in self.cmods.keys():
|
|
|
|
|
setattr(v, p, self.cmods[pv])
|
|
|
|
|
else:
|
|
|
|
|
self.log.error(
|
|
|
|
|
'Dependency "{}" of module "{}" could not be satisfied'.format(
|
|
|
|
|
@@ -287,11 +342,20 @@ class CommandsLoader:
|
|
|
|
|
if v.threaded:
|
|
|
|
|
v.start()
|
|
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
|
for k, v in self.cmods.items():
|
|
|
|
|
if v.threaded:
|
|
|
|
|
v.stop()
|
|
|
|
|
|
|
|
|
|
def join(self):
|
|
|
|
|
for k, v in self.cmods.items():
|
|
|
|
|
if v.threaded:
|
|
|
|
|
v.join()
|
|
|
|
|
|
|
|
|
|
def free(self):
|
|
|
|
|
for k, v in self.cmods.items():
|
|
|
|
|
v.free()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AppEngine:
|
|
|
|
|
def __init__(
|
|
|
|
|
@@ -301,16 +365,25 @@ class AppEngine:
|
|
|
|
|
log_file: str = "",
|
|
|
|
|
debug: bool = False,
|
|
|
|
|
) -> None:
|
|
|
|
|
self.cl = None
|
|
|
|
|
self.app_name = app_name
|
|
|
|
|
self.conf = ConfigParser()
|
|
|
|
|
if conf_file != "":
|
|
|
|
|
self.parse_config(conf_file)
|
|
|
|
|
self.def_log(log_file)
|
|
|
|
|
|
|
|
|
|
signal.signal(signal.SIGINT, self.signal_handler)
|
|
|
|
|
if debug:
|
|
|
|
|
self.log.setLevel(logging.DEBUG)
|
|
|
|
|
else:
|
|
|
|
|
self.log.setLevel(logging.WARNING)
|
|
|
|
|
# Mechanism to stop the application
|
|
|
|
|
self.stop_event = Event()
|
|
|
|
|
self.stop_thread = Thread(target=self.wait_stop, args=(self.stop_event,))
|
|
|
|
|
self.stop_thread.start()
|
|
|
|
|
|
|
|
|
|
def signal_handler(self, sig, frame):
|
|
|
|
|
print("\nExiting.")
|
|
|
|
|
self.stop()
|
|
|
|
|
|
|
|
|
|
def parse_config(self, cf: str):
|
|
|
|
|
if os.path.exists(cf) and os.path.isfile(cf):
|
|
|
|
|
@@ -333,13 +406,17 @@ class AppEngine:
|
|
|
|
|
if is_writeable:
|
|
|
|
|
self.log.addHandler(logging.FileHandler(fname))
|
|
|
|
|
else:
|
|
|
|
|
self.log.addHandler(journal.JournalHandler())
|
|
|
|
|
self.log.error('No write permissions: "{}"'.format(fname))
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
self.log.addHandler(journal.JournalHandler())
|
|
|
|
|
|
|
|
|
|
def exec(self, modpath: str = ""):
|
|
|
|
|
cl = CommandsLoader(self.conf, self.log, modpath)
|
|
|
|
|
cl.start()
|
|
|
|
|
cl.join()
|
|
|
|
|
self.cl = CommandsLoader(self.stop_event, self.conf, self.log, modpath)
|
|
|
|
|
self.cl.start()
|
|
|
|
|
self.cl.join()
|
|
|
|
|
self.cl.free()
|
|
|
|
|
|
|
|
|
|
def wait_stop(self, evnt):
|
|
|
|
|
evnt.wait()
|
|
|
|
|
self.cl.stop()
|
|
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
|
self.stop_event.set()
|