Merge pull request #647 from adamcik/feature/browse-by-uri
Convert browse API to be fully URI based.
This commit is contained in:
commit
da71d7fb14
@ -37,6 +37,9 @@ class Backend(object):
|
|||||||
def has_library(self):
|
def has_library(self):
|
||||||
return self.library is not None
|
return self.library is not None
|
||||||
|
|
||||||
|
def has_library_browse(self):
|
||||||
|
return self.has_library() and self.library.root_directory is not None
|
||||||
|
|
||||||
def has_playback(self):
|
def has_playback(self):
|
||||||
return self.playback is not None
|
return self.playback is not None
|
||||||
|
|
||||||
@ -52,9 +55,12 @@ class LibraryProvider(object):
|
|||||||
|
|
||||||
pykka_traversable = True
|
pykka_traversable = True
|
||||||
|
|
||||||
root_directory_name = None
|
root_directory = None
|
||||||
"""
|
"""
|
||||||
Name of the library's root directory in Mopidy's virtual file system.
|
:class:`models.Ref.directory` instance with a URI and name set
|
||||||
|
representing the root of this library's browse tree. URIs must
|
||||||
|
use one of the schemes supported by the backend, and name should
|
||||||
|
be set to a human friendly value.
|
||||||
|
|
||||||
*MUST be set by any class that implements :meth:`LibraryProvider.browse`.*
|
*MUST be set by any class that implements :meth:`LibraryProvider.browse`.*
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -88,7 +88,7 @@ class Backends(list):
|
|||||||
super(Backends, self).__init__(backends)
|
super(Backends, self).__init__(backends)
|
||||||
|
|
||||||
self.with_library = collections.OrderedDict()
|
self.with_library = collections.OrderedDict()
|
||||||
self.with_browsable_library = collections.OrderedDict()
|
self.with_library_browse = collections.OrderedDict()
|
||||||
self.with_playback = collections.OrderedDict()
|
self.with_playback = collections.OrderedDict()
|
||||||
self.with_playlists = collections.OrderedDict()
|
self.with_playlists = collections.OrderedDict()
|
||||||
|
|
||||||
@ -97,6 +97,7 @@ class Backends(list):
|
|||||||
|
|
||||||
for backend in backends:
|
for backend in backends:
|
||||||
has_library = backend.has_library().get()
|
has_library = backend.has_library().get()
|
||||||
|
has_library_browse = backend.has_library_browse().get()
|
||||||
has_playback = backend.has_playback().get()
|
has_playback = backend.has_playback().get()
|
||||||
has_playlists = backend.has_playlists().get()
|
has_playlists = backend.has_playlists().get()
|
||||||
|
|
||||||
@ -109,12 +110,9 @@ class Backends(list):
|
|||||||
|
|
||||||
if has_library:
|
if has_library:
|
||||||
self.with_library[scheme] = backend
|
self.with_library[scheme] = backend
|
||||||
|
if has_library_browse:
|
||||||
|
self.with_library_browse[scheme] = backend
|
||||||
if has_playback:
|
if has_playback:
|
||||||
self.with_playback[scheme] = backend
|
self.with_playback[scheme] = backend
|
||||||
if has_playlists:
|
if has_playlists:
|
||||||
self.with_playlists[scheme] = backend
|
self.with_playlists[scheme] = backend
|
||||||
|
|
||||||
if has_library:
|
|
||||||
root_dir_name = backend.library.root_directory_name.get()
|
|
||||||
if root_dir_name is not None:
|
|
||||||
self.with_browsable_library[root_dir_name] = backend
|
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import re
|
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
import pykka
|
import pykka
|
||||||
|
|
||||||
from mopidy.models import Ref
|
|
||||||
|
|
||||||
|
|
||||||
class LibraryController(object):
|
class LibraryController(object):
|
||||||
pykka_traversable = True
|
pykka_traversable = True
|
||||||
@ -32,15 +29,16 @@ class LibraryController(object):
|
|||||||
(b, None) for b in self.backends.with_library.values()])
|
(b, None) for b in self.backends.with_library.values()])
|
||||||
return backends_to_uris
|
return backends_to_uris
|
||||||
|
|
||||||
def browse(self, path):
|
def browse(self, uri):
|
||||||
"""
|
"""
|
||||||
Browse directories and tracks at the given ``path``.
|
Browse directories and tracks at the given ``uri``.
|
||||||
|
|
||||||
``path`` is a string that always starts with "/". It points to a
|
``uri`` is a sring which represents some directory belonging to a
|
||||||
directory in Mopidy's virtual file system.
|
backend. To get the intial root directories for backends pass None as
|
||||||
|
the URI.
|
||||||
|
|
||||||
Returns a list of :class:`mopidy.models.Ref` objects for the
|
Returns a list of :class:`mopidy.models.Ref` objects for the
|
||||||
directories and tracks at the given ``path``.
|
directories and tracks at the given ``uri``.
|
||||||
|
|
||||||
The :class:`~mopidy.models.Ref` objects representing tracks keep the
|
The :class:`~mopidy.models.Ref` objects representing tracks keep the
|
||||||
track's original URI. A matching pair of objects can look like this::
|
track's original URI. A matching pair of objects can look like this::
|
||||||
@ -49,45 +47,28 @@ class LibraryController(object):
|
|||||||
Ref.track(uri='dummy:/foo.mp3', name='foo')
|
Ref.track(uri='dummy:/foo.mp3', name='foo')
|
||||||
|
|
||||||
The :class:`~mopidy.models.Ref` objects representing directories have
|
The :class:`~mopidy.models.Ref` objects representing directories have
|
||||||
plain paths, not including any URI schema. For example, the dummy
|
backend specific URIs. These are opaque values, so no one but the
|
||||||
library's ``/bar`` directory is returned like this::
|
backend that created them should try and derive any meaning from them.
|
||||||
|
The only valid exception to this is checking the scheme, as it is used
|
||||||
|
to route browse requests to the correct backend.
|
||||||
|
|
||||||
Ref.directory(uri='/dummy/bar', name='bar')
|
For example, the dummy library's ``/bar`` directory could be returned
|
||||||
|
like this::
|
||||||
|
|
||||||
Note to backend implementors: The ``/dummy`` part of the URI is added
|
Ref.directory(uri='dummy:directory:/bar', name='bar')
|
||||||
by Mopidy core, not the individual backends.
|
|
||||||
|
|
||||||
:param path: path to browse
|
:param string uri: URI to browse
|
||||||
:type path: string
|
|
||||||
:rtype: list of :class:`mopidy.models.Ref`
|
:rtype: list of :class:`mopidy.models.Ref`
|
||||||
"""
|
"""
|
||||||
if not path.startswith('/'):
|
if uri is None:
|
||||||
return []
|
backends = self.backends.with_library_browse.values()
|
||||||
|
return [b.library.root_directory.get() for b in backends]
|
||||||
|
|
||||||
if path == '/':
|
scheme = urlparse.urlparse(uri).scheme
|
||||||
return [
|
backend = self.backends.with_library_browse.get(scheme)
|
||||||
Ref.directory(uri='/%s' % name, name=name)
|
|
||||||
for name in self.backends.with_browsable_library.keys()]
|
|
||||||
|
|
||||||
groups = re.match('/(?P<library>[^/]+)(?P<path>.*)', path).groupdict()
|
|
||||||
library_name = groups['library']
|
|
||||||
backend_path = groups['path']
|
|
||||||
if not backend_path.startswith('/'):
|
|
||||||
backend_path = '/%s' % backend_path
|
|
||||||
|
|
||||||
backend = self.backends.with_browsable_library.get(library_name, None)
|
|
||||||
if not backend:
|
if not backend:
|
||||||
return []
|
return []
|
||||||
|
return backend.library.browse(uri).get()
|
||||||
refs = backend.library.browse(backend_path).get()
|
|
||||||
result = []
|
|
||||||
for ref in refs:
|
|
||||||
if ref.type == Ref.DIRECTORY:
|
|
||||||
uri = '/'.join(['', library_name, ref.uri.lstrip('/')])
|
|
||||||
result.append(ref.copy(uri=uri))
|
|
||||||
else:
|
|
||||||
result.append(ref)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def find_exact(self, query=None, uris=None, **kwargs):
|
def find_exact(self, query=None, uris=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -49,41 +49,31 @@ class _BrowseCache(object):
|
|||||||
splitpath_re = re.compile(r'([^/]+)')
|
splitpath_re = re.compile(r'([^/]+)')
|
||||||
|
|
||||||
def __init__(self, uris):
|
def __init__(self, uris):
|
||||||
"""Create a dictionary tree for quick browsing.
|
# {parent_uri: {uri: ref}}
|
||||||
|
self._cache = {}
|
||||||
|
|
||||||
{'foo': {'bar': {None: [ref1, ref2]},
|
for track_uri in uris:
|
||||||
'baz': {},
|
path = translator.local_track_uri_to_path(track_uri, b'/')
|
||||||
None: [ref3]}}
|
|
||||||
"""
|
|
||||||
self._root = collections.OrderedDict()
|
|
||||||
|
|
||||||
for uri in uris:
|
|
||||||
path = translator.local_track_uri_to_path(uri, b'/')
|
|
||||||
parts = self.splitpath_re.findall(
|
parts = self.splitpath_re.findall(
|
||||||
path.decode(self.encoding, 'replace'))
|
path.decode(self.encoding, 'replace'))
|
||||||
filename = parts.pop()
|
track_ref = models.Ref.track(uri=track_uri, name=parts.pop())
|
||||||
node = self._root
|
|
||||||
for part in parts:
|
|
||||||
node = node.setdefault(part, collections.OrderedDict())
|
|
||||||
ref = models.Ref.track(uri=uri, name=filename)
|
|
||||||
node.setdefault(None, []).append(ref)
|
|
||||||
|
|
||||||
def lookup(self, path):
|
parent = 'local:directory'
|
||||||
results = []
|
for i in range(len(parts)):
|
||||||
node = self._root
|
self._cache.setdefault(parent, collections.OrderedDict())
|
||||||
|
|
||||||
for part in self.splitpath_re.findall(path):
|
directory = '/'.join(parts[:i+1])
|
||||||
node = node.get(part, {})
|
dir_uri = translator.path_to_local_directory_uri(directory)
|
||||||
|
dir_ref = models.Ref.directory(uri=dir_uri, name=parts[i])
|
||||||
|
self._cache[parent][dir_uri] = dir_ref
|
||||||
|
|
||||||
for key, value in node.items():
|
parent = dir_uri
|
||||||
if key is not None:
|
|
||||||
uri = os.path.join(path, key)
|
|
||||||
results.append(models.Ref.directory(uri=uri, name=key))
|
|
||||||
|
|
||||||
# Get tracks afterwards to ensure ordering.
|
self._cache.setdefault(parent, collections.OrderedDict())
|
||||||
results.extend(node.get(None, []))
|
self._cache[parent][track_uri] = track_ref
|
||||||
|
|
||||||
return results
|
def lookup(self, uri):
|
||||||
|
return self._cache.get(uri, {}).values()
|
||||||
|
|
||||||
|
|
||||||
class JsonLibrary(local.Library):
|
class JsonLibrary(local.Library):
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from mopidy import backend
|
from mopidy import backend, models
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -10,7 +10,8 @@ logger = logging.getLogger(__name__)
|
|||||||
class LocalLibraryProvider(backend.LibraryProvider):
|
class LocalLibraryProvider(backend.LibraryProvider):
|
||||||
"""Proxy library that delegates work to our active local library."""
|
"""Proxy library that delegates work to our active local library."""
|
||||||
|
|
||||||
root_directory_name = 'local'
|
root_directory = models.Ref.directory(uri=b'local:directory',
|
||||||
|
name='Local media')
|
||||||
|
|
||||||
def __init__(self, backend, library):
|
def __init__(self, backend, library):
|
||||||
super(LocalLibraryProvider, self).__init__(backend)
|
super(LocalLibraryProvider, self).__init__(backend)
|
||||||
|
|||||||
@ -33,6 +33,13 @@ def path_to_local_track_uri(relpath):
|
|||||||
return b'local:track:%s' % urllib.quote(relpath)
|
return b'local:track:%s' % urllib.quote(relpath)
|
||||||
|
|
||||||
|
|
||||||
|
def path_to_local_directory_uri(relpath):
|
||||||
|
"""Convert path relative to :confval:`local/media_dir` directory URI."""
|
||||||
|
if isinstance(relpath, unicode):
|
||||||
|
relpath = relpath.encode('utf-8')
|
||||||
|
return b'local:directory:%s' % urllib.quote(relpath)
|
||||||
|
|
||||||
|
|
||||||
def m3u_extinf_to_track(line):
|
def m3u_extinf_to_track(line):
|
||||||
"""Convert extended M3U directive to track template."""
|
"""Convert extended M3U directive to track template."""
|
||||||
m = M3U_EXTINF_RE.match(line)
|
m = M3U_EXTINF_RE.match(line)
|
||||||
|
|||||||
@ -297,3 +297,19 @@ class MpdContext(object):
|
|||||||
if uri not in self._playlist_name_from_uri:
|
if uri not in self._playlist_name_from_uri:
|
||||||
self.refresh_playlists_mapping()
|
self.refresh_playlists_mapping()
|
||||||
return self._playlist_name_from_uri[uri]
|
return self._playlist_name_from_uri[uri]
|
||||||
|
|
||||||
|
# TODO: consider making context.browse(path) which uses this internally.
|
||||||
|
# advantage would be that all browse requests then go through the same code
|
||||||
|
# and we could prebuild/cache path->uri relationships instead of having to
|
||||||
|
# look them up all the time.
|
||||||
|
def directory_path_to_uri(self, path):
|
||||||
|
parts = re.findall(r'[^/]+', path)
|
||||||
|
uri = None
|
||||||
|
for part in parts:
|
||||||
|
for ref in self.core.library.browse(uri).get():
|
||||||
|
if ref.type == ref.DIRECTORY and ref.name == part:
|
||||||
|
uri = ref.uri
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise exceptions.MpdNoExistError()
|
||||||
|
return uri
|
||||||
|
|||||||
@ -27,8 +27,12 @@ def add(context, uri):
|
|||||||
if tl_tracks:
|
if tl_tracks:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not uri.startswith('/'):
|
try:
|
||||||
uri = '/%s' % uri
|
uri = context.directory_path_to_uri(translator.normalize_path(uri))
|
||||||
|
except MpdNoExistError as e:
|
||||||
|
e.command = 'add'
|
||||||
|
e.message = 'directory or file not found'
|
||||||
|
raise
|
||||||
|
|
||||||
browse_futures = [context.core.library.browse(uri)]
|
browse_futures = [context.core.library.browse(uri)]
|
||||||
lookup_futures = []
|
lookup_futures = []
|
||||||
|
|||||||
@ -417,25 +417,33 @@ def listall(context, uri=None):
|
|||||||
|
|
||||||
Lists all songs and directories in ``URI``.
|
Lists all songs and directories in ``URI``.
|
||||||
"""
|
"""
|
||||||
if uri is None:
|
|
||||||
uri = '/'
|
|
||||||
if not uri.startswith('/'):
|
|
||||||
uri = '/%s' % uri
|
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
browse_futures = [context.core.library.browse(uri)]
|
root_path = translator.normalize_path(uri)
|
||||||
|
# TODO: doesn't the dispatcher._call_handler have enough info to catch
|
||||||
|
# the error this can produce, set the command and then 'raise'?
|
||||||
|
try:
|
||||||
|
uri = context.directory_path_to_uri(root_path)
|
||||||
|
except MpdNoExistError as e:
|
||||||
|
e.command = 'listall'
|
||||||
|
e.message = 'Not found'
|
||||||
|
raise
|
||||||
|
browse_futures = [(root_path, context.core.library.browse(uri))]
|
||||||
|
|
||||||
while browse_futures:
|
while browse_futures:
|
||||||
for ref in browse_futures.pop().get():
|
base_path, future = browse_futures.pop()
|
||||||
|
for ref in future.get():
|
||||||
if ref.type == Ref.DIRECTORY:
|
if ref.type == Ref.DIRECTORY:
|
||||||
result.append(('directory', ref.uri))
|
path = '/'.join([base_path, ref.name.replace('/', '')])
|
||||||
browse_futures.append(context.core.library.browse(ref.uri))
|
result.append(('directory', path))
|
||||||
|
browse_futures.append(
|
||||||
|
(path, context.core.library.browse(ref.uri)))
|
||||||
elif ref.type == Ref.TRACK:
|
elif ref.type == Ref.TRACK:
|
||||||
result.append(('file', ref.uri))
|
result.append(('file', ref.uri))
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
raise MpdNoExistError('Not found')
|
raise MpdNoExistError('Not found')
|
||||||
|
|
||||||
return [('directory', uri)] + result
|
return [('directory', root_path)] + result
|
||||||
|
|
||||||
|
|
||||||
@handle_request(r'listallinfo$')
|
@handle_request(r'listallinfo$')
|
||||||
@ -449,18 +457,25 @@ def listallinfo(context, uri=None):
|
|||||||
Same as ``listall``, except it also returns metadata info in the
|
Same as ``listall``, except it also returns metadata info in the
|
||||||
same format as ``lsinfo``.
|
same format as ``lsinfo``.
|
||||||
"""
|
"""
|
||||||
if uri is None:
|
|
||||||
uri = '/'
|
|
||||||
if not uri.startswith('/'):
|
|
||||||
uri = '/%s' % uri
|
|
||||||
|
|
||||||
dirs_and_futures = []
|
dirs_and_futures = []
|
||||||
browse_futures = [context.core.library.browse(uri)]
|
result = []
|
||||||
|
root_path = translator.normalize_path(uri)
|
||||||
|
try:
|
||||||
|
uri = context.directory_path_to_uri(root_path)
|
||||||
|
except MpdNoExistError as e:
|
||||||
|
e.command = 'listallinfo'
|
||||||
|
e.message = 'Not found'
|
||||||
|
raise
|
||||||
|
browse_futures = [(root_path, context.core.library.browse(uri))]
|
||||||
|
|
||||||
while browse_futures:
|
while browse_futures:
|
||||||
for ref in browse_futures.pop().get():
|
base_path, future = browse_futures.pop()
|
||||||
|
for ref in future.get():
|
||||||
if ref.type == Ref.DIRECTORY:
|
if ref.type == Ref.DIRECTORY:
|
||||||
dirs_and_futures.append(('directory', ref.uri))
|
path = '/'.join([base_path, ref.name.replace('/', '')])
|
||||||
browse_futures.append(context.core.library.browse(ref.uri))
|
future = context.core.library.browse(ref.uri)
|
||||||
|
browse_futures.append((path, future))
|
||||||
|
dirs_and_futures.append(('directory', path))
|
||||||
elif ref.type == Ref.TRACK:
|
elif ref.type == Ref.TRACK:
|
||||||
# TODO Lookup tracks in batch for better performance
|
# TODO Lookup tracks in batch for better performance
|
||||||
dirs_and_futures.append(context.core.library.lookup(ref.uri))
|
dirs_and_futures.append(context.core.library.lookup(ref.uri))
|
||||||
@ -476,7 +491,7 @@ def listallinfo(context, uri=None):
|
|||||||
if not result:
|
if not result:
|
||||||
raise MpdNoExistError('Not found')
|
raise MpdNoExistError('Not found')
|
||||||
|
|
||||||
return [('directory', uri)] + result
|
return [('directory', root_path)] + result
|
||||||
|
|
||||||
|
|
||||||
@handle_request(r'lsinfo$')
|
@handle_request(r'lsinfo$')
|
||||||
@ -498,16 +513,21 @@ def lsinfo(context, uri=None):
|
|||||||
""``, and ``lsinfo "/"``.
|
""``, and ``lsinfo "/"``.
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
if uri is None or uri == '/' or uri == '':
|
root_path = translator.normalize_path(uri, relative=True)
|
||||||
|
try:
|
||||||
|
uri = context.directory_path_to_uri(root_path)
|
||||||
|
except MpdNoExistError as e:
|
||||||
|
e.command = 'lsinfo'
|
||||||
|
e.message = 'Not found'
|
||||||
|
raise
|
||||||
|
|
||||||
|
if uri is None:
|
||||||
result.extend(stored_playlists.listplaylists(context))
|
result.extend(stored_playlists.listplaylists(context))
|
||||||
uri = '/'
|
|
||||||
if not uri.startswith('/'):
|
|
||||||
uri = '/%s' % uri
|
|
||||||
for ref in context.core.library.browse(uri).get():
|
for ref in context.core.library.browse(uri).get():
|
||||||
if ref.type == Ref.DIRECTORY:
|
if ref.type == Ref.DIRECTORY:
|
||||||
assert ref.uri.startswith('/'), (
|
path = '/'.join([root_path, ref.name.replace('/', '')])
|
||||||
'Directory URIs must start with /: %r' % ref)
|
result.append(('directory', path.lstrip('/')))
|
||||||
result.append(('directory', ref.uri[1:]))
|
|
||||||
elif ref.type == Ref.TRACK:
|
elif ref.type == Ref.TRACK:
|
||||||
# TODO Lookup tracks in batch for better performance
|
# TODO Lookup tracks in batch for better performance
|
||||||
tracks = context.core.library.lookup(ref.uri).get()
|
tracks = context.core.library.lookup(ref.uri).get()
|
||||||
|
|||||||
@ -1,11 +1,20 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
from mopidy.mpd.exceptions import MpdArgError
|
from mopidy.mpd.exceptions import MpdArgError
|
||||||
from mopidy.models import TlTrack
|
from mopidy.models import TlTrack
|
||||||
|
|
||||||
# TODO: special handling of local:// uri scheme
|
# TODO: special handling of local:// uri scheme
|
||||||
|
normalize_path_re = re.compile(r'[^/]+')
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_path(path, relative=False):
|
||||||
|
parts = normalize_path_re.findall(path or '')
|
||||||
|
if not relative:
|
||||||
|
parts.insert(0, '')
|
||||||
|
return '/'.join(parts)
|
||||||
|
|
||||||
|
|
||||||
def track_to_mpd_format(track, position=None):
|
def track_to_mpd_format(track, position=None):
|
||||||
|
|||||||
@ -9,32 +9,35 @@ from mopidy.models import Ref, SearchResult, Track
|
|||||||
|
|
||||||
class CoreLibraryTest(unittest.TestCase):
|
class CoreLibraryTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
dummy1_root = Ref.directory(uri='dummy1:directory', name='dummy1')
|
||||||
self.backend1 = mock.Mock()
|
self.backend1 = mock.Mock()
|
||||||
self.backend1.uri_schemes.get.return_value = ['dummy1']
|
self.backend1.uri_schemes.get.return_value = ['dummy1']
|
||||||
self.library1 = mock.Mock(spec=backend.LibraryProvider)
|
self.library1 = mock.Mock(spec=backend.LibraryProvider)
|
||||||
self.library1.root_directory_name.get.return_value = 'dummy1'
|
self.library1.root_directory.get.return_value = dummy1_root
|
||||||
self.backend1.library = self.library1
|
self.backend1.library = self.library1
|
||||||
|
|
||||||
|
dummy2_root = Ref.directory(uri='dummy2:directory', name='dummy2')
|
||||||
self.backend2 = mock.Mock()
|
self.backend2 = mock.Mock()
|
||||||
self.backend2.uri_schemes.get.return_value = ['dummy2']
|
self.backend2.uri_schemes.get.return_value = ['dummy2']
|
||||||
self.library2 = mock.Mock(spec=backend.LibraryProvider)
|
self.library2 = mock.Mock(spec=backend.LibraryProvider)
|
||||||
self.library2.root_directory_name.get.return_value = 'dummy2'
|
self.library2.root_directory.get.return_value = dummy2_root
|
||||||
self.backend2.library = self.library2
|
self.backend2.library = self.library2
|
||||||
|
|
||||||
# A backend without the optional library provider
|
# A backend without the optional library provider
|
||||||
self.backend3 = mock.Mock()
|
self.backend3 = mock.Mock()
|
||||||
self.backend3.uri_schemes.get.return_value = ['dummy3']
|
self.backend3.uri_schemes.get.return_value = ['dummy3']
|
||||||
self.backend3.has_library().get.return_value = False
|
self.backend3.has_library().get.return_value = False
|
||||||
|
self.backend3.has_library_browse().get.return_value = False
|
||||||
|
|
||||||
self.core = core.Core(audio=None, backends=[
|
self.core = core.Core(audio=None, backends=[
|
||||||
self.backend1, self.backend2, self.backend3])
|
self.backend1, self.backend2, self.backend3])
|
||||||
|
|
||||||
def test_browse_root_returns_dir_ref_for_each_lib_with_root_dir_name(self):
|
def test_browse_root_returns_dir_ref_for_each_lib_with_root_dir_name(self):
|
||||||
result = self.core.library.browse('/')
|
result = self.core.library.browse(None)
|
||||||
|
|
||||||
self.assertEqual(result, [
|
self.assertEqual(result, [
|
||||||
Ref.directory(uri='/dummy1', name='dummy1'),
|
Ref.directory(uri='dummy1:directory', name='dummy1'),
|
||||||
Ref.directory(uri='/dummy2', name='dummy2'),
|
Ref.directory(uri='dummy2:directory', name='dummy2'),
|
||||||
])
|
])
|
||||||
self.assertFalse(self.library1.browse.called)
|
self.assertFalse(self.library1.browse.called)
|
||||||
self.assertFalse(self.library2.browse.called)
|
self.assertFalse(self.library2.browse.called)
|
||||||
@ -49,32 +52,32 @@ class CoreLibraryTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_browse_dummy1_selects_dummy1_backend(self):
|
def test_browse_dummy1_selects_dummy1_backend(self):
|
||||||
self.library1.browse().get.return_value = [
|
self.library1.browse().get.return_value = [
|
||||||
Ref.directory(uri='/foo/bar', name='bar'),
|
Ref.directory(uri='dummy1:directory:/foo/bar', name='bar'),
|
||||||
Ref.track(uri='dummy1:/foo/baz.mp3', name='Baz'),
|
Ref.track(uri='dummy1:track:/foo/baz.mp3', name='Baz'),
|
||||||
]
|
]
|
||||||
self.library1.browse.reset_mock()
|
self.library1.browse.reset_mock()
|
||||||
|
|
||||||
self.core.library.browse('/dummy1/foo')
|
self.core.library.browse('dummy1:directory:/foo')
|
||||||
|
|
||||||
self.assertEqual(self.library1.browse.call_count, 1)
|
self.assertEqual(self.library1.browse.call_count, 1)
|
||||||
self.assertEqual(self.library2.browse.call_count, 0)
|
self.assertEqual(self.library2.browse.call_count, 0)
|
||||||
self.library1.browse.assert_called_with('/foo')
|
self.library1.browse.assert_called_with('dummy1:directory:/foo')
|
||||||
|
|
||||||
def test_browse_dummy2_selects_dummy2_backend(self):
|
def test_browse_dummy2_selects_dummy2_backend(self):
|
||||||
self.library2.browse().get.return_value = [
|
self.library2.browse().get.return_value = [
|
||||||
Ref.directory(uri='/bar/quux', name='quux'),
|
Ref.directory(uri='dummy2:directory:/bar/baz', name='quux'),
|
||||||
Ref.track(uri='dummy2:/foo/baz.mp3', name='Baz'),
|
Ref.track(uri='dummy2:track:/bar/foo.mp3', name='Baz'),
|
||||||
]
|
]
|
||||||
self.library2.browse.reset_mock()
|
self.library2.browse.reset_mock()
|
||||||
|
|
||||||
self.core.library.browse('/dummy2/bar')
|
self.core.library.browse('dummy2:directory:/bar')
|
||||||
|
|
||||||
self.assertEqual(self.library1.browse.call_count, 0)
|
self.assertEqual(self.library1.browse.call_count, 0)
|
||||||
self.assertEqual(self.library2.browse.call_count, 1)
|
self.assertEqual(self.library2.browse.call_count, 1)
|
||||||
self.library2.browse.assert_called_with('/bar')
|
self.library2.browse.assert_called_with('dummy2:directory:/bar')
|
||||||
|
|
||||||
def test_browse_dummy3_returns_nothing(self):
|
def test_browse_dummy3_returns_nothing(self):
|
||||||
result = self.core.library.browse('/dummy3')
|
result = self.core.library.browse('dummy3:test')
|
||||||
|
|
||||||
self.assertEqual(result, [])
|
self.assertEqual(result, [])
|
||||||
self.assertEqual(self.library1.browse.call_count, 0)
|
self.assertEqual(self.library1.browse.call_count, 0)
|
||||||
@ -82,16 +85,15 @@ class CoreLibraryTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_browse_dir_returns_subdirs_and_tracks(self):
|
def test_browse_dir_returns_subdirs_and_tracks(self):
|
||||||
self.library1.browse().get.return_value = [
|
self.library1.browse().get.return_value = [
|
||||||
Ref.directory(uri='/foo/bar', name='bar'),
|
Ref.directory(uri='dummy1:directory:/foo/bar', name='Bar'),
|
||||||
Ref.track(uri='dummy1:/foo/baz.mp3', name='Baz'),
|
Ref.track(uri='dummy1:track:/foo/baz.mp3', name='Baz'),
|
||||||
]
|
]
|
||||||
self.library1.browse.reset_mock()
|
self.library1.browse.reset_mock()
|
||||||
|
|
||||||
result = self.core.library.browse('/dummy1/foo')
|
result = self.core.library.browse('dummy1:directory:/foo')
|
||||||
|
|
||||||
self.assertEqual(result, [
|
self.assertEqual(result, [
|
||||||
Ref.directory(uri='/dummy1/foo/bar', name='bar'),
|
Ref.directory(uri='dummy1:directory:/foo/bar', name='Bar'),
|
||||||
Ref.track(uri='dummy1:/foo/baz.mp3', name='Baz'),
|
Ref.track(uri='dummy1:track:/foo/baz.mp3', name='Baz'),
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_lookup_selects_dummy1_backend(self):
|
def test_lookup_selects_dummy1_backend(self):
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from __future__ import unicode_literals
|
|||||||
import pykka
|
import pykka
|
||||||
|
|
||||||
from mopidy import backend
|
from mopidy import backend
|
||||||
from mopidy.models import Playlist, SearchResult
|
from mopidy.models import Playlist, Ref, SearchResult
|
||||||
|
|
||||||
|
|
||||||
def create_dummy_backend_proxy(config=None, audio=None):
|
def create_dummy_backend_proxy(config=None, audio=None):
|
||||||
@ -28,7 +28,7 @@ class DummyBackend(pykka.ThreadingActor, backend.Backend):
|
|||||||
|
|
||||||
|
|
||||||
class DummyLibraryProvider(backend.LibraryProvider):
|
class DummyLibraryProvider(backend.LibraryProvider):
|
||||||
root_directory_name = 'dummy'
|
root_directory = Ref.directory(uri='dummy:/', name='dummy')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(DummyLibraryProvider, self).__init__(*args, **kwargs)
|
super(DummyLibraryProvider, self).__init__(*args, **kwargs)
|
||||||
|
|||||||
@ -14,23 +14,19 @@ class BrowseCacheTest(unittest.TestCase):
|
|||||||
self.cache = json._BrowseCache(self.uris)
|
self.cache = json._BrowseCache(self.uris)
|
||||||
|
|
||||||
def test_lookup_root(self):
|
def test_lookup_root(self):
|
||||||
expected = [Ref.directory(uri='/foo', name='foo')]
|
expected = [Ref.directory(uri='local:directory:foo', name='foo')]
|
||||||
self.assertEqual(expected, self.cache.lookup('/'))
|
self.assertEqual(expected, self.cache.lookup('local:directory'))
|
||||||
|
|
||||||
def test_lookup_foo(self):
|
def test_lookup_foo(self):
|
||||||
expected = [Ref.directory(uri='/foo/bar', name='bar'),
|
expected = [Ref.directory(uri='local:directory:foo/bar', name='bar'),
|
||||||
Ref.track(uri=self.uris[2], name='song3')]
|
Ref.track(uri=self.uris[2], name='song3')]
|
||||||
self.assertEqual(expected, self.cache.lookup('/foo'))
|
self.assertEqual(expected, self.cache.lookup('local:directory:foo'))
|
||||||
|
|
||||||
def test_lookup_foo_bar(self):
|
def test_lookup_foo_bar(self):
|
||||||
expected = [Ref.track(uri=self.uris[0], name='song1'),
|
expected = [Ref.track(uri=self.uris[0], name='song1'),
|
||||||
Ref.track(uri=self.uris[1], name='song2')]
|
Ref.track(uri=self.uris[1], name='song2')]
|
||||||
self.assertEqual(expected, self.cache.lookup('/foo/bar'))
|
self.assertEqual(
|
||||||
|
expected, self.cache.lookup('local:directory:foo/bar'))
|
||||||
|
|
||||||
def test_lookup_foo_baz(self):
|
def test_lookup_foo_baz(self):
|
||||||
self.assertEqual([], self.cache.lookup('/foo/baz'))
|
self.assertEqual([], self.cache.lookup('local:directory:foo/baz'))
|
||||||
|
|
||||||
def test_lookup_normalize_slashes(self):
|
|
||||||
expected = [Ref.track(uri=self.uris[0], name='song1'),
|
|
||||||
Ref.track(uri=self.uris[1], name='song2')]
|
|
||||||
self.assertEqual(expected, self.cache.lookup('/foo//bar/'))
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@ class CurrentPlaylistHandlerTest(protocol.BaseTestCase):
|
|||||||
def test_add_with_empty_uri_should_not_add_anything_and_ok(self):
|
def test_add_with_empty_uri_should_not_add_anything_and_ok(self):
|
||||||
self.backend.library.dummy_library = [Track(uri='dummy:/a', name='a')]
|
self.backend.library.dummy_library = [Track(uri='dummy:/a', name='a')]
|
||||||
self.backend.library.dummy_browse_result = {
|
self.backend.library.dummy_browse_result = {
|
||||||
'/': [Ref.track(uri='dummy:/a', name='a')]}
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a')]}
|
||||||
|
|
||||||
self.sendRequest('add ""')
|
self.sendRequest('add ""')
|
||||||
self.assertEqual(len(self.core.tracklist.tracks.get()), 0)
|
self.assertEqual(len(self.core.tracklist.tracks.get()), 0)
|
||||||
@ -35,13 +35,13 @@ class CurrentPlaylistHandlerTest(protocol.BaseTestCase):
|
|||||||
|
|
||||||
def test_add_with_library_should_recurse(self):
|
def test_add_with_library_should_recurse(self):
|
||||||
tracks = [Track(uri='dummy:/a', name='a'),
|
tracks = [Track(uri='dummy:/a', name='a'),
|
||||||
Track(uri='dummy:/b', name='b')]
|
Track(uri='dummy:/foo/b', name='b')]
|
||||||
|
|
||||||
self.backend.library.dummy_library = tracks
|
self.backend.library.dummy_library = tracks
|
||||||
self.backend.library.dummy_browse_result = {
|
self.backend.library.dummy_browse_result = {
|
||||||
'/': [Ref.track(uri='dummy:/a', name='a'),
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
Ref.directory(uri='/foo')],
|
Ref.directory(uri='dummy:/foo', name='foo')],
|
||||||
'/foo': [Ref.track(uri='dummy:/b', name='b')]}
|
'dummy:/foo': [Ref.track(uri='dummy:/foo/b', name='b')]}
|
||||||
|
|
||||||
self.sendRequest('add "/dummy"')
|
self.sendRequest('add "/dummy"')
|
||||||
self.assertEqual(self.core.tracklist.tracks.get(), tracks)
|
self.assertEqual(self.core.tracklist.tracks.get(), tracks)
|
||||||
@ -50,7 +50,7 @@ class CurrentPlaylistHandlerTest(protocol.BaseTestCase):
|
|||||||
def test_add_root_should_not_add_anything_and_ok(self):
|
def test_add_root_should_not_add_anything_and_ok(self):
|
||||||
self.backend.library.dummy_library = [Track(uri='dummy:/a', name='a')]
|
self.backend.library.dummy_library = [Track(uri='dummy:/a', name='a')]
|
||||||
self.backend.library.dummy_browse_result = {
|
self.backend.library.dummy_browse_result = {
|
||||||
'/': [Ref.track(uri='dummy:/a', name='a')]}
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a')]}
|
||||||
|
|
||||||
self.sendRequest('add "/"')
|
self.sendRequest('add "/"')
|
||||||
self.assertEqual(len(self.core.tracklist.tracks.get()), 0)
|
self.assertEqual(len(self.core.tracklist.tracks.get()), 0)
|
||||||
|
|||||||
@ -124,34 +124,34 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase):
|
|||||||
|
|
||||||
def test_listall_without_uri(self):
|
def test_listall_without_uri(self):
|
||||||
tracks = [Track(uri='dummy:/a', name='a'),
|
tracks = [Track(uri='dummy:/a', name='a'),
|
||||||
Track(uri='dummy:/b', name='b')]
|
Track(uri='dummy:/foo/b', name='b')]
|
||||||
self.backend.library.dummy_library = tracks
|
self.backend.library.dummy_library = tracks
|
||||||
self.backend.library.dummy_browse_result = {
|
self.backend.library.dummy_browse_result = {
|
||||||
'/': [Ref.track(uri='dummy:/a', name='a'),
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
Ref.directory(uri='/foo')],
|
Ref.directory(uri='dummy:/foo', name='foo')],
|
||||||
'/foo': [Ref.track(uri='dummy:/b', name='b')]}
|
'dummy:/foo': [Ref.track(uri='dummy:/foo/b', name='b')]}
|
||||||
|
|
||||||
self.sendRequest('listall')
|
self.sendRequest('listall')
|
||||||
|
|
||||||
self.assertInResponse('file: dummy:/a')
|
self.assertInResponse('file: dummy:/a')
|
||||||
self.assertInResponse('directory: /dummy/foo')
|
self.assertInResponse('directory: /dummy/foo')
|
||||||
self.assertInResponse('file: dummy:/b')
|
self.assertInResponse('file: dummy:/foo/b')
|
||||||
self.assertInResponse('OK')
|
self.assertInResponse('OK')
|
||||||
|
|
||||||
def test_listall_with_uri(self):
|
def test_listall_with_uri(self):
|
||||||
tracks = [Track(uri='dummy:/a', name='a'),
|
tracks = [Track(uri='dummy:/a', name='a'),
|
||||||
Track(uri='dummy:/b', name='b')]
|
Track(uri='dummy:/foo/b', name='b')]
|
||||||
self.backend.library.dummy_library = tracks
|
self.backend.library.dummy_library = tracks
|
||||||
self.backend.library.dummy_browse_result = {
|
self.backend.library.dummy_browse_result = {
|
||||||
'/': [Ref.track(uri='dummy:/a', name='a'),
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
Ref.directory(uri='/foo')],
|
Ref.directory(uri='dummy:/foo', name='foo')],
|
||||||
'/foo': [Ref.track(uri='dummy:/b', name='b')]}
|
'dummy:/foo': [Ref.track(uri='dummy:/foo/b', name='b')]}
|
||||||
|
|
||||||
self.sendRequest('listall "/dummy/foo"')
|
self.sendRequest('listall "/dummy/foo"')
|
||||||
|
|
||||||
self.assertNotInResponse('file: dummy:/a')
|
self.assertNotInResponse('file: dummy:/a')
|
||||||
self.assertInResponse('directory: /dummy/foo')
|
self.assertInResponse('directory: /dummy/foo')
|
||||||
self.assertInResponse('file: dummy:/b')
|
self.assertInResponse('file: dummy:/foo/b')
|
||||||
self.assertInResponse('OK')
|
self.assertInResponse('OK')
|
||||||
|
|
||||||
def test_listall_with_unknown_uri(self):
|
def test_listall_with_unknown_uri(self):
|
||||||
@ -159,39 +159,57 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase):
|
|||||||
|
|
||||||
self.assertEqualResponse('ACK [50@0] {listall} Not found')
|
self.assertEqualResponse('ACK [50@0] {listall} Not found')
|
||||||
|
|
||||||
|
def test_listall_for_dir_with_and_without_leading_slash_is_the_same(self):
|
||||||
|
self.backend.library.dummy_browse_result = {
|
||||||
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
|
Ref.directory(uri='dummy:/foo', name='foo')]}
|
||||||
|
|
||||||
|
response1 = self.sendRequest('listall "dummy"')
|
||||||
|
response2 = self.sendRequest('listall "/dummy"')
|
||||||
|
self.assertEqual(response1, response2)
|
||||||
|
|
||||||
|
def test_listall_for_dir_with_and_without_trailing_slash_is_the_same(self):
|
||||||
|
self.backend.library.dummy_browse_result = {
|
||||||
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
|
Ref.directory(uri='dummy:/foo', name='foo')]}
|
||||||
|
|
||||||
|
response1 = self.sendRequest('listall "dummy"')
|
||||||
|
response2 = self.sendRequest('listall "dummy/"')
|
||||||
|
self.assertEqual(response1, response2)
|
||||||
|
|
||||||
def test_listallinfo_without_uri(self):
|
def test_listallinfo_without_uri(self):
|
||||||
tracks = [Track(uri='dummy:/a', name='a'),
|
tracks = [Track(uri='dummy:/a', name='a'),
|
||||||
Track(uri='dummy:/b', name='b')]
|
Track(uri='dummy:/foo/b', name='b')]
|
||||||
self.backend.library.dummy_library = tracks
|
self.backend.library.dummy_library = tracks
|
||||||
self.backend.library.dummy_browse_result = {
|
self.backend.library.dummy_browse_result = {
|
||||||
'/': [Ref.track(uri='dummy:/a', name='a'),
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
Ref.directory(uri='/foo')],
|
Ref.directory(uri='dummy:/foo', name='foo')],
|
||||||
'/foo': [Ref.track(uri='dummy:/b', name='b')]}
|
'dummy:/foo': [Ref.track(uri='dummy:/foo/b', name='b')]}
|
||||||
|
|
||||||
self.sendRequest('listallinfo')
|
self.sendRequest('listallinfo')
|
||||||
|
|
||||||
self.assertInResponse('file: dummy:/a')
|
self.assertInResponse('file: dummy:/a')
|
||||||
self.assertInResponse('Title: a')
|
self.assertInResponse('Title: a')
|
||||||
self.assertInResponse('directory: /dummy/foo')
|
self.assertInResponse('directory: /dummy/foo')
|
||||||
self.assertInResponse('file: dummy:/b')
|
self.assertInResponse('file: dummy:/foo/b')
|
||||||
self.assertInResponse('Title: b')
|
self.assertInResponse('Title: b')
|
||||||
self.assertInResponse('OK')
|
self.assertInResponse('OK')
|
||||||
|
|
||||||
def test_listallinfo_with_uri(self):
|
def test_listallinfo_with_uri(self):
|
||||||
tracks = [Track(uri='dummy:/a', name='a'),
|
tracks = [Track(uri='dummy:/a', name='a'),
|
||||||
Track(uri='dummy:/b', name='b')]
|
Track(uri='dummy:/foo/b', name='b')]
|
||||||
self.backend.library.dummy_library = tracks
|
self.backend.library.dummy_library = tracks
|
||||||
self.backend.library.dummy_browse_result = {
|
self.backend.library.dummy_browse_result = {
|
||||||
'/': [Ref.track(uri='dummy:/a', name='a'),
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
Ref.directory(uri='/foo')],
|
Ref.directory(uri='dummy:/foo', name='foo')],
|
||||||
'/foo': [Ref.track(uri='dummy:/b', name='b')]}
|
'dummy:/foo': [Ref.track(uri='dummy:/foo/b', name='b')]}
|
||||||
|
|
||||||
self.sendRequest('listallinfo "/dummy/foo"')
|
self.sendRequest('listallinfo "/dummy/foo"')
|
||||||
|
|
||||||
self.assertNotInResponse('file: dummy:/a')
|
self.assertNotInResponse('file: dummy:/a')
|
||||||
self.assertNotInResponse('Title: a')
|
self.assertNotInResponse('Title: a')
|
||||||
self.assertInResponse('directory: /dummy/foo')
|
self.assertInResponse('directory: /dummy/foo')
|
||||||
self.assertInResponse('file: dummy:/b')
|
self.assertInResponse('file: dummy:/foo/b')
|
||||||
self.assertInResponse('Title: b')
|
self.assertInResponse('Title: b')
|
||||||
self.assertInResponse('OK')
|
self.assertInResponse('OK')
|
||||||
|
|
||||||
@ -200,6 +218,24 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase):
|
|||||||
|
|
||||||
self.assertEqualResponse('ACK [50@0] {listallinfo} Not found')
|
self.assertEqualResponse('ACK [50@0] {listallinfo} Not found')
|
||||||
|
|
||||||
|
def test_listallinfo_for_dir_with_and_without_leading_slash_is_same(self):
|
||||||
|
self.backend.library.dummy_browse_result = {
|
||||||
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
|
Ref.directory(uri='dummy:/foo', name='foo')]}
|
||||||
|
|
||||||
|
response1 = self.sendRequest('listallinfo "dummy"')
|
||||||
|
response2 = self.sendRequest('listallinfo "/dummy"')
|
||||||
|
self.assertEqual(response1, response2)
|
||||||
|
|
||||||
|
def test_listallinfo_for_dir_with_and_without_trailing_slash_is_same(self):
|
||||||
|
self.backend.library.dummy_browse_result = {
|
||||||
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
|
Ref.directory(uri='dummy:/foo', name='foo')]}
|
||||||
|
|
||||||
|
response1 = self.sendRequest('listallinfo "dummy"')
|
||||||
|
response2 = self.sendRequest('listallinfo "dummy/"')
|
||||||
|
self.assertEqual(response1, response2)
|
||||||
|
|
||||||
def test_lsinfo_without_path_returns_same_as_for_root(self):
|
def test_lsinfo_without_path_returns_same_as_for_root(self):
|
||||||
last_modified = datetime.datetime(2001, 3, 17, 13, 41, 17, 12345)
|
last_modified = datetime.datetime(2001, 3, 17, 13, 41, 17, 12345)
|
||||||
self.backend.playlists.playlists = [
|
self.backend.playlists.playlists = [
|
||||||
@ -231,8 +267,8 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase):
|
|||||||
|
|
||||||
def test_lsinfo_for_root_includes_dirs_for_each_lib_with_content(self):
|
def test_lsinfo_for_root_includes_dirs_for_each_lib_with_content(self):
|
||||||
self.backend.library.dummy_browse_result = {
|
self.backend.library.dummy_browse_result = {
|
||||||
'/': [Ref.track(uri='dummy:/a', name='a'),
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
Ref.directory(uri='/foo', name='foo')]}
|
Ref.directory(uri='dummy:/foo', name='foo')]}
|
||||||
|
|
||||||
self.sendRequest('lsinfo "/"')
|
self.sendRequest('lsinfo "/"')
|
||||||
self.assertInResponse('directory: dummy')
|
self.assertInResponse('directory: dummy')
|
||||||
@ -240,19 +276,28 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase):
|
|||||||
|
|
||||||
def test_lsinfo_for_dir_with_and_without_leading_slash_is_the_same(self):
|
def test_lsinfo_for_dir_with_and_without_leading_slash_is_the_same(self):
|
||||||
self.backend.library.dummy_browse_result = {
|
self.backend.library.dummy_browse_result = {
|
||||||
'/': [Ref.track(uri='dummy:/a', name='a'),
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
Ref.directory(uri='/foo', name='foo')]}
|
Ref.directory(uri='dummy:/foo', name='foo')]}
|
||||||
|
|
||||||
response1 = self.sendRequest('lsinfo "dummy"')
|
response1 = self.sendRequest('lsinfo "dummy"')
|
||||||
response2 = self.sendRequest('lsinfo "/dummy"')
|
response2 = self.sendRequest('lsinfo "/dummy"')
|
||||||
self.assertEqual(response1, response2)
|
self.assertEqual(response1, response2)
|
||||||
|
|
||||||
|
def test_lsinfo_for_dir_with_and_without_trailing_slash_is_the_same(self):
|
||||||
|
self.backend.library.dummy_browse_result = {
|
||||||
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
|
Ref.directory(uri='dummy:/foo', name='foo')]}
|
||||||
|
|
||||||
|
response1 = self.sendRequest('lsinfo "dummy"')
|
||||||
|
response2 = self.sendRequest('lsinfo "dummy/"')
|
||||||
|
self.assertEqual(response1, response2)
|
||||||
|
|
||||||
def test_lsinfo_for_dir_includes_tracks(self):
|
def test_lsinfo_for_dir_includes_tracks(self):
|
||||||
self.backend.library.dummy_library = [
|
self.backend.library.dummy_library = [
|
||||||
Track(uri='dummy:/a', name='a'),
|
Track(uri='dummy:/a', name='a'),
|
||||||
]
|
]
|
||||||
self.backend.library.dummy_browse_result = {
|
self.backend.library.dummy_browse_result = {
|
||||||
'/': [Ref.track(uri='dummy:/a', name='a')]}
|
'dummy:/': [Ref.track(uri='dummy:/a', name='a')]}
|
||||||
|
|
||||||
self.sendRequest('lsinfo "/dummy"')
|
self.sendRequest('lsinfo "/dummy"')
|
||||||
self.assertInResponse('file: dummy:/a')
|
self.assertInResponse('file: dummy:/a')
|
||||||
@ -261,7 +306,7 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase):
|
|||||||
|
|
||||||
def test_lsinfo_for_dir_includes_subdirs(self):
|
def test_lsinfo_for_dir_includes_subdirs(self):
|
||||||
self.backend.library.dummy_browse_result = {
|
self.backend.library.dummy_browse_result = {
|
||||||
'/': [Ref.directory(uri='/foo', name='foo')]}
|
'dummy:/': [Ref.directory(uri='/foo', name='foo')]}
|
||||||
|
|
||||||
self.sendRequest('lsinfo "/dummy"')
|
self.sendRequest('lsinfo "/dummy"')
|
||||||
self.assertInResponse('directory: dummy/foo')
|
self.assertInResponse('directory: dummy/foo')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user