From 5c64a39ad4536c8fe1c15c257de159e50547bfd3 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 25 Dec 2009 15:24:47 +0100 Subject: [PATCH 1/4] Add empty handlers for music database commands --- mopidy/handler.py | 43 +++++++++++++++++++- tests/handlertest.py | 96 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 136 insertions(+), 3 deletions(-) diff --git a/mopidy/handler.py b/mopidy/handler.py index 03f06d45..c319f3af 100644 --- a/mopidy/handler.py +++ b/mopidy/handler.py @@ -66,6 +66,10 @@ class MpdHandler(object): else: pass # TODO + @register(r'^count (?P\S+) (?P\S+)$') + def _count(self, tag, needle): + pass # TODO + @register(r'^crossfade "(?P\d+)"$') def _crossfade(self, seconds): seconds = int(seconds) @@ -87,6 +91,16 @@ class MpdHandler(object): def _empty(self): pass + @register(r'^find (?P(album|artist|title)) (?P.*)$') + def _find(self, type, what): + pass # TODO + + @register(r'^findadd (?P(album|artist|title)) (?P.*)$') + def _findadd(self, type, what): + result = self._find(type, what) + # TODO Add result to current playlist + return result + @register(r'^idle( (?P.+))*$') def _idle(self, subsystems=None): pass # TODO @@ -95,6 +109,20 @@ class MpdHandler(object): def _kill(self): self.session.do_kill() + @register(r'^list (?P(artist|album))( (?P.*))*$') + def _list(self, type, artist=None): + if type == u'artist' and artist is not None: + return False + pass # TODO + + @register(r'^listall "(?P[^"]+)"') + def _listall(self, uri): + pass # TODO + + @register(r'^listallinfo "(?P[^"]+)"') + def _listallinfo(self, uri): + pass # TODO + @register(r'^listplaylist (?P.+)$') def _listplaylist(self, name): pass # TODO @@ -115,8 +143,7 @@ class MpdHandler(object): def _lsinfo(self, uri): if uri == u'/': return self._listplaylists() - # TODO - return self._listplaylists() + pass # TODO @register(r'^move ((?P\d+)|(?P\d+):(?P\d+)*) (?P\d+)$') def _move(self, songpos=None, start=None, end=None, to=None): @@ -226,6 +253,10 @@ class MpdHandler(object): def _replay_gain_status(self): return u'off' # TODO + @register(r'^rescan( "(?P[^"]+)")*$') + def _update(self, uri=None): + return self._update(uri, rescan_unmodified_files=True) + @register(r'^rm (?P\S+)$') def _rm(self, name): pass # TODO @@ -234,6 +265,10 @@ class MpdHandler(object): def _save(self, name): pass # TODO + @register(r'^search (?P(album|artist|filename|title)) (?P.+)$') + def _search(self, type, what): + pass # TODO + @register(r'^seek (?P.+) (?P\d+)$') def _seek(self, songpos, seconds): pass # TODO @@ -301,3 +336,7 @@ class MpdHandler(object): @register(r'^swapid (?P\S+) (?P\S+)$') def _swapid(self, songid1, songid2): pass # TODO + + @register(r'^update( "(?P[^"]+)")*$') + def _update(self, uri=None, rescan_unmodified_files=False): + return u'updating_db: 0' # TODO diff --git a/tests/handlertest.py b/tests/handlertest.py index 5270e65e..3b5e1bb4 100644 --- a/tests/handlertest.py +++ b/tests/handlertest.py @@ -400,7 +400,53 @@ class MusicDatabaseHandlerTest(unittest.TestCase): def setUp(self): self.h = handler.MpdHandler(backend=DummyBackend) - pass # TODO + def test_count(self): + result = self.h.handle_request(u'count tag needle') + self.assert_(result is None) + + def test_find_album(self): + result = self.h.handle_request(u'find album what') + self.assert_(result is None) + + def test_find_artist(self): + result = self.h.handle_request(u'find artist what') + self.assert_(result is None) + + def test_find_title(self): + result = self.h.handle_request(u'find title what') + self.assert_(result is None) + + def test_find_else_should_fail(self): + result = self.h.handle_request(u'find somethingelse what') + self.assert_(result is False) + + def test_findadd(self): + result = self.h.handle_request(u'findadd album what') + self.assert_(result is None) + + def test_list_artist(self): + result = self.h.handle_request(u'list artist') + self.assert_(result is None) + + def test_list_artist_with_artist_should_fail(self): + result = self.h.handle_request(u'list artist anartist') + self.assert_(result is False) + + def test_list_album_without_artist(self): + result = self.h.handle_request(u'list album') + self.assert_(result is None) + + def test_list_album_with_artist(self): + result = self.h.handle_request(u'list album anartist') + self.assert_(result is None) + + def test_listall(self): + result = self.h.handle_request(u'listall "file:///dev/urandom"') + self.assert_(result is None) + + def test_listallinfo(self): + result = self.h.handle_request(u'listallinfo "file:///dev/urandom"') + self.assert_(result is None) def test_lsinfo_for_root_returns_same_as_listplaylists(self): lsinfo_result = self.h.handle_request(u'lsinfo "/"') @@ -411,6 +457,54 @@ class MusicDatabaseHandlerTest(unittest.TestCase): result = self.h.handle_request(u'lsinfo ""') self.assert_(result is None) + def test_search_album(self): + result = self.h.handle_request(u'search album analbum') + self.assert_(result is None) + + def test_search_artist(self): + result = self.h.handle_request(u'search artist anartist') + self.assert_(result is None) + + def test_search_filename(self): + result = self.h.handle_request(u'search filename afilename') + self.assert_(result is None) + + def test_search_title(self): + result = self.h.handle_request(u'search title atitle') + self.assert_(result is None) + + def test_search_else_should_fail(self): + result = self.h.handle_request(u'search sometype something') + self.assert_(result is False) + + def test_update_without_uri(self): + result = self.h.handle_request(u'update') + (label, jobid) = result.split(':', 1) + self.assertEquals(u'updating_db', label) + self.assert_(jobid.strip().isdigit()) + self.assert_(int(jobid) >= 0) + + def test_update_with_uri(self): + result = self.h.handle_request(u'update "file:///dev/urandom"') + (label, jobid) = result.split(':', 1) + self.assertEquals(u'updating_db', label) + self.assert_(jobid.strip().isdigit()) + self.assert_(int(jobid) >= 0) + + def test_rescan_without_uri(self): + result = self.h.handle_request(u'rescan') + (label, jobid) = result.split(':', 1) + self.assertEquals(u'updating_db', label) + self.assert_(jobid.strip().isdigit()) + self.assert_(int(jobid) >= 0) + + def test_rescan_with_uri(self): + result = self.h.handle_request(u'rescan "file:///dev/urandom"') + (label, jobid) = result.split(':', 1) + self.assertEquals(u'updating_db', label) + self.assert_(jobid.strip().isdigit()) + self.assert_(int(jobid) >= 0) + class StickersHandlerTest(unittest.TestCase): def setUp(self): From 3f41fc8df70933a8df5a21f557dcd12d1c79df22 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 25 Dec 2009 15:37:25 +0100 Subject: [PATCH 2/4] Add 'urlhandler' command --- mopidy/backends/dummy.py | 3 ++- mopidy/backends/spotify.py | 3 ++- mopidy/handler.py | 4 ++++ tests/handlertest.py | 4 ++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/mopidy/backends/dummy.py b/mopidy/backends/dummy.py index b8713c81..c3862bb7 100644 --- a/mopidy/backends/dummy.py +++ b/mopidy/backends/dummy.py @@ -1,4 +1,5 @@ from mopidy.backends.base import BaseBackend class DummyBackend(BaseBackend): - pass + def url_handlers(self): + return [u'dummy:'] diff --git a/mopidy/backends/spotify.py b/mopidy/backends/spotify.py index 1b9e6866..f159f60a 100644 --- a/mopidy/backends/spotify.py +++ b/mopidy/backends/spotify.py @@ -28,4 +28,5 @@ class SpotifyBackend(BaseBackend): playlists.append(u'playlist: %s' % playlist.name.decode('utf-8')) return playlists - + def url_handlers(self): + return [u'spotify:', u'http://open.spotify.com/'] diff --git a/mopidy/handler.py b/mopidy/handler.py index c319f3af..aad615e4 100644 --- a/mopidy/handler.py +++ b/mopidy/handler.py @@ -340,3 +340,7 @@ class MpdHandler(object): @register(r'^update( "(?P[^"]+)")*$') def _update(self, uri=None, rescan_unmodified_files=False): return u'updating_db: 0' # TODO + + @register(r'^urlhandlers$') + def _urlhandlers(self): + return self.backend.url_handlers() diff --git a/tests/handlertest.py b/tests/handlertest.py index 3b5e1bb4..863a7b00 100644 --- a/tests/handlertest.py +++ b/tests/handlertest.py @@ -557,4 +557,8 @@ class ReflectionHandlerTest(unittest.TestCase): def setUp(self): self.h = handler.MpdHandler(backend=DummyBackend) + def test_urlhandlers(self): + result = self.h.handle_request(u'urlhandlers') + self.assert_('dummy:' in result) + pass # TODO From 256e5e685aed1675fc34663218ffb6e65a49c27f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 25 Dec 2009 15:54:44 +0100 Subject: [PATCH 3/4] Move instantiation of backend from handler to server as the same backend should be used for multiple (simultaneous) sessions --- mopidy/handler.py | 7 ++----- mopidy/server.py | 9 ++++++--- mopidy/session.py | 7 +++---- tests/handlertest.py | 27 +++++++++++---------------- 4 files changed, 22 insertions(+), 28 deletions(-) diff --git a/mopidy/handler.py b/mopidy/handler.py index aad615e4..45a5e54d 100644 --- a/mopidy/handler.py +++ b/mopidy/handler.py @@ -21,9 +21,9 @@ def register(pattern): return decorator class MpdHandler(object): - def __init__(self, session=None, backend=SpotifyBackend): + def __init__(self, session=None, backend=None): self.session = session - self.register_backend(backend()) + self.backend = backend def handle_request(self, request): for pattern in _request_handlers: @@ -34,9 +34,6 @@ class MpdHandler(object): logger.warning(u'Unhandled request: %s', request) return False - def register_backend(self, backend): - self.backend = backend - @register(r'^add "(?P[^"]*)"$') def _add(self, uri): pass # TODO diff --git a/mopidy/server.py b/mopidy/server.py index fbe38599..2d07d51e 100644 --- a/mopidy/server.py +++ b/mopidy/server.py @@ -5,13 +5,15 @@ import sys from mopidy import settings from mopidy.session import MpdSession +from mopidy.backends.spotify import SpotifyBackend logger = logging.getLogger(u'server') class MpdServer(asyncore.dispatcher): - def __init__(self, handler_class=MpdSession): + def __init__(self, session_class=MpdSession, backend=SpotifyBackend): asyncore.dispatcher.__init__(self) - self.handler_class = handler_class + self.session_class = session_class + self.backend = SpotifyBackend() self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.set_reuse_addr() self.bind((settings.MPD_SERVER_HOSTNAME, settings.MPD_SERVER_PORT)) @@ -20,7 +22,8 @@ class MpdServer(asyncore.dispatcher): def handle_accept(self): (client_socket, client_address) = self.accept() logger.info(u'Connection from: [%s]:%s', *client_address) - self.handler_class(self, client_socket, client_address) + self.session_class(self, client_socket, client_address, + backend=self.backend) def handle_close(self): self.close() diff --git a/mopidy/session.py b/mopidy/session.py index 9002aaa7..6e76c5cd 100644 --- a/mopidy/session.py +++ b/mopidy/session.py @@ -7,14 +7,14 @@ from mopidy.handler import MpdHandler logger = logging.getLogger(u'session') class MpdSession(asynchat.async_chat): - def __init__(self, server, client_socket, client_address, - handler=MpdHandler): + def __init__(self, server, client_socket, client_address, backend, + handler_class=MpdHandler): asynchat.async_chat.__init__(self, sock=client_socket) self.server = server self.client_address = client_address self.input_buffer = [] self.set_terminator(settings.MPD_LINE_TERMINATOR) - self.handler = handler(session=self) + self.handler = handler_class(session=self, backend=backend) self.send_response(u'OK MPD %s' % get_mpd_version()) def do_close(self): @@ -51,4 +51,3 @@ class MpdSession(asynchat.async_chat): output = u'%s%s' % (output, settings.MPD_LINE_TERMINATOR) data = output.encode(settings.MPD_LINE_ENCODING) self.push(data) - diff --git a/tests/handlertest.py b/tests/handlertest.py index 863a7b00..5e3432a6 100644 --- a/tests/handlertest.py +++ b/tests/handlertest.py @@ -5,7 +5,7 @@ from mopidy.backends.dummy import DummyBackend class RequestHandlerTest(unittest.TestCase): def setUp(self): - self.h = handler.MpdHandler(backend=DummyBackend) + self.h = handler.MpdHandler(backend=DummyBackend()) def test_register_same_pattern_twice_fails(self): func = lambda: None @@ -26,15 +26,10 @@ class RequestHandlerTest(unittest.TestCase): result = self.h.handle_request('known request') self.assertEquals(expected, result) - def test_register_backend(self): - expected = 'magic' - self.h.register_backend(expected) - self.assertEquals(expected, self.h.backend) - class StatusHandlerTest(unittest.TestCase): def setUp(self): - self.h = handler.MpdHandler(backend=DummyBackend) + self.h = handler.MpdHandler(backend=DummyBackend()) def test_clearerror(self): result = self.h.handle_request(u'clearerror') @@ -93,7 +88,7 @@ class StatusHandlerTest(unittest.TestCase): class PlaybackOptionsHandlerTest(unittest.TestCase): def setUp(self): - self.h = handler.MpdHandler(backend=DummyBackend) + self.h = handler.MpdHandler(backend=DummyBackend()) def test_consume_off(self): result = self.h.handle_request(u'consume "0"') @@ -189,7 +184,7 @@ class PlaybackOptionsHandlerTest(unittest.TestCase): class PlaybackControlHandlerTest(unittest.TestCase): def setUp(self): - self.h = handler.MpdHandler(backend=DummyBackend) + self.h = handler.MpdHandler(backend=DummyBackend()) def test_next(self): result = self.h.handle_request(u'next') @@ -230,7 +225,7 @@ class PlaybackControlHandlerTest(unittest.TestCase): class CurrentPlaylistHandlerTest(unittest.TestCase): def setUp(self): - self.h = handler.MpdHandler(backend=DummyBackend) + self.h = handler.MpdHandler(backend=DummyBackend()) def test_add(self): result = self.h.handle_request(u'add "file:///dev/urandom"') @@ -348,7 +343,7 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): class StoredPlaylistsHandlerTest(unittest.TestCase): def setUp(self): - self.h = handler.MpdHandler(backend=DummyBackend) + self.h = handler.MpdHandler(backend=DummyBackend()) def test_listplaylist(self): result = self.h.handle_request(u'listplaylist name') @@ -398,7 +393,7 @@ class StoredPlaylistsHandlerTest(unittest.TestCase): class MusicDatabaseHandlerTest(unittest.TestCase): def setUp(self): - self.h = handler.MpdHandler(backend=DummyBackend) + self.h = handler.MpdHandler(backend=DummyBackend()) def test_count(self): result = self.h.handle_request(u'count tag needle') @@ -508,7 +503,7 @@ class MusicDatabaseHandlerTest(unittest.TestCase): class StickersHandlerTest(unittest.TestCase): def setUp(self): - self.h = handler.MpdHandler(backend=DummyBackend) + self.h = handler.MpdHandler(backend=DummyBackend()) pass # TODO @@ -524,7 +519,7 @@ class DummySession(object): class ConnectionHandlerTest(unittest.TestCase): def setUp(self): self.h = handler.MpdHandler(session=DummySession(), - backend=DummyBackend) + backend=DummyBackend()) def test_close(self): result = self.h.handle_request(u'close') @@ -548,14 +543,14 @@ class ConnectionHandlerTest(unittest.TestCase): class AudioOutputHandlerTest(unittest.TestCase): def setUp(self): - self.h = handler.MpdHandler(backend=DummyBackend) + self.h = handler.MpdHandler(backend=DummyBackend()) pass # TODO class ReflectionHandlerTest(unittest.TestCase): def setUp(self): - self.h = handler.MpdHandler(backend=DummyBackend) + self.h = handler.MpdHandler(backend=DummyBackend()) def test_urlhandlers(self): result = self.h.handle_request(u'urlhandlers') From 671ccd8e82e0c106b0ccd9cb61b674f342319725 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 25 Dec 2009 16:00:41 +0100 Subject: [PATCH 4/4] Exit if SPOTIFY_{USERNAME,PASSWORD} is not set --- mopidy/backends/spotify.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/mopidy/backends/spotify.py b/mopidy/backends/spotify.py index f159f60a..dff800c6 100644 --- a/mopidy/backends/spotify.py +++ b/mopidy/backends/spotify.py @@ -1,3 +1,5 @@ +import sys + import spytify from mopidy import settings @@ -6,11 +8,23 @@ from mopidy.backends.base import BaseBackend class SpotifyBackend(BaseBackend): def __init__(self, *args, **kwargs): super(SpotifyBackend, self).__init__(*args, **kwargs) - self.spotify = spytify.Spytify( - settings.SPOTIFY_USERNAME.encode('utf-8'), - settings.SPOTIFY_PASSWORD.encode('utf-8')) + self.spotify = spytify.Spytify(self.username, self.password) self._playlist_load_cache = None + @property + def username(self): + username = settings.SPOTIFY_USERNAME.encode('utf-8') + if not username: + sys.exit('Setting SPOTIFY_USERNAME is not set.') + return username + + @property + def password(self): + password = settings.SPOTIFY_PASSWORD.encode('utf-8') + if not password: + sys.exit('Setting SPOTIFY_PASSWORD is not set.') + return password + def playlist_load(self, name): if not self._playlist_load_cache: for playlist in self.spotify.stored_playlists: