add plugin system
This commit is contained in:
parent
f186f19d0a
commit
1d8238ee52
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,5 +8,7 @@ data/uploads/*
|
||||
data/db/*
|
||||
!data/db/slideshow.json.dist
|
||||
config.json
|
||||
/plugins/user/*
|
||||
!/plugins/user/.gitkeep
|
||||
*.lock
|
||||
__pycache__/
|
||||
|
||||
@ -4,5 +4,5 @@ import os
|
||||
from src.Application import Application
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = Application(project_dir=os.path.dirname(__file__))
|
||||
app = Application(project_dir=os.path.dirname(__file__)[:-2])
|
||||
app.start()
|
||||
|
||||
24
plugins/system/FleetScreenRestart/FleetScreenRestart.py
Normal file
24
plugins/system/FleetScreenRestart/FleetScreenRestart.py
Normal file
@ -0,0 +1,24 @@
|
||||
from plugins.system.ObPlugin import ObPlugin
|
||||
|
||||
from typing import List, Dict
|
||||
from src.model.Variable import Variable
|
||||
from src.model.VariableType import VariableType
|
||||
from src.model.HookType import HookType
|
||||
from src.model.HookRegistration import HookRegistration
|
||||
|
||||
|
||||
class FleetScreenRestart(ObPlugin):
|
||||
|
||||
def use_id(self):
|
||||
return self.__class__.__name__.lower()
|
||||
|
||||
def use_title(self):
|
||||
return 'Fleet Screen Restart'
|
||||
|
||||
def use_variables(self) -> List[Variable]:
|
||||
return []
|
||||
|
||||
def use_hooks_registrations(self) -> List[HookRegistration]:
|
||||
return [
|
||||
super().set_hook_registration(hook=HookType.H_FLEET_SLIDESHOW_TOOLBAR_ACTIONS, priority=10)
|
||||
]
|
||||
@ -0,0 +1 @@
|
||||
<button class="normal sysinfo-restart"><i class="fa fa-refresh icon-left"></i>{{ l.sysinfo_panel_button_restart }}</button>
|
||||
50
plugins/system/ObPlugin.py
Normal file
50
plugins/system/ObPlugin.py
Normal file
@ -0,0 +1,50 @@
|
||||
import abc
|
||||
|
||||
from typing import Optional, List, Dict, Union
|
||||
from src.model.Variable import Variable
|
||||
from src.model.VariableType import VariableType
|
||||
from src.model.HookType import HookType
|
||||
from src.model.HookRegistration import HookRegistration
|
||||
from src.service.ModelStore import ModelStore
|
||||
|
||||
|
||||
class ObPlugin(abc.ABC):
|
||||
|
||||
PLUGIN_PREFIX = "plugin_"
|
||||
|
||||
def __init__(self, directory: str, model_store: ModelStore):
|
||||
self._directory = directory
|
||||
self._model_store = model_store
|
||||
|
||||
@abc.abstractmethod
|
||||
def use_id(self) -> str:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def use_title(self) -> str:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def use_variables(self) -> List[Variable]:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def use_hooks_registrations(self) -> List[HookRegistration]:
|
||||
pass
|
||||
|
||||
def get_directory(self) -> Optional[str]:
|
||||
return self._directory
|
||||
|
||||
def get_plugin_variable_prefix(self) -> str:
|
||||
return "{}{}".format(self.PLUGIN_PREFIX, self.use_id())
|
||||
|
||||
def get_plugin_variable_name(self, name: str) -> str:
|
||||
return "{}_{}".format(self.get_plugin_variable_prefix(), name)
|
||||
|
||||
def set_variable(self, name: str, value, type: VariableType, editable: bool, description: str) -> Variable:
|
||||
return self._model_store.variable().set_variable(
|
||||
name=self.get_plugin_variable_name(name), value=value, type=type, editable=editable, description=description
|
||||
)
|
||||
|
||||
def set_hook_registration(self, hook: HookType, priority: int = 0) -> HookRegistration:
|
||||
return HookRegistration(plugin=self, hook=hook, priority=priority)
|
||||
0
plugins/user/.gitkeep
Normal file
0
plugins/user/.gitkeep
Normal file
@ -3,7 +3,8 @@ import logging
|
||||
import signal
|
||||
import threading
|
||||
|
||||
from src.service.ModelManager import ModelManager
|
||||
from src.service.ModelStore import ModelStore
|
||||
from src.service.PluginStore import PluginStore
|
||||
from src.service.WebServer import WebServer
|
||||
|
||||
|
||||
@ -12,8 +13,9 @@ class Application:
|
||||
def __init__(self, project_dir: str):
|
||||
self._project_dir = project_dir
|
||||
self._stop_event = threading.Event()
|
||||
self._model_manager = ModelManager()
|
||||
self._web_server = WebServer(project_dir=project_dir, model_manager=self._model_manager)
|
||||
self._model_store = ModelStore()
|
||||
self._plugin_store = PluginStore(project_dir=project_dir, model_store=self._model_store)
|
||||
self._web_server = WebServer(project_dir=project_dir, model_store=self._model_store, plugin_store=self._plugin_store)
|
||||
|
||||
signal.signal(signal.SIGINT, self.signal_handler)
|
||||
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import json
|
||||
|
||||
from flask import Flask, render_template, redirect, request, url_for, jsonify
|
||||
from src.service.ModelManager import ModelManager
|
||||
from src.service.ModelStore import ModelStore
|
||||
from src.model.Screen import Screen
|
||||
|
||||
|
||||
class FleetController:
|
||||
|
||||
def __init__(self, app, model_manager: ModelManager):
|
||||
def __init__(self, app, model_store: ModelStore):
|
||||
self._app = app
|
||||
self._model_manager = model_manager
|
||||
self._model_store = model_store
|
||||
self.register()
|
||||
|
||||
def register(self):
|
||||
@ -24,19 +24,19 @@ class FleetController:
|
||||
def fleet(self):
|
||||
return render_template(
|
||||
'fleet/fleet.jinja.html',
|
||||
screens=self._model_manager.screen().get_enabled_screens(),
|
||||
screens=self._model_store.screen().get_enabled_screens(),
|
||||
)
|
||||
|
||||
def fleet_screen_list(self):
|
||||
return render_template(
|
||||
'fleet/list.jinja.html',
|
||||
l=self._model_manager.lang().map(),
|
||||
enabled_screens=self._model_manager.screen().get_enabled_screens(),
|
||||
disabled_screens=self._model_manager.screen().get_disabled_screens(),
|
||||
l=self._model_store.lang().map(),
|
||||
enabled_screens=self._model_store.screen().get_enabled_screens(),
|
||||
disabled_screens=self._model_store.screen().get_disabled_screens(),
|
||||
)
|
||||
|
||||
def fleet_screen_add(self):
|
||||
self._model_manager.screen().add_form(Screen(
|
||||
self._model_store.screen().add_form(Screen(
|
||||
name=request.form['name'],
|
||||
host=request.form['host'],
|
||||
port=request.form['port'],
|
||||
@ -44,20 +44,20 @@ class FleetController:
|
||||
return redirect(url_for('fleet_screen_list'))
|
||||
|
||||
def fleet_screen_edit(self):
|
||||
self._model_manager.screen().update_form(request.form['id'], request.form['name'], request.form['host'], request.form['port'])
|
||||
self._model_store.screen().update_form(request.form['id'], request.form['name'], request.form['host'], request.form['port'])
|
||||
return redirect(url_for('fleet_screen_list'))
|
||||
|
||||
def fleet_screen_toggle(self):
|
||||
data = request.get_json()
|
||||
self._model_manager.screen().update_enabled(data.get('id'), data.get('enabled'))
|
||||
self._model_store.screen().update_enabled(data.get('id'), data.get('enabled'))
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
def fleet_screen_delete(self):
|
||||
data = request.get_json()
|
||||
self._model_manager.screen().delete(data.get('id'))
|
||||
self._model_store.screen().delete(data.get('id'))
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
def fleet_screen_position(self):
|
||||
data = request.get_json()
|
||||
self._model_manager.screen().update_positions(data)
|
||||
self._model_store.screen().update_positions(data)
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import json
|
||||
|
||||
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify
|
||||
from src.service.ModelManager import ModelManager
|
||||
from src.service.ModelStore import ModelStore
|
||||
from src.utils import get_ip_address
|
||||
|
||||
|
||||
class PlayerController:
|
||||
|
||||
def __init__(self, app, model_manager: ModelManager):
|
||||
def __init__(self, app, model_store: ModelStore):
|
||||
self._app = app
|
||||
self._model_manager = model_manager
|
||||
self._model_store = model_store
|
||||
self.register()
|
||||
|
||||
def _get_playlist(self) -> dict:
|
||||
slides = self._model_manager.slide().to_dict(self._model_manager.slide().get_enabled_slides())
|
||||
slides = self._model_store.slide().to_dict(self._model_store.slide().get_enabled_slides())
|
||||
|
||||
if len(slides) == 1:
|
||||
return [slides[0], slides[0]]
|
||||
@ -35,8 +35,8 @@ class PlayerController:
|
||||
ipaddr = get_ip_address()
|
||||
return render_template(
|
||||
'player/default.jinja.html',
|
||||
ipaddr=ipaddr if ipaddr else self._model_manager.lang().map().get('common_unknown_ipaddr'),
|
||||
l=self._model_manager.lang().map()
|
||||
ipaddr=ipaddr if ipaddr else self._model_store.lang().map().get('common_unknown_ipaddr'),
|
||||
l=self._model_store.lang().map()
|
||||
)
|
||||
|
||||
def player_playlist(self):
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import json
|
||||
|
||||
from flask import Flask, render_template, redirect, request, url_for
|
||||
from src.service.ModelManager import ModelManager
|
||||
from src.service.ModelStore import ModelStore
|
||||
|
||||
|
||||
class SettingsController:
|
||||
|
||||
def __init__(self, app, model_manager: ModelManager):
|
||||
def __init__(self, app, model_store: ModelStore):
|
||||
self._app = app
|
||||
self._model_manager = model_manager
|
||||
self._model_store = model_store
|
||||
self.register()
|
||||
|
||||
def register(self):
|
||||
@ -18,10 +18,10 @@ class SettingsController:
|
||||
def settings_variable_list(self):
|
||||
return render_template(
|
||||
'settings/list.jinja.html',
|
||||
l=self._model_manager.lang().map(),
|
||||
variables=self._model_manager.variable().get_editable_variables(),
|
||||
l=self._model_store.lang().map(),
|
||||
variables=self._model_store.variable().get_editable_variables(),
|
||||
)
|
||||
|
||||
def settings_variable_edit(self):
|
||||
self._model_manager.variable().update_form(request.form['id'], request.form['value'])
|
||||
self._model_store.variable().update_form(request.form['id'], request.form['value'])
|
||||
return redirect(url_for('settings_variable_list'))
|
||||
@ -4,7 +4,7 @@ import time
|
||||
|
||||
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify
|
||||
from werkzeug.utils import secure_filename
|
||||
from src.service.ModelManager import ModelManager
|
||||
from src.service.ModelStore import ModelStore
|
||||
from src.model.Slide import Slide
|
||||
from src.model.SlideType import SlideType
|
||||
from src.utils import str_to_enum
|
||||
@ -12,9 +12,9 @@ from src.utils import str_to_enum
|
||||
|
||||
class SlideshowController:
|
||||
|
||||
def __init__(self, app, model_manager: ModelManager):
|
||||
def __init__(self, app, model_store: ModelStore):
|
||||
self._app = app
|
||||
self._model_manager = model_manager
|
||||
self._model_store = model_store
|
||||
self.register()
|
||||
|
||||
def register(self):
|
||||
@ -32,11 +32,11 @@ class SlideshowController:
|
||||
def slideshow(self):
|
||||
return render_template(
|
||||
'slideshow/list.jinja.html',
|
||||
l=self._model_manager.lang().map(),
|
||||
enabled_slides=self._model_manager.slide().get_enabled_slides(),
|
||||
disabled_slides=self._model_manager.slide().get_disabled_slides(),
|
||||
var_last_restart=self._model_manager.variable().get_one_by_name('last_restart'),
|
||||
var_external_url=self._model_manager.variable().get_one_by_name('external_url')
|
||||
l=self._model_store.lang().map(),
|
||||
enabled_slides=self._model_store.slide().get_enabled_slides(),
|
||||
disabled_slides=self._model_store.slide().get_disabled_slides(),
|
||||
var_last_restart=self._model_store.variable().get_one_by_name('last_restart'),
|
||||
var_external_url=self._model_store.variable().get_one_by_name('external_url')
|
||||
)
|
||||
|
||||
def slideshow_slide_add(self):
|
||||
@ -63,34 +63,34 @@ class SlideshowController:
|
||||
else:
|
||||
slide.location = request.form['object']
|
||||
|
||||
self._model_manager.slide().add_form(slide)
|
||||
self._model_store.slide().add_form(slide)
|
||||
self._post_update()
|
||||
|
||||
return redirect(url_for('slideshow_slide_list'))
|
||||
|
||||
def slideshow_slide_edit(self):
|
||||
self._model_manager.slide().update_form(request.form['id'], request.form['name'], request.form['duration'])
|
||||
self._model_store.slide().update_form(request.form['id'], request.form['name'], request.form['duration'])
|
||||
self._post_update()
|
||||
return redirect(url_for('slideshow_slide_list'))
|
||||
|
||||
def slideshow_slide_toggle(self):
|
||||
data = request.get_json()
|
||||
self._model_manager.slide().update_enabled(data.get('id'), data.get('enabled'))
|
||||
self._model_store.slide().update_enabled(data.get('id'), data.get('enabled'))
|
||||
self._post_update()
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
def slideshow_slide_delete(self):
|
||||
data = request.get_json()
|
||||
self._model_manager.slide().delete(data.get('id'))
|
||||
self._model_store.slide().delete(data.get('id'))
|
||||
self._post_update()
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
def slideshow_slide_position(self):
|
||||
data = request.get_json()
|
||||
self._model_manager.slide().update_positions(data)
|
||||
self._model_store.slide().update_positions(data)
|
||||
self._post_update()
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
def _post_update(self):
|
||||
self._model_manager.variable().update_by_name("last_slide_update", time.time())
|
||||
self._model_store.variable().update_by_name("last_slide_update", time.time())
|
||||
|
||||
|
||||
@ -6,16 +6,16 @@ import subprocess
|
||||
from flask import Flask, render_template, jsonify
|
||||
from src.manager.VariableManager import VariableManager
|
||||
from src.manager.ConfigManager import ConfigManager
|
||||
from src.service.ModelManager import ModelManager
|
||||
from src.service.ModelStore import ModelStore
|
||||
|
||||
from src.utils import get_ip_address
|
||||
|
||||
|
||||
class SysinfoController:
|
||||
|
||||
def __init__(self, app, model_manager: ModelManager):
|
||||
def __init__(self, app, model_store: ModelStore):
|
||||
self._app = app
|
||||
self._model_manager = model_manager
|
||||
self._model_store = model_store
|
||||
self.register()
|
||||
|
||||
def register(self):
|
||||
@ -27,14 +27,14 @@ class SysinfoController:
|
||||
ipaddr = get_ip_address()
|
||||
return render_template(
|
||||
'sysinfo/list.jinja.html',
|
||||
ipaddr=ipaddr if ipaddr else self._model_manager.lang().map().get('common_unknown_ipaddr'),
|
||||
l=self._model_manager.lang().map(),
|
||||
ro_variables=self._model_manager.variable().get_readonly_variables(),
|
||||
ipaddr=ipaddr if ipaddr else self._model_store.lang().map().get('common_unknown_ipaddr'),
|
||||
l=self._model_store.lang().map(),
|
||||
ro_variables=self._model_store.variable().get_readonly_variables(),
|
||||
)
|
||||
|
||||
def sysinfo_restart(self):
|
||||
if platform.system().lower() == 'darwin':
|
||||
if self._model_manager.config().map().get('debug'):
|
||||
if self._model_store.config().map().get('debug'):
|
||||
python = sys.executable
|
||||
os.execl(python, python, *sys.argv)
|
||||
else:
|
||||
@ -49,8 +49,8 @@ class SysinfoController:
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
def sysinfo_restart_needed(self):
|
||||
var_last_slide_update = self._model_manager.variable().get_one_by_name('last_slide_update')
|
||||
var_last_restart = self._model_manager.variable().get_one_by_name('last_restart')
|
||||
var_last_slide_update = self._model_store.variable().get_one_by_name('last_slide_update')
|
||||
var_last_restart = self._model_store.variable().get_one_by_name('last_restart')
|
||||
|
||||
if var_last_slide_update.value <= var_last_restart.value:
|
||||
return jsonify({'status': False})
|
||||
|
||||
@ -83,7 +83,6 @@ class SlideManager:
|
||||
|
||||
def delete(self, id: str) -> None:
|
||||
slide = self.get(id)
|
||||
print(id)
|
||||
|
||||
if slide:
|
||||
if slide.has_file():
|
||||
|
||||
@ -15,18 +15,19 @@ class VariableManager:
|
||||
self._var_map = {}
|
||||
self.reload()
|
||||
|
||||
def reload(self, lang_map: Optional[Dict] = None) -> None:
|
||||
default_vars = [
|
||||
{"name": "port", "value": 5000, "type": VariableType.INT.value, "editable": True, "description": lang_map['settings_variable_help_port'] if lang_map else ""},
|
||||
{"name": "bind", "value": '0.0.0.0', "type": VariableType.STRING.value, "editable": True, "description": lang_map['settings_variable_help_bind'] if lang_map else ""},
|
||||
{"name": "lang", "value": "en", "type": VariableType.STRING.value, "editable": True, "description": lang_map['settings_variable_help_lang'] if lang_map else ""},
|
||||
{"name": "fleet_enabled", "value": "0", "type": VariableType.BOOL.value, "editable": True, "description": lang_map['settings_variable_help_fleet_enabled'] if lang_map else ""},
|
||||
{"name": "external_url", "value": "", "type": VariableType.STRING.value, "editable": True, "description": lang_map['settings_variable_help_external_url'] if lang_map else ""},
|
||||
{"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP.value, "editable": False, "description": lang_map['settings_variable_help_ro_editable'] if lang_map else ""},
|
||||
{"name": "last_slide_update", "value": time.time(), "type": VariableType.TIMESTAMP.value, "editable": False, "description": lang_map['settings_variable_help_ro_last_slide_update'] if lang_map else ""},
|
||||
]
|
||||
def set_variable(self, name: str, value, type: VariableType, editable: bool, description: str) -> Variable:
|
||||
if isinstance(value, bool) and value:
|
||||
value = '1'
|
||||
elif isinstance(value, bool) and not value:
|
||||
value = '0'
|
||||
|
||||
for default_var in default_vars:
|
||||
default_var = {
|
||||
"name": name,
|
||||
"value": value,
|
||||
"type": type.value,
|
||||
"editable": editable,
|
||||
"description": description
|
||||
}
|
||||
variable = self.get_one_by_name(default_var['name'])
|
||||
|
||||
if not variable:
|
||||
@ -38,15 +39,35 @@ class VariableManager:
|
||||
if variable.name == 'last_restart':
|
||||
self._db.update_by_id(variable.id, {"value": time.time()})
|
||||
|
||||
return variable
|
||||
|
||||
def reload(self, lang_map: Optional[Dict] = None) -> None:
|
||||
default_vars = [
|
||||
{"name": "port", "value": 5000, "type": VariableType.INT, "editable": True, "description": lang_map['settings_variable_help_port'] if lang_map else ""},
|
||||
{"name": "bind", "value": '0.0.0.0', "type": VariableType.STRING, "editable": True, "description": lang_map['settings_variable_help_bind'] if lang_map else ""},
|
||||
{"name": "lang", "value": "en", "type": VariableType.STRING, "editable": True, "description": lang_map['settings_variable_help_lang'] if lang_map else ""},
|
||||
{"name": "fleet_enabled", "value": False, "type": VariableType.BOOL, "editable": True, "description": lang_map['settings_variable_help_fleet_enabled'] if lang_map else ""},
|
||||
{"name": "external_url", "value": "", "type": VariableType.STRING, "editable": True, "description": lang_map['settings_variable_help_external_url'] if lang_map else ""},
|
||||
{"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": lang_map['settings_variable_help_ro_editable'] if lang_map else ""},
|
||||
{"name": "last_slide_update", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": lang_map['settings_variable_help_ro_last_slide_update'] if lang_map else ""},
|
||||
]
|
||||
|
||||
for default_var in default_vars:
|
||||
self.set_variable(**default_var)
|
||||
|
||||
self._var_map = self.prepare_variable_map()
|
||||
|
||||
def map(self) -> dict:
|
||||
return self._var_map
|
||||
|
||||
def prepare_variable_map(self) -> Dict[str, Variable]:
|
||||
return self.list_to_map(self.get_all())
|
||||
|
||||
@staticmethod
|
||||
def list_to_map(list: List[Variable]) -> Dict[str, Variable]:
|
||||
var_map = {}
|
||||
|
||||
for var in self.get_all():
|
||||
for var in list:
|
||||
var_map[var.name] = var
|
||||
|
||||
return var_map
|
||||
@ -75,6 +96,9 @@ class VariableManager:
|
||||
def get_by(self, query) -> List[Variable]:
|
||||
return self.hydrate_dict(self._db.get_by_query(query=query))
|
||||
|
||||
def get_by_prefix(self, prefix: str) -> List[Variable]:
|
||||
return self.hydrate_dict(self._db.get_by_query(query=lambda v: v['name'].startswith(prefix)))
|
||||
|
||||
def get_one_by_name(self, name: str) -> Optional[Variable]:
|
||||
return self.get_one_by(query=lambda v: v['name'] == name)
|
||||
|
||||
|
||||
51
src/model/HookRegistration.py
Normal file
51
src/model/HookRegistration.py
Normal file
@ -0,0 +1,51 @@
|
||||
from src.model.HookType import HookType
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class HookRegistration:
|
||||
|
||||
def __init__(self, plugin, hook: HookType, priority: int = 0, template: Optional[str] = None):
|
||||
self._plugin = plugin
|
||||
self._hook = hook
|
||||
self._priority = priority
|
||||
self._template = template
|
||||
|
||||
@property
|
||||
def plugin(self):
|
||||
return self._plugin
|
||||
|
||||
@plugin.setter
|
||||
def plugin(self, value):
|
||||
self._plugin = value
|
||||
|
||||
@property
|
||||
def hook(self) -> HookType:
|
||||
return self._hook
|
||||
|
||||
@hook.setter
|
||||
def hook(self, value: HookType):
|
||||
self._hook = value
|
||||
|
||||
@property
|
||||
def priority(self) -> int:
|
||||
return self._priority
|
||||
|
||||
@priority.setter
|
||||
def priority(self, value: int):
|
||||
self._priority = value
|
||||
|
||||
@property
|
||||
def template(self) -> Optional[str]:
|
||||
return self._template
|
||||
|
||||
@template.setter
|
||||
def template(self, value: Optional[str]):
|
||||
self._template = value
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"HookRegistration(" \
|
||||
f"plugin='{self.plugin.get_id()}',\n" \
|
||||
f"hook='{self.hook}',\n" \
|
||||
f"priority='{self.priority}',\n" \
|
||||
f"template='{self.template}',\n" \
|
||||
f")"
|
||||
6
src/model/HookType.py
Normal file
6
src/model/HookType.py
Normal file
@ -0,0 +1,6 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class HookType(Enum):
|
||||
|
||||
H_FLEET_SLIDESHOW_TOOLBAR_ACTIONS = 'h_fleet_slideshow_toolbar_actions'
|
||||
@ -6,7 +6,7 @@ from src.manager.ConfigManager import ConfigManager
|
||||
from src.manager.LoggingManager import LoggingManager
|
||||
|
||||
|
||||
class ModelManager:
|
||||
class ModelStore:
|
||||
|
||||
def __init__(self):
|
||||
self._variable_manager = VariableManager()
|
||||
112
src/service/PluginStore.py
Normal file
112
src/service/PluginStore.py
Normal file
@ -0,0 +1,112 @@
|
||||
import os
|
||||
import logging
|
||||
import inspect
|
||||
import importlib
|
||||
|
||||
from plugins.system.ObPlugin import ObPlugin
|
||||
from src.service.ModelStore import ModelStore
|
||||
from src.manager.VariableManager import VariableManager
|
||||
from src.model.VariableType import VariableType
|
||||
from src.model.HookType import HookType
|
||||
from src.model.HookRegistration import HookRegistration
|
||||
from typing import List, Dict, Union
|
||||
|
||||
|
||||
class PluginStore:
|
||||
|
||||
FOLDER_PLUGINS_SYSTEM = 'plugins/system'
|
||||
FOLDER_PLUGINS_USER = 'plugins/user'
|
||||
DEFAULT_PLUGIN_ENABLED_VARIABLE = "enabled"
|
||||
|
||||
def __init__(self, project_dir: str, model_store: ModelStore):
|
||||
self._project_dir = project_dir
|
||||
self._model_store = model_store
|
||||
self._hooks = self.pre_load_hooks()
|
||||
self._dead_variables_candidates = VariableManager.list_to_map(self._model_store.variable().get_by_prefix(ObPlugin.PLUGIN_PREFIX))
|
||||
self._system_plugins = self.find_plugins_in_directory(self.FOLDER_PLUGINS_SYSTEM)
|
||||
self._system_plugins = self.find_plugins_in_directory(self.FOLDER_PLUGINS_USER)
|
||||
self.post_load_hooks()
|
||||
self.clean_dead_variables()
|
||||
|
||||
|
||||
def map_hooks(self) -> Dict[HookType, List[HookRegistration]]:
|
||||
return self._hooks
|
||||
|
||||
def find_plugins_in_directory(self, directory: str) -> list:
|
||||
plugins = []
|
||||
for root, dirs, files in os.walk('{}/{}'.format(self._project_dir, directory)):
|
||||
for file in files:
|
||||
if file.endswith(".py"):
|
||||
module_name = file[:-3]
|
||||
module_path = os.path.join(root, file)
|
||||
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
for name, obj in inspect.getmembers(module):
|
||||
if inspect.isclass(obj) and issubclass(obj, ObPlugin) and obj is not ObPlugin:
|
||||
plugin = obj(
|
||||
directory=root,
|
||||
model_store=self._model_store
|
||||
)
|
||||
plugins.append(plugin)
|
||||
self.setup_plugin(plugin)
|
||||
|
||||
return plugins
|
||||
|
||||
def pre_load_hooks(self) -> Dict[HookType, List[HookRegistration]]:
|
||||
hooks = {}
|
||||
|
||||
for hook in HookType:
|
||||
hooks[hook] = []
|
||||
|
||||
return hooks
|
||||
|
||||
def post_load_hooks(self) -> None:
|
||||
for hook_type in self._hooks:
|
||||
self._hooks[hook_type] = sorted(self._hooks[hook_type], key=lambda hook_reg: hook_reg.priority, reverse=True)
|
||||
|
||||
def setup_plugin(self, plugin: ObPlugin) -> None:
|
||||
variables = plugin.use_variables() + [
|
||||
plugin.set_variable(
|
||||
name=self.DEFAULT_PLUGIN_ENABLED_VARIABLE,
|
||||
value=False,
|
||||
type=VariableType.BOOL,
|
||||
editable=True,
|
||||
description="Enable {} plugin".format(plugin.use_title())
|
||||
)
|
||||
]
|
||||
|
||||
if not self.is_plugin_enabled(plugin):
|
||||
return
|
||||
|
||||
for variable in variables:
|
||||
if variable.name in self._dead_variables_candidates:
|
||||
del self._dead_variables_candidates[variable.name]
|
||||
|
||||
hooks_registrations = plugin.use_hooks_registrations()
|
||||
|
||||
for hook_registration in hooks_registrations:
|
||||
if hook_registration.hook not in self._hooks:
|
||||
logging.error("Hook {} does not exist".format(hook.name))
|
||||
continue
|
||||
|
||||
hook_registration.template = "{}/views/{}.jinja.html".format(plugin.get_directory(), hook_registration.hook.value)
|
||||
self._hooks[hook_registration.hook].append(hook_registration)
|
||||
|
||||
logging.info("Plugin {} loaded ({} var{} and {} hook) from {}".format(
|
||||
plugin.use_title(),
|
||||
len(variables),
|
||||
"s" if len(variables) > 1 else "",
|
||||
len(hooks_registrations),
|
||||
"s" if len(hooks_registrations) > 1 else "",
|
||||
plugin.get_directory()
|
||||
))
|
||||
|
||||
def clean_dead_variables(self) -> None:
|
||||
for variable_name, variable in self._dead_variables_candidates.items():
|
||||
logging.info("Removing dead plugin variable {}".format(variable_name))
|
||||
self._model_store.variable().delete(variable.id)
|
||||
|
||||
def is_plugin_enabled(self, plugin: ObPlugin) -> bool:
|
||||
var = self._model_store.variable().get_one_by_name(plugin.get_plugin_variable_name(self.DEFAULT_PLUGIN_ENABLED_VARIABLE))
|
||||
return var.as_bool() if var else False
|
||||
@ -1,13 +1,16 @@
|
||||
import os
|
||||
import time
|
||||
|
||||
from flask import Flask, send_from_directory
|
||||
from src.service.ModelManager import ModelManager
|
||||
from flask import Flask, send_from_directory, Markup
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
from src.service.ModelStore import ModelStore
|
||||
from src.service.PluginStore import PluginStore
|
||||
from src.controller.PlayerController import PlayerController
|
||||
from src.controller.SlideshowController import SlideshowController
|
||||
from src.controller.FleetController import FleetController
|
||||
from src.controller.SysinfoController import SysinfoController
|
||||
from src.controller.SettingsController import SettingsController
|
||||
from src.model.HookType import HookType
|
||||
|
||||
|
||||
class WebServer:
|
||||
@ -18,16 +21,17 @@ class WebServer:
|
||||
FOLDER_STATIC_WEB_ASSETS = "www"
|
||||
MAX_UPLOADS = 16 * 1024 * 1024
|
||||
|
||||
def __init__(self, project_dir: str, model_manager: ModelManager):
|
||||
def __init__(self, project_dir: str, model_store: ModelStore, plugin_store: PluginStore):
|
||||
self._project_dir = project_dir
|
||||
self._model_manager = model_manager
|
||||
self._debug = self._model_manager.config().map().get('debug')
|
||||
self._model_store = model_store
|
||||
self._plugin_store = plugin_store
|
||||
self._debug = self._model_store.config().map().get('debug')
|
||||
self.setup()
|
||||
|
||||
def run(self) -> None:
|
||||
self._app.run(
|
||||
host=self._model_manager.variable().map().get('bind').as_string(),
|
||||
port=self._model_manager.variable().map().get('port').as_int(),
|
||||
host=self._model_store.variable().map().get('bind').as_string(),
|
||||
port=self._model_store.variable().map().get('port').as_int(),
|
||||
debug=self._debug
|
||||
)
|
||||
|
||||
@ -58,23 +62,29 @@ class WebServer:
|
||||
self._app.config['TEMPLATES_AUTO_RELOAD'] = True
|
||||
|
||||
def _setup_view_controllers(self) -> None:
|
||||
PlayerController(self._app, self._model_manager)
|
||||
SlideshowController(self._app, self._model_manager)
|
||||
SettingsController(self._app, self._model_manager)
|
||||
SysinfoController(self._app, self._model_manager)
|
||||
PlayerController(self._app, self._model_store)
|
||||
SlideshowController(self._app, self._model_store)
|
||||
SettingsController(self._app, self._model_store)
|
||||
SysinfoController(self._app, self._model_store)
|
||||
|
||||
if self._model_manager.variable().map().get('fleet_enabled').as_bool():
|
||||
FleetController(self._app, self._model_manager)
|
||||
if self._model_store.variable().map().get('fleet_enabled').as_bool():
|
||||
FleetController(self._app, self._model_store)
|
||||
|
||||
def _setup_view_globals(self) -> None:
|
||||
@self._app.context_processor
|
||||
def inject_global_vars():
|
||||
return dict(
|
||||
FLEET_ENABLED=self._model_manager.variable().map().get('fleet_enabled').as_bool(),
|
||||
LANG=self._model_manager.variable().map().get('lang').as_string(),
|
||||
STATIC_PREFIX="/{}/{}/".format(self.FOLDER_STATIC, self.FOLDER_STATIC_WEB_ASSETS)
|
||||
globals = dict(
|
||||
FLEET_ENABLED=self._model_store.variable().map().get('fleet_enabled').as_bool(),
|
||||
LANG=self._model_store.variable().map().get('lang').as_string(),
|
||||
STATIC_PREFIX="/{}/{}/".format(self.FOLDER_STATIC, self.FOLDER_STATIC_WEB_ASSETS),
|
||||
HOOK=self.render_hook,
|
||||
)
|
||||
|
||||
for hook in HookType:
|
||||
globals[hook.name] = hook
|
||||
|
||||
return globals
|
||||
|
||||
def _setup_view_extensions(self) -> None:
|
||||
@self._app.template_filter('ctime')
|
||||
def time_ctime(s):
|
||||
@ -84,3 +94,22 @@ class WebServer:
|
||||
@self._app.errorhandler(404)
|
||||
def not_found(e):
|
||||
return send_from_directory(self._get_template_folder(), 'core/error404.html'), 404
|
||||
|
||||
def render_hook(self, hook: HookType):
|
||||
content = []
|
||||
|
||||
for hook_registration in self._plugin_store.map_hooks()[hook]:
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader("{}/{}".format(hook_registration.plugin.get_directory(), self.FOLDER_TEMPLATES)),
|
||||
autoescape=select_autoescape(['html', 'xml'])
|
||||
)
|
||||
|
||||
template = env.get_template(os.path.basename(hook_registration.template))
|
||||
content.append(
|
||||
template.render(
|
||||
l=self._model_store.lang().map()
|
||||
)
|
||||
)
|
||||
|
||||
return Markup("".join(content))
|
||||
|
||||
@ -16,8 +16,9 @@
|
||||
<h2>{{ l.slideshow_page_title }}</h2>
|
||||
|
||||
<div class="toolbar-actions">
|
||||
|
||||
{% if fleet_mode %}
|
||||
<button class="normal sysinfo-restart"><i class="fa fa-refresh icon-left"></i>{{ l.sysinfo_panel_button_restart }}</button>
|
||||
{{ HOOK(H_FLEET_SLIDESHOW_TOOLBAR_ACTIONS) }}
|
||||
{% endif %}
|
||||
|
||||
<button class="purple slide-add item-add"><i class="fa fa-plus icon-left"></i>{{ l.slideshow_slide_button_add }}</button>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user