done
This commit is contained in:
parent
70efdf877e
commit
bdb4f1d7bd
@ -5,11 +5,7 @@ SECRET_KEY=ANY_SECRET_KEY_HERE
|
||||
# Application Server
|
||||
PORT=5000
|
||||
BIND=0.0.0.0
|
||||
|
||||
# HTTP External Storage Server
|
||||
PORT_HTTP_EXTERNAL_STORAGE=5001
|
||||
BIND_HTTP_EXTERNAL_STORAGE=0.0.0.0
|
||||
CHROOT_HTTP_EXTERNAL_STORAGE=%application_dir%/var/run/storage
|
||||
EXTERNAL_STORAGE_MOUNTPOINT=%application_dir%/var/run/storage
|
||||
|
||||
# Misc
|
||||
DEMO=false
|
||||
|
||||
@ -10,10 +10,8 @@ services:
|
||||
- DEBUG=false
|
||||
- SECRET_KEY=ANY_SECRET_KEY_HERE
|
||||
- PORT=5000
|
||||
- PORT_HTTP_EXTERNAL_STORAGE=5001
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ./:/app/
|
||||
ports:
|
||||
- 5000:5000
|
||||
- 5001:5001
|
||||
|
||||
@ -8,7 +8,6 @@ services:
|
||||
- DEBUG=false
|
||||
- SECRET_KEY=ANY_SECRET_KEY_HERE
|
||||
- PORT=5000
|
||||
- PORT_HTTP_EXTERNAL_STORAGE=5001
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- ./data/db:/app/data/db
|
||||
@ -16,4 +15,3 @@ services:
|
||||
- ./var/run/storage:/app/var/run/storage
|
||||
ports:
|
||||
- 5000:5000
|
||||
- 5001:5001
|
||||
|
||||
@ -184,12 +184,12 @@
|
||||
"settings_variable_desc_auth_enabled": "Enable auth management",
|
||||
"settings_variable_desc_edition_auth_enabled": "Default user credentials will be %username%/%password%",
|
||||
"settings_variable_desc_external_url": "External url (i.e: https://studio-01.company.com or http://10.10.3.100)",
|
||||
"settings_variable_desc_external_storage_url": "External url for external storage (i.e: https://studio-01.company.com or http://10.10.3.100)",
|
||||
"settings_variable_desc_slide_upload_limit": "Slide upload limit (in megabytes)",
|
||||
"settings_variable_desc_dark_mode": "Dark mode",
|
||||
"settings_variable_desc_intro_slide_duration": "Introduction slide duration (in seconds)",
|
||||
"settings_variable_desc_default_slide_time_with_seconds": "Show the seconds on the clock in the introduction slide",
|
||||
"settings_variable_desc_polling_interval": "Refresh interval applied for settings to the player (in seconds)",
|
||||
"settings_variable_desc_player_content_cache": "Enable cache",
|
||||
"settings_variable_desc_slide_animation_enabled": "Enable animation effect between slides",
|
||||
"settings_variable_desc_slide_animation_entrance_effect": "Slide animation entrance effect",
|
||||
"settings_variable_desc_slide_animation_exit_effect": "Slide animation exit effect (generally better off without it)",
|
||||
|
||||
@ -185,12 +185,12 @@
|
||||
"settings_variable_desc_auth_enabled": "Habilitar gestión de autenticación",
|
||||
"settings_variable_desc_edition_auth_enabled": "Las credenciales predeterminadas del usuario serán %username%/%password%",
|
||||
"settings_variable_desc_external_url": "URL externa (ej.: https://studio-01.company.com o http://10.10.3.100)",
|
||||
"settings_variable_desc_external_storage_url": "URL externa para almacenamiento externo(ej.: https://studio-01.company.com o http://10.10.3.100)",
|
||||
"settings_variable_desc_slide_upload_limit": "Límite de carga de diapositivas (en megabytes)",
|
||||
"settings_variable_desc_dark_mode": "Modo oscuro",
|
||||
"settings_variable_desc_intro_slide_duration": "Duración de la diapositiva de introducción (en segundos)",
|
||||
"settings_variable_desc_default_slide_time_with_seconds": "Mostrar los segundos en el reloj de la diapositiva de introducción",
|
||||
"settings_variable_desc_polling_interval": "Intervalo de actualización aplicado para configuraciones del reproductor (en segundos)",
|
||||
"settings_variable_desc_player_content_cache": "Habilitar la caché",
|
||||
"settings_variable_desc_slide_animation_enabled": "Habilitar efecto de animación entre diapositivas",
|
||||
"settings_variable_desc_slide_animation_entrance_effect": "Efecto de entrada de animación de diapositiva",
|
||||
"settings_variable_desc_slide_animation_exit_effect": "Efecto de salida de animación de diapositiva (generalmente mejor sin él)",
|
||||
|
||||
@ -186,12 +186,12 @@
|
||||
"settings_variable_desc_auth_enabled": "Activer la gestion de l'authentification",
|
||||
"settings_variable_desc_edition_auth_enabled": "Les identifiants de l'utilisateur par défaut seront %username%/%password%",
|
||||
"settings_variable_desc_external_url": "URL externe (i.e: https://studio-01.company.com or http://10.10.3.100)",
|
||||
"settings_variable_desc_external_storage_url": "URL externe pour le stockage externe (i.e: https://studio-01.company.com or http://10.10.3.100)",
|
||||
"settings_variable_desc_slide_upload_limit": "Limite d'upload du fichier d'une slide (en mégaoctets)",
|
||||
"settings_variable_desc_dark_mode": "Mdoe sombre",
|
||||
"settings_variable_desc_intro_slide_duration": "Durée de la slide d'introduction (en secondes)",
|
||||
"settings_variable_desc_default_slide_time_with_seconds": "Afficher les secondes de l'horloge de la slide d'introduction",
|
||||
"settings_variable_desc_polling_interval": "Intervalle de rafraîchissement des paramètres à appliquer au lecteur (en secondes)",
|
||||
"settings_variable_desc_player_content_cache": "Activer le cache",
|
||||
"settings_variable_desc_slide_animation_enabled": "Activer les effets d'animation entre les slides",
|
||||
"settings_variable_desc_slide_animation_entrance_effect": "Effet d'animation d'arrivée de la slide",
|
||||
"settings_variable_desc_slide_animation_exit_effect": "Effet d'animation de sortie de la slide (généralement mieux sans)",
|
||||
|
||||
@ -185,12 +185,12 @@
|
||||
"settings_variable_desc_auth_enabled": "Abilita la gestione autenticazione",
|
||||
"settings_variable_desc_edition_auth_enabled": "Le credenziali utente predefinite sono %username%/%password%",
|
||||
"settings_variable_desc_external_url": "Url esterno (esempio: https://studio-01.company.com or http://10.10.3.100)",
|
||||
"settings_variable_desc_external_storage_url": "Url esterno per l'archiviazione esterna (esempio: https://studio-01.company.com or http://10.10.3.100)",
|
||||
"settings_variable_desc_slide_upload_limit": "Limite upload slide (in megabytes)",
|
||||
"settings_variable_desc_dark_mode": "Modalità scura",
|
||||
"settings_variable_desc_intro_slide_duration": "Durata introduzione slide (in secondi)",
|
||||
"settings_variable_desc_default_slide_time_with_seconds": "Mostra secondi introduzione slide",
|
||||
"settings_variable_desc_polling_interval": "Intervallo di aggiornamento applicato per le impostazioni del monitor (in secondi)",
|
||||
"settings_variable_desc_player_content_cache": "Abilita la cache",
|
||||
"settings_variable_desc_slide_animation_enabled": "Abilita l'effetto di animazione tra le diapositive",
|
||||
"settings_variable_desc_slide_animation_entrance_effect": "Effetto ingresso diapositiva",
|
||||
"settings_variable_desc_slide_animation_exit_effect": "Effetto di uscita della diapositiva (meglio senza)",
|
||||
|
||||
@ -6,7 +6,6 @@ import threading
|
||||
from src.service.ModelStore import ModelStore
|
||||
from src.service.PluginStore import PluginStore
|
||||
from src.service.TemplateRenderer import TemplateRenderer
|
||||
from src.service.ExternalStorageServer import ExternalStorageServer
|
||||
from src.service.WebServer import WebServer
|
||||
from src.model.enum.HookType import HookType
|
||||
|
||||
@ -19,7 +18,6 @@ class Application:
|
||||
self._model_store = ModelStore(self, self.get_plugins)
|
||||
self._template_renderer = TemplateRenderer(kernel=self, model_store=self._model_store, render_hook=self.render_hook)
|
||||
self._web_server = WebServer(kernel=self, model_store=self._model_store, template_renderer=self._template_renderer)
|
||||
self._external_storage_server = ExternalStorageServer(kernel=self, model_store=self._model_store)
|
||||
|
||||
logging.info("[{}] Starting application v{}...".format(self.get_name(), self.get_version()))
|
||||
self._plugin_store = PluginStore(kernel=self, model_store=self._model_store, template_renderer=self._template_renderer, web_server=self._web_server)
|
||||
@ -31,7 +29,6 @@ class Application:
|
||||
if variable:
|
||||
self._model_store.variable().update_by_name(variable.name, variable.as_int() + 1)
|
||||
|
||||
self._external_storage_server.run()
|
||||
self._web_server.run()
|
||||
|
||||
def signal_handler(self, signal, frame) -> None:
|
||||
@ -62,7 +59,3 @@ class Application:
|
||||
self._model_store.lang().set_lang(lang)
|
||||
self._model_store.variable().reload()
|
||||
self._plugin_store.reload_lang()
|
||||
|
||||
@property
|
||||
def external_storage_server(self):
|
||||
return self._external_storage_server
|
||||
|
||||
@ -9,7 +9,6 @@ from src.model.entity.Content import Content
|
||||
from src.model.enum.ContentType import ContentType
|
||||
from src.model.enum.FolderEntity import FolderEntity, FOLDER_ROOT_PATH
|
||||
from src.interface.ObController import ObController
|
||||
from src.service.ExternalStorageServer import ExternalStorageServer
|
||||
from src.util.utils import str_to_enum, get_optional_string
|
||||
from src.util.UtilFile import randomize_filename
|
||||
|
||||
@ -60,7 +59,7 @@ class ContentController(ObController):
|
||||
working_folder_children=self._model_store.folder().get_children(folder=working_folder, entity=FolderEntity.CONTENT, sort='created_at', ascending=False),
|
||||
enum_content_type=ContentType,
|
||||
enum_folder_entity=FolderEntity,
|
||||
chroot_http_external_storage=self.get_external_storage_server().get_directory(),
|
||||
external_storage_mountpoint=self._model_store.config().map().get('external_storage_mountpoint'),
|
||||
)
|
||||
|
||||
def slideshow_content_add(self):
|
||||
@ -119,7 +118,7 @@ class ContentController(ObController):
|
||||
working_folder_path=working_folder_path,
|
||||
working_folder=working_folder,
|
||||
enum_content_type=ContentType,
|
||||
chroot_http_external_storage=self.get_external_storage_server().get_directory(),
|
||||
external_storage_mountpoint=self._model_store.config().map().get('external_storage_mountpoint')
|
||||
)
|
||||
|
||||
def slideshow_content_save(self, content_id: int = 0):
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import hashlib
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, List, Dict
|
||||
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify, abort
|
||||
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify, abort, send_file, Response
|
||||
from pathlib import Path
|
||||
|
||||
from src.model.entity.Slide import Slide
|
||||
from src.model.entity.Content import Content
|
||||
from src.model.enum.ContentType import ContentType
|
||||
from src.exceptions.NoFallbackPlaylistException import NoFallbackPlaylistException
|
||||
from src.service.ModelStore import ModelStore
|
||||
from src.interface.ObController import ObController
|
||||
from src.util.utils import get_safe_cron_descriptor, is_cron_in_datetime_moment, is_cron_in_week_moment, is_now_after_cron_date_time_moment, is_now_after_cron_week_moment
|
||||
from src.util.utils import get_safe_cron_descriptor, is_cron_in_datetime_moment, is_cron_in_week_moment, is_now_after_cron_date_time_moment, is_now_after_cron_week_moment, decode_uri_component
|
||||
from src.util.UtilNetwork import get_safe_remote_addr, get_network_interfaces
|
||||
from src.model.enum.AnimationSpeed import animation_speed_duration
|
||||
|
||||
@ -25,6 +27,7 @@ class PlayerController(ObController):
|
||||
self._app.add_url_rule('/player/default', 'player_default', self.player_default, methods=['GET'])
|
||||
self._app.add_url_rule('/player/playlist', 'player_playlist', self.player_playlist, methods=['GET'])
|
||||
self._app.add_url_rule('/player/playlist/use/<playlist_slug_or_id>', 'player_playlist_use', self.player_playlist, methods=['GET'])
|
||||
self._app.add_url_rule('/serve/content/<content_type>/<content_id>/<content_location>', 'serve_content_file', self.serve_content_file, methods=['GET'])
|
||||
|
||||
def player(self, playlist_slug_or_id: str = ''):
|
||||
preview_content_id = request.args.get('preview_content_id')
|
||||
@ -136,24 +139,26 @@ class PlayerController(ObController):
|
||||
|
||||
content = contents[int(slide['content_id'])]
|
||||
slide['name'] = content.name
|
||||
slide['location'] = content.location
|
||||
slide['type'] = content.type.value
|
||||
slide['location'] = self._model_store.content().resolve_content_location(content)
|
||||
|
||||
if slide['type'] == ContentType.EXTERNAL_STORAGE.value:
|
||||
mount_point_dir = Path(self.get_external_storage_server().get_directory(), slide['location'])
|
||||
mount_point_dir = Path(self._model_store.config().map().get('external_storage_mountpoint'), slide['location'])
|
||||
if mount_point_dir.is_dir():
|
||||
for file in mount_point_dir.iterdir():
|
||||
if file.is_file() and not file.stem.startswith('.'):
|
||||
virtual_content = Content(
|
||||
name=file.stem,
|
||||
location=self._model_store.content().resolve_content_location(Path(content.location, file.name)),
|
||||
type=ContentType.guess_content_type_file(str(file.resolve())),
|
||||
)
|
||||
slide = dict(slide)
|
||||
slide['id'] = hashlib.md5(str(file).encode('utf-8')).hexdigest()
|
||||
slide['position'] = position
|
||||
slide['type'] = ContentType.guess_content_type_file(str(file.resolve())).value
|
||||
slide['name'] = file.stem
|
||||
slide['delegate_duration'] = 1 if slide['type'] == ContentType.VIDEO.value else 0
|
||||
slide['location'] = "{}/{}".format(
|
||||
self._model_store.content().resolve_content_location(content),
|
||||
file.name
|
||||
)
|
||||
slide['name'] = virtual_content.stem
|
||||
slide['type'] = virtual_content.type.value
|
||||
slide['location'] = self._model_store.content().resolve_content_location(virtual_content)
|
||||
self._check_slide_enablement(playlist_loop, playlist_notifications, slide)
|
||||
position = position + 1
|
||||
else:
|
||||
@ -197,3 +202,39 @@ class PlayerController(ObController):
|
||||
return
|
||||
|
||||
loop.append(slide)
|
||||
|
||||
def serve_content_file(self, content_location, content_type, content_id):
|
||||
content = self._model_store.content().get(content_id)
|
||||
|
||||
if not content:
|
||||
abort(404, 'Content not found')
|
||||
|
||||
content_location = decode_uri_component(content_location)
|
||||
|
||||
content_path = str(Path(self.get_application_dir(), content_location))
|
||||
|
||||
if content_type == ContentType.EXTERNAL_STORAGE.value:
|
||||
content_path = str(Path(self._model_store.config().map().get('external_storage_mountpoint'), content_location))
|
||||
|
||||
if not os.path.exists(content_path) or '..' in content_path:
|
||||
abort(404, 'Content not found')
|
||||
|
||||
if not self._model_store.variable().get_one_by_name('player_content_cache').as_bool():
|
||||
response = send_file(content_path)
|
||||
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
||||
response.headers['Pragma'] = 'no-cache'
|
||||
response.headers['Expires'] = '0'
|
||||
return response
|
||||
|
||||
content_path_hash = hashlib.sha256(str(content_path).encode()).hexdigest()
|
||||
etag = f'"{content_path_hash}-{content_id}-{os.path.getmtime(content_path)}"'
|
||||
|
||||
if_none_match = request.headers.get('If-None-Match')
|
||||
if if_none_match == etag:
|
||||
return Response(status=304)
|
||||
|
||||
response = send_file(content_path)
|
||||
response.headers['Cache-Control'] = 'public, max-age=3153600000' # 100 years
|
||||
response.headers['ETag'] = etag
|
||||
|
||||
return response
|
||||
|
||||
@ -40,12 +40,12 @@ class ObController(abc.ABC):
|
||||
def reload_lang(self, lang: str):
|
||||
self._kernel.reload_lang(lang)
|
||||
|
||||
def get_application_dir(self):
|
||||
return self._kernel.get_application_dir()
|
||||
|
||||
def t(self, token) -> Union[Dict, str]:
|
||||
return self._model_store.lang().translate(token)
|
||||
|
||||
def get_external_storage_server(self):
|
||||
return self._kernel.external_storage_server
|
||||
|
||||
def render_view(self, template_file: str, **parameters: dict) -> str:
|
||||
return self._template_renderer.render_view(template_file, self.plugin(), **parameters)
|
||||
|
||||
|
||||
@ -11,7 +11,6 @@ class ConfigManager:
|
||||
|
||||
APPLICATION_NAME = "Obscreen"
|
||||
DEFAULT_PORT = 5000
|
||||
DEFAULT_PORT_HTTP_EXTERNAL_STORAGE = 5001
|
||||
VERSION_FILE = 'version.txt'
|
||||
|
||||
def __init__(self, replacers: Dict):
|
||||
@ -20,9 +19,7 @@ class ConfigManager:
|
||||
'application_name': self.APPLICATION_NAME,
|
||||
'version': None,
|
||||
'demo': False,
|
||||
'port_http_external_storage': self.DEFAULT_PORT_HTTP_EXTERNAL_STORAGE,
|
||||
'bind_http_external_storage': '0.0.0.0',
|
||||
'chroot_http_external_storage': '%application_dir%/var/run/storage',
|
||||
'external_storage_mountpoint': '%application_dir%/var/run/storage',
|
||||
'port': self.DEFAULT_PORT,
|
||||
'bind': '0.0.0.0',
|
||||
'debug': False,
|
||||
@ -56,9 +53,7 @@ class ConfigManager:
|
||||
parser.add_argument('--log-level', '-ll', default=self._CONFIG['log_level'], help='Log Level')
|
||||
parser.add_argument('--log-stdout', '-ls', default=self._CONFIG['log_stdout'], action='store_true', help='Log to standard output')
|
||||
parser.add_argument('--demo', '-o', default=self._CONFIG['demo'], help='Demo mode to showcase obscreen in a sandbox')
|
||||
parser.add_argument('--port-http-external-storage', '-bx', default=self._CONFIG['port_http_external_storage'], help='Port of http server serving external storage')
|
||||
parser.add_argument('--bind-http-external-storage', '-px', default=self._CONFIG['bind_http_external_storage'], help='Bind address of http server serving external storage')
|
||||
parser.add_argument('--chroot-http-external-storage', '-cx', default=self._CONFIG['chroot_http_external_storage'], help='Chroot directory of http server serving external storage')
|
||||
parser.add_argument('--external-storage-mountpoint', '-e', default=self._CONFIG['external_storage_mountpoint'], help='Mountpoint directory of external storage')
|
||||
parser.add_argument('--version', '-v', default=None, action='store_true', help='Get version number')
|
||||
|
||||
return parser.parse_args()
|
||||
@ -74,12 +69,8 @@ class ConfigManager:
|
||||
self._CONFIG['debug'] = args.debug
|
||||
if args.demo:
|
||||
self._CONFIG['demo'] = args.demo
|
||||
if args.port_http_external_storage:
|
||||
self._CONFIG['port_http_external_storage'] = args.port_http_external_storage
|
||||
if args.bind_http_external_storage:
|
||||
self._CONFIG['bind_http_external_storage'] = args.bind_http_external_storage
|
||||
if args.chroot_http_external_storage:
|
||||
self._CONFIG['chroot_http_external_storage'] = args.chroot_http_external_storage
|
||||
if args.external_storage_mountpoint:
|
||||
self._CONFIG['external_storage_mountpoint'] = args.external_storage_mountpoint
|
||||
if args.log_file:
|
||||
self._CONFIG['log_file'] = args.log_file
|
||||
if args.secret_key:
|
||||
|
||||
@ -2,6 +2,7 @@ import os
|
||||
|
||||
from typing import Dict, Optional, List, Tuple, Union
|
||||
from werkzeug.datastructures import FileStorage
|
||||
from flask import url_for
|
||||
|
||||
from src.model.entity.Content import Content
|
||||
from src.model.entity.Playlist import Playlist
|
||||
@ -16,6 +17,7 @@ from src.service.ModelManager import ModelManager
|
||||
from src.util.UtilFile import randomize_filename
|
||||
from src.util.UtilNetwork import get_preferred_ip_address
|
||||
from src.util.UtilVideo import mp4_duration_with_ffprobe
|
||||
from src.util.utils import encode_uri_component
|
||||
|
||||
|
||||
class ContentManager(ModelManager):
|
||||
@ -233,19 +235,18 @@ class ContentManager(ModelManager):
|
||||
var_external_url = self._variable_manager.get_one_by_name('external_url').as_string().strip().strip('/')
|
||||
location = content.location
|
||||
|
||||
if content.type == ContentType.EXTERNAL_STORAGE:
|
||||
var_external_storage_url = self._variable_manager.get_one_by_name('external_url_storage').as_string().strip().strip('/')
|
||||
port_ex_st = self._config_manager.map().get('port_http_external_storage')
|
||||
location = "{}/{}".format(
|
||||
var_external_storage_url if var_external_storage_url else 'http://{}:{}'.format(get_preferred_ip_address(), port_ex_st),
|
||||
content.location.strip('/')
|
||||
)
|
||||
elif content.type == ContentType.YOUTUBE:
|
||||
if content.type == ContentType.YOUTUBE:
|
||||
location = "https://www.youtube.com/watch?v={}".format(content.location)
|
||||
elif len(var_external_url) > 0 and content.has_file():
|
||||
location = "{}/{}".format(var_external_url, content.location)
|
||||
elif content.has_file():
|
||||
location = "/{}".format(content.location)
|
||||
elif content.has_file() or content.type == ContentType.EXTERNAL_STORAGE:
|
||||
location = "{}/{}".format(
|
||||
var_external_url if len(var_external_url) > 0 else "",
|
||||
url_for(
|
||||
'serve_content_file',
|
||||
content_location=encode_uri_component(content.location),
|
||||
content_type=content.type.value,
|
||||
content_id=content.id
|
||||
).strip('/')
|
||||
).strip('/')
|
||||
elif content.type == ContentType.URL:
|
||||
location = 'http://' + content.location if not content.location.startswith('http') else content.location
|
||||
|
||||
|
||||
@ -214,6 +214,7 @@ class DatabaseManager:
|
||||
"DELETE FROM settings WHERE name = 'playlist_default_time_sync'",
|
||||
"DELETE FROM settings WHERE name = 'slide_animation_exit_effect'",
|
||||
"DELETE FROM settings WHERE name = 'playlist_enabled'",
|
||||
"DELETE FROM settings WHERE name = 'external_url_storage'",
|
||||
"UPDATE fleet_player_group SET slug = id WHERE slug = '' or slug is null",
|
||||
"UPDATE content SET uuid = id WHERE uuid = '' or uuid is null",
|
||||
"UPDATE slide SET uuid = id WHERE uuid = '' or uuid is null",
|
||||
|
||||
@ -117,7 +117,6 @@ class VariableManager:
|
||||
### General
|
||||
{"name": "lang", "section": self.t(VariableSection.GENERAL), "value": "en", "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_lang'), "selectables": self.t(ApplicationLanguage), "refresh_player": False},
|
||||
{"name": "external_url", "section": self.t(VariableSection.GENERAL), "value": "", "type": VariableType.STRING, "editable": False if demo else True, "description": self.t('settings_variable_desc_external_url'), "refresh_player": False},
|
||||
{"name": "external_url_storage", "section": self.t(VariableSection.GENERAL), "value": "", "type": VariableType.STRING, "editable": False if demo else True, "description": self.t('settings_variable_desc_external_storage_url'), "refresh_player": False},
|
||||
{"name": "slide_upload_limit", "section": self.t(VariableSection.GENERAL), "value": 32, "unit": VariableUnit.MEGABYTE, "type": VariableType.INT, "editable": False if demo else True, "description": self.t('settings_variable_desc_slide_upload_limit'), "refresh_player": False},
|
||||
{"name": "dark_mode", "section": self.t(VariableSection.GENERAL), "value": True, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_dark_mode'), "refresh_player": False},
|
||||
|
||||
@ -125,6 +124,7 @@ class VariableManager:
|
||||
{"name": "intro_slide_duration", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 3, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_intro_slide_duration'), "refresh_player": False},
|
||||
{"name": "default_slide_time_with_seconds", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_default_slide_time_with_seconds'), "refresh_player": False},
|
||||
{"name": "polling_interval", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 5, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_polling_interval'), "refresh_player": True},
|
||||
{"name": "player_content_cache", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": True, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_player_content_cache'), "refresh_player": False},
|
||||
|
||||
### Player Animation
|
||||
{"name": "slide_animation_enabled", "section": self.t(VariableSection.PLAYER_ANIMATION), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_slide_animation_enabled'), "refresh_player": True},
|
||||
|
||||
@ -1,103 +0,0 @@
|
||||
import os
|
||||
import psutil
|
||||
import platform
|
||||
import logging
|
||||
import http.server
|
||||
import socketserver
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import socket
|
||||
import threading
|
||||
|
||||
from typing import Dict, Optional, List, Tuple, Union
|
||||
from pathlib import Path
|
||||
|
||||
from src.service.ModelStore import ModelStore
|
||||
from src.model.entity.ExternalStorage import ExternalStorage
|
||||
|
||||
|
||||
class ExternalStorageServer:
|
||||
|
||||
def __init__(self, kernel, model_store: ModelStore):
|
||||
self._kernel = kernel
|
||||
self._model_store = model_store
|
||||
|
||||
def get_directory(self):
|
||||
return self._model_store.config().map().get('chroot_http_external_storage').replace(
|
||||
'%application_dir%', self._kernel.get_application_dir()
|
||||
)
|
||||
|
||||
def get_port(self) -> Optional[int]:
|
||||
port = self._model_store.config().map().get('port_http_external_storage')
|
||||
return int(port) if port else None
|
||||
|
||||
def run(self):
|
||||
port = self.get_port()
|
||||
bind = self._model_store.config().map().get('bind_http_external_storage')
|
||||
if not port:
|
||||
return
|
||||
|
||||
thread = threading.Thread(target=self._start, args=(self.get_directory(), port, bind))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def _start(self, directory, port, bind):
|
||||
class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, directory=directory, **kwargs)
|
||||
|
||||
Handler = CustomHTTPRequestHandler
|
||||
|
||||
class ReusableTCPServer(socketserver.TCPServer):
|
||||
allow_reuse_address = True
|
||||
|
||||
with ReusableTCPServer((bind, port), Handler) as httpd:
|
||||
logging.info("Serving external storage on path>{}:{}".format(directory, port))
|
||||
httpd.serve_forever()
|
||||
|
||||
@staticmethod
|
||||
def list_usb_storage_devices() -> List[ExternalStorage]:
|
||||
os_type = platform.system()
|
||||
partitions = psutil.disk_partitions()
|
||||
removable_devices = []
|
||||
for partition in partitions:
|
||||
if 'dontbrowse' in partition.opts:
|
||||
continue
|
||||
|
||||
if os_type == "Windows":
|
||||
if 'removable' in partition.opts:
|
||||
removable_devices.append(partition)
|
||||
else:
|
||||
if '/media' in partition.mountpoint or '/run/media' in partition.mountpoint or '/mnt' in partition.mountpoint or '/Volumes' in partition.mountpoint:
|
||||
removable_devices.append(partition)
|
||||
|
||||
if not removable_devices:
|
||||
return {}
|
||||
|
||||
storages = []
|
||||
|
||||
for device in removable_devices:
|
||||
try:
|
||||
usage = psutil.disk_usage(device.mountpoint)
|
||||
# total_size = usage.total / (1024 ** 3)
|
||||
external_storage = ExternalStorage(
|
||||
logical_name=device.device,
|
||||
mount_point=device.mountpoint,
|
||||
content_id=None,
|
||||
total_size=usage.total,
|
||||
)
|
||||
storages.append(external_storage)
|
||||
except Exception as e:
|
||||
logging.error(f"Could not retrieve size for device {device.device}: {e}")
|
||||
|
||||
return storages
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_external_storage_devices():
|
||||
return {storage.mount_point: "{} ({} - {}GB)".format(
|
||||
storage.mount_point,
|
||||
storage.logical_name,
|
||||
storage.total_size_in_gigabytes()
|
||||
) for storage in ExternalStorageServer.list_usb_storage_devices()}
|
||||
54
src/util/UtilExternalStorage.py
Normal file
54
src/util/UtilExternalStorage.py
Normal file
@ -0,0 +1,54 @@
|
||||
import os
|
||||
import psutil
|
||||
import platform
|
||||
import logging
|
||||
import os
|
||||
|
||||
from typing import Dict, Optional, List, Tuple, Union
|
||||
|
||||
from src.model.entity.ExternalStorage import ExternalStorage
|
||||
|
||||
|
||||
def list_usb_storage_devices() -> List[ExternalStorage]:
|
||||
os_type = platform.system()
|
||||
partitions = psutil.disk_partitions()
|
||||
removable_devices = []
|
||||
for partition in partitions:
|
||||
if 'dontbrowse' in partition.opts:
|
||||
continue
|
||||
|
||||
if os_type == "Windows":
|
||||
if 'removable' in partition.opts:
|
||||
removable_devices.append(partition)
|
||||
else:
|
||||
if '/media' in partition.mountpoint or '/run/media' in partition.mountpoint or '/mnt' in partition.mountpoint or '/Volumes' in partition.mountpoint:
|
||||
removable_devices.append(partition)
|
||||
|
||||
if not removable_devices:
|
||||
return {}
|
||||
|
||||
storages = []
|
||||
|
||||
for device in removable_devices:
|
||||
try:
|
||||
usage = psutil.disk_usage(device.mountpoint)
|
||||
# total_size = usage.total / (1024 ** 3)
|
||||
external_storage = ExternalStorage(
|
||||
logical_name=device.device,
|
||||
mount_point=device.mountpoint,
|
||||
content_id=None,
|
||||
total_size=usage.total,
|
||||
)
|
||||
storages.append(external_storage)
|
||||
except Exception as e:
|
||||
logging.error(f"Could not retrieve size for device {device.device}: {e}")
|
||||
|
||||
return storages
|
||||
|
||||
|
||||
def get_external_storage_devices():
|
||||
return {storage.mount_point: "{} ({} - {}GB)".format(
|
||||
storage.mount_point,
|
||||
storage.logical_name,
|
||||
storage.total_size_in_gigabytes()
|
||||
) for storage in ExternalStorageServer.list_usb_storage_devices()}
|
||||
@ -6,7 +6,7 @@ import inspect
|
||||
import subprocess
|
||||
import unicodedata
|
||||
import platform
|
||||
|
||||
import urllib.parse
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, List, Dict
|
||||
@ -335,3 +335,11 @@ def str_to_bool(value):
|
||||
return True
|
||||
else:
|
||||
raise ValueError('Boolean value expected.')
|
||||
|
||||
|
||||
def encode_uri_component(uri_component):
|
||||
return urllib.parse.quote(uri_component, safe='~()*!.\'')
|
||||
|
||||
|
||||
def decode_uri_component(encoded_component):
|
||||
return urllib.parse.unquote(encoded_component)
|
||||
|
||||
@ -16,4 +16,4 @@ WIDTH=$(echo $RESOLUTION | cut -d 'x' -f 1)
|
||||
HEIGHT=$(echo $RESOLUTION | cut -d 'x' -f 2)
|
||||
|
||||
# Start Chromium in kiosk mode
|
||||
chromium-browser --disable-features=Translate --ignore-certificate-errors --disable-web-security --disable-restore-session-state --autoplay-policy=no-user-gesture-required --start-maximized --allow-running-insecure-content --remember-cert-error-decisions --noerrdialogs --kiosk --incognito --window-position=0,0 --window-size=${WIDTH},${HEIGHT} --display=:0 http://localhost:5000
|
||||
chromium-browser --disk-cache-size=2147483648 --disable-features=Translate --ignore-certificate-errors --disable-web-security --disable-restore-session-state --autoplay-policy=no-user-gesture-required --start-maximized --allow-running-insecure-content --remember-cert-error-decisions --noerrdialogs --kiosk --incognito --window-position=0,0 --window-size=${WIDTH},${HEIGHT} --display=:0 http://localhost:5000
|
||||
|
||||
@ -351,8 +351,7 @@
|
||||
}
|
||||
|
||||
const loadPicture = function(element, callbackReady, item) {
|
||||
const hasScheme = item.location.indexOf('://') >= 0;
|
||||
element.innerHTML = `<img src="${hasScheme ? item.location : ('/' + item.location)}" alt="" />`;
|
||||
element.innerHTML = `<img src="${item.location}" alt="" />`;
|
||||
callbackReady(function() {});
|
||||
};
|
||||
|
||||
@ -378,8 +377,7 @@
|
||||
};
|
||||
|
||||
const loadVideo = function(element, callbackReady, item) {
|
||||
const location = item.location.indexOf('http') === 0 ? item.location : `/${item.location}`;
|
||||
element.innerHTML = `<video ${previewMode ? 'controls' : ''}><source src=${location} type="video/mp4" /></video>`;
|
||||
element.innerHTML = `<video ${previewMode ? 'controls' : ''}><source src=${item.location} type="video/mp4" /></video>`;
|
||||
const video = element.querySelector('video');
|
||||
callbackReady(function() {});
|
||||
|
||||
|
||||
@ -78,7 +78,7 @@
|
||||
</label>
|
||||
<div class="widget vertical">
|
||||
{% if content.type == enum_content_type.EXTERNAL_STORAGE %}
|
||||
<input type="text" class="disabled" disabled value="{{ chroot_http_external_storage }}/" />
|
||||
<input type="text" class="disabled" disabled value="{{ external_storage_mountpoint }}/" />
|
||||
{% endif %}
|
||||
|
||||
{% set location = content.location %}
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
<div class="form-group">
|
||||
<label for="" class="object-label"></label>
|
||||
<div class="widget vertical">
|
||||
<input type="text" class="disabled" value="{{ chroot_http_external_storage }}/" />
|
||||
<input type="text" class="disabled" value="{{ external_storage_mountpoint }}/" />
|
||||
<input type="text" name="object" data-input-type="storage" class="content-object-input" disabled="disabled" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user