bug fix
This commit is contained in:
parent
9cc6c2e248
commit
3e038c4c16
|
@ -106,15 +106,15 @@
|
||||||
</header>
|
</header>
|
||||||
<div class="sources">
|
<div class="sources">
|
||||||
{% with source=controller.master %}
|
{% with source=controller.master %}
|
||||||
{% include 'aircox_liquidsoap/source.html' %}
|
{% include 'aircox/liquidsoap/source.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% with source=controller.dealer %}
|
{% with source=controller.dealer %}
|
||||||
{% include 'aircox_liquidsoap/source.html' %}
|
{% include 'aircox/liquidsoap/source.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% for source in controller.streams.values %}
|
{% for source in controller.streams.values %}
|
||||||
{% include 'aircox_liquidsoap/source.html' %}
|
{% include 'aircox/liquidsoap/source.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="next">
|
<div class="next">
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
def interactive_source (id, s) = \
|
def interactive_source (id, s) = \
|
||||||
def handler(m) = \
|
def handler(m) = \
|
||||||
file = string.escape(m['filename']) \
|
file = string.escape(m['filename']) \
|
||||||
system('{{ log_script }} -s "#{id}" -p "#{file}" -c "liquidsoap: play" &') \
|
system('echo {{ log_script }} -s "#{id}" -p \"#{file}\" -c "liquidsoap: play" &') \
|
||||||
|
system('{{ log_script }} -s "#{id}" -p \"#{file}\" -c "liquidsoap: play" &') \
|
||||||
end \
|
end \
|
||||||
\
|
\
|
||||||
s = on_track(id=id, handler, s)
|
s = on_track(id=id, handler, s)
|
||||||
|
@ -65,7 +66,7 @@ set("{{ key|safe }}", {{ value|safe }}) \
|
||||||
{% if controller.station.fallback %}
|
{% if controller.station.fallback %}
|
||||||
single("{{ controller.station.fallback }}"), \
|
single("{{ controller.station.fallback }}"), \
|
||||||
{% else %}
|
{% else %}
|
||||||
blank(), \
|
blank(duration=0.1), \
|
||||||
{% endif %}
|
{% endif %}
|
||||||
]) \
|
]) \
|
||||||
) \
|
) \
|
||||||
|
|
|
@ -173,7 +173,7 @@ class Source:
|
||||||
|
|
||||||
def stream_info (self):
|
def stream_info (self):
|
||||||
"""
|
"""
|
||||||
Return a dict with info related to the program's stream
|
Return a dict with info related to the program's stream.
|
||||||
"""
|
"""
|
||||||
if not self.program:
|
if not self.program:
|
||||||
return
|
return
|
||||||
|
@ -279,6 +279,38 @@ 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 and not self.on:
|
||||||
|
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:
|
||||||
"""
|
"""
|
||||||
|
@ -385,7 +417,6 @@ class Controller:
|
||||||
"""
|
"""
|
||||||
if not self.connector.available and self.connector.open():
|
if not self.connector.available and self.connector.open():
|
||||||
return
|
return
|
||||||
|
|
||||||
self.dealer.monitor()
|
self.dealer.monitor()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import copy
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
|
|
||||||
from aircox.programs.models import *
|
from aircox.programs.models import *
|
||||||
|
|
||||||
|
@ -64,6 +65,12 @@ class StationAdmin (NameableAdmin):
|
||||||
|
|
||||||
@admin.register(Program)
|
@admin.register(Program)
|
||||||
class ProgramAdmin (NameableAdmin):
|
class ProgramAdmin (NameableAdmin):
|
||||||
|
def schedule (self, obj):
|
||||||
|
return Schedule.objects.filter(program = obj).count() > 0
|
||||||
|
schedule.boolean = True
|
||||||
|
schedule.short_description = _("Schedule")
|
||||||
|
|
||||||
|
list_display = ('id', 'name', 'active', 'schedule')
|
||||||
fields = NameableAdmin.fields + [ 'station', 'active' ]
|
fields = NameableAdmin.fields + [ 'station', 'active' ]
|
||||||
# TODO list_display
|
# TODO list_display
|
||||||
inlines = [ ScheduleInline, StreamInline ]
|
inlines = [ ScheduleInline, StreamInline ]
|
||||||
|
@ -92,7 +99,7 @@ class DiffusionAdmin (admin.ModelAdmin):
|
||||||
list_filter = ('type', 'date', 'program')
|
list_filter = ('type', 'date', 'program')
|
||||||
list_editable = ('type', 'date')
|
list_editable = ('type', 'date')
|
||||||
|
|
||||||
fields = ['type', 'date', 'initial', 'program', 'sounds']
|
fields = ['type', 'date', 'duration', 'initial', 'program', 'sounds']
|
||||||
|
|
||||||
def get_form(self, request, obj=None, **kwargs):
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
if request.user.has_perm('aircox_program.programming'):
|
if request.user.has_perm('aircox_program.programming'):
|
||||||
|
@ -118,6 +125,24 @@ class LogAdmin (admin.ModelAdmin):
|
||||||
list_display = ['id', 'date', 'source', 'comment', 'related_object']
|
list_display = ['id', 'date', 'source', 'comment', 'related_object']
|
||||||
list_filter = ['date', 'related_type']
|
list_filter = ['date', 'related_type']
|
||||||
|
|
||||||
admin.site.register(Track)
|
|
||||||
admin.site.register(Schedule)
|
@admin.register(Schedule)
|
||||||
|
class ScheduleAdmin (admin.ModelAdmin):
|
||||||
|
def program_name (self, obj):
|
||||||
|
return obj.program.name
|
||||||
|
program_name.short_description = _('Program')
|
||||||
|
|
||||||
|
def day (self, obj):
|
||||||
|
return obj.date.strftime('%A')
|
||||||
|
day.short_description = _('Day')
|
||||||
|
|
||||||
|
def rerun (self, obj):
|
||||||
|
return obj.initial != None
|
||||||
|
rerun.short_description = _('Rerun')
|
||||||
|
rerun.boolean = True
|
||||||
|
|
||||||
|
list_display = ['id', 'program_name', 'frequency', 'date', 'day', 'rerun']
|
||||||
|
list_editable = ['frequency', 'date']
|
||||||
|
|
||||||
|
admin.site.register(Track)
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ from django.utils import timezone as tz
|
||||||
|
|
||||||
from aircox.programs.models import *
|
from aircox.programs.models import *
|
||||||
|
|
||||||
logger = logging.getLogger('aircox.programs.' + __name__)
|
logger = logging.getLogger('aircox.tools')
|
||||||
|
|
||||||
class Actions:
|
class Actions:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -33,19 +33,28 @@ class Actions:
|
||||||
items if they have been generated during this
|
items if they have been generated during this
|
||||||
update.
|
update.
|
||||||
|
|
||||||
|
It set an attribute 'do_not_save' if the item should not
|
||||||
|
be saved. FIXME: find proper way
|
||||||
|
|
||||||
Return the number of conflicts
|
Return the number of conflicts
|
||||||
"""
|
"""
|
||||||
conflicts = item.get_conflicts()
|
conflicts = item.get_conflicts()
|
||||||
|
for i, conflict in enumerate(conflicts):
|
||||||
|
if conflict.program == item.program:
|
||||||
|
item.do_not_save = True
|
||||||
|
del conflicts[i]
|
||||||
|
continue
|
||||||
|
|
||||||
|
if conflict.pk in saved_items and \
|
||||||
|
conflict.type != Diffusion.Type['unconfirmed']:
|
||||||
|
conflict.type = Diffusion.Type['unconfirmed']
|
||||||
|
conflict.save()
|
||||||
|
|
||||||
if not conflicts:
|
if not conflicts:
|
||||||
item.type = Diffusion.Type['normal']
|
item.type = Diffusion.Type['normal']
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
item.type = Diffusion.Type['unconfirmed']
|
item.type = Diffusion.Type['unconfirmed']
|
||||||
for conflict in conflicts:
|
|
||||||
if conflict.pk in saved_items and \
|
|
||||||
conflict.type != Diffusion.Type['unconfirmed']:
|
|
||||||
conflict.type = Diffusion.Type['unconfirmed']
|
|
||||||
conflict.save()
|
|
||||||
return len(conflicts)
|
return len(conflicts)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -67,14 +76,18 @@ class Actions:
|
||||||
else:
|
else:
|
||||||
for item in items:
|
for item in items:
|
||||||
count[1] += cl.__check_conflicts(item, saved_items)
|
count[1] += cl.__check_conflicts(item, saved_items)
|
||||||
|
if hasattr(item, 'do_not_save'):
|
||||||
|
count[0] -= 1
|
||||||
|
continue
|
||||||
|
|
||||||
item.save()
|
item.save()
|
||||||
saved_items.add(item)
|
saved_items.add(item)
|
||||||
|
|
||||||
logger.info('[update] {} new diffusions for schedule #{} ({})'.format(
|
logger.info('[update] schedule %s: %d new diffusions',
|
||||||
len(items), schedule.id, str(schedule)
|
str(schedule), len(items),
|
||||||
))
|
)
|
||||||
|
|
||||||
logger.info('[update] total of {} diffusions have been created,'.format(count[0]),
|
logger.info('[update] %d diffusions have been created, %s', count[0],
|
||||||
'do not forget manual approval' if manual else
|
'do not forget manual approval' if manual else
|
||||||
'{} conflicts found'.format(count[1]))
|
'{} conflicts found'.format(count[1]))
|
||||||
|
|
||||||
|
@ -82,7 +95,7 @@ class Actions:
|
||||||
def clean (date):
|
def clean (date):
|
||||||
qs = Diffusion.objects.filter(type = Diffusion.Type['unconfirmed'],
|
qs = Diffusion.objects.filter(type = Diffusion.Type['unconfirmed'],
|
||||||
date__lt = date)
|
date__lt = date)
|
||||||
logger.info('[clean] {} diffusions will be removed'.format(qs.count()))
|
logger.info('[clean] %d diffusions will be removed', qs.count())
|
||||||
qs.delete()
|
qs.delete()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -98,7 +111,7 @@ class Actions:
|
||||||
else:
|
else:
|
||||||
items.append(diffusion.id)
|
items.append(diffusion.id)
|
||||||
|
|
||||||
logger.info('[check] {} diffusions will be removed'.format(len(items)))
|
logger.info('[check] %d diffusions will be removed', len(items))
|
||||||
if len(items):
|
if len(items):
|
||||||
Diffusion.objects.filter(id__in = items).delete()
|
Diffusion.objects.filter(id__in = items).delete()
|
||||||
|
|
||||||
|
|
|
@ -33,16 +33,17 @@ from aircox.programs.models import *
|
||||||
import aircox.programs.settings as settings
|
import aircox.programs.settings as settings
|
||||||
import aircox.programs.utils as utils
|
import aircox.programs.utils as utils
|
||||||
|
|
||||||
logger = logging.getLogger('aircox.programs.' + __name__)
|
logger = logging.getLogger('aircox.tools')
|
||||||
|
|
||||||
class Command (BaseCommand):
|
class Command (BaseCommand):
|
||||||
help= __doc__
|
help= __doc__
|
||||||
|
|
||||||
def report (self, program = None, component = None, *content):
|
def report (self, program = None, component = None, *content):
|
||||||
if not component:
|
if not component:
|
||||||
logger.info('{}: '.format(program), *content)
|
logger.info('%s: %s', str(program), ' '.join([str(c) for c in content]))
|
||||||
else:
|
else:
|
||||||
logger.info('{}, {}: '.format(program, component), *content)
|
logger.info('%s, %s: %s', str(program), str(component),
|
||||||
|
' '.join([str(c) for c in content]))
|
||||||
|
|
||||||
def add_arguments (self, parser):
|
def add_arguments (self, parser):
|
||||||
parser.formatter_class=RawTextHelpFormatter
|
parser.formatter_class=RawTextHelpFormatter
|
||||||
|
@ -70,7 +71,7 @@ class Command (BaseCommand):
|
||||||
if not err:
|
if not err:
|
||||||
return utils.seconds_to_time(int(float(out)))
|
return utils.seconds_to_time(int(float(out)))
|
||||||
|
|
||||||
def get_sound_info (self, program, path):
|
def _get_sound_info (self, program, path):
|
||||||
"""
|
"""
|
||||||
Parse file name to get info on the assumption it has the correct
|
Parse file name to get info on the assumption it has the correct
|
||||||
format (given in Command.help)
|
format (given in Command.help)
|
||||||
|
@ -92,7 +93,6 @@ class Command (BaseCommand):
|
||||||
else:
|
else:
|
||||||
r = r.groupdict()
|
r = r.groupdict()
|
||||||
|
|
||||||
r['duration'] = self._get_duration(path)
|
|
||||||
r['name'] = r['name'].replace('_', ' ').capitalize()
|
r['name'] = r['name'].replace('_', ' ').capitalize()
|
||||||
r['path'] = path
|
r['path'] = path
|
||||||
return r
|
return r
|
||||||
|
@ -132,11 +132,11 @@ class Command (BaseCommand):
|
||||||
"""
|
"""
|
||||||
For all programs, scan dirs
|
For all programs, scan dirs
|
||||||
"""
|
"""
|
||||||
logger.info('scan files for all programs...')
|
logger.info('scan all programs...')
|
||||||
programs = Program.objects.filter()
|
programs = Program.objects.filter()
|
||||||
|
|
||||||
for program in programs:
|
for program in programs:
|
||||||
logger.info('program', program.name)
|
logger.info('#%d %s', program.id, program.name)
|
||||||
self.scan_for_program(
|
self.scan_for_program(
|
||||||
program, settings.AIRCOX_SOUND_ARCHIVES_SUBDIR,
|
program, settings.AIRCOX_SOUND_ARCHIVES_SUBDIR,
|
||||||
type = Sound.Type['archive'],
|
type = Sound.Type['archive'],
|
||||||
|
@ -151,7 +151,7 @@ class Command (BaseCommand):
|
||||||
Scan a given directory that is associated to the given program, and
|
Scan a given directory that is associated to the given program, and
|
||||||
update sounds information.
|
update sounds information.
|
||||||
"""
|
"""
|
||||||
logger.info('scan files in', subdir)
|
logger.info('- %s/', subdir)
|
||||||
if not program.ensure_dir(subdir):
|
if not program.ensure_dir(subdir):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -163,13 +163,16 @@ class Command (BaseCommand):
|
||||||
if not path.endswith(settings.AIRCOX_SOUND_FILE_EXT):
|
if not path.endswith(settings.AIRCOX_SOUND_FILE_EXT):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sound_info = self.get_sound_info(program, path)
|
sound, created = Sound.objects.get_or_create(
|
||||||
sound = Sound.objects.get_or_create(
|
|
||||||
path = path,
|
path = path,
|
||||||
defaults = { 'name': sound_info['name'],
|
defaults = sound_kwargs,
|
||||||
'duration': sound_info['duration'] or None }
|
)
|
||||||
)[0]
|
|
||||||
sound.__dict__.update(sound_kwargs)
|
sound_info = self._get_sound_info(program, path)
|
||||||
|
|
||||||
|
if created or sound.check_on_file():
|
||||||
|
sound_info['duration'] = self._get_duration()
|
||||||
|
sound.__dict__.update(sound_info)
|
||||||
sound.save(check = False)
|
sound.save(check = False)
|
||||||
|
|
||||||
# initial diffusion association
|
# initial diffusion association
|
||||||
|
@ -181,12 +184,12 @@ class Command (BaseCommand):
|
||||||
self.report(program, path,
|
self.report(program, path,
|
||||||
'the diffusion must be an initial diffusion')
|
'the diffusion must be an initial diffusion')
|
||||||
else:
|
else:
|
||||||
sound = initial.sounds.get_queryset() \
|
sound_ = initial.sounds.get_queryset() \
|
||||||
.filter(path == sound.path)
|
.filter(path = sound.path)
|
||||||
if not sound:
|
if not sound_:
|
||||||
self.report(program, path,
|
self.report(program, path,
|
||||||
'add sound to diffusion ', initial.id)
|
'add sound to diffusion ', initial.id)
|
||||||
initial.sounds.add(sound)
|
initial.sounds.add(sound.pk)
|
||||||
initial.save()
|
initial.save()
|
||||||
|
|
||||||
self.check_sounds(Sound.objects.filter(path__startswith = subdir))
|
self.check_sounds(Sound.objects.filter(path__startswith = subdir))
|
||||||
|
@ -201,16 +204,18 @@ class Command (BaseCommand):
|
||||||
sounds = Sound.objects.filter(good_quality = False)
|
sounds = Sound.objects.filter(good_quality = False)
|
||||||
if check:
|
if check:
|
||||||
self.check_sounds(sounds)
|
self.check_sounds(sounds)
|
||||||
files = [ sound.path for sound in sounds if not sound.removed ]
|
files = [ sound.path for sound in sounds
|
||||||
|
if not sound.removed and os.path.exists(sound.path) ]
|
||||||
else:
|
else:
|
||||||
files = [ sound.path for sound in sounds.filter(removed = False) ]
|
files = [ sound.path for sound in sounds.filter(removed = False)
|
||||||
|
if os.path.exists(sound.path) ]
|
||||||
|
|
||||||
logger.info('start quality check...')
|
logger.info('quality check...',)
|
||||||
cmd = quality_check.Command()
|
cmd = quality_check.Command()
|
||||||
cmd.handle( files = files,
|
cmd.handle( files = files,
|
||||||
**settings.AIRCOX_SOUND_QUALITY )
|
**settings.AIRCOX_SOUND_QUALITY )
|
||||||
|
|
||||||
logger.info('update sounds in database')
|
logger.info('update database')
|
||||||
def update_stats(sound_info, sound):
|
def update_stats(sound_info, sound):
|
||||||
stats = sound_info.get_file_stats()
|
stats = sound_info.get_file_stats()
|
||||||
if stats:
|
if stats:
|
||||||
|
|
|
@ -9,7 +9,7 @@ from argparse import RawTextHelpFormatter
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
logger = logging.getLogger('aircox.programs.' + __name__)
|
logger = logging.getLogger('aircox.tools')
|
||||||
|
|
||||||
class Stats:
|
class Stats:
|
||||||
attributes = [
|
attributes = [
|
||||||
|
@ -104,11 +104,11 @@ class Sound:
|
||||||
]
|
]
|
||||||
|
|
||||||
if self.good:
|
if self.good:
|
||||||
logger.info(self.path, ': good samples:\033[92m',
|
logger.info(self.path + ': good samples:\033[92m%s\033[0m',
|
||||||
', '.join( view(self.good) ), '\033[0m')
|
', '.join(view(self.good)))
|
||||||
if self.bad:
|
if self.bad:
|
||||||
loggeer.info(self.path ': bad samples:\033[91m',
|
logger.info(self.path + ': good samples:\033[91m%s\033[0m',
|
||||||
', '.join( view(self.bad) ), '\033[0m')
|
', '.join(view(self.bad)))
|
||||||
|
|
||||||
class Command (BaseCommand):
|
class Command (BaseCommand):
|
||||||
help = __doc__
|
help = __doc__
|
||||||
|
@ -157,7 +157,7 @@ class Command (BaseCommand):
|
||||||
self.bad = []
|
self.bad = []
|
||||||
self.good = []
|
self.good = []
|
||||||
for sound in self.sounds:
|
for sound in self.sounds:
|
||||||
logger.info('analyse ', sound.path)
|
logger.info('analyse ' + sound.path)
|
||||||
sound.analyse()
|
sound.analyse()
|
||||||
sound.check(attr, minmax[0], minmax[1])
|
sound.check(attr, minmax[0], minmax[1])
|
||||||
if sound.bad:
|
if sound.bad:
|
||||||
|
@ -167,13 +167,8 @@ class Command (BaseCommand):
|
||||||
|
|
||||||
# resume
|
# resume
|
||||||
if options.get('resume'):
|
if options.get('resume'):
|
||||||
if self.good:
|
for sound in self.good:
|
||||||
logger.info('files that did not failed the test:\033[92m\n ',
|
logger.info('\033[92m+ %s\033[0m', sound.path)
|
||||||
'\n '.join([sound.path for sound in self.good]),
|
for sound in self.bad:
|
||||||
'\033[0m')
|
logger.info('\033[91m+ %s\033[0m', sound.path)
|
||||||
if self.bad:
|
|
||||||
# bad at the end for ergonomy
|
|
||||||
logger.info('files that failed the test:\033[91m\n ',
|
|
||||||
'\n '.join([sound.path for sound in self.bad]),
|
|
||||||
'\033[0m')
|
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import aircox.programs.utils as utils
|
||||||
import aircox.programs.settings as settings
|
import aircox.programs.settings as settings
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger('aircox.core')
|
||||||
|
|
||||||
|
|
||||||
def date_or_default (date, date_only = False):
|
def date_or_default (date, date_only = False):
|
||||||
|
@ -171,7 +171,7 @@ class Sound (Nameable):
|
||||||
if not self.file_exists():
|
if not self.file_exists():
|
||||||
if self.removed:
|
if self.removed:
|
||||||
return
|
return
|
||||||
logger.info('sound file {} has been removed'.format(self.path))
|
logger.info('sound %s: has been removed', self.path)
|
||||||
self.removed = True
|
self.removed = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -182,8 +182,8 @@ class Sound (Nameable):
|
||||||
if self.mtime != mtime:
|
if self.mtime != mtime:
|
||||||
self.mtime = mtime
|
self.mtime = mtime
|
||||||
self.good_quality = False
|
self.good_quality = False
|
||||||
logger.info('sound file {} m_time has changed. Reset quality info'
|
logger.info('sound %s: m_time has changed. Reset quality info',
|
||||||
.format(self.path))
|
self.path)
|
||||||
return True
|
return True
|
||||||
return old_removed != self.removed
|
return old_removed != self.removed
|
||||||
|
|
||||||
|
@ -255,7 +255,7 @@ class Schedule (models.Model):
|
||||||
'last': (0b010000, _('last week of the month')),
|
'last': (0b010000, _('last week of the month')),
|
||||||
'first and third': (0b000101, _('first and third weeks of the month')),
|
'first and third': (0b000101, _('first and third weeks of the month')),
|
||||||
'second and fourth': (0b001010, _('second and fourth weeks of the month')),
|
'second and fourth': (0b001010, _('second and fourth weeks of the month')),
|
||||||
'every': (0b011111, _('once a week')),
|
'every': (0b011111, _('every week')),
|
||||||
'one on two': (0b100000, _('one week on two')),
|
'one on two': (0b100000, _('one week on two')),
|
||||||
}
|
}
|
||||||
VerboseFrequency = { value[0]: value[1] for key, value in Frequency.items() }
|
VerboseFrequency = { value[0]: value[1] for key, value in Frequency.items() }
|
||||||
|
@ -323,40 +323,42 @@ class Schedule (models.Model):
|
||||||
Return a list with all matching dates of date.month (=today)
|
Return a list with all matching dates of date.month (=today)
|
||||||
"""
|
"""
|
||||||
date = date_or_default(date, True).replace(day=1)
|
date = date_or_default(date, True).replace(day=1)
|
||||||
fwday = date.weekday()
|
freq = self.frequency
|
||||||
wday = self.date.weekday()
|
|
||||||
|
|
||||||
# move date to the date weekday of the schedule
|
# move to the first day of the month that matches the schedule's weekday
|
||||||
# check on SO#3284452 for the formula
|
# check on SO#3284452 for the formula
|
||||||
date += tz.timedelta(days = (7 if fwday > wday else 0) - fwday + wday)
|
first_weekday = date.weekday()
|
||||||
fwday = date.weekday()
|
sched_weekday = self.date.weekday()
|
||||||
|
date += tz.timedelta(days = (7 if first_weekday > sched_weekday else 0) \
|
||||||
|
- first_weekday + sched_weekday)
|
||||||
|
month = date.month
|
||||||
|
|
||||||
# special frequency case
|
# last of the month
|
||||||
weeks = self.frequency
|
if freq == Schedule.Frequency['last']:
|
||||||
if self.frequency == Schedule.Frequency['last']:
|
date += tz.timedelta(days = 4 * 7)
|
||||||
date += tz.timedelta(month = 1, days = -7)
|
next_date = date + tz.timedelta(days = 7)
|
||||||
return self.normalize([date])
|
if next_date.month == month:
|
||||||
if weeks == Schedule.Frequency['one on two']:
|
date = next_date
|
||||||
# if both week are the same, then the date week of the month
|
return [self.normalize(date)]
|
||||||
# matches. Note: wday % 2 + fwday % 2 => (wday + fwday) % 2
|
|
||||||
fweek = date.isocalendar()[1]
|
|
||||||
|
|
||||||
if date.month == 1 and fweek >= 50:
|
|
||||||
# isocalendar can think we are on the last week of the
|
|
||||||
# previous year
|
|
||||||
fweek = 0
|
|
||||||
week = self.date.isocalendar()[1]
|
|
||||||
weeks = 0b010101 if not (fweek + week) % 2 else 0b001010
|
|
||||||
|
|
||||||
dates = []
|
dates = []
|
||||||
for week in range(0,5):
|
if freq == Schedule.Frequency['one on two']:
|
||||||
# there can be five weeks in a month
|
# NOTE previous algorithm was based on the week number, but this
|
||||||
if not weeks & (0b1 << week):
|
# approach is wrong because number of weeks in a year can be
|
||||||
continue
|
# 52 or 53. This also clashes with the first week of the year.
|
||||||
wdate = date + tz.timedelta(days = week * 7)
|
if not (date - self.date).days % 14:
|
||||||
if wdate.month == date.month:
|
date += tz.timedelta(days = 7)
|
||||||
dates.append(self.normalize(wdate))
|
|
||||||
return dates
|
while date.month == month:
|
||||||
|
dates.append(date)
|
||||||
|
date += tz.timedelta(days = 14)
|
||||||
|
else:
|
||||||
|
week = 0
|
||||||
|
while week < 5 and date.month == month:
|
||||||
|
if freq & (0b1 << week):
|
||||||
|
dates.append(date)
|
||||||
|
date += tz.timedelta(days = 7)
|
||||||
|
return [self.normalize(date) for date in dates]
|
||||||
|
|
||||||
def diffusions_of_month (self, date, exclude_saved = False):
|
def diffusions_of_month (self, date, exclude_saved = False):
|
||||||
"""
|
"""
|
||||||
|
@ -397,9 +399,16 @@ class Schedule (models.Model):
|
||||||
return diffusions
|
return diffusions
|
||||||
|
|
||||||
def __str__ (self):
|
def __str__ (self):
|
||||||
frequency = [ x for x,y in Schedule.Frequency.items()
|
return ' | '.join([ '#' + str(self.id), self.program.name,
|
||||||
if y == self.frequency ]
|
self.get_frequency_display(),
|
||||||
return self.program.name + ': ' + frequency[0] + ' (' + str(self.date) + ')'
|
self.date.strftime('%a %H:%M') ])
|
||||||
|
|
||||||
|
def save (self, *args, **kwargs):
|
||||||
|
if self.initial:
|
||||||
|
self.program = self.initial.program
|
||||||
|
self.duration = self.initial.duration
|
||||||
|
self.frequency = self.initial.frequency
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Schedule')
|
verbose_name = _('Schedule')
|
||||||
|
@ -489,16 +498,24 @@ class Program (Nameable):
|
||||||
|
|
||||||
def __init__ (self, *kargs, **kwargs):
|
def __init__ (self, *kargs, **kwargs):
|
||||||
super().__init__(*kargs, **kwargs)
|
super().__init__(*kargs, **kwargs)
|
||||||
|
if self.name:
|
||||||
self.__original_path = self.path
|
self.__original_path = self.path
|
||||||
|
|
||||||
def save (self, *kargs, **kwargs):
|
def save (self, *kargs, **kwargs):
|
||||||
super().__init__(*kargs, **kwargs)
|
super().save(*kargs, **kwargs)
|
||||||
if self.__original_path != self.path and \
|
if hasattr(self, '__original_path') and \
|
||||||
|
self.__original_path != self.path and \
|
||||||
|
os.path.exists(self.__original_path) and \
|
||||||
not os.path.exists(self.path):
|
not os.path.exists(self.path):
|
||||||
logger.info('program {} name changed to {}. Change dir name' \
|
logger.info('program #%s\'s name changed to %s. Change dir name',
|
||||||
.format(self.id, self.name))
|
self.id, self.name)
|
||||||
shutil.move(self.__original_path, self.path)
|
shutil.move(self.__original_path, self.path)
|
||||||
|
|
||||||
|
sounds = Sounds.objects.filter(path__startswith = self.__original_path)
|
||||||
|
for sound in sounds:
|
||||||
|
sound.path.replace(self.__original_path, self.path)
|
||||||
|
sound.save()
|
||||||
|
|
||||||
class Diffusion (models.Model):
|
class Diffusion (models.Model):
|
||||||
"""
|
"""
|
||||||
A Diffusion is an occurrence of a Program that is scheduled on the
|
A Diffusion is an occurrence of a Program that is scheduled on the
|
||||||
|
@ -624,7 +641,7 @@ class Diffusion (models.Model):
|
||||||
if diff.pk == self.pk:
|
if diff.pk == self.pk:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if diff.date < end:
|
if diff.date < end and diff not in r:
|
||||||
r.append(diff)
|
r.append(diff)
|
||||||
continue
|
continue
|
||||||
count+=1
|
count+=1
|
||||||
|
@ -637,10 +654,10 @@ class Diffusion (models.Model):
|
||||||
if self.initial.initial:
|
if self.initial.initial:
|
||||||
self.initial = self.initial.initial
|
self.initial = self.initial.initial
|
||||||
self.program = self.initial.program
|
self.program = self.initial.program
|
||||||
super(Diffusion, self).save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def __str__ (self):
|
def __str__ (self):
|
||||||
return self.program.name + ', ' + \
|
return '#' + str(self.pk) + ' ' + self.program.name + ', ' + \
|
||||||
self.date.strftime('%Y-%m-%d %H:%M') +\
|
self.date.strftime('%Y-%m-%d %H:%M') +\
|
||||||
'' # FIXME str(self.type_display)
|
'' # FIXME str(self.type_display)
|
||||||
|
|
||||||
|
@ -691,15 +708,15 @@ class Log (models.Model):
|
||||||
ContentType.objects.get_for_model(model).id)
|
ContentType.objects.get_for_model(model).id)
|
||||||
|
|
||||||
def print (self):
|
def print (self):
|
||||||
logger.info('log #{} ({}): {}{}'.format(
|
logger.info('log #%s: %s%s',
|
||||||
str(self),
|
str(self),
|
||||||
self.comment or '',
|
self.comment or '',
|
||||||
'\n - {}: #{}'.format(self.related_type, self.related_id)
|
'\n - {}: #{}'.format(self.related_type, self.related_id)
|
||||||
if self.related_object else ''
|
if self.related_object else ''
|
||||||
))
|
)
|
||||||
|
|
||||||
def __str__ (self):
|
def __str__ (self):
|
||||||
return 'log #{} ({}, {})'.format(
|
return '#{} ({}, {})'.format(
|
||||||
self.id, self.date.strftime('%Y-%m-%d %H:%M'), self.source
|
self.id, self.date.strftime('%Y-%m-%d %H:%M'), self.source
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user