diff --git a/aircox/management/commands/streamer.py b/aircox/management/commands/streamer.py index 05bf115..e62fd4e 100755 --- a/aircox/management/commands/streamer.py +++ b/aircox/management/commands/streamer.py @@ -18,6 +18,13 @@ from django.utils import timezone as tz from aircox.models import Station, Diffusion, Track, Sound, Log +class Tracer: + """ + Keep trace of played item and update logs in adequation to it + """ + pass + + class Monitor: """ Log and launch diffusions for the given station. @@ -65,11 +72,12 @@ class Monitor: self.sync_playlists() self.handle() - def log(self, **kwargs): + def log(self, date = None, **kwargs): """ Create a log using **kwargs, and print info """ - log = Log(station = self.station, **kwargs) + log = Log(station = self.station, date = date or tz.now(), + **kwargs) log.save() log.print() @@ -147,8 +155,8 @@ class Monitor: for source in self.station.sources: if source == self.station.dealer: continue - playlist = [ sound.path for sound in - source.program.sound_set.all() ] + playlist = source.program.sound_set.all() \ + .values_list('path', flat = True) source.playlist = playlist def trace_canceled(self): @@ -187,52 +195,78 @@ class Monitor: station = self.station now = tz.now() - diff_log = station.played(models = Diffusion) \ - .order_by('date').last() - if not diff_log or \ - not diff_log.related.is_date_in_range(now): + log = station.played(models = Diffusion).order_by('date').last() + if not log or not log.related.is_date_in_range(now): + # not running anymore return None, [] # sound has switched? assume it has been (forced to) stopped sounds = station.played(models = Sound) \ - .filter(date__gte = diff_log.date) \ + .filter(date__gte = log.date) \ .order_by('date') - if sounds.last() and sounds.last().source != diff_log.source: - return diff_log, [] + # last sound source change: end of file reached + if sounds.count() and sounds.last().source != log.source: + # diffusion is finished: end of sound file reached + return None, [] - # 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 != Sound.Type.removed - ] + # last diff is still playing: get remaining playlist + sounds = sounds \ + .filter(source = log.source, pk__gt = diff_log.pk) \ + .exclude(related__type = Sound.Type.removed) + .values_list('related__path', flat = True) + remaining = log.related.get_archives().exclude(path__in = sounds) - return ( - diff_log.related, - [ path for path in diff_log.related.playlist - if path not in sounds ] - ) + return log.related, remaining def __next_diff(self, diff): """ - Return the tuple with the next diff that should be played and - the playlist - - Note: diff is a log + Return the next diffusion to be played as tuple of (diff, playlist). + If diff is given, it is the one to be played right after it. """ station = self.station now = tz.now() - args = {'start__gt': diff.date } if diff else {} - diff = Diffusion.objects.at(station, now).filter( - type = Diffusion.Type.normal, - sound__type = Sound.Type.archive, - **args - ).distinct().order_by('start').first() - return (diff, diff and diff.playlist or []) + kwargs = {'start__gte': diff.end } if diff else {} + diff = Diffusion.objects \ + .at(station, now) \ + .filter(type = Diffusion.Type.normal, + sound_type = Sound.Type.archive, **kwargs) \ + .distinct().order_by('start').first() + return diff + + def handle_pl_sync(self, source, playlist, diff = None, date = None): + """ + Update playlist of a source if required, and handle logging when + it is needed. + """ + dealer = self.station.dealer + if dealer.playlist != playlist: + dealer.playlist = playlist + if diff and not diff.is_live(): + self.log(type = Log.Type.load, source = source.id, + related = diff, date = date) + + def handle_diff_start(self, source, diff, date): + """ + Enable dealer in order to play a given diffusion if required, + handle start of diffusion + """ + if not diff or not diff.start <= now: + return + + # live: just log it + if diff.is_live(): + if not Log.get_for(object = diff).count(): + self.log(type = Log.Type.live, source = source.id, + related = diff, date = date) + return + + # enable dealer + if not dealer.active: + dealer.active = True + self.log(type = Log.Type.play, source = source.id, + related = diff, date = date) def handle(self): """ @@ -246,33 +280,15 @@ class Monitor: now = tz.now() # current and next diffs - diff, playlist = self.__current_diff() - dealer.active = bool(playlist) + current_diff, current_pl = self.__current_diff() + next_diff, next_pl = self.__next_diff(diff) - next_diff, next_playlist = self.__next_diff(diff) - playlist += next_playlist + # playlist + dealer.active = bool(current_pl) + playlist = current_pl + next_pl - # playlist update - if dealer.playlist != playlist: - dealer.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.active and \ - next_diff.start <= now: - dealer.active = True - self.log( - type = Log.Type.play, - source = dealer.id, - date = now, - related = next_diff, - ) + self.handle_pl_sync(dealer, playlist, next_diff, now) + self.handle_diff_start(dealer, next_diff) class Command (BaseCommand): diff --git a/aircox/models.py b/aircox/models.py index 3a7bdca..0660673 100755 --- a/aircox/models.py +++ b/aircox/models.py @@ -231,11 +231,11 @@ class Station(Nameable): self.__prepare() return self.__streamer - def played(self, models, archives = True): + def played(self, models, archives = True, include_live = True): """ Call Log.objects.played for this station """ - return Log.objects.played(self, models, archives) + return Log.objects.played(self, models, archives, include_live) @staticmethod def __mix_logs_and_diff(diffs, logs, count = 0): @@ -867,7 +867,7 @@ class Diffusion(models.Model): """ List of archives' path; uses get_archives """ - return [ sound.path for sound in self.get_archives() ] + return self.get_archives().values_list('path', flat = True) def get_archives(self): """ @@ -1260,7 +1260,7 @@ class LogManager(RelatedManager): qs = self.get_for(station, object, model, qs) return self._at(date, qs) - def played(self, station, models, archives = True): + def played(self, station, models, archives = True, include_live = True): """ Return a queryset of the played elements' log for the given station and model. This queryset is ordered by date ascending @@ -1270,8 +1270,12 @@ class LogManager(RelatedManager): * archives: if false, exclude log of diffusion's archives from the queryset; """ - qs = self.get_for(station, model = models) \ - .filter(type = Log.Type.play) + if include_live: + qs = self.get_for(station, model = models) \ + .filter(type__in = (Log.Type.play, Log.Type.live)) + else: + qs = self.get_for(station, model = models) \ + .filter(type = Log.Type.play) if not archives and station.dealer: qs = qs.exclude( @@ -1303,7 +1307,11 @@ class Log(Related): """ Source starts to be preload related_object """ - other = 0x03 + live = 0x03 + """ + A diffusion occured, but in live (no sound played by Aircox) + """ + other = 0x04 """ Other log """