diff --git a/docs/ext/local.rst b/docs/ext/local.rst index fc89e69a..1abebb1d 100644 --- a/docs/ext/local.rst +++ b/docs/ext/local.rst @@ -47,6 +47,11 @@ Configuration values Path to tag cache for local media. +.. confval:: local/scan_timeout + + Number of milliseconds before giving up scanning a file and moving on to + the next file. + Usage ===== diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index f718eeb5..3e2561a1 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -21,6 +21,7 @@ class Extension(ext.Extension): schema['media_dir'] = config.Path() schema['playlists_dir'] = config.Path() schema['tag_cache_file'] = config.Path() + schema['scan_timeout'] = config.Integer(minimum=0) return schema def validate_environment(self): diff --git a/mopidy/backends/local/ext.conf b/mopidy/backends/local/ext.conf index 54c3ab78..7e0f0f2b 100644 --- a/mopidy/backends/local/ext.conf +++ b/mopidy/backends/local/ext.conf @@ -3,3 +3,4 @@ enabled = true media_dir = $XDG_MUSIC_DIR playlists_dir = $XDG_DATA_DIR/mopidy/local/playlists tag_cache_file = $XDG_DATA_DIR/mopidy/local/tag_cache +scan_timeout = 1000 diff --git a/mopidy/scanner.py b/mopidy/scanner.py index 9f13d454..669f8182 100644 --- a/mopidy/scanner.py +++ b/mopidy/scanner.py @@ -54,6 +54,10 @@ def main(): logging.warning('Config value local/media_dir is not set.') return + if not config['local']['scan_timeout']: + logging.warning('Config value local/scan_timeout is not set.') + return + # TODO: missing config error checking and other default setup code. audio = dummy_audio.DummyAudio() @@ -97,9 +101,11 @@ def main(): logging.warning('Failed %s: %s', uri, error) logging.debug('Debug info for %s: %s', uri, debug) + scan_timeout = config['local']['scan_timeout'] + logging.info('Scanning new and modified tracks.') # TODO: just pass the library in instead? - scanner = Scanner(uris_update, store, debug) + scanner = Scanner(uris_update, store, debug, scan_timeout) try: scanner.start() except KeyboardInterrupt: @@ -176,12 +182,14 @@ def translator(data): class Scanner(object): - def __init__(self, uris, data_callback, error_callback=None): + def __init__(self, uris, data_callback, error_callback=None, scan_timeout=1000): self.data = {} self.uris = iter(uris) self.data_callback = data_callback self.error_callback = error_callback + self.scan_timeout = scan_timeout self.loop = gobject.MainLoop() + self.timeout_id = None self.fakesink = gst.element_factory_make('fakesink') self.fakesink.set_property('signal-handoffs', True) @@ -252,6 +260,14 @@ class Scanner(object): self.error_callback(uri, error, debug) self.next_uri() + def process_timeout(self): + if self.error_callback: + uri = self.uribin.get_property('uri') + self.error_callback( + uri, 'Scan timed out after %d ms' % self.scan_timeout, None) + self.next_uri() + return False + def get_duration(self): self.pipe.get_state() # Block until state change is done. try: @@ -262,6 +278,9 @@ class Scanner(object): def next_uri(self): self.data = {} + if self.timeout_id: + gobject.source_remove(self.timeout_id) + self.timeout_id = None try: uri = next(self.uris) except StopIteration: @@ -269,6 +288,7 @@ class Scanner(object): return False self.pipe.set_state(gst.STATE_NULL) self.uribin.set_property('uri', uri) + self.timeout_id = gobject.timeout_add(self.scan_timeout, self.process_timeout) self.pipe.set_state(gst.STATE_PLAYING) return True diff --git a/tests/data/scanner/example.log b/tests/data/scanner/example.log new file mode 100644 index 00000000..c49a044d Binary files /dev/null and b/tests/data/scanner/example.log differ diff --git a/tests/scanner_test.py b/tests/scanner_test.py index dcf891c0..903d6510 100644 --- a/tests/scanner_test.py +++ b/tests/scanner_test.py @@ -210,6 +210,10 @@ class ScannerTest(unittest.TestCase): self.scan('scanner/image') self.assert_(self.errors) + def test_log_file_is_ignored(self): + self.scan('scanner/example.log') + self.assert_(self.errors) + @unittest.SkipTest def test_song_without_time_is_handeled(self): pass