diff --git a/controllers/admin.py b/controllers/admin.py index dbe0741..c8bff2e 100644 --- a/controllers/admin.py +++ b/controllers/admin.py @@ -2,12 +2,25 @@ from django.contrib import admin import aircox.controllers.models as models + +class SourceInline(admin.StackedInline): + model = models.Source + extra = 0 + +class OutputInline(admin.StackedInline): + model = models.Output + extra = 0 + + +@admin.register(models.Station) +class StationAdmin(admin.ModelAdmin): + inlines = [ SourceInline, OutputInline ] + #@admin.register(Log) #class LogAdmin(admin.ModelAdmin): # list_display = ['id', 'date', 'source', 'comment', 'related_object'] # list_filter = ['date', 'source', 'related_type'] -admin.site.register(models.Station) admin.site.register(models.Source) admin.site.register(models.Output) admin.site.register(models.Log) diff --git a/controllers/models.py b/controllers/models.py index 5082ff7..2867a1c 100644 --- a/controllers/models.py +++ b/controllers/models.py @@ -51,6 +51,10 @@ class Station(programs.Nameable): plugin.StationController """ + @property + def id_(self): + return self.slug + def get_sources(self, type = None, prepare = True): """ Return a list of active sources that can have their controllers @@ -87,7 +91,8 @@ class Station(programs.Nameable): """ List of active outputs """ - return [ output for output in self.output_set if output.active ] + print(self.output_set) + return [ output for output in self.output_set.filter(active = True) ] def prepare(self, fetch = True): """ @@ -129,6 +134,32 @@ class Station(programs.Nameable): if self.plugin_name: self.plugin = Plugins.registry.get(self.plugin_name) + + def play_logs(self, include_diffusions = True, + include_sounds = True, + exclude_archives = True): + """ + Return a queryset with what is playing on air for this station. + Ordered by date ascending. + """ + models = [] + if include_diffusions: models.append(programs.Diffusion) + if include_sounds: models.append(programs.Sound) + + qs = Log.get_for(model = models) \ + .filter(station = station, type = Log.Type.play) + + if exclude_archives and self.dealer: + qs = qs.exclude( + source = self.dealer.id_, + related_type = ContentType.objects.get_for_model( + program.Sound + ) + ) + + return qs.order_by('date') + + def save(self, make_sources = True, *args, **kwargs): """ * make_sources: if the model has not been yet saved, generate @@ -216,6 +247,10 @@ class Source(programs.Nameable): implements plugin.SourceController; """ + @property + def id_(self): + return self.slug + @property def stream(self): if self.type != self.Type.stream or not self.program: @@ -392,8 +427,14 @@ class Log(models.Model): if not model and object: model = type(object) - qs = cl.objects.filter(related_type__pk = - ContentTYpe.objects.get_for_model(model).id) + if type(model) in (list, tuple): + model = [ ContentType.objects.get_for_model(m).id + for m in model ] + qs = cl.objects.filter(related_type__pk__in = model) + else: + model = ContentType.objects.get_for_model(model) + qs = cl.objects.filter(related_type__pk = model.id) + if object: qs = qs.filter(related_id = object.pk) return qs @@ -408,7 +449,7 @@ class Log(models.Model): def __str__(self): return '#{} ({}, {})'.format( - self.id, self.date.strftime('%Y/%m/%d %H:%M'), self.source.name + self.pk, self.date.strftime('%Y/%m/%d %H:%M'), self.source.name ) diff --git a/controllers/monitor.py b/controllers/monitor.py index 73bbfe9..e66fa30 100644 --- a/controllers/monitor.py +++ b/controllers/monitor.py @@ -12,6 +12,18 @@ class Monitor: do that. """ station = None + controller = None + + def run(self): + """ + Run all monitoring functions. Ensure that station has controllers + """ + if not self.controller: + self.station.prepare() + self.controller = self.station.controller + + self.track() + self.handler() @staticmethod def log(**kwargs): @@ -27,25 +39,23 @@ class Monitor: Check the current_sound of the station and update logs if needed """ - station = self.station - station.controller.fetch() - - current_sound = station.controller.current_sound - current_source = station.controller.current_source + self.controller.fetch() + current_sound = self.controller.current_sound + current_source = self.controller.current_source if not current_sound: return log = Log.get_for(model = programs.Sound) \ - .filter(station = station).order_by('date').last() + .filter(station = self.station).order_by('date').last() # TODO: expiration - if log and (log.source == current_source and \ + if log and (log.source == current_source.id_ and \ log.related.path == current_sound): return sound = programs.Sound.object.filter(path = current_sound) self.log( type = Log.Type.play, - source = current_source, + source = current_source.id_, date = tz.make_aware(tz.datetime.now()), related = sound[0] if sound else None, @@ -60,17 +70,21 @@ class Monitor: station = self.station now = tz.make_aware(tz.datetime.now()) - sound_log = Log.get_for(model = programs.Sound) \ - .filter(station = station).order_by('date').last() diff_log = Log.get_for(model = programs.Diffusion) \ - .filter(station = station).order_by('date').last() + .filter(station = station, type = Log.Type.play) \ + .order_by('date').last() - if not sound_log or not diff_log or \ - sound_log.source != diff_log.source or \ - diff_log.related.is_date_in_my_range(now) : + if not diff_log or \ + not diff_log.related.is_date_in_my_range(now): return None, [] - # last registered diff is still playing: update the playlist + # sound has switched? assume it has been (forced to) stopped + sound_log = Log.get_for(model = programs.Sound) \ + .filter(station = station).order_by('date').last() + if sound_log and sound_log.source != diff_log.source: + return None, [] + + # last diff is still playing: get the remaining playlist sounds = Log.get_for(model = programs.Sound) \ .filter(station = station, source = diff_log.source) \ .filter(pk__gt = diff.log.pk) @@ -94,13 +108,13 @@ class Monitor: diff = programs.Diffusion.get( now, now = True, type = programs.Diffusion.Type.normal, + sound__type = programs.Sound.Type.archive, sound__removed = False, **args ).distinct().order_by('start').first() return (diff, diff and diff.playlist or []) - def handle(self): """ Handle scheduled diffusion, trigger if needed, preload playlists @@ -116,7 +130,7 @@ class Monitor: diff, playlist = self.__current_diff() dealer.on = bool(playlist) - next_diff, next_playlist = self.__next_diff() + next_diff, next_playlist = self.__next_diff(diff) playlist += next_playlist # playlist update @@ -125,7 +139,7 @@ class Monitor: if next_diff: self.log( type = Log.Type.load, - source = dealer.id, + source = dealer.id_, date = now, related_object = next_diff ) @@ -137,7 +151,7 @@ class Monitor: source.controller.skip() cl.log( type = Log.Type.play, - source = dealer.id, + source = dealer.id_, date = now, related_object = next_diff, ) diff --git a/controllers/plugins/liquidsoap.py b/controllers/plugins/liquidsoap.py index 2059349..7c22139 100644 --- a/controllers/plugins/liquidsoap.py +++ b/controllers/plugins/liquidsoap.py @@ -34,17 +34,20 @@ class StationController(plugins.StationController): def fetch(self): super().fetch() - data = self._send('request.on_air') - if not data: + rid = self._send('request.on_air') + if not rid: return - data = self._send('request.metadata', data, parse = True) + data = self._send('request.metadata', rid, parse = True) if not data: return self.current_sound = data.get('initial_uri') - # FIXME: point to the Source object - self.current_source = data.get('source') + self.current_source = [ + # we assume sound is always from a registered source + source for source in self.station.get_sources() + if source.rid = rid + ][0] class SourceController(plugins.SourceController): @@ -77,10 +80,7 @@ class SourceController(plugins.SourceController): if not data: return - # FIXME: still usefull? originally tested only if there ass self.program - source = data.get('source') or '' - if not source.startswith(self.id): - return + self.rid = data.get('rid') self.current_sound = data.get('initial_uri') def stream(self): diff --git a/controllers/plugins/plugins.py b/controllers/plugins/plugins.py index 7fce158..6379ab1 100644 --- a/controllers/plugins/plugins.py +++ b/controllers/plugins/plugins.py @@ -53,10 +53,10 @@ class StationController: """ Current sound being played (retrieved by fetch) """ - - @property - def id(self): - return '{station.slug}_{station.pk}'.format(station = self.station) + current_source = None + """ + Current source object that is responsible of self.current_sound + """ # TODO: add function to launch external program? @@ -72,6 +72,7 @@ class StationController: """ sources = self.station.get_sources() for source in sources: + source.prepare() if source.controller: source.controller.fetch() @@ -110,7 +111,6 @@ class StationController: pass - class SourceController: """ Controller of a Source. Value are usually updated directly on the @@ -138,10 +138,6 @@ class SourceController: Current source being responsible of the current sound """ - @property - def id(self): - return '{source.station.slug}_{source.slug}'.format(source = self.source) - __playlist = None @property diff --git a/notes.md b/notes.md index 245d34f..5e802f7 100644 --- a/notes.md +++ b/notes.md @@ -39,7 +39,9 @@ - list of played diffusions and tracks when non-stop; # Long term TODO -- automatic cancel of passed diffusion based on logs +- automatic cancel of passed diffusion based on logs? + - archives can be set afterwards for rerun, so check must be done + at the same time we monitor - sounds monitor: max_size of path, take in account - logs: archive functionnality - track stats for diffusions