ok
This commit is contained in:
parent
046666b524
commit
b7231a35a5
1
.gitignore
vendored
1
.gitignore
vendored
@ -19,3 +19,4 @@ var/run/*
|
|||||||
.env
|
.env
|
||||||
venv/
|
venv/
|
||||||
node_modules
|
node_modules
|
||||||
|
tmp.py
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -9,14 +9,15 @@ jQuery(document).ready(function ($) {
|
|||||||
}).data('input');
|
}).data('input');
|
||||||
|
|
||||||
$form.find('.content-object-input').each(function() {
|
$form.find('.content-object-input').each(function() {
|
||||||
const active = $(this).attr('data-input-type') === inputType;
|
const $input = $(this);
|
||||||
|
const active = $input.attr('data-input-type') === inputType;
|
||||||
if ($(this).is('input[type=file]')) {
|
const $holder = $input.parents('.object-holder:eq(0)');
|
||||||
$(this).prop('disabled', !active).prop('required', active);
|
$holder.find('input, select, textarea').prop('disabled', !active).prop('required', active).toggleClass('hidden', !active);
|
||||||
$(this).parents('label:eq(0)').toggleClass('hidden', !active);
|
$holder.toggleClass('hidden', !active);
|
||||||
} else {
|
console.log(active)
|
||||||
$(this).prop('disabled', !active).prop('required', active).toggleClass('hidden', !active);
|
console.log($input)
|
||||||
}
|
if (active)
|
||||||
|
console.log($holder)
|
||||||
});
|
});
|
||||||
|
|
||||||
const optionAttributes = $selectedOption.get(0).attributes;
|
const optionAttributes = $selectedOption.get(0).attributes;
|
||||||
|
|||||||
@ -35,6 +35,28 @@ form {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
margin-bottom: 20px;
|
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 {
|
label {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@ -226,6 +226,7 @@
|
|||||||
"basic_month_10": "October",
|
"basic_month_10": "October",
|
||||||
"basic_month_11": "November",
|
"basic_month_11": "November",
|
||||||
"basic_month_12": "December",
|
"basic_month_12": "December",
|
||||||
|
"common_bad_directory_path": "Directory does not exist in the specified path",
|
||||||
"common_bad_file_type": "Bad file type uploaded",
|
"common_bad_file_type": "Bad file type uploaded",
|
||||||
"common_restart_needed": "Please restart obscreen studio (or restart the device) for the changes to take effect",
|
"common_restart_needed": "Please restart obscreen studio (or restart the device) for the changes to take effect",
|
||||||
"common_pick_element": "Pick an element",
|
"common_pick_element": "Pick an element",
|
||||||
@ -282,6 +283,9 @@
|
|||||||
"enum_application_language_french": "French",
|
"enum_application_language_french": "French",
|
||||||
"enum_application_language_italian": "Italian",
|
"enum_application_language_italian": "Italian",
|
||||||
"enum_application_language_spanish": "Spanish",
|
"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_url": "URL",
|
"enum_content_type_url": "URL",
|
||||||
"enum_content_type_video": "Video",
|
"enum_content_type_video": "Video",
|
||||||
"enum_content_type_picture": "Picture",
|
"enum_content_type_picture": "Picture",
|
||||||
|
|||||||
@ -227,6 +227,7 @@
|
|||||||
"basic_month_10": "Octubre",
|
"basic_month_10": "Octubre",
|
||||||
"basic_month_11": "Noviembre",
|
"basic_month_11": "Noviembre",
|
||||||
"basic_month_12": "Diciembre",
|
"basic_month_12": "Diciembre",
|
||||||
|
"common_bad_directory_path": "El directorio no existe en la ruta especificada",
|
||||||
"common_bad_file_type": "Tipo de archivo incorrecto cargado",
|
"common_bad_file_type": "Tipo de archivo incorrecto cargado",
|
||||||
"common_restart_needed": "Reinicie obscreen studio (o reinicie el dispositivo) para que los cambios surtan efecto",
|
"common_restart_needed": "Reinicie obscreen studio (o reinicie el dispositivo) para que los cambios surtan efecto",
|
||||||
"common_pick_element": "Elige un elemento",
|
"common_pick_element": "Elige un elemento",
|
||||||
@ -283,6 +284,9 @@
|
|||||||
"enum_application_language_french": "Francés",
|
"enum_application_language_french": "Francés",
|
||||||
"enum_application_language_italian": "Italiano",
|
"enum_application_language_italian": "Italiano",
|
||||||
"enum_application_language_spanish": "Español",
|
"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_url": "URL",
|
"enum_content_type_url": "URL",
|
||||||
"enum_content_type_video": "Video",
|
"enum_content_type_video": "Video",
|
||||||
"enum_content_type_picture": "Imagen",
|
"enum_content_type_picture": "Imagen",
|
||||||
|
|||||||
@ -228,6 +228,7 @@
|
|||||||
"basic_month_10": "Octobre",
|
"basic_month_10": "Octobre",
|
||||||
"basic_month_11": "Novembre",
|
"basic_month_11": "Novembre",
|
||||||
"basic_month_12": "Décembre",
|
"basic_month_12": "Décembre",
|
||||||
|
"common_bad_directory_path": "Le dossier n'existe pas dans le chemin indiqué",
|
||||||
"common_bad_file_type": "Type de fichier uploadé incorrect",
|
"common_bad_file_type": "Type de fichier uploadé incorrect",
|
||||||
"common_restart_needed": "Veuillez redémarrer obscreen studio (ou redémarrer l'appareil) pour que les changements soient pris en compte",
|
"common_restart_needed": "Veuillez redémarrer obscreen studio (ou redémarrer l'appareil) pour que les changements soient pris en compte",
|
||||||
"common_pick_element": "Choisissez un élément",
|
"common_pick_element": "Choisissez un élément",
|
||||||
@ -284,6 +285,9 @@
|
|||||||
"enum_application_language_french": "Français",
|
"enum_application_language_french": "Français",
|
||||||
"enum_application_language_italian": "Italien",
|
"enum_application_language_italian": "Italien",
|
||||||
"enum_application_language_spanish": "Espagnol",
|
"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_url": "URL",
|
"enum_content_type_url": "URL",
|
||||||
"enum_content_type_video": "Vidéo",
|
"enum_content_type_video": "Vidéo",
|
||||||
"enum_content_type_picture": "Image",
|
"enum_content_type_picture": "Image",
|
||||||
|
|||||||
@ -227,6 +227,7 @@
|
|||||||
"basic_month_10": "Ottobre",
|
"basic_month_10": "Ottobre",
|
||||||
"basic_month_11": "Novembre",
|
"basic_month_11": "Novembre",
|
||||||
"basic_month_12": "Dicembre",
|
"basic_month_12": "Dicembre",
|
||||||
|
"common_bad_directory_path": "La directory non esiste nel percorso specificato",
|
||||||
"common_bad_file_type": "Tipo di file caricato non valido",
|
"common_bad_file_type": "Tipo di file caricato non valido",
|
||||||
"common_restart_needed": "Riavvia obscreen studio (o riavvia il dispositivo) affinché le modifiche abbiano effetto",
|
"common_restart_needed": "Riavvia obscreen studio (o riavvia il dispositivo) affinché le modifiche abbiano effetto",
|
||||||
"common_pick_element": "Scegli un elemento",
|
"common_pick_element": "Scegli un elemento",
|
||||||
@ -283,6 +284,9 @@
|
|||||||
"enum_application_language_french": "Francese",
|
"enum_application_language_french": "Francese",
|
||||||
"enum_application_language_italian": "Italiano",
|
"enum_application_language_italian": "Italiano",
|
||||||
"enum_application_language_spanish": "Spagnolo",
|
"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_url": "URL",
|
"enum_content_type_url": "URL",
|
||||||
"enum_content_type_video": "Video",
|
"enum_content_type_video": "Video",
|
||||||
"enum_content_type_picture": "Immagine",
|
"enum_content_type_picture": "Immagine",
|
||||||
|
|||||||
@ -48,6 +48,7 @@ class ContentController(ObController):
|
|||||||
self._model_store.variable().update_by_name('last_pillmenu_slideshow', 'slideshow_content_list')
|
self._model_store.variable().update_by_name('last_pillmenu_slideshow', 'slideshow_content_list')
|
||||||
working_folder_path, working_folder = self.get_working_folder()
|
working_folder_path, working_folder = self.get_working_folder()
|
||||||
slides_with_content = self._model_store.slide().get_all_indexed(attribute='content_id', multiple=True)
|
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()
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'slideshow/contents/list.jinja.html',
|
'slideshow/contents/list.jinja.html',
|
||||||
@ -59,6 +60,11 @@ class ContentController(ObController):
|
|||||||
working_folder_children=self._model_store.folder().get_children(folder=working_folder, entity=FolderEntity.CONTENT, sort='created_at', ascending=False),
|
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_content_type=ContentType,
|
||||||
enum_folder_entity=FolderEntity,
|
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},
|
||||||
)
|
)
|
||||||
|
|
||||||
def slideshow_content_add(self):
|
def slideshow_content_add(self):
|
||||||
@ -67,12 +73,21 @@ class ContentController(ObController):
|
|||||||
"path": working_folder_path,
|
"path": working_folder_path,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
content = self._model_store.content().add_form_raw(
|
||||||
name=request.form['name'],
|
name=request.form['name'],
|
||||||
type=str_to_enum(request.form['type'], ContentType),
|
type=str_to_enum(request.form['type'], ContentType),
|
||||||
request_files=request.files,
|
request_files=request.files,
|
||||||
upload_dir=self._app.config['UPLOAD_FOLDER'],
|
upload_dir=self._app.config['UPLOAD_FOLDER'],
|
||||||
location=request.form['object'] if 'object' in request.form else None,
|
location=location,
|
||||||
folder_id=working_folder.id if working_folder else None
|
folder_id=working_folder.id if working_folder else None
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -87,7 +102,7 @@ class ContentController(ObController):
|
|||||||
for key in request.files:
|
for key in request.files:
|
||||||
files = request.files.getlist(key)
|
files = request.files.getlist(key)
|
||||||
for file in files:
|
for file in files:
|
||||||
type = ContentType.guess_content_type_file(file)
|
type = ContentType.guess_content_type_file(file.filename)
|
||||||
name = file.filename.rsplit('.', 1)[0]
|
name = file.filename.rsplit('.', 1)[0]
|
||||||
|
|
||||||
if type:
|
if type:
|
||||||
@ -240,7 +255,9 @@ class ContentController(ObController):
|
|||||||
var_external_url = self._model_store.variable().get_one_by_name('external_url')
|
var_external_url = self._model_store.variable().get_one_by_name('external_url')
|
||||||
location = content.location
|
location = content.location
|
||||||
|
|
||||||
if content.type == ContentType.YOUTUBE:
|
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)
|
location = "https://www.youtube.com/watch?v={}".format(content.location)
|
||||||
elif len(var_external_url.as_string().strip()) > 0 and content.has_file():
|
elif len(var_external_url.as_string().strip()) > 0 and content.has_file():
|
||||||
location = "{}/{}".format(var_external_url.value, content.location)
|
location = "{}/{}".format(var_external_url.value, content.location)
|
||||||
|
|||||||
@ -2,10 +2,12 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
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
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from src.model.entity.Slide import Slide
|
from src.model.entity.Slide import Slide
|
||||||
|
from src.model.enum.ContentType import ContentType
|
||||||
from src.exceptions.NoFallbackPlaylistException import NoFallbackPlaylistException
|
from src.exceptions.NoFallbackPlaylistException import NoFallbackPlaylistException
|
||||||
from src.service.ModelStore import ModelStore
|
from src.service.ModelStore import ModelStore
|
||||||
from src.interface.ObController import ObController
|
from src.interface.ObController import ObController
|
||||||
@ -123,7 +125,9 @@ class PlayerController(ObController):
|
|||||||
playlist_notifications = []
|
playlist_notifications = []
|
||||||
|
|
||||||
for slide in slides:
|
for slide in slides:
|
||||||
if slide['content_id']:
|
if not slide['content_id']:
|
||||||
|
continue
|
||||||
|
|
||||||
if int(slide['content_id']) not in contents:
|
if int(slide['content_id']) not in contents:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -131,31 +135,18 @@ class PlayerController(ObController):
|
|||||||
slide['name'] = content['name']
|
slide['name'] = content['name']
|
||||||
slide['location'] = content['location']
|
slide['location'] = content['location']
|
||||||
slide['type'] = content['type']
|
slide['type'] = content['type']
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
has_valid_start_date = 'cron_schedule' in slide and slide['cron_schedule'] and get_safe_cron_descriptor(slide['cron_schedule']) and is_cron_calendar_moment(slide['cron_schedule'])
|
if slide['type'] == ContentType.EXTERNAL_STORAGE.value:
|
||||||
has_valid_end_date = 'cron_schedule_end' in slide and slide['cron_schedule_end'] and get_safe_cron_descriptor(slide['cron_schedule_end']) and is_cron_calendar_moment(slide['cron_schedule_end'])
|
mount_point_dir = Path(slide['location'])
|
||||||
|
if mount_point_dir.is_dir():
|
||||||
if slide['is_notification']:
|
for file in mount_point_dir.iterdir():
|
||||||
if has_valid_start_date:
|
if file.is_file() and not file.stem.startswith('.'):
|
||||||
playlist_notifications.append(slide)
|
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)
|
||||||
else:
|
else:
|
||||||
logging.warn('Slide \'{}\' is a notification but start date is invalid'.format(slide['name']))
|
self._feed_playlist(playlist_loop, playlist_notifications, slide)
|
||||||
else:
|
|
||||||
if has_valid_start_date:
|
|
||||||
start_date = get_cron_date_time(slide['cron_schedule'], object=True)
|
|
||||||
if datetime.now() <= start_date:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if has_valid_end_date:
|
|
||||||
end_date = get_cron_date_time(slide['cron_schedule_end'], object=True)
|
|
||||||
if datetime.now() >= end_date:
|
|
||||||
continue
|
|
||||||
|
|
||||||
playlist_loop.append(slide)
|
|
||||||
else:
|
|
||||||
playlist_loop.append(slide)
|
|
||||||
|
|
||||||
playlists = {
|
playlists = {
|
||||||
'playlist_id': playlist.id if playlist else None,
|
'playlist_id': playlist.id if playlist else None,
|
||||||
@ -167,3 +158,24 @@ class PlayerController(ObController):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return playlists
|
return playlists
|
||||||
|
|
||||||
|
def _feed_playlist(self, loop: List, notifications: List, slide: Dict) -> None:
|
||||||
|
has_valid_start_date = 'cron_schedule' in slide and slide['cron_schedule'] and get_safe_cron_descriptor(slide['cron_schedule']) and is_cron_calendar_moment(slide['cron_schedule'])
|
||||||
|
has_valid_end_date = 'cron_schedule_end' in slide and slide['cron_schedule_end'] and get_safe_cron_descriptor(slide['cron_schedule_end']) and is_cron_calendar_moment(slide['cron_schedule_end'])
|
||||||
|
|
||||||
|
if slide['is_notification']:
|
||||||
|
if has_valid_start_date:
|
||||||
|
return notifications.append(slide)
|
||||||
|
return logging.warn('Slide \'{}\' is a notification but start date is invalid'.format(slide['name']))
|
||||||
|
|
||||||
|
if has_valid_start_date:
|
||||||
|
start_date = get_cron_date_time(slide['cron_schedule'], object=True)
|
||||||
|
if datetime.now() <= start_date:
|
||||||
|
return
|
||||||
|
|
||||||
|
if has_valid_end_date:
|
||||||
|
end_date = get_cron_date_time(slide['cron_schedule_end'], object=True)
|
||||||
|
if datetime.now() >= end_date:
|
||||||
|
return
|
||||||
|
|
||||||
|
loop.append(slide)
|
||||||
|
|||||||
@ -177,7 +177,7 @@ class ContentManager(ModelManager):
|
|||||||
if not object or object.filename == '':
|
if not object or object.filename == '':
|
||||||
return None
|
return None
|
||||||
|
|
||||||
guessed_type = ContentType.guess_content_type_file(object)
|
guessed_type = ContentType.guess_content_type_file(object.filename)
|
||||||
|
|
||||||
if not guessed_type or guessed_type != type:
|
if not guessed_type or guessed_type != type:
|
||||||
return None
|
return None
|
||||||
|
|||||||
208
src/manager/ExternalStorageManager.py
Normal file
208
src/manager/ExternalStorageManager.py
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
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
|
||||||
|
|
||||||
141
src/model/entity/ExternalStorage.py
Normal file
141
src/model/entity/ExternalStorage.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import json
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
import math
|
||||||
|
|
||||||
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
from src.model.enum.FolderEntity import FolderEntity
|
||||||
|
from src.util.utils import str_to_enum
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalStorage:
|
||||||
|
|
||||||
|
def __init__(self, uuid: str = '', total_size: int = 0, logical_name: str = '', mount_point: str = '', content_id: Optional[int] = None, id: Optional[int] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None):
|
||||||
|
self._uuid = uuid if uuid else self.generate_and_set_uuid()
|
||||||
|
self._id = id if id else None
|
||||||
|
self._total_size = total_size
|
||||||
|
self._logical_name = logical_name
|
||||||
|
self._mount_point = mount_point
|
||||||
|
self._content_id = content_id
|
||||||
|
self._created_by = created_by if created_by else None
|
||||||
|
self._updated_by = updated_by if updated_by else None
|
||||||
|
self._created_at = int(created_at if created_at else time.time())
|
||||||
|
self._updated_at = int(updated_at if updated_at else time.time())
|
||||||
|
|
||||||
|
def generate_and_set_uuid(self) -> str:
|
||||||
|
self._uuid = str(uuid.uuid4())
|
||||||
|
|
||||||
|
return self._uuid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> Optional[int]:
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def uuid(self) -> str:
|
||||||
|
return self._uuid
|
||||||
|
|
||||||
|
@uuid.setter
|
||||||
|
def uuid(self, value: str):
|
||||||
|
self._uuid = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def content_id(self) -> Optional[int]:
|
||||||
|
return self._content_id
|
||||||
|
|
||||||
|
@content_id.setter
|
||||||
|
def content_id(self, value: Optional[int]):
|
||||||
|
self._content_id = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_size(self) -> int:
|
||||||
|
return self._total_size
|
||||||
|
|
||||||
|
@total_size.setter
|
||||||
|
def total_size(self, value: int):
|
||||||
|
self._total_size = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def logical_name(self) -> str:
|
||||||
|
return self._logical_name
|
||||||
|
|
||||||
|
@logical_name.setter
|
||||||
|
def logical_name(self, value: str):
|
||||||
|
self._logical_name = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mount_point(self) -> str:
|
||||||
|
return self._mount_point
|
||||||
|
|
||||||
|
@mount_point.setter
|
||||||
|
def mount_point(self, value: str):
|
||||||
|
self._mount_point = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def created_by(self) -> str:
|
||||||
|
return self._created_by
|
||||||
|
|
||||||
|
@created_by.setter
|
||||||
|
def created_by(self, value: str):
|
||||||
|
self._created_by = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def updated_by(self) -> str:
|
||||||
|
return self._updated_by
|
||||||
|
|
||||||
|
@updated_by.setter
|
||||||
|
def updated_by(self, value: str):
|
||||||
|
self._updated_by = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def created_at(self) -> int:
|
||||||
|
return self._created_at
|
||||||
|
|
||||||
|
@created_at.setter
|
||||||
|
def created_at(self, value: int):
|
||||||
|
self._created_at = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def updated_at(self) -> int:
|
||||||
|
return self._updated_at
|
||||||
|
|
||||||
|
@updated_at.setter
|
||||||
|
def updated_at(self, value: int):
|
||||||
|
self._updated_at = value
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"ExternalStorage(" \
|
||||||
|
f"id='{self.id}',\n" \
|
||||||
|
f"uuid='{self.uuid}',\n" \
|
||||||
|
f"total_size='{self.total_size}',\n" \
|
||||||
|
f"logical_name='{self.logical_name}',\n" \
|
||||||
|
f"mount_point='{self.mount_point}',\n" \
|
||||||
|
f"content_id='{self.content_id}',\n" \
|
||||||
|
f"created_by='{self.created_by}',\n" \
|
||||||
|
f"updated_by='{self.updated_by}',\n" \
|
||||||
|
f"created_at='{self.created_at}',\n" \
|
||||||
|
f"updated_at='{self.updated_at}',\n" \
|
||||||
|
f")"
|
||||||
|
|
||||||
|
def to_json(self) -> str:
|
||||||
|
return json.dumps(self.to_dict())
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"uuid": self.uuid,
|
||||||
|
"total_size": self.total_size,
|
||||||
|
"logical_name": self.logical_name,
|
||||||
|
"mount_point": self.mount_point,
|
||||||
|
"content_id": self.content_id,
|
||||||
|
"created_by": self.created_by,
|
||||||
|
"updated_by": self.updated_by,
|
||||||
|
"created_at": self.created_at,
|
||||||
|
"updated_at": self.updated_at,
|
||||||
|
}
|
||||||
|
|
||||||
|
def total_size_in_gigabytes(self) -> str:
|
||||||
|
return f"{self.total_size / (1024 ** 3):.2f}"
|
||||||
|
|
||||||
|
|
||||||
@ -10,6 +10,7 @@ class ContentInputType(Enum):
|
|||||||
|
|
||||||
UPLOAD = 'upload'
|
UPLOAD = 'upload'
|
||||||
TEXT = 'text'
|
TEXT = 'text'
|
||||||
|
STORAGE = 'storage'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_editable(value: Enum) -> bool:
|
def is_editable(value: Enum) -> bool:
|
||||||
@ -17,18 +18,21 @@ class ContentInputType(Enum):
|
|||||||
return False
|
return False
|
||||||
elif value == ContentInputType.TEXT:
|
elif value == ContentInputType.TEXT:
|
||||||
return True
|
return True
|
||||||
|
elif value == ContentInputType.STORAGE:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class ContentType(Enum):
|
class ContentType(Enum):
|
||||||
|
|
||||||
|
EXTERNAL_STORAGE = 'external_storage'
|
||||||
PICTURE = 'picture'
|
PICTURE = 'picture'
|
||||||
URL = 'url'
|
URL = 'url'
|
||||||
YOUTUBE = 'youtube'
|
YOUTUBE = 'youtube'
|
||||||
VIDEO = 'video'
|
VIDEO = 'video'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def guess_content_type_file(file):
|
def guess_content_type_file(filename: str):
|
||||||
mime_type, _ = mimetypes.guess_type(file.filename)
|
mime_type, _ = mimetypes.guess_type(filename)
|
||||||
|
|
||||||
if mime_type in [
|
if mime_type in [
|
||||||
'image/gif',
|
'image/gif',
|
||||||
@ -55,6 +59,8 @@ class ContentType(Enum):
|
|||||||
return ContentInputType.TEXT
|
return ContentInputType.TEXT
|
||||||
elif value == ContentType.URL:
|
elif value == ContentType.URL:
|
||||||
return ContentInputType.TEXT
|
return ContentInputType.TEXT
|
||||||
|
elif value == ContentType.EXTERNAL_STORAGE:
|
||||||
|
return ContentInputType.STORAGE
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_fa_icon(value: Union[Enum, str]) -> str:
|
def get_fa_icon(value: Union[Enum, str]) -> str:
|
||||||
@ -69,6 +75,8 @@ class ContentType(Enum):
|
|||||||
return 'fa-brands fa-youtube'
|
return 'fa-brands fa-youtube'
|
||||||
elif value == ContentType.URL:
|
elif value == ContentType.URL:
|
||||||
return 'fa-link'
|
return 'fa-link'
|
||||||
|
elif value == ContentType.EXTERNAL_STORAGE:
|
||||||
|
return 'fa-brands fa-usb'
|
||||||
|
|
||||||
return 'fa-file'
|
return 'fa-file'
|
||||||
|
|
||||||
@ -85,5 +93,7 @@ class ContentType(Enum):
|
|||||||
return 'youtube'
|
return 'youtube'
|
||||||
elif value == ContentType.URL:
|
elif value == ContentType.URL:
|
||||||
return 'danger'
|
return 'danger'
|
||||||
|
elif value == ContentType.EXTERNAL_STORAGE:
|
||||||
|
return 'other'
|
||||||
|
|
||||||
return 'neutral'
|
return 'neutral'
|
||||||
|
|||||||
@ -12,6 +12,7 @@ from src.manager.LangManager import LangManager
|
|||||||
from src.manager.DatabaseManager import DatabaseManager
|
from src.manager.DatabaseManager import DatabaseManager
|
||||||
from src.manager.ConfigManager import ConfigManager
|
from src.manager.ConfigManager import ConfigManager
|
||||||
from src.manager.LoggingManager import LoggingManager
|
from src.manager.LoggingManager import LoggingManager
|
||||||
|
from src.manager.ExternalStorageManager import ExternalStorageManager
|
||||||
|
|
||||||
|
|
||||||
class ModelStore:
|
class ModelStore:
|
||||||
@ -39,6 +40,7 @@ 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._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._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._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()
|
self._variable_manager.reload()
|
||||||
|
|
||||||
def logging(self) -> LoggingManager:
|
def logging(self) -> LoggingManager:
|
||||||
@ -47,6 +49,9 @@ class ModelStore:
|
|||||||
def config(self) -> ConfigManager:
|
def config(self) -> ConfigManager:
|
||||||
return self._config_manager
|
return self._config_manager
|
||||||
|
|
||||||
|
def external_storage(self) -> ExternalStorageManager:
|
||||||
|
return self._external_storage_manager
|
||||||
|
|
||||||
def variable(self) -> VariableManager:
|
def variable(self) -> VariableManager:
|
||||||
return self._variable_manager
|
return self._variable_manager
|
||||||
|
|
||||||
@ -81,6 +86,7 @@ class ModelStore:
|
|||||||
return self._get_plugins()
|
return self._get_plugins()
|
||||||
|
|
||||||
def on_user_delete(self, user_id: int) -> None:
|
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._playlist_manager.forget_for_user(user_id)
|
||||||
self._folder_manager.forget_for_user(user_id)
|
self._folder_manager.forget_for_user(user_id)
|
||||||
self._node_player_group_manager.forget_for_user(user_id)
|
self._node_player_group_manager.forget_for_user(user_id)
|
||||||
|
|||||||
@ -327,7 +327,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadPicture = function(element, callbackReady, item) {
|
const loadPicture = function(element, callbackReady, item) {
|
||||||
element.innerHTML = `<img src="/${item.location}" alt="" />`;
|
const hasScheme = item.location.indexOf('://') >= 0;
|
||||||
|
element.innerHTML = `<img src="${hasScheme ? item.location : ('/' + item.location)}" alt="" />`;
|
||||||
callbackReady(function() {});
|
callbackReady(function() {});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
{% block add_js %}
|
{% block add_js %}
|
||||||
<script>
|
<script>
|
||||||
|
var external_storages = {{ json_dumps(external_storages) | safe }};
|
||||||
var l = $.extend(l, {
|
var l = $.extend(l, {
|
||||||
'js_common_http_error_occured': '{{ l.common_http_error_occured }}',
|
'js_common_http_error_occured': '{{ l.common_http_error_occured }}',
|
||||||
'js_common_http_error_413': '{{ l.common_http_error_413 }}'
|
'js_common_http_error_413': '{{ l.common_http_error_413 }}'
|
||||||
|
|||||||
@ -30,16 +30,30 @@
|
|||||||
<div class="form-group object-input">
|
<div class="form-group object-input">
|
||||||
<label for="" class="object-label">{{ l.slideshow_content_form_label_object }}</label>
|
<label for="" class="object-label">{{ l.slideshow_content_form_label_object }}</label>
|
||||||
<div class="widget">
|
<div class="widget">
|
||||||
|
<div class="object-holder hidden">
|
||||||
<input type="text" name="object" data-input-type="text" class="content-object-input" />
|
<input type="text" name="object" data-input-type="text" class="content-object-input" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<label for="content-add-object-input-upload" class="btn-upload hidden">
|
<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"/>
|
<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">
|
<span class="btn btn-neutral normal">
|
||||||
<i class="fa fa-file-import"></i>
|
<i class="fa fa-file-import"></i>
|
||||||
{{ l.slideshow_content_form_button_upload }}
|
{{ l.slideshow_content_form_button_upload }}
|
||||||
</span>
|
</span>
|
||||||
<input type="text" value="{{ l.slideshow_content_form_button_upload_choosen }}" disabled="disabled" class="disabled" />
|
<input type="text" value="{{ l.slideshow_content_form_button_upload_choosen }}" disabled="disabled" class="disabled" />
|
||||||
</label>
|
</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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user