Merge pull request #643 from jodal/feature/mpd-listall
Add "listall" and "listallinfo" MPD commands
This commit is contained in:
commit
fe1b01df4d
@ -18,7 +18,7 @@ v0.18.0 (UNRELEASED)
|
|||||||
|
|
||||||
- Add :meth:`mopidy.core.LibraryController.browse` method for browsing a
|
- Add :meth:`mopidy.core.LibraryController.browse` method for browsing a
|
||||||
virtual file system of tracks. Backends can implement support for this by
|
virtual file system of tracks. Backends can implement support for this by
|
||||||
implementing :meth:`mopidy.backends.base.BaseLibraryController.browse`.
|
implementing :meth:`mopidy.backend.LibraryProvider.browse`.
|
||||||
|
|
||||||
- Events emitted on play/stop, pause/resume, next/previous and on end of track
|
- Events emitted on play/stop, pause/resume, next/previous and on end of track
|
||||||
has been cleaned up to work consistenly. See the message of
|
has been cleaned up to work consistenly. See the message of
|
||||||
@ -46,6 +46,10 @@ v0.18.0 (UNRELEASED)
|
|||||||
|
|
||||||
Imports from the old locations still works, but are deprecated.
|
Imports from the old locations still works, but are deprecated.
|
||||||
|
|
||||||
|
- Add :meth:`mopidy.backend.LibraryProvider.browse`, which can be implemented
|
||||||
|
by backends that wants to expose directories of tracks in Mopidy's virtual
|
||||||
|
file system.
|
||||||
|
|
||||||
**Commands**
|
**Commands**
|
||||||
|
|
||||||
- Reduce amount of logging from dependencies when using :option:`mopidy -v`.
|
- Reduce amount of logging from dependencies when using :option:`mopidy -v`.
|
||||||
@ -85,7 +89,7 @@ v0.18.0 (UNRELEASED)
|
|||||||
|
|
||||||
**Local backend**
|
**Local backend**
|
||||||
|
|
||||||
- Added support for browsing local directories.
|
- Added support for browsing local directories in Mopidy's virtual file system.
|
||||||
|
|
||||||
- Finished the work on creating pluggable libraries. Users can now
|
- Finished the work on creating pluggable libraries. Users can now
|
||||||
reconfigure Mopidy to use alternate library providers of their choosing for
|
reconfigure Mopidy to use alternate library providers of their choosing for
|
||||||
@ -125,9 +129,8 @@ v0.18.0 (UNRELEASED)
|
|||||||
|
|
||||||
**MPD frontend**
|
**MPD frontend**
|
||||||
|
|
||||||
- Make the ``lsinfo`` command support browsing of Mopidy's virtual file
|
- Make the ``lsinfo``, ``listall``, and ``listallinfo`` commands support
|
||||||
system. Note that the related ``listall`` and ``listallinfo`` commands are
|
browsing of Mopidy's virtual file system. (Fixes: :issue:`145`)
|
||||||
still not implemented.
|
|
||||||
|
|
||||||
- Empty commands now return a ``ACK [5@0] {} No command given`` error instead
|
- Empty commands now return a ``ACK [5@0] {} No command given`` error instead
|
||||||
of ``OK``. This is consistent with the original MPD server implementation.
|
of ``OK``. This is consistent with the original MPD server implementation.
|
||||||
|
|||||||
@ -47,7 +47,6 @@ near future:
|
|||||||
|
|
||||||
- Modifying stored playlists is not supported
|
- Modifying stored playlists is not supported
|
||||||
- ``tagtypes`` is not supported
|
- ``tagtypes`` is not supported
|
||||||
- Browsing the file system is not supported
|
|
||||||
- Live update of the music database is not supported
|
- Live update of the music database is not supported
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import re
|
|||||||
|
|
||||||
from mopidy.models import Ref, Track
|
from mopidy.models import Ref, Track
|
||||||
from mopidy.mpd import translator
|
from mopidy.mpd import translator
|
||||||
from mopidy.mpd.exceptions import MpdArgError, MpdNotImplemented
|
from mopidy.mpd.exceptions import MpdArgError, MpdNoExistError
|
||||||
from mopidy.mpd.protocol import handle_request, stored_playlists
|
from mopidy.mpd.protocol import handle_request, stored_playlists
|
||||||
|
|
||||||
|
|
||||||
@ -417,7 +417,25 @@ def listall(context, uri=None):
|
|||||||
|
|
||||||
Lists all songs and directories in ``URI``.
|
Lists all songs and directories in ``URI``.
|
||||||
"""
|
"""
|
||||||
raise MpdNotImplemented # TODO
|
if uri is None:
|
||||||
|
uri = '/'
|
||||||
|
if not uri.startswith('/'):
|
||||||
|
uri = '/%s' % uri
|
||||||
|
|
||||||
|
result = []
|
||||||
|
browse_futures = [context.core.library.browse(uri)]
|
||||||
|
while browse_futures:
|
||||||
|
for ref in browse_futures.pop().get():
|
||||||
|
if ref.type == Ref.DIRECTORY:
|
||||||
|
result.append(('directory', ref.uri))
|
||||||
|
browse_futures.append(context.core.library.browse(ref.uri))
|
||||||
|
elif ref.type == Ref.TRACK:
|
||||||
|
result.append(('file', ref.uri))
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
raise MpdNoExistError('Not found', command='listall')
|
||||||
|
|
||||||
|
return [('directory', uri)] + result
|
||||||
|
|
||||||
|
|
||||||
@handle_request(r'listallinfo$')
|
@handle_request(r'listallinfo$')
|
||||||
@ -431,7 +449,34 @@ 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``.
|
||||||
"""
|
"""
|
||||||
raise MpdNotImplemented # TODO
|
if uri is None:
|
||||||
|
uri = '/'
|
||||||
|
if not uri.startswith('/'):
|
||||||
|
uri = '/%s' % uri
|
||||||
|
|
||||||
|
dirs_and_futures = []
|
||||||
|
browse_futures = [context.core.library.browse(uri)]
|
||||||
|
while browse_futures:
|
||||||
|
for ref in browse_futures.pop().get():
|
||||||
|
if ref.type == Ref.DIRECTORY:
|
||||||
|
dirs_and_futures.append(('directory', ref.uri))
|
||||||
|
browse_futures.append(context.core.library.browse(ref.uri))
|
||||||
|
elif ref.type == Ref.TRACK:
|
||||||
|
# TODO Lookup tracks in batch for better performance
|
||||||
|
dirs_and_futures.append(context.core.library.lookup(ref.uri))
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for obj in dirs_and_futures:
|
||||||
|
if hasattr(obj, 'get'):
|
||||||
|
for track in obj.get():
|
||||||
|
result.extend(translator.track_to_mpd_format(track))
|
||||||
|
else:
|
||||||
|
result.append(obj)
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
raise MpdNoExistError('Not found', command='listallinfo')
|
||||||
|
|
||||||
|
return [('directory', uri)] + result
|
||||||
|
|
||||||
|
|
||||||
@handle_request(r'lsinfo$')
|
@handle_request(r'lsinfo$')
|
||||||
|
|||||||
@ -123,20 +123,82 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase):
|
|||||||
self.assertInResponse('OK')
|
self.assertInResponse('OK')
|
||||||
|
|
||||||
def test_listall_without_uri(self):
|
def test_listall_without_uri(self):
|
||||||
|
tracks = [Track(uri='dummy:/a', name='a'),
|
||||||
|
Track(uri='dummy:/b', name='b')]
|
||||||
|
self.backend.library.dummy_library = tracks
|
||||||
|
self.backend.library.dummy_browse_result = {
|
||||||
|
'/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
|
Ref.directory(uri='/foo')],
|
||||||
|
'/foo': [Ref.track(uri='dummy:/b', name='b')]}
|
||||||
|
|
||||||
self.sendRequest('listall')
|
self.sendRequest('listall')
|
||||||
self.assertEqualResponse('ACK [0@0] {} Not implemented')
|
|
||||||
|
self.assertInResponse('file: dummy:/a')
|
||||||
|
self.assertInResponse('directory: /dummy/foo')
|
||||||
|
self.assertInResponse('file: dummy:/b')
|
||||||
|
self.assertInResponse('OK')
|
||||||
|
|
||||||
def test_listall_with_uri(self):
|
def test_listall_with_uri(self):
|
||||||
self.sendRequest('listall "file:///dev/urandom"')
|
tracks = [Track(uri='dummy:/a', name='a'),
|
||||||
self.assertEqualResponse('ACK [0@0] {} Not implemented')
|
Track(uri='dummy:/b', name='b')]
|
||||||
|
self.backend.library.dummy_library = tracks
|
||||||
|
self.backend.library.dummy_browse_result = {
|
||||||
|
'/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
|
Ref.directory(uri='/foo')],
|
||||||
|
'/foo': [Ref.track(uri='dummy:/b', name='b')]}
|
||||||
|
|
||||||
|
self.sendRequest('listall "/dummy/foo"')
|
||||||
|
|
||||||
|
self.assertNotInResponse('file: dummy:/a')
|
||||||
|
self.assertInResponse('directory: /dummy/foo')
|
||||||
|
self.assertInResponse('file: dummy:/b')
|
||||||
|
self.assertInResponse('OK')
|
||||||
|
|
||||||
|
def test_listall_with_unknown_uri(self):
|
||||||
|
self.sendRequest('listall "/unknown"')
|
||||||
|
|
||||||
|
self.assertEqualResponse('ACK [50@0] {listall} Not found')
|
||||||
|
|
||||||
def test_listallinfo_without_uri(self):
|
def test_listallinfo_without_uri(self):
|
||||||
|
tracks = [Track(uri='dummy:/a', name='a'),
|
||||||
|
Track(uri='dummy:/b', name='b')]
|
||||||
|
self.backend.library.dummy_library = tracks
|
||||||
|
self.backend.library.dummy_browse_result = {
|
||||||
|
'/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
|
Ref.directory(uri='/foo')],
|
||||||
|
'/foo': [Ref.track(uri='dummy:/b', name='b')]}
|
||||||
|
|
||||||
self.sendRequest('listallinfo')
|
self.sendRequest('listallinfo')
|
||||||
self.assertEqualResponse('ACK [0@0] {} Not implemented')
|
|
||||||
|
self.assertInResponse('file: dummy:/a')
|
||||||
|
self.assertInResponse('Title: a')
|
||||||
|
self.assertInResponse('directory: /dummy/foo')
|
||||||
|
self.assertInResponse('file: dummy:/b')
|
||||||
|
self.assertInResponse('Title: b')
|
||||||
|
self.assertInResponse('OK')
|
||||||
|
|
||||||
def test_listallinfo_with_uri(self):
|
def test_listallinfo_with_uri(self):
|
||||||
self.sendRequest('listallinfo "file:///dev/urandom"')
|
tracks = [Track(uri='dummy:/a', name='a'),
|
||||||
self.assertEqualResponse('ACK [0@0] {} Not implemented')
|
Track(uri='dummy:/b', name='b')]
|
||||||
|
self.backend.library.dummy_library = tracks
|
||||||
|
self.backend.library.dummy_browse_result = {
|
||||||
|
'/': [Ref.track(uri='dummy:/a', name='a'),
|
||||||
|
Ref.directory(uri='/foo')],
|
||||||
|
'/foo': [Ref.track(uri='dummy:/b', name='b')]}
|
||||||
|
|
||||||
|
self.sendRequest('listallinfo "/dummy/foo"')
|
||||||
|
|
||||||
|
self.assertNotInResponse('file: dummy:/a')
|
||||||
|
self.assertNotInResponse('Title: a')
|
||||||
|
self.assertInResponse('directory: /dummy/foo')
|
||||||
|
self.assertInResponse('file: dummy:/b')
|
||||||
|
self.assertInResponse('Title: b')
|
||||||
|
self.assertInResponse('OK')
|
||||||
|
|
||||||
|
def test_listallinfo_with_unknown_uri(self):
|
||||||
|
self.sendRequest('listallinfo "/unknown"')
|
||||||
|
|
||||||
|
self.assertEqualResponse('ACK [50@0] {listallinfo} Not found')
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user