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 11a5488c..41870d5e 100644 --- a/mopidy/backends/spotify.py +++ b/mopidy/backends/spotify.py @@ -1,3 +1,5 @@ +import sys + import spytify from mopidy import settings @@ -6,13 +8,25 @@ 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 self.current_playlist = [] self.current_playlist_version = 0 + @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): for playlist in self.spotify.stored_playlists: if playlist.name == name: @@ -58,3 +72,6 @@ class SpotifyBackend(BaseBackend): def status_playlist(self): return self.current_playlist_version + + def url_handlers(self): + return [u'spotify:', u'http://open.spotify.com/'] diff --git a/mopidy/handler.py b/mopidy/handler.py index 7599d3a5..d8575741 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 @@ -66,6 +63,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 +88,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 +106,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 +140,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 +250,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 +262,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 +333,11 @@ 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 + + @register(r'^urlhandlers$') + def _urlhandlers(self): + return self.backend.url_handlers() 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 5270e65e..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,9 +393,55 @@ class StoredPlaylistsHandlerTest(unittest.TestCase): class MusicDatabaseHandlerTest(unittest.TestCase): def setUp(self): - self.h = handler.MpdHandler(backend=DummyBackend) + 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,10 +452,58 @@ 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): - self.h = handler.MpdHandler(backend=DummyBackend) + self.h = handler.MpdHandler(backend=DummyBackend()) pass # TODO @@ -430,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') @@ -454,13 +543,17 @@ 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') + self.assert_('dummy:' in result) pass # TODO