change strategy on liquidsoap logging
This commit is contained in:
parent
1a71aeb166
commit
f03acb4c1b
|
@ -12,10 +12,6 @@ Platform to manage a radio. We use the power of Django
|
||||||
## Applications
|
## Applications
|
||||||
* **programs**: managing stations, programs, schedules and diffusions. This is the core application, that handle most of the work.
|
* **programs**: managing stations, programs, schedules and diffusions. This is the core application, that handle most of the work.
|
||||||
* **cms**: cms manager with reusable tools.
|
* **cms**: cms manager with reusable tools.
|
||||||
* **liquidsoap**: liquidsoap manager and control.
|
* **liquidsoap**: liquidsoap controls.
|
||||||
|
|
||||||
## Code and names conventions and uses
|
|
||||||
* absolute dates: datetime fields, named "begin" "end" for ranges and "date" otherwise
|
|
||||||
* time range: timefield name "duration"
|
|
||||||
* parents: when only one parent, named "parent", otherwise model/reference's name
|
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ class PostListView (PostBaseView, ListView):
|
||||||
return
|
return
|
||||||
self.__dict__.update(query)
|
self.__dict__.update(query)
|
||||||
|
|
||||||
template_name = 'aircox_cms/list.html'
|
template_name = 'aircox/cms/list.html'
|
||||||
allow_empty = True
|
allow_empty = True
|
||||||
paginate_by = 50
|
paginate_by = 50
|
||||||
model = None
|
model = None
|
||||||
|
@ -143,7 +143,7 @@ class PostDetailView (DetailView, PostBaseView):
|
||||||
"""
|
"""
|
||||||
Detail view for posts and children
|
Detail view for posts and children
|
||||||
"""
|
"""
|
||||||
template_name = 'aircox_cms/detail.html'
|
template_name = 'aircox/cms/detail.html'
|
||||||
|
|
||||||
sections = []
|
sections = []
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ class PostDetailView (DetailView, PostBaseView):
|
||||||
|
|
||||||
|
|
||||||
class Menu (View):
|
class Menu (View):
|
||||||
template_name = 'aircox_cms/menu.html'
|
template_name = 'aircox/cms/menu.html'
|
||||||
|
|
||||||
name = ''
|
name = ''
|
||||||
tag = 'nav'
|
tag = 'nav'
|
||||||
|
@ -218,7 +218,7 @@ class BaseSection (View):
|
||||||
Base class for sections. Sections are view that can be used in detail view
|
Base class for sections. Sections are view that can be used in detail view
|
||||||
in order to have extra content about a post, or in menus.
|
in order to have extra content about a post, or in menus.
|
||||||
"""
|
"""
|
||||||
template_name = 'aircox_cms/base_section.html'
|
template_name = 'aircox/cms/base_section.html'
|
||||||
kwargs = None # kwargs argument passed to get
|
kwargs = None # kwargs argument passed to get
|
||||||
tag = 'div' # container tags
|
tag = 'div' # container tags
|
||||||
classes = '' # container classes
|
classes = '' # container classes
|
||||||
|
@ -253,7 +253,7 @@ class Section (BaseSection):
|
||||||
"""
|
"""
|
||||||
A Section that can be related to an object.
|
A Section that can be related to an object.
|
||||||
"""
|
"""
|
||||||
template_name = 'aircox_cms/section.html'
|
template_name = 'aircox/cms/section.html'
|
||||||
object = None
|
object = None
|
||||||
object_required = False
|
object_required = False
|
||||||
title = ''
|
title = ''
|
||||||
|
@ -331,7 +331,7 @@ class Sections:
|
||||||
use_icons = True # print icons
|
use_icons = True # print icons
|
||||||
paginate_by = 0 # number of items
|
paginate_by = 0 # number of items
|
||||||
icon_size = '32x32' # icons size
|
icon_size = '32x32' # icons size
|
||||||
template_name = 'aircox_cms/section_list.html'
|
template_name = 'aircox/cms/section_list.html'
|
||||||
|
|
||||||
def get_object_list (self):
|
def get_object_list (self):
|
||||||
return []
|
return []
|
||||||
|
|
|
@ -8,6 +8,7 @@ import time
|
||||||
import re
|
import re
|
||||||
from argparse import RawTextHelpFormatter
|
from argparse import RawTextHelpFormatter
|
||||||
|
|
||||||
|
from django.conf import settings as main_settings
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
|
@ -36,12 +37,20 @@ class StationConfig:
|
||||||
self.make_playlists()
|
self.make_playlists()
|
||||||
|
|
||||||
def make_config (self):
|
def make_config (self):
|
||||||
|
log_script = main_settings.BASE_DIR \
|
||||||
|
if hasattr(main_settings, 'BASE_DIR') else \
|
||||||
|
main_settings.PROJECT_ROOT
|
||||||
|
log_script = os.path.join(log_script, 'manage.py') + \
|
||||||
|
' liquidsoap_log'
|
||||||
|
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'controller': self.controller,
|
'controller': self.controller,
|
||||||
'settings': settings,
|
'settings': settings,
|
||||||
|
'log_script': log_script,
|
||||||
}
|
}
|
||||||
|
|
||||||
data = render_to_string('aircox_liquidsoap/station.liq', context)
|
data = render_to_string('aircox/liquidsoap/station.liq', context)
|
||||||
data = re.sub(r'\s*\\\n', r'#\\n#', data)
|
data = re.sub(r'\s*\\\n', r'#\\n#', data)
|
||||||
data = data.replace('\n', '')
|
data = data.replace('\n', '')
|
||||||
data = re.sub(r'#\\n#', '\n', data)
|
data = re.sub(r'#\\n#', '\n', data)
|
||||||
|
|
62
aircox/liquidsoap/management/commands/liquidsoap_log.py
Normal file
62
aircox/liquidsoap/management/commands/liquidsoap_log.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
"""
|
||||||
|
This script is used by liquidsoap in order to log a file change. It should not
|
||||||
|
be used for other purposes.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from argparse import RawTextHelpFormatter
|
||||||
|
|
||||||
|
from django.utils import timezone as tz
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
|
import aircox.programs.models as programs
|
||||||
|
|
||||||
|
|
||||||
|
class Command (BaseCommand):
|
||||||
|
help= __doc__
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def date(s):
|
||||||
|
try:
|
||||||
|
return tz.make_aware(tz.datetime.strptime(s, '%Y/%m/%d %H:%M:%S'))
|
||||||
|
except ValueError:
|
||||||
|
raise argparse.ArgumentTypeError('Invalid date format')
|
||||||
|
|
||||||
|
|
||||||
|
def add_arguments (self, parser):
|
||||||
|
parser.formatter_class=RawTextHelpFormatter
|
||||||
|
parser.add_argument(
|
||||||
|
'-c', '--comment', type=str,
|
||||||
|
help='log comment'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-s', '--source', type=str,
|
||||||
|
required=True,
|
||||||
|
help='source path'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-p', '--path', type=str,
|
||||||
|
required=True,
|
||||||
|
help='sound path to log'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-d', '--date', type=Command.date,
|
||||||
|
help='set date instead of now (using format "%Y/%m/%d %H:%M:%S")'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle (self, *args, **options):
|
||||||
|
comment = options.get('comment') or ''
|
||||||
|
path = os.path.realpath(options.get('path'))
|
||||||
|
|
||||||
|
sound = programs.Sound.objects.filter(path = path)
|
||||||
|
if sound:
|
||||||
|
sound = sound[0]
|
||||||
|
else:
|
||||||
|
sound = None
|
||||||
|
comment += '\nunregistered sound: {}'.format(path)
|
||||||
|
|
||||||
|
programs.Log(source = options.get('source'),
|
||||||
|
comment = comment,
|
||||||
|
related_object = sound).save()
|
||||||
|
|
||||||
|
|
|
@ -106,15 +106,15 @@
|
||||||
</header>
|
</header>
|
||||||
<div class="sources">
|
<div class="sources">
|
||||||
{% with source=controller.master %}
|
{% with source=controller.master %}
|
||||||
{% include 'aircox_liquidsoap/base_source.html' %}
|
{% include 'aircox_liquidsoap/source.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with source=controller.dealer %}
|
{% with source=controller.dealer %}
|
||||||
{% include 'aircox_liquidsoap/base_source.html' %}
|
{% include 'aircox_liquidsoap/source.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% for source in controller.streams.values %}
|
{% for source in controller.streams.values %}
|
||||||
{% include 'aircox_liquidsoap/base_source.html' %}
|
{% include 'aircox_liquidsoap/source.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="next">
|
<div class="next">
|
|
@ -1,9 +1,14 @@
|
||||||
{# Context: #}
|
{# Context: #}
|
||||||
{# - controller: controller used to generate the current file #}
|
{# - controller: controller used to generate the current file #}
|
||||||
{# - settings: global settings #}
|
{# - settings: global settings #}
|
||||||
|
|
||||||
def interactive_source (id, s) = \
|
def interactive_source (id, s) = \
|
||||||
s = store_metadata(id=id, size=1, s) \
|
def handler(m) = \
|
||||||
|
file = string.escape(m['filename']) \
|
||||||
|
system('{{ log_script }} -s "#{id}" -p "#{file}" -c "liquidsoap: play" &') \
|
||||||
|
end \
|
||||||
|
\
|
||||||
|
s = on_track(id=id, handler, s)
|
||||||
|
# s = store_metadata(id=id, size=1, s) \
|
||||||
add_skip_command(s) \
|
add_skip_command(s) \
|
||||||
s \
|
s \
|
||||||
end \
|
end \
|
||||||
|
@ -16,7 +21,7 @@ end \
|
||||||
\
|
\
|
||||||
{# Config #}
|
{# Config #}
|
||||||
set("server.socket", true) \
|
set("server.socket", true) \
|
||||||
set("server.socket.path", {{ controller.socket_path }}) \
|
set("server.socket.path", "{{ controller.socket_path }}") \
|
||||||
{% for key, value in settings.AIRCOX_LIQUIDSOAP_SET.items %}
|
{% for key, value in settings.AIRCOX_LIQUIDSOAP_SET.items %}
|
||||||
set("{{ key|safe }}", {{ value|safe }}) \
|
set("{{ key|safe }}", {{ value|safe }}) \
|
||||||
{% endfor %}
|
{% endfor %}
|
|
@ -279,39 +279,6 @@ class Dealer (Source):
|
||||||
if diffusion.playlist and on_air not in diffusion.playlist:
|
if diffusion.playlist and on_air not in diffusion.playlist:
|
||||||
return diffusion
|
return diffusion
|
||||||
|
|
||||||
def monitor (self):
|
|
||||||
"""
|
|
||||||
Monitor playlist (if it is time to load) and if it time to trigger
|
|
||||||
the button to start a diffusion.
|
|
||||||
"""
|
|
||||||
playlist = self.playlist
|
|
||||||
on_air = self.current_sound
|
|
||||||
now = tz.make_aware(tz.datetime.now())
|
|
||||||
|
|
||||||
diff = self.__get_next(now, on_air)
|
|
||||||
if not diff:
|
|
||||||
return # there is nothing we can do
|
|
||||||
|
|
||||||
# playlist reload
|
|
||||||
if self.playlist != diff.playlist:
|
|
||||||
if not playlist or on_air == playlist[-1] or \
|
|
||||||
on_air not in playlist:
|
|
||||||
self.on = False
|
|
||||||
self.playlist = diff.playlist
|
|
||||||
|
|
||||||
# run the diff
|
|
||||||
if self.playlist == diff.playlist and diff.date <= now:
|
|
||||||
self.on = True
|
|
||||||
for source in self.controller.streams.values():
|
|
||||||
source.skip()
|
|
||||||
self.controller.log(
|
|
||||||
source = self.id,
|
|
||||||
date = now,
|
|
||||||
comment = 'trigger the scheduled diffusion to liquidsoap; '
|
|
||||||
'skip all other streams',
|
|
||||||
related_object = diff,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Controller:
|
class Controller:
|
||||||
"""
|
"""
|
||||||
|
@ -412,27 +379,6 @@ class Controller:
|
||||||
for source in self.streams.values():
|
for source in self.streams.values():
|
||||||
source.update()
|
source.update()
|
||||||
|
|
||||||
def __change_log (self, source):
|
|
||||||
last_log = programs.Log.objects.filter(
|
|
||||||
source = source.id,
|
|
||||||
).prefetch_related('sound').order_by('-date')
|
|
||||||
|
|
||||||
on_air = source.current_sound
|
|
||||||
if not on_air:
|
|
||||||
return
|
|
||||||
|
|
||||||
if last_log:
|
|
||||||
last_log = last_log[0]
|
|
||||||
if last_log.sound and on_air == last_log.sound.path:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.log(
|
|
||||||
source = source.id,
|
|
||||||
date = tz.make_aware(tz.datetime.now()),
|
|
||||||
comment = 'sound has changed',
|
|
||||||
related_object = programs.Sound.objects.get(path = on_air),
|
|
||||||
)
|
|
||||||
|
|
||||||
def monitor (self):
|
def monitor (self):
|
||||||
"""
|
"""
|
||||||
Log changes in the streams, and call dealer.monitor.
|
Log changes in the streams, and call dealer.monitor.
|
||||||
|
@ -441,9 +387,6 @@ class Controller:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.dealer.monitor()
|
self.dealer.monitor()
|
||||||
self.__change_log(self.dealer)
|
|
||||||
for source in self.streams.values():
|
|
||||||
self.__change_log(source)
|
|
||||||
|
|
||||||
|
|
||||||
class Monitor:
|
class Monitor:
|
||||||
|
|
|
@ -38,7 +38,7 @@ class Actions:
|
||||||
|
|
||||||
|
|
||||||
class LiquidControl (View):
|
class LiquidControl (View):
|
||||||
template_name = 'aircox_liquidsoap/controller.html'
|
template_name = 'aircox/liquidsoap/controller.html'
|
||||||
|
|
||||||
def get_context_data (self, **kwargs):
|
def get_context_data (self, **kwargs):
|
||||||
get_monitor().update()
|
get_monitor().update()
|
||||||
|
|
|
@ -100,7 +100,7 @@ class DiffusionAdmin (admin.ModelAdmin):
|
||||||
else:
|
else:
|
||||||
self.readonly_fields = ['program', 'date', 'duration']
|
self.readonly_fields = ['program', 'date', 'duration']
|
||||||
|
|
||||||
if obj.initial:
|
if obj and obj.initial:
|
||||||
self.readonly_fields += ['program', 'sounds']
|
self.readonly_fields += ['program', 'sounds']
|
||||||
return super().get_form(request, obj, **kwargs)
|
return super().get_form(request, obj, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -639,13 +639,9 @@ class Log (models.Model):
|
||||||
help_text = 'source information',
|
help_text = 'source information',
|
||||||
blank = True, null = True,
|
blank = True, null = True,
|
||||||
)
|
)
|
||||||
sound = models.ForeignKey(
|
|
||||||
'Sound',
|
|
||||||
help_text = _('played sound'),
|
|
||||||
blank = True, null = True,
|
|
||||||
)
|
|
||||||
date = models.DateTimeField(
|
date = models.DateTimeField(
|
||||||
'date',
|
'date',
|
||||||
|
auto_now_add=True,
|
||||||
)
|
)
|
||||||
comment = models.CharField(
|
comment = models.CharField(
|
||||||
max_length = 512,
|
max_length = 512,
|
||||||
|
@ -662,12 +658,20 @@ class Log (models.Model):
|
||||||
'related_type', 'related_id',
|
'related_type', 'related_id',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_for_related_model (cl, model):
|
||||||
|
"""
|
||||||
|
Return a queryset that filter related_type to the given one.
|
||||||
|
"""
|
||||||
|
return cl.objects.filter(related_type__pk =
|
||||||
|
ContentType.objects.get_for_model(model).id)
|
||||||
|
|
||||||
def print (self):
|
def print (self):
|
||||||
print(str(self), ':', self.comment or '')
|
print(str(self), ':', self.comment or '')
|
||||||
if self.diffusion:
|
if self.related_object:
|
||||||
print(' - diffusion #' + str(self.diffusion.id))
|
print(' - {}: #{}'.format(self.related_type,
|
||||||
if self.sound:
|
self.related_id))
|
||||||
print(' - sound #' + str(self.sound.id), self.sound.path)
|
|
||||||
|
|
||||||
def __str__ (self):
|
def __str__ (self):
|
||||||
return self.date.strftime('%Y-%m-%d %H:%M') + ', ' + self.source
|
return self.date.strftime('%Y-%m-%d %H:%M') + ', ' + self.source
|
||||||
|
|
Loading…
Reference in New Issue
Block a user