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/*
|
||||||
!data/db/slideshow.json.dist
|
!data/db/slideshow.json.dist
|
||||||
config.json
|
config.json
|
||||||
|
/plugins/user/*
|
||||||
|
!/plugins/user/.gitkeep
|
||||||
*.lock
|
*.lock
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
@ -4,5 +4,5 @@ import os
|
|||||||
from src.Application import Application
|
from src.Application import Application
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = Application(project_dir=os.path.dirname(__file__))
|
app = Application(project_dir=os.path.dirname(__file__)[:-2])
|
||||||
app.start()
|
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 signal
|
||||||
import threading
|
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
|
from src.service.WebServer import WebServer
|
||||||
|
|
||||||
|
|
||||||
@ -12,8 +13,9 @@ class Application:
|
|||||||
def __init__(self, project_dir: str):
|
def __init__(self, project_dir: str):
|
||||||
self._project_dir = project_dir
|
self._project_dir = project_dir
|
||||||
self._stop_event = threading.Event()
|
self._stop_event = threading.Event()
|
||||||
self._model_manager = ModelManager()
|
self._model_store = ModelStore()
|
||||||
self._web_server = WebServer(project_dir=project_dir, model_manager=self._model_manager)
|
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)
|
signal.signal(signal.SIGINT, self.signal_handler)
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from flask import Flask, render_template, redirect, request, url_for, jsonify
|
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
|
from src.model.Screen import Screen
|
||||||
|
|
||||||
|
|
||||||
class FleetController:
|
class FleetController:
|
||||||
|
|
||||||
def __init__(self, app, model_manager: ModelManager):
|
def __init__(self, app, model_store: ModelStore):
|
||||||
self._app = app
|
self._app = app
|
||||||
self._model_manager = model_manager
|
self._model_store = model_store
|
||||||
self.register()
|
self.register()
|
||||||
|
|
||||||
def register(self):
|
def register(self):
|
||||||
@ -24,19 +24,19 @@ class FleetController:
|
|||||||
def fleet(self):
|
def fleet(self):
|
||||||
return render_template(
|
return render_template(
|
||||||
'fleet/fleet.jinja.html',
|
'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):
|
def fleet_screen_list(self):
|
||||||
return render_template(
|
return render_template(
|
||||||
'fleet/list.jinja.html',
|
'fleet/list.jinja.html',
|
||||||
l=self._model_manager.lang().map(),
|
l=self._model_store.lang().map(),
|
||||||
enabled_screens=self._model_manager.screen().get_enabled_screens(),
|
enabled_screens=self._model_store.screen().get_enabled_screens(),
|
||||||
disabled_screens=self._model_manager.screen().get_disabled_screens(),
|
disabled_screens=self._model_store.screen().get_disabled_screens(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def fleet_screen_add(self):
|
def fleet_screen_add(self):
|
||||||
self._model_manager.screen().add_form(Screen(
|
self._model_store.screen().add_form(Screen(
|
||||||
name=request.form['name'],
|
name=request.form['name'],
|
||||||
host=request.form['host'],
|
host=request.form['host'],
|
||||||
port=request.form['port'],
|
port=request.form['port'],
|
||||||
@ -44,20 +44,20 @@ class FleetController:
|
|||||||
return redirect(url_for('fleet_screen_list'))
|
return redirect(url_for('fleet_screen_list'))
|
||||||
|
|
||||||
def fleet_screen_edit(self):
|
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'))
|
return redirect(url_for('fleet_screen_list'))
|
||||||
|
|
||||||
def fleet_screen_toggle(self):
|
def fleet_screen_toggle(self):
|
||||||
data = request.get_json()
|
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'})
|
return jsonify({'status': 'ok'})
|
||||||
|
|
||||||
def fleet_screen_delete(self):
|
def fleet_screen_delete(self):
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
self._model_manager.screen().delete(data.get('id'))
|
self._model_store.screen().delete(data.get('id'))
|
||||||
return jsonify({'status': 'ok'})
|
return jsonify({'status': 'ok'})
|
||||||
|
|
||||||
def fleet_screen_position(self):
|
def fleet_screen_position(self):
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
self._model_manager.screen().update_positions(data)
|
self._model_store.screen().update_positions(data)
|
||||||
return jsonify({'status': 'ok'})
|
return jsonify({'status': 'ok'})
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify
|
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
|
from src.utils import get_ip_address
|
||||||
|
|
||||||
|
|
||||||
class PlayerController:
|
class PlayerController:
|
||||||
|
|
||||||
def __init__(self, app, model_manager: ModelManager):
|
def __init__(self, app, model_store: ModelStore):
|
||||||
self._app = app
|
self._app = app
|
||||||
self._model_manager = model_manager
|
self._model_store = model_store
|
||||||
self.register()
|
self.register()
|
||||||
|
|
||||||
def _get_playlist(self) -> dict:
|
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:
|
if len(slides) == 1:
|
||||||
return [slides[0], slides[0]]
|
return [slides[0], slides[0]]
|
||||||
@ -35,8 +35,8 @@ class PlayerController:
|
|||||||
ipaddr = get_ip_address()
|
ipaddr = get_ip_address()
|
||||||
return render_template(
|
return render_template(
|
||||||
'player/default.jinja.html',
|
'player/default.jinja.html',
|
||||||
ipaddr=ipaddr if ipaddr else self._model_manager.lang().map().get('common_unknown_ipaddr'),
|
ipaddr=ipaddr if ipaddr else self._model_store.lang().map().get('common_unknown_ipaddr'),
|
||||||
l=self._model_manager.lang().map()
|
l=self._model_store.lang().map()
|
||||||
)
|
)
|
||||||
|
|
||||||
def player_playlist(self):
|
def player_playlist(self):
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from flask import Flask, render_template, redirect, request, url_for
|
from flask import Flask, render_template, redirect, request, url_for
|
||||||
from src.service.ModelManager import ModelManager
|
from src.service.ModelStore import ModelStore
|
||||||
|
|
||||||
|
|
||||||
class SettingsController:
|
class SettingsController:
|
||||||
|
|
||||||
def __init__(self, app, model_manager: ModelManager):
|
def __init__(self, app, model_store: ModelStore):
|
||||||
self._app = app
|
self._app = app
|
||||||
self._model_manager = model_manager
|
self._model_store = model_store
|
||||||
self.register()
|
self.register()
|
||||||
|
|
||||||
def register(self):
|
def register(self):
|
||||||
@ -18,10 +18,10 @@ class SettingsController:
|
|||||||
def settings_variable_list(self):
|
def settings_variable_list(self):
|
||||||
return render_template(
|
return render_template(
|
||||||
'settings/list.jinja.html',
|
'settings/list.jinja.html',
|
||||||
l=self._model_manager.lang().map(),
|
l=self._model_store.lang().map(),
|
||||||
variables=self._model_manager.variable().get_editable_variables(),
|
variables=self._model_store.variable().get_editable_variables(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def settings_variable_edit(self):
|
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'))
|
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 flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify
|
||||||
from werkzeug.utils import secure_filename
|
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.Slide import Slide
|
||||||
from src.model.SlideType import SlideType
|
from src.model.SlideType import SlideType
|
||||||
from src.utils import str_to_enum
|
from src.utils import str_to_enum
|
||||||
@ -12,9 +12,9 @@ from src.utils import str_to_enum
|
|||||||
|
|
||||||
class SlideshowController:
|
class SlideshowController:
|
||||||
|
|
||||||
def __init__(self, app, model_manager: ModelManager):
|
def __init__(self, app, model_store: ModelStore):
|
||||||
self._app = app
|
self._app = app
|
||||||
self._model_manager = model_manager
|
self._model_store = model_store
|
||||||
self.register()
|
self.register()
|
||||||
|
|
||||||
def register(self):
|
def register(self):
|
||||||
@ -32,11 +32,11 @@ class SlideshowController:
|
|||||||
def slideshow(self):
|
def slideshow(self):
|
||||||
return render_template(
|
return render_template(
|
||||||
'slideshow/list.jinja.html',
|
'slideshow/list.jinja.html',
|
||||||
l=self._model_manager.lang().map(),
|
l=self._model_store.lang().map(),
|
||||||
enabled_slides=self._model_manager.slide().get_enabled_slides(),
|
enabled_slides=self._model_store.slide().get_enabled_slides(),
|
||||||
disabled_slides=self._model_manager.slide().get_disabled_slides(),
|
disabled_slides=self._model_store.slide().get_disabled_slides(),
|
||||||
var_last_restart=self._model_manager.variable().get_one_by_name('last_restart'),
|
var_last_restart=self._model_store.variable().get_one_by_name('last_restart'),
|
||||||
var_external_url=self._model_manager.variable().get_one_by_name('external_url')
|
var_external_url=self._model_store.variable().get_one_by_name('external_url')
|
||||||
)
|
)
|
||||||
|
|
||||||
def slideshow_slide_add(self):
|
def slideshow_slide_add(self):
|
||||||
@ -63,34 +63,34 @@ class SlideshowController:
|
|||||||
else:
|
else:
|
||||||
slide.location = request.form['object']
|
slide.location = request.form['object']
|
||||||
|
|
||||||
self._model_manager.slide().add_form(slide)
|
self._model_store.slide().add_form(slide)
|
||||||
self._post_update()
|
self._post_update()
|
||||||
|
|
||||||
return redirect(url_for('slideshow_slide_list'))
|
return redirect(url_for('slideshow_slide_list'))
|
||||||
|
|
||||||
def slideshow_slide_edit(self):
|
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()
|
self._post_update()
|
||||||
return redirect(url_for('slideshow_slide_list'))
|
return redirect(url_for('slideshow_slide_list'))
|
||||||
|
|
||||||
def slideshow_slide_toggle(self):
|
def slideshow_slide_toggle(self):
|
||||||
data = request.get_json()
|
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()
|
self._post_update()
|
||||||
return jsonify({'status': 'ok'})
|
return jsonify({'status': 'ok'})
|
||||||
|
|
||||||
def slideshow_slide_delete(self):
|
def slideshow_slide_delete(self):
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
self._model_manager.slide().delete(data.get('id'))
|
self._model_store.slide().delete(data.get('id'))
|
||||||
self._post_update()
|
self._post_update()
|
||||||
return jsonify({'status': 'ok'})
|
return jsonify({'status': 'ok'})
|
||||||
|
|
||||||
def slideshow_slide_position(self):
|
def slideshow_slide_position(self):
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
self._model_manager.slide().update_positions(data)
|
self._model_store.slide().update_positions(data)
|
||||||
self._post_update()
|
self._post_update()
|
||||||
return jsonify({'status': 'ok'})
|
return jsonify({'status': 'ok'})
|
||||||
|
|
||||||
def _post_update(self):
|
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 flask import Flask, render_template, jsonify
|
||||||
from src.manager.VariableManager import VariableManager
|
from src.manager.VariableManager import VariableManager
|
||||||
from src.manager.ConfigManager import ConfigManager
|
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
|
from src.utils import get_ip_address
|
||||||
|
|
||||||
|
|
||||||
class SysinfoController:
|
class SysinfoController:
|
||||||
|
|
||||||
def __init__(self, app, model_manager: ModelManager):
|
def __init__(self, app, model_store: ModelStore):
|
||||||
self._app = app
|
self._app = app
|
||||||
self._model_manager = model_manager
|
self._model_store = model_store
|
||||||
self.register()
|
self.register()
|
||||||
|
|
||||||
def register(self):
|
def register(self):
|
||||||
@ -27,14 +27,14 @@ class SysinfoController:
|
|||||||
ipaddr = get_ip_address()
|
ipaddr = get_ip_address()
|
||||||
return render_template(
|
return render_template(
|
||||||
'sysinfo/list.jinja.html',
|
'sysinfo/list.jinja.html',
|
||||||
ipaddr=ipaddr if ipaddr else self._model_manager.lang().map().get('common_unknown_ipaddr'),
|
ipaddr=ipaddr if ipaddr else self._model_store.lang().map().get('common_unknown_ipaddr'),
|
||||||
l=self._model_manager.lang().map(),
|
l=self._model_store.lang().map(),
|
||||||
ro_variables=self._model_manager.variable().get_readonly_variables(),
|
ro_variables=self._model_store.variable().get_readonly_variables(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def sysinfo_restart(self):
|
def sysinfo_restart(self):
|
||||||
if platform.system().lower() == 'darwin':
|
if platform.system().lower() == 'darwin':
|
||||||
if self._model_manager.config().map().get('debug'):
|
if self._model_store.config().map().get('debug'):
|
||||||
python = sys.executable
|
python = sys.executable
|
||||||
os.execl(python, python, *sys.argv)
|
os.execl(python, python, *sys.argv)
|
||||||
else:
|
else:
|
||||||
@ -49,8 +49,8 @@ class SysinfoController:
|
|||||||
return jsonify({'status': 'ok'})
|
return jsonify({'status': 'ok'})
|
||||||
|
|
||||||
def sysinfo_restart_needed(self):
|
def sysinfo_restart_needed(self):
|
||||||
var_last_slide_update = self._model_manager.variable().get_one_by_name('last_slide_update')
|
var_last_slide_update = self._model_store.variable().get_one_by_name('last_slide_update')
|
||||||
var_last_restart = self._model_manager.variable().get_one_by_name('last_restart')
|
var_last_restart = self._model_store.variable().get_one_by_name('last_restart')
|
||||||
|
|
||||||
if var_last_slide_update.value <= var_last_restart.value:
|
if var_last_slide_update.value <= var_last_restart.value:
|
||||||
return jsonify({'status': False})
|
return jsonify({'status': False})
|
||||||
|
|||||||
@ -83,7 +83,6 @@ class SlideManager:
|
|||||||
|
|
||||||
def delete(self, id: str) -> None:
|
def delete(self, id: str) -> None:
|
||||||
slide = self.get(id)
|
slide = self.get(id)
|
||||||
print(id)
|
|
||||||
|
|
||||||
if slide:
|
if slide:
|
||||||
if slide.has_file():
|
if slide.has_file():
|
||||||
|
|||||||
@ -15,18 +15,19 @@ class VariableManager:
|
|||||||
self._var_map = {}
|
self._var_map = {}
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|
||||||
def reload(self, lang_map: Optional[Dict] = None) -> None:
|
def set_variable(self, name: str, value, type: VariableType, editable: bool, description: str) -> Variable:
|
||||||
default_vars = [
|
if isinstance(value, bool) and value:
|
||||||
{"name": "port", "value": 5000, "type": VariableType.INT.value, "editable": True, "description": lang_map['settings_variable_help_port'] if lang_map else ""},
|
value = '1'
|
||||||
{"name": "bind", "value": '0.0.0.0', "type": VariableType.STRING.value, "editable": True, "description": lang_map['settings_variable_help_bind'] if lang_map else ""},
|
elif isinstance(value, bool) and not value:
|
||||||
{"name": "lang", "value": "en", "type": VariableType.STRING.value, "editable": True, "description": lang_map['settings_variable_help_lang'] if lang_map else ""},
|
value = '0'
|
||||||
{"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 ""},
|
|
||||||
]
|
|
||||||
|
|
||||||
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'])
|
variable = self.get_one_by_name(default_var['name'])
|
||||||
|
|
||||||
if not variable:
|
if not variable:
|
||||||
@ -38,15 +39,35 @@ class VariableManager:
|
|||||||
if variable.name == 'last_restart':
|
if variable.name == 'last_restart':
|
||||||
self._db.update_by_id(variable.id, {"value": time.time()})
|
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()
|
self._var_map = self.prepare_variable_map()
|
||||||
|
|
||||||
def map(self) -> dict:
|
def map(self) -> dict:
|
||||||
return self._var_map
|
return self._var_map
|
||||||
|
|
||||||
def prepare_variable_map(self) -> Dict[str, Variable]:
|
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 = {}
|
var_map = {}
|
||||||
|
|
||||||
for var in self.get_all():
|
for var in list:
|
||||||
var_map[var.name] = var
|
var_map[var.name] = var
|
||||||
|
|
||||||
return var_map
|
return var_map
|
||||||
@ -75,6 +96,9 @@ class VariableManager:
|
|||||||
def get_by(self, query) -> List[Variable]:
|
def get_by(self, query) -> List[Variable]:
|
||||||
return self.hydrate_dict(self._db.get_by_query(query=query))
|
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]:
|
def get_one_by_name(self, name: str) -> Optional[Variable]:
|
||||||
return self.get_one_by(query=lambda v: v['name'] == name)
|
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
|
from src.manager.LoggingManager import LoggingManager
|
||||||
|
|
||||||
|
|
||||||
class ModelManager:
|
class ModelStore:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._variable_manager = VariableManager()
|
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 os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from flask import Flask, send_from_directory
|
from flask import Flask, send_from_directory, Markup
|
||||||
from src.service.ModelManager import ModelManager
|
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.PlayerController import PlayerController
|
||||||
from src.controller.SlideshowController import SlideshowController
|
from src.controller.SlideshowController import SlideshowController
|
||||||
from src.controller.FleetController import FleetController
|
from src.controller.FleetController import FleetController
|
||||||
from src.controller.SysinfoController import SysinfoController
|
from src.controller.SysinfoController import SysinfoController
|
||||||
from src.controller.SettingsController import SettingsController
|
from src.controller.SettingsController import SettingsController
|
||||||
|
from src.model.HookType import HookType
|
||||||
|
|
||||||
|
|
||||||
class WebServer:
|
class WebServer:
|
||||||
@ -18,16 +21,17 @@ class WebServer:
|
|||||||
FOLDER_STATIC_WEB_ASSETS = "www"
|
FOLDER_STATIC_WEB_ASSETS = "www"
|
||||||
MAX_UPLOADS = 16 * 1024 * 1024
|
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._project_dir = project_dir
|
||||||
self._model_manager = model_manager
|
self._model_store = model_store
|
||||||
self._debug = self._model_manager.config().map().get('debug')
|
self._plugin_store = plugin_store
|
||||||
|
self._debug = self._model_store.config().map().get('debug')
|
||||||
self.setup()
|
self.setup()
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
self._app.run(
|
self._app.run(
|
||||||
host=self._model_manager.variable().map().get('bind').as_string(),
|
host=self._model_store.variable().map().get('bind').as_string(),
|
||||||
port=self._model_manager.variable().map().get('port').as_int(),
|
port=self._model_store.variable().map().get('port').as_int(),
|
||||||
debug=self._debug
|
debug=self._debug
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -58,23 +62,29 @@ class WebServer:
|
|||||||
self._app.config['TEMPLATES_AUTO_RELOAD'] = True
|
self._app.config['TEMPLATES_AUTO_RELOAD'] = True
|
||||||
|
|
||||||
def _setup_view_controllers(self) -> None:
|
def _setup_view_controllers(self) -> None:
|
||||||
PlayerController(self._app, self._model_manager)
|
PlayerController(self._app, self._model_store)
|
||||||
SlideshowController(self._app, self._model_manager)
|
SlideshowController(self._app, self._model_store)
|
||||||
SettingsController(self._app, self._model_manager)
|
SettingsController(self._app, self._model_store)
|
||||||
SysinfoController(self._app, self._model_manager)
|
SysinfoController(self._app, self._model_store)
|
||||||
|
|
||||||
if self._model_manager.variable().map().get('fleet_enabled').as_bool():
|
if self._model_store.variable().map().get('fleet_enabled').as_bool():
|
||||||
FleetController(self._app, self._model_manager)
|
FleetController(self._app, self._model_store)
|
||||||
|
|
||||||
def _setup_view_globals(self) -> None:
|
def _setup_view_globals(self) -> None:
|
||||||
@self._app.context_processor
|
@self._app.context_processor
|
||||||
def inject_global_vars():
|
def inject_global_vars():
|
||||||
return dict(
|
globals = dict(
|
||||||
FLEET_ENABLED=self._model_manager.variable().map().get('fleet_enabled').as_bool(),
|
FLEET_ENABLED=self._model_store.variable().map().get('fleet_enabled').as_bool(),
|
||||||
LANG=self._model_manager.variable().map().get('lang').as_string(),
|
LANG=self._model_store.variable().map().get('lang').as_string(),
|
||||||
STATIC_PREFIX="/{}/{}/".format(self.FOLDER_STATIC, self.FOLDER_STATIC_WEB_ASSETS)
|
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:
|
def _setup_view_extensions(self) -> None:
|
||||||
@self._app.template_filter('ctime')
|
@self._app.template_filter('ctime')
|
||||||
def time_ctime(s):
|
def time_ctime(s):
|
||||||
@ -84,3 +94,22 @@ class WebServer:
|
|||||||
@self._app.errorhandler(404)
|
@self._app.errorhandler(404)
|
||||||
def not_found(e):
|
def not_found(e):
|
||||||
return send_from_directory(self._get_template_folder(), 'core/error404.html'), 404
|
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>
|
<h2>{{ l.slideshow_page_title }}</h2>
|
||||||
|
|
||||||
<div class="toolbar-actions">
|
<div class="toolbar-actions">
|
||||||
|
|
||||||
{% if fleet_mode %}
|
{% 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 %}
|
{% endif %}
|
||||||
|
|
||||||
<button class="purple slide-add item-add"><i class="fa fa-plus icon-left"></i>{{ l.slideshow_slide_button_add }}</button>
|
<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