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:
		@ -70,12 +70,12 @@ class SoundAdmin(SortableAdminBase, admin.ModelAdmin):
 | 
			
		||||
                if obj.type != Sound.TYPE_REMOVED else ''
 | 
			
		||||
    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['init_app'] = True
 | 
			
		||||
        context['init_el'] = '#inline-tracks'
 | 
			
		||||
        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):
 | 
			
		||||
        context = context or {}
 | 
			
		||||
 | 
			
		||||
@ -111,8 +111,12 @@ class Command(BaseCommand):
 | 
			
		||||
    def monitor(self):
 | 
			
		||||
        """ Run in monitor mode """
 | 
			
		||||
        with futures.ThreadPoolExecutor() as pool:
 | 
			
		||||
            archives_handler = MonitorHandler(settings.AIRCOX_SOUND_ARCHIVES_SUBDIR, pool)
 | 
			
		||||
            excerpts_handler = MonitorHandler(settings.AIRCOX_SOUND_EXCERPTS_SUBDIR, pool)
 | 
			
		||||
            archives_handler = MonitorHandler(
 | 
			
		||||
                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.schedule(archives_handler, settings.AIRCOX_PROGRAMS_DIR_ABS,
 | 
			
		||||
 | 
			
		||||
@ -61,12 +61,13 @@ class SoundFile:
 | 
			
		||||
    def episode(self):
 | 
			
		||||
        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.
 | 
			
		||||
        """
 | 
			
		||||
        if deleted:
 | 
			
		||||
            return self._on_delete(self.path)
 | 
			
		||||
            return self._on_delete(self.path, keep_deleted)
 | 
			
		||||
 | 
			
		||||
        # FIXME: sound.program as not null
 | 
			
		||||
        if not program:
 | 
			
		||||
@ -99,14 +100,18 @@ class SoundFile:
 | 
			
		||||
        self.find_playlist(sound)
 | 
			
		||||
        return sound
 | 
			
		||||
 | 
			
		||||
    def _on_delete(self, path):
 | 
			
		||||
    def _on_delete(self, path, keep_deleted):
 | 
			
		||||
        # TODO: remove from db on delete
 | 
			
		||||
        sound = Sound.objects.path(self.path).first()
 | 
			
		||||
        if sound:
 | 
			
		||||
            sound.type = sound.TYPE_REMOVED
 | 
			
		||||
            sound.check_on_file()
 | 
			
		||||
            sound.save()
 | 
			
		||||
        return sound
 | 
			
		||||
        if keep_deleted:
 | 
			
		||||
            sound = Sound.objects.path(self.path).first()
 | 
			
		||||
            if sound:
 | 
			
		||||
                if keep_deleted:
 | 
			
		||||
                    sound.type = sound.TYPE_REMOVED
 | 
			
		||||
                    sound.check_on_file()
 | 
			
		||||
                    sound.save()
 | 
			
		||||
            return sound
 | 
			
		||||
        else:
 | 
			
		||||
            Sound.objects.path(self.path).delete()
 | 
			
		||||
 | 
			
		||||
    def read_path(self, path):
 | 
			
		||||
        """
 | 
			
		||||
@ -144,7 +149,12 @@ class SoundFile:
 | 
			
		||||
 | 
			
		||||
    def read_file_info(self):
 | 
			
		||||
        """ 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):
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
@ -114,16 +114,16 @@ class MonitorHandler(PatternMatchingEventHandler):
 | 
			
		||||
    pool = None
 | 
			
		||||
    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.pool = pool
 | 
			
		||||
        if self.subdir == settings.AIRCOX_SOUND_ARCHIVES_SUBDIR:
 | 
			
		||||
            self.sync_kw = {'type': Sound.TYPE_ARCHIVE}
 | 
			
		||||
        else:
 | 
			
		||||
            self.sync_kw = {'type': Sound.TYPE_EXCERPT}
 | 
			
		||||
        self.sync_kw = sync_kw
 | 
			
		||||
 | 
			
		||||
        patterns = ['*/{}/*{}'.format(self.subdir, ext)
 | 
			
		||||
                    for ext in settings.AIRCOX_SOUND_FILE_EXT]
 | 
			
		||||
@ -156,7 +156,6 @@ class MonitorHandler(PatternMatchingEventHandler):
 | 
			
		||||
        self.jobs[key] = handler
 | 
			
		||||
 | 
			
		||||
        def done(r):
 | 
			
		||||
            print(':::: job done', key)
 | 
			
		||||
            if self.jobs.get(key) is handler:
 | 
			
		||||
                del self.jobs[key]
 | 
			
		||||
        handler.future.add_done_callback(done)
 | 
			
		||||
 | 
			
		||||
@ -130,6 +130,13 @@ ensure(
 | 
			
		||||
    ('.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
 | 
			
		||||
 | 
			
		||||
@ -62,7 +62,7 @@ class MonitorHandlerTestCase(TestCase):
 | 
			
		||||
    def test_submit_new_job(self):
 | 
			
		||||
        event = FakeEvent(src_path='dummy_src')
 | 
			
		||||
        handler = NotifyHandler()
 | 
			
		||||
        result = self.monitor._submit(handler, event, 'up')
 | 
			
		||||
        result, _ = self.monitor._submit(handler, event, 'up')
 | 
			
		||||
        self.assertIs(handler, result)
 | 
			
		||||
        self.assertIsInstance(handler.future, futures.Future)
 | 
			
		||||
        self.monitor.pool.shutdown()
 | 
			
		||||
@ -70,7 +70,9 @@ class MonitorHandlerTestCase(TestCase):
 | 
			
		||||
    def test_submit_job_exists(self):
 | 
			
		||||
        event = FakeEvent(src_path='dummy_src')
 | 
			
		||||
 | 
			
		||||
        job_1 = self.monitor._submit(WaitHandler(), event, 'up')
 | 
			
		||||
        job_2 = self.monitor._submit(NotifyHandler(), event, 'up')
 | 
			
		||||
        job_1, new_1 = self.monitor._submit(WaitHandler(), event, 'up')
 | 
			
		||||
        job_2, new_2 = self.monitor._submit(NotifyHandler(), event, 'up')
 | 
			
		||||
        self.assertIs(job_1, job_2)
 | 
			
		||||
        self.assertTrue(new_1)
 | 
			
		||||
        self.assertFalse(new_2)
 | 
			
		||||
        self.monitor.pool.shutdown()
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user