rewrite streamer and controller -- much cleaner and efficient; continue to work on new architecture

This commit is contained in:
bkfox
2019-07-31 02:17:30 +02:00
parent 8581743d13
commit 8e1d2b6769
20 changed files with 550 additions and 2540 deletions

View File

@ -10,20 +10,12 @@ from django.utils.functional import cached_property
from aircox import settings, utils
from .program import Program, BaseRerun, BaseRerunQuerySet
from .program import Program, InProgramQuerySet, \
BaseRerun, BaseRerunQuerySet
from .page import Page, PageQuerySet
__all__ = ['Episode', 'EpisodeQuerySet', 'Diffusion', 'DiffusionQuerySet']
class EpisodeQuerySet(PageQuerySet):
def station(self, station):
return self.filter(program__station=station)
# FIXME: useful??? might use program.episode_set
def program(self, program):
return self.filter(program=program)
__all__ = ['Episode', 'Diffusion', 'DiffusionQuerySet']
class Episode(Page):
@ -32,7 +24,7 @@ class Episode(Page):
verbose_name=_('program'),
)
objects = EpisodeQuerySet.as_manager()
objects = InProgramQuerySet.as_manager()
class Meta:
verbose_name = _('Episode')
@ -52,40 +44,31 @@ class Episode(Page):
return cls(program=program, title=title)
class DiffusionQuerySet(BaseRerunQuerySet):
def station(self, station):
return self.filter(episode__program__station=station)
def program(self, program):
return self.filter(program=program)
def episode(self, episode=None, id=None):
""" Diffusions for this episode """
return self.filter(episode=episode) if id is None else \
self.filter(episode__id=id)
def on_air(self):
""" On air diffusions """
return self.filter(type=Diffusion.Type.on_air)
def at(self, date=None):
"""
Return diffusions occuring at the given date, ordered by +start
def now(self, now=None, order=True):
""" Diffusions occuring now """
now = now or tz.now()
qs = self.filter(start__lte=now, end__gte=now).distinct()
return qs.order_by('start') if order else qs
If date is a datetime instance, get diffusions that occurs at
the given moment. If date is not a datetime object, it uses
it as a date, and get diffusions that occurs this day.
def today(self, today=None, order=True):
""" Diffusions occuring today. """
today = today or datetime.date.today()
qs = self.filter(Q(start__date=today) | Q(end__date=today))
return qs.order_by('start') if order else qs
When date is None, uses tz.now().
"""
# note: we work with localtime
date = utils.date_or_default(date)
qs = self
filters = None
if isinstance(date, datetime.datetime):
# use datetime: we want diffusion that occurs around this
# range
filters = {'start__lte': date, 'end__gte': date}
qs = qs.filter(**filters)
else:
# use date: we want diffusions that occurs this day
qs = qs.filter(Q(start__date=date) | Q(end__date=date))
return qs.order_by('start').distinct()
def at(self, date, order=True):
""" Return diffusions at specified date or datetime """
return self.now(date, order) if isinstance(date, tz.datetime) else \
self.today(date, order)
def after(self, date=None):
"""
@ -183,10 +166,12 @@ class Diffusion(BaseRerun):
# self.check_conflicts()
def save_rerun(self):
print('rerun save', self)
self.episode = self.initial.episode
self.program = self.episode.program
def save_original(self):
def save_initial(self):
print('initial save', self)
self.program = self.episode.program
if self.episode != self._initial['episode']:
self.rerun_set.update(episode=self.episode, program=self.program)
@ -221,20 +206,11 @@ class Diffusion(BaseRerun):
return tz.localtime(self.end, tz.get_current_timezone())
@property
def original(self):
""" Return the original diffusion (self or initial) """
return self.initial.original if self.initial else self
# TODO: property?
def is_live(self):
"""
True if Diffusion is live (False if there are sounds files)
"""
""" True if Diffusion is live (False if there are sounds files). """
return self.type == self.Type.on_air and \
not self.get_sounds(archive=True).count()
not self.episode.sound_set.archive().count()
def get_playlist(self, **types):
"""

View File

@ -21,8 +21,9 @@ __all__ = ['Log', 'LogQuerySet']
class LogQuerySet(models.QuerySet):
def station(self, station):
return self.filter(station=station)
def station(self, station=None, id=None):
return self.filter(station=station) if id is None else \
self.filter(station_id=id)
def at(self, date=None):
date = utils.date_or_default(date)
@ -189,16 +190,20 @@ class Log(models.Model):
Other log
"""
station = models.ForeignKey(
Station, models.CASCADE,
verbose_name=_('station'),
help_text=_('related station'),
)
type = models.SmallIntegerField(
choices=[(int(y), _(x.replace('_', ' ')))
for x, y in Type.__members__.items()],
blank=True, null=True,
verbose_name=_('type'),
)
station = models.ForeignKey(
Station, models.CASCADE,
verbose_name=_('station'),
help_text=_('related station'),
date = models.DateTimeField(
default=tz.now, db_index=True,
verbose_name=_('date'),
)
source = models.CharField(
# we use a CharField to avoid loosing logs information if the
@ -207,10 +212,6 @@ class Log(models.Model):
verbose_name=_('source'),
help_text=_('identifier of the source related to this log'),
)
date = models.DateTimeField(
default=tz.now, db_index=True,
verbose_name=_('date'),
)
comment = models.CharField(
max_length=512, blank=True, null=True,
verbose_name=_('comment'),

View File

@ -66,7 +66,8 @@ class Program(Page):
@property
def path(self):
""" Return program's directory path """
return os.path.join(settings.AIRCOX_PROGRAMS_DIR, self.slug)
return os.path.join(settings.AIRCOX_PROGRAMS_DIR,
self.slug.replace('-', '_'))
@property
def archives_path(self):
@ -80,7 +81,6 @@ class Program(Page):
def __init__(self, *kargs, **kwargs):
super().__init__(*kargs, **kwargs)
if self.slug:
self.__initial_path = self.path
@ -137,7 +137,21 @@ class Program(Page):
.update(path=Concat('path', Substr(F('path'), len(path_))))
class BaseRerunQuerySet(models.QuerySet):
class InProgramQuerySet(models.QuerySet):
"""
Queryset for model having a ForeignKey field "program" to `Program`.
"""
def station(self, station=None, id=None):
return self.filter(program__station=station) if id is None else \
self.filter(program__station__id=id)
def program(self, program=None, id=None):
return self.filter(program=program) if id is None else \
self.filter(program__id=id)
class BaseRerunQuerySet(InProgramQuerySet):
""" Queryset for BaseRerun (sub)classes. """
def rerun(self):
return self.filter(initial__isnull=False)
@ -147,8 +161,8 @@ class BaseRerunQuerySet(models.QuerySet):
class BaseRerun(models.Model):
"""
Abstract model offering rerun facilities.
`start` datetime field or property must be implemented by sub-classes
Abstract model offering rerun facilities. Assume `start` is a
datetime field or attribute implemented by subclass.
"""
program = models.ForeignKey(
Program, models.CASCADE,
@ -157,10 +171,13 @@ class BaseRerun(models.Model):
initial = models.ForeignKey(
'self', models.SET_NULL, related_name='rerun_set',
verbose_name=_('initial schedule'),
limit_choices_to={'initial__isnull': True},
blank=True, null=True,
help_text=_('mark as rerun of this %(model_name)'),
)
objects = BaseRerunQuerySet.as_manager()
class Meta:
abstract = True

View File

@ -22,15 +22,32 @@ __all__ = ['Sound', 'SoundQuerySet', 'Track']
class SoundQuerySet(models.QuerySet):
def episode(self, episode=None, id=None):
return self.filter(episode=episode) if id is None else \
self.filter(episode__id=id)
def diffusion(self, diffusion=None, id=None):
return self.filter(episode__diffusion=diffusion) if id is None else \
self.filter(episode__diffusion__id=id)
def podcasts(self):
""" Return sound available as podcasts """
""" Return sounds available as podcasts """
return self.filter(Q(embed__isnull=False) | Q(is_public=True))
def episode(self, episode):
return self.filter(episode=episode)
def archive(self):
""" Return sounds that are archives """
return self.filter(type=Sound.Type.archive)
def diffusion(self, diffusion):
return self.filter(episode__diffusion=diffusion)
def paths(self, archive=True, order_by=True):
"""
Return paths as a flat list (exclude sound without path).
If `order_by` is True, order by path.
"""
if archive:
self = self.archive()
if order_by:
self = self.order_by('path')
return self.filter(path__isnull=False).values_list('path', flat=True)
class Sound(models.Model):
@ -46,6 +63,7 @@ class Sound(models.Model):
name = models.CharField(_('name'), max_length=64)
program = models.ForeignKey(
# FIXME: not nullable?
Program, models.SET_NULL, blank=True, null=True,
verbose_name=_('program'),
help_text=_('program related to it'),
@ -95,6 +113,21 @@ class Sound(models.Model):
objects = SoundQuerySet.as_manager()
class Meta:
verbose_name = _('Sound')
verbose_name_plural = _('Sounds')
def __str__(self):
return '/'.join(self.path.split('/')[-3:])
def save(self, check=True, *args, **kwargs):
if self.episode is not None and self.program is None:
self.program = self.episode.program
if check:
self.check_on_file()
self.__check_name()
super().save(*args, **kwargs)
def get_mtime(self):
"""
Get the last modification date from file
@ -220,21 +253,6 @@ class Sound(models.Model):
super().__init__(*args, **kwargs)
self.__check_name()
def save(self, check=True, *args, **kwargs):
if self.episode is not None and self.program is None:
self.program = self.episode.program
if check:
self.check_on_file()
self.__check_name()
super().save(*args, **kwargs)
def __str__(self):
return '/'.join(self.path.split('/')[-3:])
class Meta:
verbose_name = _('Sound')
verbose_name_plural = _('Sounds')
class Track(models.Model):
"""

View File

@ -33,6 +33,7 @@ class Station(models.Model):
"""
name = models.CharField(_('name'), max_length=64)
slug = models.SlugField(_('slug'), max_length=64, unique=True)
# FIXME: remove - should be decided only by Streamer controller + settings
path = models.CharField(
_('path'),
help_text=_('path to the working directory'),
@ -47,70 +48,13 @@ class Station(models.Model):
objects = StationQuerySet.as_manager()
#
# Controllers
#
__sources = None
__dealer = None
__streamer = None
def __prepare_controls(self):
import aircox.controllers as controllers
from .program import Program
if not self.__streamer:
self.__streamer = controllers.Streamer(station=self)
self.__dealer = controllers.Source(station=self)
self.__sources = [self.__dealer] + [
controllers.Source(station=self, program=program)
for program in Program.objects.filter(stream__isnull=False)
]
@property
def inputs(self):
"""
Return all active input ports of the station
"""
return self.port_set.filter(
direction=Port.Direction.input,
active=True
)
@property
def outputs(self):
""" Return all active output ports of the station """
return self.port_set.filter(
direction=Port.Direction.output,
active=True,
)
@property
def sources(self):
""" Audio sources, dealer included """
self.__prepare_controls()
return self.__sources
@property
def dealer(self):
""" Get dealer control """
self.__prepare_controls()
return self.__dealer
@property
def streamer(self):
""" Audio controller for the station """
self.__prepare_controls()
return self.__streamer
def __str__(self):
return self.name
def save(self, make_sources=True, *args, **kwargs):
if not self.path:
self.path = os.path.join(
settings.AIRCOX_CONTROLLERS_WORKING_DIR,
self.slug
)
self.path = os.path.join(settings.AIRCOX_CONTROLLERS_WORKING_DIR,
self.slug.replace('-', '_'))
if self.default:
qs = Station.objects.filter(default=True)