forked from rc/aircox
239 lines
7.4 KiB
Python
239 lines
7.4 KiB
Python
import time
|
|
|
|
from django.utils import timezone as tz
|
|
|
|
import aircox.programs.models as programs
|
|
from aircox.controllers.models import Log
|
|
|
|
class Monitor:
|
|
"""
|
|
Log and launch diffusions for the given station.
|
|
|
|
Monitor should be able to be used after a crash a go back
|
|
where it was playing, so we heavily use logs to be able to
|
|
do that.
|
|
|
|
We keep trace of played items on the generated stream:
|
|
- sounds played on this stream;
|
|
- scheduled diffusions
|
|
- tracks for sounds of streamed programs
|
|
"""
|
|
station = None
|
|
controller = None
|
|
cancel_timeout = 60*10
|
|
"""
|
|
Time in seconds before a diffusion that have archives is cancelled
|
|
because it has not been played.
|
|
"""
|
|
|
|
def __init__(self, station, **kwargs):
|
|
self.station = station
|
|
self.__dict__.update(kwargs)
|
|
|
|
def monitor(self):
|
|
"""
|
|
Run all monitoring functions. Ensure that station has controllers
|
|
"""
|
|
if not self.controller:
|
|
self.station.prepare()
|
|
for stream in self.station.stream_sources:
|
|
stream.load_playlist()
|
|
self.controller = self.station.controller
|
|
|
|
if not self.controller.ready():
|
|
return
|
|
|
|
self.trace()
|
|
self.handle()
|
|
|
|
def log(self, **kwargs):
|
|
"""
|
|
Create a log using **kwargs, and print info
|
|
"""
|
|
log = Log(station = self.station, **kwargs)
|
|
log.save()
|
|
log.print()
|
|
|
|
def trace(self):
|
|
"""
|
|
Check the current_sound of the station and update logs if
|
|
needed.
|
|
"""
|
|
self.controller.fetch()
|
|
current_sound = self.controller.current_sound
|
|
current_source = self.controller.current_source
|
|
if not current_sound or not current_source:
|
|
return
|
|
|
|
log = Log.objects.get_for(model = programs.Sound) \
|
|
.filter(station = self.station).order_by('date').last()
|
|
|
|
# only streamed
|
|
if log and not log.related.diffusion:
|
|
self.trace_sound_tracks(log)
|
|
|
|
# TODO: expiration
|
|
if log and (log.source == current_source.id_ and \
|
|
log.related.path == current_sound):
|
|
return
|
|
|
|
sound = programs.Sound.objects.filter(path = current_sound)
|
|
self.log(
|
|
type = Log.Type.play,
|
|
source = current_source.id_,
|
|
date = tz.now(),
|
|
related = sound[0] if sound else None,
|
|
comment = None if sound else current_sound,
|
|
)
|
|
|
|
def trace_sound_tracks(self, log):
|
|
"""
|
|
Log tracks for the given sound (for streamed programs); Called by
|
|
self.trace
|
|
"""
|
|
logs = Log.objects.get_for(model = programs.Track) \
|
|
.filter(pk__gt = log.pk)
|
|
logs = [ log.related_id for log in logs ]
|
|
|
|
tracks = programs.Track.objects.get_for(object = log.related) \
|
|
.filter(in_seconds = True)
|
|
if tracks and len(tracks) == len(logs):
|
|
return
|
|
|
|
tracks = tracks.exclude(pk__in = logs).order_by('position')
|
|
now = tz.now()
|
|
for track in tracks:
|
|
pos = log.date + tz.timedelta(seconds = track.position)
|
|
if pos < now:
|
|
self.log(
|
|
type = Log.Type.play,
|
|
source = log.source,
|
|
date = pos,
|
|
related = track
|
|
)
|
|
|
|
def trace_canceled(self):
|
|
"""
|
|
Check diffusions that should have been played but did not start,
|
|
and cancel them
|
|
"""
|
|
if not self.cancel_timeout:
|
|
return
|
|
|
|
diffs = programs.objects.get_at().filter(
|
|
type = programs.Diffusion.Type.normal,
|
|
sound__type = programs.Sound.Type.archive,
|
|
)
|
|
logs = station.get_played(models = programs.Diffusion)
|
|
|
|
date = tz.now() - datetime.timedelta(seconds = self.cancel_timeout)
|
|
for diff in diffs:
|
|
if logs.filter(related = diff):
|
|
continue
|
|
if diff.start < now:
|
|
diff.type = programs.Diffusion.Type.canceled
|
|
diff.save()
|
|
self.log(
|
|
type = Log.Type.other,
|
|
related = diff,
|
|
comment = 'Diffusion canceled after {} seconds' \
|
|
.format(self.cancel_timeout)
|
|
)
|
|
|
|
def __current_diff(self):
|
|
"""
|
|
Return a tuple with the currently running diffusion and the items
|
|
that still have to be played. If there is not, return None
|
|
"""
|
|
station = self.station
|
|
now = tz.make_aware(tz.datetime.now())
|
|
|
|
diff_log = station.get_played(models = programs.Diffusion) \
|
|
.order_by('date').last()
|
|
if not diff_log or \
|
|
not diff_log.related.is_date_in_range(now):
|
|
return None, []
|
|
|
|
# sound has switched? assume it has been (forced to) stopped
|
|
sounds = station.get_played(models = programs.Sound) \
|
|
.filter(date__gte = diff_log.date) \
|
|
.order_by('date')
|
|
|
|
if sounds.last() and sounds.last().source != diff_log.source:
|
|
return diff_log, []
|
|
|
|
# last diff is still playing: get the remaining playlist
|
|
sounds = sounds.filter(
|
|
source = diff_log.source, pk__gt = diff_log.pk
|
|
)
|
|
sounds = [
|
|
sound.related.path for sound in sounds
|
|
if sound.related.type != programs.Sound.Type.removed
|
|
]
|
|
|
|
return (
|
|
diff_log.related,
|
|
[ path for path in diff_log.related.playlist
|
|
if path not in sounds ]
|
|
)
|
|
|
|
def __next_diff(self, diff):
|
|
"""
|
|
Return the tuple with the next diff that should be played and
|
|
the playlist
|
|
|
|
Note: diff is a log
|
|
"""
|
|
station = self.station
|
|
now = tz.now()
|
|
|
|
args = {'start__gt': diff.date } if diff else {}
|
|
diff = programs.Diffusion.objects.get_at(now).filter(
|
|
type = programs.Diffusion.Type.normal,
|
|
sound__type = programs.Sound.Type.archive,
|
|
**args
|
|
).distinct().order_by('start').first()
|
|
return (diff, diff and diff.playlist or [])
|
|
|
|
def handle(self):
|
|
"""
|
|
Handle scheduled diffusion, trigger if needed, preload playlists
|
|
and so on.
|
|
"""
|
|
station = self.station
|
|
dealer = station.dealer
|
|
if not dealer:
|
|
return
|
|
now = tz.now()
|
|
|
|
# current and next diffs
|
|
diff, playlist = self.__current_diff()
|
|
dealer.controller.active = bool(playlist)
|
|
|
|
next_diff, next_playlist = self.__next_diff(diff)
|
|
playlist += next_playlist
|
|
|
|
# playlist update
|
|
if dealer.controller.playlist != playlist:
|
|
dealer.controller.playlist = playlist
|
|
if next_diff:
|
|
self.log(
|
|
type = Log.Type.load,
|
|
source = dealer.id_,
|
|
date = now,
|
|
related = next_diff
|
|
)
|
|
|
|
# dealer.on when next_diff start <= now
|
|
if next_diff and not dealer.controller.active and \
|
|
next_diff.start <= now:
|
|
dealer.controller.active = True
|
|
self.log(
|
|
type = Log.Type.play,
|
|
source = dealer.id_,
|
|
date = now,
|
|
related = next_diff,
|
|
)
|
|
|
|
|