From 53b9acf83daa0d84bcf06cab7f185856eac6e9eb Mon Sep 17 00:00:00 2001 From: jr-k Date: Sat, 18 May 2024 20:07:18 +0200 Subject: [PATCH 1/4] replace pysondb with sqlite --- Dockerfile | 4 +- README.md | 2 +- docs/setup-run-headless.md | 3 + requirements.txt | 2 +- src/Application.py | 4 +- src/controller/SettingsController.py | 2 +- src/manager/DatabaseManager.py | 157 +++++++++++++++++++++++---- src/manager/ScreenManager.py | 69 +++++------- src/manager/SlideManager.py | 91 +++++++--------- src/manager/UserManager.py | 97 ++++++++--------- src/manager/VariableManager.py | 109 ++++++++----------- src/model/entity/Screen.py | 8 +- src/model/entity/Slide.py | 8 +- src/model/entity/User.py | 8 +- src/model/entity/Variable.py | 16 +-- src/service/ModelStore.py | 2 +- src/service/PluginStore.py | 3 + src/utils.py | 14 +++ 18 files changed, 342 insertions(+), 257 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0c1b186..e97ad5d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,11 @@ FROM python:3.9.17-alpine3.17 +RUN apk add --no-cache --virtual .build-deps gcc musl-dev sqlite-dev + WORKDIR /app COPY . . -RUN pip install -r requirements.txt +RUN pip install -r requirements.txt && apk del .build-deps gcc musl-dev sqlite-dev ENTRYPOINT ["python", "/app/obscreen.py"] diff --git a/README.md b/README.md index a4cd5a3..397557d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Use a RaspberryPi (with desktop) to show a full-screen slideshow (Kiosk-mode) - Clear GUI - Fleet view to manage many devices easily - Very few dependencies -- JSON database files +- SQLite database - Plugin system - No stupid pricing plan - No cloud diff --git a/docs/setup-run-headless.md b/docs/setup-run-headless.md index 19da716..5677891 100644 --- a/docs/setup-run-headless.md +++ b/docs/setup-run-headless.md @@ -53,6 +53,9 @@ cd ~ && git clone https://github.com/jr-k/obscreen.git && cd obscreen # Install application dependencies python3 -m venv venv source ./venv/bin/activate + +# 🚨For MacOS users, requirements installation may cause an error but it's ok if only for pysqlite3 package +# you'll need to install brew and execute command `brew install sqlite3` pip install -r requirements.txt # Add some sample data diff --git a/requirements.txt b/requirements.txt index 2d29840..a20ed37 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ flask==2.3.3 -pysondb-v2==2.1.0 python-dotenv cron-descriptor waitress flask-login +pysqlite3 diff --git a/src/Application.py b/src/Application.py index 9536aeb..50dfd38 100644 --- a/src/Application.py +++ b/src/Application.py @@ -18,8 +18,9 @@ class Application: self._model_store = ModelStore() self._template_renderer = TemplateRenderer(project_dir=project_dir, model_store=self._model_store, render_hook=self.render_hook) self._web_server = WebServer(project_dir=project_dir, model_store=self._model_store, template_renderer=self._template_renderer) - self._plugin_store = PluginStore(project_dir=project_dir, model_store=self._model_store, template_renderer=self._template_renderer, web_server=self._web_server) + logging.info("[Obscreen] Starting...") + self._plugin_store = PluginStore(project_dir=project_dir, model_store=self._model_store, template_renderer=self._template_renderer, web_server=self._web_server) signal.signal(signal.SIGINT, self.signal_handler) def start(self) -> None: @@ -27,6 +28,7 @@ class Application: def signal_handler(self, signal, frame) -> None: logging.info("Shutting down...") + self._model_store.database().close() self._stop_event.set() sys.exit(0) diff --git a/src/controller/SettingsController.py b/src/controller/SettingsController.py index 2d0c6ab..be60776 100644 --- a/src/controller/SettingsController.py +++ b/src/controller/SettingsController.py @@ -24,7 +24,7 @@ class SettingsController(ObController): forward = self._post_update(request.form['id']) return forward if forward is not None else redirect(url_for('settings_variable_list')) - def _post_update(self, id: str): + def _post_update(self, id: int): variable = self._model_store.variable().get(id) if variable.refresh_player: diff --git a/src/manager/DatabaseManager.py b/src/manager/DatabaseManager.py index 8ff6dcc..22ca2ed 100644 --- a/src/manager/DatabaseManager.py +++ b/src/manager/DatabaseManager.py @@ -1,33 +1,144 @@ +import os import json -import sys - -from pysondb import PysonDB -from typing import Optional +import sqlite3 +import logging +from sqlite3 import Cursor +from src.utils import wrap_if, is_wrapped_by +from typing import Optional, Dict class DatabaseManager: - DB_DIR = 'data/db' + DB_FILE: str = "data/db/obscreen.db" def __init__(self): - pass + self._conn = None + self._enabled = True + self.init() - def open(self, table_name: str, table_model: list) -> PysonDB: - db_file = "{}/{}.json".format(self.DB_DIR, table_name) - db = PysonDB(db_file) - db = self._update_model(db_file, table_model) - return db + def init(self): + logging.info('Using DB engine {}'.format(self.__class__.__name__)) + self._open() + + def _open(self, flush: bool = False) -> None: + if flush and os.path.isfile(self.DB_FILE): + os.unlink(self.DB_FILE) + + self._conn = sqlite3.connect(self.DB_FILE, check_same_thread=False) + self._conn.row_factory = sqlite3.Row + + def open(self, table_name: str, table_model: list): + self.execute_write_query('''CREATE TABLE IF NOT EXISTS {} ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + {} + )'''.format(table_name, ", ".join(table_model))) + + return self + + def close(self) -> None: + self._conn.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + def get_connection(self): + return self._conn + + def execute_write_query(self, query, params=()) -> None: + logging.debug(query) + cur = None + sanitized_params = [] + + for param in params: + if isinstance(param, bool): + sanitized_params.append(int(param)) + elif isinstance(param, dict) or isinstance(param, list): + sanitized_params.append(json.dumps(param)) + else: + sanitized_params.append(param) - @staticmethod - def _update_model(db_file: str, table_model: list) -> Optional[PysonDB]: try: - with open(db_file, 'r') as file: - db_model = file.read() - db_model = json.loads(db_model) - db_model['keys'] = table_model - with open(db_file, 'w') as file: - file.write(json.dumps(db_model, indent=4)) - return PysonDB(db_file) - except FileNotFoundError: - logging.error("Database file {} not found".format(db_file)) - return None + with self._conn: + cur = self._conn.cursor() + cur.execute(query, tuple(sanitized_params)) + except sqlite3.Error as e: + logging.error("SQL query execution error while writing '{}': {}".format(query, e)) + self._conn.rollback() + finally: + if cur is not None: + cur.close() + + def execute_read_query(self, query, params=()) -> list: + logging.debug(query) + cur = None + + try: + with self._conn: + cur = self._conn.cursor() + cur.execute(query, params) + rows = cur.fetchall() + result = [dict(row) for row in rows] + except sqlite3.Error as e: + logging.error("SQL query execution error while reading '{}': {}".format(query, e)) + result = [] + finally: + if cur is not None: + cur.close() + + return result + + def get_all(self, table_name: str, sort: Optional[str] = None) -> list: + return self.execute_read_query( + query="select * from {} {}".format(table_name, "ORDER BY {} ASC".format(sort) if sort else "") + ) + + def get_by_query(self, table_name: str, query: str = "1=1", sort: Optional[str] = None) -> list: + return self.execute_read_query( + query="select * from {} where {} {}".format( + table_name, + query, + "ORDER BY {} ASC".format(sort) if sort else "" + ) + ) + + def get_one_by_query(self, table_name: str, query: str = "1=1", sort: Optional[str] = None) -> list: + query = "select * from {} where {} {}".format(table_name, query, "ORDER BY {} ASC".format(sort) if sort else "") + lines = self.execute_read_query(query=query) + count = len(lines) + + if count > 1: + raise Error("More than one line returned by query '{}'".format(query)) + + return lines[0] if count == 1 else None + + def update_by_query(self, table_name: str, query: str = "1=1", values: dict = {}) -> list: + return self.execute_write_query( + query="UPDATE {} SET {} where {}".format( + table_name, + " , ".join(["{} = ?".format(k, v) for k, v in values.items()]), + query + ), + params=tuple(v for v in values.values()) + ) + + def update_by_id(self, table_name: str, id: int, values: dict = {}) -> list: + return self.update_by_query(table_name, "id = {}".format(id), values) + + def get_by_id(self, table_name: str, id: int) -> Optional[Dict]: + return self.get_one_by_query(table_name, "id = {}".format(id)) + + def add(self, table_name: str, values: dict) -> None: + self.execute_write_query( + query="INSERT INTO {} ({}) VALUES ({})".format( + table_name, + ", ".join(["{}".format(key) for key in values.keys()]), + ", ".join(["?" for _ in values.keys()]), + ), + params=tuple(v for v in values.values()) + ) + + def delete_by_id(self, table_name: str, id: int) -> None: + self.execute_write_query("DELETE FROM {} WHERE id = ?".format(table_name), params=(id,)) diff --git a/src/manager/ScreenManager.py b/src/manager/ScreenManager.py index 811ff65..4e207e9 100644 --- a/src/manager/ScreenManager.py +++ b/src/manager/ScreenManager.py @@ -1,4 +1,3 @@ -from pysondb.errors import IdDoesNotExistError from typing import Dict, Optional, List, Tuple, Union from src.model.entity.Screen import Screen @@ -12,71 +11,59 @@ class ScreenManager(ModelManager): TABLE_NAME = "fleet" TABLE_MODEL = [ - "name", - "enabled", - "position", - "host", - "port" + "name CHAR(255)", + "enabled INTEGER", + "position INTEGER", + "host CHAR(255)", + "port INTEGER" ] def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager): super().__init__(lang_manager, database_manager, user_manager) self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL) - def hydrate_object(self, raw_screen: dict, id: Optional[str] = None) -> Screen: + def hydrate_object(self, raw_screen: dict, id: Optional[int] = None) -> Screen: if id: raw_screen['id'] = id return Screen(**raw_screen) - def hydrate_dict(self, raw_screens: dict) -> List[Screen]: - return [self.hydrate_object(raw_screen, raw_id) for raw_id, raw_screen in raw_screens.items()] - def hydrate_list(self, raw_screens: list) -> List[Screen]: return [self.hydrate_object(raw_screen) for raw_screen in raw_screens] - def get(self, id: str) -> Optional[Screen]: - try: - return self.hydrate_object(self._db.get_by_id(id), id) - except IdDoesNotExistError: - return None + def get(self, id: int) -> Optional[Screen]: + object = self._db.get_by_id(self.TABLE_NAME, id) + return self.hydrate_object(object, id) if object else None - def get_by(self, query) -> List[Screen]: - return self.hydrate_dict(self._db.get_by_query(query=query)) + def get_by(self, query, sort: Optional[str] = None) -> List[Screen]: + return self.hydrate_list(self._db.get_by_query(self.TABLE_NAME, query=query, sort=sort)) def get_one_by(self, query) -> Optional[Screen]: - screens = self.hydrate_dict(self._db.get_by_query(query=query)) - if len(screens) == 1: - return screens[0] - elif len(screens) > 1: - raise Error("More than one result for query") - return None + object = self._db.get_one_by_query(self.TABLE_NAME, query=query) + + if not object: + return None + + return self.hydrate_object(object) def get_all(self, sort: bool = False) -> List[Screen]: - raw_screens = self._db.get_all() - - if isinstance(raw_screens, dict): - if sort: - return sorted(self.hydrate_dict(raw_screens), key=lambda x: x.position) - return self.hydrate_dict(raw_screens) - - return self.hydrate_list(sorted(raw_screens, key=lambda x: x['position']) if sort else raw_screens) + return self.hydrate_list(self._db.get_all(self.TABLE_NAME, "position" if sort else None)) def get_enabled_screens(self) -> List[Screen]: - return [screen for screen in self.get_all(sort=True) if screen.enabled] + return self.get_by(query="enabled = 1", sort="position") def get_disabled_screens(self) -> List[Screen]: - return [screen for screen in self.get_all(sort=True) if not screen.enabled] + return self.get_by(query="enabled = 0", sort="position") - def update_enabled(self, id: str, enabled: bool) -> None: - self._db.update_by_id(id, {"enabled": enabled, "position": 999}) + def update_enabled(self, id: int, enabled: bool) -> None: + self._db.update_by_id(self.TABLE_NAME, id, {"enabled": enabled, "position": 999}) def update_positions(self, positions: list) -> None: for screen_id, screen_position in positions.items(): - self._db.update_by_id(screen_id, {"position": screen_position}) + self._db.update_by_id(self.TABLE_NAME, screen_id, {"position": screen_position}) - def update_form(self, id: str, name: str, host: str, port: int) -> None: - self._db.update_by_id(id, {"name": name, "host": host, "port": port}) + def update_form(self, id: int, name: str, host: str, port: int) -> None: + self._db.update_by_id(self.TABLE_NAME, id, {"name": name, "host": host, "port": port}) def add_form(self, screen: Union[Screen, Dict]) -> None: form = screen @@ -85,10 +72,10 @@ class ScreenManager(ModelManager): form = screen.to_dict() del form['id'] - self._db.add(form) + self._db.add(self.TABLE_NAME, form) - def delete(self, id: str) -> None: - self._db.delete_by_id(id) + def delete(self, id: int) -> None: + self._db.delete_by_id(self.TABLE_NAME, id) def to_dict(self, screens: List[Screen]) -> List[Dict]: return [screen.to_dict() for screen in screens] diff --git a/src/manager/SlideManager.py b/src/manager/SlideManager.py index 82c022c..4c808be 100644 --- a/src/manager/SlideManager.py +++ b/src/manager/SlideManager.py @@ -1,7 +1,6 @@ import os from typing import Dict, Optional, List, Tuple, Union -from pysondb.errors import IdDoesNotExistError from src.model.entity.Slide import Slide from src.model.enum.SlideType import SlideType @@ -16,80 +15,68 @@ class SlideManager(ModelManager): TABLE_NAME = "slideshow" TABLE_MODEL = [ - "name", - "type", - "enabled", - "duration", - "position", - "location", - "cron_schedule", - "cron_schedule_end", - "created_by", - "updated_by", - "created_at", - "updated_at" + "name CHAR(255)", + "type CHAR(30)", + "enabled INTEGER", + "duration INTEGER", + "position INTEGER", + "location TEXT", + "cron_schedule CHAR(255)", + "cron_schedule_end CHAR(255)", + "created_by CHAR(255)", + "updated_by CHAR(255)", + "created_at INTEGER", + "updated_at INTEGER" ] def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager): super().__init__(lang_manager, database_manager, user_manager) self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL) - def hydrate_object(self, raw_slide: dict, id: str = None) -> Slide: + def hydrate_object(self, raw_slide: dict, id: int = None) -> Slide: if id: raw_slide['id'] = id [raw_slide, user_tracker_edits] = self.user_manager.initialize_user_trackers(raw_slide) if len(user_tracker_edits) > 0: - self._db.update_by_id(raw_slide['id'], user_tracker_edits) + self._db.update_by_id(self.TABLE_NAME, raw_slide['id'], user_tracker_edits) return Slide(**raw_slide) - def hydrate_dict(self, raw_slides: dict) -> List[Slide]: - return [self.hydrate_object(raw_slide, raw_id) for raw_id, raw_slide in raw_slides.items()] - def hydrate_list(self, raw_slides: list) -> List[Slide]: return [self.hydrate_object(raw_slide) for raw_slide in raw_slides] - def get(self, id: str) -> Optional[Slide]: - try: - return self.hydrate_object(self._db.get_by_id(id), id) - except IdDoesNotExistError: - return None + def get(self, id: int) -> Optional[Slide]: + object = self._db.get_by_id(self.TABLE_NAME, id) + return self.hydrate_object(object, id) if object else None - def get_by(self, query) -> List[Slide]: - return self.hydrate_dict(self._db.get_by_query(query=query)) + def get_by(self, query, sort: Optional[str] = None) -> List[Slide]: + return self.hydrate_list(self._db.get_by_query(self.TABLE_NAME, query=query, sort=sort)) def get_one_by(self, query) -> Optional[Slide]: - slides = self.hydrate_dict(self._db.get_by_query(query=query)) - if len(slides) == 1: - return slides[0] - elif len(slides) > 1: - raise Error("More than one result for query") - return None + object = self._db.get_one_by_query(self.TABLE_NAME, query=query) + + if not object: + return None + + return self.hydrate_object(object) def get_all(self, sort: bool = False) -> List[Slide]: - raw_slides = self._db.get_all() + return self.hydrate_list(self._db.get_all(self.TABLE_NAME, sort="position" if sort else None)) - if isinstance(raw_slides, dict): - if sort: - return sorted(self.hydrate_dict(raw_slides), key=lambda x: x.position) - return self.hydrate_dict(raw_slides) - - return self.hydrate_list(sorted(raw_slides, key=lambda x: x['position']) if sort else raw_slides) - - def forget_user(self, user_id: str): - slides = self.hydrate_dict(self._db.get_by_query(query=lambda s: s['created_by'] == user_id or s['updated_by'] == user_id)) + def forget_user(self, user_id: int): + slides = self.get_by("created_by = '{}' or updated_by = '{}'".format(user_id, user_id)) edits_slides = self.user_manager.forget_user(slides, user_id) for slide_id, edits in edits_slides.items(): - self._db.update_by_id(slide_id, edits) + self._db.update_by_id(self.TABLE_NAME, slide_id, edits) def get_enabled_slides(self) -> List[Slide]: - return [slide for slide in self.get_all(sort=True) if slide.enabled] + return self.get_by(query="enabled = 1", sort="position") def get_disabled_slides(self) -> List[Slide]: - return [slide for slide in self.get_all(sort=True) if not slide.enabled] + return self.get_by(query="enabled = 0", sort="position") def pre_add(self, slide: Dict) -> Dict: self.user_manager.track_user_on_create(slide) @@ -115,15 +102,15 @@ class SlideManager(ModelManager): def post_delete(self, slide_id: str) -> str: return slide_id - def update_enabled(self, id: str, enabled: bool) -> None: - self._db.update_by_id(id, self.pre_update({"enabled": enabled, "position": 999})) + def update_enabled(self, id: int, enabled: bool) -> None: + self._db.update_by_id(self.TABLE_NAME, id, self.pre_update({"enabled": enabled, "position": 999})) self.post_update(id) def update_positions(self, positions: list) -> None: for slide_id, slide_position in positions.items(): - self._db.update_by_id(slide_id, {"position": slide_position}) + self._db.update_by_id(self.TABLE_NAME, slide_id, {"position": slide_position}) - def update_form(self, id: str, name: str, duration: int, cron_schedule: Optional[str] = '', cron_schedule_end: Optional[str] = '', location: Optional[str] = None) -> None: + def update_form(self, id: int, name: str, duration: int, cron_schedule: Optional[str] = '', cron_schedule_end: Optional[str] = '', location: Optional[str] = None) -> None: slide = self.get(id) if not slide: @@ -142,7 +129,7 @@ class SlideManager(ModelManager): if slide.type == SlideType.YOUTUBE: form['location'] = get_yt_video_id(form['location']) - self._db.update_by_id(id, self.pre_update(form)) + self._db.update_by_id(self.TABLE_NAME, id, self.pre_update(form)) self.post_update(id) def add_form(self, slide: Union[Slide, Dict]) -> None: @@ -155,10 +142,10 @@ class SlideManager(ModelManager): if form['type'] == SlideType.YOUTUBE.value: form['location'] = get_yt_video_id(form['location']) - self._db.add(self.pre_add(form)) + self._db.add(self.TABLE_NAME, self.pre_add(form)) self.post_add(slide.id) - def delete(self, id: str) -> None: + def delete(self, id: int) -> None: slide = self.get(id) if slide: @@ -169,7 +156,7 @@ class SlideManager(ModelManager): pass self.pre_delete(id) - self._db.delete_by_id(id) + self._db.delete_by_id(self.TABLE_NAME, id) self.post_delete(id) def to_dict(self, slides: List[Slide]) -> List[Dict]: diff --git a/src/manager/UserManager.py b/src/manager/UserManager.py index f384649..eb80151 100644 --- a/src/manager/UserManager.py +++ b/src/manager/UserManager.py @@ -1,6 +1,5 @@ import hashlib import time -from pysondb.errors import IdDoesNotExistError from typing import Dict, Optional, List, Tuple, Union from flask_login import current_user @@ -14,9 +13,9 @@ class UserManager: TABLE_NAME = "user" TABLE_MODEL = [ - "username", - "password", - "enabled" + "username CHAR(255)", + "password CHAR(255)", + "enabled INTEGER" ] def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, on_user_delete): @@ -44,37 +43,32 @@ class UserManager: return user_map - def hydrate_object(self, raw_user: dict, id: Optional[str] = None) -> User: + def hydrate_object(self, raw_user: dict, id: Optional[int] = None) -> User: if id: raw_user['id'] = id return User(**raw_user) - def hydrate_dict(self, raw_users: dict) -> List[User]: - return [self.hydrate_object(raw_user, raw_id) for raw_id, raw_user in raw_users.items()] - def hydrate_list(self, raw_users: list) -> List[User]: return [self.hydrate_object(raw_user) for raw_user in raw_users] - def get(self, id: str) -> Optional[User]: - try: - return self.hydrate_object(self._db.get_by_id(id), id) - except IdDoesNotExistError: - return None + def get(self, id: int) -> Optional[User]: + object = self._db.get_by_id(self.TABLE_NAME, id) + return self.hydrate_object(object, id) if object else None - def get_by(self, query) -> List[User]: - return self.hydrate_dict(self._db.get_by_query(query=query)) + def get_by(self, query, sort: Optional[str] = None) -> List[User]: + return self.hydrate_list(self._db.get_by_query(self.TABLE_NAME, query=query, sort=sort)) def get_one_by(self, query) -> Optional[User]: - users = self.hydrate_dict(self._db.get_by_query(query=query)) - if len(users) == 1: - return users[0] - elif len(users) > 1: - raise Error("More than one result for query") - return None + object = self._db.get_one_by_query(self.TABLE_NAME, query=query) + + if not object: + return None + + return self.hydrate_object(object) def get_one_by_username(self, username: str, enabled: bool = None) -> Optional[User]: - return self.get_one_by(query=lambda v: v['username'] == username and (enabled is None or v['enabled'] == enabled)) + return self.get_one_by("username = '{}' and (enabled is null or enabled = {})".format(username, int(enabled))) def count_all_enabled(self): return len(self.get_enabled_users()) @@ -85,12 +79,16 @@ class UserManager: def track_user_updated(self, id_or_entity: Optional[str]) -> User: return self.track_user_action(id_or_entity, 'updated_by') - def track_user_action(self, id_or_entity: Optional[str], attribute: Optional[str] = 'created_by') -> User: - if not isinstance(id_or_entity, str): + def track_user_action(self, id_or_entity: Optional[int], attribute: Optional[str] = 'created_by') -> User: + if not isinstance(id_or_entity, int): id_or_entity = getattr(id_or_entity, attribute) - id_or_entity = str(id_or_entity) - user_map = self.map() + try: + id_or_entity = int(id_or_entity) + except ValueError: + return User(username=id_or_entity, enabled=False) + + user_map = self.prepare_map() if id_or_entity in user_map: return user_map[id_or_entity] @@ -101,20 +99,13 @@ class UserManager: return User(username=self._lang_manager.translate('anonymous'), enabled=False) def get_all(self, sort: bool = False) -> List[User]: - raw_users = self._db.get_all() - - if isinstance(raw_users, dict): - if sort: - return sorted(self.hydrate_dict(raw_users), key=lambda x: x.username) - return self.hydrate_dict(raw_users) - - return self.hydrate_list(sorted(raw_users, key=lambda x: x['username']) if sort else raw_users) + return self.hydrate_list(self._db.get_all(self.TABLE_NAME, "username" if sort else None)) def get_enabled_users(self) -> List[User]: - return [user for user in self.get_all(sort=True) if user.enabled] + return self.get_by(query="enabled = 1", sort="username") def get_disabled_users(self) -> List[User]: - return [user for user in self.get_all(sort=True) if not user.enabled] + return self.get_by(query="enabled = 0", sort="username") def pre_add(self, user: Dict) -> Dict: return user @@ -122,33 +113,33 @@ class UserManager: def pre_update(self, user: Dict) -> Dict: return user - def pre_delete(self, user_id: str) -> str: + def pre_delete(self, user_id: int) -> int: self._on_user_delete(user_id) return user_id - def post_add(self, user_id: str) -> str: + def post_add(self, user_id: int) -> int: self.reload() return user_id - def post_update(self, user_id: str) -> str: + def post_update(self, user_id: int) -> int: self.reload() return user_id - def post_delete(self, user_id: str) -> str: + def post_delete(self, user_id: int) -> int: self.reload() return user_id - def update_enabled(self, id: str, enabled: bool) -> None: - self._db.update_by_id(id, self.pre_update({"enabled": enabled})) + def update_enabled(self, id: int, enabled: bool) -> None: + self._db.update_by_id(self.TABLE_NAME, id, self.pre_update({"enabled": enabled})) self.post_update(id) - def update_form(self, id: str, username: str, password: Optional[str]) -> None: + def update_form(self, id: int, username: str, password: Optional[str]) -> None: form = {"username": username} if password is not None and password: form['password'] = self.encode_password(password) - self._db.update_by_id(id, self.pre_update(form)) + self._db.update_by_id(self.TABLE_NAME, id, self.pre_update(form)) self.post_update(id) def add_form(self, user: Union[User, Dict]) -> None: @@ -160,12 +151,12 @@ class UserManager: form['password'] = self.encode_password(form['password']) - self._db.add(self.pre_add(form)) + self._db.add(self.TABLE_NAME, self.pre_add(form)) self.post_add(user.id) - def delete(self, id: str) -> None: + def delete(self, id: int) -> None: self.pre_delete(id) - self._db.delete_by_id(id) + self._db.delete_by_id(self.TABLE_NAME, id) self.post_delete(id) def to_dict(self, users: List[User]) -> List[Dict]: @@ -215,18 +206,18 @@ class UserManager: return None - def forget_user(self, objects: List, user_id: str) -> Dict: - user_map = self.map() - user_id = str(user_id) + def forget_user(self, objects: List, user_id: int) -> Dict: + user_map = self.prepare_map() + user_id = int(user_id) edits = {} for object in objects: - edits = {object.id: {}} + edits[object.id] = {} - if str(object.created_by) == user_id and user_id in user_map: + if int(object.created_by) == user_id and user_id in user_map: edits[object.id]['created_by'] = user_map[user_id].username - if str(object.updated_by) == user_id and user_id in user_map: + if int(object.updated_by) == user_id and user_id in user_map: edits[object.id]['updated_by'] = user_map[user_id].username return edits diff --git a/src/manager/VariableManager.py b/src/manager/VariableManager.py index 9404cfc..d1888ad 100644 --- a/src/manager/VariableManager.py +++ b/src/manager/VariableManager.py @@ -1,6 +1,6 @@ +import json import time from typing import Dict, Optional, List, Tuple, Union -from pysondb.errors import IdDoesNotExistError from src.manager.DatabaseManager import DatabaseManager from src.manager.LangManager import LangManager @@ -24,17 +24,17 @@ class VariableManager(ModelManager): TABLE_NAME = "settings" TABLE_MODEL = [ - "description", - "description_edition", - "editable", - "name", - "section", - "plugin", - "selectables", - "type", - "unit", - "refresh_player", - "value" + "description TEXT", + "description_edition TEXT", + "editable INTEGER", + "name CHAR(255)", + "section CHAR(255)", + "plugin CHAR(255)", + "selectables TEXT", + "type CHAR(255)", + "unit CHAR(255)", + "refresh_player INTEGER", + "value TEXT" ] def __init__(self, lang_manager: LangManager, database_manager: DatabaseManager, user_manager: UserManager): @@ -75,25 +75,25 @@ class VariableManager(ModelManager): same_selectables_label = get_keys(default_var, 'selectables', 'label') == get_keys(variable, 'selectables', 'label') if variable.description != default_var['description']: - self._db.update_by_id(variable.id, {"description": default_var['description']}) + self._db.update_by_id(self.TABLE_NAME, variable.id, {"description": default_var['description']}) if variable.description_edition != default_var['description_edition']: - self._db.update_by_id(variable.id, {"description_edition": default_var['description_edition']}) + self._db.update_by_id(self.TABLE_NAME, variable.id, {"description_edition": default_var['description_edition']}) if variable.unit != default_var['unit']: - self._db.update_by_id(variable.id, {"unit": default_var['unit']}) + self._db.update_by_id(self.TABLE_NAME, variable.id, {"unit": default_var['unit']}) if variable.section != default_var['section']: - self._db.update_by_id(variable.id, {"section": default_var['section']}) + self._db.update_by_id(self.TABLE_NAME, variable.id, {"section": default_var['section']}) if variable.refresh_player != default_var['refresh_player']: - self._db.update_by_id(variable.id, {"refresh_player": default_var['refresh_player']}) + self._db.update_by_id(self.TABLE_NAME, variable.id, {"refresh_player": default_var['refresh_player']}) if not same_selectables_keys or not same_selectables_label: - self._db.update_by_id(variable.id, {"selectables": default_var['selectables']}) + self._db.update_by_id(self.TABLE_NAME, variable.id, {"selectables": default_var['selectables']}) if variable.name == 'last_restart': - self._db.update_by_id(variable.id, {"value": time.time()}) + self._db.update_by_id(self.TABLE_NAME, variable.id, {"value": time.time()}) return variable @@ -144,75 +144,60 @@ class VariableManager(ModelManager): return var_map - def hydrate_object(self, raw_variable: dict, id: Optional[str] = None) -> Variable: + def hydrate_object(self, raw_variable: dict, id: Optional[int] = None) -> Variable: if id: raw_variable['id'] = id if 'selectables' in raw_variable and raw_variable['selectables']: - raw_variable['selectables'] = [Selectable(**selectable) for selectable in raw_variable['selectables']] + raw_variable['selectables'] = [Selectable(**selectable) for selectable in json.loads(raw_variable['selectables'])] return Variable(**raw_variable) - def hydrate_dict(self, raw_variables: dict) -> List[Variable]: - return [self.hydrate_object(raw_variable, raw_id) for raw_id, raw_variable in raw_variables.items()] - def hydrate_list(self, raw_variables: list) -> List[Variable]: return [self.hydrate_object(raw_variable) for raw_variable in raw_variables] - def get(self, id: str) -> Optional[Variable]: - try: - return self.hydrate_object(self._db.get_by_id(id), id) - except IdDoesNotExistError: - return None + def get(self, id: int) -> Optional[Variable]: + object = self._db.get_by_id(self.TABLE_NAME, id) + return self.hydrate_object(object, id) if object else None - def get_by(self, query) -> List[Variable]: - return self.hydrate_dict(self._db.get_by_query(query=query)) + def get_by(self, query, sort: Optional[str] = None) -> List[Variable]: + return self.hydrate_list(self._db.get_by_query(self.TABLE_NAME, query=query, sort=sort)) 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))) + return self.get_by(query="name like '{}%'".format(prefix)) def get_by_plugin(self, plugin: str) -> List[Variable]: - return self.hydrate_dict(self._db.get_by_query(query=lambda v: v['plugin'] == plugin)) + return self.get_by(query="plugin = '{}'".format(plugin)) 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("name = '{}'".format(name)) def get_one_by(self, query) -> Optional[Variable]: - object = self._db.get_by_query(query=query) - variables = self.hydrate_dict(object) - if len(variables) == 1: - return variables[0] - elif len(variables) > 1: - raise Error("More than one result for query") - return None + object = self._db.get_one_by_query(self.TABLE_NAME, query=query) + + if not object: + return None + + return self.hydrate_object(object) def get_all(self) -> List[Variable]: - raw_variables = self._db.get_all() - - if isinstance(raw_variables, dict): - return self.hydrate_dict(raw_variables) - - return self.hydrate_list(raw_variables) + return self.hydrate_list(self._db.get_all(self.TABLE_NAME)) def get_editable_variables(self, plugin: bool = True, sort: Optional[str] = None) -> List[Variable]: - query = lambda v: (not plugin and not isinstance(v['plugin'], str)) or (plugin and isinstance(v['plugin'], str)) - variables = [variable for variable in self.get_by(query=query) if variable.editable] - if sort is not None and sort: - return sorted(variables, key=lambda x: getattr(x, sort)) - return variables + query = "plugin is null and editable = 1" if not plugin else "plugin is not null and length(plugin) > 0 and editable = 1" + return self.get_by(query=query, sort=sort) def get_readonly_variables(self) -> List[Variable]: - return [variable for variable in self.get_all() if not variable.editable] + return self.get_by(query="editable = 0", sort="name") - def update_form(self, id: str, value: Union[int, bool, str]) -> None: - var_dict = self._db.update_by_id(id, {"value": value}) - var = self.hydrate_object(var_dict, id) + def update_form(self, id: int, value: Union[int, bool, str]) -> None: + self._db.update_by_id(self.TABLE_NAME, id, {"value": value}) + var = self.get_one_by("id = {}".format(id)) self._var_map[var.name] = var def update_by_name(self, name: str, value) -> Optional[Variable]: - [var_id] = self._db.update_by_query(query=lambda v: v['name'] == name, new_data={"value": value}) - var_dict = self._db.get_by_id(var_id) - var = self.hydrate_object(var_dict, id) + self._db.update_by_query(self.TABLE_NAME, query="name = '{}'".format(name), values={"value": value}) + var = self.get_one_by_name(name) self._var_map[name] = var def add_form(self, variable: Union[Variable, Dict]) -> None: @@ -222,10 +207,10 @@ class VariableManager(ModelManager): form = variable.to_dict() del form['id'] - self._db.add(form) + self._db.add(self.TABLE_NAME, form) - def delete(self, id: str) -> None: - self._db.delete_by_id(id) + def delete(self, id: int) -> None: + self._db.delete_by_id(self.TABLE_NAME, id) def to_dict(self, variables: List[Variable]) -> List[Dict]: return [variable.to_dict() for variable in variables] diff --git a/src/model/entity/Screen.py b/src/model/entity/Screen.py index ff3c122..74d5b2d 100644 --- a/src/model/entity/Screen.py +++ b/src/model/entity/Screen.py @@ -5,7 +5,7 @@ from typing import Optional, Union class Screen: - def __init__(self, host: str = '', port: int = 5000, enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[str] = None): + def __init__(self, host: str = '', port: int = 5000, enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[int] = None): self._id = id if id else None self._host = host self._port = port @@ -14,7 +14,7 @@ class Screen: self._position = position @property - def id(self) -> Union[int, str]: + def id(self) -> Optional[int]: return self._id @property @@ -35,11 +35,11 @@ class Screen: @property def enabled(self) -> bool: - return self._enabled + return bool(self._enabled) @enabled.setter def enabled(self, value: bool): - self._enabled = value + self._enabled = bool(value) @property def name(self) -> str: diff --git a/src/model/entity/Slide.py b/src/model/entity/Slide.py index a7f508f..fed0d8b 100644 --- a/src/model/entity/Slide.py +++ b/src/model/entity/Slide.py @@ -8,7 +8,7 @@ from src.utils import str_to_enum class Slide: - def __init__(self, location: str = '', duration: int = 3, type: Union[SlideType, str] = SlideType.URL, enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[str] = None, cron_schedule: Optional[str] = None, cron_schedule_end: Optional[str] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None): + def __init__(self, location: str = '', duration: int = 3, type: Union[SlideType, str] = SlideType.URL, enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[int] = None, cron_schedule: Optional[str] = None, cron_schedule_end: Optional[str] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None): self._id = id if id else None self._location = location self._duration = duration @@ -24,7 +24,7 @@ class Slide: self._updated_at = int(updated_at if updated_at else time.time()) @property - def id(self) -> Optional[str]: + def id(self) -> Optional[int]: return self._id @property @@ -101,11 +101,11 @@ class Slide: @property def enabled(self) -> bool: - return self._enabled + return bool(self._enabled) @enabled.setter def enabled(self, value: bool): - self._enabled = value + self._enabled = bool(value) @property def name(self) -> str: diff --git a/src/model/entity/User.py b/src/model/entity/User.py index 6f1d599..5e515fa 100644 --- a/src/model/entity/User.py +++ b/src/model/entity/User.py @@ -5,14 +5,14 @@ from typing import Optional, Union class User: - def __init__(self, username: str = '', password: str = '', enabled: bool = True, id: Optional[str] = None): + def __init__(self, username: str = '', password: str = '', enabled: bool = True, id: Optional[int] = None): self._id = id if id else None self._username = username self._password = password self._enabled = enabled @property - def id(self) -> Union[int, str]: + def id(self) -> Optional[int]: return self._id @property @@ -33,11 +33,11 @@ class User: @property def enabled(self) -> bool: - return self._enabled + return bool(self._enabled) @enabled.setter def enabled(self, value: bool): - self._enabled = value + self._enabled = bool(value) def __str__(self) -> str: return f"User(" \ diff --git a/src/model/entity/Variable.py b/src/model/entity/Variable.py index 700e49d..d91c60c 100644 --- a/src/model/entity/Variable.py +++ b/src/model/entity/Variable.py @@ -11,7 +11,7 @@ from src.utils import str_to_enum class Variable: def __init__(self, name: str = '', section: str = '', description: str = '', description_edition: str = '', type: Union[VariableType, str] = VariableType.STRING, - value: Union[int, bool, str] = '', editable: bool = True, id: Optional[str] = None, + value: Union[int, bool, str] = '', editable: bool = True, id: Optional[int] = None, plugin: Optional[str] = None, selectables: Optional[List[Selectable]] = None, unit: Optional[VariableUnit] = None, refresh_player: bool = False): self._id = id if id else None @@ -32,7 +32,7 @@ class Variable: self._unit = None @property - def id(self) -> Union[int, str]: + def id(self) -> Optional[int]: return self._id @property @@ -96,19 +96,19 @@ class Variable: @property def editable(self) -> bool: - return self._editable + return bool(self._editable) @editable.setter def editable(self, value: bool): - self._editable = value + self._editable = bool(value) @property def refresh_player(self) -> bool: - return self._refresh_player + return bool(self._refresh_player) @refresh_player.setter def refresh_player(self, value: bool): - self._refresh_player = value + self._refresh_player = bool(value) @property def value(self) -> Union[int, bool, str]: @@ -171,10 +171,10 @@ class Variable: return str(self._value) def as_int(self) -> int: - return int(self._value) + return int(float(self._value)) def as_ctime(self) -> int: - return time.ctime(self._value) + return time.ctime(int(float(self._value))) def display(self) -> Union[int, bool, str]: value = self.eval() diff --git a/src/service/ModelStore.py b/src/service/ModelStore.py index 0a31d3c..1aa530d 100644 --- a/src/service/ModelStore.py +++ b/src/service/ModelStore.py @@ -53,5 +53,5 @@ class ModelStore: def user(self) -> UserManager: return self._user_manager - def on_user_delete(self, user_id: str): + def on_user_delete(self, user_id: int) -> None: self._slide_manager.forget_user(user_id) diff --git a/src/service/PluginStore.py b/src/service/PluginStore.py index 1009ec2..4ff4c53 100644 --- a/src/service/PluginStore.py +++ b/src/service/PluginStore.py @@ -156,6 +156,9 @@ class PluginStore: 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)) + if var.as_bool: + logging.info("[Plugin] {} enabled".format(plugin.use_title())) + return var.as_bool() if var else False diff --git a/src/utils.py b/src/utils.py index 5998c9c..497fedc 100644 --- a/src/utils.py +++ b/src/utils.py @@ -13,6 +13,20 @@ from cron_descriptor.Exception import FormatException, WrongArgumentException, M CAMEL_CASE_TO_SNAKE_CASE_PATTERN = re.compile(r'(? bool: + return s[0] == head and s[-1] == tail if len(s) > 0 else None + + +def wrap_if(s: str, condition: bool = True, quote_type: str = "'") -> str: + if not condition or is_wrapped_by(s, quote_type, quote_type): + return s + return "{}{}{}".format( + quote_type, + s, + quote_type + ) + + def am_i_in_docker(): docker_env = os.path.exists('/.dockerenv') docker_cgroup = False From b0cddc81feec504b7cfba240224ed7e74e54277b Mon Sep 17 00:00:00 2001 From: jr-k Date: Sat, 18 May 2024 20:09:04 +0200 Subject: [PATCH 2/4] remove any reference to old json db file --- .dockerignore | 1 - .gitignore | 1 - data/db/slideshow.json.dist | 37 ------------------------------------- docs/setup-run-headless.md | 3 --- docs/setup-run-on-rpi.md | 3 --- 5 files changed, 45 deletions(-) delete mode 100755 data/db/slideshow.json.dist diff --git a/.dockerignore b/.dockerignore index b313384..ad7e611 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,7 +6,6 @@ out/ data/uploads/* !data/uploads/sample.jpg data/db/* -!data/db/slideshow.json.dist /plugins/user/* !/plugins/user/.gitkeep *.lock diff --git a/.gitignore b/.gitignore index 724b2c5..a14e5b1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ out/ data/uploads/* !data/uploads/sample.jpg data/db/* -!data/db/slideshow.json.dist /plugins/user/* !/plugins/user/.gitkeep *.lock diff --git a/data/db/slideshow.json.dist b/data/db/slideshow.json.dist deleted file mode 100755 index 43fba9f..0000000 --- a/data/db/slideshow.json.dist +++ /dev/null @@ -1,37 +0,0 @@ -{ - "version": 2, - "keys": [ - "duration", - "enabled", - "location", - "name", - "position", - "type", - "cron_schedule", - "cron_schedule_end", - "created_by", - "updated_by", - "created_at", - "updated_at" - ], - "data": { - "0": { - "location": "data/uploads/sample.jpg", - "duration": 10, - "type": "picture", - "enabled": true, - "name": "Picture Sample", - "position": 0, - "cron_schedule": null - }, - "1": { - "location": "https://unix.org", - "duration": 20, - "type": "url", - "enabled": true, - "name": "URL Sample", - "position": 1, - "cron_schedule": null - } - } -} \ No newline at end of file diff --git a/docs/setup-run-headless.md b/docs/setup-run-headless.md index 5677891..cf01716 100644 --- a/docs/setup-run-headless.md +++ b/docs/setup-run-headless.md @@ -58,9 +58,6 @@ source ./venv/bin/activate # you'll need to install brew and execute command `brew install sqlite3` pip install -r requirements.txt -# Add some sample data -cp data/db/slideshow.json.dist data/db/slideshow.json - # Customize server default values cp .env.dist .env ``` diff --git a/docs/setup-run-on-rpi.md b/docs/setup-run-on-rpi.md index eb49913..c99645c 100644 --- a/docs/setup-run-on-rpi.md +++ b/docs/setup-run-on-rpi.md @@ -72,9 +72,6 @@ python3 -m venv venv source ./venv/bin/activate pip install -r requirements.txt -# Add some sample data -cp data/db/slideshow.json.dist data/db/slideshow.json - # Customize server default values cp .env.dist .env ``` From eb8dd4eeee966b893db37a3abac4386298b4f047 Mon Sep 17 00:00:00 2001 From: jr-k Date: Sat, 18 May 2024 20:42:39 +0200 Subject: [PATCH 3/4] better restart management --- data/www/js/restart.js | 2 +- src/controller/AuthController.py | 10 ++++++ src/controller/SettingsController.py | 5 ++- src/controller/SysinfoController.py | 48 ++++++++++++++++++---------- src/service/TemplateRenderer.py | 1 + views/base.jinja.html | 1 + 6 files changed, 49 insertions(+), 18 deletions(-) diff --git a/data/www/js/restart.js b/data/www/js/restart.js index 1e07999..9f4ed34 100644 --- a/data/www/js/restart.js +++ b/data/www/js/restart.js @@ -9,7 +9,7 @@ jQuery(document).ready(function ($) { if (confirm(l.js_sysinfo_restart_confirmation)) { $('body').html(l.js_sysinfo_restart_loading).css({margin:200}); $.ajax({ - url: '/sysinfo/restart', + url: '/sysinfo/restart?secret_key='+secret_key, headers: {'Content-Type': 'application/json'}, data: '', method: 'POST', diff --git a/src/controller/AuthController.py b/src/controller/AuthController.py index 0e1a023..153eee0 100644 --- a/src/controller/AuthController.py +++ b/src/controller/AuthController.py @@ -24,6 +24,9 @@ class AuthController(ObController): if current_user.is_authenticated: return redirect(url_for('slideshow_slide_list')) + if not self._model_store.variable().map().get('auth_enabled').as_bool(): + return redirect(url_for('slideshow_slide_list')) + if len(request.form): user = self._model_store.user().get_one_by_username(request.form['username'], enabled=True) if user: @@ -42,6 +45,13 @@ class AuthController(ObController): def logout(self): logout_user() + + if request.args.get('restart'): + return redirect(url_for( + 'sysinfo_restart', + secret_key=self._model_store.config().map().get('secret_key') + )) + return redirect(url_for('login')) def auth_user_list(self): diff --git a/src/controller/SettingsController.py b/src/controller/SettingsController.py index be60776..c837e7a 100644 --- a/src/controller/SettingsController.py +++ b/src/controller/SettingsController.py @@ -39,7 +39,10 @@ class SettingsController(ObController): if variable.name == 'auth_enabled': self.reload_web_server() if variable.as_bool(): - return redirect(url_for('logout')) + return redirect(url_for( + 'logout', + restart=1 + )) if variable.name == 'lang': self._model_store.lang().set_lang(variable.value) diff --git a/src/controller/SysinfoController.py b/src/controller/SysinfoController.py index c937ea9..1d5fb5a 100644 --- a/src/controller/SysinfoController.py +++ b/src/controller/SysinfoController.py @@ -2,21 +2,23 @@ import os import sys import platform import subprocess +import threading +import time -from flask import Flask, render_template, jsonify +from flask import Flask, render_template, jsonify, request, url_for, redirect from src.manager.VariableManager import VariableManager from src.manager.ConfigManager import ConfigManager from src.service.ModelStore import ModelStore from src.interface.ObController import ObController -from src.utils import get_ip_address +from src.utils import get_ip_address, am_i_in_docker class SysinfoController(ObController): def register(self): self._app.add_url_rule('/sysinfo', 'sysinfo_attribute_list', self._auth(self.sysinfo), methods=['GET']) - self._app.add_url_rule('/sysinfo/restart', 'sysinfo_restart', self._auth(self.sysinfo_restart), methods=['POST']) + self._app.add_url_rule('/sysinfo/restart', 'sysinfo_restart', self.sysinfo_restart, methods=['GET', 'POST']) self._app.add_url_rule('/sysinfo/restart/needed', 'sysinfo_restart_needed', self._auth(self.sysinfo_restart_needed), methods=['GET']) def sysinfo(self): @@ -29,20 +31,13 @@ class SysinfoController(ObController): ) def sysinfo_restart(self): - if platform.system().lower() == 'darwin': - if self._model_store.config().map().get('debug'): - python = sys.executable - os.execl(python, python, *sys.argv) - else: - try: - subprocess.run(["sudo", "systemctl", "restart", 'obscreen'], check=True, timeout=10, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - pass - except subprocess.TimeoutExpired: - pass - except subprocess.CalledProcessError: - pass + secret = self._model_store.config().map().get('secret_key') + challenge = request.args.get('secret_key') + thread = threading.Thread(target=self.restart, args=(secret, challenge)) + thread.daemon = True + thread.start() - return jsonify({'status': 'ok'}) + return redirect(url_for('manage')) def sysinfo_restart_needed(self): var_last_slide_update = self._model_store.variable().get_one_by_name('last_slide_update') @@ -53,3 +48,24 @@ class SysinfoController(ObController): return jsonify({'status': True}) + def restart(self, secret: str, challenge: str) -> None: + time.sleep(1) + + if secret != challenge: + return jsonify({'status': 'error'}) + + if platform.system().lower() == 'darwin': + if self._model_store.config().map().get('debug'): + python = sys.executable + os.execl(python, python, *sys.argv) + elif am_i_in_docker: + python = sys.executable + os.execl(python, python, *sys.argv) + else: + try: + subprocess.run(["sudo", "systemctl", "restart", 'obscreen-manager'], check=True, timeout=10, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + pass + except subprocess.TimeoutExpired: + pass + except subprocess.CalledProcessError: + pass diff --git a/src/service/TemplateRenderer.py b/src/service/TemplateRenderer.py index c680099..5c3bb1f 100644 --- a/src/service/TemplateRenderer.py +++ b/src/service/TemplateRenderer.py @@ -25,6 +25,7 @@ class TemplateRenderer: def get_view_globals(self) -> dict: globals = dict( STATIC_PREFIX="/{}/{}/".format(WebDirConstant.FOLDER_STATIC, WebDirConstant.FOLDER_STATIC_WEB_ASSETS), + SECRET_KEY=self._model_store.config().map().get('secret_key'), FLEET_ENABLED=self._model_store.variable().map().get('fleet_enabled').as_bool(), AUTH_ENABLED=self._model_store.variable().map().get('auth_enabled').as_bool(), track_created=self._model_store.user().track_user_created, diff --git a/views/base.jinja.html b/views/base.jinja.html index d97b3bb..87dfedf 100755 --- a/views/base.jinja.html +++ b/views/base.jinja.html @@ -113,6 +113,7 @@ {% endblock %}