change logic

This commit is contained in:
jr-k 2024-07-21 18:42:20 +02:00
parent 46f06a6927
commit f8b6da0d4c
27 changed files with 252 additions and 323 deletions

View File

@ -1,3 +1,6 @@
DEMO=false
DEBUG=false
PORT=5000
PORT_HTTP_EXTERNAL_STORAGE=5001
SECRET_KEY=ANY_SECRET_KEY_HERE

View File

@ -1,11 +1,11 @@
FROM python:3.9.17-alpine3.17
RUN apk add --no-cache --virtual .build-deps gcc musl-dev sqlite-dev build-base linux-headers
RUN apk add --no-cache --virtual .build-deps gcc musl-dev sqlite-dev exfat-fuse ntfs-3g build-base linux-headers
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt && apk del .build-deps gcc musl-dev sqlite-dev build-base linux-headers
RUN pip install -r requirements.txt && apk del .build-deps gcc musl-dev sqlite-dev exfat-fuse ntfs-3g build-base linux-headers
ENTRYPOINT ["python", "/app/obscreen.py"]

View File

@ -76,7 +76,7 @@ If you value this project, please think about awarding it a ⭐. Thanks ! 🙏
<details closed>
<summary><h5>Videos aren't playing why ?</h3></summary>
<summary><h3>Videos aren't playing why ?</h3></summary>
This is "normal" behavior. Videos do not play automatically in Chrome because it requires user interaction with the page (a simple click inside the webpage is enough). If you open the console, you'll see the error: [Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first...](https://goo.gl/xX8pDD)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,18 +11,14 @@ jQuery(document).ready(function ($) {
$form.find('.content-object-input').each(function() {
const $input = $(this);
const active = $input.attr('data-input-type') === inputType;
const $holder = $input.parents('.object-holder:eq(0)');
const $holder = $input.parents('.from-group-condition:eq(0)');
$holder.find('input, select, textarea').prop('disabled', !active).prop('required', active).toggleClass('hidden', !active);
$holder.toggleClass('hidden', !active);
console.log(active)
console.log($input)
if (active)
console.log($holder)
});
const optionAttributes = $selectedOption.get(0).attributes;
const color = optionAttributes['data-color'].value;
$form.find('.object-label').html(optionAttributes['data-object-label'].value);
$form.find('.object-label:visible').html(optionAttributes['data-object-label'].value);
$('.type-icon').attr('class', 'type-icon fa ' + optionAttributes['data-icon'].value);
$('.tab-select .widget').attr('class', 'widget ' + ('border-' + color) + ' ' + color);
$form.find('button[type=submit]').attr('class', 'btn ' + ('btn-' + color));

View File

@ -34,6 +34,7 @@
.modals-outer {
min-width: 464px;
max-width: 464px;
display: flex;
flex-direction: column;
overflow: auto;

View File

@ -25,6 +25,14 @@ form {
}
}
.from-group-condition {
flex-direction: column;
align-self: stretch;
justify-content: flex-start;
align-items: flex-start;
display: flex;
}
.form-group {
display: flex;
flex-direction: column;
@ -35,32 +43,10 @@ form {
flex: 1;
margin-bottom: 20px;
.object-holder {
flex-direction: column;
align-self: stretch;
justify-content: flex-start;
align-items: flex-start;
display: flex;
flex: 1;
input,
select,
textarea {
flex: 1;
align-self: stretch;
}
.form-group {
label {
margin-top: 20px;
margin-bottom: 8px;
}
}
}
label {
flex: 1;
font-size: 12px;
line-height: 18px;
display: flex;
flex-direction: row;
justify-content: flex-start;

View File

@ -66,7 +66,7 @@ docker compose up --detach --pull=always
```bash
# Install system dependencies
sudo apt-get update
sudo apt-get install -y git python3-pip python3-venv libsqlite3-dev
sudo apt-get install -y git python3-pip python3-venv libsqlite3-dev exfat-fuse ntfs-3g
# Get files
cd ~ && git clone https://github.com/jr-k/obscreen.git && cd obscreen

View File

@ -284,8 +284,8 @@
"enum_application_language_italian": "Italian",
"enum_application_language_spanish": "Spanish",
"enum_content_type_external_storage": "External Storage",
"enum_content_type_external_storage_object_label": "Choose an external storage drive",
"enum_content_type_external_storage_target_path_label": "Write an existing directory path inside your storage device",
"enum_content_type_external_storage_object_label": "Specify an existing directory path with displayable files within your removeable device",
"enum_content_type_external_storage_flashdrive_label": "Path relative to a removeable device",
"enum_content_type_url": "URL",
"enum_content_type_video": "Video",
"enum_content_type_picture": "Picture",

View File

@ -285,8 +285,8 @@
"enum_application_language_italian": "Italiano",
"enum_application_language_spanish": "Español",
"enum_content_type_external_storage": "Almacenamiento externo",
"enum_content_type_external_storage_object_label": "Elija una unidad de almacenamiento externa",
"enum_content_type_external_storage_target_path_label": "Escribe una ruta de directorio existente dentro de tu dispositivo de almacenamiento",
"enum_content_type_external_storage_object_label": "Especifique una ruta de directorio existente con archivos visualizables dentro de su dispositivo extraíble",
"enum_content_type_external_storage_flashdrive_label": "Ruta relativa a un dispositivo extraíble",
"enum_content_type_url": "URL",
"enum_content_type_video": "Video",
"enum_content_type_picture": "Imagen",

View File

@ -286,8 +286,8 @@
"enum_application_language_italian": "Italien",
"enum_application_language_spanish": "Espagnol",
"enum_content_type_external_storage": "Stockage externe",
"enum_content_type_external_storage_object_label": "Choisissez un disque de stockage externe",
"enum_content_type_external_storage_target_path_label": "Écrivez un chemin de répertoire existant dans votre périphérique de stockage",
"enum_content_type_external_storage_object_label": "Spécifiez un chemin de répertoire existant avec des fichiers affichables dans votre périphérique amovible",
"enum_content_type_external_storage_flashdrive_label": "Chemin relatif à un périphérique amovible",
"enum_content_type_url": "URL",
"enum_content_type_video": "Vidéo",
"enum_content_type_picture": "Image",

View File

@ -285,8 +285,8 @@
"enum_application_language_italian": "Italiano",
"enum_application_language_spanish": "Spagnolo",
"enum_content_type_external_storage": "Archiviazione esterna",
"enum_content_type_external_storage_object_label": "Scegli un'unità di archiviazione esterna",
"enum_content_type_external_storage_target_path_label": "Écrivez un chemin de répertoire existant dans votre périphérique de stockage",
"enum_content_type_external_storage_object_label": "Specifica un percorso di directory esistente con file visualizzabili all'interno del tuo dispositivo rimovibile",
"enum_content_type_external_storage_flashdrive_label": "Percorso relativo ad un dispositivo rimovibile",
"enum_content_type_url": "URL",
"enum_content_type_video": "Video",
"enum_content_type_picture": "Immagine",

View File

@ -6,6 +6,7 @@ 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
@ -18,6 +19,7 @@ class Application:
self._model_store = ModelStore(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)
@ -29,6 +31,7 @@ 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:
@ -59,3 +62,7 @@ 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

View File

@ -9,6 +9,7 @@ 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
@ -48,8 +49,6 @@ class ContentController(ObController):
self._model_store.variable().update_by_name('last_pillmenu_slideshow', 'slideshow_content_list')
working_folder_path, working_folder = self.get_working_folder()
slides_with_content = self._model_store.slide().get_all_indexed(attribute='content_id', multiple=True)
external_storages = self._model_store.external_storage().list_usb_storage_devices()
print(external_storages[0] if len(external_storages) > 0 else 'none')
return render_template(
'slideshow/contents/list.jinja.html',
@ -60,12 +59,7 @@ class ContentController(ObController):
working_folder=working_folder,
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,
external_storages={storage.mount_point: "{} ({} - {}GB)".format(
storage.mount_point,
storage.logical_name,
storage.total_size_in_gigabytes()
) for storage in external_storages},
enum_folder_entity=FolderEntity
)
def slideshow_content_add(self):
@ -76,13 +70,6 @@ class ContentController(ObController):
location = request.form['object'] if 'object' in request.form else None
if 'storage' in request.form:
location = "{}/{}".format(request.form['storage'], location.strip('/'))
if not os.path.exists(location):
route_args["error"] = "common_bad_directory_path"
return redirect(url_for('slideshow_content_list', **route_args))
content = self._model_store.content().add_form_raw(
name=request.form['name'],
type=str_to_enum(request.form['type'], ContentType),
@ -253,21 +240,7 @@ class ContentController(ObController):
if not content:
return abort(404)
var_external_url = self._model_store.variable().get_one_by_name('external_url')
location = content.location
if content.type == ContentType.EXTERNAL_STORAGE:
location = "file://{}".format(location)
elif content.type == ContentType.YOUTUBE:
location = "https://www.youtube.com/watch?v={}".format(content.location)
elif len(var_external_url.as_string().strip()) > 0 and content.has_file():
location = "{}/{}".format(var_external_url.value, content.location)
elif content.has_file():
location = "/{}".format(content.location)
elif content.type == ContentType.URL:
location = 'http://' + content.location if not content.location.startswith('http') else content.location
return redirect(location)
return redirect(self._model_store.content().resolve_content_location(content))
def slideshow_content_delete_bulk_explr(self):
working_folder_path, working_folder = self.get_working_folder()

View File

@ -139,13 +139,21 @@ class PlayerController(ObController):
if slide['type'] == ContentType.EXTERNAL_STORAGE.value:
mount_point_dir = Path(slide['location'])
if True: #mount_point_dir.is_dir():
for file in [Path('/Volumes/ESD-USB/obscreen/test/autumn.jpg'), Path('/Volumes/ESD-USB/obscreen/test/soundonly.jpg')]: #mount_point_dir.iterdir():
# if file.is_file() and not file.stem.startswith('.'):
slide['type'] = ContentType.guess_content_type_file(str(file.resolve())).value
slide['location'] = "file://{}".format(str(file.resolve()))
slide['name'] = file.stem
self._feed_playlist(playlist_loop, playlist_notifications, slide)
logging.info(mount_point_dir)
if mount_point_dir.is_dir():
logging.info('exist !')
for file in mount_point_dir.iterdir():
logging.info(file)
if file.is_file() and not file.stem.startswith('.'):
logging.info("f is ok !")
slide['type'] = ContentType.guess_content_type_file(str(file.resolve())).value
slide['location'] = "{}/{}".format(
self._model_store.content().resolve_content_location(content),
file.name
)
slide['name'] = file.stem
logging.info(slide.to_json())
self._feed_playlist(playlist_loop, playlist_notifications, slide)
else:
self._feed_playlist(playlist_loop, playlist_notifications, slide)

View File

@ -42,3 +42,6 @@ class ObController(abc.ABC):
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

View File

@ -15,6 +15,8 @@ class ConfigManager:
self._CONFIG = {
'version': None,
'demo': False,
'port_http_external_storage': None,
'bind_http_external_storage': '0.0.0.0',
'port': self.DEFAULT_PORT,
'bind': '0.0.0.0',
'debug': False,
@ -46,6 +48,8 @@ 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 directory')
parser.add_argument('--bind-http-external-storage', '-px', default=self._CONFIG['bind_http_external_storage'], help='Bind address of http server serving external storage directory')
parser.add_argument('--version', '-v', default=None, action='store_true', help='Get version number')
return parser.parse_args()
@ -61,6 +65,10 @@ 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.log_file:
self._CONFIG['log_file'] = args.log_file
if args.secret_key:

View File

@ -216,3 +216,24 @@ class ContentManager(ModelManager):
def count_contents_for_folder(self, folder_id: int) -> int:
return len(self.get_contents(folder_id=folder_id))
def resolve_content_location(self, content: Content) -> str:
var_external_url = self._model_store.variable().get_one_by_name('external_url').as_string().strip().strip('/')
location = content.location
if content.type == ContentType.EXTERNAL_STORAGE:
port_ex_st = self.get_external_storage_server().get_port()
if len(var_external_url) > 0:
location = "{}:{}/{}".format(var_external_url, port_ex_st, content.location.strip('/'))
else:
location = "http://localhost:{}/{}".format(port_ex_st, content.location.strip('/'))
elif 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.value, content.location)
elif content.has_file():
location = "/{}".format(content.location)
elif content.type == ContentType.URL:
location = 'http://' + content.location if not content.location.startswith('http') else content.location
return location

View File

@ -1,208 +0,0 @@
import os
import psutil
import platform
import logging
from typing import Dict, Optional, List, Tuple, Union
from werkzeug.datastructures import FileStorage
from src.model.entity.ExternalStorage import ExternalStorage
from src.util.utils import get_yt_video_id
from src.manager.DatabaseManager import DatabaseManager
from src.manager.LangManager import LangManager
from src.manager.UserManager import UserManager
from src.manager.VariableManager import VariableManager
from src.service.ModelManager import ModelManager
from src.util.UtilFile import randomize_filename
class ExternalStorageManager(ModelManager):
TABLE_NAME = "external_storage"
TABLE_MODEL = [
"uuid CHAR(255)",
"total_size INTEGER",
"logical_name TEXT",
"mount_point TEXT",
"content_id INTEGER",
"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, variable_manager: VariableManager):
super().__init__(lang_manager, database_manager, user_manager, variable_manager)
self._db = database_manager.open(self.TABLE_NAME, self.TABLE_MODEL)
def hydrate_object(self, raw_external_storage: dict, id: int = None) -> ExternalStorage:
if id:
raw_external_storage['id'] = id
[raw_external_storage, user_tracker_edits] = self.user_manager.initialize_user_trackers(raw_external_storage)
if len(user_tracker_edits) > 0:
self._db.update_by_id(self.TABLE_NAME, raw_external_storage['id'], user_tracker_edits)
return ExternalStorage(**raw_external_storage)
def hydrate_list(self, raw_external_storages: list) -> List[ExternalStorage]:
return [self.hydrate_object(raw_external_storage) for raw_external_storage in raw_external_storages]
def get(self, id: int) -> Optional[ExternalStorage]:
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, sort: Optional[str] = None) -> List[ExternalStorage]:
return self.hydrate_list(self._db.get_by_query(self.TABLE_NAME, query=query, sort=sort))
def get_one_by(self, query) -> Optional[ExternalStorage]:
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: Optional[str] = 'created_at', ascending=False) -> List[ExternalStorage]:
return self.hydrate_list(self._db.get_all(table_name=self.TABLE_NAME, sort=sort, ascending=ascending))
def get_all_indexed(self, attribute: str = 'id', multiple=False) -> Dict[str, ExternalStorage]:
index = {}
for item in self.get_external_storages():
id = getattr(item, attribute)
if multiple:
if id not in index:
index[id] = []
index[id].append(item)
else:
index[id] = item
return index
def forget_for_user(self, user_id: int):
external_storages = self.get_by("created_by = '{}' or updated_by = '{}'".format(user_id, user_id))
edits_external_storages = self.user_manager.forget_user_for_entity(external_storages, user_id)
for external_storage_id, edits in edits_external_storages.items():
self._db.update_by_id(self.TABLE_NAME, external_storage_id, edits)
def get_external_storages(self, content_id: Optional[int] = None) -> List[ExternalStorage]:
query = " 1=1 "
if content_id:
query = "{} {}".format(query, "AND content_id = {}".format(content_id))
return self.get_by(query=query)
def pre_add(self, external_storage: Dict) -> Dict:
self.user_manager.track_user_on_create(external_storage)
self.user_manager.track_user_on_update(external_storage)
return external_storage
def pre_update(self, external_storage: Dict) -> Dict:
self.user_manager.track_user_on_update(external_storage)
return external_storage
def pre_delete(self, external_storage_id: str) -> str:
return external_storage_id
def post_add(self, external_storage_id: str) -> str:
return external_storage_id
def post_update(self, external_storage_id: str) -> str:
return external_storage_id
def post_updates(self):
pass
def post_delete(self, external_storage_id: str) -> str:
return external_storage_id
def update_form(self, id: int, logical_name: Optional[str] = None, mount_point: Optional[str] = None, content_id: Optional[int] = None, total_size: Optional[int] = None) -> ExternalStorage:
external_storage = self.get(id)
if not external_storage:
return
form = {
"total_size": total_size if total_size else external_storage.total_size,
"logical_name": logical_name if logical_name else external_storage.logical_name,
"mount_point": mount_point if mount_point else external_storage.mount_point,
"content_id": content_id if content_id else external_storage.content_id,
}
self._db.update_by_id(self.TABLE_NAME, id, self.pre_update(form))
self.post_update(id)
return self.get(id)
def add_form(self, external_storage: Union[ExternalStorage, Dict]) -> None:
form = external_storage
if not isinstance(external_storage, dict):
form = external_storage.to_dict()
del form['id']
self._db.add(self.TABLE_NAME, self.pre_add(form))
self.post_add(external_storage.id)
def add_form_raw(self, logical_name: Optional[str] = None, mount_point: Optional[str] = None, content_id: Optional[int] = None, total_size: Optional[int] = None) -> ExternalStorage:
external_storage = ExternalStorage(
logical_name=logical_name,
mount_point=mount_point,
content_id=content_id,
total_size=total_size,
)
self.add_form(external_storage)
return self.get_one_by(query="uuid = '{}'".format(external_storage.uuid))
def delete(self, id: int) -> None:
external_storage = self.get(id)
if external_storage:
self.pre_delete(id)
self._db.delete_by_id(self.TABLE_NAME, id)
self.post_delete(id)
def to_dict(self, external_storages: List[ExternalStorage]) -> List[Dict]:
return [external_storage.to_dict() for external_storage in external_storages]
@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

View File

@ -19,7 +19,7 @@ class ContentInputType(Enum):
elif value == ContentInputType.TEXT:
return True
elif value == ContentInputType.STORAGE:
return False
return True
class ContentType(Enum):

View File

@ -0,0 +1,103 @@
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:
MOUNTPOINT = Path("var/run/storage")
def __init__(self, kernel, model_store: ModelStore):
self._kernel = kernel
self._model_store = model_store
def get_directory(self):
return Path(self._kernel.get_project_dir(), self.MOUNTPOINT)
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 get_port(self) -> Optional[int]:
port = self._model_store.config().map().get('port_http_external_storage')
return int(port) if port else None
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 dir://{}:{}".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()}

View File

@ -12,7 +12,6 @@ from src.manager.LangManager import LangManager
from src.manager.DatabaseManager import DatabaseManager
from src.manager.ConfigManager import ConfigManager
from src.manager.LoggingManager import LoggingManager
from src.manager.ExternalStorageManager import ExternalStorageManager
class ModelStore:
@ -40,7 +39,6 @@ class ModelStore:
self._playlist_manager = PlaylistManager(lang_manager=self._lang_manager, database_manager=self._database_manager, user_manager=self._user_manager, variable_manager=self._variable_manager)
self._slide_manager = SlideManager(lang_manager=self._lang_manager, database_manager=self._database_manager, user_manager=self._user_manager, variable_manager=self._variable_manager)
self._content_manager = ContentManager(lang_manager=self._lang_manager, database_manager=self._database_manager, user_manager=self._user_manager, variable_manager=self._variable_manager)
self._external_storage_manager = ExternalStorageManager(lang_manager=self._lang_manager, database_manager=self._database_manager, user_manager=self._user_manager, variable_manager=self._variable_manager)
self._variable_manager.reload()
def logging(self) -> LoggingManager:
@ -49,9 +47,6 @@ class ModelStore:
def config(self) -> ConfigManager:
return self._config_manager
def external_storage(self) -> ExternalStorageManager:
return self._external_storage_manager
def variable(self) -> VariableManager:
return self._variable_manager
@ -86,7 +81,6 @@ class ModelStore:
return self._get_plugins()
def on_user_delete(self, user_id: int) -> None:
self._external_storage_manager.forget_for_user(user_id)
self._playlist_manager.forget_for_user(user_id)
self._folder_manager.forget_for_user(user_id)
self._node_player_group_manager.forget_for_user(user_id)

View File

@ -0,0 +1,25 @@
# normally we start at sdb to ignore the system hard drive but we're in rpi, sdcard is the system hard drive
KERNEL!="sd[a-z]*", GOTO="obscreen_automount_end"
ACTION=="add", PROGRAM!="/sbin/blkid %N", GOTO="obscreen_automount_end"
# import some useful filesystem info as variables
IMPORT{program}="/sbin/blkid -o udev -p %N"
# set mountpoint directory output
ENV{dir_name}="/home/pi/obscreen/var/run/storage"
ACTION=="add", RUN+="/bin/mkdir -p '%E{dir_name}'"
# global mount options
ACTION=="add", ENV{mount_options}="relatime"
# filesystem-specific mount options (777/666 dir/file perms for ntfs/vfat)
ACTION=="add", ENV{ID_FS_TYPE}=="vfat|ntfs", ENV{mount_options}="$env{mount_options},gid=100,dmask=000,fmask=111,utf8"
# automount all other filesystems
ACTION=="add", ENV{ID_FS_TYPE}!="ntfs", RUN+="/usr/bin/systemd-mount --no-block --automount=yes --collect -o %E{mount_options} /dev/%k '%E{dir_name}'"
# clean up after device removal
ACTION=="remove", ENV{dir_name}!="", RUN+="/usr/bin/systemd-umount '%E{dir_name}'"
# exit
LABEL="obscreen_automount_end"

View File

@ -59,11 +59,10 @@ sleep 3
# Update and install necessary packages
apt update
apt install -y xinit xserver-xorg chromium-browser unclutter pulseaudio
apt install -y xinit xserver-xorg chromium-browser unclutter pulseaudio exfat-fuse ntfs-3g
# Add user to tty and video groups
usermod -aG tty $OWNER
usermod -aG video $OWNER
# Add user to tty, video and plugdev groups
usermod -aG tty,video,plugdev $OWNER
# Configure Xwrapper
touch /etc/X11/Xwrapper.config
@ -73,6 +72,12 @@ grep -qxF "needs_root_rights=yes" /etc/X11/Xwrapper.config || echo "needs_root_r
# Create the systemd service to start Chromium in kiosk mode
curl https://raw.githubusercontent.com/jr-k/obscreen/master/system/obscreen-player.service | sed "s#/home/pi#$WORKING_DIR#g" | sed "s#=pi#=$OWNER#g" | tee /etc/systemd/system/obscreen-player.service
# Configure external storage automount
curl https://raw.githubusercontent.com/jr-k/obscreen/master/system/10-obscreen-media-automount.rules | sed "s#/home/pi#$WORKING_DIR#g" | tee /etc/udev/rules.d/10-obscreen-media-automount.rules
udevadm control --reload-rules
systemctl restart udev
udevadm trigger
# Reload systemd, enable and start the service
systemctl daemon-reload
systemctl enable obscreen-player.service

View File

@ -11,7 +11,6 @@
{% block add_js %}
<script>
var external_storages = {{ json_dumps(external_storages) | safe }};
var l = $.extend(l, {
'js_common_http_error_occured': '{{ l.common_http_error_occured }}',
'js_common_http_error_413': '{{ l.common_http_error_413 }}'

View File

@ -27,32 +27,37 @@
</div>
</div>
<div class="form-group object-input">
<label for="" class="object-label">{{ l.slideshow_content_form_label_object }}</label>
<div class="widget">
<div class="object-holder hidden">
<input type="text" name="object" data-input-type="text" class="content-object-input" />
<div class="from-group-condition hidden">
<div class="form-group">
<label for="" class="object-label"></label>
<div class="widget">
<input type="text" name="object" data-input-type="text" class="content-object-input"/>
</div>
</div>
</div>
<label for="content-add-object-input-upload" class="btn-upload hidden object-holder">
<input type="file" name="object" data-input-type="upload" class="content-object-input" disabled="disabled" id="content-add-object-input-upload" />
<span class="btn btn-neutral normal">
<i class="fa fa-file-import"></i>
{{ l.slideshow_content_form_button_upload }}
</span>
<input type="text" value="{{ l.slideshow_content_form_button_upload_choosen }}" disabled="disabled" class="disabled" />
</label>
<div class="object-holder hidden">
<select name="storage" disabled="disabled">
{% for key, value in external_storages.items() %}
<option value="{{ key }}">{{ value }}</option>
{% endfor %}
</select>
<div class="form-group">
<label for="content-add-object-input-storage-target-path">{{ l.enum_content_type_external_storage_target_path_label }}</label>
<input type="text" name="object" data-input-type="storage" class="content-object-input" disabled="disabled" id="content-add-object-input-storage-target-path" />
<div class="from-group-condition hidden">
<div class="form-group">
<label for="" class="object-label"></label>
<label class="btn-upload" for="content-add-object-input-upload">
<div class="widget">
<input type="file" name="object" data-input-type="upload" class="content-object-input" disabled="disabled" id="content-add-object-input-upload"/>
<span class="btn btn-neutral normal">
<i class="fa fa-file-import"></i>
{{ l.slideshow_content_form_button_upload }}
</span>
<input type="text" value="{{ l.slideshow_content_form_button_upload_choosen }}" disabled="disabled" class="disabled"/>
</div>
</label>
</div>
</div>
<div class="from-group-condition hidden">
<div class="form-group">
<label for="" class="object-label"></label>
<div class="widget">
<input type="text" name="object" data-input-type="storage" class="content-object-input" disabled="disabled" />
</div>
</div>
</div>