242 lines
5.8 KiB
Python
242 lines
5.8 KiB
Python
import os
|
|
import re
|
|
import subprocess
|
|
import atexit
|
|
|
|
from django.template.loader import render_to_string
|
|
|
|
class Plugins(type):
|
|
registry = {}
|
|
|
|
def __new__(cls, name, bases, attrs):
|
|
cl = super().__new__(cls, name, bases, attrs)
|
|
if name != 'Plugin':
|
|
if not cl.name:
|
|
cl.name = name.lower()
|
|
cls.registry[cl.name] = cl
|
|
return cl
|
|
|
|
@classmethod
|
|
def discover(cls):
|
|
"""
|
|
Discover plugins -- needed because of the import traps
|
|
"""
|
|
import aircox.controllers.plugins.liquidsoap
|
|
|
|
|
|
class Plugin(metaclass=Plugins):
|
|
name = ''
|
|
|
|
def init_station(self, station):
|
|
pass
|
|
|
|
def init_source(self, source):
|
|
pass
|
|
|
|
|
|
class StationController:
|
|
"""
|
|
Controller of a Station.
|
|
"""
|
|
station = None
|
|
"""
|
|
Related station
|
|
"""
|
|
template_name = ''
|
|
"""
|
|
If set, use this template in order to generated the configuration
|
|
file in self.path file
|
|
"""
|
|
path = None
|
|
"""
|
|
Path of the configuration file.
|
|
"""
|
|
current_sound = ''
|
|
"""
|
|
Current sound being played (retrieved by fetch)
|
|
"""
|
|
current_source = None
|
|
"""
|
|
Current source object that is responsible of self.current_sound
|
|
"""
|
|
process = None
|
|
|
|
def __init__(self, **kwargs):
|
|
self.__dict__.update(kwargs)
|
|
|
|
def fetch(self):
|
|
"""
|
|
Fetch data of the children and so on
|
|
|
|
The base function just execute the function of all children
|
|
sources. The plugin must implement the other extra part
|
|
"""
|
|
sources = self.station.get_sources(dealer = True)
|
|
for source in sources:
|
|
source.prepare()
|
|
if source.controller:
|
|
source.controller.fetch()
|
|
|
|
def __get_process_args(self):
|
|
"""
|
|
Get arguments for the executed application. Called by exec, to be
|
|
used as subprocess.Popen(__get_process_args()).
|
|
If no value is returned, abort the execution.
|
|
|
|
Must be implemented by the plugin
|
|
"""
|
|
return []
|
|
|
|
def process_run(self):
|
|
"""
|
|
Execute the external application with corresponding informations.
|
|
|
|
This function must make sure that all needed files have been generated.
|
|
"""
|
|
if self.process:
|
|
return
|
|
|
|
self.push()
|
|
|
|
args = self.__get_process_args()
|
|
if not args:
|
|
return
|
|
self.process = subprocess.Popen(args, stderr=subprocess.STDOUT)
|
|
atexit.register(self.process.terminate)
|
|
|
|
def process_terminate(self):
|
|
if self.process:
|
|
self.process.terminate()
|
|
self.process = None
|
|
|
|
def process_wait(self):
|
|
"""
|
|
Wait for the process to terminate if there is a process
|
|
"""
|
|
if self.process:
|
|
self.process.wait()
|
|
self.process = None
|
|
|
|
def ready(self):
|
|
"""
|
|
If external program is ready to use, returns True
|
|
"""
|
|
|
|
def push(self, config = True):
|
|
"""
|
|
Update configuration and children's info.
|
|
|
|
The base function just execute the function of all children
|
|
sources. The plugin must implement the other extra part
|
|
"""
|
|
sources = self.station.get_sources(dealer = True)
|
|
for source in sources:
|
|
source.prepare()
|
|
if source.controller:
|
|
source.controller.push()
|
|
|
|
if config and self.path and self.template_name:
|
|
import aircox.controllers.settings as settings
|
|
|
|
data = render_to_string(self.template_name, {
|
|
'station': self.station,
|
|
'settings': settings,
|
|
})
|
|
data = re.sub('[\t ]+\n', '\n', data)
|
|
data = re.sub('\n{3,}', '\n\n', data)
|
|
|
|
os.makedirs(os.path.dirname(self.path), exist_ok = True)
|
|
with open(self.path, 'w+') as file:
|
|
file.write(data)
|
|
|
|
|
|
def skip(self):
|
|
"""
|
|
Skip the current sound on the station
|
|
"""
|
|
if self.current_source:
|
|
self.current_source.controller.skip()
|
|
|
|
|
|
class SourceController:
|
|
"""
|
|
Controller of a Source. Value are usually updated directly on the
|
|
external side.
|
|
"""
|
|
source = None
|
|
"""
|
|
Related source
|
|
"""
|
|
path = ''
|
|
"""
|
|
Path to the Source's playlist file. Optional.
|
|
"""
|
|
active = True
|
|
"""
|
|
Source is available. May be different from the containing Source,
|
|
e.g. dealer and liquidsoap.
|
|
"""
|
|
current_sound = ''
|
|
"""
|
|
Current sound being played (retrieved by fetch)
|
|
"""
|
|
current_source = None
|
|
"""
|
|
Current source being responsible of the current sound
|
|
"""
|
|
|
|
__playlist = None
|
|
|
|
@property
|
|
def playlist(self):
|
|
"""
|
|
Current playlist on the Source, list of paths to play
|
|
"""
|
|
return self.__playlist
|
|
|
|
@playlist.setter
|
|
def playlist(self, value):
|
|
self.__playlist = value
|
|
self.push()
|
|
|
|
def __init__(self, **kwargs):
|
|
self.__dict__.update(kwargs)
|
|
self.__playlist = []
|
|
if not self.path:
|
|
self.path = os.path.join(self.source.station.path,
|
|
self.source.slug + '.m3u')
|
|
|
|
def skip(self):
|
|
"""
|
|
Skip the current sound in the source
|
|
"""
|
|
pass
|
|
|
|
def fetch(self):
|
|
"""
|
|
Get the source information
|
|
"""
|
|
pass
|
|
|
|
def push(self):
|
|
"""
|
|
Update data relative to the source on the external program.
|
|
By default write the playlist.
|
|
"""
|
|
os.makedirs(os.path.dirname(self.path), exist_ok = True)
|
|
with open(self.path, 'w') as file:
|
|
file.write('\n'.join(self.playlist or []))
|
|
|
|
def activate(self, value = True):
|
|
"""
|
|
Activate/Deactivate current source. May be different from the
|
|
containing Source.
|
|
"""
|
|
pass
|
|
|
|
|
|
class Monitor:
|
|
station = None
|
|
|
|
|