From 704b5517e12e4b48321944ebabd6331d8c012ff4 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 26 Oct 2010 13:25:52 +0200 Subject: [PATCH 001/111] Split xPlaybackController into xPlaybackController and xPlaybackProvider --- mopidy/backends/base/__init__.py | 3 +- mopidy/backends/base/playback.py | 126 ++++++++++++++----------- mopidy/backends/dummy/__init__.py | 53 ++++++----- mopidy/backends/libspotify/__init__.py | 14 ++- mopidy/backends/libspotify/playback.py | 18 ++-- mopidy/backends/local/__init__.py | 67 +++++++------ tests/backends/base/playback.py | 16 ++-- 7 files changed, 165 insertions(+), 132 deletions(-) diff --git a/mopidy/backends/base/__init__.py b/mopidy/backends/base/__init__.py index 491c5b73..f42a042c 100644 --- a/mopidy/backends/base/__init__.py +++ b/mopidy/backends/base/__init__.py @@ -6,7 +6,8 @@ import time from mopidy import settings from mopidy.backends.base.current_playlist import BaseCurrentPlaylistController from mopidy.backends.base.library import BaseLibraryController -from mopidy.backends.base.playback import BasePlaybackController +from mopidy.backends.base.playback import (BasePlaybackController, + BasePlaybackProvider) from mopidy.backends.base.stored_playlists import BaseStoredPlaylistsController from mopidy.frontends.mpd import translator from mopidy.models import Playlist diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index b7ceeee2..0d4ef52f 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -6,8 +6,10 @@ logger = logging.getLogger('mopidy.backends.base') class BasePlaybackController(object): """ - :param backend: backend the controller is a part of + :param backend: the backend :type backend: :class:`BaseBackend` + :param provider: provider the controller should use + :type provider: instance of :class:`BasePlaybackProvider` """ # pylint: disable = R0902 @@ -54,8 +56,9 @@ class BasePlaybackController(object): #: Playback continues after current song. single = False - def __init__(self, backend): + def __init__(self, backend, provider): self.backend = backend + self.provider = provider self._state = self.STOPPED self._shuffled = [] self._first_shuffle = True @@ -353,18 +356,9 @@ class BasePlaybackController(object): def pause(self): """Pause playback.""" - if self.state == self.PLAYING and self._pause(): + if self.state == self.PLAYING and self.provider.pause(): self.state = self.PAUSED - def _pause(self): - """ - To be overridden by subclass. Implement your backend's pause - functionality here. - - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError - def play(self, cp_track=None, on_error_step=1): """ Play the given track, or if the given track is :class:`None`, play the @@ -391,7 +385,7 @@ class BasePlaybackController(object): self.state = self.STOPPED self.current_cp_track = cp_track self.state = self.PLAYING - if not self._play(cp_track[1]): + if not self.provider.play(cp_track[1]): # Track is not playable if self.random and self._shuffled: self._shuffled.remove(cp_track) @@ -405,18 +399,6 @@ class BasePlaybackController(object): self._trigger_started_playing_event() - def _play(self, track): - """ - To be overridden by subclass. Implement your backend's play - functionality here. - - :param track: the track to play - :type track: :class:`mopidy.models.Track` - :rtype: :class:`True` if successful, else :class:`False` - """ - - raise NotImplementedError - def previous(self): """Play the previous track.""" if self.cp_track_at_previous is None: @@ -428,18 +410,9 @@ class BasePlaybackController(object): def resume(self): """If paused, resume playing the current track.""" - if self.state == self.PAUSED and self._resume(): + if self.state == self.PAUSED and self.provider.resume(): self.state = self.PLAYING - def _resume(self): - """ - To be overridden by subclass. Implement your backend's resume - functionality here. - - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError - def seek(self, time_position): """ Seeks to time position given in milliseconds. @@ -465,18 +438,7 @@ class BasePlaybackController(object): self._play_time_started = self._current_wall_time self._play_time_accumulated = time_position - return self._seek(time_position) - - def _seek(self, time_position): - """ - To be overridden by subclass. Implement your backend's seek - functionality here. - - :param time_position: time position in milliseconds - :type time_position: int - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError + return self.provider.seek(time_position) def stop(self, clear_current_track=False): """ @@ -489,20 +451,11 @@ class BasePlaybackController(object): if self.state == self.STOPPED: return self._trigger_stopped_playing_event() - if self._stop(): + if self.provider.stop(): self.state = self.STOPPED if clear_current_track: self.current_cp_track = None - def _stop(self): - """ - To be overridden by subclass. Implement your backend's stop - functionality here. - - :rtype: :class:`True` if successful, else :class:`False` - """ - raise NotImplementedError - def _trigger_started_playing_event(self): """ Notifies frontends that a track has started playing. @@ -532,3 +485,62 @@ class BasePlaybackController(object): 'track': self.current_track, 'stop_position': self.time_position, }) + + +class BasePlaybackProvider(object): + """ + :param backend: the backend + :type backend: :class:`BaseBackend` + """ + + def __init__(self, backend): + self.backend = backend + + def pause(self): + """ + To be overridden by subclass. Implement your backend's pause + functionality here. + + :rtype: :class:`True` if successful, else :class:`False` + """ + raise NotImplementedError + + def play(self, track): + """ + To be overridden by subclass. Implement your backend's play + functionality here. + + :param track: the track to play + :type track: :class:`mopidy.models.Track` + :rtype: :class:`True` if successful, else :class:`False` + """ + raise NotImplementedError + + def resume(self): + """ + To be overridden by subclass. Implement your backend's resume + functionality here. + + :rtype: :class:`True` if successful, else :class:`False` + """ + raise NotImplementedError + + def seek(self, time_position): + """ + To be overridden by subclass. Implement your backend's seek + functionality here. + + :param time_position: time position in milliseconds + :type time_position: int + :rtype: :class:`True` if successful, else :class:`False` + """ + raise NotImplementedError + + def stop(self): + """ + To be overridden by subclass. Implement your backend's stop + functionality here. + + :rtype: :class:`True` if successful, else :class:`False` + """ + raise NotImplementedError diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py index 62cbd7e2..7a2788b7 100644 --- a/mopidy/backends/dummy/__init__.py +++ b/mopidy/backends/dummy/__init__.py @@ -1,5 +1,5 @@ from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, - BasePlaybackController, BaseLibraryController, + BasePlaybackController, BasePlaybackProvider, BaseLibraryController, BaseStoredPlaylistsController) from mopidy.models import Playlist @@ -13,10 +13,17 @@ class DummyBackend(BaseBackend): def __init__(self, *args, **kwargs): super(DummyBackend, self).__init__(*args, **kwargs) + self.current_playlist = DummyCurrentPlaylistController(backend=self) + self.library = DummyLibraryController(backend=self) - self.playback = DummyPlaybackController(backend=self) + + playback_provider = DummyPlaybackProvider(backend=self) + self.playback = DummyPlaybackController(backend=self, + provider=playback_provider) + self.stored_playlists = DummyStoredPlaylistsController(backend=self) + self.uri_handlers = [u'dummy:'] @@ -43,30 +50,6 @@ class DummyLibraryController(BaseLibraryController): class DummyPlaybackController(BasePlaybackController): - def _next(self, track): - """Pass None as track to force failure""" - return track is not None - - def _pause(self): - return True - - def _play(self, track): - """Pass None as track to force failure""" - return track is not None - - def _previous(self, track): - """Pass None as track to force failure""" - return track is not None - - def _resume(self): - return True - - def _seek(self, time_position): - return True - - def _stop(self): - return True - def _trigger_started_playing_event(self): pass # noop @@ -74,6 +57,24 @@ class DummyPlaybackController(BasePlaybackController): pass # noop +class DummyPlaybackProvider(BasePlaybackProvider): + def pause(self): + return True + + def play(self, track): + """Pass None as track to force failure""" + return track is not None + + def resume(self): + return True + + def seek(self, time_position): + return True + + def stop(self): + return True + + class DummyStoredPlaylistsController(BaseStoredPlaylistsController): _playlists = [] diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index 223d9968..c067cb67 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -1,7 +1,8 @@ import logging from mopidy import settings -from mopidy.backends.base import BaseBackend, BaseCurrentPlaylistController +from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, + BasePlaybackController) logger = logging.getLogger('mopidy.backends.libspotify') @@ -34,17 +35,24 @@ class LibspotifyBackend(BaseBackend): def __init__(self, *args, **kwargs): from .library import LibspotifyLibraryController - from .playback import LibspotifyPlaybackController + from .playback import LibspotifyPlaybackProvider 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) + + playback_provider = LibspotifyPlaybackProvider(backend=self) + self.playback = BasePlaybackController(backend=self, + provider=playback_provider) + self.stored_playlists = LibspotifyStoredPlaylistsController( backend=self) + self.uri_handlers = [u'spotify:', u'http://open.spotify.com/'] + self.spotify = self._connect() def _connect(self): diff --git a/mopidy/backends/libspotify/playback.py b/mopidy/backends/libspotify/playback.py index 39c56bf6..29409ff4 100644 --- a/mopidy/backends/libspotify/playback.py +++ b/mopidy/backends/libspotify/playback.py @@ -2,17 +2,17 @@ import logging from spotify import Link, SpotifyError -from mopidy.backends.base import BasePlaybackController +from mopidy.backends.base import BasePlaybackProvider logger = logging.getLogger('mopidy.backends.libspotify.playback') -class LibspotifyPlaybackController(BasePlaybackController): - def _pause(self): +class LibspotifyPlaybackProvider(BasePlaybackProvider): + def pause(self): return self.backend.output.set_state('PAUSED') - def _play(self, track): + def play(self, track): self.backend.output.set_state('READY') - if self.state == self.PLAYING: + if self.backend.playback.state == self.backend.playback.PLAYING: self.backend.spotify.session.play(0) if track.uri is None: return False @@ -26,16 +26,16 @@ class LibspotifyPlaybackController(BasePlaybackController): logger.warning('Play %s failed: %s', track.uri, e) return False - def _resume(self): - return self._seek(self.time_position) + def resume(self): + return self.seek(self.backend.playback.time_position) - def _seek(self, time_position): + def seek(self, time_position): self.backend.output.set_state('READY') self.backend.spotify.session.seek(time_position) self.backend.output.set_state('PLAYING') return True - def _stop(self): + def stop(self): result = self.backend.output.set_state('READY') self.backend.spotify.session.play(0) return result diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 04761e17..4ad8947b 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -5,9 +5,10 @@ import os import shutil from mopidy import settings -from mopidy.backends.base import (BaseBackend, BaseLibraryController, - BaseStoredPlaylistsController, BaseCurrentPlaylistController, - BasePlaybackController) +from mopidy.backends.base import (BaseBackend, + BaseCurrentPlaylistController, BaseLibraryController, + BasePlaybackController, BasePlaybackProvider, + BaseStoredPlaylistsController) from mopidy.models import Playlist, Track, Album from mopidy.utils.process import pickle_connection @@ -31,41 +32,51 @@ class LocalBackend(BaseBackend): def __init__(self, *args, **kwargs): super(LocalBackend, self).__init__(*args, **kwargs) - self.library = LocalLibraryController(self) - self.stored_playlists = LocalStoredPlaylistsController(self) - self.current_playlist = BaseCurrentPlaylistController(self) - self.playback = LocalPlaybackController(self) + self.library = LocalLibraryController(backend=self) + + self.stored_playlists = LocalStoredPlaylistsController(backend=self) + + self.current_playlist = BaseCurrentPlaylistController(backend=self) + + playback_provider = LocalPlaybackProvider(backend=self) + self.playback = LocalPlaybackController(backend=self, + provider=playback_provider) + self.uri_handlers = [u'file://'] class LocalPlaybackController(BasePlaybackController): - def __init__(self, backend): - super(LocalPlaybackController, self).__init__(backend) + def __init__(self, *args, **kwargs): + super(LocalPlaybackController, self).__init__(*args, **kwargs) + + # XXX Why do we call stop()? Is it to set GStreamer state to 'READY'? self.stop() - def _play(self, track): - return self.backend.output.play_uri(track.uri) - - def _stop(self): - return self.backend.output.set_state('READY') - - def _pause(self): - return self.backend.output.set_state('PAUSED') - - def _resume(self): - return self.backend.output.set_state('PLAYING') - - def _seek(self, time_position): - return self.backend.output.set_position(time_position) - @property def time_position(self): return self.backend.output.get_position() +class LocalPlaybackProvider(BasePlaybackProvider): + def pause(self): + return self.backend.output.set_state('PAUSED') + + def play(self, track): + return self.backend.output.play_uri(track.uri) + + def resume(self): + return self.backend.output.set_state('PLAYING') + + def seek(self, time_position): + return self.backend.output.set_position(time_position) + + def stop(self): + return self.backend.output.set_state('READY') + + class LocalStoredPlaylistsController(BaseStoredPlaylistsController): - def __init__(self, *args): - super(LocalStoredPlaylistsController, self).__init__(*args) + def __init__(self, *args, **kwargs): + super(LocalStoredPlaylistsController, self).__init__(*args, **kwargs) self._folder = os.path.expanduser(settings.LOCAL_PLAYLIST_FOLDER) self.refresh() @@ -137,8 +148,8 @@ class LocalStoredPlaylistsController(BaseStoredPlaylistsController): class LocalLibraryController(BaseLibraryController): - def __init__(self, backend): - super(LocalLibraryController, self).__init__(backend) + def __init__(self, *args, **kwargs): + super(LocalLibraryController, self).__init__(*args, **kwargs) self._uri_mapping = {} self.refresh() diff --git a/tests/backends/base/playback.py b/tests/backends/base/playback.py index 4caaf44b..7b6efe7a 100644 --- a/tests/backends/base/playback.py +++ b/tests/backends/base/playback.py @@ -104,8 +104,8 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_play_skips_to_next_track_on_failure(self): - # If _play() returns False, it is a failure. - self.playback._play = lambda track: track != self.tracks[0] + # If provider.play() returns False, it is a failure. + self.playback.provider.play = lambda track: track != self.tracks[0] self.playback.play() self.assertNotEqual(self.playback.current_track, self.tracks[0]) self.assertEqual(self.playback.current_track, self.tracks[1]) @@ -164,8 +164,8 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_previous_skips_to_previous_track_on_failure(self): - # If _play() returns False, it is a failure. - self.playback._play = lambda track: track != self.tracks[1] + # If provider.play() returns False, it is a failure. + self.playback.provider.play = lambda track: track != self.tracks[1] self.playback.play(self.current_playlist.cp_tracks[2]) self.assertEqual(self.playback.current_track, self.tracks[2]) self.playback.previous() @@ -228,8 +228,8 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_next_skips_to_next_track_on_failure(self): - # If _play() returns False, it is a failure. - self.playback._play = lambda track: track != self.tracks[1] + # If provider.play() returns False, it is a failure. + self.playback.provider.play = lambda track: track != self.tracks[1] self.playback.play() self.assertEqual(self.playback.current_track, self.tracks[0]) self.playback.next() @@ -364,8 +364,8 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_end_of_track_skips_to_next_track_on_failure(self): - # If _play() returns False, it is a failure. - self.playback._play = lambda track: track != self.tracks[1] + # If provider.play() returns False, it is a failure. + self.playback.provider.play = lambda track: track != self.tracks[1] self.playback.play() self.assertEqual(self.playback.current_track, self.tracks[0]) self.playback.on_end_of_track() From 726b62887cb494b52802d31797acc6a23f3ca9cc Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 26 Oct 2010 13:59:27 +0200 Subject: [PATCH 002/111] Add BasePlaybackProvider to __all__ --- mopidy/backends/base/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/backends/base/__init__.py b/mopidy/backends/base/__init__.py index f42a042c..eb75467c 100644 --- a/mopidy/backends/base/__init__.py +++ b/mopidy/backends/base/__init__.py @@ -15,7 +15,7 @@ from mopidy.utils import get_class logger = logging.getLogger('mopidy.backends.base') -__all__ = ['BaseBackend', 'BasePlaybackController', +__all__ = ['BaseBackend', 'BasePlaybackController', 'BasePlaybackProvider', 'BaseCurrentPlaylistController', 'BaseStoredPlaylistsController', 'BaseLibraryController'] From 1a2f09211dd3d2a484cf64d7c6364bb4e7f2bcda Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 31 Oct 2010 02:50:38 +0200 Subject: [PATCH 003/111] Move pip requirement files into own dir --- MANIFEST.in | 3 ++- requirements-docs.txt => requirements/docs.txt | 0 .../external_mixers.rst | 0 requirements-lastfm.txt => requirements/lastfm.txt | 0 requirements-tests.txt => requirements/tests.txt | 0 5 files changed, 2 insertions(+), 1 deletion(-) rename requirements-docs.txt => requirements/docs.txt (100%) rename requirements-external-mixers.txt => requirements/external_mixers.rst (100%) rename requirements-lastfm.txt => requirements/lastfm.txt (100%) rename requirements-tests.txt => requirements/tests.txt (100%) diff --git a/MANIFEST.in b/MANIFEST.in index 33d7dc71..f629bcc7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ -include LICENSE pylintrc *.rst *.txt data/mopidy.desktop +include LICENSE pylintrc *.rst data/mopidy.desktop include mopidy/backends/libspotify/spotify_appkey.key recursive-include docs * prune docs/_build +recursive-include requirements * recursive-include tests *.py recursive-include tests/data * diff --git a/requirements-docs.txt b/requirements/docs.txt similarity index 100% rename from requirements-docs.txt rename to requirements/docs.txt diff --git a/requirements-external-mixers.txt b/requirements/external_mixers.rst similarity index 100% rename from requirements-external-mixers.txt rename to requirements/external_mixers.rst diff --git a/requirements-lastfm.txt b/requirements/lastfm.txt similarity index 100% rename from requirements-lastfm.txt rename to requirements/lastfm.txt diff --git a/requirements-tests.txt b/requirements/tests.txt similarity index 100% rename from requirements-tests.txt rename to requirements/tests.txt From 5dc35eaad41330b4a6117138816ca55cd5188897 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 31 Oct 2010 02:50:53 +0200 Subject: [PATCH 004/111] Fix broken symlinks --- tests/data/scanner/advanced/subdir1/subsubdir/song8.mp3 | 2 +- tests/data/scanner/advanced/subdir1/subsubdir/song9.mp3 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/data/scanner/advanced/subdir1/subsubdir/song8.mp3 b/tests/data/scanner/advanced/subdir1/subsubdir/song8.mp3 index 45812ac5..e84bdc24 120000 --- a/tests/data/scanner/advanced/subdir1/subsubdir/song8.mp3 +++ b/tests/data/scanner/advanced/subdir1/subsubdir/song8.mp3 @@ -1 +1 @@ -../../sample.mp3 \ No newline at end of file +../../../sample.mp3 \ No newline at end of file diff --git a/tests/data/scanner/advanced/subdir1/subsubdir/song9.mp3 b/tests/data/scanner/advanced/subdir1/subsubdir/song9.mp3 index 45812ac5..e84bdc24 120000 --- a/tests/data/scanner/advanced/subdir1/subsubdir/song9.mp3 +++ b/tests/data/scanner/advanced/subdir1/subsubdir/song9.mp3 @@ -1 +1 @@ -../../sample.mp3 \ No newline at end of file +../../../sample.mp3 \ No newline at end of file From d7dc91f2103def3e77cab0a243749b14e24e1f6e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 31 Oct 2010 02:51:58 +0200 Subject: [PATCH 005/111] Fix wrong file extension --- requirements/{external_mixers.rst => external_mixers.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename requirements/{external_mixers.rst => external_mixers.txt} (100%) diff --git a/requirements/external_mixers.rst b/requirements/external_mixers.txt similarity index 100% rename from requirements/external_mixers.rst rename to requirements/external_mixers.txt From f50b2b15b1d7c9319bd1d0843a81d3e8e589e587 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 31 Oct 2010 02:57:04 +0200 Subject: [PATCH 006/111] Add README to requirements/ dir --- requirements/README.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 requirements/README.rst diff --git a/requirements/README.rst b/requirements/README.rst new file mode 100644 index 00000000..cc061a7b --- /dev/null +++ b/requirements/README.rst @@ -0,0 +1,11 @@ +********************* +pip requirement files +********************* + +The files found here are `requirement files +`_ that may be used with `pip +`_. + +To install the dependencies found in one of these files, simply run e.g.:: + + pip install -r requirements/tests.txt From 1437ba63ded573d6ddbeeaf468fe76c5ec7e8f7a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 31 Oct 2010 02:16:40 +0100 Subject: [PATCH 007/111] docs: Split API and implementation docs --- docs/api/{backends/index.rst => backends.rst} | 4 +- .../{frontends/index.rst => frontends.rst} | 4 +- docs/api/index.rst | 6 +- docs/api/mixers.rst | 65 +++---------------- docs/api/outputs.rst | 10 +-- docs/index.rst | 1 + docs/{api => modules}/backends/dummy.rst | 0 docs/{api => modules}/backends/libspotify.rst | 0 docs/{api => modules}/backends/local.rst | 0 docs/{api => modules}/frontends/lastfm.rst | 0 docs/{api => modules}/frontends/mpd.rst | 0 docs/modules/index.rst | 8 +++ docs/modules/mixers/alsa.rst | 9 +++ docs/modules/mixers/denon.rst | 9 +++ docs/modules/mixers/dummy.rst | 9 +++ docs/modules/mixers/gstreamer_software.rst | 9 +++ docs/modules/mixers/nad.rst | 9 +++ docs/modules/mixers/osa.rst | 9 +++ docs/modules/outputs/gstreamer.rst | 9 +++ 19 files changed, 90 insertions(+), 71 deletions(-) rename docs/api/{backends/index.rst => backends.rst} (97%) rename docs/api/{frontends/index.rst => frontends.rst} (94%) rename docs/{api => modules}/backends/dummy.rst (100%) rename docs/{api => modules}/backends/libspotify.rst (100%) rename docs/{api => modules}/backends/local.rst (100%) rename docs/{api => modules}/frontends/lastfm.rst (100%) rename docs/{api => modules}/frontends/mpd.rst (100%) create mode 100644 docs/modules/index.rst create mode 100644 docs/modules/mixers/alsa.rst create mode 100644 docs/modules/mixers/denon.rst create mode 100644 docs/modules/mixers/dummy.rst create mode 100644 docs/modules/mixers/gstreamer_software.rst create mode 100644 docs/modules/mixers/nad.rst create mode 100644 docs/modules/mixers/osa.rst create mode 100644 docs/modules/outputs/gstreamer.rst diff --git a/docs/api/backends/index.rst b/docs/api/backends.rst similarity index 97% rename from docs/api/backends/index.rst rename to docs/api/backends.rst index 100f6f0d..c8a72b4d 100644 --- a/docs/api/backends/index.rst +++ b/docs/api/backends.rst @@ -82,8 +82,8 @@ Manages the music library, e.g. searching for tracks to be added to a playlist. :undoc-members: -Backends -======== +Backend implementations +======================= * :mod:`mopidy.backends.dummy` * :mod:`mopidy.backends.libspotify` diff --git a/docs/api/frontends/index.rst b/docs/api/frontends.rst similarity index 94% rename from docs/api/frontends/index.rst rename to docs/api/frontends.rst index b01bac3d..2f20c72a 100644 --- a/docs/api/frontends/index.rst +++ b/docs/api/frontends.rst @@ -27,8 +27,8 @@ Frontend API :members: -Frontends -========= +Frontend implementations +======================== * :mod:`mopidy.frontends.lastfm` * :mod:`mopidy.frontends.mpd` diff --git a/docs/api/index.rst b/docs/api/index.rst index 86f4e06e..87ec9bb3 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -1,6 +1,6 @@ -***************** -API documentation -***************** +************* +API reference +************* .. toctree:: :glob: diff --git a/docs/api/mixers.rst b/docs/api/mixers.rst index edaea306..1d9937ac 100644 --- a/docs/api/mixers.rst +++ b/docs/api/mixers.rst @@ -37,61 +37,12 @@ methods as described below. :undoc-members: -:mod:`mopidy.mixers.alsa` -- ALSA mixer for Linux -================================================= +Mixer implementations +===================== -.. inheritance-diagram:: mopidy.mixers.alsa - -.. automodule:: mopidy.mixers.alsa - :synopsis: ALSA mixer for Linux - :members: - - -:mod:`mopidy.mixers.denon` -- Hardware mixer for Denon amplifiers -================================================================= - -.. inheritance-diagram:: mopidy.mixers.denon - -.. automodule:: mopidy.mixers.denon - :synopsis: Hardware mixer for Denon amplifiers - :members: - - -:mod:`mopidy.mixers.dummy` -- Dummy mixer for testing -===================================================== - -.. inheritance-diagram:: mopidy.mixers.dummy - -.. automodule:: mopidy.mixers.dummy - :synopsis: Dummy mixer for testing - :members: - - -:mod:`mopidy.mixers.gstreamer_software` -- Software mixer for all platforms -=========================================================================== - -.. inheritance-diagram:: mopidy.mixers.gstreamer_software - -.. automodule:: mopidy.mixers.gstreamer_software - :synopsis: Software mixer for all platforms - :members: - - -:mod:`mopidy.mixers.osa` -- Osa mixer for OS X -============================================== - -.. inheritance-diagram:: mopidy.mixers.osa - -.. automodule:: mopidy.mixers.osa - :synopsis: Osa mixer for OS X - :members: - - -:mod:`mopidy.mixers.nad` -- Hardware mixer for NAD amplifiers -============================================================= - -.. inheritance-diagram:: mopidy.mixers.nad - -.. automodule:: mopidy.mixers.nad - :synopsis: Hardware mixer for NAD amplifiers - :members: +* :mod:`mopidy.mixers.alsa` +* :mod:`mopidy.mixers.denon` +* :mod:`mopidy.mixers.dummy` +* :mod:`mopidy.mixers.gstreamer_software` +* :mod:`mopidy.mixers.osa` +* :mod:`mopidy.mixers.nad` diff --git a/docs/api/outputs.rst b/docs/api/outputs.rst index 8f4e33c0..d8c2932e 100644 --- a/docs/api/outputs.rst +++ b/docs/api/outputs.rst @@ -12,11 +12,7 @@ A stable output API is not available yet, as we've only implemented a single output module. -:mod:`mopidy.outputs.gstreamer` -- GStreamer output for all platforms -===================================================================== +Output implementations +====================== -.. inheritance-diagram:: mopidy.outputs.gstreamer - -.. automodule:: mopidy.outputs.gstreamer - :synopsis: GStreamer output for all platforms - :members: +* :mod:`mopidy.outputs.gstreamer` diff --git a/docs/index.rst b/docs/index.rst index f53373dc..09029a4f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,6 +21,7 @@ Reference documentation :maxdepth: 3 api/index + modules/index Development documentation ========================= diff --git a/docs/api/backends/dummy.rst b/docs/modules/backends/dummy.rst similarity index 100% rename from docs/api/backends/dummy.rst rename to docs/modules/backends/dummy.rst diff --git a/docs/api/backends/libspotify.rst b/docs/modules/backends/libspotify.rst similarity index 100% rename from docs/api/backends/libspotify.rst rename to docs/modules/backends/libspotify.rst diff --git a/docs/api/backends/local.rst b/docs/modules/backends/local.rst similarity index 100% rename from docs/api/backends/local.rst rename to docs/modules/backends/local.rst diff --git a/docs/api/frontends/lastfm.rst b/docs/modules/frontends/lastfm.rst similarity index 100% rename from docs/api/frontends/lastfm.rst rename to docs/modules/frontends/lastfm.rst diff --git a/docs/api/frontends/mpd.rst b/docs/modules/frontends/mpd.rst similarity index 100% rename from docs/api/frontends/mpd.rst rename to docs/modules/frontends/mpd.rst diff --git a/docs/modules/index.rst b/docs/modules/index.rst new file mode 100644 index 00000000..44da0028 --- /dev/null +++ b/docs/modules/index.rst @@ -0,0 +1,8 @@ +**************** +Module reference +**************** + +.. toctree:: + :glob: + + ** diff --git a/docs/modules/mixers/alsa.rst b/docs/modules/mixers/alsa.rst new file mode 100644 index 00000000..05f429eb --- /dev/null +++ b/docs/modules/mixers/alsa.rst @@ -0,0 +1,9 @@ +************************************************* +:mod:`mopidy.mixers.alsa` -- ALSA mixer for Linux +************************************************* + +.. inheritance-diagram:: mopidy.mixers.alsa + +.. automodule:: mopidy.mixers.alsa + :synopsis: ALSA mixer for Linux + :members: diff --git a/docs/modules/mixers/denon.rst b/docs/modules/mixers/denon.rst new file mode 100644 index 00000000..ac944ccc --- /dev/null +++ b/docs/modules/mixers/denon.rst @@ -0,0 +1,9 @@ +***************************************************************** +:mod:`mopidy.mixers.denon` -- Hardware mixer for Denon amplifiers +***************************************************************** + +.. inheritance-diagram:: mopidy.mixers.denon + +.. automodule:: mopidy.mixers.denon + :synopsis: Hardware mixer for Denon amplifiers + :members: diff --git a/docs/modules/mixers/dummy.rst b/docs/modules/mixers/dummy.rst new file mode 100644 index 00000000..6665f949 --- /dev/null +++ b/docs/modules/mixers/dummy.rst @@ -0,0 +1,9 @@ +***************************************************** +:mod:`mopidy.mixers.dummy` -- Dummy mixer for testing +***************************************************** + +.. inheritance-diagram:: mopidy.mixers.dummy + +.. automodule:: mopidy.mixers.dummy + :synopsis: Dummy mixer for testing + :members: diff --git a/docs/modules/mixers/gstreamer_software.rst b/docs/modules/mixers/gstreamer_software.rst new file mode 100644 index 00000000..ef8cc310 --- /dev/null +++ b/docs/modules/mixers/gstreamer_software.rst @@ -0,0 +1,9 @@ +*************************************************************************** +:mod:`mopidy.mixers.gstreamer_software` -- Software mixer for all platforms +*************************************************************************** + +.. inheritance-diagram:: mopidy.mixers.gstreamer_software + +.. automodule:: mopidy.mixers.gstreamer_software + :synopsis: Software mixer for all platforms + :members: diff --git a/docs/modules/mixers/nad.rst b/docs/modules/mixers/nad.rst new file mode 100644 index 00000000..d441b3fd --- /dev/null +++ b/docs/modules/mixers/nad.rst @@ -0,0 +1,9 @@ +************************************************************* +:mod:`mopidy.mixers.nad` -- Hardware mixer for NAD amplifiers +************************************************************* + +.. inheritance-diagram:: mopidy.mixers.nad + +.. automodule:: mopidy.mixers.nad + :synopsis: Hardware mixer for NAD amplifiers + :members: diff --git a/docs/modules/mixers/osa.rst b/docs/modules/mixers/osa.rst new file mode 100644 index 00000000..14bf9a49 --- /dev/null +++ b/docs/modules/mixers/osa.rst @@ -0,0 +1,9 @@ +********************************************** +:mod:`mopidy.mixers.osa` -- Osa mixer for OS X +********************************************** + +.. inheritance-diagram:: mopidy.mixers.osa + +.. automodule:: mopidy.mixers.osa + :synopsis: Osa mixer for OS X + :members: diff --git a/docs/modules/outputs/gstreamer.rst b/docs/modules/outputs/gstreamer.rst new file mode 100644 index 00000000..69c77dad --- /dev/null +++ b/docs/modules/outputs/gstreamer.rst @@ -0,0 +1,9 @@ +********************************************************************* +:mod:`mopidy.outputs.gstreamer` -- GStreamer output for all platforms +********************************************************************* + +.. inheritance-diagram:: mopidy.outputs.gstreamer + +.. automodule:: mopidy.outputs.gstreamer + :synopsis: GStreamer output for all platforms + :members: From 69fbe82b56820b179862762ee3eb4b0caf46766a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 31 Oct 2010 02:28:56 +0100 Subject: [PATCH 008/111] Update Sphinx Makefile with new targets --- docs/Makefile | 130 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 52 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 4ad8691e..6a3272f4 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -4,101 +4,127 @@ # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build -PAPER = +PAPER = +BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: - -rm -rf _build/* + -rm -rf $(BUILDDIR)/* html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo - @echo "Build finished. The HTML pages are in _build/html." + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo - @echo "Build finished. The HTML pages are in _build/dirhtml." + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in _build/htmlhelp." + ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in _build/qthelp, like this:" - @echo "# qcollectiongenerator _build/qthelp/Mopidy.qhcp" + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Mopidy.qhcp" @echo "To view the help file:" - @echo "# assistant -collectionFile _build/qthelp/Mopidy.qhc" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Mopidy.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Mopidy" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Mopidy" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo - @echo "Build finished; the LaTeX files are in _build/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo - @echo "The overview file is in _build/changes." + @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ - "or in _build/linkcheck/output.txt." + "or in $(BUILDDIR)/linkcheck/output.txt." doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ - "results in _build/doctest/output.txt." - -public: clean dirhtml - rm -rf /tmp/mopidy-html && cp -r _build/dirhtml /tmp/mopidy-html - git stash save - cd .. && \ - git checkout gh-pages && \ - git pull && \ - rm -r * && \ - cp -r /tmp/mopidy-html/* . && \ - mv _sources sources && \ - (find . -type f | xargs sed -i -e 's/_sources/sources/g') && \ - mv _static static && \ - (find . -type f | xargs sed -i -e 's/_static/static/g') && \ - if [ -d _images ]; then mv _images images; fi && \ - (find . -type f | xargs sed -i -e 's/_images/images/g') && \ - git add * + "results in $(BUILDDIR)/doctest/output.txt." From a34532171e48cc3c4537f55ddaea61da7757ff43 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 31 Oct 2010 03:17:29 +0100 Subject: [PATCH 009/111] docs: Update roadmap --- docs/development/roadmap.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/development/roadmap.rst b/docs/development/roadmap.rst index 645cbd30..c88ecdf0 100644 --- a/docs/development/roadmap.rst +++ b/docs/development/roadmap.rst @@ -14,11 +14,13 @@ release. Possible targets for the next version ===================================== -- Reintroduce support for OS X. See :issue:`14` for details. -- Support for using multiple Mopidy backends simultaneously. Should make it - possible to have both Spotify tracks and local tracks in the same playlist. +- Reintroduce support for OS X. See :issue:`25` for details. +- **[WIP: feature/multi-backend]** Support for using multiple Mopidy backends + simultaneously. Should make it possible to have both Spotify tracks and local + tracks in the same playlist. - MPD frontend: + - **[WIP: feature/mpd-password]** Password authentication. - ``idle`` support. - Spotify backend: @@ -30,10 +32,10 @@ Possible targets for the next version - Local backend: - Better library support. - - A script for creating a tag cache. + - **[DONE: v0.3]** A script for creating a tag cache. - An alternative to tag cache for caching metadata, i.e. Sqlite. -- **[DONE]** Last.fm scrobbling. +- **[DONE: v0.2]** Last.fm scrobbling. Stuff we want to do, but not right now, and maybe never @@ -52,7 +54,8 @@ Stuff we want to do, but not right now, and maybe never - Compatability: - - Run frontend tests against a real MPD server to ensure we are in sync. + - **[WIP: feature/blackbox-testing]** Run frontend tests against a real MPD + server to ensure we are in sync. - Backends: @@ -64,7 +67,7 @@ Stuff we want to do, but not right now, and maybe never - Publish the server's presence to the network using `Zeroconf `_/Avahi. - - D-Bus/`MPRIS `_ + - **[WIP: feature/mpris-frontend]** D-Bus/`MPRIS `_ - REST/JSON web service with a jQuery client as example application. Maybe based upon `Tornado `_ and `jQuery Mobile `_. From 5442fc246356323792e6effa53e505e1568e579b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 31 Oct 2010 14:22:53 +0100 Subject: [PATCH 010/111] Add Debian packaging support --- debian/TODO | 14 +++++++++++++ debian/changelog | 5 +++++ debian/compat | 1 + debian/control | 22 +++++++++++++++++++++ debian/copyright | 38 ++++++++++++++++++++++++++++++++++++ debian/docs | 2 ++ debian/menu | 2 ++ debian/pyversions | 1 + debian/rules | 27 +++++++++++++++++++++++++ debian/source/format | 1 + debian/watch | 2 ++ docs/development/roadmap.rst | 8 ++++---- 12 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 debian/TODO create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/docs create mode 100644 debian/menu create mode 100644 debian/pyversions create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 debian/watch diff --git a/debian/TODO b/debian/TODO new file mode 100644 index 00000000..51bab1ce --- /dev/null +++ b/debian/TODO @@ -0,0 +1,14 @@ +To do for Mopidy's Debian packaging +=================================== + +- Install data/mopidy.desktop into /usr/share/applications/ +- Add manpages for all commands. Build the manpages with Sphinx +- Make init script run Mopidy as a daemon +- Make init script run Mopidy with it's own user +- Add support for reading settings from /etc/mopidy/settings.py +- Log to /var/log +- Cache files in /var/cache +- Package pyspotify and add it to Recommends +- Package pylast and add it to Recommends +- Create GPG key for signing the package +- Host the packages at PPA or apt.mopidy.com diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..a04f2e78 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +mopidy (0.2.0-1) unstable; urgency=low + + * Initial release + + -- Stein Magnus Jodal Sun, 31 Oct 2010 13:07:04 +0100 diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..c2755717 --- /dev/null +++ b/debian/control @@ -0,0 +1,22 @@ +Source: mopidy +Section: sound +Priority: optional +Maintainer: Stein Magnus Jodal +Build-Depends: debhelper (>= 7.0.50~), python-support, python (>= 2.6), + python-sphinx (>= 1.0), python-pygraphviz +Standards-Version: 3.9.1 +Homepage: http://www.mopidy.com/ +Vcs-Git: git://github.com/jodal/mopidy.git +Vcs-Browser: http://github.com/jodal/mopidy + +Package: mopidy +Architecture: all +Depends: ${misc:Depends}, ${python:Depends}, python-gst0.10 +Recommends: gstreamer0.10-plugins-good, gstreamer0.10-plugins-ugly +Suggests: python-alsaaudio (>= 0.2), python-serial +Description: music server with MPD client support + Mopidy is a music server which can play music from Spotify or from your + local hard drive. To search for music in Spotify’s vast archive, manage + playlists, and play music, you can use most MPD clients. MPD clients are + available for most platforms, including Windows, Mac OS X, Linux, and + iPhone and Android phones. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..c29416d6 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,38 @@ +This work was packaged for Debian by: + + Stein Magnus Jodal on Sun, 31 Oct 2010 09:50:28 +0100 + +It was downloaded from: + + http://pypi.python.org/packages/source/M/Mopidy/Mopidy-0.2.0.tar.gz + +Upstream Author(s): + + Stein Magnus Jodal + +Copyright: + + Copyright 2009-2010 Stein Magnus Jodal and contributors + +License: + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. See the License for the specific language governing + permissions and limitations under the License. + +On Debian systems, the complete text of the Apache version 2.0 license +can be found in "/usr/share/common-licenses/Apache-2.0". + +The Debian packaging is: + + Copyright 2010 Stein Magnus Jodal + +and is licensed under the Apache License, Version 2.0, see above. diff --git a/debian/docs b/debian/docs new file mode 100644 index 00000000..a4b46448 --- /dev/null +++ b/debian/docs @@ -0,0 +1,2 @@ +README.rst +docs/_build/html/ diff --git a/debian/menu b/debian/menu new file mode 100644 index 00000000..6376a81e --- /dev/null +++ b/debian/menu @@ -0,0 +1,2 @@ +?package(mopidy):needs="text" section="Applications/Sound"\ + title="Mopidy" command="/usr/bin/mopidy" diff --git a/debian/pyversions b/debian/pyversions new file mode 100644 index 00000000..0c043f18 --- /dev/null +++ b/debian/pyversions @@ -0,0 +1 @@ +2.6- diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..926a81b1 --- /dev/null +++ b/debian/rules @@ -0,0 +1,27 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +%: + dh $@ + +override_dh_clean: + make -C docs/ clean + dh_clean + +override_dh_installchangelogs: + dh_installchangelogs docs/changes.rst + +override_dh_installdocs: + make -C docs/ clean html + dh_installdocs + +.PHONY: override_dh_clean override_dh_installchangelogs \ + override_dh_installdocs override_dh_installinit diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 00000000..163aaf8d --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/debian/watch b/debian/watch new file mode 100644 index 00000000..3d4d3a41 --- /dev/null +++ b/debian/watch @@ -0,0 +1,2 @@ +version=3 +http://pypi.python.org/packages/source/M/Mopidy/Mopidy-(.*)\.tar\.gz diff --git a/docs/development/roadmap.rst b/docs/development/roadmap.rst index c88ecdf0..2b0cf2ba 100644 --- a/docs/development/roadmap.rst +++ b/docs/development/roadmap.rst @@ -47,10 +47,10 @@ Stuff we want to do, but not right now, and maybe never 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 `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. - Compatability: From 15ccb16e5d2ff4dcaa70d41d1721de4b38d4e1fa Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 31 Oct 2010 14:23:52 +0100 Subject: [PATCH 011/111] Fix typo --- debian/TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/TODO b/debian/TODO index 51bab1ce..4551dc55 100644 --- a/debian/TODO +++ b/debian/TODO @@ -4,7 +4,7 @@ To do for Mopidy's Debian packaging - Install data/mopidy.desktop into /usr/share/applications/ - Add manpages for all commands. Build the manpages with Sphinx - Make init script run Mopidy as a daemon -- Make init script run Mopidy with it's own user +- Make init script run Mopidy with its own user - Add support for reading settings from /etc/mopidy/settings.py - Log to /var/log - Cache files in /var/cache From 545ad9486cbcd486ae2227b8e0355ccb6080f206 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 31 Oct 2010 15:56:22 +0100 Subject: [PATCH 012/111] docs: Add local paths to the start and not end of PYTHONPATH --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index d0d8f3af..16a85975 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,8 +16,8 @@ import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.append(os.path.abspath(os.path.dirname(__file__))) -sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/../')) +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/../')) import mopidy From acda478ed3c0781301b3502c9a84b68c7b999d13 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 31 Oct 2010 23:23:40 +0100 Subject: [PATCH 013/111] docs: Start splitting controllers into controllers and providers --- docs/api/backends.rst | 62 +++++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/docs/api/backends.rst b/docs/api/backends.rst index c8a72b4d..92334ab8 100644 --- a/docs/api/backends.rst +++ b/docs/api/backends.rst @@ -6,29 +6,43 @@ :synopsis: Backend API -The backend and its controllers -=============================== +The backend, controller, and provider concepts +============================================== -.. graph:: backend_relations +Backend: + The backend is mostly for convenience. It is a container that holds + references to all the controllers. +Controllers: + Each controller has responsibility for a given part of the backend + functionality. Most, but not all, controllers delegates some work to one or + more providers. The controllers are responsible for choosing the right + provider for any given task based upon i.e. the track's URI. +Providers: + Anything specific to i.e. Spotify integration or local storage is contained + in the providers. To integrate with new music sources, you just add new + providers. - backend -- current_playlist - backend -- library - backend -- playback - backend -- stored_playlists +.. digraph:: backend_relations + Backend -> "Current\nplaylist\ncontroller" + Backend -> "Library\ncontroller" + "Library\ncontroller" -> "Library\nproviders" + Backend -> "Playback\ncontroller" + "Playback\ncontroller" -> "Playback\nproviders" + Backend -> "Stored\nplaylists\ncontroller" + "Stored\nplaylists\ncontroller" -> "Stored\nplaylist\nproviders" + Backend -> Mixer + +.. _backend-api: Backend API =========== .. note:: - Currently this only documents the API that is available for use by - frontends like :mod:`mopidy.frontends.mpd`, and not what is required to - implement your own backend. :class:`mopidy.backends.base.BaseBackend` and - its controllers implements many of these methods in a matter that should be - independent of most concrete backend implementations, so you should - generally just implement or override a few of these methods yourself to - create a new backend with a complete feature set. + The backend API is the interface that is used by frontends like + :mod:`mopidy.frontends.mpd`. If you want to implement your own backend, see + the :ref:`provider-api`. .. autoclass:: mopidy.backends.base.BaseBackend :members: @@ -82,6 +96,26 @@ Manages the music library, e.g. searching for tracks to be added to a playlist. :undoc-members: +.. _provider-api: + +Provider API +============ + +.. note:: + + The provider API is the interface that must be implemented when you create + a backend. If you are working on a frontend and need to access the backend, + see the :ref:`backend-api`. + + +Playback provider +----------------- + +.. autoclass:: mopidy.backends.base.BasePlaybackProvider + :members: + :undoc-members: + + Backend implementations ======================= From 91f9180c39b19f329bf23c9fbac44915b878b933 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 1 Nov 2010 00:33:32 +0100 Subject: [PATCH 014/111] Add some basic fixmes --- mopidy/backends/local/translator.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mopidy/backends/local/translator.py b/mopidy/backends/local/translator.py index 87ea15df..b6d25b5b 100644 --- a/mopidy/backends/local/translator.py +++ b/mopidy/backends/local/translator.py @@ -106,6 +106,7 @@ def _convert_mpd_data(data, tracks, music_dir): track_kwargs['artists'] = [artist] album_kwargs['artists'] = [artist] + # FIXME Newer mpd tag caches support albumartist names if 'album' in data: album_kwargs['name'] = data['album'] album = Album(**album_kwargs) @@ -114,11 +115,15 @@ def _convert_mpd_data(data, tracks, music_dir): if 'title' in data: track_kwargs['name'] = data['title'] + # FIXME what if file is uri - generated tag cache needs to allways make + # LOCAL_MUSIC_PATH relative paths or this code must handle uris if data['file'][0] == '/': path = data['file'][1:] else: path = data['file'] + # FIXME newer mpd tag caches provide musicbrainz ids + track_kwargs['uri'] = path_to_uri(music_dir, path) track_kwargs['length'] = int(data.get('time', 0)) * 1000 From 70fe571b055b9bc44267d06cd9a925197df81955 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 31 Oct 2010 20:58:58 +0100 Subject: [PATCH 015/111] Add special casing of _PATH settings --- mopidy/utils/settings.py | 3 +++ tests/utils/settings_test.py | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index ac75cb70..6286c8b9 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -51,6 +51,9 @@ class SettingsProxy(object): value = self.current[attr] if type(value) != bool and not value: raise SettingsError(u'Setting "%s" is empty.' % attr) + if attr.endswith('_PATH'): + value = os.path.expanduser(value) + value = os.path.abspath(value) return value def __setattr__(self, attr, value): diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py index 0c06ae5c..a8b98647 100644 --- a/tests/utils/settings_test.py +++ b/tests/utils/settings_test.py @@ -1,3 +1,4 @@ +import os import unittest from mopidy import settings as default_settings_module @@ -65,3 +66,15 @@ class SettingsProxyTest(unittest.TestCase): def test_runtime_value_included_in_current(self): self.settings.TEST = 'test' self.assertEqual(self.settings.current['TEST'], 'test') + + def test_value_ending_in_path_is_expanded(self): + self.settings.TEST_PATH = '~/test' + acctual = self.settings.TEST_PATH + expected = os.path.expanduser('~/test') + self.assertEqual(acctual, expected) + + def test_value_ending_in_path_is_absolute(self): + self.settings.TEST_PATH = './test' + acctual = self.settings.TEST_PATH + expected = os.path.abspath('./test') + self.assertEqual(acctual, expected) From e8371129f74a696b004ac3bd7ad6ee893d7e455d Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 31 Oct 2010 23:19:30 +0100 Subject: [PATCH 016/111] Add test to ensure that other values are not expanded --- tests/utils/settings_test.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py index a8b98647..3b1d7439 100644 --- a/tests/utils/settings_test.py +++ b/tests/utils/settings_test.py @@ -73,8 +73,12 @@ class SettingsProxyTest(unittest.TestCase): expected = os.path.expanduser('~/test') self.assertEqual(acctual, expected) - def test_value_ending_in_path_is_absolute(self): - self.settings.TEST_PATH = './test' - acctual = self.settings.TEST_PATH - expected = os.path.abspath('./test') - self.assertEqual(acctual, expected) + def test_value_not_ending_in_path_is_not_expanded(self): + self.settings.TEST = '~/test' + acctual = self.settings.TEST + self.assertEqual(acctual, '~/test') + + def test_value_not_ending_in_path_is_not_absolute(self): + self.settings.TEST = './test' + acctual = self.settings.TEST + self.assertEqual(acctual, './test') From 3661800563ca59463f5b5e36b254ab7aaa207665 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 31 Oct 2010 23:33:04 +0100 Subject: [PATCH 017/111] Add _FILE handling --- mopidy/utils/settings.py | 2 +- tests/utils/settings_test.py | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 6286c8b9..3145d278 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -51,7 +51,7 @@ class SettingsProxy(object): value = self.current[attr] if type(value) != bool and not value: raise SettingsError(u'Setting "%s" is empty.' % attr) - if attr.endswith('_PATH'): + if attr.endswith('_PATH') or attr.endswith('_FILE'): value = os.path.expanduser(value) value = os.path.abspath(value) return value diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py index 3b1d7439..cef0069d 100644 --- a/tests/utils/settings_test.py +++ b/tests/utils/settings_test.py @@ -73,12 +73,30 @@ class SettingsProxyTest(unittest.TestCase): expected = os.path.expanduser('~/test') self.assertEqual(acctual, expected) - def test_value_not_ending_in_path_is_not_expanded(self): + def test_value_ending_in_path_is_absolute(self): + self.settings.TEST_PATH = './test' + acctual = self.settings.TEST_PATH + expected = os.path.abspath('./test') + self.assertEqual(acctual, expected) + + def test_value_ending_in_file_is_expanded(self): + self.settings.TEST_FILE = '~/test' + acctual = self.settings.TEST_FILE + expected = os.path.expanduser('~/test') + self.assertEqual(acctual, expected) + + def test_value_ending_in_file_is_absolute(self): + self.settings.TEST_FILE = './test' + acctual = self.settings.TEST_FILE + expected = os.path.abspath('./test') + self.assertEqual(acctual, expected) + + def test_value_not_ending_in_path_or_file_is_not_expanded(self): self.settings.TEST = '~/test' acctual = self.settings.TEST self.assertEqual(acctual, '~/test') - def test_value_not_ending_in_path_is_not_absolute(self): + def test_value_not_ending_in_path_or_file_is_not_absolute(self): self.settings.TEST = './test' acctual = self.settings.TEST self.assertEqual(acctual, './test') From c513d0f6e7ea053628b240e185a572669b645b27 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 31 Oct 2010 23:44:45 +0100 Subject: [PATCH 018/111] Rename settings that have dirs or file to _PATH and _FILE --- bin/mopidy | 0 bin/mopidy-scan | 4 ++-- docs/settings.rst | 6 +++--- mopidy/backends/libspotify/__init__.py | 2 +- mopidy/backends/libspotify/session_manager.py | 4 ++-- mopidy/backends/local/__init__.py | 12 ++++++------ mopidy/frontends/mpd/translator.py | 4 ++-- mopidy/settings.py | 14 +++++++------- mopidy/utils/settings.py | 4 ++++ tests/backends/base/stored_playlists.py | 10 +++++----- tests/backends/local/library_test.py | 4 ++-- tests/backends/local/stored_playlists_test.py | 12 ++++++------ tests/frontends/mpd/serializer_test.py | 6 +++--- 13 files changed, 43 insertions(+), 39 deletions(-) mode change 100644 => 100755 bin/mopidy diff --git a/bin/mopidy b/bin/mopidy old mode 100644 new mode 100755 diff --git a/bin/mopidy-scan b/bin/mopidy-scan index 8534372c..1865f317 100755 --- a/bin/mopidy-scan +++ b/bin/mopidy-scan @@ -17,9 +17,9 @@ if __name__ == '__main__': def debug(uri, error): print >> sys.stderr, 'Failed %s: %s' % (uri, error) - print >> sys.stderr, 'Scanning %s' % settings.LOCAL_MUSIC_FOLDER + print >> sys.stderr, 'Scanning %s' % settings.LOCAL_MUSIC_PATH - scanner = Scanner(settings.LOCAL_MUSIC_FOLDER, store, debug) + scanner = Scanner(settings.LOCAL_MUSIC_PATH, store, debug) scanner.start() print >> sys.stderr, 'Done' diff --git a/docs/settings.rst b/docs/settings.rst index a7638b4e..41507a4a 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -47,12 +47,12 @@ Generating a tag cache Previously the local storage backend relied purely on ``tag_cache`` files generated by the original MPD server. To remedy this the command :command:`mopidy-scan` has been created. The program will scan your current -:attr:`mopidy.settings.LOCAL_MUSIC_FOLDER` and build a MPD compatible +:attr:`mopidy.settings.LOCAL_MUSIC_PATH` and build a MPD compatible ``tag_cache``. To make a ``tag_cache`` of your local music available for Mopidy: -#. Ensure that :attr:`mopidy.settings.LOCAL_MUSIC_FOLDER` points to where your +#. Ensure that :attr:`mopidy.settings.LOCAL_MUSIC_PATH` points to where your music is located. Check the current setting by running:: mopidy --list-settings @@ -64,7 +64,7 @@ To make a ``tag_cache`` of your local music available for Mopidy: mopidy-scan > tag_cache #. Move the ``tag_cache`` file to the location - :attr:`mopidy.settings.LOCAL_TAG_CACHE` is set to, or change the setting to + :attr:`mopidy.settings.LOCAL_TAG_CACHE_FILE` is set to, or change the setting to point to where your ``tag_cache`` file is. #. Start Mopidy, find the music library in a client, and play some local music! diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index 223d9968..75739d66 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -18,7 +18,7 @@ class LibspotifyBackend(BaseBackend): **Settings:** - - :attr:`mopidy.settings.SPOTIFY_LIB_CACHE` + - :attr:`mopidy.settings.SPOTIFY_CACHE_PATH` - :attr:`mopidy.settings.SPOTIFY_USERNAME` - :attr:`mopidy.settings.SPOTIFY_PASSWORD` diff --git a/mopidy/backends/libspotify/session_manager.py b/mopidy/backends/libspotify/session_manager.py index 45841350..3e11fc6f 100644 --- a/mopidy/backends/libspotify/session_manager.py +++ b/mopidy/backends/libspotify/session_manager.py @@ -15,8 +15,8 @@ logger = logging.getLogger('mopidy.backends.libspotify.session_manager') # LibspotifySessionManager: Too many ancestors (9/7) class LibspotifySessionManager(SpotifySessionManager, BaseThread): - cache_location = os.path.expanduser(settings.SPOTIFY_LIB_CACHE) - settings_location = os.path.expanduser(settings.SPOTIFY_LIB_CACHE) + cache_location = os.path.expanduser(settings.SPOTIFY_CACHE_PATH) + settings_location = os.path.expanduser(settings.SPOTIFY_CACHE_PATH) appkey_file = os.path.join(os.path.dirname(__file__), 'spotify_appkey.key') user_agent = 'Mopidy %s' % get_version() diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index efcc3bbd..01dcf1df 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -23,9 +23,9 @@ class LocalBackend(BaseBackend): **Settings:** - - :attr:`mopidy.settings.LOCAL_MUSIC_FOLDER` - - :attr:`mopidy.settings.LOCAL_PLAYLIST_FOLDER` - - :attr:`mopidy.settings.LOCAL_TAG_CACHE` + - :attr:`mopidy.settings.LOCAL_MUSIC_PATH` + - :attr:`mopidy.settings.LOCAL_PLAYLIST_PATH` + - :attr:`mopidy.settings.LOCAL_TAG_CACHE_FILE` """ def __init__(self, *args, **kwargs): @@ -66,7 +66,7 @@ class LocalPlaybackController(BasePlaybackController): class LocalStoredPlaylistsController(BaseStoredPlaylistsController): def __init__(self, *args): super(LocalStoredPlaylistsController, self).__init__(*args) - self._folder = os.path.expanduser(settings.LOCAL_PLAYLIST_FOLDER) + self._folder = os.path.expanduser(settings.LOCAL_PLAYLIST_PATH) self.refresh() def lookup(self, uri): @@ -143,8 +143,8 @@ class LocalLibraryController(BaseLibraryController): self.refresh() def refresh(self, uri=None): - tag_cache = os.path.expanduser(settings.LOCAL_TAG_CACHE) - music_folder = os.path.expanduser(settings.LOCAL_MUSIC_FOLDER) + tag_cache = os.path.expanduser(settings.LOCAL_TAG_CACHE_FILE) + music_folder = os.path.expanduser(settings.LOCAL_MUSIC_PATH) tracks = parse_mpd_tag_cache(tag_cache, music_folder) diff --git a/mopidy/frontends/mpd/translator.py b/mopidy/frontends/mpd/translator.py index 2b1adf50..c7f5605a 100644 --- a/mopidy/frontends/mpd/translator.py +++ b/mopidy/frontends/mpd/translator.py @@ -129,7 +129,7 @@ def tracks_to_tag_cache_format(tracks): def _add_to_tag_cache(result, folders, files): for path, entry in folders.items(): name = os.path.split(path)[1] - music_folder = os.path.expanduser(settings.LOCAL_MUSIC_FOLDER) + music_folder = os.path.expanduser(settings.LOCAL_MUSIC_PATH) mtime = get_mtime(os.path.join(music_folder, path)) result.append(('directory', path)) result.append(('mtime', mtime)) @@ -150,7 +150,7 @@ def tracks_to_directory_tree(tracks): path = u'' current = directories - local_folder = os.path.expanduser(settings.LOCAL_MUSIC_FOLDER) + local_folder = os.path.expanduser(settings.LOCAL_MUSIC_PATH) track_path = uri_to_path(track.uri) track_path = re.sub('^' + re.escape(local_folder), '', track_path) track_dir = os.path.dirname(track_path) diff --git a/mopidy/settings.py b/mopidy/settings.py index c9d7b9fc..4f60ee99 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -77,8 +77,8 @@ LASTFM_PASSWORD = u'' #: #: Default:: #: -#: LOCAL_MUSIC_FOLDER = u'~/music' -LOCAL_MUSIC_FOLDER = u'~/music' +#: LOCAL_MUSIC_PATH = u'~/music' +LOCAL_MUSIC_PATH = u'~/music' #: Path to playlist folder with m3u files for local music. #: @@ -86,8 +86,8 @@ LOCAL_MUSIC_FOLDER = u'~/music' #: #: Default:: #: -#: LOCAL_PLAYLIST_FOLDER = u'~/.mopidy/playlists' -LOCAL_PLAYLIST_FOLDER = u'~/.mopidy/playlists' +#: LOCAL_PLAYLIST_PATH = u'~/.mopidy/playlists' +LOCAL_PLAYLIST_PATH = u'~/.mopidy/playlists' #: Path to tag cache for local music. #: @@ -95,8 +95,8 @@ LOCAL_PLAYLIST_FOLDER = u'~/.mopidy/playlists' #: #: Default:: #: -#: LOCAL_TAG_CACHE = u'~/.mopidy/tag_cache' -LOCAL_TAG_CACHE = u'~/.mopidy/tag_cache' +#: LOCAL_TAG_CACHE_FILE = u'~/.mopidy/tag_cache' +LOCAL_TAG_CACHE_FILE = u'~/.mopidy/tag_cache' #: Sound mixer to use. See :mod:`mopidy.mixers` for all available mixers. #: @@ -172,7 +172,7 @@ MPD_SERVER_PORT = 6600 #: Path to the libspotify cache. #: #: Used by :mod:`mopidy.backends.libspotify`. -SPOTIFY_LIB_CACHE = u'~/.mopidy/libspotify_cache' +SPOTIFY_CACHE_PATH = u'~/.mopidy/libspotify_cache' #: Your Spotify Premium username. #: diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 3145d278..2ec0f716 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -97,10 +97,14 @@ def validate_settings(defaults, settings): 'DUMP_LOG_FILENAME': 'DEBUG_LOG_FILENAME', 'DUMP_LOG_FORMAT': 'DEBUG_LOG_FORMAT', 'FRONTEND': 'FRONTENDS', + 'LOCAL_MUSIC_FOLDER': 'LOCAL_MUSIC_PATH', + 'LOCAL_PLAYLIST_FOLDER': 'LOCAL_PLAYLIST_PATH', + 'LOCAL_TAG_CACHE': 'LOCAL_TAG_CACHE_FILE', 'SERVER': None, 'SERVER_HOSTNAME': 'MPD_SERVER_HOSTNAME', 'SERVER_PORT': 'MPD_SERVER_PORT', 'SPOTIFY_LIB_APPKEY': None, + 'SPOTIFY_LIB_CACHE': 'SPOTIFY_CACHE_PATH', } for setting, value in settings.iteritems(): diff --git a/tests/backends/base/stored_playlists.py b/tests/backends/base/stored_playlists.py index ef5806ef..5bcd322c 100644 --- a/tests/backends/base/stored_playlists.py +++ b/tests/backends/base/stored_playlists.py @@ -10,9 +10,9 @@ from tests import SkipTest, data_folder class BaseStoredPlaylistsControllerTest(object): def setUp(self): - settings.LOCAL_PLAYLIST_FOLDER = tempfile.mkdtemp() - settings.LOCAL_TAG_CACHE = data_folder('library_tag_cache') - settings.LOCAL_MUSIC_FOLDER = data_folder('') + settings.LOCAL_PLAYLIST_PATH = tempfile.mkdtemp() + settings.LOCAL_TAG_CACHE_FILE = data_folder('library_tag_cache') + settings.LOCAL_MUSIC_PATH = data_folder('') self.backend = self.backend_class(mixer_class=DummyMixer) self.stored = self.backend.stored_playlists @@ -20,8 +20,8 @@ class BaseStoredPlaylistsControllerTest(object): def tearDown(self): self.backend.destroy() - if os.path.exists(settings.LOCAL_PLAYLIST_FOLDER): - shutil.rmtree(settings.LOCAL_PLAYLIST_FOLDER) + if os.path.exists(settings.LOCAL_PLAYLIST_PATH): + shutil.rmtree(settings.LOCAL_PLAYLIST_PATH) settings.runtime.clear() diff --git a/tests/backends/local/library_test.py b/tests/backends/local/library_test.py index c0605ef2..34465d09 100644 --- a/tests/backends/local/library_test.py +++ b/tests/backends/local/library_test.py @@ -17,8 +17,8 @@ class LocalLibraryControllerTest(BaseLibraryControllerTest, unittest.TestCase): backend_class = LocalBackend def setUp(self): - settings.LOCAL_TAG_CACHE = data_folder('library_tag_cache') - settings.LOCAL_MUSIC_FOLDER = data_folder('') + settings.LOCAL_TAG_CACHE_FILE = data_folder('library_tag_cache') + settings.LOCAL_MUSIC_PATH = data_folder('') super(LocalLibraryControllerTest, self).setUp() diff --git a/tests/backends/local/stored_playlists_test.py b/tests/backends/local/stored_playlists_test.py index bb03f997..4db9e1e2 100644 --- a/tests/backends/local/stored_playlists_test.py +++ b/tests/backends/local/stored_playlists_test.py @@ -25,13 +25,13 @@ class LocalStoredPlaylistsControllerTest(BaseStoredPlaylistsControllerTest, backend_class = LocalBackend def test_created_playlist_is_persisted(self): - path = os.path.join(settings.LOCAL_PLAYLIST_FOLDER, 'test.m3u') + path = os.path.join(settings.LOCAL_PLAYLIST_PATH, 'test.m3u') self.assert_(not os.path.exists(path)) self.stored.create('test') self.assert_(os.path.exists(path)) def test_saved_playlist_is_persisted(self): - path = os.path.join(settings.LOCAL_PLAYLIST_FOLDER, 'test2.m3u') + path = os.path.join(settings.LOCAL_PLAYLIST_PATH, 'test2.m3u') self.assert_(not os.path.exists(path)) self.stored.save(Playlist(name='test2')) self.assert_(os.path.exists(path)) @@ -39,13 +39,13 @@ class LocalStoredPlaylistsControllerTest(BaseStoredPlaylistsControllerTest, def test_deleted_playlist_get_removed(self): playlist = self.stored.create('test') self.stored.delete(playlist) - path = os.path.join(settings.LOCAL_PLAYLIST_FOLDER, 'test.m3u') + path = os.path.join(settings.LOCAL_PLAYLIST_PATH, 'test.m3u') self.assert_(not os.path.exists(path)) def test_renamed_playlist_gets_moved(self): playlist = self.stored.create('test') - file1 = os.path.join(settings.LOCAL_PLAYLIST_FOLDER, 'test.m3u') - file2 = os.path.join(settings.LOCAL_PLAYLIST_FOLDER, 'test2.m3u') + file1 = os.path.join(settings.LOCAL_PLAYLIST_PATH, 'test.m3u') + file2 = os.path.join(settings.LOCAL_PLAYLIST_PATH, 'test2.m3u') self.assert_(not os.path.exists(file2)) self.stored.rename(playlist, 'test2') self.assert_(not os.path.exists(file1)) @@ -55,7 +55,7 @@ class LocalStoredPlaylistsControllerTest(BaseStoredPlaylistsControllerTest, track = Track(uri=generate_song(1)) uri = track.uri[len('file://'):] playlist = Playlist(tracks=[track], name='test') - path = os.path.join(settings.LOCAL_PLAYLIST_FOLDER, 'test.m3u') + path = os.path.join(settings.LOCAL_PLAYLIST_PATH, 'test.m3u') self.stored.save(playlist) diff --git a/tests/frontends/mpd/serializer_test.py b/tests/frontends/mpd/serializer_test.py index 8e8a5d21..db0dabf4 100644 --- a/tests/frontends/mpd/serializer_test.py +++ b/tests/frontends/mpd/serializer_test.py @@ -11,7 +11,7 @@ from tests import data_folder, SkipTest class TrackMpdFormatTest(unittest.TestCase): def setUp(self): - settings.LOCAL_MUSIC_FOLDER = '/dir/subdir' + settings.LOCAL_MUSIC_PATH = '/dir/subdir' mtime.set_fake_time(1234567) def tearDown(self): @@ -104,7 +104,7 @@ class PlaylistMpdFormatTest(unittest.TestCase): class TracksToTagCacheFormatTest(unittest.TestCase): def setUp(self): - settings.LOCAL_MUSIC_FOLDER = '/dir/subdir' + settings.LOCAL_MUSIC_PATH = '/dir/subdir' mtime.set_fake_time(1234567) def tearDown(self): @@ -279,7 +279,7 @@ class TracksToTagCacheFormatTest(unittest.TestCase): class TracksToDirectoryTreeTest(unittest.TestCase): def setUp(self): - settings.LOCAL_MUSIC_FOLDER = '/root/' + settings.LOCAL_MUSIC_PATH = '/root/' def tearDown(self): settings.runtime.clear() From fe309d374e0934b481a3b3d87886692f362621fb Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 31 Oct 2010 23:51:10 +0100 Subject: [PATCH 019/111] Get rid of all kinds of abspath and expandpath usage that is no longer needed --- mopidy/backends/libspotify/session_manager.py | 4 ++-- mopidy/backends/local/__init__.py | 6 +++--- mopidy/frontends/mpd/translator.py | 4 ++-- mopidy/scanner.py | 1 - mopidy/utils/path.py | 12 +++--------- tests/utils/path_test.py | 6 ------ 6 files changed, 10 insertions(+), 23 deletions(-) diff --git a/mopidy/backends/libspotify/session_manager.py b/mopidy/backends/libspotify/session_manager.py index 3e11fc6f..5831b713 100644 --- a/mopidy/backends/libspotify/session_manager.py +++ b/mopidy/backends/libspotify/session_manager.py @@ -15,8 +15,8 @@ logger = logging.getLogger('mopidy.backends.libspotify.session_manager') # LibspotifySessionManager: Too many ancestors (9/7) class LibspotifySessionManager(SpotifySessionManager, BaseThread): - cache_location = os.path.expanduser(settings.SPOTIFY_CACHE_PATH) - settings_location = os.path.expanduser(settings.SPOTIFY_CACHE_PATH) + cache_location = settings.SPOTIFY_CACHE_PATH + settings_location = settings.SPOTIFY_CACHE_PATH appkey_file = os.path.join(os.path.dirname(__file__), 'spotify_appkey.key') user_agent = 'Mopidy %s' % get_version() diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 01dcf1df..21eea945 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -66,7 +66,7 @@ class LocalPlaybackController(BasePlaybackController): class LocalStoredPlaylistsController(BaseStoredPlaylistsController): def __init__(self, *args): super(LocalStoredPlaylistsController, self).__init__(*args) - self._folder = os.path.expanduser(settings.LOCAL_PLAYLIST_PATH) + self._folder = settings.LOCAL_PLAYLIST_PATH self.refresh() def lookup(self, uri): @@ -143,8 +143,8 @@ class LocalLibraryController(BaseLibraryController): self.refresh() def refresh(self, uri=None): - tag_cache = os.path.expanduser(settings.LOCAL_TAG_CACHE_FILE) - music_folder = os.path.expanduser(settings.LOCAL_MUSIC_PATH) + tag_cache = settings.LOCAL_TAG_CACHE_FILE + music_folder = settings.LOCAL_MUSIC_PATH tracks = parse_mpd_tag_cache(tag_cache, music_folder) diff --git a/mopidy/frontends/mpd/translator.py b/mopidy/frontends/mpd/translator.py index c7f5605a..fc1f031b 100644 --- a/mopidy/frontends/mpd/translator.py +++ b/mopidy/frontends/mpd/translator.py @@ -129,7 +129,7 @@ def tracks_to_tag_cache_format(tracks): def _add_to_tag_cache(result, folders, files): for path, entry in folders.items(): name = os.path.split(path)[1] - music_folder = os.path.expanduser(settings.LOCAL_MUSIC_PATH) + music_folder = settings.LOCAL_MUSIC_PATH mtime = get_mtime(os.path.join(music_folder, path)) result.append(('directory', path)) result.append(('mtime', mtime)) @@ -150,7 +150,7 @@ def tracks_to_directory_tree(tracks): path = u'' current = directories - local_folder = os.path.expanduser(settings.LOCAL_MUSIC_PATH) + local_folder = settings.LOCAL_MUSIC_PATH track_path = uri_to_path(track.uri) track_path = re.sub('^' + re.escape(local_folder), '', track_path) track_dir = os.path.dirname(track_path) diff --git a/mopidy/scanner.py b/mopidy/scanner.py index 436598bd..4ccccbdb 100644 --- a/mopidy/scanner.py +++ b/mopidy/scanner.py @@ -5,7 +5,6 @@ import pygst pygst.require('0.10') import gst -from os.path import abspath import datetime import sys import threading diff --git a/mopidy/utils/path.py b/mopidy/utils/path.py index b3669e38..5476e1fd 100644 --- a/mopidy/utils/path.py +++ b/mopidy/utils/path.py @@ -7,14 +7,12 @@ import urllib logger = logging.getLogger('mopidy.utils.path') def get_or_create_folder(folder): - folder = os.path.expanduser(folder) if not os.path.isdir(folder): logger.info(u'Creating dir %s', folder) os.mkdir(folder, 0755) return folder def get_or_create_file(filename): - filename = os.path.expanduser(filename) if not os.path.isfile(filename): logger.info(u'Creating file %s', filename) open(filename, 'w') @@ -22,7 +20,6 @@ def get_or_create_file(filename): def path_to_uri(*paths): path = os.path.join(*paths) - #path = os.path.expanduser(path) # FIXME Waiting for test case? path = path.encode('utf-8') if sys.platform == 'win32': return 'file:' + urllib.pathname2url(path) @@ -46,16 +43,13 @@ def split_path(path): return parts def find_files(path): - path = os.path.expanduser(path) if os.path.isfile(path): - filename = os.path.abspath(path) - if not isinstance(filename, unicode): - filename = filename.decode('utf-8') - yield filename + if not isinstance(path, unicode): + path = path.decode('utf-8') + yield path else: for dirpath, dirnames, filenames in os.walk(path): for filename in filenames: - dirpath = os.path.abspath(dirpath) filename = os.path.join(dirpath, filename) if not isinstance(filename, unicode): filename = filename.decode('utf-8') diff --git a/tests/utils/path_test.py b/tests/utils/path_test.py index 758a09ab..4366305c 100644 --- a/tests/utils/path_test.py +++ b/tests/utils/path_test.py @@ -34,9 +34,6 @@ class GetOrCreateFolderTest(unittest.TestCase): self.assert_(os.path.isdir(self.parent)) self.assertEqual(created, self.parent) - def test_that_userfolder_is_expanded(self): - raise SkipTest # Not sure how to safely test this - class PathToFileURITest(unittest.TestCase): def test_simple_path(self): @@ -139,9 +136,6 @@ class FindFilesTest(unittest.TestCase): self.assert_(is_unicode(name), '%s is not unicode object' % repr(name)) - def test_expanduser(self): - raise SkipTest - class MtimeTest(unittest.TestCase): def tearDown(self): From 71ee3a8ef74db145af731a0b2fd46b9c029e7bc9 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 1 Nov 2010 00:07:59 +0100 Subject: [PATCH 020/111] Don't remove expanduser from get_or_create_file and get_or_create_folder --- mopidy/utils/path.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mopidy/utils/path.py b/mopidy/utils/path.py index 5476e1fd..f25d754a 100644 --- a/mopidy/utils/path.py +++ b/mopidy/utils/path.py @@ -7,12 +7,14 @@ import urllib logger = logging.getLogger('mopidy.utils.path') def get_or_create_folder(folder): + folder = os.path.expanduser(folder) if not os.path.isdir(folder): logger.info(u'Creating dir %s', folder) os.mkdir(folder, 0755) return folder def get_or_create_file(filename): + filename = os.path.expanduser(filename) if not os.path.isfile(filename): logger.info(u'Creating file %s', filename) open(filename, 'w') From 913bac3b0d91fc3521a1311101d47311acbeacc4 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 1 Nov 2010 00:56:06 +0100 Subject: [PATCH 021/111] Rename with_ to copy --- mopidy/backends/dummy/__init__.py | 2 +- mopidy/backends/local/__init__.py | 2 +- mopidy/models.py | 2 +- tests/models_test.py | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py index 62cbd7e2..42acdbf2 100644 --- a/mopidy/backends/dummy/__init__.py +++ b/mopidy/backends/dummy/__init__.py @@ -93,7 +93,7 @@ class DummyStoredPlaylistsController(BaseStoredPlaylistsController): def rename(self, playlist, new_name): self._playlists[self._playlists.index(playlist)] = \ - playlist.with_(name=new_name) + playlist.copy(name=new_name) def save(self, playlist): self._playlists.append(playlist) diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 21eea945..c8331a48 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -116,7 +116,7 @@ class LocalStoredPlaylistsController(BaseStoredPlaylistsController): src = os.path.join(self._folder, playlist.name + '.m3u') dst = os.path.join(self._folder, name + '.m3u') - renamed = playlist.with_(name=name) + renamed = playlist.copy(name=name) index = self._playlists.index(playlist) self._playlists[index] = renamed diff --git a/mopidy/models.py b/mopidy/models.py index c5877657..e691ccb7 100644 --- a/mopidy/models.py +++ b/mopidy/models.py @@ -179,7 +179,7 @@ class Playlist(ImmutableObject): def mpd_format(self, *args, **kwargs): return translator.playlist_to_mpd_format(self, *args, **kwargs) - def with_(self, uri=None, name=None, tracks=None, last_modified=None): + def copy(self, uri=None, name=None, tracks=None, last_modified=None): """ Create a new playlist object with the given values. The values that are not given are taken from the object the method is called on. diff --git a/tests/models_test.py b/tests/models_test.py index ab7bc793..1ccf16ea 100644 --- a/tests/models_test.py +++ b/tests/models_test.py @@ -398,7 +398,7 @@ class PlaylistTest(unittest.TestCase): last_modified = dt.datetime.now() playlist = Playlist(uri=u'an uri', name=u'a name', tracks=tracks, last_modified=last_modified) - new_playlist = playlist.with_(uri=u'another uri') + new_playlist = playlist.copy(uri=u'another uri') self.assertEqual(new_playlist.uri, u'another uri') self.assertEqual(new_playlist.name, u'a name') self.assertEqual(new_playlist.tracks, tracks) @@ -409,7 +409,7 @@ class PlaylistTest(unittest.TestCase): last_modified = dt.datetime.now() playlist = Playlist(uri=u'an uri', name=u'a name', tracks=tracks, last_modified=last_modified) - new_playlist = playlist.with_(name=u'another name') + new_playlist = playlist.copy(name=u'another name') self.assertEqual(new_playlist.uri, u'an uri') self.assertEqual(new_playlist.name, u'another name') self.assertEqual(new_playlist.tracks, tracks) @@ -421,7 +421,7 @@ class PlaylistTest(unittest.TestCase): playlist = Playlist(uri=u'an uri', name=u'a name', tracks=tracks, last_modified=last_modified) new_tracks = [Track(), Track()] - new_playlist = playlist.with_(tracks=new_tracks) + new_playlist = playlist.copy(tracks=new_tracks) self.assertEqual(new_playlist.uri, u'an uri') self.assertEqual(new_playlist.name, u'a name') self.assertEqual(new_playlist.tracks, new_tracks) @@ -433,7 +433,7 @@ class PlaylistTest(unittest.TestCase): new_last_modified = last_modified + dt.timedelta(1) playlist = Playlist(uri=u'an uri', name=u'a name', tracks=tracks, last_modified=last_modified) - new_playlist = playlist.with_(last_modified=new_last_modified) + new_playlist = playlist.copy(last_modified=new_last_modified) self.assertEqual(new_playlist.uri, u'an uri') self.assertEqual(new_playlist.name, u'a name') self.assertEqual(new_playlist.tracks, tracks) From a9e53e06172571eb2a2bb6762430e433794d6b39 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Nov 2010 22:00:13 +0100 Subject: [PATCH 022/111] docs: Rename 'backend api' to 'backend controller api', and 'provider api' to 'backend provider api' --- docs/api/backends.rst | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/api/backends.rst b/docs/api/backends.rst index 92334ab8..de1957e8 100644 --- a/docs/api/backends.rst +++ b/docs/api/backends.rst @@ -3,7 +3,7 @@ ********************** .. automodule:: mopidy.backends - :synopsis: Backend API + :synopsis: Backend APIs The backend, controller, and provider concepts @@ -33,16 +33,16 @@ Providers: "Stored\nplaylists\ncontroller" -> "Stored\nplaylist\nproviders" Backend -> Mixer -.. _backend-api: +.. _backend-controller-api: -Backend API -=========== +Backend controller API +====================== .. note:: - The backend API is the interface that is used by frontends like + The backend controller API is the interface that is used by frontends like :mod:`mopidy.frontends.mpd`. If you want to implement your own backend, see - the :ref:`provider-api`. + the :ref:`backend-provider-api`. .. autoclass:: mopidy.backends.base.BaseBackend :members: @@ -96,16 +96,16 @@ Manages the music library, e.g. searching for tracks to be added to a playlist. :undoc-members: -.. _provider-api: +.. _backend-provider-api: -Provider API -============ +Backend provider API +==================== .. note:: - The provider API is the interface that must be implemented when you create - a backend. If you are working on a frontend and need to access the backend, - see the :ref:`backend-api`. + The backend provider API is the interface that must be implemented when you + create a backend. If you are working on a frontend and need to access the + backend, see the :ref:`backend-controller-api`. Playback provider From beeda047858d0287912f2c8cfff76d1e7fbedd7b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Nov 2010 22:52:56 +0100 Subject: [PATCH 023/111] Add destroy() to playback provider API --- mopidy/backends/base/playback.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 0d4ef52f..8b935c65 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -68,10 +68,8 @@ class BasePlaybackController(object): def destroy(self): """ Cleanup after component. - - May be overridden by subclasses. """ - pass + self.provider.destroy() def _get_cpid(self, cp_track): if cp_track is None: @@ -496,6 +494,14 @@ class BasePlaybackProvider(object): def __init__(self, backend): self.backend = backend + def destroy(self): + """ + Cleanup after component. + + May be overridden by subclasses. + """ + pass + def pause(self): """ To be overridden by subclass. Implement your backend's pause From 11bedc46dd5446339217c7c96e428d28c652c7bd Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 1 Nov 2010 23:05:15 +0100 Subject: [PATCH 024/111] Change how mopidy-scan prints tag-cache --- bin/mopidy-scan | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/mopidy-scan b/bin/mopidy-scan index 1865f317..84cfee57 100755 --- a/bin/mopidy-scan +++ b/bin/mopidy-scan @@ -26,6 +26,6 @@ if __name__ == '__main__': for a in tracks_to_tag_cache_format(tracks): if len(a) == 1: - print a[0] + print (u'%s' % a).encode('utf-8') else: - print u': '.join([unicode(b) for b in a]).encode('utf-8') + print (u'%s: %s' % a).encode('utf-8') From d4437d5adcb45df7cc9a32a4ddeafa543a39261f Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 1 Nov 2010 23:06:45 +0100 Subject: [PATCH 025/111] Decode all data from tag_cache as utf-8 --- mopidy/backends/local/translator.py | 2 +- tests/backends/local/translator_test.py | 11 ++++++++++- tests/data/utf8_tag_cache | 13 +++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 tests/data/utf8_tag_cache diff --git a/mopidy/backends/local/translator.py b/mopidy/backends/local/translator.py index b6d25b5b..26c2ad6b 100644 --- a/mopidy/backends/local/translator.py +++ b/mopidy/backends/local/translator.py @@ -84,7 +84,7 @@ def parse_mpd_tag_cache(tag_cache, music_dir=''): _convert_mpd_data(current, tracks, music_dir) current.clear() - current[key.lower()] = value + current[key.lower()] = value.decode('utf-8') _convert_mpd_data(current, tracks, music_dir) diff --git a/tests/backends/local/translator_test.py b/tests/backends/local/translator_test.py index a9fe58d8..2f97e45c 100644 --- a/tests/backends/local/translator_test.py +++ b/tests/backends/local/translator_test.py @@ -116,7 +116,16 @@ class MPDTagCacheToTracksTest(unittest.TestCase): self.assertEqual(set(expected_tracks), tracks) def test_unicode_cache(self): - raise SkipTest + tracks = parse_mpd_tag_cache(data_folder('utf8_tag_cache'), + data_folder('')) + + uri = path_to_uri(data_folder('song1.mp3')) + artists = [Artist(name=u'æøå')] + album = Album(name=u'æøå', artists=artists) + track = Track(uri=uri, name=u'æøå', artists=artists, + album=album, length=4000) + + self.assertEqual(track, list(tracks)[0]) def test_misencoded_cache(self): # FIXME not sure if this can happen diff --git a/tests/data/utf8_tag_cache b/tests/data/utf8_tag_cache new file mode 100644 index 00000000..6642ec77 --- /dev/null +++ b/tests/data/utf8_tag_cache @@ -0,0 +1,13 @@ +info_begin +mpd_version: 0.14.2 +fs_charset: UTF-8 +info_end +songList begin +key: song1.mp3 +file: /song1.mp3 +Time: 4 +Artist: æøå +Title: æøå +Album: æøå +mtime: 1272319626 +songList end From 4ac1d8d217b4b16ead73b74d838b0e8633370c56 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 1 Nov 2010 23:09:21 +0100 Subject: [PATCH 026/111] Remove mtime and key from track_to_mpd_format --- mopidy/frontends/mpd/translator.py | 6 +----- tests/frontends/mpd/serializer_test.py | 15 --------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/mopidy/frontends/mpd/translator.py b/mopidy/frontends/mpd/translator.py index fc1f031b..d470d8e3 100644 --- a/mopidy/frontends/mpd/translator.py +++ b/mopidy/frontends/mpd/translator.py @@ -6,7 +6,7 @@ from mopidy.utils.path import mtime as get_mtime from mopidy.frontends.mpd import protocol from mopidy.utils.path import path_to_uri, uri_to_path, split_path -def track_to_mpd_format(track, position=None, cpid=None, key=False, mtime=False): +def track_to_mpd_format(track, position=None, cpid=None): """ Format track for output to MPD client. @@ -41,10 +41,6 @@ def track_to_mpd_format(track, position=None, cpid=None, key=False, mtime=False) if position is not None and cpid is not None: result.append(('Pos', position)) result.append(('Id', cpid)) - if key and track.uri: - result.insert(0, ('key', os.path.basename(uri_to_path(track.uri)))) - if mtime and track.uri: - result.append(('mtime', get_mtime(uri_to_path(track.uri)))) return result MPD_KEY_ORDER = ''' diff --git a/tests/frontends/mpd/serializer_test.py b/tests/frontends/mpd/serializer_test.py index db0dabf4..1336dabe 100644 --- a/tests/frontends/mpd/serializer_test.py +++ b/tests/frontends/mpd/serializer_test.py @@ -42,21 +42,6 @@ class TrackMpdFormatTest(unittest.TestCase): self.assert_(('Pos', 1) in result) self.assert_(('Id', 2) in result) - def test_track_to_mpd_format_with_key(self): - track = Track(uri='file:///dir/subdir/file.mp3') - result = translator.track_to_mpd_format(track, key=True) - self.assert_(('key', 'file.mp3') in result) - - def test_track_to_mpd_format_with_key_not_uri_encoded(self): - track = Track(uri='file:///dir/subdir/file%20test.mp3') - result = translator.track_to_mpd_format(track, key=True) - self.assert_(('key', 'file test.mp3') in result) - - def test_track_to_mpd_format_with_mtime(self): - uri = translator.path_to_uri(data_folder('blank.mp3')) - result = translator.track_to_mpd_format(Track(uri=uri), mtime=True) - self.assert_(('mtime', 1234567) in result) - def test_track_to_mpd_format_for_nonempty_track(self): track = Track( uri=u'a uri', From 8bfa7d293770b71338a23c1a571c25c8d295e674 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 1 Nov 2010 23:16:49 +0100 Subject: [PATCH 027/111] Update tag cache code so it adds mtime and file that is relative --- mopidy/frontends/mpd/translator.py | 11 ++++++++--- tests/frontends/mpd/serializer_test.py | 10 +++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/mopidy/frontends/mpd/translator.py b/mopidy/frontends/mpd/translator.py index d470d8e3..e5a55960 100644 --- a/mopidy/frontends/mpd/translator.py +++ b/mopidy/frontends/mpd/translator.py @@ -123,9 +123,11 @@ def tracks_to_tag_cache_format(tracks): return result def _add_to_tag_cache(result, folders, files): + music_folder = settings.LOCAL_MUSIC_PATH + regexp = '^' + re.escape(music_folder).rstrip('/') + '/?' + for path, entry in folders.items(): name = os.path.split(path)[1] - music_folder = settings.LOCAL_MUSIC_PATH mtime = get_mtime(os.path.join(music_folder, path)) result.append(('directory', path)) result.append(('mtime', mtime)) @@ -135,8 +137,11 @@ def _add_to_tag_cache(result, folders, files): result.append(('songList begin',)) for track in files: - track_result = track_to_mpd_format(track, key=True, mtime=True) - track_result = order_mpd_track_info(track_result) + track_result = dict(track_to_mpd_format(track)) + path = uri_to_path(track_result['file']) + track_result['mtime'] = get_mtime(path) + track_result['file'] = re.sub(regexp, '', path) + track_result = order_mpd_track_info(track_result.items()) result.extend(track_result) result.append(('songList end',)) diff --git a/tests/frontends/mpd/serializer_test.py b/tests/frontends/mpd/serializer_test.py index 1336dabe..6fe50657 100644 --- a/tests/frontends/mpd/serializer_test.py +++ b/tests/frontends/mpd/serializer_test.py @@ -3,7 +3,7 @@ import os import unittest from mopidy import settings -from mopidy.utils.path import mtime +from mopidy.utils.path import mtime, uri_to_path from mopidy.frontends.mpd import translator, protocol from mopidy.models import Album, Artist, Playlist, Track @@ -97,8 +97,12 @@ class TracksToTagCacheFormatTest(unittest.TestCase): mtime.undo_fake() def translate(self, track): - result = translator.track_to_mpd_format(track, key=True, mtime=True) - return translator.order_mpd_track_info(result) + folder = settings.LOCAL_MUSIC_PATH + result = dict(translator.track_to_mpd_format(track)) + result['file'] = uri_to_path(result['file']) + result['file'] = result['file'][len(folder)+1:] + result['mtime'] = mtime('') + return translator.order_mpd_track_info(result.items()) def consume_headers(self, result): self.assertEqual(('info_begin',), result[0]) From f486bf512d6ddc81790cf359357597e9e9a92aad Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Nov 2010 23:19:32 +0100 Subject: [PATCH 028/111] Split BaseStoredPlaylistsController in controller and provider --- docs/api/backends.rst | 12 +- mopidy/backends/base/__init__.py | 11 +- mopidy/backends/base/stored_playlists.py | 119 +++++++++++++++--- mopidy/backends/dummy/__init__.py | 10 +- mopidy/backends/libspotify/__init__.py | 8 +- .../backends/libspotify/stored_playlists.py | 4 +- mopidy/backends/local/__init__.py | 10 +- 7 files changed, 139 insertions(+), 35 deletions(-) diff --git a/docs/api/backends.rst b/docs/api/backends.rst index de1957e8..617bcb94 100644 --- a/docs/api/backends.rst +++ b/docs/api/backends.rst @@ -116,8 +116,16 @@ Playback provider :undoc-members: -Backend implementations -======================= +Stored playlists provider +------------------------- + +.. autoclass:: mopidy.backends.base.BaseStoredPlaylistsProvider + :members: + :undoc-members: + + +Backend provider implementations +================================ * :mod:`mopidy.backends.dummy` * :mod:`mopidy.backends.libspotify` diff --git a/mopidy/backends/base/__init__.py b/mopidy/backends/base/__init__.py index eb75467c..7ee86015 100644 --- a/mopidy/backends/base/__init__.py +++ b/mopidy/backends/base/__init__.py @@ -4,15 +4,16 @@ import random import time from mopidy import settings -from mopidy.backends.base.current_playlist import BaseCurrentPlaylistController -from mopidy.backends.base.library import BaseLibraryController -from mopidy.backends.base.playback import (BasePlaybackController, - BasePlaybackProvider) -from mopidy.backends.base.stored_playlists import BaseStoredPlaylistsController from mopidy.frontends.mpd import translator from mopidy.models import Playlist from mopidy.utils import get_class +from .current_playlist import BaseCurrentPlaylistController +from .library import BaseLibraryController +from .playback import BasePlaybackController, BasePlaybackProvider +from .stored_playlists import (BaseStoredPlaylistsController, + BaseStoredPlaylistsProvider) + logger = logging.getLogger('mopidy.backends.base') __all__ = ['BaseBackend', 'BasePlaybackController', 'BasePlaybackProvider', diff --git a/mopidy/backends/base/stored_playlists.py b/mopidy/backends/base/stored_playlists.py index 61722c81..cf14db9d 100644 --- a/mopidy/backends/base/stored_playlists.py +++ b/mopidy/backends/base/stored_playlists.py @@ -7,24 +7,30 @@ class BaseStoredPlaylistsController(object): """ :param backend: backend the controller is a part of :type backend: :class:`BaseBackend` + :param provider: provider the controller should use + :type provider: instance of :class:`BaseStoredPlaylistsProvider` """ - def __init__(self, backend): + def __init__(self, backend, provider): self.backend = backend - self._playlists = [] + self.provider = provider def destroy(self): """Cleanup after component.""" - pass + self.provider.destroy() @property def playlists(self): - """List of :class:`mopidy.models.Playlist`.""" - return copy(self._playlists) + """ + Currently stored playlists. + + Read/write. List of :class:`mopidy.models.Playlist`. + """ + return self.provider.playlists @playlists.setter def playlists(self, playlists): - self._playlists = playlists + self.provider.playlists = playlists def create(self, name): """ @@ -34,7 +40,7 @@ class BaseStoredPlaylistsController(object): :type name: string :rtype: :class:`mopidy.models.Playlist` """ - raise NotImplementedError + return self.provider.create(name) def delete(self, playlist): """ @@ -43,7 +49,7 @@ class BaseStoredPlaylistsController(object): :param playlist: the playlist to delete :type playlist: :class:`mopidy.models.Playlist` """ - raise NotImplementedError + return self.provider.delete(playlist) def get(self, **criteria): """ @@ -55,13 +61,14 @@ class BaseStoredPlaylistsController(object): get(name='a') # Returns track with name 'a' get(uri='xyz') # Returns track with URI 'xyz' - get(name='a', uri='xyz') # Returns track with name 'a' and URI 'xyz' + get(name='a', uri='xyz') # Returns track with name 'a' and URI + # 'xyz' :param criteria: one or more criteria to match by :type criteria: dict :rtype: :class:`mopidy.models.Playlist` """ - matches = self._playlists + matches = self.playlists for (key, value) in criteria.iteritems(): matches = filter(lambda p: getattr(p, key) == value, matches) if len(matches) == 1: @@ -82,11 +89,14 @@ class BaseStoredPlaylistsController(object): :type uri: string :rtype: :class:`mopidy.models.Playlist` """ - raise NotImplementedError + return self.provider.lookup(uri) def refresh(self): - """Refresh stored playlists.""" - raise NotImplementedError + """ + Refresh the stored playlists in + :attr:`mopidy.backends.base.BaseStoredPlaylistsController.playlists`. + """ + return self.provider.refresh(uri) def rename(self, playlist, new_name): """ @@ -97,7 +107,7 @@ class BaseStoredPlaylistsController(object): :param new_name: the new name :type new_name: string """ - raise NotImplementedError + return self.provider.rename(playlist, new_name) def save(self, playlist): """ @@ -106,4 +116,85 @@ class BaseStoredPlaylistsController(object): :param playlist: the playlist :type playlist: :class:`mopidy.models.Playlist` """ + return self.provider.save(playlist) + + +class BaseStoredPlaylistsProvider(object): + """ + :param backend: backend the controller is a part of + :type backend: :class:`BaseBackend` + """ + + def __init__(self, backend): + self.backend = backend + self._playlists = [] + + def destroy(self): + """ + Cleanup after component. + + May be overridden by subclasses. + """ + pass + + @property + def playlists(self): + """ + Currently stored playlists. + + Read/write. List of :class:`mopidy.models.Playlist`. + """ + return copy(self._playlists) + + @playlists.setter + def playlists(self, playlists): + self._playlists = playlists + + def create(self, name): + """ + To be overridden by subclass. + + See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.create`. + """ raise NotImplementedError + + def delete(self, playlist): + """ + To be overridden by subclass. + + See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.delete`. + """ + raise NotImplementedError + + def lookup(self, uri): + """ + To be overridden by subclass. + + See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.lookup`. + """ + raise NotImplementedError + + def refresh(self): + """ + To be overridden by subclass. + + See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.refresh`. + """ + raise NotImplementedError + + def rename(self, playlist, new_name): + """ + To be overridden by subclass. + + See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.rename`. + """ + raise NotImplementedError + + def save(self, playlist): + """ + To be overridden by subclass. + + See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.save`. + """ + raise NotImplementedError + diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py index 7a2788b7..3b94452b 100644 --- a/mopidy/backends/dummy/__init__.py +++ b/mopidy/backends/dummy/__init__.py @@ -1,6 +1,6 @@ from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, BasePlaybackController, BasePlaybackProvider, BaseLibraryController, - BaseStoredPlaylistsController) + BaseStoredPlaylistsController, BaseStoredPlaylistsProvider) from mopidy.models import Playlist class DummyBackend(BaseBackend): @@ -22,7 +22,9 @@ class DummyBackend(BaseBackend): self.playback = DummyPlaybackController(backend=self, provider=playback_provider) - self.stored_playlists = DummyStoredPlaylistsController(backend=self) + stored_playlists_provider = DummyStoredPlaylistsProvider(backend=self) + self.stored_playlists = BaseStoredPlaylistsController(backend=self, + provider=stored_playlists_provider) self.uri_handlers = [u'dummy:'] @@ -75,9 +77,7 @@ class DummyPlaybackProvider(BasePlaybackProvider): return True -class DummyStoredPlaylistsController(BaseStoredPlaylistsController): - _playlists = [] - +class DummyStoredPlaylistsProvider(BaseStoredPlaylistsProvider): def create(self, name): playlist = Playlist(name=name) self._playlists.append(playlist) diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index c067cb67..dbbd4804 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -2,7 +2,7 @@ import logging from mopidy import settings from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, - BasePlaybackController) + BasePlaybackController, BaseStoredPlaylistsController) logger = logging.getLogger('mopidy.backends.libspotify') @@ -36,7 +36,7 @@ class LibspotifyBackend(BaseBackend): def __init__(self, *args, **kwargs): from .library import LibspotifyLibraryController from .playback import LibspotifyPlaybackProvider - from .stored_playlists import LibspotifyStoredPlaylistsController + from .stored_playlists import LibspotifyStoredPlaylistsProvider super(LibspotifyBackend, self).__init__(*args, **kwargs) @@ -48,8 +48,10 @@ class LibspotifyBackend(BaseBackend): self.playback = BasePlaybackController(backend=self, provider=playback_provider) - self.stored_playlists = LibspotifyStoredPlaylistsController( + stored_playlists_provider = LibspotifyStoredPlaylistsProvider( backend=self) + self.stored_playlists = BaseStoredPlaylistsController(backend=self, + provider=stored_playlists_provider) self.uri_handlers = [u'spotify:', u'http://open.spotify.com/'] diff --git a/mopidy/backends/libspotify/stored_playlists.py b/mopidy/backends/libspotify/stored_playlists.py index 3339578c..6f2a7aad 100644 --- a/mopidy/backends/libspotify/stored_playlists.py +++ b/mopidy/backends/libspotify/stored_playlists.py @@ -1,6 +1,6 @@ -from mopidy.backends.base import BaseStoredPlaylistsController +from mopidy.backends.base import BaseStoredPlaylistsProvider -class LibspotifyStoredPlaylistsController(BaseStoredPlaylistsController): +class LibspotifyStoredPlaylistsProvider(BaseStoredPlaylistsProvider): def create(self, name): pass # TODO diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 9796414d..b2c36b72 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -8,7 +8,7 @@ from mopidy import settings from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, BaseLibraryController, BasePlaybackController, BasePlaybackProvider, - BaseStoredPlaylistsController) + BaseStoredPlaylistsController, BaseStoredPlaylistsProvider) from mopidy.models import Playlist, Track, Album from mopidy.utils.process import pickle_connection @@ -34,7 +34,9 @@ class LocalBackend(BaseBackend): self.library = LocalLibraryController(backend=self) - self.stored_playlists = LocalStoredPlaylistsController(backend=self) + stored_playlists_provider = LocalStoredPlaylistsProvider(backend=self) + self.stored_playlists = BaseStoredPlaylistsController(backend=self, + provider=stored_playlists_provider) self.current_playlist = BaseCurrentPlaylistController(backend=self) @@ -74,9 +76,9 @@ class LocalPlaybackProvider(BasePlaybackProvider): return self.backend.output.set_state('READY') -class LocalStoredPlaylistsController(BaseStoredPlaylistsController): +class LocalStoredPlaylistsProvider(BaseStoredPlaylistsProvider): def __init__(self, *args, **kwargs): - super(LocalStoredPlaylistsController, self).__init__(*args, **kwargs) + super(LocalStoredPlaylistsProvider, self).__init__(*args, **kwargs) self._folder = os.path.expanduser(settings.LOCAL_PLAYLIST_FOLDER) self.refresh() From dd25fadd8bc45ce2e2670077aac6af0af1b8124e Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 1 Nov 2010 23:23:59 +0100 Subject: [PATCH 029/111] Track result should contain key --- mopidy/frontends/mpd/translator.py | 1 + tests/frontends/mpd/serializer_test.py | 1 + 2 files changed, 2 insertions(+) diff --git a/mopidy/frontends/mpd/translator.py b/mopidy/frontends/mpd/translator.py index e5a55960..e15e1ba5 100644 --- a/mopidy/frontends/mpd/translator.py +++ b/mopidy/frontends/mpd/translator.py @@ -141,6 +141,7 @@ def _add_to_tag_cache(result, folders, files): path = uri_to_path(track_result['file']) track_result['mtime'] = get_mtime(path) track_result['file'] = re.sub(regexp, '', path) + track_result['key'] = os.path.basename(track_result['file']) track_result = order_mpd_track_info(track_result.items()) result.extend(track_result) result.append(('songList end',)) diff --git a/tests/frontends/mpd/serializer_test.py b/tests/frontends/mpd/serializer_test.py index 6fe50657..77a25e15 100644 --- a/tests/frontends/mpd/serializer_test.py +++ b/tests/frontends/mpd/serializer_test.py @@ -101,6 +101,7 @@ class TracksToTagCacheFormatTest(unittest.TestCase): result = dict(translator.track_to_mpd_format(track)) result['file'] = uri_to_path(result['file']) result['file'] = result['file'][len(folder)+1:] + result['key'] = os.path.basename(result['file']) result['mtime'] = mtime('') return translator.order_mpd_track_info(result.items()) From 7193302c10d04507cf8ac2b577ed20de4a203af4 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Nov 2010 23:27:52 +0100 Subject: [PATCH 030/111] Use a DummyQueue in DummyBackend to silence triggered events --- mopidy/backends/dummy/__init__.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py index 3b94452b..6549b9ec 100644 --- a/mopidy/backends/dummy/__init__.py +++ b/mopidy/backends/dummy/__init__.py @@ -3,6 +3,15 @@ from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, BaseStoredPlaylistsController, BaseStoredPlaylistsProvider) from mopidy.models import Playlist + +class DummyQueue(object): + def __init__(self): + self.received_messages = [] + + def put(self, message): + self.received_messages.append(message) + + class DummyBackend(BaseBackend): """ A backend which implements the backend API in the simplest way possible. @@ -14,12 +23,14 @@ class DummyBackend(BaseBackend): def __init__(self, *args, **kwargs): super(DummyBackend, self).__init__(*args, **kwargs) - self.current_playlist = DummyCurrentPlaylistController(backend=self) + self.core_queue = DummyQueue() + + self.current_playlist = BaseCurrentPlaylistController(backend=self) self.library = DummyLibraryController(backend=self) playback_provider = DummyPlaybackProvider(backend=self) - self.playback = DummyPlaybackController(backend=self, + self.playback = BasePlaybackController(backend=self, provider=playback_provider) stored_playlists_provider = DummyStoredPlaylistsProvider(backend=self) @@ -29,10 +40,6 @@ class DummyBackend(BaseBackend): self.uri_handlers = [u'dummy:'] -class DummyCurrentPlaylistController(BaseCurrentPlaylistController): - pass - - class DummyLibraryController(BaseLibraryController): _library = [] @@ -51,14 +58,6 @@ class DummyLibraryController(BaseLibraryController): return Playlist() -class DummyPlaybackController(BasePlaybackController): - def _trigger_started_playing_event(self): - pass # noop - - def _trigger_stopped_playing_event(self): - pass # noop - - class DummyPlaybackProvider(BasePlaybackProvider): def pause(self): return True From cfe4ec96a6b91258067101c0da5cb2fbb264693a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Nov 2010 23:43:07 +0100 Subject: [PATCH 031/111] docs: Split backend docs into three pages --- docs/api/backends.rst | 132 ------------------------------ docs/api/backends/concepts.rst | 28 +++++++ docs/api/backends/controllers.rst | 65 +++++++++++++++ docs/api/backends/providers.rst | 33 ++++++++ docs/api/index.rst | 5 +- 5 files changed, 130 insertions(+), 133 deletions(-) delete mode 100644 docs/api/backends.rst create mode 100644 docs/api/backends/concepts.rst create mode 100644 docs/api/backends/controllers.rst create mode 100644 docs/api/backends/providers.rst diff --git a/docs/api/backends.rst b/docs/api/backends.rst deleted file mode 100644 index 617bcb94..00000000 --- a/docs/api/backends.rst +++ /dev/null @@ -1,132 +0,0 @@ -********************** -:mod:`mopidy.backends` -********************** - -.. automodule:: mopidy.backends - :synopsis: Backend APIs - - -The backend, controller, and provider concepts -============================================== - -Backend: - The backend is mostly for convenience. It is a container that holds - references to all the controllers. -Controllers: - Each controller has responsibility for a given part of the backend - functionality. Most, but not all, controllers delegates some work to one or - more providers. The controllers are responsible for choosing the right - provider for any given task based upon i.e. the track's URI. -Providers: - Anything specific to i.e. Spotify integration or local storage is contained - in the providers. To integrate with new music sources, you just add new - providers. - -.. digraph:: backend_relations - - Backend -> "Current\nplaylist\ncontroller" - Backend -> "Library\ncontroller" - "Library\ncontroller" -> "Library\nproviders" - Backend -> "Playback\ncontroller" - "Playback\ncontroller" -> "Playback\nproviders" - Backend -> "Stored\nplaylists\ncontroller" - "Stored\nplaylists\ncontroller" -> "Stored\nplaylist\nproviders" - Backend -> Mixer - -.. _backend-controller-api: - -Backend controller API -====================== - -.. note:: - - The backend controller API is the interface that is used by frontends like - :mod:`mopidy.frontends.mpd`. If you want to implement your own backend, see - the :ref:`backend-provider-api`. - -.. autoclass:: mopidy.backends.base.BaseBackend - :members: - :undoc-members: - - -Playback controller -------------------- - -Manages playback, with actions like play, pause, stop, next, previous, and -seek. - -.. autoclass:: mopidy.backends.base.BasePlaybackController - :members: - :undoc-members: - - -Mixer controller ----------------- - -Manages volume. See :class:`mopidy.mixers.BaseMixer`. - - -Current playlist controller ---------------------------- - -Manages everything related to the currently loaded playlist. - -.. autoclass:: mopidy.backends.base.BaseCurrentPlaylistController - :members: - :undoc-members: - - -Stored playlists controller ---------------------------- - -Manages stored playlist. - -.. autoclass:: mopidy.backends.base.BaseStoredPlaylistsController - :members: - :undoc-members: - - -Library controller ------------------- - -Manages the music library, e.g. searching for tracks to be added to a playlist. - -.. autoclass:: mopidy.backends.base.BaseLibraryController - :members: - :undoc-members: - - -.. _backend-provider-api: - -Backend provider API -==================== - -.. note:: - - The backend provider API is the interface that must be implemented when you - create a backend. If you are working on a frontend and need to access the - backend, see the :ref:`backend-controller-api`. - - -Playback provider ------------------ - -.. autoclass:: mopidy.backends.base.BasePlaybackProvider - :members: - :undoc-members: - - -Stored playlists provider -------------------------- - -.. autoclass:: mopidy.backends.base.BaseStoredPlaylistsProvider - :members: - :undoc-members: - - -Backend provider implementations -================================ - -* :mod:`mopidy.backends.dummy` -* :mod:`mopidy.backends.libspotify` -* :mod:`mopidy.backends.local` diff --git a/docs/api/backends/concepts.rst b/docs/api/backends/concepts.rst new file mode 100644 index 00000000..fd7b4d13 --- /dev/null +++ b/docs/api/backends/concepts.rst @@ -0,0 +1,28 @@ +********************************************** +The backend, controller, and provider concepts +********************************************** + +Backend: + The backend is mostly for convenience. It is a container that holds + references to all the controllers. +Controllers: + Each controller has responsibility for a given part of the backend + functionality. Most, but not all, controllers delegates some work to one or + more providers. The controllers are responsible for choosing the right + provider for any given task based upon i.e. the track's URI. See + :ref:`backend-controller-api` for more details. +Providers: + Anything specific to i.e. Spotify integration or local storage is contained + in the providers. To integrate with new music sources, you just add new + providers. See :ref:`backend-provider-api` for more details. + +.. digraph:: backend_relations + + Backend -> "Current\nplaylist\ncontroller" + Backend -> "Library\ncontroller" + "Library\ncontroller" -> "Library\nproviders" + Backend -> "Playback\ncontroller" + "Playback\ncontroller" -> "Playback\nproviders" + Backend -> "Stored\nplaylists\ncontroller" + "Stored\nplaylists\ncontroller" -> "Stored\nplaylist\nproviders" + Backend -> Mixer diff --git a/docs/api/backends/controllers.rst b/docs/api/backends/controllers.rst new file mode 100644 index 00000000..a9d54504 --- /dev/null +++ b/docs/api/backends/controllers.rst @@ -0,0 +1,65 @@ +.. _backend-controller-api: + +********************** +Backend controller API +********************** + + +The backend controller API is the interface that is used by frontends like +:mod:`mopidy.frontends.mpd`. If you want to implement your own backend, see the +:ref:`backend-provider-api`. + + +The backend +=========== + +.. autoclass:: mopidy.backends.base.BaseBackend + :members: + :undoc-members: + + +Playback controller +=================== + +Manages playback, with actions like play, pause, stop, next, previous, and +seek. + +.. autoclass:: mopidy.backends.base.BasePlaybackController + :members: + :undoc-members: + + +Mixer controller +================ + +Manages volume. See :class:`mopidy.mixers.BaseMixer`. + + +Current playlist controller +=========================== + +Manages everything related to the currently loaded playlist. + +.. autoclass:: mopidy.backends.base.BaseCurrentPlaylistController + :members: + :undoc-members: + + +Stored playlists controller +=========================== + +Manages stored playlist. + +.. autoclass:: mopidy.backends.base.BaseStoredPlaylistsController + :members: + :undoc-members: + + +Library controller +================== + +Manages the music library, e.g. searching for tracks to be added to a playlist. + +.. autoclass:: mopidy.backends.base.BaseLibraryController + :members: + :undoc-members: diff --git a/docs/api/backends/providers.rst b/docs/api/backends/providers.rst new file mode 100644 index 00000000..0b042241 --- /dev/null +++ b/docs/api/backends/providers.rst @@ -0,0 +1,33 @@ +.. _backend-provider-api: + +******************** +Backend provider API +******************** + +The backend provider API is the interface that must be implemented when you +create a backend. If you are working on a frontend and need to access the +backend, see the :ref:`backend-controller-api`. + + +Playback provider +================= + +.. autoclass:: mopidy.backends.base.BasePlaybackProvider + :members: + :undoc-members: + + +Stored playlists provider +========================= + +.. autoclass:: mopidy.backends.base.BaseStoredPlaylistsProvider + :members: + :undoc-members: + + +Backend provider implementations +================================ + +* :mod:`mopidy.backends.dummy` +* :mod:`mopidy.backends.libspotify` +* :mod:`mopidy.backends.local` diff --git a/docs/api/index.rst b/docs/api/index.rst index 87ec9bb3..1f37e9ff 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -5,4 +5,7 @@ API reference .. toctree:: :glob: - ** + backends/concepts + backends/controllers + backends/providers + * From 4e0c1ce93fa4ef7881eb33cc8e10cc8d689173e0 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 1 Nov 2010 23:52:21 +0100 Subject: [PATCH 032/111] docs: Improve playback provider docs --- mopidy/backends/base/playback.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 8b935c65..1ab8aeda 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -504,8 +504,9 @@ class BasePlaybackProvider(object): def pause(self): """ - To be overridden by subclass. Implement your backend's pause - functionality here. + To be overridden by subclass. + + Pause playback. :rtype: :class:`True` if successful, else :class:`False` """ @@ -513,8 +514,9 @@ class BasePlaybackProvider(object): def play(self, track): """ - To be overridden by subclass. Implement your backend's play - functionality here. + To be overridden by subclass. + + Play given track. :param track: the track to play :type track: :class:`mopidy.models.Track` @@ -524,8 +526,9 @@ class BasePlaybackProvider(object): def resume(self): """ - To be overridden by subclass. Implement your backend's resume - functionality here. + To be overridden by subclass. + + Resume playback at the same time position playback was paused. :rtype: :class:`True` if successful, else :class:`False` """ @@ -533,8 +536,9 @@ class BasePlaybackProvider(object): def seek(self, time_position): """ - To be overridden by subclass. Implement your backend's seek - functionality here. + To be overridden by subclass. + + Seek to a given time position. :param time_position: time position in milliseconds :type time_position: int @@ -544,8 +548,9 @@ class BasePlaybackProvider(object): def stop(self): """ - To be overridden by subclass. Implement your backend's stop - functionality here. + To be overridden by subclass. + + Stop playback. :rtype: :class:`True` if successful, else :class:`False` """ From 8a574a94a583add869281084a67e7dc7835f82cb Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 2 Nov 2010 00:26:16 +0100 Subject: [PATCH 033/111] docs: Move available settings to the main settings page --- docs/api/settings.rst | 27 --------------------------- docs/settings.rst | 31 +++++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 29 deletions(-) delete mode 100644 docs/api/settings.rst diff --git a/docs/api/settings.rst b/docs/api/settings.rst deleted file mode 100644 index cfc270d6..00000000 --- a/docs/api/settings.rst +++ /dev/null @@ -1,27 +0,0 @@ -********************** -:mod:`mopidy.settings` -********************** - - -Changing settings -================= - -For any Mopidy installation you will need to change at least a couple of -settings. To do this, create a new file in the ``~/.mopidy/`` directory -named ``settings.py`` and add settings you need to change from their defaults -there. - -A complete ``~/.mopidy/settings.py`` may look like this:: - - MPD_SERVER_HOSTNAME = u'::' - SPOTIFY_USERNAME = u'alice' - SPOTIFY_PASSWORD = u'mysecret' - - -Available settings -================== - -.. automodule:: mopidy.settings - :synopsis: Available settings and their default values - :members: - :undoc-members: diff --git a/docs/settings.rst b/docs/settings.rst index 41507a4a..532f52cf 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -2,13 +2,31 @@ Settings ******** +Mopidy has lots of settings. Luckily, you only need to change a few, and stay +ignorant of the rest. Below you can find guides for typical configuration +changes you may want to do, and a complete listing of available settings. + + +Changing settings +================= + Mopidy reads settings from the file ``~/.mopidy/settings.py``, where ``~`` means your *home directory*. If your username is ``alice`` and you are running Linux, the settings file should probably be at ``/home/alice/.mopidy/settings.py``. -You can either create this file yourself, or run the ``mopidy`` command, and it -will create an empty settings file for you. +You can either create the settings file yourself, or run the ``mopidy`` +command, and it will create an empty settings file for you. + +When you have created the settings file, open it in a text editor, and add +settings you want to change. If you want to keep the default value for setting, +you should *not* redefine it in your own settings file. + +A complete ``~/.mopidy/settings.py`` may look as simple as this:: + + MPD_SERVER_HOSTNAME = u'::' + SPOTIFY_USERNAME = u'alice' + SPOTIFY_PASSWORD = u'mysecret' Music from Spotify @@ -88,3 +106,12 @@ file:: LASTFM_USERNAME = u'myusername' LASTFM_PASSWORD = u'mysecret' + + +Available settings +================== + +.. automodule:: mopidy.settings + :synopsis: Available settings and their default values + :members: + :undoc-members: From 5886dbb0f0788d97afd2f812672af37a24b85b14 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 2 Nov 2010 00:31:38 +0100 Subject: [PATCH 034/111] docs: Cleanup API reference headers --- docs/api/frontends.rst | 14 +++----------- docs/api/mixers.rst | 10 +++------- docs/api/models.rst | 6 +++--- docs/api/outputs.rst | 16 +++++++++------- 4 files changed, 18 insertions(+), 28 deletions(-) diff --git a/docs/api/frontends.rst b/docs/api/frontends.rst index 2f20c72a..0c1e32a3 100644 --- a/docs/api/frontends.rst +++ b/docs/api/frontends.rst @@ -1,6 +1,6 @@ -*********************** -:mod:`mopidy.frontends` -*********************** +************ +Frontend API +************ A frontend may do whatever it wants to, including creating threads, opening TCP ports and exposing Mopidy for a type of clients. @@ -9,14 +9,6 @@ Frontends got one main limitation: they are restricted to passing messages through the ``core_queue`` for all communication with the rest of Mopidy. Thus, the frontend API is very small and reveals little of what a frontend may do. -.. automodule:: mopidy.frontends - :synopsis: Frontend API - :members: - - -Frontend API -============ - .. warning:: A stable frontend API is not available yet, as we've only implemented a diff --git a/docs/api/mixers.rst b/docs/api/mixers.rst index 1d9937ac..434637f3 100644 --- a/docs/api/mixers.rst +++ b/docs/api/mixers.rst @@ -1,6 +1,6 @@ -******************** -:mod:`mopidy.mixers` -******************** +********* +Mixer API +********* Mixers are responsible for controlling volume. Clients of the mixers will simply instantiate a mixer and read/write to the ``volume`` attribute:: @@ -24,10 +24,6 @@ enable one of the hardware device mixers, you must the set :attr:`mopidy.settings.MIXER` setting to point to one of the classes found below, and possibly add some extra settings required by the mixer you choose. - -Mixer API -========= - All mixers should subclass :class:`mopidy.mixers.BaseMixer` and override methods as described below. diff --git a/docs/api/models.rst b/docs/api/models.rst index 62e6f75a..ef11547e 100644 --- a/docs/api/models.rst +++ b/docs/api/models.rst @@ -1,6 +1,6 @@ -******************** -:mod:`mopidy.models` -******************** +*********** +Data models +*********** These immutable data models are used for all data transfer within the Mopidy backends and between the backends and the MPD frontend. All fields are optional diff --git a/docs/api/outputs.rst b/docs/api/outputs.rst index d8c2932e..5ef1606d 100644 --- a/docs/api/outputs.rst +++ b/docs/api/outputs.rst @@ -1,15 +1,17 @@ -********************* -:mod:`mopidy.outputs` -********************* +********** +Output API +********** Outputs are responsible for playing audio. +.. warning:: -Output API -========== + A stable output API is not available yet, as we've only implemented a + single output module. -A stable output API is not available yet, as we've only implemented a single -output module. +.. automodule:: mopidy.outputs.base + :synopsis: Base class for outputs + :members: Output implementations From d05e48c4391650af8b2a2cd572e4f18ed4489b24 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 00:35:27 +0100 Subject: [PATCH 035/111] Add generic copy method to models (including tests) --- mopidy/models.py | 39 +++++++++++---------------------------- tests/models_test.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/mopidy/models.py b/mopidy/models.py index e691ccb7..7dd75660 100644 --- a/mopidy/models.py +++ b/mopidy/models.py @@ -38,6 +38,17 @@ class ImmutableObject(object): def __ne__(self, other): return not self.__eq__(other) + def copy(self, **kwargs): + data = {} + for key in self.__dict__.keys(): + public_key = key.lstrip('_') + data[public_key] = kwargs.pop(public_key, self.__dict__[key]) + for key in kwargs.keys(): + if hasattr(self, key): + data[key] = kwargs.pop(key) + if kwargs: + raise TypeError("copy() got an unexpected keyword argument '%s'" % key) + return self.__class__(**data) class Artist(ImmutableObject): """ @@ -178,31 +189,3 @@ class Playlist(ImmutableObject): def mpd_format(self, *args, **kwargs): return translator.playlist_to_mpd_format(self, *args, **kwargs) - - def copy(self, uri=None, name=None, tracks=None, last_modified=None): - """ - Create a new playlist object with the given values. The values that are - not given are taken from the object the method is called on. - - Does not change the object on which it is called. - - :param uri: playlist URI - :type uri: string - :param name: playlist name - :type name: string - :param tracks: playlist's tracks - :type tracks: list of :class:`Track` elements - :param last_modified: playlist's modification time - :type last_modified: :class:`datetime.datetime` - :rtype: :class:`Playlist` - """ - if uri is None: - uri = self.uri - if name is None: - name = self.name - if tracks is None: - tracks = self.tracks - if last_modified is None: - last_modified = self.last_modified - return Playlist(uri=uri, name=name, tracks=tracks, - last_modified=last_modified) diff --git a/tests/models_test.py b/tests/models_test.py index 1ccf16ea..2c1dfec7 100644 --- a/tests/models_test.py +++ b/tests/models_test.py @@ -5,6 +5,50 @@ from mopidy.models import Artist, Album, Track, Playlist from tests import SkipTest +class GenericCopyTets(unittest.TestCase): + def compare(self, orig, other): + self.assertEqual(orig, other) + self.assertNotEqual(id(orig), id(other)) + + def test_copying_track(self): + track = Track() + self.compare(track, track.copy()) + + def test_copying_artist(self): + artist = Artist() + self.compare(artist, artist.copy()) + + def test_copying_album(self): + album = Album() + self.compare(album, album.copy()) + + def test_copying_playlist(self): + playlist = Playlist() + self.compare(playlist, playlist.copy()) + + def test_copying_track_with_basic_values(self): + track = Track(name='foo', uri='bar') + copy = track.copy(name='baz') + self.assertEqual('baz', copy.name) + self.assertEqual('bar', copy.uri) + + def test_copying_track_with_missing_values(self): + track = Track(uri='bar') + copy = track.copy(name='baz') + self.assertEqual('baz', copy.name) + self.assertEqual('bar', copy.uri) + + def test_copying_track_with_private_internal_value(self): + artists1 = [Artist(name='foo')] + artists2 = [Artist(name='bar')] + track = Track(artists=artists1) + copy = track.copy(artists=artists2) + self.assertEqual(copy.artists, artists2) + + def test_copying_track_with_invalid_key(self): + test = lambda: Track().copy(invalid_key=True) + self.assertRaises(TypeError, test) + class ArtistTest(unittest.TestCase): def test_uri(self): uri = u'an_uri' From edb2f060c4b56f9f455ed6c63679e90081fbd221 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 1 Nov 2010 23:42:33 +0100 Subject: [PATCH 036/111] Add musicbrainz to models --- mopidy/models.py | 15 ++++++++ tests/models_test.py | 86 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 89 insertions(+), 12 deletions(-) diff --git a/mopidy/models.py b/mopidy/models.py index 7dd75660..c45d6769 100644 --- a/mopidy/models.py +++ b/mopidy/models.py @@ -56,6 +56,8 @@ class Artist(ImmutableObject): :type uri: string :param name: artist name :type name: string + :param musicbrainz_id: musicbrainz id + :type musicbrainz_id: string """ #: The artist URI. Read-only. @@ -64,6 +66,9 @@ class Artist(ImmutableObject): #: The artist name. Read-only. name = None + #: The musicbrainz id of the artist. Read-only. + musicbrainz_id = None + class Album(ImmutableObject): """ @@ -75,6 +80,8 @@ class Album(ImmutableObject): :type artists: list of :class:`Artist` :param num_tracks: number of tracks in album :type num_tracks: integer + :param musicbrainz_id: musicbrainz id + :type musicbrainz_id: string """ #: The album URI. Read-only. @@ -86,6 +93,9 @@ class Album(ImmutableObject): #: The number of tracks in the album. Read-only. num_tracks = 0 + #: The musicbrainz id of the album. Read-only. + musicbrainz_id = None + def __init__(self, *args, **kwargs): self._artists = frozenset(kwargs.pop('artists', [])) super(Album, self).__init__(*args, **kwargs) @@ -114,6 +124,8 @@ class Track(ImmutableObject): :type length: integer :param bitrate: bitrate in kbit/s :type bitrate: integer + :param musicbrainz_id: musicbrainz id + :type musicbrainz_id: string """ #: The track URI. Read-only. @@ -137,6 +149,9 @@ class Track(ImmutableObject): #: The track's bitrate in kbit/s. Read-only. bitrate = None + #: The musicbrainz id of the track. Read-only. + musicbrainz_id = None + def __init__(self, *args, **kwargs): self._artists = frozenset(kwargs.pop('artists', [])) super(Track, self).__init__(*args, **kwargs) diff --git a/tests/models_test.py b/tests/models_test.py index 2c1dfec7..0b44f337 100644 --- a/tests/models_test.py +++ b/tests/models_test.py @@ -62,6 +62,13 @@ class ArtistTest(unittest.TestCase): self.assertEqual(artist.name, name) self.assertRaises(AttributeError, setattr, artist, 'name', None) + def test_musicbrainz_id(self): + mb_id = u'mb-id' + artist = Artist(musicbrainz_id=mb_id) + self.assertEqual(artist.musicbrainz_id, mb_id) + self.assertRaises(AttributeError, setattr, artist, + 'musicbrainz_id', None) + def test_invalid_kwarg(self): test = lambda: Artist(foo='baz') self.assertRaises(TypeError, test) @@ -78,9 +85,15 @@ class ArtistTest(unittest.TestCase): self.assertEqual(artist1, artist2) self.assertEqual(hash(artist1), hash(artist2)) + def test_eq_musibrainz_id(self): + artist1 = Artist(musicbrainz_id=u'id') + artist2 = Artist(musicbrainz_id=u'id') + self.assertEqual(artist1, artist2) + self.assertEqual(hash(artist1), hash(artist2)) + def test_eq(self): - artist1 = Artist(uri=u'uri', name=u'name') - artist2 = Artist(uri=u'uri', name=u'name') + artist1 = Artist(uri=u'uri', name=u'name', musicbrainz_id='id') + artist2 = Artist(uri=u'uri', name=u'name', musicbrainz_id='id') self.assertEqual(artist1, artist2) self.assertEqual(hash(artist1), hash(artist2)) @@ -102,9 +115,15 @@ class ArtistTest(unittest.TestCase): self.assertNotEqual(artist1, artist2) self.assertNotEqual(hash(artist1), hash(artist2)) + def test_ne_musicbrainz_id(self): + artist1 = Artist(musicbrainz_id=u'id1') + artist2 = Artist(musicbrainz_id=u'id2') + self.assertNotEqual(artist1, artist2) + self.assertNotEqual(hash(artist1), hash(artist2)) + def test_ne(self): - artist1 = Artist(uri=u'uri1', name=u'name1') - artist2 = Artist(uri=u'uri2', name=u'name2') + artist1 = Artist(uri=u'uri1', name=u'name1', musicbrainz_id='id1') + artist2 = Artist(uri=u'uri2', name=u'name2', musicbrainz_id='id2') self.assertNotEqual(artist1, artist2) self.assertNotEqual(hash(artist1), hash(artist2)) @@ -134,6 +153,13 @@ class AlbumTest(unittest.TestCase): self.assertEqual(album.num_tracks, num_tracks) self.assertRaises(AttributeError, setattr, album, 'num_tracks', None) + def test_musicbrainz_id(self): + mb_id = u'mb-id' + album = Album(musicbrainz_id=mb_id) + self.assertEqual(album.musicbrainz_id, mb_id) + self.assertRaises(AttributeError, setattr, album, + 'musicbrainz_id', None) + def test_invalid_kwarg(self): test = lambda: Album(foo='baz') self.assertRaises(TypeError, test) @@ -171,10 +197,16 @@ class AlbumTest(unittest.TestCase): self.assertEqual(album1, album2) self.assertEqual(hash(album1), hash(album2)) + def test_eq_musibrainz_id(self): + album1 = Album(musicbrainz_id=u'id') + album2 = Album(musicbrainz_id=u'id') + self.assertEqual(album1, album2) + self.assertEqual(hash(album1), hash(album2)) + def test_eq(self): artists = [Artist()] - album1 = Album(name=u'name', uri=u'uri', artists=artists, num_tracks=2) - album2 = Album(name=u'name', uri=u'uri', artists=artists, num_tracks=2) + album1 = Album(name=u'name', uri=u'uri', artists=artists, num_tracks=2, musicbrainz_id='id') + album2 = Album(name=u'name', uri=u'uri', artists=artists, num_tracks=2, musicbrainz_id='id') self.assertEqual(album1, album2) self.assertEqual(hash(album1), hash(album2)) @@ -208,11 +240,19 @@ class AlbumTest(unittest.TestCase): self.assertNotEqual(album1, album2) self.assertNotEqual(hash(album1), hash(album2)) + def test_ne_musicbrainz_id(self): + album1 = Album(musicbrainz_id=u'id1') + album2 = Album(musicbrainz_id=u'id2') + self.assertNotEqual(album1, album2) + self.assertNotEqual(hash(album1), hash(album2)) + def test_ne(self): album1 = Album(name=u'name1', uri=u'uri1', - artists=[Artist(name=u'name1')], num_tracks=1) + artists=[Artist(name=u'name1')], num_tracks=1, + musicbrainz_id='id1') album2 = Album(name=u'name2', uri=u'uri2', - artists=[Artist(name=u'name2')], num_tracks=2) + artists=[Artist(name=u'name2')], num_tracks=2, + musicbrainz_id='id2') self.assertNotEqual(album1, album2) self.assertNotEqual(hash(album1), hash(album2)) @@ -266,6 +306,13 @@ class TrackTest(unittest.TestCase): self.assertEqual(track.bitrate, bitrate) self.assertRaises(AttributeError, setattr, track, 'bitrate', None) + def test_musicbrainz_id(self): + mb_id = u'mb-id' + track = Track(musicbrainz_id=mb_id) + self.assertEqual(track.musicbrainz_id, mb_id) + self.assertRaises(AttributeError, setattr, track, + 'musicbrainz_id', None) + def test_invalid_kwarg(self): test = lambda: Track(foo='baz') self.assertRaises(TypeError, test) @@ -329,14 +376,22 @@ class TrackTest(unittest.TestCase): self.assertEqual(track1, track2) self.assertEqual(hash(track1), hash(track2)) + def test_eq_musibrainz_id(self): + track1 = Track(musicbrainz_id=u'id') + track2 = Track(musicbrainz_id=u'id') + self.assertEqual(track1, track2) + self.assertEqual(hash(track1), hash(track2)) + def test_eq(self): date = dt.date.today() artists = [Artist()] album = Album() track1 = Track(uri=u'uri', name=u'name', artists=artists, album=album, - track_no=1, date=date, length=100, bitrate=100) + track_no=1, date=date, length=100, bitrate=100, + musicbrainz_id='id') track2 = Track(uri=u'uri', name=u'name', artists=artists, album=album, - track_no=1, date=date, length=100, bitrate=100) + track_no=1, date=date, length=100, bitrate=100, + musicbrainz_id='id') self.assertEqual(track1, track2) self.assertEqual(hash(track1), hash(track2)) @@ -394,14 +449,21 @@ class TrackTest(unittest.TestCase): self.assertNotEqual(track1, track2) self.assertNotEqual(hash(track1), hash(track2)) + def test_ne_musicbrainz_id(self): + track1 = Track(musicbrainz_id=u'id1') + track2 = Track(musicbrainz_id=u'id2') + self.assertNotEqual(track1, track2) + self.assertNotEqual(hash(track1), hash(track2)) + def test_ne(self): track1 = Track(uri=u'uri1', name=u'name1', artists=[Artist(name=u'name1')], album=Album(name=u'name1'), - track_no=1, date=dt.date.today(), length=100, bitrate=100) + track_no=1, date=dt.date.today(), length=100, bitrate=100, + musicbrainz_id='id1') track2 = Track(uri=u'uri2', name=u'name2', artists=[Artist(name=u'name2')], album=Album(name=u'name2'), track_no=2, date=dt.date.today()-dt.timedelta(days=1), - length=200, bitrate=200) + length=200, bitrate=200, musicbrainz_id='id2') self.assertNotEqual(track1, track2) self.assertNotEqual(hash(track1), hash(track2)) From 3b7904b8265b9ca3ffbd450bdb3e080f7c8d6ee0 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 00:12:21 +0100 Subject: [PATCH 037/111] Add FIXME to data translator for scanner --- mopidy/scanner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mopidy/scanner.py b/mopidy/scanner.py index 4ccccbdb..7ae40423 100644 --- a/mopidy/scanner.py +++ b/mopidy/scanner.py @@ -18,6 +18,8 @@ def translator(data): artist_kwargs = {} track_kwargs = {} + # FIXME replace with data.get('foo', None) ? + if 'album' in data: album_kwargs['name'] = data['album'] From 5974d696f1d91d2323a703795aa7e30d36d8ae77 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 00:12:55 +0100 Subject: [PATCH 038/111] Refactor mpd formater test --- tests/frontends/mpd/serializer_test.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/frontends/mpd/serializer_test.py b/tests/frontends/mpd/serializer_test.py index 77a25e15..4313cfda 100644 --- a/tests/frontends/mpd/serializer_test.py +++ b/tests/frontends/mpd/serializer_test.py @@ -10,6 +10,17 @@ from mopidy.models import Album, Artist, Playlist, Track from tests import data_folder, SkipTest class TrackMpdFormatTest(unittest.TestCase): + track = Track( + uri=u'a uri', + artists=[Artist(name=u'an artist')], + name=u'a name', + album=Album(name=u'an album', num_tracks=13, + artists=[Artist(name=u'an other artist')]), + track_no=7, + date=dt.date(1977, 1, 1), + length=137000, + ) + def setUp(self): settings.LOCAL_MUSIC_PATH = '/dir/subdir' mtime.set_fake_time(1234567) @@ -43,17 +54,7 @@ class TrackMpdFormatTest(unittest.TestCase): self.assert_(('Id', 2) in result) def test_track_to_mpd_format_for_nonempty_track(self): - track = Track( - uri=u'a uri', - artists=[Artist(name=u'an artist')], - name=u'a name', - album=Album(name=u'an album', num_tracks=13, - artists=[Artist(name=u'an other artist')]), - track_no=7, - date=dt.date(1977, 1, 1), - length=137000, - ) - result = translator.track_to_mpd_format(track, position=9, cpid=122) + result = translator.track_to_mpd_format(self.track, position=9, cpid=122) self.assert_(('file', 'a uri') in result) self.assert_(('Time', 137) in result) self.assert_(('Artist', 'an artist') in result) From 988ff66c5e7ef480aa143169daee1398bdf21310 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 00:20:59 +0100 Subject: [PATCH 039/111] Add musicbrainz support to scanner translator --- mopidy/scanner.py | 9 +++++++++ tests/scanner_test.py | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/mopidy/scanner.py b/mopidy/scanner.py index 7ae40423..e1899c4b 100644 --- a/mopidy/scanner.py +++ b/mopidy/scanner.py @@ -46,6 +46,15 @@ def translator(data): if albumartist_kwargs: album_kwargs['artists'] = [Artist(**albumartist_kwargs)] + if 'musicbrainz-trackid' in data: + track_kwargs['musicbrainz_id'] = data['musicbrainz-trackid'] + + if 'musicbrainz-artistid' in data: + artist_kwargs['musicbrainz_id'] = data['musicbrainz-artistid'] + + if 'musicbrainz-albumid' in data: + album_kwargs['musicbrainz_id'] = data['musicbrainz-albumid'] + track_kwargs['uri'] = data['uri'] track_kwargs['length'] = data['duration'] track_kwargs['album'] = Album(**album_kwargs) diff --git a/tests/scanner_test.py b/tests/scanner_test.py index 141f2ceb..a18b64a1 100644 --- a/tests/scanner_test.py +++ b/tests/scanner_test.py @@ -25,15 +25,20 @@ class TranslatorTest(unittest.TestCase): 'date': FakeGstDate(2006, 1, 1,), 'container-format': u'ID3 tag', 'duration': 4531, + 'musicbrainz-trackid': 'mbtrackid', + 'musicbrainz-albumid': 'mbalbumid', + 'musicbrainz-artistid': 'mbartistid', } self.album = { 'name': 'albumname', 'num_tracks': 2, + 'musicbrainz_id': 'mbalbumid', } self.artist = { 'name': 'name', + 'musicbrainz_id': 'mbartistid', } self.albumartist = { @@ -46,6 +51,7 @@ class TranslatorTest(unittest.TestCase): 'date': date(2006, 1, 1), 'track_no': 1, 'length': 4531, + 'musicbrainz_id': 'mbtrackid', } def build_track(self): @@ -78,16 +84,31 @@ class TranslatorTest(unittest.TestCase): del self.track['name'] self.check() + def test_missing_track_musicbrainz_id(self): + del self.data['musicbrainz-trackid'] + del self.track['musicbrainz_id'] + self.check() + def test_missing_album_name(self): del self.data['album'] del self.album['name'] self.check() + def test_missing_album_musicbrainz_id(self): + del self.data['musicbrainz-albumid'] + del self.album['musicbrainz_id'] + self.check() + def test_missing_artist_name(self): del self.data['artist'] del self.artist['name'] self.check() + def test_missing_artist_musicbrainz_id(self): + del self.data['musicbrainz-artistid'] + del self.artist['musicbrainz_id'] + self.check() + def test_missing_album_artist(self): del self.data['album-artist'] del self.albumartist['name'] From e7a7d342b8d811d9ec8519e38b740b5b5da1f0e7 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 00:21:32 +0100 Subject: [PATCH 040/111] Add tests for musicbrainz in mpd formated tracks --- tests/frontends/mpd/serializer_test.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/frontends/mpd/serializer_test.py b/tests/frontends/mpd/serializer_test.py index 4313cfda..164e384f 100644 --- a/tests/frontends/mpd/serializer_test.py +++ b/tests/frontends/mpd/serializer_test.py @@ -67,6 +67,23 @@ class TrackMpdFormatTest(unittest.TestCase): self.assert_(('Id', 122) in result) self.assertEqual(len(result), 10) + def test_track_to_mpd_format_musicbrainz_trackid(self): + track = self.track.copy(musicbrainz_id='foo') + result = translator.track_to_mpd_format(self.track) + self.assert_(('MUSICBRAINZ_TRACKID', 'foo') in result) + + def test_track_to_mpd_format_musicbrainz_albumid(self): + album = self.track.album.copy(musicbrainz_id='foo') + track = self.track.copy(album=album) + result = translator.track_to_mpd_format(self.track) + self.assert_(('MUSICBRAINZ_ALBUMID', 'foo') in result) + + def test_track_to_mpd_format_musicbrainz_artistid(self): + artist = list(self.track.artists)[0].copy(musicbrainz_id='foo') + track = self.track.copy(artists=[artist]) + result = translator.track_to_mpd_format(self.track) + self.assert_(('MUSICBRAINZ_ARTISTID', 'foo') in result) + def test_artists_to_mpd_format(self): artists = [Artist(name=u'ABBA'), Artist(name=u'Beatles')] translated = translator.artists_to_mpd_format(artists) From 31933c2c2fa2c6aac0130070041ee497f50bae49 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 00:43:03 +0100 Subject: [PATCH 041/111] Fix musicbrainz support in mpd format track --- mopidy/frontends/mpd/translator.py | 9 +++++++++ tests/frontends/mpd/serializer_test.py | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/mopidy/frontends/mpd/translator.py b/mopidy/frontends/mpd/translator.py index e15e1ba5..48c8fe30 100644 --- a/mopidy/frontends/mpd/translator.py +++ b/mopidy/frontends/mpd/translator.py @@ -41,6 +41,15 @@ def track_to_mpd_format(track, position=None, cpid=None): if position is not None and cpid is not None: result.append(('Pos', position)) result.append(('Id', cpid)) + if track.album is not None and track.album.musicbrainz_id is not None: + result.append(('MUSICBRAINZ_ALBUMID', track.album.musicbrainz_id)) + if track.artists: + artists = filter(lambda a: a.musicbrainz_id is not None, track.artists) + if artists: + # FIXME don't use first and best artist? + result.append(('MUSICBRAINZ_ARTISTID', artists[0].musicbrainz_id)) + if track.musicbrainz_id is not None: + result.append(('MUSICBRAINZ_TRACKID', track.musicbrainz_id)) return result MPD_KEY_ORDER = ''' diff --git a/tests/frontends/mpd/serializer_test.py b/tests/frontends/mpd/serializer_test.py index 164e384f..bc01d820 100644 --- a/tests/frontends/mpd/serializer_test.py +++ b/tests/frontends/mpd/serializer_test.py @@ -69,19 +69,19 @@ class TrackMpdFormatTest(unittest.TestCase): def test_track_to_mpd_format_musicbrainz_trackid(self): track = self.track.copy(musicbrainz_id='foo') - result = translator.track_to_mpd_format(self.track) + result = translator.track_to_mpd_format(track) self.assert_(('MUSICBRAINZ_TRACKID', 'foo') in result) def test_track_to_mpd_format_musicbrainz_albumid(self): album = self.track.album.copy(musicbrainz_id='foo') track = self.track.copy(album=album) - result = translator.track_to_mpd_format(self.track) + result = translator.track_to_mpd_format(track) self.assert_(('MUSICBRAINZ_ALBUMID', 'foo') in result) def test_track_to_mpd_format_musicbrainz_artistid(self): artist = list(self.track.artists)[0].copy(musicbrainz_id='foo') track = self.track.copy(artists=[artist]) - result = translator.track_to_mpd_format(self.track) + result = translator.track_to_mpd_format(track) self.assert_(('MUSICBRAINZ_ARTISTID', 'foo') in result) def test_artists_to_mpd_format(self): From 90fbf69b85ae10fa4495294456e68c4c53d810dc Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 01:00:00 +0100 Subject: [PATCH 042/111] Add albumartistid to scanner --- mopidy/scanner.py | 9 ++++++--- tests/scanner_test.py | 7 +++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/mopidy/scanner.py b/mopidy/scanner.py index e1899c4b..a7d2e664 100644 --- a/mopidy/scanner.py +++ b/mopidy/scanner.py @@ -43,9 +43,6 @@ def translator(data): if 'album-artist' in data: albumartist_kwargs['name'] = data['album-artist'] - if albumartist_kwargs: - album_kwargs['artists'] = [Artist(**albumartist_kwargs)] - if 'musicbrainz-trackid' in data: track_kwargs['musicbrainz_id'] = data['musicbrainz-trackid'] @@ -55,6 +52,12 @@ def translator(data): if 'musicbrainz-albumid' in data: album_kwargs['musicbrainz_id'] = data['musicbrainz-albumid'] + if 'musicbrainz-albumartistid' in data: + albumartist_kwargs['musicbrainz_id'] = data['musicbrainz-albumartistid'] + + if albumartist_kwargs: + album_kwargs['artists'] = [Artist(**albumartist_kwargs)] + track_kwargs['uri'] = data['uri'] track_kwargs['length'] = data['duration'] track_kwargs['album'] = Album(**album_kwargs) diff --git a/tests/scanner_test.py b/tests/scanner_test.py index a18b64a1..a1b53bcf 100644 --- a/tests/scanner_test.py +++ b/tests/scanner_test.py @@ -28,6 +28,7 @@ class TranslatorTest(unittest.TestCase): 'musicbrainz-trackid': 'mbtrackid', 'musicbrainz-albumid': 'mbalbumid', 'musicbrainz-artistid': 'mbartistid', + 'musicbrainz-albumartistid': 'mbalbumartistid', } self.album = { @@ -43,6 +44,7 @@ class TranslatorTest(unittest.TestCase): self.albumartist = { 'name': 'albumartistname', + 'musicbrainz_id': 'mbalbumartistid', } self.track = { @@ -114,6 +116,11 @@ class TranslatorTest(unittest.TestCase): del self.albumartist['name'] self.check() + def test_missing_album_artist_musicbrainz_id(self): + del self.data['musicbrainz-albumartistid'] + del self.albumartist['musicbrainz_id'] + self.check() + def test_missing_date(self): del self.data['date'] del self.track['date'] From 37222d66f5b8e11bf70296aebe6885574d1d482a Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 01:00:11 +0100 Subject: [PATCH 043/111] Add albumartistid to frontent --- mopidy/frontends/mpd/translator.py | 7 ++++++- tests/frontends/mpd/serializer_test.py | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mopidy/frontends/mpd/translator.py b/mopidy/frontends/mpd/translator.py index 48c8fe30..0073c113 100644 --- a/mopidy/frontends/mpd/translator.py +++ b/mopidy/frontends/mpd/translator.py @@ -43,10 +43,15 @@ def track_to_mpd_format(track, position=None, cpid=None): result.append(('Id', cpid)) if track.album is not None and track.album.musicbrainz_id is not None: result.append(('MUSICBRAINZ_ALBUMID', track.album.musicbrainz_id)) + # FIXME don't use first and best artist? + # FIXME don't duplicate following code? + if track.album is not None and track.album.artists: + artists = filter(lambda a: a.musicbrainz_id is not None, track.album.artists) + if artists: + result.append(('MUSICBRAINZ_ALBUMARTISTID', artists[0].musicbrainz_id)) if track.artists: artists = filter(lambda a: a.musicbrainz_id is not None, track.artists) if artists: - # FIXME don't use first and best artist? result.append(('MUSICBRAINZ_ARTISTID', artists[0].musicbrainz_id)) if track.musicbrainz_id is not None: result.append(('MUSICBRAINZ_TRACKID', track.musicbrainz_id)) diff --git a/tests/frontends/mpd/serializer_test.py b/tests/frontends/mpd/serializer_test.py index bc01d820..7e4500ea 100644 --- a/tests/frontends/mpd/serializer_test.py +++ b/tests/frontends/mpd/serializer_test.py @@ -78,6 +78,13 @@ class TrackMpdFormatTest(unittest.TestCase): result = translator.track_to_mpd_format(track) self.assert_(('MUSICBRAINZ_ALBUMID', 'foo') in result) + def test_track_to_mpd_format_musicbrainz_albumid(self): + artist = list(self.track.artists)[0].copy(musicbrainz_id='foo') + album = self.track.album.copy(artists=[artist]) + track = self.track.copy(album=album) + result = translator.track_to_mpd_format(track) + self.assert_(('MUSICBRAINZ_ALBUMARTISTID', 'foo') in result) + def test_track_to_mpd_format_musicbrainz_artistid(self): artist = list(self.track.artists)[0].copy(musicbrainz_id='foo') track = self.track.copy(artists=[artist]) From 8d30b745d260f00026b0ca787a643273140f7b6f Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 01:02:41 +0100 Subject: [PATCH 044/111] Add test_musicbrainz_tagcache as reminder that tag cache parser does not know about musicbrainz yet --- tests/backends/local/translator_test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/backends/local/translator_test.py b/tests/backends/local/translator_test.py index 2f97e45c..3f2a1c12 100644 --- a/tests/backends/local/translator_test.py +++ b/tests/backends/local/translator_test.py @@ -136,3 +136,6 @@ class MPDTagCacheToTracksTest(unittest.TestCase): data_folder('')) uri = path_to_uri(data_folder('song1.mp3')) self.assertEqual(set([Track(uri=uri, length=4000)]), tracks) + + def test_musicbrainz_tagcache(self): + raise SkipTest From 326ade05cc5fbc3511a95ff9f6dda55fa79ec9a0 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 17:24:42 +0100 Subject: [PATCH 045/111] Add musicbrainz support to tag_cache parsing --- mopidy/backends/local/translator.py | 25 +++++++++++++++++++------ tests/backends/local/translator_test.py | 11 +++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/mopidy/backends/local/translator.py b/mopidy/backends/local/translator.py index 26c2ad6b..0af8f126 100644 --- a/mopidy/backends/local/translator.py +++ b/mopidy/backends/local/translator.py @@ -96,25 +96,31 @@ def _convert_mpd_data(data, tracks, music_dir): track_kwargs = {} album_kwargs = {} + artist_kwargs = {} if 'track' in data: album_kwargs['num_tracks'] = int(data['track'].split('/')[1]) track_kwargs['track_no'] = int(data['track'].split('/')[0]) if 'artist' in data: - artist = Artist(name=data['artist']) - track_kwargs['artists'] = [artist] - album_kwargs['artists'] = [artist] + artist_kwargs['name'] = data['artist'] # FIXME Newer mpd tag caches support albumartist names if 'album' in data: album_kwargs['name'] = data['album'] - album = Album(**album_kwargs) - track_kwargs['album'] = album if 'title' in data: track_kwargs['name'] = data['title'] + if 'musicbrainz_trackid' in data: + track_kwargs['musicbrainz_id'] = data['musicbrainz_trackid'] + + if 'musicbrainz_albumid' in data: + album_kwargs['musicbrainz_id'] = data['musicbrainz_albumid'] + + if 'musicbrainz_artistid' in data: + artist_kwargs['musicbrainz_id'] = data['musicbrainz_artistid'] + # FIXME what if file is uri - generated tag cache needs to allways make # LOCAL_MUSIC_PATH relative paths or this code must handle uris if data['file'][0] == '/': @@ -122,7 +128,14 @@ def _convert_mpd_data(data, tracks, music_dir): else: path = data['file'] - # FIXME newer mpd tag caches provide musicbrainz ids + if artist_kwargs: + artist = Artist(**artist_kwargs) + album_kwargs['artists'] = [artist] + track_kwargs['artists'] = [artist] + + if album_kwargs: + album = Album(**album_kwargs) + track_kwargs['album'] = album track_kwargs['uri'] = path_to_uri(music_dir, path) track_kwargs['length'] = int(data.get('time', 0)) * 1000 diff --git a/tests/backends/local/translator_test.py b/tests/backends/local/translator_test.py index 3f2a1c12..9347db59 100644 --- a/tests/backends/local/translator_test.py +++ b/tests/backends/local/translator_test.py @@ -138,4 +138,15 @@ class MPDTagCacheToTracksTest(unittest.TestCase): self.assertEqual(set([Track(uri=uri, length=4000)]), tracks) def test_musicbrainz_tagcache(self): + tracks = parse_mpd_tag_cache(data_folder('musicbrainz_tag_cache'), + data_folder('')) + artist = list(expected_tracks[0].artists)[0].copy( + musicbrainz_id='7364dea6-ca9a-48e3-be01-b44ad0d19897') + album = expected_tracks[0].album.copy(artists=[artist], + musicbrainz_id='cb5f1603-d314-4c9c-91e5-e295cfb125d2') + track = expected_tracks[0].copy(artists=[artist], album=album, + musicbrainz_id='90488461-8c1f-4a4e-826b-4c6dc70801f0') + self.assertEqual(track, list(tracks)[0]) + + def test_albumartist_tag_cache(self): raise SkipTest From f58d47fda2ea7ac7d7355fb379de5a8b045e8afa Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 17:41:21 +0100 Subject: [PATCH 046/111] Add fixmes --- mopidy/backends/local/translator.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mopidy/backends/local/translator.py b/mopidy/backends/local/translator.py index 0af8f126..8855c1d6 100644 --- a/mopidy/backends/local/translator.py +++ b/mopidy/backends/local/translator.py @@ -105,7 +105,6 @@ def _convert_mpd_data(data, tracks, music_dir): if 'artist' in data: artist_kwargs['name'] = data['artist'] - # FIXME Newer mpd tag caches support albumartist names if 'album' in data: album_kwargs['name'] = data['album'] @@ -121,8 +120,6 @@ def _convert_mpd_data(data, tracks, music_dir): if 'musicbrainz_artistid' in data: artist_kwargs['musicbrainz_id'] = data['musicbrainz_artistid'] - # FIXME what if file is uri - generated tag cache needs to allways make - # LOCAL_MUSIC_PATH relative paths or this code must handle uris if data['file'][0] == '/': path = data['file'][1:] else: From 89b46af983ad99499005a202dc2abefa5c276401 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 17:43:32 +0100 Subject: [PATCH 047/111] Add musicbrainz_tag_cache that was missing --- tests/data/musicbrainz_tag_cache | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/data/musicbrainz_tag_cache diff --git a/tests/data/musicbrainz_tag_cache b/tests/data/musicbrainz_tag_cache new file mode 100644 index 00000000..010242f4 --- /dev/null +++ b/tests/data/musicbrainz_tag_cache @@ -0,0 +1,19 @@ +info_begin +mpd_version: 0.16.0 +fs_charset: UTF-8 +info_end +songList begin +key: song1.mp3 +file: /song1.mp3 +Time: 4 +Artist: name +Title: trackname +Album: albumname +Track: 1/2 +Date: 2006 +MUSICBRAINZ_ALBUMID: cb5f1603-d314-4c9c-91e5-e295cfb125d2 +MUSICBRAINZ_ALBUMARTISTID: 7364dea6-ca9a-48e3-be01-b44ad0d19897 +MUSICBRAINZ_ARTISTID: 7364dea6-ca9a-48e3-be01-b44ad0d19897 +MUSICBRAINZ_TRACKID: 90488461-8c1f-4a4e-826b-4c6dc70801f0 +mtime: 1272319626 +songList end From 6cba4bc5649bdd570c4558c9ea4ba3c9ad9556f7 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 20:19:29 +0100 Subject: [PATCH 048/111] Add albumartist support --- mopidy/backends/local/translator.py | 13 ++++++++++++- tests/backends/local/translator_test.py | 9 ++++++++- tests/data/albumartist_tag_cache | 16 ++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 tests/data/albumartist_tag_cache diff --git a/mopidy/backends/local/translator.py b/mopidy/backends/local/translator.py index 8855c1d6..51522ead 100644 --- a/mopidy/backends/local/translator.py +++ b/mopidy/backends/local/translator.py @@ -97,6 +97,7 @@ def _convert_mpd_data(data, tracks, music_dir): track_kwargs = {} album_kwargs = {} artist_kwargs = {} + albumartist_kwargs = {} if 'track' in data: album_kwargs['num_tracks'] = int(data['track'].split('/')[1]) @@ -104,6 +105,10 @@ def _convert_mpd_data(data, tracks, music_dir): if 'artist' in data: artist_kwargs['name'] = data['artist'] + albumartist_kwargs['name'] = data['artist'] + + if 'albumartist' in data: + albumartist_kwargs['name'] = data['albumartist'] if 'album' in data: album_kwargs['name'] = data['album'] @@ -120,6 +125,9 @@ def _convert_mpd_data(data, tracks, music_dir): if 'musicbrainz_artistid' in data: artist_kwargs['musicbrainz_id'] = data['musicbrainz_artistid'] + if 'musicbrainz_albumartistid' in data: + albumartist_kwargs['musicbrainz_id'] = data['musicbrainz_albumartistid'] + if data['file'][0] == '/': path = data['file'][1:] else: @@ -127,8 +135,11 @@ def _convert_mpd_data(data, tracks, music_dir): if artist_kwargs: artist = Artist(**artist_kwargs) - album_kwargs['artists'] = [artist] track_kwargs['artists'] = [artist] + + if albumartist_kwargs: + albumartist = Artist(**albumartist_kwargs) + album_kwargs['artists'] = [albumartist] if album_kwargs: album = Album(**album_kwargs) diff --git a/tests/backends/local/translator_test.py b/tests/backends/local/translator_test.py index 9347db59..1a3a36b0 100644 --- a/tests/backends/local/translator_test.py +++ b/tests/backends/local/translator_test.py @@ -149,4 +149,11 @@ class MPDTagCacheToTracksTest(unittest.TestCase): self.assertEqual(track, list(tracks)[0]) def test_albumartist_tag_cache(self): - raise SkipTest + tracks = parse_mpd_tag_cache(data_folder('albumartist_tag_cache'), + data_folder('')) + uri = path_to_uri(data_folder('song1.mp3')) + artist = Artist(name='albumartistname') + album = expected_albums[0].copy(artists=[artist]) + track = Track(name='trackname', artists=expected_artists, track_no=1, + album=album, length=4000, uri=uri) + self.assertEqual(track, list(tracks)[0]) diff --git a/tests/data/albumartist_tag_cache b/tests/data/albumartist_tag_cache new file mode 100644 index 00000000..29942a75 --- /dev/null +++ b/tests/data/albumartist_tag_cache @@ -0,0 +1,16 @@ +info_begin +mpd_version: 0.14.2 +fs_charset: UTF-8 +info_end +songList begin +key: song1.mp3 +file: /song1.mp3 +Time: 4 +Artist: name +Title: trackname +Album: albumname +AlbumArtist: albumartistname +Track: 1/2 +Date: 2006 +mtime: 1272319626 +songList end From 9cc053cfd26c752eb65a0a1050992b7bf5ec97e3 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 2 Nov 2010 20:20:01 +0100 Subject: [PATCH 049/111] Add albumartist to musicbrainz test --- tests/backends/local/translator_test.py | 6 +++++- tests/data/musicbrainz_tag_cache | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/backends/local/translator_test.py b/tests/backends/local/translator_test.py index 1a3a36b0..b7fd212c 100644 --- a/tests/backends/local/translator_test.py +++ b/tests/backends/local/translator_test.py @@ -142,10 +142,14 @@ class MPDTagCacheToTracksTest(unittest.TestCase): data_folder('')) artist = list(expected_tracks[0].artists)[0].copy( musicbrainz_id='7364dea6-ca9a-48e3-be01-b44ad0d19897') - album = expected_tracks[0].album.copy(artists=[artist], + albumartist = list(expected_tracks[0].artists)[0].copy( + name='albumartistname', + musicbrainz_id='7364dea6-ca9a-48e3-be01-b44ad0d19897') + album = expected_tracks[0].album.copy(artists=[albumartist], musicbrainz_id='cb5f1603-d314-4c9c-91e5-e295cfb125d2') track = expected_tracks[0].copy(artists=[artist], album=album, musicbrainz_id='90488461-8c1f-4a4e-826b-4c6dc70801f0') + self.assertEqual(track, list(tracks)[0]) def test_albumartist_tag_cache(self): diff --git a/tests/data/musicbrainz_tag_cache b/tests/data/musicbrainz_tag_cache index 010242f4..0e9dca46 100644 --- a/tests/data/musicbrainz_tag_cache +++ b/tests/data/musicbrainz_tag_cache @@ -9,6 +9,7 @@ Time: 4 Artist: name Title: trackname Album: albumname +AlbumArtist: albumartistname Track: 1/2 Date: 2006 MUSICBRAINZ_ALBUMID: cb5f1603-d314-4c9c-91e5-e295cfb125d2 From 13ece433634a9697a5921f787fe56a148e7fabef Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 2 Nov 2010 23:07:15 +0100 Subject: [PATCH 050/111] Split BaseLibraryController in controller and provider --- docs/api/backends/providers.rst | 8 +++ mopidy/backends/base/__init__.py | 2 +- mopidy/backends/base/library.py | 61 ++++++++++++++++++-- mopidy/backends/dummy/__init__.py | 13 +++-- mopidy/backends/libspotify/__init__.py | 9 ++- mopidy/backends/libspotify/library.py | 4 +- mopidy/backends/local/__init__.py | 26 +++++---- tests/frontends/mpd/current_playlist_test.py | 8 +-- 8 files changed, 100 insertions(+), 31 deletions(-) diff --git a/docs/api/backends/providers.rst b/docs/api/backends/providers.rst index 0b042241..9289dd06 100644 --- a/docs/api/backends/providers.rst +++ b/docs/api/backends/providers.rst @@ -25,6 +25,14 @@ Stored playlists provider :undoc-members: +Library provider +================ + +.. autoclass:: mopidy.backends.base.BaseLibraryProvider + :members: + :undoc-members: + + Backend provider implementations ================================ diff --git a/mopidy/backends/base/__init__.py b/mopidy/backends/base/__init__.py index 7ee86015..70b17397 100644 --- a/mopidy/backends/base/__init__.py +++ b/mopidy/backends/base/__init__.py @@ -9,7 +9,7 @@ from mopidy.models import Playlist from mopidy.utils import get_class from .current_playlist import BaseCurrentPlaylistController -from .library import BaseLibraryController +from .library import BaseLibraryController, BaseLibraryProvider from .playback import BasePlaybackController, BasePlaybackProvider from .stored_playlists import (BaseStoredPlaylistsController, BaseStoredPlaylistsProvider) diff --git a/mopidy/backends/base/library.py b/mopidy/backends/base/library.py index 94f40863..a1df83cd 100644 --- a/mopidy/backends/base/library.py +++ b/mopidy/backends/base/library.py @@ -8,12 +8,13 @@ class BaseLibraryController(object): :type backend: :class:`BaseBackend` """ - def __init__(self, backend): + def __init__(self, backend, provider): self.backend = backend + self.provider = provider def destroy(self): """Cleanup after component.""" - pass + self.provider.destroy() def find_exact(self, **query): """ @@ -32,7 +33,7 @@ class BaseLibraryController(object): :type query: dict :rtype: :class:`mopidy.models.Playlist` """ - raise NotImplementedError + return self.provider.find_exact(**query) def lookup(self, uri): """ @@ -42,7 +43,7 @@ class BaseLibraryController(object): :type uri: string :rtype: :class:`mopidy.models.Track` or :class:`None` """ - raise NotImplementedError + return self.provider.lookup(uri) def refresh(self, uri=None): """ @@ -51,7 +52,7 @@ class BaseLibraryController(object): :param uri: directory or track URI :type uri: string """ - raise NotImplementedError + return self.provider.refresh(uri) def search(self, **query): """ @@ -70,4 +71,54 @@ class BaseLibraryController(object): :type query: dict :rtype: :class:`mopidy.models.Playlist` """ + return self.provider.search(**query) + + +class BaseLibraryProvider(object): + """ + :param backend: backend the controller is a part of + :type backend: :class:`BaseBackend` + """ + + def __init__(self, backend): + self.backend = backend + + def destroy(self): + """ + Cleanup after component. + + *MAY be implemented by subclasses.* + """ + pass + + def find_exact(self, **query): + """ + See :meth:`mopidy.backends.base.BaseLibraryController.find_exact`. + + *MUST be implemented by subclass.* + """ + raise NotImplementedError + + def lookup(self, uri): + """ + See :meth:`mopidy.backends.base.BaseLibraryController.lookup`. + + *MUST be implemented by subclass.* + """ + raise NotImplementedError + + def refresh(self, uri=None): + """ + See :meth:`mopidy.backends.base.BaseLibraryController.refresh`. + + *MUST be implemented by subclass.* + """ + raise NotImplementedError + + def search(self, **query): + """ + See :meth:`mopidy.backends.base.BaseLibraryController.search`. + + *MUST be implemented by subclass.* + """ raise NotImplementedError diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py index 6b259338..df4428e3 100644 --- a/mopidy/backends/dummy/__init__.py +++ b/mopidy/backends/dummy/__init__.py @@ -1,6 +1,7 @@ from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, BasePlaybackController, BasePlaybackProvider, BaseLibraryController, - BaseStoredPlaylistsController, BaseStoredPlaylistsProvider) + BaseLibraryProvider, BaseStoredPlaylistsController, + BaseStoredPlaylistsProvider) from mopidy.models import Playlist @@ -27,7 +28,9 @@ class DummyBackend(BaseBackend): self.current_playlist = BaseCurrentPlaylistController(backend=self) - self.library = DummyLibraryController(backend=self) + library_provider = DummyLibraryProvider(backend=self) + self.library = BaseLibraryController(backend=self, + provider=library_provider) playback_provider = DummyPlaybackProvider(backend=self) self.playback = BasePlaybackController(backend=self, @@ -40,8 +43,10 @@ class DummyBackend(BaseBackend): self.uri_handlers = [u'dummy:'] -class DummyLibraryController(BaseLibraryController): - _library = [] +class DummyLibraryProvider(BaseLibraryProvider): + def __init__(self, *args, **kwargs): + super(DummyLibraryProvider, self).__init__(*args, **kwargs) + self._library = [] def find_exact(self, **query): return Playlist() diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index 98b9e884..0eefcd41 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -2,7 +2,8 @@ import logging from mopidy import settings from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, - BasePlaybackController, BaseStoredPlaylistsController) + BaseLibraryController, BasePlaybackController, + BaseStoredPlaylistsController) logger = logging.getLogger('mopidy.backends.libspotify') @@ -34,7 +35,7 @@ class LibspotifyBackend(BaseBackend): # missing spotify dependencies. def __init__(self, *args, **kwargs): - from .library import LibspotifyLibraryController + from .library import LibspotifyLibraryProvider from .playback import LibspotifyPlaybackProvider from .stored_playlists import LibspotifyStoredPlaylistsProvider @@ -42,7 +43,9 @@ class LibspotifyBackend(BaseBackend): self.current_playlist = BaseCurrentPlaylistController(backend=self) - self.library = LibspotifyLibraryController(backend=self) + library_provider = LibspotifyLibraryProvider(backend=self) + self.library = BaseLibraryController(backend=self, + provider=library_provider) playback_provider = LibspotifyPlaybackProvider(backend=self) self.playback = BasePlaybackController(backend=self, diff --git a/mopidy/backends/libspotify/library.py b/mopidy/backends/libspotify/library.py index 972eaf03..948c69b2 100644 --- a/mopidy/backends/libspotify/library.py +++ b/mopidy/backends/libspotify/library.py @@ -3,14 +3,14 @@ import multiprocessing from spotify import Link, SpotifyError -from mopidy.backends.base import BaseLibraryController +from mopidy.backends.base import BaseLibraryProvider from mopidy.backends.libspotify import ENCODING from mopidy.backends.libspotify.translator import LibspotifyTranslator from mopidy.models import Playlist logger = logging.getLogger('mopidy.backends.libspotify.library') -class LibspotifyLibraryController(BaseLibraryController): +class LibspotifyLibraryProvider(BaseLibraryProvider): def find_exact(self, **query): return self.search(**query) diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index cf9cbd60..a5b6fb7a 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -5,10 +5,10 @@ import os import shutil from mopidy import settings -from mopidy.backends.base import (BaseBackend, - BaseCurrentPlaylistController, BaseLibraryController, - BasePlaybackController, BasePlaybackProvider, - BaseStoredPlaylistsController, BaseStoredPlaylistsProvider) +from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, + BaseLibraryController, BaseLibraryProvider, BasePlaybackController, + BasePlaybackProvider, BaseStoredPlaylistsController, + BaseStoredPlaylistsProvider) from mopidy.models import Playlist, Track, Album from mopidy.utils.process import pickle_connection @@ -32,18 +32,20 @@ class LocalBackend(BaseBackend): def __init__(self, *args, **kwargs): super(LocalBackend, self).__init__(*args, **kwargs) - self.library = LocalLibraryController(backend=self) - - stored_playlists_provider = LocalStoredPlaylistsProvider(backend=self) - self.stored_playlists = BaseStoredPlaylistsController(backend=self, - provider=stored_playlists_provider) - self.current_playlist = BaseCurrentPlaylistController(backend=self) + library_provider = LocalLibraryProvider(backend=self) + self.library = BaseLibraryController(backend=self, + provider=library_provider) + playback_provider = LocalPlaybackProvider(backend=self) self.playback = LocalPlaybackController(backend=self, provider=playback_provider) + stored_playlists_provider = LocalStoredPlaylistsProvider(backend=self) + self.stored_playlists = BaseStoredPlaylistsController(backend=self, + provider=stored_playlists_provider) + self.uri_handlers = [u'file://'] @@ -149,9 +151,9 @@ class LocalStoredPlaylistsProvider(BaseStoredPlaylistsProvider): self._playlists.append(playlist) -class LocalLibraryController(BaseLibraryController): +class LocalLibraryProvider(BaseLibraryProvider): def __init__(self, *args, **kwargs): - super(LocalLibraryController, self).__init__(*args, **kwargs) + super(LocalLibraryProvider, self).__init__(*args, **kwargs) self._uri_mapping = {} self.refresh() diff --git a/tests/frontends/mpd/current_playlist_test.py b/tests/frontends/mpd/current_playlist_test.py index 8a4b9ab5..a4179637 100644 --- a/tests/frontends/mpd/current_playlist_test.py +++ b/tests/frontends/mpd/current_playlist_test.py @@ -12,7 +12,7 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): def test_add(self): needle = Track(uri='dummy://foo') - self.b.library._library = [Track(), Track(), needle, Track()] + self.b.library.provider._library = [Track(), Track(), needle, Track()] self.b.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.b.current_playlist.tracks), 5) @@ -40,7 +40,7 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): def test_addid_without_songpos(self): needle = Track(uri='dummy://foo') - self.b.library._library = [Track(), Track(), needle, Track()] + self.b.library.provider._library = [Track(), Track(), needle, Track()] self.b.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.b.current_playlist.tracks), 5) @@ -58,7 +58,7 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): def test_addid_with_songpos(self): needle = Track(uri='dummy://foo') - self.b.library._library = [Track(), Track(), needle, Track()] + self.b.library.provider._library = [Track(), Track(), needle, Track()] self.b.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.b.current_playlist.tracks), 5) @@ -71,7 +71,7 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): def test_addid_with_songpos_out_of_bounds_should_ack(self): needle = Track(uri='dummy://foo') - self.b.library._library = [Track(), Track(), needle, Track()] + self.b.library.provider._library = [Track(), Track(), needle, Track()] self.b.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.b.current_playlist.tracks), 5) From 3e8be009e4ba19b0372cfadb29b960d1e03599e8 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 2 Nov 2010 23:33:07 +0100 Subject: [PATCH 051/111] Add consistent may/must notices to all unimplemented methods --- mopidy/backends/base/playback.py | 22 +++++++------- mopidy/backends/base/stored_playlists.py | 26 ++++++++-------- mopidy/frontends/base.py | 14 +++++++-- mopidy/mixers/__init__.py | 4 +-- mopidy/outputs/base.py | 38 +++++++++++++++++++++--- 5 files changed, 72 insertions(+), 32 deletions(-) diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 1ab8aeda..1a47391f 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -498,26 +498,26 @@ class BasePlaybackProvider(object): """ Cleanup after component. - May be overridden by subclasses. + *MAY be implemented by subclasses.* """ pass def pause(self): """ - To be overridden by subclass. - Pause playback. + *MUST be implemented by subclass.* + :rtype: :class:`True` if successful, else :class:`False` """ raise NotImplementedError def play(self, track): """ - To be overridden by subclass. - Play given track. + *MUST be implemented by subclass.* + :param track: the track to play :type track: :class:`mopidy.models.Track` :rtype: :class:`True` if successful, else :class:`False` @@ -526,20 +526,20 @@ class BasePlaybackProvider(object): def resume(self): """ - To be overridden by subclass. - Resume playback at the same time position playback was paused. + *MUST be implemented by subclass.* + :rtype: :class:`True` if successful, else :class:`False` """ raise NotImplementedError def seek(self, time_position): """ - To be overridden by subclass. - Seek to a given time position. + *MUST be implemented by subclass.* + :param time_position: time position in milliseconds :type time_position: int :rtype: :class:`True` if successful, else :class:`False` @@ -548,10 +548,10 @@ class BasePlaybackProvider(object): def stop(self): """ - To be overridden by subclass. - Stop playback. + *MUST be implemented by subclass.* + :rtype: :class:`True` if successful, else :class:`False` """ raise NotImplementedError diff --git a/mopidy/backends/base/stored_playlists.py b/mopidy/backends/base/stored_playlists.py index cf14db9d..dd7380d6 100644 --- a/mopidy/backends/base/stored_playlists.py +++ b/mopidy/backends/base/stored_playlists.py @@ -133,7 +133,7 @@ class BaseStoredPlaylistsProvider(object): """ Cleanup after component. - May be overridden by subclasses. + *MAY be implemented by subclass.* """ pass @@ -152,49 +152,49 @@ class BaseStoredPlaylistsProvider(object): def create(self, name): """ - To be overridden by subclass. - See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.create`. + + *MUST be implemented by subclass.* """ raise NotImplementedError def delete(self, playlist): """ - To be overridden by subclass. - See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.delete`. + + *MUST be implemented by subclass.* """ raise NotImplementedError def lookup(self, uri): """ - To be overridden by subclass. - See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.lookup`. + + *MUST be implemented by subclass.* """ raise NotImplementedError def refresh(self): """ - To be overridden by subclass. - See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.refresh`. + + *MUST be implemented by subclass.* """ raise NotImplementedError def rename(self, playlist, new_name): """ - To be overridden by subclass. - See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.rename`. + + *MUST be implemented by subclass.* """ raise NotImplementedError def save(self, playlist): """ - To be overridden by subclass. - See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.save`. + + *MUST be implemented by subclass.* """ raise NotImplementedError diff --git a/mopidy/frontends/base.py b/mopidy/frontends/base.py index 92545b73..e15c4745 100644 --- a/mopidy/frontends/base.py +++ b/mopidy/frontends/base.py @@ -13,17 +13,27 @@ class BaseFrontend(object): self.backend = backend def start(self): - """Start the frontend.""" + """ + Start the frontend. + + *MAY be implemented by subclass.* + """ pass def destroy(self): - """Destroy the frontend.""" + """ + Destroy the frontend. + + *MAY be implemented by subclass.* + """ pass def process_message(self, message): """ Process messages for the frontend. + *MUST be implemented by subclass.* + :param message: the message :type message: dict """ diff --git a/mopidy/mixers/__init__.py b/mopidy/mixers/__init__.py index 332718a6..231154ee 100644 --- a/mopidy/mixers/__init__.py +++ b/mopidy/mixers/__init__.py @@ -42,7 +42,7 @@ class BaseMixer(object): """ Return volume as integer in range [0, 100]. :class:`None` if unknown. - *Must be implemented by subclass.* + *MUST be implemented by subclass.* """ raise NotImplementedError @@ -50,6 +50,6 @@ class BaseMixer(object): """ Set volume as integer in range [0, 100]. - *Must be implemented by subclass.* + *MUST be implemented by subclass.* """ raise NotImplementedError diff --git a/mopidy/outputs/base.py b/mopidy/outputs/base.py index bb312323..372d7d70 100644 --- a/mopidy/outputs/base.py +++ b/mopidy/outputs/base.py @@ -7,21 +7,35 @@ class BaseOutput(object): self.core_queue = core_queue def start(self): - """Start the output.""" + """ + Start the output. + + *MAY be implemented by subclasses.* + """ pass def destroy(self): - """Destroy the output.""" + """ + Destroy the output. + + *MAY be implemented by subclasses.* + """ pass def process_message(self, message): - """Process messages with the output as destination.""" + """ + Process messages with the output as destination. + + *MUST be implemented by subclass.* + """ raise NotImplementedError def play_uri(self, uri): """ Play URI. + *MUST be implemented by subclass.* + :param uri: the URI to play :type uri: string :rtype: :class:`True` if successful, else :class:`False` @@ -32,19 +46,27 @@ class BaseOutput(object): """ Deliver audio data to be played. + *MUST be implemented by subclass.* + :param capabilities: a GStreamer capabilities string :type capabilities: string """ raise NotImplementedError def end_of_data_stream(self): - """Signal that the last audio data has been delivered.""" + """ + Signal that the last audio data has been delivered. + + *MUST be implemented by subclass.* + """ raise NotImplementedError def get_position(self): """ Get position in milliseconds. + *MUST be implemented by subclass.* + :rtype: int """ raise NotImplementedError @@ -53,6 +75,8 @@ class BaseOutput(object): """ Set position in milliseconds. + *MUST be implemented by subclass.* + :param position: the position in milliseconds :type volume: int :rtype: :class:`True` if successful, else :class:`False` @@ -63,6 +87,8 @@ class BaseOutput(object): """ Set playback state. + *MUST be implemented by subclass.* + :param state: the state :type state: string :rtype: :class:`True` if successful, else :class:`False` @@ -73,6 +99,8 @@ class BaseOutput(object): """ Get volume level for software mixer. + *MUST be implemented by subclass.* + :rtype: int in range [0..100] """ raise NotImplementedError @@ -81,6 +109,8 @@ class BaseOutput(object): """ Set volume level for software mixer. + *MUST be implemented by subclass.* + :param volume: the volume in the range [0..100] :type volume: int :rtype: :class:`True` if successful, else :class:`False` From 9267cd43cdb34183dacff3863981b6e207affaf0 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 2 Nov 2010 23:35:56 +0100 Subject: [PATCH 052/111] Move BaseMixer from __init__.py to base.py --- mopidy/mixers/__init__.py | 55 ----------------------------- mopidy/mixers/alsa.py | 2 +- mopidy/mixers/base.py | 55 +++++++++++++++++++++++++++++ mopidy/mixers/denon.py | 2 +- mopidy/mixers/dummy.py | 2 +- mopidy/mixers/gstreamer_software.py | 2 +- mopidy/mixers/nad.py | 2 +- mopidy/mixers/osa.py | 2 +- 8 files changed, 61 insertions(+), 61 deletions(-) create mode 100644 mopidy/mixers/base.py diff --git a/mopidy/mixers/__init__.py b/mopidy/mixers/__init__.py index 231154ee..e69de29b 100644 --- a/mopidy/mixers/__init__.py +++ b/mopidy/mixers/__init__.py @@ -1,55 +0,0 @@ -from mopidy import settings - -class BaseMixer(object): - """ - :param backend: a backend instance - :type mixer: :class:`mopidy.backends.base.BaseBackend` - - **Settings:** - - - :attr:`mopidy.settings.MIXER_MAX_VOLUME` - """ - - def __init__(self, backend, *args, **kwargs): - self.backend = backend - self.amplification_factor = settings.MIXER_MAX_VOLUME / 100.0 - - @property - def volume(self): - """ - The audio volume - - Integer in range [0, 100]. :class:`None` if unknown. Values below 0 is - equal to 0. Values above 100 is equal to 100. - """ - if self._get_volume() is None: - return None - return int(self._get_volume() / self.amplification_factor) - - @volume.setter - def volume(self, volume): - volume = int(int(volume) * self.amplification_factor) - if volume < 0: - volume = 0 - elif volume > 100: - volume = 100 - self._set_volume(volume) - - def destroy(self): - pass - - def _get_volume(self): - """ - Return volume as integer in range [0, 100]. :class:`None` if unknown. - - *MUST be implemented by subclass.* - """ - raise NotImplementedError - - def _set_volume(self, volume): - """ - Set volume as integer in range [0, 100]. - - *MUST be implemented by subclass.* - """ - raise NotImplementedError diff --git a/mopidy/mixers/alsa.py b/mopidy/mixers/alsa.py index 6eef6da4..f90060ce 100644 --- a/mopidy/mixers/alsa.py +++ b/mopidy/mixers/alsa.py @@ -2,7 +2,7 @@ import alsaaudio import logging from mopidy import settings -from mopidy.mixers import BaseMixer +from mopidy.mixers.base import BaseMixer logger = logging.getLogger('mopidy.mixers.alsa') diff --git a/mopidy/mixers/base.py b/mopidy/mixers/base.py new file mode 100644 index 00000000..231154ee --- /dev/null +++ b/mopidy/mixers/base.py @@ -0,0 +1,55 @@ +from mopidy import settings + +class BaseMixer(object): + """ + :param backend: a backend instance + :type mixer: :class:`mopidy.backends.base.BaseBackend` + + **Settings:** + + - :attr:`mopidy.settings.MIXER_MAX_VOLUME` + """ + + def __init__(self, backend, *args, **kwargs): + self.backend = backend + self.amplification_factor = settings.MIXER_MAX_VOLUME / 100.0 + + @property + def volume(self): + """ + The audio volume + + Integer in range [0, 100]. :class:`None` if unknown. Values below 0 is + equal to 0. Values above 100 is equal to 100. + """ + if self._get_volume() is None: + return None + return int(self._get_volume() / self.amplification_factor) + + @volume.setter + def volume(self, volume): + volume = int(int(volume) * self.amplification_factor) + if volume < 0: + volume = 0 + elif volume > 100: + volume = 100 + self._set_volume(volume) + + def destroy(self): + pass + + def _get_volume(self): + """ + Return volume as integer in range [0, 100]. :class:`None` if unknown. + + *MUST be implemented by subclass.* + """ + raise NotImplementedError + + def _set_volume(self, volume): + """ + Set volume as integer in range [0, 100]. + + *MUST be implemented by subclass.* + """ + raise NotImplementedError diff --git a/mopidy/mixers/denon.py b/mopidy/mixers/denon.py index 32750f60..e6d752b6 100644 --- a/mopidy/mixers/denon.py +++ b/mopidy/mixers/denon.py @@ -4,7 +4,7 @@ from threading import Lock from serial import Serial from mopidy import settings -from mopidy.mixers import BaseMixer +from mopidy.mixers.base import BaseMixer logger = logging.getLogger(u'mopidy.mixers.denon') diff --git a/mopidy/mixers/dummy.py b/mopidy/mixers/dummy.py index b0ea0e47..12a8137e 100644 --- a/mopidy/mixers/dummy.py +++ b/mopidy/mixers/dummy.py @@ -1,4 +1,4 @@ -from mopidy.mixers import BaseMixer +from mopidy.mixers.base import BaseMixer class DummyMixer(BaseMixer): """Mixer which just stores and reports the chosen volume.""" diff --git a/mopidy/mixers/gstreamer_software.py b/mopidy/mixers/gstreamer_software.py index 333690ea..9dca3690 100644 --- a/mopidy/mixers/gstreamer_software.py +++ b/mopidy/mixers/gstreamer_software.py @@ -1,4 +1,4 @@ -from mopidy.mixers import BaseMixer +from mopidy.mixers.base import BaseMixer class GStreamerSoftwareMixer(BaseMixer): """Mixer which uses GStreamer to control volume in software.""" diff --git a/mopidy/mixers/nad.py b/mopidy/mixers/nad.py index 8caa9700..3215a761 100644 --- a/mopidy/mixers/nad.py +++ b/mopidy/mixers/nad.py @@ -3,7 +3,7 @@ from serial import Serial from multiprocessing import Pipe from mopidy import settings -from mopidy.mixers import BaseMixer +from mopidy.mixers.base import BaseMixer from mopidy.utils.process import BaseThread logger = logging.getLogger('mopidy.mixers.nad') diff --git a/mopidy/mixers/osa.py b/mopidy/mixers/osa.py index 3aeaed5c..8d69eb47 100644 --- a/mopidy/mixers/osa.py +++ b/mopidy/mixers/osa.py @@ -1,7 +1,7 @@ from subprocess import Popen, PIPE import time -from mopidy.mixers import BaseMixer +from mopidy.mixers.base import BaseMixer class OsaMixer(BaseMixer): """Mixer which uses ``osascript`` on OS X to control volume.""" From 6a8998a5c38241154b25bb0038b01a2ba3e67652 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 2 Nov 2010 23:42:02 +0100 Subject: [PATCH 053/111] Fix pylint warnings --- mopidy/core.py | 1 + mopidy/frontends/mpd/translator.py | 2 +- mopidy/scanner.py | 4 +--- mopidy/utils/path.py | 3 +++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mopidy/core.py b/mopidy/core.py index 0be6b96f..1a4ed7cc 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -18,6 +18,7 @@ class CoreProcess(BaseThread): super(CoreProcess, self).__init__(self.core_queue) self.name = 'CoreProcess' self.options = self.parse_options() + self.gobject_loop = None self.output = None self.backend = None self.frontends = [] diff --git a/mopidy/frontends/mpd/translator.py b/mopidy/frontends/mpd/translator.py index e15e1ba5..a2fa381f 100644 --- a/mopidy/frontends/mpd/translator.py +++ b/mopidy/frontends/mpd/translator.py @@ -4,7 +4,7 @@ import re from mopidy import settings from mopidy.utils.path import mtime as get_mtime from mopidy.frontends.mpd import protocol -from mopidy.utils.path import path_to_uri, uri_to_path, split_path +from mopidy.utils.path import uri_to_path, split_path def track_to_mpd_format(track, position=None, cpid=None): """ diff --git a/mopidy/scanner.py b/mopidy/scanner.py index 4ccccbdb..8c88d5d0 100644 --- a/mopidy/scanner.py +++ b/mopidy/scanner.py @@ -6,8 +6,6 @@ pygst.require('0.10') import gst import datetime -import sys -import threading from mopidy.utils.path import path_to_uri, find_files from mopidy.models import Track, Artist, Album @@ -25,7 +23,7 @@ def translator(data): album_kwargs['num_tracks'] = data['track-count'] if 'artist' in data: - artist_kwargs['name'] =data['artist'] + artist_kwargs['name'] = data['artist'] if 'date' in data: date = data['date'] diff --git a/mopidy/utils/path.py b/mopidy/utils/path.py index f25d754a..540cb4fa 100644 --- a/mopidy/utils/path.py +++ b/mopidy/utils/path.py @@ -44,6 +44,8 @@ def split_path(path): break return parts +# pylint: disable = W0612 +# Unused variable 'dirnames' def find_files(path): if os.path.isfile(path): if not isinstance(path, unicode): @@ -56,6 +58,7 @@ def find_files(path): if not isinstance(filename, unicode): filename = filename.decode('utf-8') yield filename +# pylint: enable = W0612 class Mtime(object): def __init__(self): From 0836418bfcfa30516489f5b783aa426e58a50476 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 2 Nov 2010 23:42:58 +0100 Subject: [PATCH 054/111] Fix pylint error --- mopidy/backends/base/stored_playlists.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/backends/base/stored_playlists.py b/mopidy/backends/base/stored_playlists.py index dd7380d6..bb86c92e 100644 --- a/mopidy/backends/base/stored_playlists.py +++ b/mopidy/backends/base/stored_playlists.py @@ -96,7 +96,7 @@ class BaseStoredPlaylistsController(object): Refresh the stored playlists in :attr:`mopidy.backends.base.BaseStoredPlaylistsController.playlists`. """ - return self.provider.refresh(uri) + return self.provider.refresh() def rename(self, playlist, new_name): """ From c82b0d01aad3571d748cf092dae304b22c1a90de Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 2 Nov 2010 23:46:47 +0100 Subject: [PATCH 055/111] We do not need __all__, cause we do not 'import *' --- mopidy/backends/base/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mopidy/backends/base/__init__.py b/mopidy/backends/base/__init__.py index 70b17397..f4a54984 100644 --- a/mopidy/backends/base/__init__.py +++ b/mopidy/backends/base/__init__.py @@ -16,10 +16,6 @@ from .stored_playlists import (BaseStoredPlaylistsController, logger = logging.getLogger('mopidy.backends.base') -__all__ = ['BaseBackend', 'BasePlaybackController', 'BasePlaybackProvider', - 'BaseCurrentPlaylistController', 'BaseStoredPlaylistsController', - 'BaseLibraryController'] - class BaseBackend(object): """ :param core_queue: a queue for sending messages to From 79b61895cc89839fca35ac6ad720253fc790c3ff Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 2 Nov 2010 23:49:15 +0100 Subject: [PATCH 056/111] Remove unmaintained tests --- tests/backends/libspotify/__init__.py | 0 .../libspotify/backend_integrationtest.py | 44 ------------------- 2 files changed, 44 deletions(-) delete mode 100644 tests/backends/libspotify/__init__.py delete mode 100644 tests/backends/libspotify/backend_integrationtest.py diff --git a/tests/backends/libspotify/__init__.py b/tests/backends/libspotify/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/backends/libspotify/backend_integrationtest.py b/tests/backends/libspotify/backend_integrationtest.py deleted file mode 100644 index 8d1f0b0e..00000000 --- a/tests/backends/libspotify/backend_integrationtest.py +++ /dev/null @@ -1,44 +0,0 @@ -# TODO This integration test is work in progress. - -import unittest - -from mopidy.backends.libspotify import LibspotifyBackend -from mopidy.models import Track - -from tests.backends.base.current_playlist import \ - BaseCurrentPlaylistControllerTest -from tests.backends.base.library import BaseLibraryControllerTest -from tests.backends.base.playback import BasePlaybackControllerTest -from tests.backends.base.stored_playlists import \ - BaseStoredPlaylistsControllerTest - -uris = [ - 'spotify:track:6vqcpVcbI3Zu6sH3ieLDNt', - 'spotify:track:111sulhaZqgsnypz3MkiaW', - 'spotify:track:7t8oznvbeiAPMDRuK0R5ZT', -] - -class LibspotifyCurrentPlaylistControllerTest( - BaseCurrentPlaylistControllerTest, unittest.TestCase): - - backend_class = LibspotifyBackend - tracks = [Track(uri=uri, length=4464) for i, uri in enumerate(uris)] - - -class LibspotifyPlaybackControllerTest( - BasePlaybackControllerTest, unittest.TestCase): - - backend_class = LibspotifyBackend - tracks = [Track(uri=uri, length=4464) for i, uri in enumerate(uris)] - - -class LibspotifyStoredPlaylistsControllerTest( - BaseStoredPlaylistsControllerTest, unittest.TestCase): - - backend_class = LibspotifyBackend - - -class LibspotifyLibraryControllerTest( - BaseLibraryControllerTest, unittest.TestCase): - - backend_class = LibspotifyBackend From 67885e83b6076624c7a8870c08f8165bb8677a97 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 2 Nov 2010 23:50:47 +0100 Subject: [PATCH 057/111] Rename {Base => }CurrentPlaylistController --- docs/api/backends/controllers.rst | 2 +- mopidy/backends/base/__init__.py | 4 ++-- mopidy/backends/base/current_playlist.py | 2 +- mopidy/backends/base/playback.py | 2 +- mopidy/backends/dummy/__init__.py | 4 ++-- mopidy/backends/libspotify/__init__.py | 4 ++-- mopidy/backends/local/__init__.py | 4 ++-- tests/backends/base/current_playlist.py | 2 +- tests/backends/local/current_playlist_test.py | 5 ++--- 9 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/api/backends/controllers.rst b/docs/api/backends/controllers.rst index a9d54504..16ecc9b6 100644 --- a/docs/api/backends/controllers.rst +++ b/docs/api/backends/controllers.rst @@ -40,7 +40,7 @@ Current playlist controller Manages everything related to the currently loaded playlist. -.. autoclass:: mopidy.backends.base.BaseCurrentPlaylistController +.. autoclass:: mopidy.backends.base.CurrentPlaylistController :members: :undoc-members: diff --git a/mopidy/backends/base/__init__.py b/mopidy/backends/base/__init__.py index f4a54984..39ad6e9e 100644 --- a/mopidy/backends/base/__init__.py +++ b/mopidy/backends/base/__init__.py @@ -8,7 +8,7 @@ from mopidy.frontends.mpd import translator from mopidy.models import Playlist from mopidy.utils import get_class -from .current_playlist import BaseCurrentPlaylistController +from .current_playlist import CurrentPlaylistController from .library import BaseLibraryController, BaseLibraryProvider from .playback import BasePlaybackController, BasePlaybackProvider from .stored_playlists import (BaseStoredPlaylistsController, @@ -42,7 +42,7 @@ class BaseBackend(object): core_queue = None #: The current playlist controller. An instance of - #: :class:`mopidy.backends.base.BaseCurrentPlaylistController`. + #: :class:`mopidy.backends.base.CurrentPlaylistController`. current_playlist = None #: The library controller. An instance of diff --git a/mopidy/backends/base/current_playlist.py b/mopidy/backends/base/current_playlist.py index 34a16369..fec8e5a3 100644 --- a/mopidy/backends/base/current_playlist.py +++ b/mopidy/backends/base/current_playlist.py @@ -6,7 +6,7 @@ from mopidy.frontends.mpd import translator logger = logging.getLogger('mopidy.backends.base') -class BaseCurrentPlaylistController(object): +class CurrentPlaylistController(object): """ :param backend: backend the controller is a part of :type backend: :class:`BaseBackend` diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 1a47391f..fa848845 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -331,7 +331,7 @@ class BasePlaybackController(object): """ Tell the playback controller that the current playlist has changed. - Used by :class:`mopidy.backends.base.BaseCurrentPlaylistController`. + Used by :class:`mopidy.backends.base.CurrentPlaylistController`. """ self._first_shuffle = True self._shuffled = [] diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py index df4428e3..00f8c9f2 100644 --- a/mopidy/backends/dummy/__init__.py +++ b/mopidy/backends/dummy/__init__.py @@ -1,4 +1,4 @@ -from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, +from mopidy.backends.base import (BaseBackend, CurrentPlaylistController, BasePlaybackController, BasePlaybackProvider, BaseLibraryController, BaseLibraryProvider, BaseStoredPlaylistsController, BaseStoredPlaylistsProvider) @@ -26,7 +26,7 @@ class DummyBackend(BaseBackend): self.core_queue = DummyQueue() - self.current_playlist = BaseCurrentPlaylistController(backend=self) + self.current_playlist = CurrentPlaylistController(backend=self) library_provider = DummyLibraryProvider(backend=self) self.library = BaseLibraryController(backend=self, diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index 0eefcd41..450ac7be 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -1,7 +1,7 @@ import logging from mopidy import settings -from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, +from mopidy.backends.base import (BaseBackend, CurrentPlaylistController, BaseLibraryController, BasePlaybackController, BaseStoredPlaylistsController) @@ -41,7 +41,7 @@ class LibspotifyBackend(BaseBackend): super(LibspotifyBackend, self).__init__(*args, **kwargs) - self.current_playlist = BaseCurrentPlaylistController(backend=self) + self.current_playlist = CurrentPlaylistController(backend=self) library_provider = LibspotifyLibraryProvider(backend=self) self.library = BaseLibraryController(backend=self, diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index a5b6fb7a..0c624687 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -5,7 +5,7 @@ import os import shutil from mopidy import settings -from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController, +from mopidy.backends.base import (BaseBackend, CurrentPlaylistController, BaseLibraryController, BaseLibraryProvider, BasePlaybackController, BasePlaybackProvider, BaseStoredPlaylistsController, BaseStoredPlaylistsProvider) @@ -32,7 +32,7 @@ class LocalBackend(BaseBackend): def __init__(self, *args, **kwargs): super(LocalBackend, self).__init__(*args, **kwargs) - self.current_playlist = BaseCurrentPlaylistController(backend=self) + self.current_playlist = CurrentPlaylistController(backend=self) library_provider = LocalLibraryProvider(backend=self) self.library = BaseLibraryController(backend=self, diff --git a/tests/backends/base/current_playlist.py b/tests/backends/base/current_playlist.py index 05f08e18..2b6cb84e 100644 --- a/tests/backends/base/current_playlist.py +++ b/tests/backends/base/current_playlist.py @@ -9,7 +9,7 @@ from mopidy.utils import get_class from tests.backends.base import populate_playlist -class BaseCurrentPlaylistControllerTest(object): +class CurrentPlaylistControllerTest(object): tracks = [] def setUp(self): diff --git a/tests/backends/local/current_playlist_test.py b/tests/backends/local/current_playlist_test.py index 3895497a..6f72d7d5 100644 --- a/tests/backends/local/current_playlist_test.py +++ b/tests/backends/local/current_playlist_test.py @@ -10,11 +10,10 @@ from mopidy import settings from mopidy.backends.local import LocalBackend from mopidy.models import Track -from tests.backends.base.current_playlist import \ - BaseCurrentPlaylistControllerTest +from tests.backends.base.current_playlist import CurrentPlaylistControllerTest from tests.backends.local import generate_song -class LocalCurrentPlaylistControllerTest(BaseCurrentPlaylistControllerTest, +class LocalCurrentPlaylistControllerTest(CurrentPlaylistControllerTest, unittest.TestCase): backend_class = LocalBackend From 5ada4709bdfd5a20ec809edd4f64817ce662a608 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 2 Nov 2010 23:54:02 +0100 Subject: [PATCH 058/111] Rename {Base => }LibraryController --- docs/api/backends/controllers.rst | 2 +- mopidy/backends/base/__init__.py | 4 ++-- mopidy/backends/base/library.py | 10 +++++----- mopidy/backends/dummy/__init__.py | 4 ++-- mopidy/backends/libspotify/__init__.py | 5 ++--- mopidy/backends/local/__init__.py | 4 ++-- tests/backends/base/library.py | 2 +- tests/backends/local/library_test.py | 4 ++-- 8 files changed, 17 insertions(+), 18 deletions(-) diff --git a/docs/api/backends/controllers.rst b/docs/api/backends/controllers.rst index 16ecc9b6..d3346a27 100644 --- a/docs/api/backends/controllers.rst +++ b/docs/api/backends/controllers.rst @@ -60,6 +60,6 @@ Library controller Manages the music library, e.g. searching for tracks to be added to a playlist. -.. autoclass:: mopidy.backends.base.BaseLibraryController +.. autoclass:: mopidy.backends.base.LibraryController :members: :undoc-members: diff --git a/mopidy/backends/base/__init__.py b/mopidy/backends/base/__init__.py index 39ad6e9e..529c2179 100644 --- a/mopidy/backends/base/__init__.py +++ b/mopidy/backends/base/__init__.py @@ -9,7 +9,7 @@ from mopidy.models import Playlist from mopidy.utils import get_class from .current_playlist import CurrentPlaylistController -from .library import BaseLibraryController, BaseLibraryProvider +from .library import LibraryController, BaseLibraryProvider from .playback import BasePlaybackController, BasePlaybackProvider from .stored_playlists import (BaseStoredPlaylistsController, BaseStoredPlaylistsProvider) @@ -46,7 +46,7 @@ class BaseBackend(object): current_playlist = None #: The library controller. An instance of - # :class:`mopidy.backends.base.BaseLibraryController`. + # :class:`mopidy.backends.base.LibraryController`. library = None #: The sound mixer. An instance of :class:`mopidy.mixers.BaseMixer`. diff --git a/mopidy/backends/base/library.py b/mopidy/backends/base/library.py index a1df83cd..697d3554 100644 --- a/mopidy/backends/base/library.py +++ b/mopidy/backends/base/library.py @@ -2,7 +2,7 @@ import logging logger = logging.getLogger('mopidy.backends.base') -class BaseLibraryController(object): +class LibraryController(object): """ :param backend: backend the controller is a part of :type backend: :class:`BaseBackend` @@ -93,7 +93,7 @@ class BaseLibraryProvider(object): def find_exact(self, **query): """ - See :meth:`mopidy.backends.base.BaseLibraryController.find_exact`. + See :meth:`mopidy.backends.base.LibraryController.find_exact`. *MUST be implemented by subclass.* """ @@ -101,7 +101,7 @@ class BaseLibraryProvider(object): def lookup(self, uri): """ - See :meth:`mopidy.backends.base.BaseLibraryController.lookup`. + See :meth:`mopidy.backends.base.LibraryController.lookup`. *MUST be implemented by subclass.* """ @@ -109,7 +109,7 @@ class BaseLibraryProvider(object): def refresh(self, uri=None): """ - See :meth:`mopidy.backends.base.BaseLibraryController.refresh`. + See :meth:`mopidy.backends.base.LibraryController.refresh`. *MUST be implemented by subclass.* """ @@ -117,7 +117,7 @@ class BaseLibraryProvider(object): def search(self, **query): """ - See :meth:`mopidy.backends.base.BaseLibraryController.search`. + See :meth:`mopidy.backends.base.LibraryController.search`. *MUST be implemented by subclass.* """ diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py index 00f8c9f2..62a04567 100644 --- a/mopidy/backends/dummy/__init__.py +++ b/mopidy/backends/dummy/__init__.py @@ -1,5 +1,5 @@ from mopidy.backends.base import (BaseBackend, CurrentPlaylistController, - BasePlaybackController, BasePlaybackProvider, BaseLibraryController, + BasePlaybackController, BasePlaybackProvider, LibraryController, BaseLibraryProvider, BaseStoredPlaylistsController, BaseStoredPlaylistsProvider) from mopidy.models import Playlist @@ -29,7 +29,7 @@ class DummyBackend(BaseBackend): self.current_playlist = CurrentPlaylistController(backend=self) library_provider = DummyLibraryProvider(backend=self) - self.library = BaseLibraryController(backend=self, + self.library = LibraryController(backend=self, provider=library_provider) playback_provider = DummyPlaybackProvider(backend=self) diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index 450ac7be..59ff1fde 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -2,8 +2,7 @@ import logging from mopidy import settings from mopidy.backends.base import (BaseBackend, CurrentPlaylistController, - BaseLibraryController, BasePlaybackController, - BaseStoredPlaylistsController) + LibraryController, BasePlaybackController, BaseStoredPlaylistsController) logger = logging.getLogger('mopidy.backends.libspotify') @@ -44,7 +43,7 @@ class LibspotifyBackend(BaseBackend): self.current_playlist = CurrentPlaylistController(backend=self) library_provider = LibspotifyLibraryProvider(backend=self) - self.library = BaseLibraryController(backend=self, + self.library = LibraryController(backend=self, provider=library_provider) playback_provider = LibspotifyPlaybackProvider(backend=self) diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 0c624687..ef42f4c4 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -6,7 +6,7 @@ import shutil from mopidy import settings from mopidy.backends.base import (BaseBackend, CurrentPlaylistController, - BaseLibraryController, BaseLibraryProvider, BasePlaybackController, + LibraryController, BaseLibraryProvider, BasePlaybackController, BasePlaybackProvider, BaseStoredPlaylistsController, BaseStoredPlaylistsProvider) from mopidy.models import Playlist, Track, Album @@ -35,7 +35,7 @@ class LocalBackend(BaseBackend): self.current_playlist = CurrentPlaylistController(backend=self) library_provider = LocalLibraryProvider(backend=self) - self.library = BaseLibraryController(backend=self, + self.library = LibraryController(backend=self, provider=library_provider) playback_provider = LocalPlaybackProvider(backend=self) diff --git a/tests/backends/base/library.py b/tests/backends/base/library.py index 1239bd08..71f62147 100644 --- a/tests/backends/base/library.py +++ b/tests/backends/base/library.py @@ -3,7 +3,7 @@ from mopidy.models import Playlist, Track, Album, Artist from tests import SkipTest, data_folder -class BaseLibraryControllerTest(object): +class LibraryControllerTest(object): artists = [Artist(name='artist1'), Artist(name='artist2'), Artist()] albums = [Album(name='album1', artists=artists[:1]), Album(name='album2', artists=artists[1:2]), diff --git a/tests/backends/local/library_test.py b/tests/backends/local/library_test.py index 34465d09..0c44924a 100644 --- a/tests/backends/local/library_test.py +++ b/tests/backends/local/library_test.py @@ -10,9 +10,9 @@ from mopidy import settings from mopidy.backends.local import LocalBackend from tests import data_folder -from tests.backends.base.library import BaseLibraryControllerTest +from tests.backends.base.library import LibraryControllerTest -class LocalLibraryControllerTest(BaseLibraryControllerTest, unittest.TestCase): +class LocalLibraryControllerTest(LibraryControllerTest, unittest.TestCase): backend_class = LocalBackend From fa4818095f3e97039203f3a6622463ef70c19a7a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 2 Nov 2010 23:56:15 +0100 Subject: [PATCH 059/111] Document 'provider' arg to LibraryController() --- mopidy/backends/base/library.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mopidy/backends/base/library.py b/mopidy/backends/base/library.py index 697d3554..f3477489 100644 --- a/mopidy/backends/base/library.py +++ b/mopidy/backends/base/library.py @@ -6,6 +6,8 @@ class LibraryController(object): """ :param backend: backend the controller is a part of :type backend: :class:`BaseBackend` + :param provider: provider the controller should use + :type provider: instance of :class:`BaseLibraryProvider` """ def __init__(self, backend, provider): From 76ce2a63450e5c4efbdab612ac10e9444f99b091 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 2 Nov 2010 23:58:10 +0100 Subject: [PATCH 060/111] Rename {Base => }StoredPlaylistController --- docs/api/backends/controllers.rst | 2 +- mopidy/backends/base/__init__.py | 4 ++-- mopidy/backends/base/stored_playlists.py | 16 ++++++++-------- mopidy/backends/dummy/__init__.py | 4 ++-- mopidy/backends/libspotify/__init__.py | 4 ++-- mopidy/backends/local/__init__.py | 4 ++-- tests/backends/base/stored_playlists.py | 2 +- tests/backends/local/stored_playlists_test.py | 4 ++-- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/api/backends/controllers.rst b/docs/api/backends/controllers.rst index d3346a27..97ce2508 100644 --- a/docs/api/backends/controllers.rst +++ b/docs/api/backends/controllers.rst @@ -50,7 +50,7 @@ Stored playlists controller Manages stored playlist. -.. autoclass:: mopidy.backends.base.BaseStoredPlaylistsController +.. autoclass:: mopidy.backends.base.StoredPlaylistsController :members: :undoc-members: diff --git a/mopidy/backends/base/__init__.py b/mopidy/backends/base/__init__.py index 529c2179..f1fdfcbe 100644 --- a/mopidy/backends/base/__init__.py +++ b/mopidy/backends/base/__init__.py @@ -11,7 +11,7 @@ from mopidy.utils import get_class from .current_playlist import CurrentPlaylistController from .library import LibraryController, BaseLibraryProvider from .playback import BasePlaybackController, BasePlaybackProvider -from .stored_playlists import (BaseStoredPlaylistsController, +from .stored_playlists import (StoredPlaylistsController, BaseStoredPlaylistsProvider) logger = logging.getLogger('mopidy.backends.base') @@ -57,7 +57,7 @@ class BaseBackend(object): playback = None #: The stored playlists controller. An instance of - #: :class:`mopidy.backends.base.BaseStoredPlaylistsController`. + #: :class:`mopidy.backends.base.StoredPlaylistsController`. stored_playlists = None #: List of URI prefixes this backend can handle. diff --git a/mopidy/backends/base/stored_playlists.py b/mopidy/backends/base/stored_playlists.py index bb86c92e..733ea46f 100644 --- a/mopidy/backends/base/stored_playlists.py +++ b/mopidy/backends/base/stored_playlists.py @@ -3,7 +3,7 @@ import logging logger = logging.getLogger('mopidy.backends.base') -class BaseStoredPlaylistsController(object): +class StoredPlaylistsController(object): """ :param backend: backend the controller is a part of :type backend: :class:`BaseBackend` @@ -94,7 +94,7 @@ class BaseStoredPlaylistsController(object): def refresh(self): """ Refresh the stored playlists in - :attr:`mopidy.backends.base.BaseStoredPlaylistsController.playlists`. + :attr:`mopidy.backends.base.StoredPlaylistsController.playlists`. """ return self.provider.refresh() @@ -152,7 +152,7 @@ class BaseStoredPlaylistsProvider(object): def create(self, name): """ - See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.create`. + See :meth:`mopidy.backends.base.StoredPlaylistsController.create`. *MUST be implemented by subclass.* """ @@ -160,7 +160,7 @@ class BaseStoredPlaylistsProvider(object): def delete(self, playlist): """ - See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.delete`. + See :meth:`mopidy.backends.base.StoredPlaylistsController.delete`. *MUST be implemented by subclass.* """ @@ -168,7 +168,7 @@ class BaseStoredPlaylistsProvider(object): def lookup(self, uri): """ - See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.lookup`. + See :meth:`mopidy.backends.base.StoredPlaylistsController.lookup`. *MUST be implemented by subclass.* """ @@ -176,7 +176,7 @@ class BaseStoredPlaylistsProvider(object): def refresh(self): """ - See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.refresh`. + See :meth:`mopidy.backends.base.StoredPlaylistsController.refresh`. *MUST be implemented by subclass.* """ @@ -184,7 +184,7 @@ class BaseStoredPlaylistsProvider(object): def rename(self, playlist, new_name): """ - See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.rename`. + See :meth:`mopidy.backends.base.StoredPlaylistsController.rename`. *MUST be implemented by subclass.* """ @@ -192,7 +192,7 @@ class BaseStoredPlaylistsProvider(object): def save(self, playlist): """ - See :meth:`mopidy.backends.base.BaseStoredPlaylistsController.save`. + See :meth:`mopidy.backends.base.StoredPlaylistsController.save`. *MUST be implemented by subclass.* """ diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py index 62a04567..9f3b3af1 100644 --- a/mopidy/backends/dummy/__init__.py +++ b/mopidy/backends/dummy/__init__.py @@ -1,6 +1,6 @@ from mopidy.backends.base import (BaseBackend, CurrentPlaylistController, BasePlaybackController, BasePlaybackProvider, LibraryController, - BaseLibraryProvider, BaseStoredPlaylistsController, + BaseLibraryProvider, StoredPlaylistsController, BaseStoredPlaylistsProvider) from mopidy.models import Playlist @@ -37,7 +37,7 @@ class DummyBackend(BaseBackend): provider=playback_provider) stored_playlists_provider = DummyStoredPlaylistsProvider(backend=self) - self.stored_playlists = BaseStoredPlaylistsController(backend=self, + self.stored_playlists = StoredPlaylistsController(backend=self, provider=stored_playlists_provider) self.uri_handlers = [u'dummy:'] diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index 59ff1fde..985a1243 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -2,7 +2,7 @@ import logging from mopidy import settings from mopidy.backends.base import (BaseBackend, CurrentPlaylistController, - LibraryController, BasePlaybackController, BaseStoredPlaylistsController) + LibraryController, BasePlaybackController, StoredPlaylistsController) logger = logging.getLogger('mopidy.backends.libspotify') @@ -52,7 +52,7 @@ class LibspotifyBackend(BaseBackend): stored_playlists_provider = LibspotifyStoredPlaylistsProvider( backend=self) - self.stored_playlists = BaseStoredPlaylistsController(backend=self, + self.stored_playlists = StoredPlaylistsController(backend=self, provider=stored_playlists_provider) self.uri_handlers = [u'spotify:', u'http://open.spotify.com/'] diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index ef42f4c4..44dc727b 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -7,7 +7,7 @@ import shutil from mopidy import settings from mopidy.backends.base import (BaseBackend, CurrentPlaylistController, LibraryController, BaseLibraryProvider, BasePlaybackController, - BasePlaybackProvider, BaseStoredPlaylistsController, + BasePlaybackProvider, StoredPlaylistsController, BaseStoredPlaylistsProvider) from mopidy.models import Playlist, Track, Album from mopidy.utils.process import pickle_connection @@ -43,7 +43,7 @@ class LocalBackend(BaseBackend): provider=playback_provider) stored_playlists_provider = LocalStoredPlaylistsProvider(backend=self) - self.stored_playlists = BaseStoredPlaylistsController(backend=self, + self.stored_playlists = StoredPlaylistsController(backend=self, provider=stored_playlists_provider) self.uri_handlers = [u'file://'] diff --git a/tests/backends/base/stored_playlists.py b/tests/backends/base/stored_playlists.py index 5bcd322c..0ac0b167 100644 --- a/tests/backends/base/stored_playlists.py +++ b/tests/backends/base/stored_playlists.py @@ -8,7 +8,7 @@ from mopidy.models import Playlist from tests import SkipTest, data_folder -class BaseStoredPlaylistsControllerTest(object): +class StoredPlaylistsControllerTest(object): def setUp(self): settings.LOCAL_PLAYLIST_PATH = tempfile.mkdtemp() settings.LOCAL_TAG_CACHE_FILE = data_folder('library_tag_cache') diff --git a/tests/backends/local/stored_playlists_test.py b/tests/backends/local/stored_playlists_test.py index 4db9e1e2..a7d9043f 100644 --- a/tests/backends/local/stored_playlists_test.py +++ b/tests/backends/local/stored_playlists_test.py @@ -16,10 +16,10 @@ from mopidy.utils.path import path_to_uri from tests import data_folder from tests.backends.base.stored_playlists import \ - BaseStoredPlaylistsControllerTest + StoredPlaylistsControllerTest from tests.backends.local import generate_song -class LocalStoredPlaylistsControllerTest(BaseStoredPlaylistsControllerTest, +class LocalStoredPlaylistsControllerTest(StoredPlaylistsControllerTest, unittest.TestCase): backend_class = LocalBackend From 3fe3ed27b9fba656d99facf4b6c8172a55acbb16 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 3 Nov 2010 00:01:32 +0100 Subject: [PATCH 061/111] Document copy method and update musicbrainz field doc --- mopidy/models.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/mopidy/models.py b/mopidy/models.py index c45d6769..60569004 100644 --- a/mopidy/models.py +++ b/mopidy/models.py @@ -38,15 +38,29 @@ class ImmutableObject(object): def __ne__(self, other): return not self.__eq__(other) - def copy(self, **kwargs): + def copy(self, **values): + """ + Copy the model with ``field`` updated to new value. + + Examples:: + + # Returns a track with a new name + Track(name='foo').copy(name='bar') + # Return an album with a new number of tracks + Album(num_tracks=2).copy(num_tracks=5) + + :param values: the model field to modify + :type values: dict + :rtype: new instance of the model being copied + """ data = {} for key in self.__dict__.keys(): public_key = key.lstrip('_') - data[public_key] = kwargs.pop(public_key, self.__dict__[key]) - for key in kwargs.keys(): + data[public_key] = values.pop(public_key, self.__dict__[key]) + for key in values.keys(): if hasattr(self, key): - data[key] = kwargs.pop(key) - if kwargs: + data[key] = values.pop(key) + if values: raise TypeError("copy() got an unexpected keyword argument '%s'" % key) return self.__class__(**data) @@ -56,7 +70,7 @@ class Artist(ImmutableObject): :type uri: string :param name: artist name :type name: string - :param musicbrainz_id: musicbrainz id + :param musicbrainz_id: MusicBrainz ID :type musicbrainz_id: string """ @@ -66,7 +80,7 @@ class Artist(ImmutableObject): #: The artist name. Read-only. name = None - #: The musicbrainz id of the artist. Read-only. + #: The MusicBrainz ID of the artist. Read-only. musicbrainz_id = None @@ -80,7 +94,7 @@ class Album(ImmutableObject): :type artists: list of :class:`Artist` :param num_tracks: number of tracks in album :type num_tracks: integer - :param musicbrainz_id: musicbrainz id + :param musicbrainz_id: MusicBrainz ID :type musicbrainz_id: string """ @@ -93,7 +107,7 @@ class Album(ImmutableObject): #: The number of tracks in the album. Read-only. num_tracks = 0 - #: The musicbrainz id of the album. Read-only. + #: The MusicBrainz ID of the album. Read-only. musicbrainz_id = None def __init__(self, *args, **kwargs): @@ -124,7 +138,7 @@ class Track(ImmutableObject): :type length: integer :param bitrate: bitrate in kbit/s :type bitrate: integer - :param musicbrainz_id: musicbrainz id + :param musicbrainz_id: MusicBrainz ID :type musicbrainz_id: string """ @@ -149,7 +163,7 @@ class Track(ImmutableObject): #: The track's bitrate in kbit/s. Read-only. bitrate = None - #: The musicbrainz id of the track. Read-only. + #: The MusicBrainz ID of the track. Read-only. musicbrainz_id = None def __init__(self, *args, **kwargs): From 9a1f16cba6999a2e67293d642a4e192a82dc64e5 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 00:03:08 +0100 Subject: [PATCH 062/111] Rename {Base => }Backend --- docs/api/backends/controllers.rst | 2 +- mopidy/backends/base/__init__.py | 2 +- mopidy/backends/base/current_playlist.py | 2 +- mopidy/backends/base/library.py | 4 ++-- mopidy/backends/base/playback.py | 4 ++-- mopidy/backends/base/stored_playlists.py | 4 ++-- mopidy/backends/dummy/__init__.py | 4 ++-- mopidy/backends/libspotify/__init__.py | 4 ++-- mopidy/backends/local/__init__.py | 4 ++-- mopidy/frontends/base.py | 2 +- mopidy/mixers/base.py | 2 +- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/api/backends/controllers.rst b/docs/api/backends/controllers.rst index 97ce2508..a48a472b 100644 --- a/docs/api/backends/controllers.rst +++ b/docs/api/backends/controllers.rst @@ -13,7 +13,7 @@ The backend controller API is the interface that is used by frontends like The backend =========== -.. autoclass:: mopidy.backends.base.BaseBackend +.. autoclass:: mopidy.backends.base.Backend :members: :undoc-members: diff --git a/mopidy/backends/base/__init__.py b/mopidy/backends/base/__init__.py index f1fdfcbe..ae668807 100644 --- a/mopidy/backends/base/__init__.py +++ b/mopidy/backends/base/__init__.py @@ -16,7 +16,7 @@ from .stored_playlists import (StoredPlaylistsController, logger = logging.getLogger('mopidy.backends.base') -class BaseBackend(object): +class Backend(object): """ :param core_queue: a queue for sending messages to :class:`mopidy.process.CoreProcess` diff --git a/mopidy/backends/base/current_playlist.py b/mopidy/backends/base/current_playlist.py index fec8e5a3..fe7d1de9 100644 --- a/mopidy/backends/base/current_playlist.py +++ b/mopidy/backends/base/current_playlist.py @@ -9,7 +9,7 @@ logger = logging.getLogger('mopidy.backends.base') class CurrentPlaylistController(object): """ :param backend: backend the controller is a part of - :type backend: :class:`BaseBackend` + :type backend: :class:`mopidy.backends.base.Backend` """ def __init__(self, backend): diff --git a/mopidy/backends/base/library.py b/mopidy/backends/base/library.py index f3477489..fd018b5f 100644 --- a/mopidy/backends/base/library.py +++ b/mopidy/backends/base/library.py @@ -5,7 +5,7 @@ logger = logging.getLogger('mopidy.backends.base') class LibraryController(object): """ :param backend: backend the controller is a part of - :type backend: :class:`BaseBackend` + :type backend: :class:`mopidy.backends.base.Backend` :param provider: provider the controller should use :type provider: instance of :class:`BaseLibraryProvider` """ @@ -79,7 +79,7 @@ class LibraryController(object): class BaseLibraryProvider(object): """ :param backend: backend the controller is a part of - :type backend: :class:`BaseBackend` + :type backend: :class:`mopidy.backends.base.Backend` """ def __init__(self, backend): diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index fa848845..727cce00 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -7,7 +7,7 @@ logger = logging.getLogger('mopidy.backends.base') class BasePlaybackController(object): """ :param backend: the backend - :type backend: :class:`BaseBackend` + :type backend: :class:`mopidy.backends.base.Backend` :param provider: provider the controller should use :type provider: instance of :class:`BasePlaybackProvider` """ @@ -488,7 +488,7 @@ class BasePlaybackController(object): class BasePlaybackProvider(object): """ :param backend: the backend - :type backend: :class:`BaseBackend` + :type backend: :class:`mopidy.backends.base.Backend` """ def __init__(self, backend): diff --git a/mopidy/backends/base/stored_playlists.py b/mopidy/backends/base/stored_playlists.py index 733ea46f..6578c046 100644 --- a/mopidy/backends/base/stored_playlists.py +++ b/mopidy/backends/base/stored_playlists.py @@ -6,7 +6,7 @@ logger = logging.getLogger('mopidy.backends.base') class StoredPlaylistsController(object): """ :param backend: backend the controller is a part of - :type backend: :class:`BaseBackend` + :type backend: :class:`mopidy.backends.base.Backend` :param provider: provider the controller should use :type provider: instance of :class:`BaseStoredPlaylistsProvider` """ @@ -122,7 +122,7 @@ class StoredPlaylistsController(object): class BaseStoredPlaylistsProvider(object): """ :param backend: backend the controller is a part of - :type backend: :class:`BaseBackend` + :type backend: :class:`mopidy.backends.base.Backend` """ def __init__(self, backend): diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py index 9f3b3af1..5106de14 100644 --- a/mopidy/backends/dummy/__init__.py +++ b/mopidy/backends/dummy/__init__.py @@ -1,4 +1,4 @@ -from mopidy.backends.base import (BaseBackend, CurrentPlaylistController, +from mopidy.backends.base import (Backend, CurrentPlaylistController, BasePlaybackController, BasePlaybackProvider, LibraryController, BaseLibraryProvider, StoredPlaylistsController, BaseStoredPlaylistsProvider) @@ -13,7 +13,7 @@ class DummyQueue(object): self.received_messages.append(message) -class DummyBackend(BaseBackend): +class DummyBackend(Backend): """ A backend which implements the backend API in the simplest way possible. Used in tests of the frontends. diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index 985a1243..00619a6c 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -1,14 +1,14 @@ import logging from mopidy import settings -from mopidy.backends.base import (BaseBackend, CurrentPlaylistController, +from mopidy.backends.base import (Backend, CurrentPlaylistController, LibraryController, BasePlaybackController, StoredPlaylistsController) logger = logging.getLogger('mopidy.backends.libspotify') ENCODING = 'utf-8' -class LibspotifyBackend(BaseBackend): +class LibspotifyBackend(Backend): """ A `Spotify `_ backend which uses the official `libspotify `_ diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 44dc727b..68654749 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -5,7 +5,7 @@ import os import shutil from mopidy import settings -from mopidy.backends.base import (BaseBackend, CurrentPlaylistController, +from mopidy.backends.base import (Backend, CurrentPlaylistController, LibraryController, BaseLibraryProvider, BasePlaybackController, BasePlaybackProvider, StoredPlaylistsController, BaseStoredPlaylistsProvider) @@ -16,7 +16,7 @@ from .translator import parse_m3u, parse_mpd_tag_cache logger = logging.getLogger(u'mopidy.backends.local') -class LocalBackend(BaseBackend): +class LocalBackend(Backend): """ A backend for playing music from a local music archive. diff --git a/mopidy/frontends/base.py b/mopidy/frontends/base.py index e15c4745..bf1c9bda 100644 --- a/mopidy/frontends/base.py +++ b/mopidy/frontends/base.py @@ -5,7 +5,7 @@ class BaseFrontend(object): :param core_queue: queue for messaging the core :type core_queue: :class:`multiprocessing.Queue` :param backend: the backend - :type backend: :class:`mopidy.backends.base.BaseBackend` + :type backend: :class:`mopidy.backends.base.Backend` """ def __init__(self, core_queue, backend): diff --git a/mopidy/mixers/base.py b/mopidy/mixers/base.py index 231154ee..f7f9525c 100644 --- a/mopidy/mixers/base.py +++ b/mopidy/mixers/base.py @@ -3,7 +3,7 @@ from mopidy import settings class BaseMixer(object): """ :param backend: a backend instance - :type mixer: :class:`mopidy.backends.base.BaseBackend` + :type backend: :class:`mopidy.backends.base.Backend` **Settings:** From fc9daa5a5a95df0ce2b9fadc2cc989cea9b9f120 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 00:06:32 +0100 Subject: [PATCH 063/111] Rename {Base => }PlaybackController --- docs/api/backends/controllers.rst | 2 +- mopidy/backends/base/__init__.py | 4 ++-- mopidy/backends/base/playback.py | 2 +- mopidy/backends/dummy/__init__.py | 4 ++-- mopidy/backends/libspotify/__init__.py | 4 ++-- mopidy/backends/local/__init__.py | 4 ++-- tests/backends/base/playback.py | 2 +- tests/backends/local/playback_test.py | 6 ++---- 8 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/api/backends/controllers.rst b/docs/api/backends/controllers.rst index a48a472b..88bb48ff 100644 --- a/docs/api/backends/controllers.rst +++ b/docs/api/backends/controllers.rst @@ -24,7 +24,7 @@ Playback controller Manages playback, with actions like play, pause, stop, next, previous, and seek. -.. autoclass:: mopidy.backends.base.BasePlaybackController +.. autoclass:: mopidy.backends.base.PlaybackController :members: :undoc-members: diff --git a/mopidy/backends/base/__init__.py b/mopidy/backends/base/__init__.py index ae668807..096a433f 100644 --- a/mopidy/backends/base/__init__.py +++ b/mopidy/backends/base/__init__.py @@ -10,7 +10,7 @@ from mopidy.utils import get_class from .current_playlist import CurrentPlaylistController from .library import LibraryController, BaseLibraryProvider -from .playback import BasePlaybackController, BasePlaybackProvider +from .playback import PlaybackController, BasePlaybackProvider from .stored_playlists import (StoredPlaylistsController, BaseStoredPlaylistsProvider) @@ -53,7 +53,7 @@ class Backend(object): mixer = None #: The playback controller. An instance of - #: :class:`mopidy.backends.base.BasePlaybackController`. + #: :class:`mopidy.backends.base.PlaybackController`. playback = None #: The stored playlists controller. An instance of diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 727cce00..8a3eeee5 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -4,7 +4,7 @@ import time logger = logging.getLogger('mopidy.backends.base') -class BasePlaybackController(object): +class PlaybackController(object): """ :param backend: the backend :type backend: :class:`mopidy.backends.base.Backend` diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py index 5106de14..9c6885bc 100644 --- a/mopidy/backends/dummy/__init__.py +++ b/mopidy/backends/dummy/__init__.py @@ -1,5 +1,5 @@ from mopidy.backends.base import (Backend, CurrentPlaylistController, - BasePlaybackController, BasePlaybackProvider, LibraryController, + PlaybackController, BasePlaybackProvider, LibraryController, BaseLibraryProvider, StoredPlaylistsController, BaseStoredPlaylistsProvider) from mopidy.models import Playlist @@ -33,7 +33,7 @@ class DummyBackend(Backend): provider=library_provider) playback_provider = DummyPlaybackProvider(backend=self) - self.playback = BasePlaybackController(backend=self, + self.playback = PlaybackController(backend=self, provider=playback_provider) stored_playlists_provider = DummyStoredPlaylistsProvider(backend=self) diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index 00619a6c..4d8b67d5 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -2,7 +2,7 @@ import logging from mopidy import settings from mopidy.backends.base import (Backend, CurrentPlaylistController, - LibraryController, BasePlaybackController, StoredPlaylistsController) + LibraryController, PlaybackController, StoredPlaylistsController) logger = logging.getLogger('mopidy.backends.libspotify') @@ -47,7 +47,7 @@ class LibspotifyBackend(Backend): provider=library_provider) playback_provider = LibspotifyPlaybackProvider(backend=self) - self.playback = BasePlaybackController(backend=self, + self.playback = PlaybackController(backend=self, provider=playback_provider) stored_playlists_provider = LibspotifyStoredPlaylistsProvider( diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 68654749..532c3976 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -6,7 +6,7 @@ import shutil from mopidy import settings from mopidy.backends.base import (Backend, CurrentPlaylistController, - LibraryController, BaseLibraryProvider, BasePlaybackController, + LibraryController, BaseLibraryProvider, PlaybackController, BasePlaybackProvider, StoredPlaylistsController, BaseStoredPlaylistsProvider) from mopidy.models import Playlist, Track, Album @@ -49,7 +49,7 @@ class LocalBackend(Backend): self.uri_handlers = [u'file://'] -class LocalPlaybackController(BasePlaybackController): +class LocalPlaybackController(PlaybackController): def __init__(self, *args, **kwargs): super(LocalPlaybackController, self).__init__(*args, **kwargs) diff --git a/tests/backends/base/playback.py b/tests/backends/base/playback.py index 7b6efe7a..26662f96 100644 --- a/tests/backends/base/playback.py +++ b/tests/backends/base/playback.py @@ -13,7 +13,7 @@ from tests.backends.base import populate_playlist # TODO Test 'playlist repeat', e.g. repeat=1,single=0 -class BasePlaybackControllerTest(object): +class PlaybackControllerTest(object): tracks = [] def setUp(self): diff --git a/tests/backends/local/playback_test.py b/tests/backends/local/playback_test.py index a84dfcde..2007cff8 100644 --- a/tests/backends/local/playback_test.py +++ b/tests/backends/local/playback_test.py @@ -12,12 +12,10 @@ from mopidy.models import Track from mopidy.utils.path import path_to_uri from tests import data_folder -from tests.backends.base.playback import BasePlaybackControllerTest +from tests.backends.base.playback import PlaybackControllerTest from tests.backends.local import generate_song -class LocalPlaybackControllerTest(BasePlaybackControllerTest, - unittest.TestCase): - +class LocalPlaybackControllerTest(PlaybackControllerTest, unittest.TestCase): backend_class = LocalBackend tracks = [Track(uri=generate_song(i), length=4464) for i in range(1, 4)] From b234c0553c3fb219d05b0fc3073e332c7d2e038f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 00:09:13 +0100 Subject: [PATCH 064/111] Fix docstring indentation --- mopidy/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mopidy/models.py b/mopidy/models.py index 60569004..915dc92a 100644 --- a/mopidy/models.py +++ b/mopidy/models.py @@ -49,9 +49,9 @@ class ImmutableObject(object): # Return an album with a new number of tracks Album(num_tracks=2).copy(num_tracks=5) - :param values: the model field to modify - :type values: dict - :rtype: new instance of the model being copied + :param values: the model fields to modify + :type values: dict + :rtype: new instance of the model being copied """ data = {} for key in self.__dict__.keys(): From 1ed29a4b9a65b6e971ad191b0bc1b91795bee136 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 00:11:46 +0100 Subject: [PATCH 065/111] Fix pylint warnings --- mopidy/frontends/mpd/translator.py | 6 ++++-- mopidy/models.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mopidy/frontends/mpd/translator.py b/mopidy/frontends/mpd/translator.py index 099b9551..3ead23c7 100644 --- a/mopidy/frontends/mpd/translator.py +++ b/mopidy/frontends/mpd/translator.py @@ -46,9 +46,11 @@ def track_to_mpd_format(track, position=None, cpid=None): # FIXME don't use first and best artist? # FIXME don't duplicate following code? if track.album is not None and track.album.artists: - artists = filter(lambda a: a.musicbrainz_id is not None, track.album.artists) + artists = filter(lambda a: a.musicbrainz_id is not None, + track.album.artists) if artists: - result.append(('MUSICBRAINZ_ALBUMARTISTID', artists[0].musicbrainz_id)) + result.append( + ('MUSICBRAINZ_ALBUMARTISTID', artists[0].musicbrainz_id)) if track.artists: artists = filter(lambda a: a.musicbrainz_id is not None, track.artists) if artists: diff --git a/mopidy/models.py b/mopidy/models.py index 915dc92a..8e7585f1 100644 --- a/mopidy/models.py +++ b/mopidy/models.py @@ -61,7 +61,8 @@ class ImmutableObject(object): if hasattr(self, key): data[key] = values.pop(key) if values: - raise TypeError("copy() got an unexpected keyword argument '%s'" % key) + raise TypeError("copy() got an unexpected keyword argument '%s'" + % key) return self.__class__(**data) class Artist(ImmutableObject): From 17c30c8bcd59526d5ed199bf851e0b3626896e56 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 00:17:53 +0100 Subject: [PATCH 066/111] docs: Update refs to BaseMixer --- docs/api/backends/controllers.rst | 2 +- docs/api/mixers.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api/backends/controllers.rst b/docs/api/backends/controllers.rst index 88bb48ff..28112cf7 100644 --- a/docs/api/backends/controllers.rst +++ b/docs/api/backends/controllers.rst @@ -32,7 +32,7 @@ seek. Mixer controller ================ -Manages volume. See :class:`mopidy.mixers.BaseMixer`. +Manages volume. See :class:`mopidy.mixers.base.BaseMixer`. Current playlist controller diff --git a/docs/api/mixers.rst b/docs/api/mixers.rst index 434637f3..6daa7a4e 100644 --- a/docs/api/mixers.rst +++ b/docs/api/mixers.rst @@ -24,10 +24,10 @@ enable one of the hardware device mixers, you must the set :attr:`mopidy.settings.MIXER` setting to point to one of the classes found below, and possibly add some extra settings required by the mixer you choose. -All mixers should subclass :class:`mopidy.mixers.BaseMixer` and override +All mixers should subclass :class:`mopidy.mixers.base.BaseMixer` and override methods as described below. -.. automodule:: mopidy.mixers +.. automodule:: mopidy.mixers.base :synopsis: Mixer API :members: :undoc-members: From 95aa739c96ede3fb5a2a4c6486fb2eb14eef6e56 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 00:35:27 +0100 Subject: [PATCH 067/111] Update changelog --- docs/api/backends/concepts.rst | 2 ++ docs/changes.rst | 43 ++++++++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/docs/api/backends/concepts.rst b/docs/api/backends/concepts.rst index fd7b4d13..0d476213 100644 --- a/docs/api/backends/concepts.rst +++ b/docs/api/backends/concepts.rst @@ -1,3 +1,5 @@ +.. _backend-concepts: + ********************************************** The backend, controller, and provider concepts ********************************************** diff --git a/docs/changes.rst b/docs/changes.rst index c3df7d85..add4cfc6 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -12,10 +12,45 @@ No description yet. **Changes** -- Install ``mopidy.desktop`` file that makes Mopidy available from e.g. Gnome - application menus. -- Add :command:`mopidy-scan` command to generate ``tag_cache`` files without - any help from the original MPD server. +- Packaging and distribution: + + - Install ``mopidy.desktop`` file that makes Mopidy available from e.g. Gnome + application menus. + - Create infrastructure for creating Debian packages of Mopidy. + +- Local backend: + + - Add :command:`mopidy-scan` command to generate ``tag_cache`` files without + any help from the original MPD server. + +- Models: + + - Rename and generalize ``Playlist._with(**kwargs)`` to + :meth:`mopidy.models.ImmutableObject.copy`. + +- Introduce the :ref:`provider concept `. Split the backend + API into a :ref:`backend controller API ` (for + frontend use) and a :ref:`backend provider API ` (for + backend implementation use), which includes the following changes: + + - Rename ``BaseBackend`` to :class:`mopidy.backends.base.Backend`. + - Rename ``BaseCurrentPlaylistController`` to + :class:`mopidy.backends.base.CurrentPlaylistController`. + - Split ``BaseLibraryController`` to + :class:`mopidy.backends.base.LibraryController` and + :class:`mopidy.backends.base.BaseLibraryProvider`. + - Split ``BasePlaybackController`` to + :class:`mopidy.backends.base.PlaybackController` and + :class:`mopidy.backends.base.BasePlaybackProvider`. + - Split ``BaseStoredPlaylistsController`` to + :class:`mopidy.backends.base.StoredPlaylistsController` and + :class:`mopidy.backends.base.BaseStoredPlaylistsProvider`. + +- Other API and package structure cleaning: + + - Move ``BaseMixer`` to :class:`mopidy.mixers.base.BaseMixer`. + - Add docs for the current non-stable output API, + :class:`mopidy.outputs.base.BaseOutput`. 0.2.0 (2010-10-24) From e4f006030b52b976bd51d2dd7312df9afb0b51d7 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 00:45:48 +0100 Subject: [PATCH 068/111] Update changelog --- docs/changes.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index add4cfc6..6c9f99f1 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -12,6 +12,19 @@ No description yet. **Changes** +- Settings: + + - Automatically expand ``~`` to the user's home directory in settings with + names ending in ``_PATH`` or ``_FILE``. + - Rename the following settings. The settings validator will warn you if you + need to change your local settings. + + - ``LOCAL_MUSIC_FOLDER`` to :attr:`mopidy.settings.LOCAL_MUSIC_PATH` + - ``LOCAL_PLAYLIST_FOLDER`` to + :attr:`mopidy.settings.LOCAL_PLAYLIST_PATH` + - ``LOCAL_TAG_CACHE`` to :attr:`mopidy.settings.LOCAL_TAG_CACHE_PATH` + - ``SPOTIFY_LIB_CACHE`` to :attr:`mopidy.settings.SPOTIFY_CACHE_PATH` + - Packaging and distribution: - Install ``mopidy.desktop`` file that makes Mopidy available from e.g. Gnome @@ -22,11 +35,14 @@ No description yet. - Add :command:`mopidy-scan` command to generate ``tag_cache`` files without any help from the original MPD server. + - Support UTF-8 encoded tag caches with non-ASCII characters. - Models: - Rename and generalize ``Playlist._with(**kwargs)`` to :meth:`mopidy.models.ImmutableObject.copy`. + - Add ``musicbrainz_id`` field to :class:`mopidy.models.Artist`, + :class:`mopidy.models.Album`, and :class:`mopidy.models.Track`. - Introduce the :ref:`provider concept `. Split the backend API into a :ref:`backend controller API ` (for From a44a86acc8f28070bef2e1a84452ec4529f77b75 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Nov 2010 00:51:36 +0100 Subject: [PATCH 069/111] Update changelog --- docs/changes.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 6c9f99f1..d957e608 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -14,15 +14,15 @@ No description yet. - Settings: - - Automatically expand ``~`` to the user's home directory in settings with - names ending in ``_PATH`` or ``_FILE``. + - Automatically expand ``~`` to the user's home directory and make the path + absolute for settings with names ending in ``_PATH`` or ``_FILE``. - Rename the following settings. The settings validator will warn you if you need to change your local settings. - ``LOCAL_MUSIC_FOLDER`` to :attr:`mopidy.settings.LOCAL_MUSIC_PATH` - ``LOCAL_PLAYLIST_FOLDER`` to :attr:`mopidy.settings.LOCAL_PLAYLIST_PATH` - - ``LOCAL_TAG_CACHE`` to :attr:`mopidy.settings.LOCAL_TAG_CACHE_PATH` + - ``LOCAL_TAG_CACHE`` to :attr:`mopidy.settings.LOCAL_TAG_CACHE_FILE` - ``SPOTIFY_LIB_CACHE`` to :attr:`mopidy.settings.SPOTIFY_CACHE_PATH` - Packaging and distribution: From 8b5c3cfadc75db6ca12947e5c836bb36ba22bf49 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 9 Nov 2010 23:51:12 +0100 Subject: [PATCH 070/111] docs: Update donations section --- docs/authors.rst | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/authors.rst b/docs/authors.rst index f56242a5..01e810e4 100644 --- a/docs/authors.rst +++ b/docs/authors.rst @@ -10,13 +10,20 @@ Contributors to Mopidy in the order of appearance: - Kristian Klette -Donations -========= +Showing your appreciation +========================= If you already enjoy Mopidy, or don't enjoy it and want to help us making -Mopidy better, you can `donate money `_ to -Mopidy's development. +Mopidy better, the best way to do so is to contribute back to the community. +You can contribute code, documentation, tests, bug reports, or help other +users, spreading the word, etc. + +If you want to show your appreciation in a less time consuming way, you can +`flattr us `_, or `donate money +`_ to Mopidy's development. + +We promise that any money donated -- to Pledgie, not Flattr, due to the size of +the amounts -- will be used to cover costs related to Mopidy development, like +service subscriptions (Spotify, Last.fm, etc.) and hardware devices like an +used iPod Touch for testing Mopidy with MPod. -Any donated money will be used to cover service subscriptions (e.g. Spotify -and Last.fm) and hardware devices (e.g. an used iPod Touch for testing Mopidy -with MPod) needed for developing Mopidy. From 97f31d3cadb4d6b1de10ba27145f01e21a7c88ad Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 19 Nov 2010 22:06:41 +0100 Subject: [PATCH 071/111] docs: Fix module headers --- docs/modules/frontends/lastfm.rst | 6 +++--- docs/modules/frontends/mpd.rst | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/modules/frontends/lastfm.rst b/docs/modules/frontends/lastfm.rst index bd3e218e..a726f4a2 100644 --- a/docs/modules/frontends/lastfm.rst +++ b/docs/modules/frontends/lastfm.rst @@ -1,6 +1,6 @@ -****************************** -:mod:`mopidy.frontends.lastfm` -****************************** +*************************************************** +:mod:`mopidy.frontends.lastfm` -- Last.fm Scrobbler +*************************************************** .. automodule:: mopidy.frontends.lastfm :synopsis: Last.fm scrobbler frontend diff --git a/docs/modules/frontends/mpd.rst b/docs/modules/frontends/mpd.rst index 6361e909..35128e70 100644 --- a/docs/modules/frontends/mpd.rst +++ b/docs/modules/frontends/mpd.rst @@ -1,6 +1,6 @@ -*************************** -:mod:`mopidy.frontends.mpd` -*************************** +***************************************** +:mod:`mopidy.frontends.mpd` -- MPD server +***************************************** .. automodule:: mopidy.frontends.mpd :synopsis: MPD frontend From f307f86620874abfcf37ab457a5b7f1efd23578b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 19 Nov 2010 22:38:19 +0100 Subject: [PATCH 072/111] docs: Update roadmap --- docs/development/roadmap.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/development/roadmap.rst b/docs/development/roadmap.rst index 2b0cf2ba..175c62ac 100644 --- a/docs/development/roadmap.rst +++ b/docs/development/roadmap.rst @@ -27,11 +27,11 @@ Possible targets for the next version - Write-support for Spotify, i.e. playlist management. - Virtual directories with e.g. starred tracks from Spotify. - - Support for 320 kbps audio. + - **[WIP: possibly v0.3]** Support for 320 kbps audio. - Local backend: - - Better library support. + - Better music library support. - **[DONE: v0.3]** A script for creating a tag cache. - An alternative to tag cache for caching metadata, i.e. Sqlite. @@ -43,7 +43,7 @@ Stuff we want to do, but not right now, and maybe never - Packaging and distribution: - - **[PENDING]** Create `Homebrew `_ + - **[BLOCKED]** Create `Homebrew `_ recipies for all our dependencies and Mopidy itself to make OS X installation a breeze. See `Homebrew's issue #1612 `_. @@ -68,8 +68,9 @@ Stuff we want to do, but not right now, and maybe never - Publish the server's presence to the network using `Zeroconf `_/Avahi. - **[WIP: feature/mpris-frontend]** D-Bus/`MPRIS `_ - - REST/JSON web service with a jQuery client as example application. Maybe - based upon `Tornado `_ and `jQuery + - **[WIP: feature/http-frontend]** REST/JSON web service with a jQuery client + as example application. Maybe based upon `Tornado + `_ and `jQuery Mobile `_. - DNLA/UPnP so Mopidy can be controlled from i.e. TVs. - `XMMS2 `_ From 133a22ef978108fdfd61706558729fee7c2a5925 Mon Sep 17 00:00:00 2001 From: Johannes Knutsen Date: Tue, 23 Nov 2010 00:26:23 +0100 Subject: [PATCH 073/111] add SPOTIFY_HIGH_BITRATE setting to enable streaming of Spotify's high bitrate streams --- mopidy/backends/libspotify/session_manager.py | 6 ++++++ mopidy/settings.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/mopidy/backends/libspotify/session_manager.py b/mopidy/backends/libspotify/session_manager.py index 5831b713..e70e8cfa 100644 --- a/mopidy/backends/libspotify/session_manager.py +++ b/mopidy/backends/libspotify/session_manager.py @@ -36,6 +36,12 @@ class LibspotifySessionManager(SpotifySessionManager, BaseThread): logger.info(u'Connected to Spotify') self.session = session self.connected.set() + if settings.SPOTIFY_HIGH_BITRATE: + logger.debug(u'Prefer high bitrate') + self.session.set_preferred_bitrate(1) + else: + logger.debug(u'Prefer normal bitrate') + self.session.set_preferred_bitrate(0) def logged_out(self, session): """Callback used by pyspotify""" diff --git a/mopidy/settings.py b/mopidy/settings.py index 4f60ee99..dc315064 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -183,3 +183,8 @@ SPOTIFY_USERNAME = u'' #: #: Used by :mod:`mopidy.backends.libspotify`. SPOTIFY_PASSWORD = u'' + +#: Do you prefer high bitrate (320k)? +#: +#: Used by :mod:`mopidy.backends.libspotify`. +SPOTIFY_HIGH_BITRATE = False From 975b264c2d70797184c806f65b03cf3549448895 Mon Sep 17 00:00:00 2001 From: Johannes Knutsen Date: Tue, 23 Nov 2010 00:33:14 +0100 Subject: [PATCH 074/111] set connection connected after preferred bitrate is set --- mopidy/backends/libspotify/session_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/backends/libspotify/session_manager.py b/mopidy/backends/libspotify/session_manager.py index e70e8cfa..8a79088f 100644 --- a/mopidy/backends/libspotify/session_manager.py +++ b/mopidy/backends/libspotify/session_manager.py @@ -35,13 +35,13 @@ class LibspotifySessionManager(SpotifySessionManager, BaseThread): """Callback used by pyspotify""" logger.info(u'Connected to Spotify') self.session = session - self.connected.set() if settings.SPOTIFY_HIGH_BITRATE: logger.debug(u'Prefer high bitrate') self.session.set_preferred_bitrate(1) else: logger.debug(u'Prefer normal bitrate') self.session.set_preferred_bitrate(0) + self.connected.set() def logged_out(self, session): """Callback used by pyspotify""" From 73ed9920c43d741751ba5c17318d94c5fd1bffe5 Mon Sep 17 00:00:00 2001 From: Johannes Knutsen Date: Tue, 23 Nov 2010 00:36:02 +0100 Subject: [PATCH 075/111] Document the default value on the Spotify preferred bitrate setting --- mopidy/settings.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mopidy/settings.py b/mopidy/settings.py index dc315064..1aaa4318 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -187,4 +187,8 @@ SPOTIFY_PASSWORD = u'' #: Do you prefer high bitrate (320k)? #: #: Used by :mod:`mopidy.backends.libspotify`. +# +#: Default:: +#: +#: SPOTIFY_HIGH_BITRATE = False # 160k SPOTIFY_HIGH_BITRATE = False From 9cfd1679795cff6cef83bd074d2f83d87f2df40f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 23 Nov 2010 00:56:10 +0100 Subject: [PATCH 076/111] Require upgrade of libspotify to 0.0.6, and pyspotify from the mopidy branch --- docs/changes.rst | 11 +++++++++++ docs/installation/libspotify.rst | 18 ++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index d957e608..3a030145 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -10,6 +10,12 @@ This change log is used to track all major changes to Mopidy. No description yet. +**Important changes** + +- If you use the Spotify backend, you need to upgrade to libspotify 0.0.6 and + the latest pyspotify from the Mopidy developers. Follow the instructions at + :ref:`/installation/libspotify/`. + **Changes** - Settings: @@ -31,6 +37,11 @@ No description yet. application menus. - Create infrastructure for creating Debian packages of Mopidy. +- Spotify backend: + + - Support high bitrate (320k). See + :attr:`mopidy.settings.SPOTIFY_HIGH_BITRATE` for details. + - Local backend: - Add :command:`mopidy-scan` command to generate ``tag_cache`` files without diff --git a/docs/installation/libspotify.rst b/docs/installation/libspotify.rst index b3ea06fa..9dc91066 100644 --- a/docs/installation/libspotify.rst +++ b/docs/installation/libspotify.rst @@ -5,7 +5,7 @@ libspotify installation Mopidy uses `libspotify `_ for playing music from the Spotify music service. To use :mod:`mopidy.backends.libspotify` you must -install libspotify and `pyspotify `_. +install libspotify and `pyspotify `_. .. warning:: @@ -22,14 +22,14 @@ install libspotify and `pyspotify `_. Installing libspotify on Linux ============================== -Download and install libspotify 0.0.4 for your OS and CPU architecture from +Download and install libspotify 0.0.6 for your OS and CPU architecture from https://developer.spotify.com/en/libspotify/. For 64-bit Linux the process is as follows:: - wget http://developer.spotify.com/download/libspotify/libspotify-0.0.4-linux6-x86_64.tar.gz - tar zxfv libspotify-0.0.4-linux6-x86_64.tar.gz - cd libspotify-0.0.4-linux6-x86_64/ + wget http://developer.spotify.com/download/libspotify/libspotify-0.0.6-linux6-x86_64.tar.gz + tar zxfv libspotify-0.0.6-linux6-x86_64.tar.gz + cd libspotify-0.0.6-linux6-x86_64/ sudo make install prefix=/usr/local sudo ldconfig @@ -46,6 +46,11 @@ libspotify:: brew install libspotify +To update your existing libspotify installation using Homebrew:: + + brew update + brew install `brew outdated` + When libspotify has been installed, continue with :ref:`pyspotify_installation`. @@ -69,7 +74,8 @@ In OS X no additional dependencies are needed. Check out the pyspotify code, and install it:: - git clone git://github.com/jodal/pyspotify.git + wget --no-check-certificate -O pyspotify.tar.gz https://github.com/mopidy/pyspotify/tarball/mopidy + tar zxfv pyspotify.tar.gz cd pyspotify/pyspotify/ sudo rm -rf build/ # If you are upgrading pyspotify sudo python setup.py install From 1c48c7ab2f758836358bd8b05f2cebec76ce49ce Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 23 Nov 2010 01:31:49 +0100 Subject: [PATCH 077/111] Show 320kbit bitrate in MPD if SPOTIFY_HIGH_BITRATE=True --- mopidy/backends/libspotify/translator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mopidy/backends/libspotify/translator.py b/mopidy/backends/libspotify/translator.py index ff8f3c5c..09303eda 100644 --- a/mopidy/backends/libspotify/translator.py +++ b/mopidy/backends/libspotify/translator.py @@ -2,8 +2,9 @@ import datetime as dt from spotify import Link -from mopidy.models import Artist, Album, Track, Playlist +from mopidy import settings from mopidy.backends.libspotify import ENCODING +from mopidy.models import Artist, Album, Track, Playlist class LibspotifyTranslator(object): @classmethod @@ -39,7 +40,7 @@ class LibspotifyTranslator(object): track_no=spotify_track.index(), date=date, length=spotify_track.duration(), - bitrate=160, + bitrate=(settings.SPOTIFY_HIGH_BITRATE and 320 or 160), ) @classmethod From 0b9d7bbd3f3a172d6d5acf83184568c074088fe9 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 23 Nov 2010 09:01:44 +0100 Subject: [PATCH 078/111] Switch main GitHub repo from jodal/mopidy to mopidy/mopidy --- README.rst | 6 +++--- docs/changes.rst | 2 +- docs/conf.py | 2 +- docs/installation/index.rst | 2 +- mopidy/backends/libspotify/__init__.py | 3 ++- mopidy/backends/local/__init__.py | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index c6187119..4f31fb59 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ To install Mopidy, check out * `Documentation (latest release) `_ * `Documentation (development version) `_ -* `Source code `_ -* `Issue tracker `_ +* `Source code `_ +* `Issue tracker `_ * IRC: ``#mopidy`` at `irc.freenode.net `_ -* `Download development snapshot `_ +* `Download development snapshot `_ diff --git a/docs/changes.rst b/docs/changes.rst index 3a030145..1c97ed68 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -426,7 +426,7 @@ Mopidy is working and usable. 0.1.0a0 is an alpha release, which basicly means we will still change APIs, add features, etc. before the final 0.1.0 release. But the software is usable as is, so we release it. Please give it a try and give us feedback, either at our IRC channel or through the `issue tracker -`_. Thanks! +`_. Thanks! **Changes** diff --git a/docs/conf.py b/docs/conf.py index 16a85975..9e7ff1fb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -202,4 +202,4 @@ latex_documents = [ needs_sphinx = '1.0' -extlinks = {'issue': ('http://github.com/jodal/mopidy/issues#issue/%s', 'GH-')} +extlinks = {'issue': ('http://github.com/mopidy/mopidy/issues#issue/%s', 'GH-')} diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 580ecd6d..3f0600f4 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -103,7 +103,7 @@ ckeckout:: sudo aptitude install git-core # On Ubuntu/Debian sudo brew install git # On OS X - git clone git://github.com/jodal/mopidy.git + git clone git://github.com/mopidy/mopidy.git cd mopidy/ python mopidy # Yes, 'mopidy' is a dir diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index 4d8b67d5..ad2926c7 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -15,7 +15,8 @@ class LibspotifyBackend(Backend): library and the `pyspotify `_ Python bindings for libspotify. - **Issues:** http://github.com/jodal/mopidy/issues/labels/backend-libspotify + **Issues:** + http://github.com/mopidy/mopidy/issues/labels/backend-libspotify **Settings:** diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 532c3976..e3e1d5dc 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -20,7 +20,7 @@ class LocalBackend(Backend): """ A backend for playing music from a local music archive. - **Issues:** http://github.com/jodal/mopidy/issues/labels/backend-local + **Issues:** http://github.com/mopidy/mopidy/issues/labels/backend-local **Settings:** From 2d7ac225673279421b1dcd86acc5e2ea28867870 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 23 Nov 2010 09:38:47 +0100 Subject: [PATCH 079/111] docs: Update all Git repo references --- README.rst | 6 +++--- docs/changes.rst | 2 +- docs/conf.py | 2 +- docs/development/contributing.rst | 2 +- docs/installation/index.rst | 2 +- mopidy/backends/libspotify/__init__.py | 3 ++- mopidy/backends/local/__init__.py | 2 +- tests/frontends/mpd/regression_test.py | 6 +++--- 8 files changed, 13 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index c6187119..4f31fb59 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ To install Mopidy, check out * `Documentation (latest release) `_ * `Documentation (development version) `_ -* `Source code `_ -* `Issue tracker `_ +* `Source code `_ +* `Issue tracker `_ * IRC: ``#mopidy`` at `irc.freenode.net `_ -* `Download development snapshot `_ +* `Download development snapshot `_ diff --git a/docs/changes.rst b/docs/changes.rst index eadf8e75..9e895419 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -351,7 +351,7 @@ Mopidy is working and usable. 0.1.0a0 is an alpha release, which basicly means we will still change APIs, add features, etc. before the final 0.1.0 release. But the software is usable as is, so we release it. Please give it a try and give us feedback, either at our IRC channel or through the `issue tracker -`_. Thanks! +`_. Thanks! **Changes** diff --git a/docs/conf.py b/docs/conf.py index d0d8f3af..bb9eb3ba 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -202,4 +202,4 @@ latex_documents = [ needs_sphinx = '1.0' -extlinks = {'issue': ('http://github.com/jodal/mopidy/issues#issue/%s', 'GH-')} +extlinks = {'issue': ('http://github.com/mopidy/mopidy/issues#issue/%s', 'GH-')} diff --git a/docs/development/contributing.rst b/docs/development/contributing.rst index 4adde637..a9cd8dc3 100644 --- a/docs/development/contributing.rst +++ b/docs/development/contributing.rst @@ -137,7 +137,7 @@ Then, to generate docs:: .. note:: The documentation at http://www.mopidy.com/ is automatically updated when a - documentation update is pushed to ``jodal/mopidy`` at GitHub. + documentation update is pushed to ``mopidy/mopidy`` at GitHub. Documentation generated from the ``master`` branch is published at http://www.mopidy.com/docs/master/, and will always be valid for the latest diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 9577c383..c3bbddce 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -88,7 +88,7 @@ development version of Mopidy:: sudo aptitude install git-core # On Ubuntu/Debian sudo brew install git # On OS X - git clone git://github.com/jodal/mopidy.git + git clone git://github.com/mopidy/mopidy.git cd mopidy/ sudo python setup.py install diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py index 223d9968..f4087043 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/libspotify/__init__.py @@ -14,7 +14,8 @@ class LibspotifyBackend(BaseBackend): library and the `pyspotify `_ Python bindings for libspotify. - **Issues:** http://github.com/jodal/mopidy/issues/labels/backend-libspotify + **Issues:** + http://github.com/mopidy/mopidy/issues/labels/backend-libspotify **Settings:** diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index e5bfe8f8..bb5e6a5e 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -17,7 +17,7 @@ class LocalBackend(BaseBackend): """ A backend for playing music from a local music archive. - **Issues:** http://github.com/jodal/mopidy/issues/labels/backend-local + **Issues:** http://github.com/mopidy/mopidy/issues/labels/backend-local **Settings:** diff --git a/tests/frontends/mpd/regression_test.py b/tests/frontends/mpd/regression_test.py index 3cfdb855..7e7163d8 100644 --- a/tests/frontends/mpd/regression_test.py +++ b/tests/frontends/mpd/regression_test.py @@ -8,7 +8,7 @@ from mopidy.models import Track class IssueGH17RegressionTest(unittest.TestCase): """ - The issue: http://github.com/jodal/mopidy/issues#issue/17 + The issue: http://github.com/mopidy/mopidy/issues#issue/17 How to reproduce: @@ -42,7 +42,7 @@ class IssueGH17RegressionTest(unittest.TestCase): class IssueGH18RegressionTest(unittest.TestCase): """ - The issue: http://github.com/jodal/mopidy/issues#issue/18 + The issue: http://github.com/mopidy/mopidy/issues#issue/18 How to reproduce: @@ -79,7 +79,7 @@ class IssueGH18RegressionTest(unittest.TestCase): class IssueGH22RegressionTest(unittest.TestCase): """ - The issue: http://github.com/jodal/mopidy/issues/#issue/22 + The issue: http://github.com/mopidy/mopidy/issues/#issue/22 How to reproduce: From 827940a97785af2c8232c3a1997d90909be4c7a9 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 23 Nov 2010 09:39:40 +0100 Subject: [PATCH 080/111] Update Git repo references in tests --- tests/frontends/mpd/regression_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/frontends/mpd/regression_test.py b/tests/frontends/mpd/regression_test.py index 3cfdb855..7e7163d8 100644 --- a/tests/frontends/mpd/regression_test.py +++ b/tests/frontends/mpd/regression_test.py @@ -8,7 +8,7 @@ from mopidy.models import Track class IssueGH17RegressionTest(unittest.TestCase): """ - The issue: http://github.com/jodal/mopidy/issues#issue/17 + The issue: http://github.com/mopidy/mopidy/issues#issue/17 How to reproduce: @@ -42,7 +42,7 @@ class IssueGH17RegressionTest(unittest.TestCase): class IssueGH18RegressionTest(unittest.TestCase): """ - The issue: http://github.com/jodal/mopidy/issues#issue/18 + The issue: http://github.com/mopidy/mopidy/issues#issue/18 How to reproduce: @@ -79,7 +79,7 @@ class IssueGH18RegressionTest(unittest.TestCase): class IssueGH22RegressionTest(unittest.TestCase): """ - The issue: http://github.com/jodal/mopidy/issues/#issue/22 + The issue: http://github.com/mopidy/mopidy/issues/#issue/22 How to reproduce: From 6d21f86a9e351abbae6fe9fb1b030ec6a4fa734c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 28 Dec 2010 14:27:09 +0100 Subject: [PATCH 081/111] Fix broken link --- docs/changes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index 1c97ed68..bd4f08ae 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -14,7 +14,7 @@ No description yet. - If you use the Spotify backend, you need to upgrade to libspotify 0.0.6 and the latest pyspotify from the Mopidy developers. Follow the instructions at - :ref:`/installation/libspotify/`. + :doc:`/installation/libspotify/`. **Changes** From f7b4f65e31640393bda643999e0e67f1e390b195 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 28 Dec 2010 14:30:36 +0100 Subject: [PATCH 082/111] Move 320k support to the important section of the changelog --- docs/changes.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index bd4f08ae..7bf1ebba 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -12,9 +12,15 @@ No description yet. **Important changes** -- If you use the Spotify backend, you need to upgrade to libspotify 0.0.6 and - the latest pyspotify from the Mopidy developers. Follow the instructions at - :doc:`/installation/libspotify/`. +- Spotify backend: + + - If you use the Spotify backend, you need to upgrade to libspotify 0.0.6 and + the latest pyspotify from the Mopidy developers. Follow the instructions at + :doc:`/installation/libspotify/`. + + - Support high bitrate (320k) audio. See + :attr:`mopidy.settings.SPOTIFY_HIGH_BITRATE` for details. + **Changes** @@ -37,11 +43,6 @@ No description yet. application menus. - Create infrastructure for creating Debian packages of Mopidy. -- Spotify backend: - - - Support high bitrate (320k). See - :attr:`mopidy.settings.SPOTIFY_HIGH_BITRATE` for details. - - Local backend: - Add :command:`mopidy-scan` command to generate ``tag_cache`` files without From 2379e772c7c39b47c314c774d4b59aff4d4a56fe Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 28 Dec 2010 14:34:34 +0100 Subject: [PATCH 083/111] Remove warning that is not needed when we don't pull pyspotify from Git --- docs/installation/libspotify.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/installation/libspotify.rst b/docs/installation/libspotify.rst index 9dc91066..692bda36 100644 --- a/docs/installation/libspotify.rst +++ b/docs/installation/libspotify.rst @@ -77,12 +77,4 @@ Check out the pyspotify code, and install it:: wget --no-check-certificate -O pyspotify.tar.gz https://github.com/mopidy/pyspotify/tarball/mopidy tar zxfv pyspotify.tar.gz cd pyspotify/pyspotify/ - sudo rm -rf build/ # If you are upgrading pyspotify sudo python setup.py install - -.. note:: - - The ``sudo rm -rf build/`` step is needed if you are upgrading pyspotify. - Simply running ``python setup.py clean`` will *not* clean out the C parts - of the ``build/`` directory, and you will thus not get any changes to the C - code included in your installation. From 62af31cbcae57fa2717942a86f0c63ab347fd041 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 28 Dec 2010 15:00:23 +0100 Subject: [PATCH 084/111] Doc how to install dev snapshot using pip --- docs/installation/index.rst | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 3f0600f4..96ba6027 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -61,18 +61,16 @@ Make sure you got the required dependencies installed. - pylast >= 4.3.0 -Install latest release -====================== +Install latest stable release +============================= -To install the currently latest release of Mopidy using ``pip``:: +To install the currently latest stable release of Mopidy using ``pip``:: sudo aptitude install python-setuptools python-pip # On Ubuntu/Debian sudo brew install pip # On OS X - sudo pip install mopidy + sudo pip install -U Mopidy -To later upgrade to the latest release:: - - sudo pip install -U mopidy +To later upgrade to the latest release, just rerun the last command. If you for some reason can't use ``pip``, try ``easy_install``. @@ -94,20 +92,21 @@ Next, you need to set a couple of :doc:`settings `, and then you're ready to :doc:`run Mopidy `. -Run from source code checkout -============================= +Track development using Git +=========================== -If you may want to contribute to Mopidy, and want access to other branches as -well, you can checkout the Mopidy source from Git and run it directly from the -ckeckout:: +If you want to contribute to Mopidy, you should install Mopidy using Git:: sudo aptitude install git-core # On Ubuntu/Debian sudo brew install git # On OS X git clone git://github.com/mopidy/mopidy.git - cd mopidy/ - python mopidy # Yes, 'mopidy' is a dir -To later update to the very latest version:: +You can then run Mopidy directly from the Git repository:: + + cd mopidy/ # Move into the Git repo dir + python mopidy # Run python on the mopidy source code dir + +To get the latest changes to Mopidy:: cd mopidy/ git pull From 18caae4b18a4e20d58aad8329b64be00ead94e9a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 28 Dec 2010 15:12:04 +0100 Subject: [PATCH 085/111] Simplify dependency list --- docs/installation/index.rst | 40 ++++++++++--------------------------- mopidy/mixers/alsa.py | 4 ++++ mopidy/mixers/osa.py | 13 +++++++++++- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 96ba6027..142c5d84 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -20,45 +20,25 @@ Install dependencies Make sure you got the required dependencies installed. - Python >= 2.6, < 3 -- :doc:`GStreamer ` >= 0.10, with Python bindings -- Dependencies for at least one Mopidy mixer: - - :mod:`mopidy.mixers.alsa` (Linux only) +- GStreamer >= 0.10, with Python bindings. See :doc:`gstreamer`. - - pyalsaaudio >= 0.2 (Debian/Ubuntu package: python-alsaaudio) - - - :mod:`mopidy.mixers.denon` (Linux, OS X, and Windows) - - - pyserial (Debian/Ubuntu package: python-serial) - - - *Default:* :mod:`mopidy.mixers.gstreamer_software` (Linux, OS X, and - Windows) - - - No additional dependencies. - - - :mod:`mopidy.mixers.nad` (Linux, OS X, and Windows) - - - pyserial (Debian/Ubuntu package: python-serial) - - - :mod:`mopidy.mixers.osa` (OS X only) - - - No additional dependencies. +- Mixer dependencies: The default mixer does not require any additional + dependencies. If you use another mixer, see the mixer's docs for any + additional requirements. - Dependencies for at least one Mopidy backend: - - *Default:* :mod:`mopidy.backends.libspotify` (Linux, OS X, and Windows) + - The default backend, :mod:`mopidy.backends.libspotify`, requires libspotify + and pyspotify. See :doc:`libspotify`. - - :doc:`libspotify and pyspotify ` - - - :mod:`mopidy.backends.local` (Linux, OS X, and Windows) - - - No additional dependencies. + - The local backend, :mod:`mopidy.backends.local`, requires no additional + dependencies. - Optional dependencies: - - :mod:`mopidy.frontends.lastfm` - - - pylast >= 4.3.0 + - To use the Last.FM scrobbler, see :mod:`mopidy.frontends.lastfm` for + additional requirements. Install latest stable release diff --git a/mopidy/mixers/alsa.py b/mopidy/mixers/alsa.py index f90060ce..4aa5952f 100644 --- a/mopidy/mixers/alsa.py +++ b/mopidy/mixers/alsa.py @@ -11,6 +11,10 @@ class AlsaMixer(BaseMixer): Mixer which uses the Advanced Linux Sound Architecture (ALSA) to control volume. + **Dependencies:** + + - pyalsaaudio >= 0.2 (python-alsaaudio on Debian/Ubuntu) + **Settings:** - :attr:`mopidy.settings.MIXER_ALSA_CONTROL` diff --git a/mopidy/mixers/osa.py b/mopidy/mixers/osa.py index 8d69eb47..2ea04cf2 100644 --- a/mopidy/mixers/osa.py +++ b/mopidy/mixers/osa.py @@ -4,7 +4,18 @@ import time from mopidy.mixers.base import BaseMixer class OsaMixer(BaseMixer): - """Mixer which uses ``osascript`` on OS X to control volume.""" + """ + Mixer which uses ``osascript`` on OS X to control volume. + + **Dependencies:** + + - None + + **Settings:** + + - None + + """ CACHE_TTL = 30 From ab6dbbb8fe41df9bc8e45feafe028ab7b65cea10 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 28 Dec 2010 15:19:15 +0100 Subject: [PATCH 086/111] Add note on the importancy of using pyspotify from the correct repo --- docs/installation/libspotify.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/installation/libspotify.rst b/docs/installation/libspotify.rst index 692bda36..b511e9c9 100644 --- a/docs/installation/libspotify.rst +++ b/docs/installation/libspotify.rst @@ -7,9 +7,9 @@ Mopidy uses `libspotify the Spotify music service. To use :mod:`mopidy.backends.libspotify` you must install libspotify and `pyspotify `_. -.. warning:: +.. note:: - This backend requires a `Spotify premium account + This backend requires a paid `Spotify premium account `_. .. note:: @@ -78,3 +78,8 @@ Check out the pyspotify code, and install it:: tar zxfv pyspotify.tar.gz cd pyspotify/pyspotify/ sudo python setup.py install + +It is important that you install pyspotify from the ``mopidy`` branch of the +``mopidy/pyspotify`` repository, as the upstream repository at +``winjer/pyspotify`` is not updated with changes needed to support e.g. +libspotify 0.0.6 and high bitrate audio. From 7743fa372b91e111abd2019e1260ce33cc753228 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 28 Dec 2010 15:31:10 +0100 Subject: [PATCH 087/111] Doc gst-launch test step. Fixes GH#35. --- docs/installation/gstreamer.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/installation/gstreamer.rst b/docs/installation/gstreamer.rst index ef66c673..ad2761a3 100644 --- a/docs/installation/gstreamer.rst +++ b/docs/installation/gstreamer.rst @@ -46,3 +46,19 @@ you should see a long listing of installed plugins, ending in a summary line:: $ gst-inspect-0.10 ... long list of installed plugins ... Total count: 218 plugins (1 blacklist entry not shown), 1031 features + +You should be able to produce a audible tone by running:: + + gst-launch-0.10 audiotestsrc ! autoaudiosink + +If you cannot hear any sound when running this command, you won't hear any +sound from Mopidy either, as Mopidy uses GStreamer's ``autoaudiosink`` to play +audio. Thus, make this work before you continue installing Mopidy. + + +Using a custom audio sink +========================= + +If you for some reason want to use some other GStreamer audio sink than +``autoaudiosink``, you can change :attr:`mopidy.settings.GSTREAMER_AUDIO_SINK` +in your ``settings.py`` file. From 71ede1a37b2f17f42d292096b124d846a7908905 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 28 Dec 2010 15:41:22 +0100 Subject: [PATCH 088/111] Update 0.2.0 docs to recommend libspotify 0.0.6 and mopidy/pyspotify over 0.0.4 and jodal/pyspotify --- docs/installation/libspotify.rst | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/docs/installation/libspotify.rst b/docs/installation/libspotify.rst index b3ea06fa..4860fc4b 100644 --- a/docs/installation/libspotify.rst +++ b/docs/installation/libspotify.rst @@ -22,14 +22,14 @@ install libspotify and `pyspotify `_. Installing libspotify on Linux ============================== -Download and install libspotify 0.0.4 for your OS and CPU architecture from +Download and install libspotify 0.0.6 for your OS and CPU architecture from https://developer.spotify.com/en/libspotify/. For 64-bit Linux the process is as follows:: - wget http://developer.spotify.com/download/libspotify/libspotify-0.0.4-linux6-x86_64.tar.gz - tar zxfv libspotify-0.0.4-linux6-x86_64.tar.gz - cd libspotify-0.0.4-linux6-x86_64/ + wget http://developer.spotify.com/download/libspotify/libspotify-0.0.6-linux6-x86_64.tar.gz + tar zxfv libspotify-0.0.6-linux6-x86_64.tar.gz + cd libspotify-0.0.6-linux6-x86_64/ sudo make install prefix=/usr/local sudo ldconfig @@ -67,16 +67,14 @@ Install pyspotify's dependencies. At Debian/Ubuntu systems:: In OS X no additional dependencies are needed. -Check out the pyspotify code, and install it:: +Get the pyspotify code, and install it:: - git clone git://github.com/jodal/pyspotify.git + wget --no-check-certificate -O pyspotify.tar.gz https://github.com/mopidy/pyspotify/tarball/mopidy + tar zxfv pyspotify.tar.gz cd pyspotify/pyspotify/ - sudo rm -rf build/ # If you are upgrading pyspotify sudo python setup.py install -.. note:: - - The ``sudo rm -rf build/`` step is needed if you are upgrading pyspotify. - Simply running ``python setup.py clean`` will *not* clean out the C parts - of the ``build/`` directory, and you will thus not get any changes to the C - code included in your installation. +It is important that you install pyspotify from the ``mopidy`` branch of the +``mopidy/pyspotify`` repository, as the upstream repository at +``winjer/pyspotify`` is not updated with changes needed to support e.g. +libspotify 0.0.6 and high bitrate audio. From 27f6e95e1f268439e8e6d700d1980810f32a4a42 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 28 Dec 2010 15:42:52 +0100 Subject: [PATCH 089/111] Minimize diff with master branch --- docs/installation/libspotify.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/libspotify.rst b/docs/installation/libspotify.rst index b511e9c9..afb54d16 100644 --- a/docs/installation/libspotify.rst +++ b/docs/installation/libspotify.rst @@ -72,7 +72,7 @@ Install pyspotify's dependencies. At Debian/Ubuntu systems:: In OS X no additional dependencies are needed. -Check out the pyspotify code, and install it:: +Get the pyspotify code, and install it:: wget --no-check-certificate -O pyspotify.tar.gz https://github.com/mopidy/pyspotify/tarball/mopidy tar zxfv pyspotify.tar.gz From 19683539ea30e2c20900299ff609c028c3666df9 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 31 Dec 2010 17:25:25 +0100 Subject: [PATCH 090/111] Make mopidy.desktop pass desktop-file-validate validation --- data/mopidy.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/mopidy.desktop b/data/mopidy.desktop index f5ca43bb..70257d58 100644 --- a/data/mopidy.desktop +++ b/data/mopidy.desktop @@ -7,4 +7,4 @@ Icon=audio-x-generic TryExec=mopidy Exec=mopidy Terminal=true -Categories=AudioVideo;Audio;Player;ConsoleOnly +Categories=AudioVideo;Audio;Player;ConsoleOnly; From e8aaefa5cb8e9b4c50df2d475edd04919555ac70 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 7 Jan 2011 19:04:41 +0100 Subject: [PATCH 091/111] Debian packaging has moved to its own repo --- debian/TODO | 14 -------------- debian/changelog | 5 ----- debian/compat | 1 - debian/control | 22 ---------------------- debian/copyright | 38 -------------------------------------- debian/docs | 2 -- debian/menu | 2 -- debian/pyversions | 1 - debian/rules | 27 --------------------------- debian/source/format | 1 - debian/watch | 2 -- 11 files changed, 115 deletions(-) delete mode 100644 debian/TODO delete mode 100644 debian/changelog delete mode 100644 debian/compat delete mode 100644 debian/control delete mode 100644 debian/copyright delete mode 100644 debian/docs delete mode 100644 debian/menu delete mode 100644 debian/pyversions delete mode 100755 debian/rules delete mode 100644 debian/source/format delete mode 100644 debian/watch diff --git a/debian/TODO b/debian/TODO deleted file mode 100644 index 4551dc55..00000000 --- a/debian/TODO +++ /dev/null @@ -1,14 +0,0 @@ -To do for Mopidy's Debian packaging -=================================== - -- Install data/mopidy.desktop into /usr/share/applications/ -- Add manpages for all commands. Build the manpages with Sphinx -- Make init script run Mopidy as a daemon -- Make init script run Mopidy with its own user -- Add support for reading settings from /etc/mopidy/settings.py -- Log to /var/log -- Cache files in /var/cache -- Package pyspotify and add it to Recommends -- Package pylast and add it to Recommends -- Create GPG key for signing the package -- Host the packages at PPA or apt.mopidy.com diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index a04f2e78..00000000 --- a/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -mopidy (0.2.0-1) unstable; urgency=low - - * Initial release - - -- Stein Magnus Jodal Sun, 31 Oct 2010 13:07:04 +0100 diff --git a/debian/compat b/debian/compat deleted file mode 100644 index 7f8f011e..00000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -7 diff --git a/debian/control b/debian/control deleted file mode 100644 index c2755717..00000000 --- a/debian/control +++ /dev/null @@ -1,22 +0,0 @@ -Source: mopidy -Section: sound -Priority: optional -Maintainer: Stein Magnus Jodal -Build-Depends: debhelper (>= 7.0.50~), python-support, python (>= 2.6), - python-sphinx (>= 1.0), python-pygraphviz -Standards-Version: 3.9.1 -Homepage: http://www.mopidy.com/ -Vcs-Git: git://github.com/jodal/mopidy.git -Vcs-Browser: http://github.com/jodal/mopidy - -Package: mopidy -Architecture: all -Depends: ${misc:Depends}, ${python:Depends}, python-gst0.10 -Recommends: gstreamer0.10-plugins-good, gstreamer0.10-plugins-ugly -Suggests: python-alsaaudio (>= 0.2), python-serial -Description: music server with MPD client support - Mopidy is a music server which can play music from Spotify or from your - local hard drive. To search for music in Spotify’s vast archive, manage - playlists, and play music, you can use most MPD clients. MPD clients are - available for most platforms, including Windows, Mac OS X, Linux, and - iPhone and Android phones. diff --git a/debian/copyright b/debian/copyright deleted file mode 100644 index c29416d6..00000000 --- a/debian/copyright +++ /dev/null @@ -1,38 +0,0 @@ -This work was packaged for Debian by: - - Stein Magnus Jodal on Sun, 31 Oct 2010 09:50:28 +0100 - -It was downloaded from: - - http://pypi.python.org/packages/source/M/Mopidy/Mopidy-0.2.0.tar.gz - -Upstream Author(s): - - Stein Magnus Jodal - -Copyright: - - Copyright 2009-2010 Stein Magnus Jodal and contributors - -License: - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied. See the License for the specific language governing - permissions and limitations under the License. - -On Debian systems, the complete text of the Apache version 2.0 license -can be found in "/usr/share/common-licenses/Apache-2.0". - -The Debian packaging is: - - Copyright 2010 Stein Magnus Jodal - -and is licensed under the Apache License, Version 2.0, see above. diff --git a/debian/docs b/debian/docs deleted file mode 100644 index a4b46448..00000000 --- a/debian/docs +++ /dev/null @@ -1,2 +0,0 @@ -README.rst -docs/_build/html/ diff --git a/debian/menu b/debian/menu deleted file mode 100644 index 6376a81e..00000000 --- a/debian/menu +++ /dev/null @@ -1,2 +0,0 @@ -?package(mopidy):needs="text" section="Applications/Sound"\ - title="Mopidy" command="/usr/bin/mopidy" diff --git a/debian/pyversions b/debian/pyversions deleted file mode 100644 index 0c043f18..00000000 --- a/debian/pyversions +++ /dev/null @@ -1 +0,0 @@ -2.6- diff --git a/debian/rules b/debian/rules deleted file mode 100755 index 926a81b1..00000000 --- a/debian/rules +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/make -f -# -*- makefile -*- -# Sample debian/rules that uses debhelper. -# This file was originally written by Joey Hess and Craig Small. -# As a special exception, when this file is copied by dh-make into a -# dh-make output file, you may use that output file without restriction. -# This special exception was added by Craig Small in version 0.37 of dh-make. - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -%: - dh $@ - -override_dh_clean: - make -C docs/ clean - dh_clean - -override_dh_installchangelogs: - dh_installchangelogs docs/changes.rst - -override_dh_installdocs: - make -C docs/ clean html - dh_installdocs - -.PHONY: override_dh_clean override_dh_installchangelogs \ - override_dh_installdocs override_dh_installinit diff --git a/debian/source/format b/debian/source/format deleted file mode 100644 index 163aaf8d..00000000 --- a/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/debian/watch b/debian/watch deleted file mode 100644 index 3d4d3a41..00000000 --- a/debian/watch +++ /dev/null @@ -1,2 +0,0 @@ -version=3 -http://pypi.python.org/packages/source/M/Mopidy/Mopidy-(.*)\.tar\.gz From 7dce103d9056d38a6500859a26f54069122db4fc Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 7 Jan 2011 20:18:40 +0100 Subject: [PATCH 092/111] Fix crash in Last.fm frontend if pylast was not installed or frontend not configured --- mopidy/frontends/lastfm.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mopidy/frontends/lastfm.py b/mopidy/frontends/lastfm.py index e91dd272..c4ea73c4 100644 --- a/mopidy/frontends/lastfm.py +++ b/mopidy/frontends/lastfm.py @@ -54,7 +54,8 @@ class LastfmFrontend(BaseFrontend): self.thread.destroy() def process_message(self, message): - self.connection.send(message) + if self.thread.is_alive(): + self.connection.send(message) class LastfmFrontendThread(BaseThread): @@ -68,7 +69,7 @@ class LastfmFrontendThread(BaseThread): def run_inside_try(self): self.setup() - while True: + while self.scrobbler is not None: self.connection.poll(None) message = self.connection.recv() self.process_message(message) From d32d4b2cad7750c5021428ac958da5025bfd00c1 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 7 Jan 2011 20:38:48 +0100 Subject: [PATCH 093/111] Release v0.2.1 --- docs/changes.rst | 13 +++++++++++++ mopidy/__init__.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index 9e895419..3232cfcc 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -5,6 +5,19 @@ Changes This change log is used to track all major changes to Mopidy. +0.2.1 (2011-01-07) +================== + +This is a maintenance release without any new features. + +**Bugfixes** + +- Fix crash in :mod:`mopidy.frontends.lastfm` which occurred at playback if + either :mod:`pylast` was not installed or the Last.fm scrobbling was not + correctly configured. The scrobbling thread now shuts properly down at + failure. + + 0.2.0 (2010-10-24) ================== diff --git a/mopidy/__init__.py b/mopidy/__init__.py index 5e1b26de..350fc8d7 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -3,7 +3,7 @@ if not (2, 6) <= sys.version_info < (3,): sys.exit(u'Mopidy requires Python >= 2.6, < 3') def get_version(): - return u'0.2.0' + return u'0.2.1' class MopidyException(Exception): def __init__(self, message, *args, **kwargs): From 2161e6cb94840e5d1345ab6ad63847aa5963e7ee Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 7 Jan 2011 22:52:11 +0100 Subject: [PATCH 094/111] docs: Remove empty section on installing libspotify on Windows --- docs/installation/libspotify.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/installation/libspotify.rst b/docs/installation/libspotify.rst index afb54d16..cb352b8c 100644 --- a/docs/installation/libspotify.rst +++ b/docs/installation/libspotify.rst @@ -55,12 +55,6 @@ When libspotify has been installed, continue with :ref:`pyspotify_installation`. -Install libspotify on Windows -============================= - -**TODO** Test and document installation on Windows. - - .. _pyspotify_installation: Installing pyspotify From 0212e7181b8de646d75e7d532e6178508d114e74 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 8 Jan 2011 01:10:49 +0100 Subject: [PATCH 095/111] docs: Darker background on pre tags --- docs/_themes/nature/static/nature.css_t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_themes/nature/static/nature.css_t b/docs/_themes/nature/static/nature.css_t index 63ef80d6..b6c0f22e 100644 --- a/docs/_themes/nature/static/nature.css_t +++ b/docs/_themes/nature/static/nature.css_t @@ -214,7 +214,7 @@ p.admonition-title:after { pre { padding: 10px; - background-color: #fafafa; + background-color: #eeeeee; color: #222222; line-height: 1.5em; font-size: 1.1em; From 7e0047a8b5d77f877a3d0bc5a16169d94a6bd357 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 8 Jan 2011 01:25:13 +0100 Subject: [PATCH 096/111] docs: Rewrite installation docs and include the APT archive alternative --- docs/installation/gstreamer.rst | 29 +++++-- docs/installation/index.rst | 136 ++++++++++++++++++++++++------- docs/installation/libspotify.rst | 53 ++++++++++-- 3 files changed, 173 insertions(+), 45 deletions(-) diff --git a/docs/installation/gstreamer.rst b/docs/installation/gstreamer.rst index ad2761a3..72d55908 100644 --- a/docs/installation/gstreamer.rst +++ b/docs/installation/gstreamer.rst @@ -5,23 +5,32 @@ GStreamer installation To use the Mopidy, you first need to install GStreamer and its Python bindings. -Installing GStreamer on Linux -============================= +Installing GStreamer +==================== -GStreamer is packaged for most popular Linux distributions. If you use -Debian/Ubuntu you can install GStreamer with Aptitude:: +On Linux +-------- - sudo aptitude install python-gst0.10 gstreamer0.10-plugins-good \ +GStreamer is packaged for most popular Linux distributions. Search for +GStreamer in your package manager, and make sure to install the Python +bindings, and the "good" and "ugly" plugin sets. + +If you use Debian/Ubuntu you can install GStreamer like this:: + + sudo apt-get install python-gst0.10 gstreamer0.10-plugins-good \ gstreamer0.10-plugins-ugly +If you install Mopidy from our APT archive, you don't need to install GStreamer +yourself. The Mopidy Debian package will handle it for you. -Installing GStreamer on OS X -============================ + +On OS X from Homebrew +--------------------- .. note:: We have created GStreamer formulas for Homebrew to make the GStreamer - installation easy for you, but our formulas has not been merged into + installation easy for you, but not all our formulas have been merged into Homebrew's master branch yet. You should either fetch the formula files from `Homebrew's issue #1612 `_ yourself, or fall @@ -31,6 +40,10 @@ To install GStreamer on OS X using Homebrew:: brew install gst-python gst-plugins-good gst-plugins-ugly + +On OS X from MacPorts +--------------------- + To install GStreamer on OS X using MacPorts:: sudo port install py26-gst-python gstreamer-plugins-good \ diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 142c5d84..d45ac1c9 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -2,10 +2,9 @@ Installation ************ -To get a basic version of Mopidy running, you need Python and the -:doc:`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. +There are several ways to install Mopidy. What way is best depends upon your +setup and whether you want to use stable releases or less stable development +versions. Install dependencies @@ -17,7 +16,10 @@ Install dependencies gstreamer libspotify -Make sure you got the required dependencies installed. +If you install Mopidy from the APT archive, as described below, you can skip +the dependency installation part. + +Otherwise, make sure you got the required dependencies installed. - Python >= 2.6, < 3 @@ -44,49 +46,125 @@ Make sure you got the required dependencies installed. Install latest stable release ============================= -To install the currently latest stable release of Mopidy using ``pip``:: - sudo aptitude install python-setuptools python-pip # On Ubuntu/Debian - sudo brew install pip # On OS X - sudo pip install -U Mopidy +From APT archive +---------------- -To later upgrade to the latest release, just rerun the last command. +If you run a Debian based Linux distribution, like Ubuntu, the easiest way to +install Mopidy is from the Mopidy APT archive. When installing from the APT +archive, you will automatically get updates to Mopidy in the same way as you +get updates to the rest of your distribution. -If you for some reason can't use ``pip``, try ``easy_install``. +#. Add the archive's GPG key:: -Next, you need to set a couple of :doc:`settings `, and then you're -ready to :doc:`run Mopidy `. + wget -q -O - http://apt.mopidy.com/mopidy.gpg | sudo apt-key add - + +#. Add the following to ``/etc/apt/sources.list``, or if you have the directory + ``/etc/apt/sources.list.d/``, add it to a file called ``mopidy.list`` in + that directory:: + + # Mopidy APT archive + deb http://apt.mopidy.com/ stable main contrib non-free + deb-src http://apt.mopidy.com/ stable main contrib non-free + +#. Install Mopidy and all dependencies:: + + sudo apt-get update + sudo apt-get install mopidy + +#. Next, you need to set a couple of :doc:`settings `, and then + you're ready to :doc:`run Mopidy `. + +When a new release is out, and you can't wait for you system to figure it out +for itself, run the following to force an upgrade:: + + sudo apt-get update + sudo apt-get dist-upgrade -Install development snapshot -============================ +From PyPI using Pip +------------------- -If you want to follow Mopidy development closer, you may install a snapshot of -Mopidy's ``develop`` branch:: +If you are on OS X or on Linux, but can't install from the APT archive, you can +install Mopidy from PyPI using Pip. - sudo aptitude install python-setuptools python-pip # On Ubuntu/Debian - sudo brew install pip # On OS X - sudo pip install mopidy==dev +#. When you install using Pip, you first need to ensure that all of Mopidy's + dependencies have been installed. See the section on dependencies above. -Next, you need to set a couple of :doc:`settings `, and then you're -ready to :doc:`run Mopidy `. +#. Then, you need to install Pip:: + + sudo aptitude install python-setuptools python-pip # On Ubuntu/Debian + sudo brew install pip # On OS X + +#. To install the currently latest stable release of Mopidy:: + + sudo pip install -U Mopidy + + To upgrade Mopidy to future releases, just rerun this command. + +#. Next, you need to set a couple of :doc:`settings `, and then + you're ready to :doc:`run Mopidy `. + +If you for some reason can't use Pip, try ``easy_install`` instead. -Track development using Git +Install development version =========================== -If you want to contribute to Mopidy, you should install Mopidy using Git:: +If you want to follow the development of Mopidy closer, you may install a +development version of Mopidy. These are not as stable as the releases, but +you'll get access to new features earlier and may help us by reporting issues. - sudo aptitude install git-core # On Ubuntu/Debian - sudo brew install git # On OS X - git clone git://github.com/mopidy/mopidy.git -You can then run Mopidy directly from the Git repository:: +From snapshot +------------- + +If you want to follow Mopidy development closer, you may install a snapshot of +Mopidy's ``develop`` branch. + +#. When you install using Pip, you first need to ensure that all of Mopidy's + dependencies have been installed. See the section on dependencies above. + +#. Then, you need to install Pip:: + + sudo aptitude install python-setuptools python-pip # On Ubuntu/Debian + sudo brew install pip # On OS X + +#. To install the latest snapshot of Mopidy, run:: + + sudo pip install mopidy==dev + + To upgrade Mopidy to future releases, just rerun this command. + +#. Next, you need to set a couple of :doc:`settings `, and then + you're ready to :doc:`run Mopidy `. + + +From Git +-------- + +If you want to contribute to Mopidy, you should install Mopidy using Git. + +#. When you install from Git, you first need to ensure that all of Mopidy's + dependencies have been installed. See the section on dependencies above. + +#. Then install Git, if haven't already:: + + sudo aptitude install git-core # On Ubuntu/Debian + sudo brew install git # On OS X + +#. Clone the official Mopidy repository, or your own fork of it:: + + git clone git://github.com/mopidy/mopidy.git + +#. Next, you need to set a couple of :doc:`settings `. + +#. You can then run Mopidy directly from the Git repository:: cd mopidy/ # Move into the Git repo dir python mopidy # Run python on the mopidy source code dir -To get the latest changes to Mopidy:: +#. Later, to get the latest changes to Mopidy:: cd mopidy/ git pull diff --git a/docs/installation/libspotify.rst b/docs/installation/libspotify.rst index cb352b8c..5d278fe2 100644 --- a/docs/installation/libspotify.rst +++ b/docs/installation/libspotify.rst @@ -19,8 +19,25 @@ install libspotify and `pyspotify `_. Spotify Group. -Installing libspotify on Linux -============================== +Installing libspotify +===================== + + +On Linux from APT archive +------------------------- + +If you run a Debian based Linux distribution, like Ubuntu, see +http://apt.mopidy.com/ for how to the Mopidy APT archive as a software source +on your installation. Then, simply run:: + + sudo apt-get install libspotify6 + +When libspotify has been installed, continue with +:ref:`pyspotify_installation`. + + +On Linux from source +-------------------- Download and install libspotify 0.0.6 for your OS and CPU architecture from https://developer.spotify.com/en/libspotify/. @@ -37,8 +54,8 @@ When libspotify has been installed, continue with :ref:`pyspotify_installation`. -Installing libspotify on OS X -============================= +On OS X from Homebrew +--------------------- In OS X you need to have `XCode `_ and `Homebrew `_ installed. Then, to install @@ -60,17 +77,37 @@ When libspotify has been installed, continue with Installing pyspotify ==================== -Install pyspotify's dependencies. At Debian/Ubuntu systems:: +When you've installed libspotify, it's time for making it available from Python +by installing pyspotify. - sudo aptitude install python-dev -In OS X no additional dependencies are needed. +On Linux from APT archive +------------------------- + +Assuming that you've already set up http://apt.mopidy.com/ as a software +source, run:: + + sudo apt-get install python-spotify + +If you haven't already installed libspotify, this command will install both +libspotify and pyspotify for you. + + +On Linux/OS X from source +------------------------- + +On Linux, you need to get the Python development files installed. On +Debian/Ubuntu systems run:: + + sudo apt-get install python-dev + +On OS X no additional dependencies are needed. Get the pyspotify code, and install it:: wget --no-check-certificate -O pyspotify.tar.gz https://github.com/mopidy/pyspotify/tarball/mopidy tar zxfv pyspotify.tar.gz - cd pyspotify/pyspotify/ + cd pyspotify/ sudo python setup.py install It is important that you install pyspotify from the ``mopidy`` branch of the From bb57ad9f60abfaf2e31b187c7644ac5341f842c7 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 8 Jan 2011 01:33:45 +0100 Subject: [PATCH 097/111] docs: Add note ArchLinux AUR package --- docs/installation/index.rst | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index d45ac1c9..df41f521 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -116,8 +116,8 @@ development version of Mopidy. These are not as stable as the releases, but you'll get access to new features earlier and may help us by reporting issues. -From snapshot -------------- +From snapshot using Pip +----------------------- If you want to follow Mopidy development closer, you may install a snapshot of Mopidy's ``develop`` branch. @@ -172,3 +172,10 @@ If you want to contribute to Mopidy, you should install Mopidy using Git. For an introduction to ``git``, please visit `git-scm.com `_. Also, please read our :doc:`developer documentation `. + + +From AUR on ArchLinux +--------------------- + +If you are running ArchLinux, you can install a development snapshot of Mopidy +using the package found at http://aur.archlinux.org/packages.php?ID=44026. From 35e35054f0aa041d5a570fada0a51a91010a00e2 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 8 Jan 2011 01:42:33 +0100 Subject: [PATCH 098/111] docs: Update development roadmap --- docs/development/roadmap.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/development/roadmap.rst b/docs/development/roadmap.rst index 175c62ac..9db74a4d 100644 --- a/docs/development/roadmap.rst +++ b/docs/development/roadmap.rst @@ -27,7 +27,7 @@ Possible targets for the next version - Write-support for Spotify, i.e. playlist management. - Virtual directories with e.g. starred tracks from Spotify. - - **[WIP: possibly v0.3]** Support for 320 kbps audio. + - **[DONE: v0.3]** Support for 320 kbps audio. - Local backend: @@ -47,7 +47,7 @@ Stuff we want to do, but not right now, and maybe never recipies for all our dependencies and Mopidy itself to make OS X installation a breeze. See `Homebrew's issue #1612 `_. - - **[WIP]** Create `Debian packages + - **[DONE]** 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. From 3989891de5d17fe91b7a7d4bcffa8350826e5e16 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 8 Jan 2011 02:47:16 +0100 Subject: [PATCH 099/111] docs: Better instructions for ArchLinux (thanks to sandsmark) --- docs/installation/index.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index df41f521..746648f4 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -179,3 +179,12 @@ From AUR on ArchLinux If you are running ArchLinux, you can install a development snapshot of Mopidy using the package found at http://aur.archlinux.org/packages.php?ID=44026. + +To install it, you can use ``packer``, ``yaourt``, or do it by hand like this:: + + wget http://aur.archlinux.org/packages/mopidy-git/mopidy-git.tar.gz + tar xf mopidy-git.tar.gz + cd mopidy-git/ + makepkg -si + +To upgrade Mopidy to future releases, just rerun ``makepkg``. From 6a29222751468ccc81dd8bdec6349382be83da7b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 8 Jan 2011 02:58:50 +0100 Subject: [PATCH 100/111] docs: Split ArchLinux instructions into several steps, like the others --- docs/installation/index.rst | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 746648f4..f6754371 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -180,11 +180,18 @@ From AUR on ArchLinux If you are running ArchLinux, you can install a development snapshot of Mopidy using the package found at http://aur.archlinux.org/packages.php?ID=44026. -To install it, you can use ``packer``, ``yaourt``, or do it by hand like this:: +#. First, you should consider installing any optional dependencies not included + by the AUR package, like required for e.g. Last.fm scrobbling. - wget http://aur.archlinux.org/packages/mopidy-git/mopidy-git.tar.gz - tar xf mopidy-git.tar.gz - cd mopidy-git/ - makepkg -si +#. To install Mopidy with GStreamer, libspotify and pyspotify, you can use + ``packer``, ``yaourt``, or do it by hand like this:: -To upgrade Mopidy to future releases, just rerun ``makepkg``. + wget http://aur.archlinux.org/packages/mopidy-git/mopidy-git.tar.gz + tar xf mopidy-git.tar.gz + cd mopidy-git/ + makepkg -si + + To upgrade Mopidy to future releases, just rerun ``makepkg``. + +#. Next, you need to set a couple of :doc:`settings `, and then + you're ready to :doc:`run Mopidy `. From dbbde5fdd6f685a7cfdf17c2f7c7328f296795e1 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 8 Jan 2011 03:48:16 +0100 Subject: [PATCH 101/111] Update Last.fm frontend to use pylast>=0.5 and the new Scrobbling 2.0 API --- docs/changes.rst | 7 +++++++ mopidy/frontends/lastfm.py | 32 +++++++++++++++----------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 4a8548bf..624f7ef4 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -21,6 +21,13 @@ No description yet. - Support high bitrate (320k) audio. See :attr:`mopidy.settings.SPOTIFY_HIGH_BITRATE` for details. +- Last.fm frontend: + + - If you use the Last.fm frontend, you need to upgrade to pylast 0.5. + + - Update to use Last.fm's new Scrobbling 2.0 API, as the old Submissions + Protocol 1.2.1 is deprecated. (Fixes: :issue:`33`) + **Changes** diff --git a/mopidy/frontends/lastfm.py b/mopidy/frontends/lastfm.py index ddd621f8..8d912f64 100644 --- a/mopidy/frontends/lastfm.py +++ b/mopidy/frontends/lastfm.py @@ -15,8 +15,8 @@ from mopidy.utils.process import BaseThread logger = logging.getLogger('mopidy.frontends.lastfm') -CLIENT_ID = u'mop' -CLIENT_VERSION = get_version() +API_KEY = '2236babefa8ebb3d93ea467560d00d04' +API_SECRET = '94d9a09c0cd5be955c4afaeaffcaefcd' # pylast raises UnicodeEncodeError on conversion from unicode objects to # ascii-encoded bytestrings, so we explicitly encode as utf-8 before passing @@ -34,7 +34,7 @@ class LastfmFrontend(BaseFrontend): **Dependencies:** - - `pylast `_ >= 0.4.30 + - `pylast `_ >= 0.5 **Settings:** @@ -64,12 +64,11 @@ class LastfmFrontendThread(BaseThread): self.name = u'LastfmFrontendThread' self.connection = connection self.lastfm = None - self.scrobbler = None self.last_start_time = None def run_inside_try(self): self.setup() - while self.scrobbler is not None: + while self.lastfm is not None: self.connection.poll(None) message = self.connection.recv() self.process_message(message) @@ -78,10 +77,9 @@ class LastfmFrontendThread(BaseThread): try: username = settings.LASTFM_USERNAME password_hash = pylast.md5(settings.LASTFM_PASSWORD) - self.lastfm = pylast.get_lastfm_network( + self.lastfm = pylast.LastFMNetwork( + api_key=API_KEY, api_secret=API_SECRET, username=username, password_hash=password_hash) - self.scrobbler = self.lastfm.get_scrobbler( - CLIENT_ID, CLIENT_VERSION) logger.info(u'Connected to Last.fm') except SettingsError as e: logger.info(u'Last.fm scrobbler not started') @@ -103,12 +101,13 @@ class LastfmFrontendThread(BaseThread): self.last_start_time = int(time.time()) logger.debug(u'Now playing track: %s - %s', artists, track.name) try: - self.scrobbler.report_now_playing( + self.lastfm.update_now_playing( artists.encode(ENCODING), track.name.encode(ENCODING), album=track.album.name.encode(ENCODING), - duration=duration, - track_number=track.track_no) + duration=str(duration), + track_number=str(track.track_no), + mbid=(track.musicbrainz_id or '').encode(ENCODING)) except (pylast.ScrobblingError, socket.error) as e: logger.warning(u'Last.fm now playing error: %s', e) @@ -127,14 +126,13 @@ class LastfmFrontendThread(BaseThread): self.last_start_time = int(time.time()) - duration logger.debug(u'Scrobbling track: %s - %s', artists, track.name) try: - self.scrobbler.scrobble( + self.lastfm.scrobble( artists.encode(ENCODING), track.name.encode(ENCODING), - time_started=self.last_start_time, - source=pylast.SCROBBLE_SOURCE_USER, - mode=pylast.SCROBBLE_MODE_PLAYED, - duration=duration, + str(self.last_start_time), album=track.album.name.encode(ENCODING), - track_number=track.track_no) + track_number=str(track.track_no), + duration=str(duration), + mbid=(track.musicbrainz_id or '').encode(ENCODING)) except (pylast.ScrobblingError, socket.error) as e: logger.warning(u'Last.fm scrobbling error: %s', e) From 85d4dde338c6b8b02bf52952fa24ed781e7c3079 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 9 Jan 2011 13:04:28 +0100 Subject: [PATCH 102/111] Forgot to update pylast version in requirements file --- requirements/lastfm.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/lastfm.txt b/requirements/lastfm.txt index 642735be..887a0f0d 100644 --- a/requirements/lastfm.txt +++ b/requirements/lastfm.txt @@ -1 +1 @@ -pylast >= 0.4.30 +pylast >= 0.5 From a14f114d2aed2f45a41ca8809da095f684b6ca2d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 9 Jan 2011 22:54:18 +0100 Subject: [PATCH 103/111] Fix crash for Spotify users with playlist folders --- docs/changes.rst | 4 ++++ mopidy/backends/libspotify/session_manager.py | 1 + mopidy/backends/libspotify/translator.py | 22 ++++++++++++++----- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 624f7ef4..72e9217b 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -21,6 +21,10 @@ No description yet. - Support high bitrate (320k) audio. See :attr:`mopidy.settings.SPOTIFY_HIGH_BITRATE` for details. + - Catch and log error caused by playlist folder boundaries being threated as + normal playlists. More permanent fix requires support for checking playlist + types in pyspotify. + - Last.fm frontend: - If you use the Last.fm frontend, you need to upgrade to pylast 0.5. diff --git a/mopidy/backends/libspotify/session_manager.py b/mopidy/backends/libspotify/session_manager.py index 8a79088f..f736a40e 100644 --- a/mopidy/backends/libspotify/session_manager.py +++ b/mopidy/backends/libspotify/session_manager.py @@ -54,6 +54,7 @@ class LibspotifySessionManager(SpotifySessionManager, BaseThread): for spotify_playlist in session.playlist_container(): playlists.append( LibspotifyTranslator.to_mopidy_playlist(spotify_playlist)) + playlists = filter(None, playlists) self.core_queue.put({ 'command': 'set_stored_playlists', 'playlists': playlists, diff --git a/mopidy/backends/libspotify/translator.py b/mopidy/backends/libspotify/translator.py index 09303eda..4a42cf97 100644 --- a/mopidy/backends/libspotify/translator.py +++ b/mopidy/backends/libspotify/translator.py @@ -1,11 +1,14 @@ import datetime as dt +import logging -from spotify import Link +from spotify import Link, SpotifyError from mopidy import settings from mopidy.backends.libspotify import ENCODING from mopidy.models import Artist, Album, Track, Playlist +logger = logging.getLogger('mopidy.backends.libspotify.translator') + class LibspotifyTranslator(object): @classmethod def to_mopidy_artist(cls, spotify_artist): @@ -47,8 +50,15 @@ class LibspotifyTranslator(object): 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], - ) + # FIXME Replace this try-except with a check on the playlist type, + # which is currently not supported by pyspotify, to avoid handling + # playlist folder boundaries like normal playlists. + try: + 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], + ) + except SpotifyError, e: + logger.warning(u'Failed translating Spotify playlist ' + '(probably a playlist folder boundary): %s', e) From 469e5fa189774b87e711b6480375175576c85eeb Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 9 Jan 2011 23:38:46 +0100 Subject: [PATCH 104/111] Fix volume setting for Droid MPD --- docs/changes.rst | 5 +++++ mopidy/frontends/mpd/protocol/playback.py | 5 +++++ tests/frontends/mpd/playback_test.py | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index 72e9217b..931e99cf 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -54,6 +54,11 @@ No description yet. application menus. - Create infrastructure for creating Debian packages of Mopidy. +- MPD frontend: + + - Support ``setvol 50`` without quotes around the argument. Fixes volume + control in Droid MPD. + - Local backend: - Add :command:`mopidy-scan` command to generate ``tag_cache`` files without diff --git a/mopidy/frontends/mpd/protocol/playback.py b/mopidy/frontends/mpd/protocol/playback.py index 2f5dd29e..13f7d2a8 100644 --- a/mopidy/frontends/mpd/protocol/playback.py +++ b/mopidy/frontends/mpd/protocol/playback.py @@ -320,6 +320,7 @@ def seekid(frontend, cpid, seconds): playid(frontend, cpid) frontend.backend.playback.seek(int(seconds) * 1000) +@handle_pattern(r'^setvol (?P[-+]*\d+)$') @handle_pattern(r'^setvol "(?P[-+]*\d+)"$') def setvol(frontend, volume): """ @@ -328,6 +329,10 @@ def setvol(frontend, volume): ``setvol {VOL}`` Sets volume to ``VOL``, the range of volume is 0-100. + + *Droid MPD:* + + - issues ``setvol 50`` without quotes around the argument. """ volume = int(volume) if volume < 0: diff --git a/tests/frontends/mpd/playback_test.py b/tests/frontends/mpd/playback_test.py index 4e60546d..f6e22641 100644 --- a/tests/frontends/mpd/playback_test.py +++ b/tests/frontends/mpd/playback_test.py @@ -104,6 +104,11 @@ class PlaybackOptionsHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) self.assertEqual(10, self.b.mixer.volume) + def test_setvol_without_quotes(self): + result = self.h.handle_request(u'setvol 50') + self.assert_(u'OK' in result) + self.assertEqual(50, self.b.mixer.volume) + def test_single_off(self): result = self.h.handle_request(u'single "0"') self.assertFalse(self.b.playback.single) From 3eb1d477651ef92c13da21995279c4ad1ae856ad Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 9 Jan 2011 23:46:03 +0100 Subject: [PATCH 105/111] Fix seek for Droid MPD --- docs/changes.rst | 2 ++ mopidy/frontends/mpd/protocol/playback.py | 5 +++++ tests/frontends/mpd/playback_test.py | 7 +++++++ 3 files changed, 14 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index 931e99cf..03044731 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -58,6 +58,8 @@ No description yet. - Support ``setvol 50`` without quotes around the argument. Fixes volume control in Droid MPD. + - Support ``seek 1 120`` without quotes around the arguments. Fixes seek in + Droid MPD. - Local backend: diff --git a/mopidy/frontends/mpd/protocol/playback.py b/mopidy/frontends/mpd/protocol/playback.py index 13f7d2a8..19922bc3 100644 --- a/mopidy/frontends/mpd/protocol/playback.py +++ b/mopidy/frontends/mpd/protocol/playback.py @@ -293,6 +293,7 @@ def replay_gain_status(frontend): """ return u'off' # TODO +@handle_pattern(r'^seek (?P\d+) (?P\d+)$') @handle_pattern(r'^seek "(?P\d+)" "(?P\d+)"$') def seek(frontend, songpos, seconds): """ @@ -302,6 +303,10 @@ def seek(frontend, songpos, seconds): Seeks to the position ``TIME`` (in seconds) of entry ``SONGPOS`` in the playlist. + + *Droid MPD:* + + - issues ``seek 1 120`` without quotes around the arguments. """ if frontend.backend.playback.current_playlist_position != songpos: playpos(frontend, songpos) diff --git a/tests/frontends/mpd/playback_test.py b/tests/frontends/mpd/playback_test.py index f6e22641..43614173 100644 --- a/tests/frontends/mpd/playback_test.py +++ b/tests/frontends/mpd/playback_test.py @@ -325,6 +325,13 @@ class PlaybackControlHandlerTest(unittest.TestCase): result = self.h.handle_request(u'seek "1" "30"') self.assertEqual(self.b.playback.current_track, seek_track) + def test_seek_without_quotes(self): + self.b.current_playlist.append([Track(length=40000)]) + self.h.handle_request(u'seek 0') + result = self.h.handle_request(u'seek 0 30') + self.assert_(u'OK' in result) + self.assert_(self.b.playback.time_position >= 30000) + def test_seekid(self): self.b.current_playlist.append([Track(length=40000)]) result = self.h.handle_request(u'seekid "0" "30"') From 8503fe7b9e32210eedf39cbc674c447be1f795ab Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 10 Jan 2011 16:08:05 +0100 Subject: [PATCH 106/111] pylast 0.5 seems to prefer unicode over utf-8 encoded bytestrings --- mopidy/frontends/lastfm.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/mopidy/frontends/lastfm.py b/mopidy/frontends/lastfm.py index 8d912f64..60c2d708 100644 --- a/mopidy/frontends/lastfm.py +++ b/mopidy/frontends/lastfm.py @@ -18,11 +18,6 @@ logger = logging.getLogger('mopidy.frontends.lastfm') API_KEY = '2236babefa8ebb3d93ea467560d00d04' API_SECRET = '94d9a09c0cd5be955c4afaeaffcaefcd' -# pylast raises UnicodeEncodeError on conversion from unicode objects to -# ascii-encoded bytestrings, so we explicitly encode as utf-8 before passing -# strings to pylast. -ENCODING = u'utf-8' - class LastfmFrontend(BaseFrontend): """ Frontend which scrobbles the music you play to your `Last.fm @@ -102,12 +97,12 @@ class LastfmFrontendThread(BaseThread): logger.debug(u'Now playing track: %s - %s', artists, track.name) try: self.lastfm.update_now_playing( - artists.encode(ENCODING), - track.name.encode(ENCODING), - album=track.album.name.encode(ENCODING), + artists, + track.name, + album=track.album.name, duration=str(duration), track_number=str(track.track_no), - mbid=(track.musicbrainz_id or '').encode(ENCODING)) + mbid=(track.musicbrainz_id or '')) except (pylast.ScrobblingError, socket.error) as e: logger.warning(u'Last.fm now playing error: %s', e) @@ -127,12 +122,12 @@ class LastfmFrontendThread(BaseThread): logger.debug(u'Scrobbling track: %s - %s', artists, track.name) try: self.lastfm.scrobble( - artists.encode(ENCODING), - track.name.encode(ENCODING), + artists, + track.name, str(self.last_start_time), - album=track.album.name.encode(ENCODING), + album=track.album.name, track_number=str(track.track_no), duration=str(duration), - mbid=(track.musicbrainz_id or '').encode(ENCODING)) + mbid=(track.musicbrainz_id or '')) except (pylast.ScrobblingError, socket.error) as e: logger.warning(u'Last.fm scrobbling error: %s', e) From 099544d9158c709fe051cb9b04ec6a7382d968b3 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 10 Jan 2011 16:45:44 +0100 Subject: [PATCH 107/111] Rename libspotify backend to simply 'spotify', as suggested by adamcik and knutz3n half a year ago --- docs/api/backends/providers.rst | 2 +- docs/changes.rst | 4 ++ docs/installation/index.rst | 2 +- docs/modules/backends/libspotify.rst | 7 --- docs/modules/backends/spotify.rst | 7 +++ .../{libspotify => spotify}/__init__.py | 49 +++++++++--------- .../{libspotify => spotify}/library.py | 10 ++-- .../{libspotify => spotify}/playback.py | 4 +- .../session_manager.py | 19 +++---- .../spotify_appkey.key | Bin .../stored_playlists.py | 2 +- .../{libspotify => spotify}/translator.py | 6 +-- mopidy/settings.py | 16 +++--- 13 files changed, 67 insertions(+), 61 deletions(-) delete mode 100644 docs/modules/backends/libspotify.rst create mode 100644 docs/modules/backends/spotify.rst rename mopidy/backends/{libspotify => spotify}/__init__.py (61%) rename mopidy/backends/{libspotify => spotify}/library.py (87%) rename mopidy/backends/{libspotify => spotify}/playback.py (91%) rename mopidy/backends/{libspotify => spotify}/session_manager.py (87%) rename mopidy/backends/{libspotify => spotify}/spotify_appkey.key (100%) rename mopidy/backends/{libspotify => spotify}/stored_playlists.py (84%) rename mopidy/backends/{libspotify => spotify}/translator.py (93%) diff --git a/docs/api/backends/providers.rst b/docs/api/backends/providers.rst index 9289dd06..903e220b 100644 --- a/docs/api/backends/providers.rst +++ b/docs/api/backends/providers.rst @@ -37,5 +37,5 @@ Backend provider implementations ================================ * :mod:`mopidy.backends.dummy` -* :mod:`mopidy.backends.libspotify` +* :mod:`mopidy.backends.spotify` * :mod:`mopidy.backends.local` diff --git a/docs/changes.rst b/docs/changes.rst index 03044731..ddb46bb8 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -21,6 +21,10 @@ No description yet. - Support high bitrate (320k) audio. See :attr:`mopidy.settings.SPOTIFY_HIGH_BITRATE` for details. + - Rename :mod:`mopidy.backends.libspotify` to :mod:`mopidy.backends.spotify`. + If you have set :attr:`mopidy.settings.BACKENDS` explicitly, you may need + to update the setting's value. + - Catch and log error caused by playlist folder boundaries being threated as normal playlists. More permanent fix requires support for checking playlist types in pyspotify. diff --git a/docs/installation/index.rst b/docs/installation/index.rst index f6754371..26b50994 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -31,7 +31,7 @@ Otherwise, make sure you got the required dependencies installed. - Dependencies for at least one Mopidy backend: - - The default backend, :mod:`mopidy.backends.libspotify`, requires libspotify + - The default backend, :mod:`mopidy.backends.spotify`, requires libspotify and pyspotify. See :doc:`libspotify`. - The local backend, :mod:`mopidy.backends.local`, requires no additional diff --git a/docs/modules/backends/libspotify.rst b/docs/modules/backends/libspotify.rst deleted file mode 100644 index e7528757..00000000 --- a/docs/modules/backends/libspotify.rst +++ /dev/null @@ -1,7 +0,0 @@ -******************************************************* -:mod:`mopidy.backends.libspotify` -- Libspotify backend -******************************************************* - -.. automodule:: mopidy.backends.libspotify - :synopsis: Spotify backend using the libspotify library - :members: diff --git a/docs/modules/backends/spotify.rst b/docs/modules/backends/spotify.rst new file mode 100644 index 00000000..938d6337 --- /dev/null +++ b/docs/modules/backends/spotify.rst @@ -0,0 +1,7 @@ +************************************************* +:mod:`mopidy.backends.spotify` -- Spotify backend +************************************************* + +.. automodule:: mopidy.backends.spotify + :synopsis: Backend for the Spotify music streaming service + :members: diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/spotify/__init__.py similarity index 61% rename from mopidy/backends/libspotify/__init__.py rename to mopidy/backends/spotify/__init__.py index ad2926c7..d36f6250 100644 --- a/mopidy/backends/libspotify/__init__.py +++ b/mopidy/backends/spotify/__init__.py @@ -4,54 +4,55 @@ from mopidy import settings from mopidy.backends.base import (Backend, CurrentPlaylistController, LibraryController, PlaybackController, StoredPlaylistsController) -logger = logging.getLogger('mopidy.backends.libspotify') +logger = logging.getLogger('mopidy.backends.spotify') ENCODING = 'utf-8' -class LibspotifyBackend(Backend): +class SpotifyBackend(Backend): """ - A `Spotify `_ backend which uses the official - `libspotify `_ - library and the `pyspotify `_ Python - bindings for libspotify. - - **Issues:** - http://github.com/mopidy/mopidy/issues/labels/backend-libspotify - - **Settings:** - - - :attr:`mopidy.settings.SPOTIFY_CACHE_PATH` - - :attr:`mopidy.settings.SPOTIFY_USERNAME` - - :attr:`mopidy.settings.SPOTIFY_PASSWORD` + A backend for playing music from the `Spotify `_ + music streaming service. The backend uses the official `libspotify + `_ library and the + `pyspotify `_ Python bindings for + libspotify. .. note:: This product uses SPOTIFY(R) CORE but is not endorsed, certified or otherwise approved in any way by Spotify. Spotify is the registered trade mark of the Spotify Group. + + **Issues:** + http://github.com/mopidy/mopidy/issues/labels/backend-spotify + + **Settings:** + + - :attr:`mopidy.settings.SPOTIFY_CACHE_PATH` + - :attr:`mopidy.settings.SPOTIFY_USERNAME` + - :attr:`mopidy.settings.SPOTIFY_PASSWORD` """ # Imports inside methods are to prevent loading of __init__.py to fail on # missing spotify dependencies. def __init__(self, *args, **kwargs): - from .library import LibspotifyLibraryProvider - from .playback import LibspotifyPlaybackProvider - from .stored_playlists import LibspotifyStoredPlaylistsProvider + from .library import SpotifyLibraryProvider + from .playback import SpotifyPlaybackProvider + from .stored_playlists import SpotifyStoredPlaylistsProvider - super(LibspotifyBackend, self).__init__(*args, **kwargs) + super(SpotifyBackend, self).__init__(*args, **kwargs) self.current_playlist = CurrentPlaylistController(backend=self) - library_provider = LibspotifyLibraryProvider(backend=self) + library_provider = SpotifyLibraryProvider(backend=self) self.library = LibraryController(backend=self, provider=library_provider) - playback_provider = LibspotifyPlaybackProvider(backend=self) + playback_provider = SpotifyPlaybackProvider(backend=self) self.playback = PlaybackController(backend=self, provider=playback_provider) - stored_playlists_provider = LibspotifyStoredPlaylistsProvider( + stored_playlists_provider = SpotifyStoredPlaylistsProvider( backend=self) self.stored_playlists = StoredPlaylistsController(backend=self, provider=stored_playlists_provider) @@ -61,11 +62,11 @@ class LibspotifyBackend(Backend): self.spotify = self._connect() def _connect(self): - from .session_manager import LibspotifySessionManager + from .session_manager import SpotifySessionManager logger.info(u'Mopidy uses SPOTIFY(R) CORE') logger.debug(u'Connecting to Spotify') - spotify = LibspotifySessionManager( + spotify = SpotifySessionManager( settings.SPOTIFY_USERNAME, settings.SPOTIFY_PASSWORD, core_queue=self.core_queue, output=self.output) diff --git a/mopidy/backends/libspotify/library.py b/mopidy/backends/spotify/library.py similarity index 87% rename from mopidy/backends/libspotify/library.py rename to mopidy/backends/spotify/library.py index 948c69b2..16391473 100644 --- a/mopidy/backends/libspotify/library.py +++ b/mopidy/backends/spotify/library.py @@ -4,13 +4,13 @@ import multiprocessing from spotify import Link, SpotifyError from mopidy.backends.base import BaseLibraryProvider -from mopidy.backends.libspotify import ENCODING -from mopidy.backends.libspotify.translator import LibspotifyTranslator +from mopidy.backends.spotify import ENCODING +from mopidy.backends.spotify.translator import SpotifyTranslator from mopidy.models import Playlist -logger = logging.getLogger('mopidy.backends.libspotify.library') +logger = logging.getLogger('mopidy.backends.spotify.library') -class LibspotifyLibraryProvider(BaseLibraryProvider): +class SpotifyLibraryProvider(BaseLibraryProvider): def find_exact(self, **query): return self.search(**query) @@ -20,7 +20,7 @@ class LibspotifyLibraryProvider(BaseLibraryProvider): # TODO Block until metadata_updated callback is called. Before that # the track will be unloaded, unless it's already in the stored # playlists. - return LibspotifyTranslator.to_mopidy_track(spotify_track) + return SpotifyTranslator.to_mopidy_track(spotify_track) except SpotifyError as e: logger.warning(u'Failed to lookup: %s', uri, e) return None diff --git a/mopidy/backends/libspotify/playback.py b/mopidy/backends/spotify/playback.py similarity index 91% rename from mopidy/backends/libspotify/playback.py rename to mopidy/backends/spotify/playback.py index 29409ff4..a066d90e 100644 --- a/mopidy/backends/libspotify/playback.py +++ b/mopidy/backends/spotify/playback.py @@ -4,9 +4,9 @@ from spotify import Link, SpotifyError from mopidy.backends.base import BasePlaybackProvider -logger = logging.getLogger('mopidy.backends.libspotify.playback') +logger = logging.getLogger('mopidy.backends.spotify.playback') -class LibspotifyPlaybackProvider(BasePlaybackProvider): +class SpotifyPlaybackProvider(BasePlaybackProvider): def pause(self): return self.backend.output.set_state('PAUSED') diff --git a/mopidy/backends/libspotify/session_manager.py b/mopidy/backends/spotify/session_manager.py similarity index 87% rename from mopidy/backends/libspotify/session_manager.py rename to mopidy/backends/spotify/session_manager.py index f736a40e..6ec6b4c9 100644 --- a/mopidy/backends/libspotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -2,28 +2,29 @@ import logging import os import threading -from spotify.manager import SpotifySessionManager +import spotify.manager from mopidy import get_version, settings -from mopidy.backends.libspotify.translator import LibspotifyTranslator +from mopidy.backends.spotify.translator import SpotifyTranslator from mopidy.models import Playlist from mopidy.utils.process import BaseThread -logger = logging.getLogger('mopidy.backends.libspotify.session_manager') +logger = logging.getLogger('mopidy.backends.spotify.session_manager') # pylint: disable = R0901 -# LibspotifySessionManager: Too many ancestors (9/7) +# SpotifySessionManager: Too many ancestors (9/7) -class LibspotifySessionManager(SpotifySessionManager, BaseThread): +class SpotifySessionManager(spotify.manager.SpotifySessionManager, BaseThread): cache_location = settings.SPOTIFY_CACHE_PATH settings_location = settings.SPOTIFY_CACHE_PATH 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): - SpotifySessionManager.__init__(self, username, password) + spotify.manager.SpotifySessionManager.__init__( + self, username, password) BaseThread.__init__(self, core_queue) - self.name = 'LibspotifySMThread' + self.name = 'SpotifySMThread' self.output = output self.connected = threading.Event() self.session = None @@ -53,7 +54,7 @@ class LibspotifySessionManager(SpotifySessionManager, BaseThread): playlists = [] for spotify_playlist in session.playlist_container(): playlists.append( - LibspotifyTranslator.to_mopidy_playlist(spotify_playlist)) + SpotifyTranslator.to_mopidy_playlist(spotify_playlist)) playlists = filter(None, playlists) self.core_queue.put({ 'command': 'set_stored_playlists', @@ -111,7 +112,7 @@ class LibspotifySessionManager(SpotifySessionManager, BaseThread): def callback(results, userdata=None): # TODO Include results from results.albums(), etc. too playlist = Playlist(tracks=[ - LibspotifyTranslator.to_mopidy_track(t) + SpotifyTranslator.to_mopidy_track(t) for t in results.tracks()]) connection.send(playlist) self.connected.wait() diff --git a/mopidy/backends/libspotify/spotify_appkey.key b/mopidy/backends/spotify/spotify_appkey.key similarity index 100% rename from mopidy/backends/libspotify/spotify_appkey.key rename to mopidy/backends/spotify/spotify_appkey.key diff --git a/mopidy/backends/libspotify/stored_playlists.py b/mopidy/backends/spotify/stored_playlists.py similarity index 84% rename from mopidy/backends/libspotify/stored_playlists.py rename to mopidy/backends/spotify/stored_playlists.py index 6f2a7aad..054e2bd1 100644 --- a/mopidy/backends/libspotify/stored_playlists.py +++ b/mopidy/backends/spotify/stored_playlists.py @@ -1,6 +1,6 @@ from mopidy.backends.base import BaseStoredPlaylistsProvider -class LibspotifyStoredPlaylistsProvider(BaseStoredPlaylistsProvider): +class SpotifyStoredPlaylistsProvider(BaseStoredPlaylistsProvider): def create(self, name): pass # TODO diff --git a/mopidy/backends/libspotify/translator.py b/mopidy/backends/spotify/translator.py similarity index 93% rename from mopidy/backends/libspotify/translator.py rename to mopidy/backends/spotify/translator.py index 4a42cf97..50ee07d1 100644 --- a/mopidy/backends/libspotify/translator.py +++ b/mopidy/backends/spotify/translator.py @@ -4,12 +4,12 @@ import logging from spotify import Link, SpotifyError from mopidy import settings -from mopidy.backends.libspotify import ENCODING +from mopidy.backends.spotify import ENCODING from mopidy.models import Artist, Album, Track, Playlist -logger = logging.getLogger('mopidy.backends.libspotify.translator') +logger = logging.getLogger('mopidy.backends.spotify.translator') -class LibspotifyTranslator(object): +class SpotifyTranslator(object): @classmethod def to_mopidy_artist(cls, spotify_artist): if not spotify_artist.is_loaded(): diff --git a/mopidy/settings.py b/mopidy/settings.py index 1aaa4318..23aa7cb6 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -12,12 +12,12 @@ Available settings and their default values. #: #: Default:: #: -#: BACKENDS = (u'mopidy.backends.libspotify.LibspotifyBackend',) +#: BACKENDS = (u'mopidy.backends.spotify.SpotifyBackend',) #: #: .. note:: #: Currently only the first backend in the list is used. BACKENDS = ( - u'mopidy.backends.libspotify.LibspotifyBackend', + u'mopidy.backends.spotify.SpotifyBackend', ) #: The log format used for informational logging. @@ -169,24 +169,24 @@ MPD_SERVER_HOSTNAME = u'127.0.0.1' #: Default: 6600 MPD_SERVER_PORT = 6600 -#: Path to the libspotify cache. +#: Path to the Spotify cache. #: -#: Used by :mod:`mopidy.backends.libspotify`. -SPOTIFY_CACHE_PATH = u'~/.mopidy/libspotify_cache' +#: Used by :mod:`mopidy.backends.spotify`. +SPOTIFY_CACHE_PATH = u'~/.mopidy/spotify_cache' #: Your Spotify Premium username. #: -#: Used by :mod:`mopidy.backends.libspotify`. +#: Used by :mod:`mopidy.backends.spotify`. SPOTIFY_USERNAME = u'' #: Your Spotify Premium password. #: -#: Used by :mod:`mopidy.backends.libspotify`. +#: Used by :mod:`mopidy.backends.spotify`. SPOTIFY_PASSWORD = u'' #: Do you prefer high bitrate (320k)? #: -#: Used by :mod:`mopidy.backends.libspotify`. +#: Used by :mod:`mopidy.backends.spotify`. # #: Default:: #: From c2c39acbe87735954d7591a8b1e676f8994b6942 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 10 Jan 2011 17:03:54 +0100 Subject: [PATCH 108/111] Tweak log messages --- mopidy/backends/spotify/session_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index 6ec6b4c9..c32148c3 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -37,10 +37,10 @@ class SpotifySessionManager(spotify.manager.SpotifySessionManager, BaseThread): logger.info(u'Connected to Spotify') self.session = session if settings.SPOTIFY_HIGH_BITRATE: - logger.debug(u'Prefer high bitrate') + logger.debug(u'Preferring high bitrate from Spotify') self.session.set_preferred_bitrate(1) else: - logger.debug(u'Prefer normal bitrate') + logger.debug(u'Preferring normal bitrate from Spotify') self.session.set_preferred_bitrate(0) self.connected.set() From feead97b5bf1fef8e1de6929538f0482eaef346e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 10 Jan 2011 22:13:06 +0100 Subject: [PATCH 109/111] Update reference to Spotify application key --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d77be3cd..d9d6af42 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ setup( author='Stein Magnus Jodal', author_email='stein.magnus@jodal.no', packages=packages, - package_data={'mopidy': ['backends/libspotify/spotify_appkey.key']}, + package_data={'mopidy': ['backends/spotify/spotify_appkey.key']}, cmdclass=cmdclasses, data_files=data_files, scripts=['bin/mopidy', 'bin/mopidy-scan'], From 9f5e97d14be6b0063357e96e301318eabcf2cd09 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 10 Jan 2011 23:18:12 +0100 Subject: [PATCH 110/111] Make all tests pass without having pyserial installed --- mopidy/mixers/denon.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mopidy/mixers/denon.py b/mopidy/mixers/denon.py index e6d752b6..f0712f95 100644 --- a/mopidy/mixers/denon.py +++ b/mopidy/mixers/denon.py @@ -1,8 +1,6 @@ import logging from threading import Lock -from serial import Serial - from mopidy import settings from mopidy.mixers.base import BaseMixer @@ -33,8 +31,11 @@ class DenonMixer(BaseMixer): """ super(DenonMixer, self).__init__(*args, **kwargs) device = kwargs.get('device', None) - self._device = device or Serial(port=settings.MIXER_EXT_PORT, - timeout=0.2) + if device: + self._device = device + else: + from serial import Serial + self._device = Serial(port=settings.MIXER_EXT_PORT, timeout=0.2) self._levels = ['99'] + ["%(#)02d" % {'#': v} for v in range(0, 99)] self._volume = 0 self._lock = Lock() From 65f8acb2873424ffee019ab546b2ebe0754b496e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 11 Jan 2011 22:09:51 +0100 Subject: [PATCH 111/111] Split Spotify playlist refreshing out of metadata updated callback --- mopidy/backends/spotify/session_manager.py | 26 +++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index c32148c3..9736f2eb 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -50,16 +50,8 @@ class SpotifySessionManager(spotify.manager.SpotifySessionManager, BaseThread): def metadata_updated(self, session): """Callback used by pyspotify""" - logger.debug(u'Metadata updated, refreshing stored playlists') - playlists = [] - for spotify_playlist in session.playlist_container(): - playlists.append( - SpotifyTranslator.to_mopidy_playlist(spotify_playlist)) - playlists = filter(None, playlists) - self.core_queue.put({ - 'command': 'set_stored_playlists', - 'playlists': playlists, - }) + logger.debug(u'Metadata updated') + self.refresh_stored_playlists() def connection_error(self, session, error): """Callback used by pyspotify""" @@ -107,6 +99,20 @@ class SpotifySessionManager(spotify.manager.SpotifySessionManager, BaseThread): logger.debug(u'End of data stream reached') self.output.end_of_data_stream() + def refresh_stored_playlists(self): + """Refresh the stored playlists in the backend with fresh meta data + from Spotify""" + playlists = [] + for spotify_playlist in self.session.playlist_container(): + playlists.append( + SpotifyTranslator.to_mopidy_playlist(spotify_playlist)) + playlists = filter(None, playlists) + self.core_queue.put({ + 'command': 'set_stored_playlists', + 'playlists': playlists, + }) + logger.debug(u'Refreshed %d stored playlist(s)', len(playlists)) + def search(self, query, connection): """Search method used by Mopidy backend""" def callback(results, userdata=None):