From 5fce38a7fad9c5285a3b749094d99eaa0fb6e232 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 13 Aug 2010 21:41:47 +0200 Subject: [PATCH 01/17] docs: Update install docs --- docs/installation/index.rst | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 044f2155..d5e76cce 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -2,12 +2,10 @@ Installation ************ -Mopidy itself is a breeze to install, as it just requires a standard Python -installation and the GStreamer library. The libraries we depend on to connect -to the Spotify service is far more tricky to get working for the time being. -Until installation of these libraries are either well documented by their -developers, or the libraries are packaged for various Linux distributions, we -will supply our own installation guides, as linked to below. +To get a basic version of Mopidy running, you need Python and the GStreamer +library. To use Spotify with Mopidy, you also need :doc:`libspotify and +pyspotify `. Mopidy itself can either be installed from the Python +package index, PyPI, or from git. Install dependencies @@ -102,13 +100,8 @@ username and password into the file, like this:: SPOTIFY_PASSWORD = u'mysecret' Currently :mod:`mopidy.backends.libspotify` is the default -backend. - -If you want to use :mod:`mopidy.backends.libspotify`, copy the Spotify -application key to ``~/.mopidy/spotify_appkey.key``, and add the following -setting:: - - BACKENDS = (u'mopidy.backends.libspotify.LibspotifyBackend',) +backend. Before you can use :mod:`mopidy.backends.libspotify`, you must copy +the Spotify application key to ``~/.mopidy/spotify_appkey.key``. If you want to use :mod:`mopidy.backends.local`, add the following setting:: From 581d694cb1a660a0ce6dd4399ec37394858e0edd Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Aug 2010 21:51:02 +0200 Subject: [PATCH 02/17] Split libspotify backend into one file per class, and thus ensure that spotify depenedices don't fail tests --- mopidy/backends/libspotify/__init__.py | 263 +----------------- mopidy/backends/libspotify/library.py | 41 +++ mopidy/backends/libspotify/playback.py | 51 ++++ mopidy/backends/libspotify/session_manager.py | 106 +++++++ .../backends/libspotify/stored_playlists.py | 20 ++ mopidy/backends/libspotify/translator.py | 53 ++++ 6 files changed, 282 insertions(+), 252 deletions(-) create mode 100644 mopidy/backends/libspotify/library.py create mode 100644 mopidy/backends/libspotify/playback.py create mode 100644 mopidy/backends/libspotify/session_manager.py create mode 100644 mopidy/backends/libspotify/stored_playlists.py create mode 100644 mopidy/backends/libspotify/translator.py diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index 974e52df..ead08c44 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -1,19 +1,7 @@ -import datetime as dt import logging -import os -import multiprocessing -import threading -from spotify import Link, SpotifyError -from spotify.manager import SpotifySessionManager -from spotify.alsahelper import AlsaController - -from mopidy import get_version, settings -from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, - BaseLibraryController, BasePlaybackController, - BaseStoredPlaylistsController) -from mopidy.models import Artist, Album, Track, Playlist -from mopidy.process import pickle_connection +from mopidy import settings +from mopidy.backends.base import BaseBackend, BaseCurrentPlaylistController logger = logging.getLogger('mopidy.backends.libspotify') @@ -35,8 +23,15 @@ class LibspotifyBackend(BaseBackend): **Issues:** http://github.com/jodal/mopidy/issues/labels/backend-libspotify """ + # Imports inside methods are to prevent loading of __init__ to fail on + # missing spotify dependencies. def __init__(self, *args, **kwargs): + from .library import LibspotifyLibraryController + from .playback import LibspotifyPlaybackController + from .stored_playlists import LibspotifyStoredPlaylistsController + super(LibspotifyBackend, self).__init__(*args, **kwargs) + self.current_playlist = BaseCurrentPlaylistController(backend=self) self.library = LibspotifyLibraryController(backend=self) self.playback = LibspotifyPlaybackController(backend=self) @@ -46,6 +41,8 @@ class LibspotifyBackend(BaseBackend): self.spotify = self._connect() def _connect(self): + from .session_manager import LibspotifySessionManager + logger.info(u'Connecting to Spotify') spotify = LibspotifySessionManager( settings.SPOTIFY_USERNAME, settings.SPOTIFY_PASSWORD, @@ -53,241 +50,3 @@ class LibspotifyBackend(BaseBackend): output_queue=self.output_queue) spotify.start() return spotify - - -class LibspotifyLibraryController(BaseLibraryController): - def find_exact(self, **query): - return self.search(**query) - - def lookup(self, uri): - spotify_track = Link.from_string(uri).as_track() - return LibspotifyTranslator.to_mopidy_track(spotify_track) - - def refresh(self, uri=None): - pass # TODO - - def search(self, **query): - spotify_query = [] - for (field, values) in query.iteritems(): - if not hasattr(values, '__iter__'): - values = [values] - for value in values: - if field == u'track': - field = u'title' - if field == u'any': - spotify_query.append(value) - else: - spotify_query.append(u'%s:"%s"' % (field, value)) - spotify_query = u' '.join(spotify_query) - logger.debug(u'Spotify search query: %s' % spotify_query) - my_end, other_end = multiprocessing.Pipe() - self.backend.spotify.search(spotify_query.encode(ENCODING), other_end) - my_end.poll(None) - playlist = my_end.recv() - return playlist - - -class LibspotifyPlaybackController(BasePlaybackController): - def _set_output_state(self, state_name): - logger.debug(u'Setting output state to %s ...', state_name) - (my_end, other_end) = multiprocessing.Pipe() - self.backend.output_queue.put({ - 'command': 'set_state', - 'state': state_name, - 'reply_to': pickle_connection(other_end), - }) - my_end.poll(None) - return my_end.recv() - - def _pause(self): - return self._set_output_state('PAUSED') - - def _play(self, track): - self._set_output_state('READY') - if self.state == self.PLAYING: - self.stop() - if track.uri is None: - return False - try: - self.backend.spotify.session.load( - Link.from_string(track.uri).as_track()) - self.backend.spotify.session.play(1) - self._set_output_state('PLAYING') - return True - except SpotifyError as e: - logger.warning('Play %s failed: %s', track.uri, e) - return False - - def _resume(self): - return self._set_output_state('PLAYING') - - def _seek(self, time_position): - pass # TODO - - def _stop(self): - result = self._set_output_state('READY') - self.backend.spotify.session.play(0) - return result - - -class LibspotifyStoredPlaylistsController(BaseStoredPlaylistsController): - def create(self, name): - pass # TODO - - def delete(self, playlist): - pass # TODO - - def lookup(self, uri): - pass # TODO - - def refresh(self): - pass # TODO - - def rename(self, playlist, new_name): - pass # TODO - - def save(self, playlist): - pass # TODO - - -class LibspotifyTranslator(object): - @classmethod - def to_mopidy_artist(cls, spotify_artist): - if not spotify_artist.is_loaded(): - return Artist(name=u'[loading...]') - return Artist( - uri=str(Link.from_artist(spotify_artist)), - name=spotify_artist.name().decode(ENCODING), - ) - - @classmethod - def to_mopidy_album(cls, spotify_album): - if not spotify_album.is_loaded(): - return Album(name=u'[loading...]') - # TODO pyspotify got much more data on albums than this - return Album(name=spotify_album.name().decode(ENCODING)) - - @classmethod - def to_mopidy_track(cls, spotify_track): - if not spotify_track.is_loaded(): - return Track(name=u'[loading...]') - uri = str(Link.from_track(spotify_track, 0)) - if dt.MINYEAR <= int(spotify_track.album().year()) <= dt.MAXYEAR: - date = dt.date(spotify_track.album().year(), 1, 1) - else: - date = None - return Track( - uri=uri, - name=spotify_track.name().decode(ENCODING), - artists=[cls.to_mopidy_artist(a) for a in spotify_track.artists()], - album=cls.to_mopidy_album(spotify_track.album()), - track_no=spotify_track.index(), - date=date, - length=spotify_track.duration(), - bitrate=320, - ) - - @classmethod - def to_mopidy_playlist(cls, spotify_playlist): - if not spotify_playlist.is_loaded(): - return Playlist(name=u'[loading...]') - return Playlist( - uri=str(Link.from_playlist(spotify_playlist)), - name=spotify_playlist.name().decode(ENCODING), - tracks=[cls.to_mopidy_track(t) for t in spotify_playlist], - ) - -class LibspotifySessionManager(SpotifySessionManager, threading.Thread): - cache_location = os.path.expanduser(settings.SPOTIFY_LIB_CACHE) - settings_location = os.path.expanduser(settings.SPOTIFY_LIB_CACHE) - appkey_file = os.path.expanduser(settings.SPOTIFY_LIB_APPKEY) - user_agent = 'Mopidy %s' % get_version() - - def __init__(self, username, password, core_queue, output_queue): - SpotifySessionManager.__init__(self, username, password) - threading.Thread.__init__(self) - self.core_queue = core_queue - self.output_queue = output_queue - self.connected = threading.Event() - self.session = None - - def run(self): - self.connect() - - def logged_in(self, session, error): - """Callback used by pyspotify""" - logger.info('Logged in') - self.session = session - self.connected.set() - - def logged_out(self, session): - """Callback used by pyspotify""" - logger.info('Logged out') - - def metadata_updated(self, session): - """Callback used by pyspotify""" - logger.debug('Metadata updated, refreshing stored playlists') - playlists = [] - for spotify_playlist in session.playlist_container(): - playlists.append( - LibspotifyTranslator.to_mopidy_playlist(spotify_playlist)) - self.core_queue.put({ - 'command': 'set_stored_playlists', - 'playlists': playlists, - }) - - def connection_error(self, session, error): - """Callback used by pyspotify""" - logger.error('Connection error: %s', error) - - def message_to_user(self, session, message): - """Callback used by pyspotify""" - logger.info(message) - - def notify_main_thread(self, session): - """Callback used by pyspotify""" - logger.debug('Notify main thread') - - def music_delivery(self, session, frames, frame_size, num_frames, - sample_type, sample_rate, channels): - """Callback used by pyspotify""" - # TODO Base caps_string on arguments - caps_string = """ - audio/x-raw-int, - endianness=(int)1234, - channels=(int)2, - width=(int)16, - depth=(int)16, - signed=True, - rate=(int)44100 - """ - self.output_queue.put({ - 'command': 'deliver_data', - 'caps': caps_string, - 'data': bytes(frames), - }) - - def play_token_lost(self, session): - """Callback used by pyspotify""" - logger.debug('Play token lost') - self.core_queue.put({'command': 'stop_playback'}) - - def log_message(self, session, data): - """Callback used by pyspotify""" - logger.debug(data) - - def end_of_track(self, session): - """Callback used by pyspotify""" - logger.debug('End of data stream.') - self.output_queue.put({'command': 'end_of_data_stream'}) - - def search(self, query, connection): - """Search method used by Mopidy backend""" - def callback(results, userdata): - # TODO Include results from results.albums(), etc. too - playlist = Playlist(tracks=[ - LibspotifyTranslator.to_mopidy_track(t) - for t in results.tracks()]) - connection.send(playlist) - self.connected.wait() - self.session.search(query, callback) diff --git a/mopidy/backends/libspotify/library.py b/mopidy/backends/libspotify/library.py new file mode 100644 index 00000000..c2b70dca --- /dev/null +++ b/mopidy/backends/libspotify/library.py @@ -0,0 +1,41 @@ +import logging +import multiprocessing + +from spotify import Link + +from mopidy.backends.base import BaseLibraryController +from mopidy.backends.libspotify import ENCODING +from mopidy.backends.libspotify.translator import LibspotifyTranslator + +logger = logging.getLogger('mopidy.backends.libspotify.library') + +class LibspotifyLibraryController(BaseLibraryController): + def find_exact(self, **query): + return self.search(**query) + + def lookup(self, uri): + spotify_track = Link.from_string(uri).as_track() + return LibspotifyTranslator.to_mopidy_track(spotify_track) + + def refresh(self, uri=None): + pass # TODO + + def search(self, **query): + spotify_query = [] + for (field, values) in query.iteritems(): + if not hasattr(values, '__iter__'): + values = [values] + for value in values: + if field == u'track': + field = u'title' + if field == u'any': + spotify_query.append(value) + else: + spotify_query.append(u'%s:"%s"' % (field, value)) + spotify_query = u' '.join(spotify_query) + logger.debug(u'Spotify search query: %s' % spotify_query) + my_end, other_end = multiprocessing.Pipe() + self.backend.spotify.search(spotify_query.encode(ENCODING), other_end) + my_end.poll(None) + playlist = my_end.recv() + return playlist diff --git a/mopidy/backends/libspotify/playback.py b/mopidy/backends/libspotify/playback.py new file mode 100644 index 00000000..3ba91d5f --- /dev/null +++ b/mopidy/backends/libspotify/playback.py @@ -0,0 +1,51 @@ +import logging +import multiprocessing + +from spotify import Link, SpotifyError + +from mopidy.backends.base import BasePlaybackController +from mopidy.process import pickle_connection + +logger = logging.getLogger('mopidy.backends.libspotify.playback') + +class LibspotifyPlaybackController(BasePlaybackController): + def _set_output_state(self, state_name): + logger.debug(u'Setting output state to %s ...', state_name) + (my_end, other_end) = multiprocessing.Pipe() + self.backend.output_queue.put({ + 'command': 'set_state', + 'state': state_name, + 'reply_to': pickle_connection(other_end), + }) + my_end.poll(None) + return my_end.recv() + + def _pause(self): + return self._set_output_state('PAUSED') + + def _play(self, track): + self._set_output_state('READY') + if self.state == self.PLAYING: + self.stop() + if track.uri is None: + return False + try: + self.backend.spotify.session.load( + Link.from_string(track.uri).as_track()) + self.backend.spotify.session.play(1) + self._set_output_state('PLAYING') + return True + except SpotifyError as e: + logger.warning('Play %s failed: %s', track.uri, e) + return False + + def _resume(self): + return self._set_output_state('PLAYING') + + def _seek(self, time_position): + pass # TODO + + def _stop(self): + result = self._set_output_state('READY') + self.backend.spotify.session.play(0) + return result diff --git a/mopidy/backends/libspotify/session_manager.py b/mopidy/backends/libspotify/session_manager.py new file mode 100644 index 00000000..e286b059 --- /dev/null +++ b/mopidy/backends/libspotify/session_manager.py @@ -0,0 +1,106 @@ +import logging +import os +import threading + +from spotify.manager import SpotifySessionManager + +from mopidy import get_version, settings +from mopidy.models import Playlist +from mopidy.backends.libspotify.translator import LibspotifyTranslator + +logger = logging.getLogger('mopidy.backends.libspotify.session_manager') + +class LibspotifySessionManager(SpotifySessionManager, threading.Thread): + cache_location = os.path.expanduser(settings.SPOTIFY_LIB_CACHE) + settings_location = os.path.expanduser(settings.SPOTIFY_LIB_CACHE) + appkey_file = os.path.expanduser(settings.SPOTIFY_LIB_APPKEY) + user_agent = 'Mopidy %s' % get_version() + + def __init__(self, username, password, core_queue, output_queue): + SpotifySessionManager.__init__(self, username, password) + threading.Thread.__init__(self) + self.core_queue = core_queue + self.output_queue = output_queue + self.connected = threading.Event() + self.session = None + + def run(self): + self.connect() + + def logged_in(self, session, error): + """Callback used by pyspotify""" + logger.info('Logged in') + self.session = session + self.connected.set() + + def logged_out(self, session): + """Callback used by pyspotify""" + logger.info('Logged out') + + def metadata_updated(self, session): + """Callback used by pyspotify""" + logger.debug('Metadata updated, refreshing stored playlists') + playlists = [] + for spotify_playlist in session.playlist_container(): + playlists.append( + LibspotifyTranslator.to_mopidy_playlist(spotify_playlist)) + self.core_queue.put({ + 'command': 'set_stored_playlists', + 'playlists': playlists, + }) + + def connection_error(self, session, error): + """Callback used by pyspotify""" + logger.error('Connection error: %s', error) + + def message_to_user(self, session, message): + """Callback used by pyspotify""" + logger.info(message) + + def notify_main_thread(self, session): + """Callback used by pyspotify""" + logger.debug('Notify main thread') + + def music_delivery(self, session, frames, frame_size, num_frames, + sample_type, sample_rate, channels): + """Callback used by pyspotify""" + # TODO Base caps_string on arguments + caps_string = """ + audio/x-raw-int, + endianness=(int)1234, + channels=(int)2, + width=(int)16, + depth=(int)16, + signed=True, + rate=(int)44100 + """ + self.output_queue.put({ + 'command': 'deliver_data', + 'caps': caps_string, + 'data': bytes(frames), + }) + + def play_token_lost(self, session): + """Callback used by pyspotify""" + logger.debug('Play token lost') + self.core_queue.put({'command': 'stop_playback'}) + + def log_message(self, session, data): + """Callback used by pyspotify""" + logger.debug(data) + + def end_of_track(self, session): + """Callback used by pyspotify""" + logger.debug('End of data stream.') + self.output_queue.put({'command': 'end_of_data_stream'}) + + def search(self, query, connection): + """Search method used by Mopidy backend""" + def callback(results, userdata): + # TODO Include results from results.albums(), etc. too + playlist = Playlist(tracks=[ + LibspotifyTranslator.to_mopidy_track(t) + for t in results.tracks()]) + connection.send(playlist) + self.connected.wait() + self.session.search(query, callback) diff --git a/mopidy/backends/libspotify/stored_playlists.py b/mopidy/backends/libspotify/stored_playlists.py new file mode 100644 index 00000000..3339578c --- /dev/null +++ b/mopidy/backends/libspotify/stored_playlists.py @@ -0,0 +1,20 @@ +from mopidy.backends.base import BaseStoredPlaylistsController + +class LibspotifyStoredPlaylistsController(BaseStoredPlaylistsController): + def create(self, name): + pass # TODO + + def delete(self, playlist): + pass # TODO + + def lookup(self, uri): + pass # TODO + + def refresh(self): + pass # TODO + + def rename(self, playlist, new_name): + pass # TODO + + def save(self, playlist): + pass # TODO diff --git a/mopidy/backends/libspotify/translator.py b/mopidy/backends/libspotify/translator.py new file mode 100644 index 00000000..3a39aad5 --- /dev/null +++ b/mopidy/backends/libspotify/translator.py @@ -0,0 +1,53 @@ +import datetime as dt + +from spotify import Link + +from mopidy.models import Artist, Album, Track, Playlist +from mopidy.backends.libspotify import ENCODING + +class LibspotifyTranslator(object): + @classmethod + def to_mopidy_artist(cls, spotify_artist): + if not spotify_artist.is_loaded(): + return Artist(name=u'[loading...]') + return Artist( + uri=str(Link.from_artist(spotify_artist)), + name=spotify_artist.name().decode(ENCODING), + ) + + @classmethod + def to_mopidy_album(cls, spotify_album): + if not spotify_album.is_loaded(): + return Album(name=u'[loading...]') + # TODO pyspotify got much more data on albums than this + return Album(name=spotify_album.name().decode(ENCODING)) + + @classmethod + def to_mopidy_track(cls, spotify_track): + if not spotify_track.is_loaded(): + return Track(name=u'[loading...]') + uri = str(Link.from_track(spotify_track, 0)) + if dt.MINYEAR <= int(spotify_track.album().year()) <= dt.MAXYEAR: + date = dt.date(spotify_track.album().year(), 1, 1) + else: + date = None + return Track( + uri=uri, + name=spotify_track.name().decode(ENCODING), + artists=[cls.to_mopidy_artist(a) for a in spotify_track.artists()], + album=cls.to_mopidy_album(spotify_track.album()), + track_no=spotify_track.index(), + date=date, + length=spotify_track.duration(), + bitrate=320, + ) + + @classmethod + def to_mopidy_playlist(cls, spotify_playlist): + if not spotify_playlist.is_loaded(): + return Playlist(name=u'[loading...]') + return Playlist( + uri=str(Link.from_playlist(spotify_playlist)), + name=spotify_playlist.name().decode(ENCODING), + tracks=[cls.to_mopidy_track(t) for t in spotify_playlist], + ) From 059f96814d80fc1a3c14e7e9f40428865ceb233a Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Aug 2010 22:16:11 +0200 Subject: [PATCH 03/17] Add basic tests for get_class util --- tests/utils_test.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/utils_test.py b/tests/utils_test.py index d5c98d86..9a8f1129 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -11,6 +11,15 @@ from mopidy.models import Track, Artist, Album from tests import SkipTest, data_folder +class GetClassTest(unittest.TestCase): + def test_loading_class_that_does_not_exist(self): + test = lambda: get_class('foo.bar.Baz') + self.assertRaises(ImportError, test) + + def test_loading_existing_class(self): + cls = get_class('unittest.TestCase') + self.assertEqual(cls.__name__, 'TestCase') + class GetOrCreateFolderTest(unittest.TestCase): def setUp(self): self.parent = tempfile.mkdtemp() From db26a7198da4cf222abd399241700fbfc9554316 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 13 Aug 2010 22:26:13 +0200 Subject: [PATCH 04/17] Remove notes on openspotify, as development has been inactive for six months --- docs/development/roadmap.rst | 4 ---- mopidy/backends/libspotify/__init__.py | 7 ++----- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/docs/development/roadmap.rst b/docs/development/roadmap.rst index 5544a005..7d97d55b 100644 --- a/docs/development/roadmap.rst +++ b/docs/development/roadmap.rst @@ -31,10 +31,6 @@ released when we reach the other goal. Stuff we really want to do, but just not right now ================================================== -- Replace libspotify with `openspotify - `_ for - :mod:`mopidy.backends.libspotify`. *Update:* Seems like openspotify - development has stalled. - Create `Debian packages `_ of all our dependencies and Mopidy itself (hosted in our own Debian repo until we get stuff into the various distros) to make Debian/Ubuntu installation a breeze. diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index ead08c44..7a971bc5 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -16,15 +16,12 @@ class LibspotifyBackend(BaseBackend): for libspotify. It got no documentation, but multiple examples are available. Like libspotify, pyspotify's calls are mostly asynchronous. - This backend should also work with `openspotify - `_, but we haven't tested - that yet. - **Issues:** http://github.com/jodal/mopidy/issues/labels/backend-libspotify """ - # Imports inside methods are to prevent loading of __init__ to fail on + # Imports inside methods are to prevent loading of __init__.py to fail on # missing spotify dependencies. + def __init__(self, *args, **kwargs): from .library import LibspotifyLibraryController from .playback import LibspotifyPlaybackController From e4bdacbb61a6894a8515bd5bae100cb978514028 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Aug 2010 22:28:02 +0200 Subject: [PATCH 05/17] Add test_import_error_message_contains_complete_class_path test for get_class --- mopidy/utils.py | 5 ++++- tests/utils_test.py | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/mopidy/utils.py b/mopidy/utils.py index ff032b4e..b8aa574c 100644 --- a/mopidy/utils.py +++ b/mopidy/utils.py @@ -24,7 +24,10 @@ def get_class(name): module_name = name[:name.rindex('.')] class_name = name[name.rindex('.') + 1:] logger.debug('Loading: %s', name) - module = import_module(module_name) + try: + module = import_module(module_name) + except ImportError: + raise ImportError("Couldn't load: %s" % name) class_object = getattr(module, class_name) return class_object diff --git a/tests/utils_test.py b/tests/utils_test.py index 9a8f1129..d5beade2 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -16,6 +16,12 @@ class GetClassTest(unittest.TestCase): test = lambda: get_class('foo.bar.Baz') self.assertRaises(ImportError, test) + def test_import_error_message_contains_complete_class_path(self): + try: + get_class('foo.bar.Baz') + except ImportError as e: + self.assert_('foo.bar.Baz' in str(e)) + def test_loading_existing_class(self): cls = get_class('unittest.TestCase') self.assertEqual(cls.__name__, 'TestCase') From 710eb91892aeddc79d9e8e542d9e4350a3095989 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 13 Aug 2010 22:29:27 +0200 Subject: [PATCH 06/17] docs: Update Homebrew point on roadmap --- docs/development/roadmap.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/development/roadmap.rst b/docs/development/roadmap.rst index 7d97d55b..243243ab 100644 --- a/docs/development/roadmap.rst +++ b/docs/development/roadmap.rst @@ -31,11 +31,13 @@ released when we reach the other goal. Stuff we really want to do, but just not right now ================================================== +- **[PENDING]** Create `Homebrew `_ recipies + for all our dependencies and Mopidy itself to make OS X installation a + breeze. See `Homebrew's issue #1612 + `_. - Create `Debian packages `_ of all our dependencies and Mopidy itself (hosted in our own Debian repo until we get stuff into the various distros) to make Debian/Ubuntu installation a breeze. -- **[WIP]** Create `Homebrew `_ recipies for - all our dependencies and Mopidy itself to make OS X installation a breeze. - Run frontend tests against a real MPD server to ensure we are in sync. - Start working with MPD client maintainers to get rid of weird assumptions like only searching for first two letters and doing the rest of the filtering From ec67d43fc960d5537fef3fdf5e6fc46c6a354e0f Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Aug 2010 22:29:41 +0200 Subject: [PATCH 07/17] Test both case where class and/or module does not exist for get_class --- mopidy/utils.py | 4 ++-- tests/utils_test.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/mopidy/utils.py b/mopidy/utils.py index b8aa574c..bdc0b632 100644 --- a/mopidy/utils.py +++ b/mopidy/utils.py @@ -26,9 +26,9 @@ def get_class(name): logger.debug('Loading: %s', name) try: module = import_module(module_name) - except ImportError: + class_object = getattr(module, class_name) + except (ImportError, AttributeError): raise ImportError("Couldn't load: %s" % name) - class_object = getattr(module, class_name) return class_object def get_or_create_folder(folder): diff --git a/tests/utils_test.py b/tests/utils_test.py index d5beade2..ca44de45 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -12,10 +12,14 @@ from mopidy.models import Track, Artist, Album from tests import SkipTest, data_folder class GetClassTest(unittest.TestCase): - def test_loading_class_that_does_not_exist(self): + def test_loading_module_that_does_not_exist(self): test = lambda: get_class('foo.bar.Baz') self.assertRaises(ImportError, test) + def test_loading_class_that_does_not_exist(self): + test = lambda: get_class('unittest.FooBarBaz') + self.assertRaises(ImportError, test) + def test_import_error_message_contains_complete_class_path(self): try: get_class('foo.bar.Baz') From fa9edf23cf0d3a028095e83737cff4a69fc43dff Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 13 Aug 2010 22:29:56 +0200 Subject: [PATCH 08/17] Update MANIFEST.in to include LICENSE instead of COPYING --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index cb752f87..8a73b481 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include COPYING pylintrc *.rst *.txt +include LICENSE pylintrc *.rst *.txt recursive-include docs * prune docs/_build recursive-include tests *.py From 9c11c5ecb9ad5f57307a30d133ccdeb8f1a11f93 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Aug 2010 22:40:38 +0200 Subject: [PATCH 09/17] Log when a process has a problem importing classes and try to exit --- mopidy/process.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mopidy/process.py b/mopidy/process.py index 9759c4e6..79638515 100644 --- a/mopidy/process.py +++ b/mopidy/process.py @@ -28,6 +28,9 @@ class BaseProcess(multiprocessing.Process): except SettingsError as e: logger.error(e.message) sys.exit(1) + except ImportError as e: + logger.error(e) + sys.exit(1) def run_inside_try(self): raise NotImplementedError From a05212251bec02740b651eaf9849e190ebd1546e Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Aug 2010 22:48:51 +0200 Subject: [PATCH 10/17] Pass output, backend and frontend classes into coreprocess to so that import errors are handeled better --- mopidy/__main__.py | 5 ++++- mopidy/process.py | 14 ++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 7c62033b..c92ce1ed 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -22,7 +22,10 @@ def main(): get_or_create_folder('~/.mopidy/') core_queue = multiprocessing.Queue() get_class(settings.SERVER)(core_queue).start() - core = CoreProcess(core_queue) + output_class = get_class(settings.OUTPUT) + backend_class = get_class(settings.BACKENDS[0]) + frontend_class = get_class(settings.FRONTEND) + core = CoreProcess(core_queue, output_class, backend_class, frontend_class) core.start() asyncore.loop() diff --git a/mopidy/process.py b/mopidy/process.py index 79638515..b1cdc8af 100644 --- a/mopidy/process.py +++ b/mopidy/process.py @@ -37,10 +37,14 @@ class BaseProcess(multiprocessing.Process): class CoreProcess(BaseProcess): - def __init__(self, core_queue): + def __init__(self, core_queue, output_class, backend_class, + frontend_class): super(CoreProcess, self).__init__() self.core_queue = core_queue self.output_queue = None + self.output_class = output_class + self.backend_class = backend_class + self.frontend_class = frontend_class self.output = None self.backend = None self.frontend = None @@ -53,11 +57,9 @@ class CoreProcess(BaseProcess): def setup(self): self.output_queue = multiprocessing.Queue() - self.output = get_class(settings.OUTPUT)(self.core_queue, - self.output_queue) - self.backend = get_class(settings.BACKENDS[0])(self.core_queue, - self.output_queue) - self.frontend = get_class(settings.FRONTEND)(self.backend) + self.output = self.output_class(self.core_queue, self.output_queue) + self.backend = self.backend_class(self.core_queue, self.output_queue) + self.frontend = self.frontend_class(self.backend) def process_message(self, message): if message.get('to') == 'output': From 81928b831c354de0b79b4e203f235c8067b535fb Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 13 Aug 2010 23:20:17 +0200 Subject: [PATCH 11/17] Strip newline at end of libspotify log messages --- mopidy/backends/libspotify/session_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/backends/libspotify/session_manager.py b/mopidy/backends/libspotify/session_manager.py index e286b059..2de6ae63 100644 --- a/mopidy/backends/libspotify/session_manager.py +++ b/mopidy/backends/libspotify/session_manager.py @@ -55,7 +55,7 @@ class LibspotifySessionManager(SpotifySessionManager, threading.Thread): def message_to_user(self, session, message): """Callback used by pyspotify""" - logger.info(message) + logger.info(message.strip()) def notify_main_thread(self, session): """Callback used by pyspotify""" @@ -87,7 +87,7 @@ class LibspotifySessionManager(SpotifySessionManager, threading.Thread): def log_message(self, session, data): """Callback used by pyspotify""" - logger.debug(data) + logger.debug(data.strip()) def end_of_track(self, session): """Callback used by pyspotify""" From 5a4d0bd7160ce8741ca876bf626ee0a93959259e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 13 Aug 2010 23:41:31 +0200 Subject: [PATCH 12/17] Freshen up settings docs --- docs/api/settings.rst | 2 +- mopidy/settings.py | 81 ++++++++++++++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 22 deletions(-) diff --git a/docs/api/settings.rst b/docs/api/settings.rst index 12d2833f..cfc270d6 100644 --- a/docs/api/settings.rst +++ b/docs/api/settings.rst @@ -13,7 +13,7 @@ there. A complete ``~/.mopidy/settings.py`` may look like this:: - MPD_SERVER_HOSTNAME = u'0.0.0.0' + MPD_SERVER_HOSTNAME = u'::' SPOTIFY_USERNAME = u'alice' SPOTIFY_PASSWORD = u'mysecret' diff --git a/mopidy/settings.py b/mopidy/settings.py index d4321685..949b2e06 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -3,16 +3,19 @@ Available settings and their default values. .. warning:: - Do *not* change settings in ``mopidy/settings.py``. Instead, add a file - called ``~/.mopidy/settings.py`` and redefine settings there. + Do *not* change settings directly in :mod:`mopidy.settings`. Instead, add a + file called ``~/.mopidy/settings.py`` and redefine settings there. """ +# Absolute import needed to import ~/.mopidy/settings.py and not ourselves from __future__ import absolute_import import os import sys #: List of playback backends to use. See :mod:`mopidy.backends` for all -#: available backends. Default:: +#: available backends. +#: +#: Default:: #: #: BACKENDS = (u'mopidy.backends.libspotify.LibspotifyBackend',) #: @@ -28,32 +31,51 @@ BACKENDS = ( CONSOLE_LOG_FORMAT = u'%(levelname)-8s %(asctime)s' + \ ' [%(process)d:%(threadName)s] %(name)s\n %(message)s' -#: The log format used for dump logs. Default:: +#: The log format used for dump logs. +#: +#: Default:: #: #: DUMP_LOG_FILENAME = CONSOLE_LOG_FORMAT DUMP_LOG_FORMAT = CONSOLE_LOG_FORMAT -#: The file to dump debug log data to. Default:: +#: The file to dump debug log data to when Mopidy is run with the +#: :option:`--dump` option. +#: +#: Default:: #: #: DUMP_LOG_FILENAME = u'dump.log' DUMP_LOG_FILENAME = u'dump.log' -#: Protocol frontend to use. Default:: +#: Protocol frontend to use. +#: +#: Default:: #: #: FRONTEND = u'mopidy.frontends.mpd.frontend.MpdFrontend' FRONTEND = u'mopidy.frontends.mpd.frontend.MpdFrontend' -#: Path to folder with local music. Default:: +#: Path to folder with local music. +#: +#: Used by :mod:`mopidy.backends.local`. +#: +#: Default:: #: #: LOCAL_MUSIC_FOLDER = u'~/music' LOCAL_MUSIC_FOLDER = u'~/music' -#: Path to playlist folder with m3u files for local music. Default:: +#: Path to playlist folder with m3u files for local music. +#: +#: Used by :mod:`mopidy.backends.local`. +#: +#: Default:: #: #: LOCAL_PLAYLIST_FOLDER = u'~/.mopidy/playlists' LOCAL_PLAYLIST_FOLDER = u'~/.mopidy/playlists' -#: Path to tag cache for local music. Default:: +#: Path to tag cache for local music. +#: +#: Used by :mod:`mopidy.backends.local`. +#: +#: Default:: #: #: LOCAL_TAG_CACHE = u'~/.mopidy/tag_cache' LOCAL_TAG_CACHE = u'~/.mopidy/tag_cache' @@ -86,6 +108,7 @@ MIXER_ALSA_CONTROL = False #: External mixers only. Which port the mixer is connected to. #: #: This must point to the device port like ``/dev/ttyUSB0``. +#: #: Default: :class:`None` MIXER_EXT_PORT = None @@ -104,17 +127,23 @@ MIXER_EXT_SPEAKERS_A = None #: Default: :class:`None`. MIXER_EXT_SPEAKERS_B = None -#: Audio output handler to use. Default:: +#: Audio output handler to use. +#: +#: Default:: #: #: OUTPUT = u'mopidy.outputs.gstreamer.GStreamerOutput' OUTPUT = u'mopidy.outputs.gstreamer.GStreamerOutput' -#: Server to use. Default:: +#: Server to use. +#: +#: Default:: #: #: SERVER = u'mopidy.frontends.mpd.server.MpdServer' SERVER = u'mopidy.frontends.mpd.server.MpdServer' -#: Which address Mopidy should bind to. Examples: +#: Which address Mopidy's MPD server should bind to. +#: +#:Examples: #: #: ``127.0.0.1`` #: Listens only on the IPv4 loopback interface. Default. @@ -126,21 +155,31 @@ SERVER = u'mopidy.frontends.mpd.server.MpdServer' #: Listens on all interfaces, both IPv4 and IPv6. MPD_SERVER_HOSTNAME = u'127.0.0.1' -#: Which TCP port Mopidy should listen to. Default: 6600 +#: Which TCP port Mopidy's MPD server should listen to. +#: +#: Default: 6600 MPD_SERVER_PORT = 6600 -#: Your Spotify Premium username. Used by all Spotify backends. -SPOTIFY_USERNAME = u'' - -#: Your Spotify Premium password. Used by all Spotify backends. -SPOTIFY_PASSWORD = u'' - -#: Path to your libspotify application key. Used by LibspotifyBackend. +#: Path to your libspotify application key. +#: +#: Used by :mod:`mopidy.backends.libspotify`. SPOTIFY_LIB_APPKEY = u'~/.mopidy/spotify_appkey.key' -#: Path to the libspotify cache. Used by LibspotifyBackend. +#: Path to the libspotify cache. +#: +#: Used by :mod:`mopidy.backends.libspotify`. SPOTIFY_LIB_CACHE = u'~/.mopidy/libspotify_cache' +#: Your Spotify Premium username. +#: +#: Used by :mod:`mopidy.backends.libspotify`. +SPOTIFY_USERNAME = u'' + +#: Your Spotify Premium password. +#: +#: Used by :mod:`mopidy.backends.libspotify`. +SPOTIFY_PASSWORD = u'' + # Import user specific settings dotdir = os.path.expanduser(u'~/.mopidy/') settings_file = os.path.join(dotdir, u'settings.py') From b777397cceb63bbccce302904e78b51c5991ab17 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Aug 2010 23:52:46 +0200 Subject: [PATCH 13/17] Cleanup pipe creation for GStreamer output --- mopidy/outputs/gstreamer.py | 44 +++++-------------------------------- 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 65b65504..b81fbd0f 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -39,6 +39,8 @@ class GStreamerProcess(BaseProcess): http://jameswestby.net/weblog/tech/14-caution-python-multiprocessing-and-glib-dont-mix.html. """ + pipeline_description = 'appsrc name=data ! volume name=volume ! autoaudiosink name=sink' + def __init__(self, core_queue, output_queue): super(GStreamerProcess, self).__init__() self.core_queue = core_queue @@ -65,8 +67,10 @@ class GStreamerProcess(BaseProcess): messages_thread.daemon = True messages_thread.start() - # A pipeline consisting of many elements - self.gst_pipeline = gst.Pipeline("pipeline") + self.gst_pipeline = gst.parse_launch(self.pipeline_description) + self.gst_data_src = self.gst_pipeline.get_by_name('data') + self.gst_volume = self.gst_pipeline.get_by_name('volume') + self.gst_sink = self.gst_pipeline.get_by_name('sink') # Setup bus and message processor self.gst_bus = self.gst_pipeline.get_bus() @@ -74,42 +78,6 @@ class GStreamerProcess(BaseProcess): self.gst_bus_id = self.gst_bus.connect('message', self.process_gst_message) - # Bin for playing audio URIs - #self.gst_uri_src = gst.element_factory_make('uridecodebin', 'uri_src') - #self.gst_pipeline.add(self.gst_uri_src) - - # Bin for playing audio data - self.gst_data_src = gst.element_factory_make('appsrc', 'data_src') - self.gst_pipeline.add(self.gst_data_src) - - # Volume filter - self.gst_volume = gst.element_factory_make('volume', 'volume') - self.gst_pipeline.add(self.gst_volume) - - # Audio output sink - self.gst_sink = gst.element_factory_make('autoaudiosink', 'sink') - self.gst_pipeline.add(self.gst_sink) - - # Add callback that will link uri_src output with volume filter input - # when the output pad is ready. - # See http://stackoverflow.com/questions/2993777 for details. - def on_new_decoded_pad(dbin, pad, is_last): - uri_src = pad.get_parent() - pipeline = uri_src.get_parent() - volume = pipeline.get_by_name('volume') - uri_src.link(volume) - logger.debug("Linked uri_src's new decoded pad to volume filter") - # FIXME uridecodebin got no new-decoded-pad signal, but it's - # subcomponent decodebin2 got that signal. Fixing this is postponed - # till after data_src is up and running perfectly - #self.gst_uri_src.connect('new-decoded-pad', on_new_decoded_pad) - - # Link data source output with volume filter input - self.gst_data_src.link(self.gst_volume) - - # Link volume filter output to audio sink input - self.gst_volume.link(self.gst_sink) - def process_mopidy_message(self, message): """Process messages from the rest of Mopidy.""" if message['command'] == 'play_uri': From 8d19301d41e8bc6f2e81c875671ea8e500a9e3c5 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 14 Aug 2010 00:48:15 +0200 Subject: [PATCH 14/17] Update license i PyPI classifiers --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index bbf300f7..33113732 100644 --- a/setup.py +++ b/setup.py @@ -52,14 +52,14 @@ setup( data_files=data_files, scripts=['bin/mopidy'], url='http://www.mopidy.com/', - license='GPLv2', + license='Apache License, Version 2.0', description='MPD server with Spotify support', long_description=open('README.rst').read(), classifiers=[ 'Development Status :: 3 - Alpha', 'Environment :: No Input/Output (Daemon)', 'Intended Audience :: End Users/Desktop', - 'License :: OSI Approved :: GNU General Public License (GPL)', + 'License :: OSI Approved :: Apache Software License', 'Operating System :: MacOS :: MacOS X', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python :: 2.6', From 4ceb86cad0882a6f0568a58ada154b1a5c9b66da Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 14 Aug 2010 00:48:37 +0200 Subject: [PATCH 15/17] Switch to beta status in PyPI classifiers --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 33113732..5ac94c00 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ setup( description='MPD server with Spotify support', long_description=open('README.rst').read(), classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 4 - Beta', 'Environment :: No Input/Output (Daemon)', 'Intended Audience :: End Users/Desktop', 'License :: OSI Approved :: Apache Software License', From 63d2e7710e6088cee4162c9987c4ba4179d32965 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 14 Aug 2010 00:50:22 +0200 Subject: [PATCH 16/17] Copy distutils install_data fix from Django --- setup.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5ac94c00..76c38e4b 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,34 @@ +""" +Most of this file is taken from the Django project, which is BSD licensed. +""" + from distutils.core import setup +from distutils.command.install_data import install_data from distutils.command.install import INSTALL_SCHEMES import os +import sys from mopidy import get_version +class osx_install_data(install_data): + # On MacOS, the platform-specific lib dir is + # /System/Library/Framework/Python/.../ which is wrong. Python 2.5 supplied + # with MacOS 10.5 has an Apple-specific fix for this in + # distutils.command.install_data#306. It fixes install_lib but not + # install_data, which is why we roll our own install_data class. + + def finalize_options(self): + # By the time finalize_options is called, install.install_lib is set to + # the fixed directory, so we set the installdir to install_lib. The + # install_data class uses ('install_data', 'install_dir') instead. + self.set_undefined_options('install', ('install_lib', 'install_dir')) + install_data.finalize_options(self) + +if sys.platform == "darwin": + cmdclasses = {'install_data': osx_install_data} +else: + cmdclasses = {'install_data': install_data} + def fullsplit(path, result=None): """ Split a pathname into components (the opposite of os.path.join) in a @@ -20,7 +45,8 @@ def fullsplit(path, result=None): # Tell distutils to put the data_files in platform-specific installation # locations. See here for an explanation: -# http://groups.google.com/group/comp.lang.python/browse_thread/thread/35ec7b2fed36eaec/2105ee4d9e8042cb +# http://groups.google.com/group/comp.lang.python/browse_thread/ +# thread/35ec7b2fed36eaec/2105ee4d9e8042cb for scheme in INSTALL_SCHEMES.values(): scheme['data'] = scheme['purelib'] @@ -49,6 +75,7 @@ setup( author='Stein Magnus Jodal', author_email='stein.magnus@jodal.no', packages=packages, + cmdclass=cmdclasses, data_files=data_files, scripts=['bin/mopidy'], url='http://www.mopidy.com/', From 8238e955b815794b5316a4d94f8a2087d9443d97 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 14 Aug 2010 00:55:25 +0200 Subject: [PATCH 17/17] Bundle a Spotify appkey with the libspotify backend --- MANIFEST.in | 1 + docs/changes.rst | 2 ++ mopidy/backends/libspotify/session_manager.py | 2 +- mopidy/backends/libspotify/spotify_appkey.key | Bin 0 -> 321 bytes mopidy/settings.py | 5 ----- setup.py | 1 + 6 files changed, 5 insertions(+), 6 deletions(-) create mode 100644 mopidy/backends/libspotify/spotify_appkey.key diff --git a/MANIFEST.in b/MANIFEST.in index 8a73b481..38819adb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include LICENSE pylintrc *.rst *.txt +include mopidy/backends/libspotify/spotify_appkey.key recursive-include docs * prune docs/_build recursive-include tests *.py diff --git a/docs/changes.rst b/docs/changes.rst index 7b154915..c20b2ad1 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -23,6 +23,8 @@ Another great release. - Remove :mod:`mopidy.backends.despotify`, as Despotify is little maintained and the Libspotify backend is working much better. - :mod:`mopidy.backends.libspotify` is now the default backend. +- A Spotify application key is now bundled with the source. The + ``SPOTIFY_LIB_APPKEY`` setting is thus removed. - MPD frontend: - Relocate from :mod:`mopidy.mpd` to :mod:`mopidy.frontends.mpd`. diff --git a/mopidy/backends/libspotify/session_manager.py b/mopidy/backends/libspotify/session_manager.py index 2de6ae63..707423aa 100644 --- a/mopidy/backends/libspotify/session_manager.py +++ b/mopidy/backends/libspotify/session_manager.py @@ -13,7 +13,7 @@ logger = logging.getLogger('mopidy.backends.libspotify.session_manager') class LibspotifySessionManager(SpotifySessionManager, threading.Thread): cache_location = os.path.expanduser(settings.SPOTIFY_LIB_CACHE) settings_location = os.path.expanduser(settings.SPOTIFY_LIB_CACHE) - appkey_file = os.path.expanduser(settings.SPOTIFY_LIB_APPKEY) + appkey_file = os.path.join(os.path.dirname(__file__), 'spotify_appkey.key') user_agent = 'Mopidy %s' % get_version() def __init__(self, username, password, core_queue, output_queue): diff --git a/mopidy/backends/libspotify/spotify_appkey.key b/mopidy/backends/libspotify/spotify_appkey.key new file mode 100644 index 0000000000000000000000000000000000000000..1f840b962d9245820e73803ae5995650b4f84f62 GIT binary patch literal 321 zcmV-H0lxkL&xsG-pVlEz7LL?2e{+JtQpZk(M<9(;xguUY#VZNv&txxTh0nuFe(N{} zC?#&u)&58KeoT-KpSTN{8Wb)hzuj?jZNaN?^McImAMP|w&4GR8DyOK-#=V!cSw`&V5lyby`QwVzk}bWQ#Ui#m2fN)=wRSqK33~=D8OATMF|fdmT#G0B?yVov-+)u7w0gkTjyb{I{VGW`-;#R z$iCRsr@I8@9i#w7y@Y$>dnR3OOhWI%a!F~QeP*7Os+7-($V~m!LFZ(l=H!@+PtT&9 literal 0 HcmV?d00001 diff --git a/mopidy/settings.py b/mopidy/settings.py index 949b2e06..b17af913 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -160,11 +160,6 @@ MPD_SERVER_HOSTNAME = u'127.0.0.1' #: Default: 6600 MPD_SERVER_PORT = 6600 -#: Path to your libspotify application key. -#: -#: Used by :mod:`mopidy.backends.libspotify`. -SPOTIFY_LIB_APPKEY = u'~/.mopidy/spotify_appkey.key' - #: Path to the libspotify cache. #: #: Used by :mod:`mopidy.backends.libspotify`. diff --git a/setup.py b/setup.py index 76c38e4b..fabc8353 100644 --- a/setup.py +++ b/setup.py @@ -75,6 +75,7 @@ setup( author='Stein Magnus Jodal', author_email='stein.magnus@jodal.no', packages=packages, + package_data={'mopidy': ['backends/libspotify/spotify_appkey.key']}, cmdclass=cmdclasses, data_files=data_files, scripts=['bin/mopidy'],