Merge pull request 'Sound Monitor' (#83) from dev-1.0-sound-monitor-fixes into develop-1.0
Reviewed-on: #83
This commit is contained in:
commit
ba585c64bb
|
@ -70,12 +70,12 @@ class SoundAdmin(SortableAdminBase, admin.ModelAdmin):
|
||||||
if obj.type != Sound.TYPE_REMOVED else ''
|
if obj.type != Sound.TYPE_REMOVED else ''
|
||||||
audio.short_description = _('Audio')
|
audio.short_description = _('Audio')
|
||||||
|
|
||||||
def add_view(self, request, object_id, form_url='', context=None):
|
def add_view(self, request, form_url='', context=None):
|
||||||
context = context or {}
|
context = context or {}
|
||||||
context['init_app'] = True
|
context['init_app'] = True
|
||||||
context['init_el'] = '#inline-tracks'
|
context['init_el'] = '#inline-tracks'
|
||||||
context['track_timestamp'] = True
|
context['track_timestamp'] = True
|
||||||
return super().change_view(request, object_id, form_url, context)
|
return super().add_view(request, form_url, context)
|
||||||
|
|
||||||
def change_view(self, request, object_id, form_url='', context=None):
|
def change_view(self, request, object_id, form_url='', context=None):
|
||||||
context = context or {}
|
context = context or {}
|
||||||
|
|
|
@ -111,8 +111,12 @@ class Command(BaseCommand):
|
||||||
def monitor(self):
|
def monitor(self):
|
||||||
""" Run in monitor mode """
|
""" Run in monitor mode """
|
||||||
with futures.ThreadPoolExecutor() as pool:
|
with futures.ThreadPoolExecutor() as pool:
|
||||||
archives_handler = MonitorHandler(settings.AIRCOX_SOUND_ARCHIVES_SUBDIR, pool)
|
archives_handler = MonitorHandler(
|
||||||
excerpts_handler = MonitorHandler(settings.AIRCOX_SOUND_EXCERPTS_SUBDIR, pool)
|
settings.AIRCOX_SOUND_ARCHIVES_SUBDIR, pool,
|
||||||
|
type=Sound.TYPE_ARCHIVE)
|
||||||
|
excerpts_handler = MonitorHandler(
|
||||||
|
settings.AIRCOX_SOUND_EXCERPTS_SUBDIR, pool,
|
||||||
|
type=Sound.TYPE_EXCERPT)
|
||||||
|
|
||||||
observer = Observer()
|
observer = Observer()
|
||||||
observer.schedule(archives_handler, settings.AIRCOX_PROGRAMS_DIR_ABS,
|
observer.schedule(archives_handler, settings.AIRCOX_PROGRAMS_DIR_ABS,
|
||||||
|
|
|
@ -61,12 +61,13 @@ class SoundFile:
|
||||||
def episode(self):
|
def episode(self):
|
||||||
return self.sound and self.sound.episode
|
return self.sound and self.sound.episode
|
||||||
|
|
||||||
def sync(self, sound=None, program=None, deleted=False, **kwargs):
|
def sync(self, sound=None, program=None, deleted=False, keep_deleted=False,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Update related sound model and save it.
|
Update related sound model and save it.
|
||||||
"""
|
"""
|
||||||
if deleted:
|
if deleted:
|
||||||
return self._on_delete(self.path)
|
return self._on_delete(self.path, keep_deleted)
|
||||||
|
|
||||||
# FIXME: sound.program as not null
|
# FIXME: sound.program as not null
|
||||||
if not program:
|
if not program:
|
||||||
|
@ -99,14 +100,18 @@ class SoundFile:
|
||||||
self.find_playlist(sound)
|
self.find_playlist(sound)
|
||||||
return sound
|
return sound
|
||||||
|
|
||||||
def _on_delete(self, path):
|
def _on_delete(self, path, keep_deleted):
|
||||||
# TODO: remove from db on delete
|
# TODO: remove from db on delete
|
||||||
sound = Sound.objects.path(self.path).first()
|
if keep_deleted:
|
||||||
if sound:
|
sound = Sound.objects.path(self.path).first()
|
||||||
sound.type = sound.TYPE_REMOVED
|
if sound:
|
||||||
sound.check_on_file()
|
if keep_deleted:
|
||||||
sound.save()
|
sound.type = sound.TYPE_REMOVED
|
||||||
return sound
|
sound.check_on_file()
|
||||||
|
sound.save()
|
||||||
|
return sound
|
||||||
|
else:
|
||||||
|
Sound.objects.path(self.path).delete()
|
||||||
|
|
||||||
def read_path(self, path):
|
def read_path(self, path):
|
||||||
"""
|
"""
|
||||||
|
@ -144,7 +149,12 @@ class SoundFile:
|
||||||
|
|
||||||
def read_file_info(self):
|
def read_file_info(self):
|
||||||
""" Read file information and metadata. """
|
""" Read file information and metadata. """
|
||||||
return mutagen.File(self.path) if os.path.exists(self.path) else None
|
try:
|
||||||
|
if os.path.exists(self.path):
|
||||||
|
return mutagen.File(self.path)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
def find_episode(self, sound, path_info):
|
def find_episode(self, sound, path_info):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -114,16 +114,16 @@ class MonitorHandler(PatternMatchingEventHandler):
|
||||||
pool = None
|
pool = None
|
||||||
jobs = {}
|
jobs = {}
|
||||||
|
|
||||||
def __init__(self, subdir, pool):
|
def __init__(self, subdir, pool, **sync_kw):
|
||||||
"""
|
"""
|
||||||
subdir: AIRCOX_SOUND_ARCHIVES_SUBDIR or AIRCOX_SOUND_EXCERPTS_SUBDIR
|
:param str subdir: sub-directory in program dirs to monitor \
|
||||||
|
(AIRCOX_SOUND_ARCHIVES_SUBDIR or AIRCOX_SOUND_EXCERPTS_SUBDIR);
|
||||||
|
:param concurrent.futures.Executor pool: pool executing jobs on file change;
|
||||||
|
:param **sync_kw: kwargs passed to `SoundFile.sync`;
|
||||||
"""
|
"""
|
||||||
self.subdir = subdir
|
self.subdir = subdir
|
||||||
self.pool = pool
|
self.pool = pool
|
||||||
if self.subdir == settings.AIRCOX_SOUND_ARCHIVES_SUBDIR:
|
self.sync_kw = sync_kw
|
||||||
self.sync_kw = {'type': Sound.TYPE_ARCHIVE}
|
|
||||||
else:
|
|
||||||
self.sync_kw = {'type': Sound.TYPE_EXCERPT}
|
|
||||||
|
|
||||||
patterns = ['*/{}/*{}'.format(self.subdir, ext)
|
patterns = ['*/{}/*{}'.format(self.subdir, ext)
|
||||||
for ext in settings.AIRCOX_SOUND_FILE_EXT]
|
for ext in settings.AIRCOX_SOUND_FILE_EXT]
|
||||||
|
@ -156,7 +156,6 @@ class MonitorHandler(PatternMatchingEventHandler):
|
||||||
self.jobs[key] = handler
|
self.jobs[key] = handler
|
||||||
|
|
||||||
def done(r):
|
def done(r):
|
||||||
print(':::: job done', key)
|
|
||||||
if self.jobs.get(key) is handler:
|
if self.jobs.get(key) is handler:
|
||||||
del self.jobs[key]
|
del self.jobs[key]
|
||||||
handler.future.add_done_callback(done)
|
handler.future.add_done_callback(done)
|
||||||
|
|
|
@ -130,6 +130,13 @@ ensure(
|
||||||
('.ogg', '.flac', '.wav', '.mp3', '.opus')
|
('.ogg', '.flac', '.wav', '.mp3', '.opus')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Tag sounds as deleted instead of deleting them when file has been removed
|
||||||
|
# from filesystem (sound monitoring)
|
||||||
|
ensure(
|
||||||
|
'AIRCOX_SOUND_KEEP_DELETED',
|
||||||
|
False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
# Streamer & Controllers
|
# Streamer & Controllers
|
||||||
|
|
|
@ -62,7 +62,7 @@ class MonitorHandlerTestCase(TestCase):
|
||||||
def test_submit_new_job(self):
|
def test_submit_new_job(self):
|
||||||
event = FakeEvent(src_path='dummy_src')
|
event = FakeEvent(src_path='dummy_src')
|
||||||
handler = NotifyHandler()
|
handler = NotifyHandler()
|
||||||
result = self.monitor._submit(handler, event, 'up')
|
result, _ = self.monitor._submit(handler, event, 'up')
|
||||||
self.assertIs(handler, result)
|
self.assertIs(handler, result)
|
||||||
self.assertIsInstance(handler.future, futures.Future)
|
self.assertIsInstance(handler.future, futures.Future)
|
||||||
self.monitor.pool.shutdown()
|
self.monitor.pool.shutdown()
|
||||||
|
@ -70,7 +70,9 @@ class MonitorHandlerTestCase(TestCase):
|
||||||
def test_submit_job_exists(self):
|
def test_submit_job_exists(self):
|
||||||
event = FakeEvent(src_path='dummy_src')
|
event = FakeEvent(src_path='dummy_src')
|
||||||
|
|
||||||
job_1 = self.monitor._submit(WaitHandler(), event, 'up')
|
job_1, new_1 = self.monitor._submit(WaitHandler(), event, 'up')
|
||||||
job_2 = self.monitor._submit(NotifyHandler(), event, 'up')
|
job_2, new_2 = self.monitor._submit(NotifyHandler(), event, 'up')
|
||||||
self.assertIs(job_1, job_2)
|
self.assertIs(job_1, job_2)
|
||||||
|
self.assertTrue(new_1)
|
||||||
|
self.assertFalse(new_2)
|
||||||
self.monitor.pool.shutdown()
|
self.monitor.pool.shutdown()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user