diff --git a/mopidy/core/library.py b/mopidy/core/library.py index 469b6160..80d9cbe5 100644 --- a/mopidy/core/library.py +++ b/mopidy/core/library.py @@ -1,10 +1,25 @@ +import pykka + +from mopidy.models import Playlist + + class LibraryController(object): pykka_traversable = True def __init__(self, backends, core): self.backends = backends + uri_schemes_by_backend = {backend: backend.uri_schemes.get() + for backend in backends} + self.backends_by_uri_scheme = {uri_scheme: backend + for backend, uri_schemes in uri_schemes_by_backend.items() + for uri_scheme in uri_schemes} + self.core = core + def _get_backend(self, uri): + uri_scheme = uri.split(':', 1)[0] + return self.backends_by_uri_scheme.get(uri_scheme) + def find_exact(self, **query): """ Search the library for tracks where ``field`` is ``values``. @@ -22,7 +37,12 @@ class LibraryController(object): :type query: dict :rtype: :class:`mopidy.models.Playlist` """ - return self.backends[0].library.find_exact(**query).get() + futures = [] + for backend in self.backends: + futures.append(backend.library.find_exact(**query)) + results = pykka.get_all(futures) + return Playlist(tracks=[ + track for playlist in results for track in playlist.tracks]) def lookup(self, uri): """ @@ -32,7 +52,9 @@ class LibraryController(object): :type uri: string :rtype: :class:`mopidy.models.Track` or :class:`None` """ - return self.backends[0].library.lookup(uri).get() + backend = self._get_backend(uri) + if backend: + return backend.library.lookup(uri).get() def refresh(self, uri=None): """ @@ -41,7 +63,15 @@ class LibraryController(object): :param uri: directory or track URI :type uri: string """ - return self.backends[0].library.refresh(uri).get() + if uri is not None: + backend = self._get_backend(uri) + if backend: + return backend.library.refresh(uri).get() + else: + futures = [] + for backend in self.backends: + futures.append(backend.library.refresh(uri)) + return pykka.get_all(futures) def search(self, **query): """ @@ -60,4 +90,9 @@ class LibraryController(object): :type query: dict :rtype: :class:`mopidy.models.Playlist` """ - return self.backends[0].library.search(**query).get() + futures = [] + for backend in self.backends: + futures.append(backend.library.search(**query)) + results = pykka.get_all(futures) + return Playlist(tracks=[ + track for playlist in results for track in playlist.tracks]) diff --git a/tests/core/library_test.py b/tests/core/library_test.py new file mode 100644 index 00000000..04f19909 --- /dev/null +++ b/tests/core/library_test.py @@ -0,0 +1,82 @@ +import mock + +from mopidy.backends import base +from mopidy.core import Core +from mopidy.models import Playlist, Track + +from tests import unittest + + +class CoreLibraryTest(unittest.TestCase): + def setUp(self): + self.backend1 = mock.Mock() + self.backend1.uri_schemes.get.return_value = ['dummy1'] + self.library1 = mock.Mock(spec=base.BaseLibraryProvider) + self.backend1.library = self.library1 + + self.backend2 = mock.Mock() + self.backend2.uri_schemes.get.return_value = ['dummy2'] + self.library2 = mock.Mock(spec=base.BaseLibraryProvider) + self.backend2.library = self.library2 + + self.core = Core(audio=None, backends=[self.backend1, self.backend2]) + + def test_lookup_selects_dummy1_backend(self): + self.core.library.lookup('dummy1:a') + + self.library1.lookup.assert_called_once_with('dummy1:a') + self.assertFalse(self.library2.lookup.called) + + def test_lookup_selects_dummy2_backend(self): + self.core.library.lookup('dummy2:a') + + self.assertFalse(self.library1.lookup.called) + self.library2.lookup.assert_called_once_with('dummy2:a') + + def test_refresh_with_uri_selects_dummy1_backend(self): + self.core.library.refresh('dummy1:a') + + self.library1.refresh.assert_called_once_with('dummy1:a') + self.assertFalse(self.library2.refresh.called) + + def test_refresh_with_uri_selects_dummy2_backend(self): + self.core.library.refresh('dummy2:a') + + self.assertFalse(self.library1.refresh.called) + self.library2.refresh.assert_called_once_with('dummy2:a') + + def test_refresh_without_uri_calls_all_backends(self): + self.core.library.refresh() + + self.library1.refresh.assert_called_once_with(None) + self.library2.refresh.assert_called_once_with(None) + + def test_find_exact_combines_results_from_all_backends(self): + track1 = Track(uri='dummy1:a') + track2 = Track(uri='dummy2:a') + self.library1.find_exact().get.return_value = Playlist(tracks=[track1]) + self.library1.find_exact.reset_mock() + self.library2.find_exact().get.return_value = Playlist(tracks=[track2]) + self.library2.find_exact.reset_mock() + + result = self.core.library.find_exact(any=['a']) + + self.assertIn(track1, result.tracks) + self.assertIn(track2, result.tracks) + self.library1.find_exact.assert_called_once_with(any=['a']) + self.library2.find_exact.assert_called_once_with(any=['a']) + + def test_search_combines_results_from_all_backends(self): + track1 = Track(uri='dummy1:a') + track2 = Track(uri='dummy2:a') + self.library1.search().get.return_value = Playlist(tracks=[track1]) + self.library1.search.reset_mock() + self.library2.search().get.return_value = Playlist(tracks=[track2]) + self.library2.search.reset_mock() + + result = self.core.library.search(any=['a']) + + self.assertIn(track1, result.tracks) + self.assertIn(track2, result.tracks) + self.library1.search.assert_called_once_with(any=['a']) + self.library2.search.assert_called_once_with(any=['a'])