mirror of
https://github.com/Screenly/Anthias.git
synced 2025-12-23 22:38:05 -05:00
237 lines
6.9 KiB
Python
237 lines
6.9 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
from __future__ import unicode_literals
|
|
|
|
import configparser
|
|
import hashlib
|
|
import json
|
|
import logging
|
|
from builtins import object, str
|
|
from collections import UserDict
|
|
from os import getenv, path
|
|
from time import sleep
|
|
|
|
import zmq
|
|
|
|
from lib.auth import BasicAuth, NoAuth
|
|
from lib.errors import ZmqCollectorTimeoutError
|
|
|
|
CONFIG_DIR = '.screenly/'
|
|
CONFIG_FILE = 'screenly.conf'
|
|
DEFAULTS = {
|
|
'main': {
|
|
'analytics_opt_out': False,
|
|
'assetdir': 'screenly_assets',
|
|
'database': CONFIG_DIR + 'screenly.db',
|
|
'date_format': 'mm/dd/yyyy',
|
|
'use_24_hour_clock': False,
|
|
'use_ssl': False,
|
|
'auth_backend': '',
|
|
'websocket_port': '9999',
|
|
'django_secret_key': '',
|
|
},
|
|
'viewer': {
|
|
'audio_output': 'hdmi',
|
|
'debug_logging': False,
|
|
'default_duration': 10,
|
|
'default_streaming_duration': '300',
|
|
'player_name': '',
|
|
'resolution': '1920x1080',
|
|
'show_splash': True,
|
|
'shuffle_playlist': False,
|
|
'verify_ssl': True,
|
|
'default_assets': False,
|
|
},
|
|
}
|
|
CONFIGURABLE_SETTINGS = DEFAULTS['viewer'].copy()
|
|
CONFIGURABLE_SETTINGS['use_24_hour_clock'] = DEFAULTS['main'][
|
|
'use_24_hour_clock'
|
|
]
|
|
CONFIGURABLE_SETTINGS['date_format'] = DEFAULTS['main']['date_format']
|
|
|
|
PORT = int(getenv('PORT', 8080))
|
|
LISTEN = getenv('LISTEN', '127.0.0.1')
|
|
|
|
# Initiate logging
|
|
logging.basicConfig(
|
|
level=logging.INFO, format='%(message)s', datefmt='%a, %d %b %Y %H:%M:%S'
|
|
)
|
|
|
|
# Silence urllib info messages ('Starting new HTTP connection')
|
|
# that are triggered by the remote url availability check in view_web
|
|
requests_log = logging.getLogger('requests')
|
|
requests_log.setLevel(logging.WARNING)
|
|
|
|
logging.debug('Starting viewer')
|
|
|
|
|
|
class AnthiasSettings(UserDict):
|
|
"""Anthias' Settings."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
UserDict.__init__(self, *args, **kwargs)
|
|
self.home = getenv('HOME')
|
|
self.conf_file = self.get_configfile()
|
|
self.auth_backends_list = [NoAuth(), BasicAuth(self)]
|
|
self.auth_backends = {}
|
|
for backend in self.auth_backends_list:
|
|
DEFAULTS.update(backend.config)
|
|
self.auth_backends[backend.name] = backend
|
|
|
|
if not path.isfile(self.conf_file):
|
|
logging.error(
|
|
'Config-file %s missing. Using defaults.', self.conf_file
|
|
)
|
|
self.use_defaults()
|
|
self.save()
|
|
else:
|
|
self.load()
|
|
|
|
def _get(self, config, section, field, default):
|
|
try:
|
|
if isinstance(default, bool):
|
|
self[field] = config.getboolean(section, field)
|
|
elif isinstance(default, int):
|
|
self[field] = config.getint(section, field)
|
|
else:
|
|
self[field] = config.get(section, field)
|
|
# Likely not a hashed password
|
|
if (
|
|
field == 'password'
|
|
and self[field] != ''
|
|
and len(self[field]) != 64
|
|
):
|
|
# Hash the original password.
|
|
self[field] = hashlib.sha256(self[field]).hexdigest()
|
|
except configparser.Error as e:
|
|
logging.debug(
|
|
"Could not parse setting '%s.%s': %s. "
|
|
"Using default value: '%s'.",
|
|
section,
|
|
field,
|
|
str(e),
|
|
default,
|
|
)
|
|
self[field] = default
|
|
if field in ['database', 'assetdir']:
|
|
self[field] = str(path.join(self.home, self[field]))
|
|
|
|
def _set(self, config, section, field, default):
|
|
if isinstance(default, bool):
|
|
config.set(
|
|
section, field, self.get(field, default) and 'on' or 'off'
|
|
)
|
|
else:
|
|
config.set(section, field, str(self.get(field, default)))
|
|
|
|
def load(self):
|
|
"""Loads the latest settings from screenly.conf into memory."""
|
|
logging.debug('Reading config-file...')
|
|
config = configparser.ConfigParser()
|
|
config.read(self.conf_file)
|
|
|
|
for section, defaults in list(DEFAULTS.items()):
|
|
for field, default in list(defaults.items()):
|
|
self._get(config, section, field, default)
|
|
|
|
def use_defaults(self):
|
|
for defaults in list(DEFAULTS.items()):
|
|
for field, default in list(defaults[1].items()):
|
|
self[field] = default
|
|
|
|
def save(self):
|
|
# Write new settings to disk.
|
|
config = configparser.ConfigParser()
|
|
for section, defaults in list(DEFAULTS.items()):
|
|
config.add_section(section)
|
|
for field, default in list(defaults.items()):
|
|
self._set(config, section, field, default)
|
|
with open(self.conf_file, 'w') as f:
|
|
config.write(f)
|
|
self.load()
|
|
|
|
def get_configdir(self):
|
|
return path.join(self.home, CONFIG_DIR)
|
|
|
|
def get_configfile(self):
|
|
return path.join(self.home, CONFIG_DIR, CONFIG_FILE)
|
|
|
|
@property
|
|
def auth(self):
|
|
backend_name = self['auth_backend']
|
|
if backend_name in self.auth_backends:
|
|
return self.auth_backends[self['auth_backend']]
|
|
|
|
|
|
settings = AnthiasSettings()
|
|
|
|
|
|
class ZmqPublisher(object):
|
|
INSTANCE = None
|
|
|
|
def __init__(self):
|
|
if self.INSTANCE is not None:
|
|
raise ValueError('An instance already exists!')
|
|
|
|
self.context = zmq.Context()
|
|
|
|
self.socket = self.context.socket(zmq.PUB)
|
|
self.socket.bind('tcp://0.0.0.0:10001')
|
|
sleep(1)
|
|
|
|
@classmethod
|
|
def get_instance(cls):
|
|
if cls.INSTANCE is None:
|
|
cls.INSTANCE = ZmqPublisher()
|
|
return cls.INSTANCE
|
|
|
|
def send_to_ws_server(self, msg):
|
|
self.socket.send('ws_server {}'.format(msg).encode('utf-8'))
|
|
|
|
def send_to_viewer(self, msg):
|
|
self.socket.send_string('viewer {}'.format(msg))
|
|
|
|
|
|
class ZmqConsumer(object):
|
|
def __init__(self):
|
|
self.context = zmq.Context()
|
|
|
|
self.socket = self.context.socket(zmq.PUSH)
|
|
self.socket.setsockopt(zmq.LINGER, 0)
|
|
self.socket.connect('tcp://anthias-server:5558')
|
|
|
|
sleep(1)
|
|
|
|
def send(self, msg):
|
|
self.socket.send_json(msg, flags=zmq.NOBLOCK)
|
|
|
|
|
|
class ZmqCollector(object):
|
|
INSTANCE = None
|
|
|
|
def __init__(self):
|
|
if self.INSTANCE is not None:
|
|
raise ValueError('An instance already exists!')
|
|
|
|
self.context = zmq.Context()
|
|
|
|
self.socket = self.context.socket(zmq.PULL)
|
|
self.socket.bind('tcp://0.0.0.0:5558')
|
|
|
|
self.poller = zmq.Poller()
|
|
self.poller.register(self.socket, zmq.POLLIN)
|
|
|
|
sleep(1)
|
|
|
|
@classmethod
|
|
def get_instance(cls):
|
|
if cls.INSTANCE is None:
|
|
cls.INSTANCE = ZmqCollector()
|
|
return cls.INSTANCE
|
|
|
|
def recv_json(self, timeout):
|
|
if self.poller.poll(timeout):
|
|
return json.loads(self.socket.recv(zmq.NOBLOCK))
|
|
|
|
raise ZmqCollectorTimeoutError
|