core: Add library.browse()

This commit is contained in:
Stein Magnus Jodal 2014-01-02 22:06:33 +01:00
parent a3731c8187
commit 6027ed1fac
2 changed files with 128 additions and 1 deletions

View File

@ -5,6 +5,8 @@ import urlparse
import pykka
from mopidy.models import Ref
class LibraryController(object):
pykka_traversable = True
@ -29,6 +31,63 @@ class LibraryController(object):
(b, None) for b in self.backends.with_library.values()])
return backends_to_uris
def browse(self, path):
"""
Browse directories and tracks at the given ``path``.
``path`` is a string that always starts with "/". It points to a
directory in Mopidy's virtual file system.
Returns a list of :class:`mopidy.models.Ref` objects for the
directories and tracks at the given ``path``.
The :class:`~mopidy.models.Ref` objects representing tracks keeps the
track's original URI. A matching pair of objects can look like this::
Track(uri='dummy:/foo.mp3', name='foo', artists=..., album=...)
Ref(uri='dummy:/foo.mp3', name='foo', type='track')
The :class:`~mopidy.models.Ref` objects representing directories has
plain paths, not including any URI schema. For example, the dummy
library's ``/bar`` directory is returned like this::
Ref(uri='/dummy/bar', name='bar', type='directory')
Note to backend implementors: The ``/dummy`` part of the URI is added
by Mopidy core, not the individual backends.
:param path: path to browse
:type path: string
:rtype: list of :class:`mopidy.models.Ref`
"""
if not path.startswith('/'):
return []
if path == '/':
library_names = [
backend.library.name.get()
for backend in self.backends.with_library.values()
if backend.library.browse('/').get()]
return [
Ref(uri='/%s' % name, name=name, type='directory')
for name in library_names]
uri_scheme = path.split('/', 2)[1]
backend = self.backends.with_library.get(uri_scheme, None)
if backend:
backend_path = path.replace('/%s' % uri_scheme, '')
if not backend_path.startswith('/'):
backend_path = '/%s' % backend_path
refs = backend.library.browse(backend_path).get()
result = []
for ref in refs:
if ref.type == 'directory':
result.append(
ref.copy(uri='/%s%s' % (uri_scheme, ref.uri)))
else:
result.append(ref)
return result
else:
return []
def find_exact(self, query=None, uris=None, **kwargs):
"""
Search the library for tracks where ``field`` is ``values``.

View File

@ -5,7 +5,7 @@ import unittest
from mopidy.backends import base
from mopidy.core import Core
from mopidy.models import SearchResult, Track
from mopidy.models import Ref, SearchResult, Track
class CoreLibraryTest(unittest.TestCase):
@ -13,11 +13,13 @@ class CoreLibraryTest(unittest.TestCase):
self.backend1 = mock.Mock()
self.backend1.uri_schemes.get.return_value = ['dummy1']
self.library1 = mock.Mock(spec=base.BaseLibraryProvider)
self.library1.name.get.return_value = 'dummy1'
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.library2.name.get.return_value = 'dummy2'
self.backend2.library = self.library2
# A backend without the optional library provider
@ -28,6 +30,72 @@ class CoreLibraryTest(unittest.TestCase):
self.core = Core(audio=None, backends=[
self.backend1, self.backend2, self.backend3])
def test_browse_root_returns_dir_ref_for_each_library_with_content(self):
result1 = [
Ref(uri='/foo/bar', name='bar', type='directory'),
Ref(uri='dummy1:/foo/baz.mp3', name='Baz', type='track'),
]
self.library1.browse().get.return_value = result1
self.library1.browse.reset_mock()
self.library2.browse().get.return_value = []
self.library2.browse.reset_mock()
result = self.core.library.browse('/')
self.assertEqual(result, [
Ref(uri='/dummy1', name='dummy1', type='directory'),
])
self.assertTrue(self.library1.browse.called)
self.assertTrue(self.library2.browse.called)
self.assertFalse(self.backend3.library.browse.called)
def test_browse_empty_string_returns_nothing(self):
result = self.core.library.browse('')
self.assertEqual(result, [])
self.assertFalse(self.library1.browse.called)
self.assertFalse(self.library2.browse.called)
def test_browse_dummy1_selects_dummy1_backend(self):
self.library1.browse().get.return_value = []
self.library1.browse.reset_mock()
self.core.library.browse('/dummy1/foo')
self.library1.browse.assert_called_once_with('/foo')
self.assertFalse(self.library2.browse.called)
def test_browse_dummy2_selects_dummy2_backend(self):
self.library2.browse().get.return_value = []
self.library2.browse.reset_mock()
self.core.library.browse('/dummy2/bar')
self.assertFalse(self.library1.browse.called)
self.library2.browse.assert_called_once_with('/bar')
def test_browse_dummy3_returns_nothing(self):
result = self.core.library.browse('/dummy3')
self.assertEqual(result, [])
self.assertFalse(self.library1.browse.called)
self.assertFalse(self.library2.browse.called)
def test_browse_dir_returns_subdirs_and_tracks(self):
result1 = [
Ref(uri='/foo/bar', name='bar', type='directory'),
Ref(uri='dummy1:/foo/baz.mp3', name='Baz', type='track'),
]
self.library1.browse().get.return_value = result1
self.library1.browse.reset_mock()
result = self.core.library.browse('/dummy1/foo')
self.assertEqual(result, [
Ref(uri='/dummy1/foo/bar', name='bar', type='directory'),
Ref(uri='dummy1:/foo/baz.mp3', name='Baz', type='track'),
])
def test_lookup_selects_dummy1_backend(self):
self.core.library.lookup('dummy1:a')