Merge pull request #71 from jr-k/develop

Release v1.18
This commit is contained in:
JRK 2024-06-01 11:23:34 +02:00 committed by GitHub
commit f82156b389
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 164 additions and 136 deletions

View File

@ -118,7 +118,8 @@
"settings_variable_desc_edition_auth_enabled": "Default user credentials will be admin/admin",
"settings_variable_desc_external_url": "External url (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_default_slide_duration": "Intro slide duration (in seconds)",
"settings_variable_desc_default_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_playlist_default_time_sync": "Sync slides across players for default playlist",

View File

@ -119,6 +119,7 @@
"settings_variable_desc_external_url": "URL 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_default_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_playlist_default_time_sync": "Synchroniser les slides des lecteurs pour la liste de lecture par défaut",

View File

@ -5,7 +5,7 @@ from src.model.entity.Variable import Variable
from src.model.enum.VariableType import VariableType
from src.model.enum.HookType import HookType
from src.model.hook.HookRegistration import HookRegistration
from src.utils import am_i_in_docker
from src.util.utils import am_i_in_docker
class GitUpdater(ObPlugin):

View File

@ -4,7 +4,7 @@ import platform
from flask import Flask, redirect, url_for
from src.interface.ObController import ObController
from src.utils import run_system_command, sudo_run_system_command, get_working_directory, am_i_in_docker
from src.util.utils import run_system_command, sudo_run_system_command, get_working_directory, am_i_in_docker
from src.Application import Application

View File

@ -5,7 +5,8 @@ from flask import Flask, render_template, redirect, request, url_for, send_from_
from src.service.ModelStore import ModelStore
from src.interface.ObController import ObController
from src.utils import get_ip_address, get_safe_cron_descriptor
from src.util.utils import get_safe_cron_descriptor
from src.util.UtilNetwork import get_ip_address
from src.model.enum.AnimationSpeed import animation_speed_duration
@ -63,10 +64,10 @@ class PlayerController(ObController):
)
def player_default(self):
ipaddr = get_ip_address()
return render_template(
'player/default.jinja.html',
ipaddr=ipaddr if ipaddr else self._model_store.lang().map().get('common_unknown_ipaddr')
ipaddr=get_ip_address(),
time_with_seconds=self._model_store.variable().get_one_by_name('default_slide_time_with_seconds')
)
def player_playlist(self, playlist_slug_or_id: str = ''):

View File

@ -8,7 +8,8 @@ from src.service.ModelStore import ModelStore
from src.model.entity.Slide import Slide
from src.model.enum.SlideType import SlideType
from src.interface.ObController import ObController
from src.utils import str_to_enum, get_optional_string, randomize_filename
from src.util.utils import str_to_enum, get_optional_string
from src.util.UtilFile import randomize_filename
class SlideshowController(ObController):

View File

@ -11,7 +11,8 @@ from src.manager.ConfigManager import ConfigManager
from src.service.ModelStore import ModelStore
from src.interface.ObController import ObController
from src.utils import get_ip_address, am_i_in_docker
from src.util.utils import am_i_in_docker
from src.util.UtilNetwork import get_ip_address
from src.service.Sysinfo import get_all_sysinfo
@ -21,6 +22,7 @@ class SysinfoController(ObController):
self._app.add_url_rule('/sysinfo', 'sysinfo_attribute_list', self._auth(self.sysinfo), methods=['GET'])
self._app.add_url_rule('/sysinfo/restart', 'sysinfo_restart', self.sysinfo_restart, methods=['GET', 'POST'])
self._app.add_url_rule('/sysinfo/restart/needed', 'sysinfo_restart_needed', self._auth(self.sysinfo_restart_needed), methods=['GET'])
self._app.add_url_rule('/sysinfo/get/ipaddr', 'sysinfo_get_ipaddr', self._auth(self.sysinfo_get_ipaddr), methods=['GET'])
def sysinfo(self):
return render_template(
@ -70,3 +72,7 @@ class SysinfoController(ObController):
pass
except subprocess.CalledProcessError:
pass
def sysinfo_get_ipaddr(self):
ipaddr = get_ip_address()
return ipaddr if ipaddr else ''

View File

@ -5,7 +5,7 @@ import logging
import argparse
from src.manager.VariableManager import VariableManager
from src.utils import am_i_in_docker
from src.util.utils import am_i_in_docker
from dotenv import load_dotenv
load_dotenv()

View File

@ -5,7 +5,7 @@ import sqlite3
import logging
from sqlite3 import Cursor
from src.utils import wrap_if, is_wrapped_by
from src.util.utils import wrap_if, is_wrapped_by
from typing import Optional, Dict

View File

@ -4,7 +4,7 @@ import logging
from typing import Union, Dict
from enum import Enum
from src.utils import camel_to_snake
from src.util.utils import camel_to_snake
class LangManager:

View File

@ -3,7 +3,7 @@ import os
from typing import Dict, Optional, List, Tuple, Union
from src.model.entity.Playlist import Playlist
from src.utils import get_optional_string, get_yt_video_id, slugify
from src.util.utils import get_optional_string, get_yt_video_id, slugify
from src.manager.DatabaseManager import DatabaseManager
from src.manager.LangManager import LangManager
from src.manager.UserManager import UserManager

View File

@ -5,7 +5,7 @@ from typing import Dict, Optional, List, Tuple, Union
from src.model.entity.Slide import Slide
from src.model.entity.Playlist import Playlist
from src.model.enum.SlideType import SlideType
from src.utils import get_optional_string, get_yt_video_id
from src.util.utils import get_optional_string, get_yt_video_id
from src.manager.DatabaseManager import DatabaseManager
from src.manager.LangManager import LangManager
from src.manager.UserManager import UserManager

View File

@ -14,7 +14,7 @@ from src.model.enum.VariableSection import VariableSection
from src.model.enum.AnimationEntranceEffect import AnimationEntranceEffect
from src.model.enum.AnimationExitEffect import AnimationExitEffect
from src.model.enum.AnimationSpeed import AnimationSpeed
from src.utils import get_keys, enum_to_str, enum_to_dict
from src.util.utils import get_keys, enum_to_str, enum_to_dict
SELECTABLE_BOOLEAN = {"1": "", "0": ""}
@ -111,6 +111,7 @@ class VariableManager:
### Player Options
{"name": "default_slide_duration", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 3, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_default_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},
### Player Animation

View File

@ -3,7 +3,7 @@ import time
from typing import Optional, Union
from src.model.enum.SlideType import SlideType, SlideInputType
from src.utils import str_to_enum
from src.util.utils import str_to_enum
class Slide:

View File

@ -5,7 +5,7 @@ from typing import Optional, Union, Dict, List
from src.model.enum.VariableType import VariableType
from src.model.enum.VariableUnit import VariableUnit
from src.model.entity.Selectable import Selectable
from src.utils import str_to_enum
from src.util.utils import str_to_enum
class Variable:

View File

@ -3,7 +3,8 @@ import platform
import psutil
import socket
from src.utils import convert_size, get_working_directory
from src.util.utils import get_working_directory
from src.util.UtilFile import convert_size
def get_rpi_model():
@ -94,6 +95,14 @@ def get_default_log_file():
return None
def get_network_ipaddr():
network_info = get_network_info()
if isinstance(network_info, dict):
return network_info['ip_address']
return None
def get_all_sysinfo():
rpi_model = get_rpi_model()
infos = {

View File

@ -9,7 +9,7 @@ from src.model.hook.HookRegistration import HookRegistration
from src.model.hook.StaticHookRegistration import StaticHookRegistration
from src.model.hook.FunctionalHookRegistration import FunctionalHookRegistration
from src.constant.WebDirConstant import WebDirConstant
from src.utils import get_safe_cron_descriptor, is_validate_cron_date_time, seconds_to_hhmmss, am_i_in_docker
from src.util.utils import get_safe_cron_descriptor, is_validate_cron_date_time, seconds_to_hhmmss, am_i_in_docker
class TemplateRenderer:

19
src/util/UtilFile.py Normal file
View File

@ -0,0 +1,19 @@
import os
import uuid
import math
def randomize_filename(old_filename: str) -> str:
new_uuid = str(uuid.uuid4())
_, extension = os.path.splitext(old_filename)
return f"{new_uuid}{extension}"
def convert_size(size_bytes):
if size_bytes == 0:
return "0B"
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
i = int(math.floor(math.log(size_bytes, 1024)))
p = math.pow(1024, i)
s = round(size_bytes / p, 2)
return f"{s} {size_name[i]}"

36
src/util/UtilNetwork.py Normal file
View File

@ -0,0 +1,36 @@
import os
import logging
import subprocess
import platform
from typing import Optional
from src.service.Sysinfo import get_network_ipaddr
def get_ip_address() -> Optional[str]:
try:
os_name = platform.system().lower()
if os_name == "linux":
result = subprocess.run(["ip", "-4", "route", "get", "8.8.8.8"], capture_output=True, text=True)
ip_address = result.stdout.split()[6]
elif os_name == "darwin":
result = subprocess.run(
["ipconfig", "getifaddr", "en0"], capture_output=True, text=True)
ip_address = result.stdout.strip()
elif os_name == "windows":
result = subprocess.run(["ipconfig"], capture_output=True, text=True)
lines = result.stdout.split('\n')
ip_address = None
for line in lines:
if "ipv4 address" in line.lower():
ip_address = line.split(': ')[1].strip()
break
else:
logging.warn(f"Unsupported OS: {os_name}")
return get_network_ipaddr()
return ip_address
except Exception as e:
logging.error(f"Error obtaining IP address: {e}")
return get_network_ipaddr()

View File

@ -1,12 +1,8 @@
import os
import re
import uuid
import inspect
import logging
import subprocess
import unicodedata
import platform
import math
from typing import Optional, List, Dict
@ -139,34 +135,6 @@ def str_to_enum(str_val: str, enum_class) -> Enum:
return enum_item
raise ValueError(f"{str_val} is not a valid {enum_class.__name__} item")
def get_ip_address() -> Optional[str]:
try:
os_name = platform.system().lower()
if os_name == "linux":
result = subprocess.run(["ip", "-4", "route", "get", "8.8.8.8"], capture_output=True, text=True)
ip_address = result.stdout.split()[6]
elif os_name == "darwin":
result = subprocess.run(
["ipconfig", "getifaddr", "en0"], capture_output=True, text=True)
ip_address = result.stdout.strip()
elif os_name == "windows":
result = subprocess.run(["ipconfig"], capture_output=True, text=True)
lines = result.stdout.split('\n')
ip_address = None
for line in lines:
if "ipv4 address" in line.lower():
ip_address = line.split(': ')[1].strip()
break
else:
logging.warn(f"Unsupported OS: {os_name}")
return None
return ip_address
except Exception as e:
logging.error(f"Error obtaining IP address: {e}")
return None
def regex_search(pattern: str, string: str, group: int):
"""Shortcut method to search a string for a given pattern.
:param str pattern:
@ -221,22 +189,6 @@ def seconds_to_hhmmss(seconds):
return f"{hours:02}:{minutes:02}:{secs:02}"
def randomize_filename(old_filename: str) -> str:
new_uuid = str(uuid.uuid4())
_, extension = os.path.splitext(old_filename)
return f"{new_uuid}{extension}"
def convert_size(size_bytes):
if size_bytes == 0:
return "0B"
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
i = int(math.floor(math.log(size_bytes, 1024)))
p = math.pow(1024, i)
s = round(size_bytes / p, 2)
return f"{s} {size_name[i]}"
def get_working_directory():
return os.getcwd()

View File

@ -1 +1 @@
1.17
1.18

View File

@ -1,75 +1,76 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function updateTime() {
var date = new Date();
var hours = (date.getHours() < 10 ? '0' : '') + date.getHours();
var minutes = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes();
var seconds = (date.getSeconds() < 10 ? '0' : '') + date.getSeconds();
var dayInMonth = date.getDate();
var month = date.getMonth();
var year = date.getFullYear();
var day = date.getDay();
var dayLabels = ["{{l.basic_day_7}}", "{{l.basic_day_1}}", "{{l.basic_day_2}}", "{{l.basic_day_3}}", "{{l.basic_day_4}}", "{{l.basic_day_5}}", "{{l.basic_day_6}}"];
var monthLabels = ["{{l.basic_month_1}}", "{{l.basic_month_2}}", "{{l.basic_month_3}}", "{{l.basic_month_4}}", "{{l.basic_month_5}}", "{{l.basic_month_6}}", "{{l.basic_month_7}}", "{{l.basic_month_8}}", "{{l.basic_month_9}}", "{{l.basic_month_10}}", "{{l.basic_month_11}}", "{{l.basic_month_12}}"];
<head>
<script type="text/javascript">
const time_with_seconds = {{ 'true' if time_with_seconds.as_bool() else 'false' }};
var timeLabel = hours + ":" + minutes;
var dateLabel = dayLabels[day] + " " + dayInMonth + " " + monthLabels[month] + " " + year;
function updateTime() {
const date = new Date();
const hours = (date.getHours() < 10 ? '0' : '') + date.getHours();
const minutes = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes();
const seconds = (date.getSeconds() < 10 ? '0' : '') + date.getSeconds();
const dayInMonth = date.getDate();
const month = date.getMonth();
const year = date.getFullYear();
const day = date.getDay();
const dayLabels = ["{{l.basic_day_7}}", "{{l.basic_day_1}}", "{{l.basic_day_2}}", "{{l.basic_day_3}}", "{{l.basic_day_4}}", "{{l.basic_day_5}}", "{{l.basic_day_6}}"];
const monthLabels = ["{{l.basic_month_1}}", "{{l.basic_month_2}}", "{{l.basic_month_3}}", "{{l.basic_month_4}}", "{{l.basic_month_5}}", "{{l.basic_month_6}}", "{{l.basic_month_7}}", "{{l.basic_month_8}}", "{{l.basic_month_9}}", "{{l.basic_month_10}}", "{{l.basic_month_11}}", "{{l.basic_month_12}}"];
document.getElementById('time').innerHTML = timeLabel;
document.getElementById('date').innerHTML = dateLabel;
setTimeout(updateTime, 1000);
}
const timeLabel = hours + ":" + minutes + (time_with_seconds ? ':' + seconds : '');
const dateLabel = dayLabels[day] + " " + dayInMonth + " " + monthLabels[month] + " " + year;
window.addEventListener("load", updateTime);
const urlParams = new URLSearchParams(window.location.search);
</script>
<style>
body {
text-align: center;
font-family: 'Arial', 'sans-serif';
color: white;
background-color: black
}
#bottom {
background: #111;
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 50px 0;
}
#time {
font-size: 10em;
}
#date {
font-size: 3em;
}
#ipaddr {
font-size: 1.25em;
color: #888888;
}
#ipaddr a {
color: #FFFFFF;
text-decoration: none;
font-weight: bold;
}
</style>
</head>
<body>
<div id="time"></div>
<div id="date"></div>
<div id="bottom">
<div id="ipaddr">
{% set link = 'http://' ~ ipaddr ~ ':' ~ PORT ~ url_for('manage') %}
{{
(l.player_default_welcome_message|safe)|replace(
'%link%',
('<a href="' ~ url_for('manage') ~ '" target="_blank">' ~ link ~ '</a>')|safe
)
}}
</div>
</div>
</body>
document.getElementById('time').innerHTML = timeLabel;
document.getElementById('date').innerHTML = dateLabel;
setTimeout(updateTime, 1000);
}
window.addEventListener("load", updateTime);
const urlParams = new URLSearchParams(window.location.search);
setInterval(function(){
const xhr = new XMLHttpRequest();
xhr.open("GET", "{{ url_for('sysinfo_get_ipaddr') }}", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
setIp(xhr.responseText);
}
};
xhr.send();
}, 5000);
</script>
<style>
body { text-align: center; font-family: 'Arial', 'sans-serif'; color: white; background-color: black; }
#bottom { display: flex; justify-content: center; align-items: center; background: #111; position: fixed; left: 0; right: 0; bottom: 0; padding: 50px 0; min-height: 50px; }
#time { font-size: 10em; }
#date { font-size: 3em; }
#ipaddr { font-size: 1.25em; color: #888888; }
#ipaddr a { color: #FFFFFF; text-decoration: none; font-weight: bold; }
#hidden-container { display: none; }
</style>
</head>
<body>
<div id="hidden-container"></div>
<div id="time"></div>
<div id="date"></div>
<div id="bottom">
<div id="ipaddr"> </div>
</div>
<script>
const js_l_common_unknown_ipaddr = '{{ l.common_unknown_ipaddr }}';
const js_l_player_default_welcome_message = '{{ l.player_default_welcome_message }}';
const manage_url_template = '{{ 'http://%ipaddr%:' ~ PORT ~ url_for('manage') }}';
const setIp = function(ipaddr) {
const $container = document.getElementById('ipaddr');
if (ipaddr) {
const link = manage_url_template.replace('%ipaddr%', ipaddr);
$container.innerHTML = js_l_player_default_welcome_message.replace('%link%', '<a href="'+link+'" target="_blank">'+link+'</a>')
} else {
$container.innerHTML = js_l_common_unknown_ipaddr;
}
};
setIp('{{ ipaddr if ipaddr else '' }}')
</script>
</body>
</html>