From f912164d399051ce5f0a9538460a92737c2fb975 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 17 Jul 2011 03:31:02 +0200 Subject: [PATCH 001/116] Quick rewrite to tee-less design --- mopidy/gstreamer.py | 34 ++++++++++++++-------------------- mopidy/outputs/__init__.py | 2 +- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index b5e38b92..1de6e000 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -36,7 +36,6 @@ class GStreamer(ThreadingActor): def __init__(self): self._pipeline = None self._source = None - self._tee = None self._uridecodebin = None self._volume = None self._outputs = [] @@ -54,13 +53,11 @@ class GStreamer(ThreadingActor): description = ' ! '.join([ 'uridecodebin name=uri', 'audioconvert name=convert', - 'volume name=volume', - 'tee name=tee']) + 'volume name=volume']) logger.debug(u'Setting up base GStreamer pipeline: %s', description) self._pipeline = gst.parse_launch(description) - self._tee = self._pipeline.get_by_name('tee') self._volume = self._pipeline.get_by_name('volume') self._uridecodebin = self._pipeline.get_by_name('uri') @@ -70,7 +67,8 @@ class GStreamer(ThreadingActor): def _setup_outputs(self): for output in settings.OUTPUTS: - get_class(output)(self).connect() + self._outputs.append(get_class(output)(self)) + self._outputs[0].connect() def _setup_message_processor(self): bus = self._pipeline.get_bus() @@ -304,9 +302,8 @@ class GStreamer(ThreadingActor): """ self._pipeline.add(output) output.sync_state_with_parent() # Required to add to running pipe - gst.element_link_many(self._tee, output) - self._outputs.append(output) - logger.debug('GStreamer added %s', output.get_name()) + gst.element_link_many(self._volume, output) + logger.debug('Output set to %s', output.get_name()) def list_outputs(self): """ @@ -323,26 +320,23 @@ class GStreamer(ThreadingActor): :param output: output to remove from the pipeline :type output: :class:`gst.Bin` """ - if output not in self._outputs: - raise LookupError('Ouput %s not present in pipeline' - % output.get_name) - teesrc = output.get_pad('sink').get_peer() - handler = teesrc.add_event_probe(self._handle_event_probe) + peersrc = output.get_pad('sink').get_peer() + handler = peersrc.add_event_probe(self._handle_event_probe) - struct = gst.Structure('mopidy-unlink-tee') + struct = gst.Structure('mopidy-unlink') struct.set_value('handler', handler) event = gst.event_new_custom(gst.EVENT_CUSTOM_DOWNSTREAM, struct) - self._tee.send_event(event) + self._volume.send_event(event) - def _handle_event_probe(self, teesrc, event): - if event.type == gst.EVENT_CUSTOM_DOWNSTREAM and event.has_name('mopidy-unlink-tee'): + def _handle_event_probe(self, srcpad, event): + if event.type == gst.EVENT_CUSTOM_DOWNSTREAM and event.has_name('mopidy-unlink'): data = self._get_structure_data(event.get_structure()) - output = teesrc.get_peer().get_parent() + output = srcpad.get_peer().get_parent() - teesrc.unlink(teesrc.get_peer()) - teesrc.remove_event_probe(data['handler']) + srcpad.unlink(srcpad.get_peer()) + srcpad.remove_event_probe(data['handler']) output.set_state(gst.STATE_NULL) self._pipeline.remove(output) diff --git a/mopidy/outputs/__init__.py b/mopidy/outputs/__init__.py index ba242c4b..d94c0727 100644 --- a/mopidy/outputs/__init__.py +++ b/mopidy/outputs/__init__.py @@ -21,7 +21,7 @@ class BaseOutput(object): self.modify_bin() def _build_bin(self): - description = 'queue ! %s' % self.describe_bin() + description = self.describe_bin() logger.debug('Creating new output: %s', description) return gst.parse_bin_from_description(description, True) From b2ccdec9603a26e5efeb557db69d8aab23aa2e29 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 17 Jul 2011 04:14:21 +0200 Subject: [PATCH 002/116] Rip out rest of code that supported simulatnous outputs --- docs/changes.rst | 9 +++- docs/installation/gstreamer.rst | 4 +- docs/settings.rst | 2 +- mopidy/gstreamer.py | 96 ++------------------------------- mopidy/outputs/__init__.py | 29 +--------- mopidy/outputs/shoutcast.py | 16 ------ mopidy/settings.py | 8 +-- 7 files changed, 19 insertions(+), 145 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 5506bfb0..b0d320eb 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -17,10 +17,17 @@ v0.6.0 (in development) - Replace :attr:`mopidy.backends.base.Backend.uri_handlers` with :attr:`mopidy.backends.base.Backend.uri_schemes`, which just takes the part up to the colon of an URI, and not any prefix. + - Add Listener API, :mod:`mopidy.listeners`, to be implemented by actors wanting to receive events from the backend. This is a formalization of the ad hoc events the Last.fm scrobbler has already been using for some time. -- Fix metadata update in Shoutcast streaming (Fixes: :issue:`122`) + +- Fix metadata update in Shoutcast streaming (Fixes: :issue:`122`) + +- Multiple simultaneously playing outputs was considered more trouble than what + it is worth maintnance wise. Thus, this feature has been axed for now. + Switching outputs is still posible, but only one can be active at a time, and + it is still the case that switching during playback does not funtion. v0.5.0 (2011-06-15) diff --git a/docs/installation/gstreamer.rst b/docs/installation/gstreamer.rst index 08e16378..8f2ea07e 100644 --- a/docs/installation/gstreamer.rst +++ b/docs/installation/gstreamer.rst @@ -73,8 +73,8 @@ Using a custom audio sink ========================= If you for some reason want to use some other GStreamer audio sink than -``autoaudiosink``, you can add ``mopidy.outputs.custom.CustomOutput`` to the -:attr:`mopidy.settings.OUTPUTS` setting, and set the +``autoaudiosink``, you can set :attr:`mopidy.settings.OUTPUTS` to +``mopidy.outputs.custom.CustomOutput``, and set the :attr:`mopidy.settings.CUSTOM_OUTPUT` setting to a partial GStreamer pipeline description describing the GStreamer sink you want to use. diff --git a/docs/settings.rst b/docs/settings.rst index 68adfd55..d3c9015e 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -131,7 +131,7 @@ server simultaneously. To use the SHOUTcast output, do the following: #. Install, configure and start the Icecast server. It can be found in the ``icecast2`` package in Debian/Ubuntu. -#. Add ``mopidy.outputs.shoutcast.ShoutcastOutput`` output to the +#. Set ``mopidy.outputs.shoutcast.ShoutcastOutput`` as the first output in the :attr:`mopidy.settings.OUTPUTS` setting. #. Check the default values for the following settings, and alter them to match diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 1de6e000..b43089e0 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -29,7 +29,7 @@ class GStreamer(ThreadingActor): **Settings:** - - :attr:`mopidy.settings.OUTPUTS` + - :attr:`mopidy.settings.OUTPUT` """ @@ -66,9 +66,9 @@ class GStreamer(ThreadingActor): self._pipeline.get_by_name('convert').get_pad('sink')) def _setup_outputs(self): - for output in settings.OUTPUTS: - self._outputs.append(get_class(output)(self)) - self._outputs[0].connect() + for klass in settings.OUTPUTS: + self._outputs.append(get_class(klass)()) + self.connect_output(self._outputs[0].bin) def _setup_message_processor(self): bus = self._pipeline.get_bus() @@ -87,10 +87,6 @@ class GStreamer(ThreadingActor): pad.link(target_pad) def _on_message(self, bus, message): - if message.src in self._handlers: - if self._handlers[message.src](message): - return # Message was handeled by output - if message.type == gst.MESSAGE_EOS: logger.debug(u'GStreamer signalled end-of-stream. ' 'Telling backend ...') @@ -305,86 +301,4 @@ class GStreamer(ThreadingActor): gst.element_link_many(self._volume, output) logger.debug('Output set to %s', output.get_name()) - def list_outputs(self): - """ - Get list with the name of all active outputs. - - :rtype: list of strings - """ - return [output.get_name() for output in self._outputs] - - def remove_output(self, output): - """ - Remove output from our pipeline. - - :param output: output to remove from the pipeline - :type output: :class:`gst.Bin` - """ - peersrc = output.get_pad('sink').get_peer() - handler = peersrc.add_event_probe(self._handle_event_probe) - - struct = gst.Structure('mopidy-unlink') - struct.set_value('handler', handler) - - event = gst.event_new_custom(gst.EVENT_CUSTOM_DOWNSTREAM, struct) - self._volume.send_event(event) - - def _handle_event_probe(self, srcpad, event): - if event.type == gst.EVENT_CUSTOM_DOWNSTREAM and event.has_name('mopidy-unlink'): - data = self._get_structure_data(event.get_structure()) - - output = srcpad.get_peer().get_parent() - - srcpad.unlink(srcpad.get_peer()) - srcpad.remove_event_probe(data['handler']) - - output.set_state(gst.STATE_NULL) - self._pipeline.remove(output) - - logger.warning('Removed %s', output.get_name()) - return False - return True - - def _get_structure_data(self, struct): - # Ugly hack to get around missing get_value in pygst bindings :/ - data = {} - def get_data(key, value): - data[key] = value - struct.foreach(get_data) - return data - - def connect_message_handler(self, element, handler): - """ - Attach custom message handler for given element. - - Hook to allow outputs (or other code) to register custom message - handlers for all messages coming from the element in question. - - In the case of outputs, :meth:`mopidy.outputs.BaseOutput.on_connect` - should be used to attach such handlers and care should be taken to - remove them in :meth:`mopidy.outputs.BaseOutput.on_remove` using - :meth:`remove_message_handler`. - - The handler callback will only be given the message in question, and - is free to ignore the message. However, if the handler wants to prevent - the default handling of the message it should return :class:`True` - indicating that the message has been handled. - - Note that there can only be one handler per element. - - :param element: element to watch messages from - :type element: :class:`gst.Element` - :param handler: callable that takes :class:`gst.Message` and returns - :class:`True` if the message has been handeled - :type handler: callable - """ - self._handlers[element] = handler - - def remove_message_handler(self, element): - """ - Remove custom message handler. - - :param element: element to remove message handling from. - :type element: :class:`gst.Element` - """ - self._handlers.pop(element, None) + # FIXME re-add disconnect / swap output code? diff --git a/mopidy/outputs/__init__.py b/mopidy/outputs/__init__.py index d94c0727..21179f94 100644 --- a/mopidy/outputs/__init__.py +++ b/mopidy/outputs/__init__.py @@ -13,8 +13,7 @@ class BaseOutput(object): MESSAGE_ERROR = gst.MESSAGE_ERROR MESSAGE_WARNING = gst.MESSAGE_WARNING - def __init__(self, gstreamer): - self.gstreamer = gstreamer + def __init__(self): self.bin = self._build_bin() self.bin.set_name(self.get_name()) @@ -25,32 +24,6 @@ class BaseOutput(object): logger.debug('Creating new output: %s', description) return gst.parse_bin_from_description(description, True) - def connect(self): - """Attach output to GStreamer pipeline.""" - self.gstreamer.connect_output(self.bin) - self.on_connect() - - def on_connect(self): - """ - Called after output has been connected to GStreamer pipeline. - - *MAY be implemented by subclass.* - """ - pass - - def remove(self): - """Remove output from GStreamer pipeline.""" - self.gstreamer.remove_output(self.bin) - self.on_remove() - - def on_remove(self): - """ - Called after output has been removed from GStreamer pipeline. - - *MAY be implemented by subclass.* - """ - pass - def get_name(self): """ Get name of the output. Defaults to the output's class name. diff --git a/mopidy/outputs/shoutcast.py b/mopidy/outputs/shoutcast.py index ffe09aae..0279ae2d 100644 --- a/mopidy/outputs/shoutcast.py +++ b/mopidy/outputs/shoutcast.py @@ -40,19 +40,3 @@ class ShoutcastOutput(BaseOutput): u'username': settings.SHOUTCAST_OUTPUT_USERNAME, u'password': settings.SHOUTCAST_OUTPUT_PASSWORD, }) - - def on_connect(self): - self.gstreamer.connect_message_handler( - self.bin.get_by_name('shoutcast'), self.message_handler) - - def on_remove(self): - self.gstreamer.remove_message_handler( - self.bin.get_by_name('shoutcast')) - - def message_handler(self, message): - if message.type != self.MESSAGE_ERROR: - return False - error, debug = message.parse_error() - logger.warning('%s (%s)', error, debug) - self.remove() - return True diff --git a/mopidy/settings.py b/mopidy/settings.py index f3e012ed..392c9ad7 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -173,12 +173,8 @@ MPD_SERVER_PASSWORD = None #: #: Default:: #: -#: OUTPUTS = ( -#: u'mopidy.outputs.local.LocalOutput', -#: ) -OUTPUTS = ( - u'mopidy.outputs.local.LocalOutput', -) +#: OUTPUTS = (u'mopidy.outputs.local.LocalOutput',) +OUTPUTS = (u'mopidy.outputs.local.LocalOutput',) #: Hostname of the SHOUTcast server which Mopidy should stream audio to. #: From c6d258fae2383bf483cfc0b0ab95eddd3b228294 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 3 Aug 2011 12:15:52 +0200 Subject: [PATCH 003/116] Remove unused destroy() methods from backend controllers and providers --- docs/changes.rst | 6 ++++++ mopidy/backends/base/current_playlist.py | 4 ---- mopidy/backends/base/library.py | 12 ------------ mopidy/backends/base/playback.py | 14 -------------- mopidy/backends/base/stored_playlists.py | 12 ------------ 5 files changed, 6 insertions(+), 42 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index dab00c08..32115a95 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -50,6 +50,12 @@ v0.6.0 (in development) - Unescape all incoming MPD requests. (Fixes: :issue:`113`) +**multi-backend changes** + +- Remove `destroy()` methods from backend controller and provider APIs, as it + was not in use and actually not called by any code. Will reintroduce when + needed. + v0.5.0 (2011-06-15) =================== diff --git a/mopidy/backends/base/current_playlist.py b/mopidy/backends/base/current_playlist.py index e89c23d5..17125ac0 100644 --- a/mopidy/backends/base/current_playlist.py +++ b/mopidy/backends/base/current_playlist.py @@ -21,10 +21,6 @@ class CurrentPlaylistController(object): self._cp_tracks = [] self._version = 0 - def destroy(self): - """Cleanup after component.""" - pass - @property def cp_tracks(self): """ diff --git a/mopidy/backends/base/library.py b/mopidy/backends/base/library.py index a30ed412..9e3afe9a 100644 --- a/mopidy/backends/base/library.py +++ b/mopidy/backends/base/library.py @@ -16,10 +16,6 @@ class LibraryController(object): self.backend = backend self.provider = provider - def destroy(self): - """Cleanup after component.""" - self.provider.destroy() - def find_exact(self, **query): """ Search the library for tracks where ``field`` is ``values``. @@ -89,14 +85,6 @@ class BaseLibraryProvider(object): 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.LibraryController.find_exact`. diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 57a7ad85..51fe0d3b 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -82,12 +82,6 @@ class PlaybackController(object): self.play_time_accumulated = 0 self.play_time_started = None - def destroy(self): - """ - Cleanup after component. - """ - self.provider.destroy() - def _get_cpid(self, cp_track): if cp_track is None: return None @@ -559,14 +553,6 @@ class BasePlaybackProvider(object): def __init__(self, backend): self.backend = backend - def destroy(self): - """ - Cleanup after component. - - *MAY be implemented by subclasses.* - """ - pass - def pause(self): """ Pause playback. diff --git a/mopidy/backends/base/stored_playlists.py b/mopidy/backends/base/stored_playlists.py index aca78a8c..c742cd23 100644 --- a/mopidy/backends/base/stored_playlists.py +++ b/mopidy/backends/base/stored_playlists.py @@ -17,10 +17,6 @@ class StoredPlaylistsController(object): self.backend = backend self.provider = provider - def destroy(self): - """Cleanup after component.""" - self.provider.destroy() - @property def playlists(self): """ @@ -133,14 +129,6 @@ class BaseStoredPlaylistsProvider(object): self.backend = backend self._playlists = [] - def destroy(self): - """ - Cleanup after component. - - *MAY be implemented by subclass.* - """ - pass - @property def playlists(self): """ From 1817ca2804f37fb92cf6e76abf387c9b36c9d992 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 6 Aug 2011 00:41:26 +0200 Subject: [PATCH 004/116] Formatting --- mopidy/backends/base/stored_playlists.py | 1 - mopidy/backends/local/__init__.py | 3 ++- mopidy/backends/spotify/__init__.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mopidy/backends/base/stored_playlists.py b/mopidy/backends/base/stored_playlists.py index c742cd23..0ce2e196 100644 --- a/mopidy/backends/base/stored_playlists.py +++ b/mopidy/backends/base/stored_playlists.py @@ -189,4 +189,3 @@ class BaseStoredPlaylistsProvider(object): *MUST be implemented by subclass.* """ raise NotImplementedError - diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index e689f666..e1d11bcb 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -67,7 +67,8 @@ class LocalBackend(ThreadingActor, Backend): def on_start(self): gstreamer_refs = ActorRegistry.get_by_class(GStreamer) - assert len(gstreamer_refs) == 1, 'Expected exactly one running gstreamer.' + assert len(gstreamer_refs) == 1, \ + 'Expected exactly one running GStreamer.' self.gstreamer = gstreamer_refs[0].proxy() diff --git a/mopidy/backends/spotify/__init__.py b/mopidy/backends/spotify/__init__.py index 02ccd802..08c3ed49 100644 --- a/mopidy/backends/spotify/__init__.py +++ b/mopidy/backends/spotify/__init__.py @@ -78,7 +78,8 @@ class SpotifyBackend(ThreadingActor, Backend): def on_start(self): gstreamer_refs = ActorRegistry.get_by_class(GStreamer) - assert len(gstreamer_refs) == 1, 'Expected exactly one running gstreamer.' + assert len(gstreamer_refs) == 1, \ + 'Expected exactly one running GStreamer.' self.gstreamer = gstreamer_refs[0].proxy() logger.info(u'Mopidy uses SPOTIFY(R) CORE') From 6a470f96940c281f309acd46a358be36de42a817 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 6 Aug 2011 01:19:45 +0200 Subject: [PATCH 005/116] Fix typo --- tests/frontends/mpd/protocol/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/frontends/mpd/protocol/__init__.py b/tests/frontends/mpd/protocol/__init__.py index 591ef5ce..b54906be 100644 --- a/tests/frontends/mpd/protocol/__init__.py +++ b/tests/frontends/mpd/protocol/__init__.py @@ -8,9 +8,9 @@ from mopidy.mixers import dummy as mixer from tests import unittest -class MockConnetion(mock.Mock): +class MockConnection(mock.Mock): def __init__(self, *args, **kwargs): - super(MockConnetion, self).__init__(*args, **kwargs) + super(MockConnection, self).__init__(*args, **kwargs) self.host = mock.sentinel.host self.port = mock.sentinel.port self.response = [] @@ -25,7 +25,7 @@ class BaseTestCase(unittest.TestCase): self.backend = backend.DummyBackend.start().proxy() self.mixer = mixer.DummyMixer.start().proxy() - self.connection = MockConnetion() + self.connection = MockConnection() self.session = mpd.MpdSession(self.connection) self.dispatcher = self.session.dispatcher self.context = self.dispatcher.context From 28257306a4f59d2824a7a8efdde71258cbe78153 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 13 Aug 2011 17:39:14 +0200 Subject: [PATCH 006/116] Increase max number of Spotify search results --- docs/changes.rst | 3 +++ mopidy/backends/spotify/session_manager.py | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index 32115a95..968ef255 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -50,6 +50,9 @@ v0.6.0 (in development) - Unescape all incoming MPD requests. (Fixes: :issue:`113`) +- Increase the maximum number of results returned by Spotify searches from 32 + to 100. + **multi-backend changes** - Remove `destroy()` methods from backend controller and provider APIs, as it diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index 9c8853e6..9a883d23 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -151,9 +151,12 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): """Search method used by Mopidy backend""" def callback(results, userdata=None): # TODO Include results from results.albums(), etc. too + # TODO Consider launching a second search if results.total_tracks() + # is larger than len(results.tracks()) playlist = Playlist(tracks=[ SpotifyTranslator.to_mopidy_track(t) for t in results.tracks()]) queue.put(playlist) self.connected.wait() - self.session.search(query, callback) + self.session.search(query, callback, track_count=100, + album_count=0, artist_count=0) From 83bf9af8c14dc59637fa94755551f5ea8f0eb1b1 Mon Sep 17 00:00:00 2001 From: sandos Date: Wed, 21 Sep 2011 21:25:11 +0200 Subject: [PATCH 007/116] Log out from spotify when shutting down --- mopidy/backends/spotify/__init__.py | 3 +++ mopidy/backends/spotify/session_manager.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/mopidy/backends/spotify/__init__.py b/mopidy/backends/spotify/__init__.py index 08c3ed49..a50f1724 100644 --- a/mopidy/backends/spotify/__init__.py +++ b/mopidy/backends/spotify/__init__.py @@ -85,6 +85,9 @@ class SpotifyBackend(ThreadingActor, Backend): logger.info(u'Mopidy uses SPOTIFY(R) CORE') self.spotify = self._connect() + def on_stop(self): + self.spotify.logout() + def _connect(self): from .session_manager import SpotifySessionManager diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index 9a883d23..5261f0cf 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -160,3 +160,8 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): self.connected.wait() self.session.search(query, callback, track_count=100, album_count=0, artist_count=0) + + def logout(self): + """Log out from spotify""" + logger.debug(u'Logging out from spotify') + self.session.logout() From 808b9e026a272a37d2d6de39be54aa80653c5480 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 24 Sep 2011 19:09:05 +0200 Subject: [PATCH 008/116] Add yappi profiling to tests/__main__.py --- requirements/tests.txt | 1 + tests/__main__.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/requirements/tests.txt b/requirements/tests.txt index 0bc8380f..922ef6dc 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -2,3 +2,4 @@ coverage mock >= 0.7 nose tox +yappi diff --git a/tests/__main__.py b/tests/__main__.py index e2bb3e72..69113580 100644 --- a/tests/__main__.py +++ b/tests/__main__.py @@ -1,4 +1,8 @@ import nose +import yappi -if __name__ == '__main__': +try: + yappi.start() nose.main() +finally: + yappi.print_stats() From 6b7e7d288944635bbbd01c15a115a78128abac74 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 24 Sep 2011 20:04:55 +0200 Subject: [PATCH 009/116] Use unicode for Spotify search queries, as required by pyspotify 1.4 (fixes #129) --- docs/changes.rst | 5 +++++ mopidy/backends/spotify/library.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index 968ef255..1d85821c 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -12,6 +12,8 @@ v0.6.0 (in development) - Pykka 0.12.3 or greater is required. +- pyspotify 1.4 or greater is required. + - All config, data, and cache locations are now based on the XDG spec. - This means that your settings file will need to be moved from @@ -53,6 +55,9 @@ v0.6.0 (in development) - Increase the maximum number of results returned by Spotify searches from 32 to 100. +- Send Spotify search queries to pyspotify as unicode objects, as required by + pyspotify 1.4. (Fixes: :issue:`129`) + **multi-backend changes** - Remove `destroy()` methods from backend controller and provider APIs, as it diff --git a/mopidy/backends/spotify/library.py b/mopidy/backends/spotify/library.py index 40d4a099..59aa9a2c 100644 --- a/mopidy/backends/spotify/library.py +++ b/mopidy/backends/spotify/library.py @@ -55,7 +55,7 @@ class SpotifyLibraryProvider(BaseLibraryProvider): spotify_query = u' '.join(spotify_query) logger.debug(u'Spotify search query: %s' % spotify_query) queue = Queue.Queue() - self.backend.spotify.search(spotify_query.encode(ENCODING), queue) + self.backend.spotify.search(spotify_query, queue) try: return queue.get(timeout=3) # XXX What is an reasonable timeout? except Queue.Empty: From eb4bf2e8bf351851e5b56d020bdc821fb13d95fc Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 25 Sep 2011 11:30:42 +0200 Subject: [PATCH 010/116] Formatting --- mopidy/frontends/mpd/dispatcher.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index cab014a8..5ee70a5b 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -90,7 +90,7 @@ class MpdDispatcher(object): def _authenticate_filter(self, request, response, filter_chain): if self.authenticated: return self._call_next_filter(request, response, filter_chain) - elif settings.MPD_SERVER_PASSWORD is None: + elif settings.MPD_SERVER_PASSWORD is None: self.authenticated = True return self._call_next_filter(request, response, filter_chain) else: @@ -161,6 +161,7 @@ class MpdDispatcher(object): def _has_error(self, response): return response and response[-1].startswith(u'ACK') + ### Filter: call handler def _call_handler_filter(self, request, response, filter_chain): @@ -241,11 +242,10 @@ class MpdContext(object): """ The backend. An instance of :class:`mopidy.backends.base.Backend`. """ - if self._backend is not None: - return self._backend - backend_refs = ActorRegistry.get_by_class(Backend) - assert len(backend_refs) == 1, 'Expected exactly one running backend.' - self._backend = backend_refs[0].proxy() + if self._backend is None: + backend_refs = ActorRegistry.get_by_class(Backend) + assert len(backend_refs) == 1, 'Expected exactly one running backend.' + self._backend = backend_refs[0].proxy() return self._backend @property @@ -253,9 +253,8 @@ class MpdContext(object): """ The mixer. An instance of :class:`mopidy.mixers.base.BaseMixer`. """ - if self._mixer is not None: - return self._mixer - mixer_refs = ActorRegistry.get_by_class(BaseMixer) - assert len(mixer_refs) == 1, 'Expected exactly one running mixer.' - self._mixer = mixer_refs[0].proxy() + if self._mixer is None: + mixer_refs = ActorRegistry.get_by_class(BaseMixer) + assert len(mixer_refs) == 1, 'Expected exactly one running mixer.' + self._mixer = mixer_refs[0].proxy() return self._mixer From e0bddfa10984877e9cd32c18ff29f77d8679811c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 25 Sep 2011 11:49:31 +0200 Subject: [PATCH 011/116] Add MPD_SERVER_MAX_CONNECTIONS setting (fixes: #134) --- docs/changes.rst | 3 +++ mopidy/frontends/mpd/__init__.py | 3 ++- mopidy/settings.py | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index 1d85821c..1a447179 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -58,6 +58,9 @@ v0.6.0 (in development) - Send Spotify search queries to pyspotify as unicode objects, as required by pyspotify 1.4. (Fixes: :issue:`129`) +- Add setting :attr:`mopidy.settings.MPD_SERVER_MAX_CONNECTIONS`. (Fixes: + :issue:`134`) + **multi-backend changes** - Remove `destroy()` methods from backend controller and provider APIs, as it diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index a2faedc2..b6adc09d 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -29,7 +29,8 @@ class MpdFrontend(actor.ThreadingActor, listeners.BackendListener): port = settings.MPD_SERVER_PORT try: - network.Server(hostname, port, protocol=MpdSession) + network.Server(hostname, port, protocol=MpdSession, + max_connections=settings.MPD_SERVER_MAX_CONNECTIONS) except IOError, e: logger.error(u'MPD server startup failed: %s', e) sys.exit(1) diff --git a/mopidy/settings.py b/mopidy/settings.py index b1e0c791..ccbf8457 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -180,6 +180,11 @@ MPD_SERVER_PORT = 6600 #: Default: :class:`None`, which means no password required. MPD_SERVER_PASSWORD = None +#: The maximum number of concurrent connections the MPD server will accept. +#: +#: Default: 20 +MPD_SERVER_MAX_CONNECTIONS = 20 + #: List of outputs to use. See :mod:`mopidy.outputs` for all available #: backends #: From 65ddaa1583bf724c61958b915a404b855b695b3f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 9 Oct 2011 00:03:03 +0200 Subject: [PATCH 012/116] Update changelog for v0.6.0 release --- docs/changes.rst | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 1a447179..445e7984 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -5,8 +5,19 @@ Changes This change log is used to track all major changes to Mopidy. -v0.6.0 (in development) -======================= +v0.6.0 (2011-10-09) +=================== + +The development of Mopidy have been quite slow for the last couple of months, +but we do have some goodies to release which have been idling in the +develop branch since the warmer days of the summer. This release brings support +for the MPD ``idle`` command, which makes it possible for a client wait for +updates from the server instead of polling every second. Also, we've added +support for the MPRIS standard, so that Mopidy can be controlled over D-Bus +from e.g. the Ubuntu Sound Menu. + +Please note that 0.6.0 requires some updated dependencies, as listed under +*Important changes* below. **Important changes** @@ -31,7 +42,7 @@ v0.6.0 (in development) - A new frontend :mod:`mopidy.frontends.mpris` have been added. It exposes Mopidy through the `MPRIS interface `_ over D-Bus. In - practice, this makes it possible to control Mopidy thorugh the `Ubuntu Sound + practice, this makes it possible to control Mopidy through the `Ubuntu Sound Menu `_. **Changes** @@ -61,8 +72,6 @@ v0.6.0 (in development) - Add setting :attr:`mopidy.settings.MPD_SERVER_MAX_CONNECTIONS`. (Fixes: :issue:`134`) -**multi-backend changes** - - Remove `destroy()` methods from backend controller and provider APIs, as it was not in use and actually not called by any code. Will reintroduce when needed. From b0319d1f70e9064c311699308950fbc1fd020fff Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 9 Oct 2011 00:43:20 +0200 Subject: [PATCH 013/116] Prepare for v0.7 development --- docs/changes.rst | 12 ++++++++++++ mopidy/__init__.py | 2 +- tests/version_test.py | 5 +++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 445e7984..cc3deacc 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -5,6 +5,18 @@ Changes This change log is used to track all major changes to Mopidy. +v0.7.0 (in development) +======================= + +**Important changes** + +- Nothing so far + +**Changes** + +- Nothing so far + + v0.6.0 (2011-10-09) =================== diff --git a/mopidy/__init__.py b/mopidy/__init__.py index 1d820fd0..ced47e07 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -8,7 +8,7 @@ import os from subprocess import PIPE, Popen -VERSION = (0, 6, 0) +VERSION = (0, 7, 0) DATA_PATH = os.path.join(glib.get_user_data_dir(), 'mopidy') CACHE_PATH = os.path.join(glib.get_user_cache_dir(), 'mopidy') diff --git a/tests/version_test.py b/tests/version_test.py index 4544349d..bd9ba7b9 100644 --- a/tests/version_test.py +++ b/tests/version_test.py @@ -22,8 +22,9 @@ class VersionTest(unittest.TestCase): self.assert_(SV('0.3.1') < SV('0.4.0')) self.assert_(SV('0.4.0') < SV('0.4.1')) self.assert_(SV('0.4.1') < SV('0.5.0')) - self.assert_(SV('0.5.0') < SV(get_plain_version())) - self.assert_(SV(get_plain_version()) < SV('0.6.1')) + self.assert_(SV('0.5.0') < SV('0.6.0')) + self.assert_(SV('0.6.0') < SV(get_plain_version())) + self.assert_(SV(get_plain_version()) < SV('0.7.1')) def test_get_platform_contains_platform(self): self.assert_(platform.platform() in get_platform()) From 041fd27990f21e53750b3b1b1cd76a8464a67d9a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 9 Oct 2011 19:30:57 +0200 Subject: [PATCH 014/116] docs: Add link from requirements to the MPRIS frontend --- docs/installation/index.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 198ac9e8..8fd3e840 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -46,6 +46,9 @@ Otherwise, make sure you got the required dependencies installed. - To use the Last.FM scrobbler, see :mod:`mopidy.frontends.lastfm` for additional requirements. + - To use the MPRIS frontend, e.g. using the Ubuntu Sound Menu, see + :mod:`mopidy.frontends.mpris` for additional requirements. + Install latest stable release ============================= From a95f960fdb98f8a164ffd6649fb1b433ee678e56 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 9 Oct 2011 19:34:28 +0200 Subject: [PATCH 015/116] docs: Update location of settings file after XDG-ification --- docs/settings.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 76eb6315..a6ad3693 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -10,10 +10,10 @@ 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``. +Mopidy reads settings from the file ``~/.config/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/.config/mopidy/settings.py``. You can either create the settings file yourself, or run the ``mopidy`` command, and it will create an empty settings file for you. @@ -22,7 +22,7 @@ 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:: +A complete ``~/.config/mopidy/settings.py`` may look as simple as this:: MPD_SERVER_HOSTNAME = u'::' SPOTIFY_USERNAME = u'alice' @@ -77,7 +77,7 @@ To make a ``tag_cache`` of your local music available for Mopidy: mopidy --list-settings -#. Scan your music library. Currently the command outputs the ``tag_cache`` to +#. Scan your music library. The command outputs the ``tag_cache`` to ``stdout``, which means that you will need to redirect the output to a file yourself:: From 139ecb6ccf540287cb67f82ce8a4675f85b11675 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 9 Oct 2011 19:45:10 +0200 Subject: [PATCH 016/116] docs: Note on stopping Mopidy using kill/SIGTERM --- docs/running.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/running.rst b/docs/running.rst index 4912512f..6c8d0ede 100644 --- a/docs/running.rst +++ b/docs/running.rst @@ -10,4 +10,11 @@ When Mopidy says ``MPD server running at [127.0.0.1]:6600`` it's ready to accept connections by any MPD client. Check out our non-exhaustive :doc:`/clients/mpd` list to find recommended clients. -To stop Mopidy, press ``CTRL+C``. +To stop Mopidy, press ``CTRL+C`` in the terminal where you started Mopidy. + +Mopidy will also shut down properly if you send it the TERM signal, e.g. by +using ``kill``:: + + kill `ps ax | grep mopidy | grep -v grep | cut -d' ' -f1` + +This can be useful e.g. if you create init script for managing Mopidy. From 20cd11eb30d92635199fffe944277dbf954b3a23 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 19 Oct 2011 19:12:03 +0200 Subject: [PATCH 017/116] Update to async version of Google Analytics tracking --- docs/_templates/layout.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index d6cb00e9..14113da6 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -3,13 +3,13 @@ {% block footer %} {{ super() }} - {% endblock %} From e5bbda739ce47304c415e74c5e7a9a905d9fd980 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 19 Oct 2011 19:12:03 +0200 Subject: [PATCH 018/116] Update to async version of Google Analytics tracking --- docs/_templates/layout.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index d6cb00e9..14113da6 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -3,13 +3,13 @@ {% block footer %} {{ super() }} - {% endblock %} From ceb5753c81ce4966146f5d5d54cf113987afb916 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 19 Oct 2011 20:10:24 +0200 Subject: [PATCH 019/116] Move Google Analytics code to --- docs/_templates/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index 14113da6..485debc5 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -1,6 +1,6 @@ {% extends "!layout.html" %} -{% block footer %} +{% block extrahead %} {{ super() }} -{% endblock %} diff --git a/docs/_themes/nature/static/nature.css_t b/docs/_themes/nature/static/nature.css_t deleted file mode 100644 index b6c0f22e..00000000 --- a/docs/_themes/nature/static/nature.css_t +++ /dev/null @@ -1,236 +0,0 @@ -/** - * Sphinx stylesheet -- default theme - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: Arial, sans-serif; - font-size: 100%; - background-color: #111111; - color: #555555; - margin: 0; - padding: 0; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 300px; -} - -hr{ - border: 1px solid #B1B4B6; -} - -div.document { - background-color: #eeeeee; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 1em 30px 30px 30px; - font-size: 0.9em; -} - -div.footer { - color: #555; - width: 100%; - padding: 13px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #444444; -} - -div.related { - background-color: #6BA81E; - line-height: 36px; - color: #ffffff; - text-shadow: 0px 1px 0 #444444; - font-size: 1.1em; -} - -div.related a { - color: #E2F3CC; -} - -div.related .right { - font-size: 0.9em; -} - -div.sphinxsidebar { - font-size: 0.9em; - line-height: 1.5em; - width: 300px -} - -div.sphinxsidebarwrapper{ - padding: 20px 0; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: Arial, sans-serif; - color: #222222; - font-size: 1.2em; - font-weight: bold; - margin: 0; - padding: 5px 10px; - text-shadow: 1px 1px 0 white -} - -div.sphinxsidebar h3 a { - color: #444444; -} - -div.sphinxsidebar p { - color: #888888; - padding: 5px 20px; - margin: 0.5em 0px; -} - -div.sphinxsidebar p.topless { -} - -div.sphinxsidebar ul { - margin: 10px 10px 10px 20px; - padding: 0; - color: #000000; -} - -div.sphinxsidebar a { - color: #444444; -} - -div.sphinxsidebar a:hover { - color: #E32E00; -} - -div.sphinxsidebar input { - border: 1px solid #cccccc; - font-family: sans-serif; - font-size: 1.1em; - padding: 0.15em 0.3em; -} - -div.sphinxsidebar input[type=text]{ - margin-left: 20px; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #005B81; - text-decoration: none; -} - -a:hover { - color: #E32E00; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: Arial, sans-serif; - font-weight: normal; - color: #212224; - margin: 30px 0px 10px 0px; - padding: 5px 0 5px 0px; - text-shadow: 0px 1px 0 white; - border-bottom: 1px solid #C8D5E3; -} - -div.body h1 { margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 150%; } -div.body h3 { font-size: 120%; } -div.body h4 { font-size: 110%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li { - line-height: 1.8em; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.highlight{ - background-color: white; -} - -div.note { - background-color: #eeeeee; - border: 1px solid #cccccc; -} - -div.seealso { - background-color: #ffffcc; - border: 1px solid #ffff66; -} - -div.topic { - background-color: #fafafa; - border-width: 0; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #ff6666; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 10px; - background-color: #eeeeee; - color: #222222; - line-height: 1.5em; - font-size: 1.1em; - margin: 1.5em 0 1.5em 0; - -webkit-box-shadow: 0px 0px 4px #d8d8d8; - -moz-box-shadow: 0px 0px 4px #d8d8d8; - box-shadow: 0px 0px 4px #d8d8d8; -} - -tt { - color: #222222; - padding: 1px 2px; - font-size: 1.2em; - font-family: monospace; -} - -#table-of-contents ul { - padding-left: 2em; -} diff --git a/docs/_themes/nature/static/pygments.css b/docs/_themes/nature/static/pygments.css deleted file mode 100644 index 652b7612..00000000 --- a/docs/_themes/nature/static/pygments.css +++ /dev/null @@ -1,54 +0,0 @@ -.c { color: #999988; font-style: italic } /* Comment */ -.k { font-weight: bold } /* Keyword */ -.o { font-weight: bold } /* Operator */ -.cm { color: #999988; font-style: italic } /* Comment.Multiline */ -.cp { color: #999999; font-weight: bold } /* Comment.preproc */ -.c1 { color: #999988; font-style: italic } /* Comment.Single */ -.gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ -.ge { font-style: italic } /* Generic.Emph */ -.gr { color: #aa0000 } /* Generic.Error */ -.gh { color: #999999 } /* Generic.Heading */ -.gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ -.go { color: #111 } /* Generic.Output */ -.gp { color: #555555 } /* Generic.Prompt */ -.gs { font-weight: bold } /* Generic.Strong */ -.gu { color: #aaaaaa } /* Generic.Subheading */ -.gt { color: #aa0000 } /* Generic.Traceback */ -.kc { font-weight: bold } /* Keyword.Constant */ -.kd { font-weight: bold } /* Keyword.Declaration */ -.kp { font-weight: bold } /* Keyword.Pseudo */ -.kr { font-weight: bold } /* Keyword.Reserved */ -.kt { color: #445588; font-weight: bold } /* Keyword.Type */ -.m { color: #009999 } /* Literal.Number */ -.s { color: #bb8844 } /* Literal.String */ -.na { color: #008080 } /* Name.Attribute */ -.nb { color: #999999 } /* Name.Builtin */ -.nc { color: #445588; font-weight: bold } /* Name.Class */ -.no { color: #ff99ff } /* Name.Constant */ -.ni { color: #800080 } /* Name.Entity */ -.ne { color: #990000; font-weight: bold } /* Name.Exception */ -.nf { color: #990000; font-weight: bold } /* Name.Function */ -.nn { color: #555555 } /* Name.Namespace */ -.nt { color: #000080 } /* Name.Tag */ -.nv { color: purple } /* Name.Variable */ -.ow { font-weight: bold } /* Operator.Word */ -.mf { color: #009999 } /* Literal.Number.Float */ -.mh { color: #009999 } /* Literal.Number.Hex */ -.mi { color: #009999 } /* Literal.Number.Integer */ -.mo { color: #009999 } /* Literal.Number.Oct */ -.sb { color: #bb8844 } /* Literal.String.Backtick */ -.sc { color: #bb8844 } /* Literal.String.Char */ -.sd { color: #bb8844 } /* Literal.String.Doc */ -.s2 { color: #bb8844 } /* Literal.String.Double */ -.se { color: #bb8844 } /* Literal.String.Escape */ -.sh { color: #bb8844 } /* Literal.String.Heredoc */ -.si { color: #bb8844 } /* Literal.String.Interpol */ -.sx { color: #bb8844 } /* Literal.String.Other */ -.sr { color: #808000 } /* Literal.String.Regex */ -.s1 { color: #bb8844 } /* Literal.String.Single */ -.ss { color: #bb8844 } /* Literal.String.Symbol */ -.bp { color: #999999 } /* Name.Builtin.Pseudo */ -.vc { color: #ff99ff } /* Name.Variable.Class */ -.vg { color: #ff99ff } /* Name.Variable.Global */ -.vi { color: #ff99ff } /* Name.Variable.Instance */ -.il { color: #009999 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/_themes/nature/theme.conf b/docs/_themes/nature/theme.conf deleted file mode 100644 index 1cc40044..00000000 --- a/docs/_themes/nature/theme.conf +++ /dev/null @@ -1,4 +0,0 @@ -[theme] -inherit = basic -stylesheet = nature.css -pygments_style = tango diff --git a/docs/conf.py b/docs/conf.py index d14075d0..b4dbc35b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -135,10 +135,7 @@ modindex_common_prefix = ['mopidy.'] # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. -if on_rtd: - html_theme = 'default' -else: - html_theme = 'nature' +html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -157,7 +154,8 @@ html_theme_path = ['_themes'] # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = '_static/mopidy.png' +if on_rtd: + html_logo = '_static/mopidy.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 From 9bcd0fe3a718d28a4d57a02c0e7490f5a19d6840 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 6 Feb 2012 13:22:42 +0100 Subject: [PATCH 060/116] Replace aptitude with apt-get --- docs/development/contributing.rst | 4 ++-- docs/installation/index.rst | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/development/contributing.rst b/docs/development/contributing.rst index 0303bdc7..782d2f20 100644 --- a/docs/development/contributing.rst +++ b/docs/development/contributing.rst @@ -74,7 +74,7 @@ Running tests To run tests, you need a couple of dependencies. They can be installed through Debian/Ubuntu package management:: - sudo aptitude install python-coverage python-mock python-nose + sudo apt-get install python-coverage python-mock python-nose Or, they can be installed using ``pip``:: @@ -126,7 +126,7 @@ from the documentation files, you need some additional dependencies. You can install them through Debian/Ubuntu package management:: - sudo aptitude install python-sphinx python-pygraphviz graphviz + sudo apt-get install python-sphinx python-pygraphviz graphviz Then, to generate docs:: diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 8dd47ada..b9d76e60 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -100,7 +100,7 @@ install Mopidy from PyPI using Pip. #. Then, you need to install Pip:: - sudo aptitude install python-setuptools python-pip # On Ubuntu/Debian + sudo apt-get install python-setuptools python-pip # On Ubuntu/Debian sudo easy_install pip # On OS X #. To install the currently latest stable release of Mopidy:: @@ -134,7 +134,7 @@ Mopidy's ``develop`` branch. #. Then, you need to install Pip:: - sudo aptitude install python-setuptools python-pip # On Ubuntu/Debian + sudo apt-get install python-setuptools python-pip # On Ubuntu/Debian sudo easy_install pip # On OS X #. To install the latest snapshot of Mopidy, run:: @@ -157,7 +157,7 @@ If you want to contribute to Mopidy, you should install Mopidy using Git. #. Then install Git, if haven't already:: - sudo aptitude install git-core # On Ubuntu/Debian + sudo apt-get install git-core # On Ubuntu/Debian sudo brew install git # On OS X using Homebrew #. Clone the official Mopidy repository, or your own fork of it:: From 95ab34dd7095196724a3bd952510e6451ef20684 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Feb 2012 00:11:55 +0100 Subject: [PATCH 061/116] Simplify requirements listing --- docs/installation/index.rst | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index b9d76e60..59e51500 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -18,36 +18,36 @@ Requirements gstreamer libspotify -If you install Mopidy from the APT archive, as described below, you can skip -the dependency installation part. +If you install Mopidy from the APT archive, as described below, APT will take +care of all the dependencies for you. Otherwise, make sure you got the required +dependencies installed. -Otherwise, make sure you got the required dependencies installed. +- Hard dependencies: -- Python >= 2.6, < 3 + - Python >= 2.6, < 3 -- `Pykka `_ >= 0.12.3 + - Pykka >= 0.12.3:: -- GStreamer >= 0.10, with Python bindings. See :doc:`gstreamer`. + sudo pip install -U pykka -- 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: - - - The default backend, :mod:`mopidy.backends.spotify`, requires libspotify - and pyspotify. See :doc:`libspotify`. - - - The local backend, :mod:`mopidy.backends.local`, requires no additional - dependencies. + - GStreamer 0.10.x, with Python bindings. See :doc:`gstreamer`. - Optional dependencies: - - To use the Last.FM scrobbler, see :mod:`mopidy.frontends.lastfm` for - additional requirements. + - For Spotify support, you need libspotify and pyspotify. See + :doc:`libspotify`. - - To use the MPRIS frontend, e.g. using the Ubuntu Sound Menu, see - :mod:`mopidy.frontends.mpris` for additional requirements. + - To scrobble your played tracks to Last.FM, you need pylast:: + + sudo pip install -U pylast + + - To use MPRIS, e.g. for controlling Mopidy from the Ubuntu Sound Menu, you + need some additional requirements:: + + sudo apt-get install python-dbus python-indicate + + - Some custom mixers (but not the default one) require additional + dependencies. See the docs for each mixer. Install latest stable release From 0d16bb0048a973d376a7b7b87628a3360ee6fb13 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Feb 2012 00:27:14 +0100 Subject: [PATCH 062/116] Simplify libspotify/pyspotify installation instructions --- docs/installation/libspotify.rst | 52 ++++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/installation/libspotify.rst b/docs/installation/libspotify.rst index 2728be94..0b0535d7 100644 --- a/docs/installation/libspotify.rst +++ b/docs/installation/libspotify.rst @@ -12,12 +12,6 @@ install libspotify and `pyspotify `_. This backend requires a paid `Spotify premium account `_. -.. note:: - - This product uses SPOTIFY CORE but is not endorsed, certified or otherwise - approved in any way by Spotify. Spotify is the registered trade mark of the - Spotify Group. - Installing libspotify ===================== @@ -26,23 +20,20 @@ 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 libspotify8 - -When libspotify has been installed, continue with -:ref:`pyspotify_installation`. +If you install from APT, jump directly to :ref:`pyspotify_installation` below. On Linux from source -------------------- -Download and install libspotify 0.0.8 for your OS and CPU architecture from -https://developer.spotify.com/en/libspotify/. +First, check pyspotify's changelog to see what's the latest version of +libspotify which is supported. The versions of libspotify and pyspotify are +tightly coupled. -For 64-bit Linux the process is as follows:: +Download and install the appropriate version of libspotify for your OS and CPU +architecture from https://developer.spotify.com/en/libspotify/. + +For libspotify 0.0.8 for 64-bit Linux the process is as follows:: wget http://developer.spotify.com/download/libspotify/libspotify-0.0.8-linux6-x86_64.tar.gz tar zxfv libspotify-0.0.8-linux6-x86_64.tar.gz @@ -50,6 +41,9 @@ For 64-bit Linux the process is as follows:: sudo make install prefix=/usr/local sudo ldconfig +Remember to adjust for the latest libspotify version supported by pyspotify, +your OS and your CPU architecture. + When libspotify has been installed, continue with :ref:`pyspotify_installation`. @@ -84,29 +78,35 @@ by installing pyspotify. On Linux from APT archive ------------------------- -Assuming that you've already set up http://apt.mopidy.com/ as a software -source, run:: +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 python-spotify -If you haven't already installed libspotify, this command will install both -libspotify and pyspotify for you. +This command will install both libspotify and pyspotify for you. -On Linux/OS X from source +On Linux from source ------------------------- +If you have have already installed libspotify, you can continue with installing +the libspotify Python bindings, called pyspotify. + 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. - Then get, build, and install the latest releast of pyspotify using ``pip``:: sudo pip install -U pyspotify -Or using the older ``easy_install``:: - sudo easy_install pyspotify +On OS X from source +------------------- + +If you have already installed libspotify, you can get, build, and install the +latest releast of pyspotify using ``pip``:: + + sudo pip install -U pyspotify From a679d0c2eee75bffccb4d334044f9c0d5f3d8a6c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Feb 2012 00:46:38 +0100 Subject: [PATCH 063/116] Add detailed GStreamer installation instructions for OS X --- docs/installation/gstreamer.rst | 70 ++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/docs/installation/gstreamer.rst b/docs/installation/gstreamer.rst index 08e16378..7daac9cf 100644 --- a/docs/installation/gstreamer.rst +++ b/docs/installation/gstreamer.rst @@ -2,7 +2,8 @@ GStreamer installation ********************** -To use the Mopidy, you first need to install GStreamer and its Python bindings. +To use the Mopidy, you first need to install GStreamer and the GStreamer Python +bindings. Installing GStreamer @@ -15,6 +16,10 @@ 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. + +On Debian/Ubuntu +^^^^^^^^^^^^^^^^ + If you use Debian/Ubuntu you can install GStreamer like this:: sudo apt-get install python-gst0.10 gstreamer0.10-plugins-good \ @@ -24,30 +29,67 @@ 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. +On Arch Linux +^^^^^^^^^^^^^ + +If you use Arch Linux, install the following packages from the official +repository:: + + sudo pacman -S gstreamer0.10-python gstreamer0.10-good-plugins \ + gstreamer0.10-ugly-plugins + + On OS X from Homebrew --------------------- .. note:: - We have created GStreamer formulas for Homebrew to make the GStreamer - 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 - back to using MacPorts. + We have been working with `Homebrew `_ to + make all the GStreamer packages easily installable on OS X using Homebrew. + We've gotten most of our packages included, but the Homebrew guys aren't + very happy to include Python specific packages into Homebrew, even though + they are not installable by pip. If you're interested, see the discussion + in `Homebrew's issue #1612 + `_ for details. -To install GStreamer on OS X using Homebrew:: +The following is currently the shortest path to installing GStreamer with +Python bindings on OS X using Homebrew. - brew install gst-python gst-plugins-good gst-plugins-ugly +#. Install `Homebrew `_. +#. Download our Homebrew formulas for `pycairo`, `pygobject`, `pygtk`, and + `gst-python`:: -On OS X from MacPorts ---------------------- + wget -O/usr/local/Library/Formula/pycairo.rb \ + https://github.com/jodal/homebrew/raw/gst-python/Library/Formula/pycairo.rb + wget -O/usr/local/Library/Formula/pygobject.rb \ + https://github.com/jodal/homebrew/raw/gst-python/Library/Formula/pygobject.rb + wget -O/usr/local/Library/Formula/pygtk.rb \ + https://github.com/jodal/homebrew/raw/gst-python/Library/Formula/pygtk.rb + wget -O/usr/local/Library/Formula/gst-python.rb \ + https://github.com/jodal/homebrew/raw/gst-python/Library/Formula/gst-python.rb -To install GStreamer on OS X using MacPorts:: +#. Install the required packages:: - sudo port install py26-gst-python gstreamer-plugins-good \ - gstreamer-plugins-ugly + brew install gst-python gst-plugins-good gst-plugins-ugly + +#. Make sure to include Homebrew's Python ``site-packages`` directory in your + ``PYTHONPATH``. If you don't include this, Mopidy will not find GStreamer + and crash. + + You can either amend your ``PYTHONPATH`` permanently, by adding the + following statement to your shell's init file, e.g. ``~/.bashrc``:: + + export PYTHONPATH=$(brew --prefix)/lib/python2.6/site-packages:$PYTHONPATH + + Or, you can prefix the Mopidy command every time you run it:: + + PYTHONPATH=$(brew --prefix)/lib/python2.6/site-packages mopidy + + Note that you need to replace ``python2.6`` with ``python2.7`` if that's + the Python version you are using. To find your Python version, run:: + + python --version Testing the installation From 3f86d3fd321d778f2e8f54eab2ad8f5a20d6d9b4 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Feb 2012 00:51:34 +0100 Subject: [PATCH 064/116] A bit less easy_install in the world --- docs/installation/index.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 59e51500..8e62421c 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -101,7 +101,7 @@ install Mopidy from PyPI using Pip. #. Then, you need to install Pip:: sudo apt-get install python-setuptools python-pip # On Ubuntu/Debian - sudo easy_install pip # On OS X + sudo easy_install pip # On OS X #. To install the currently latest stable release of Mopidy:: @@ -112,8 +112,6 @@ install Mopidy from PyPI using Pip. #. 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. - Install development version =========================== @@ -135,7 +133,7 @@ Mopidy's ``develop`` branch. #. Then, you need to install Pip:: sudo apt-get install python-setuptools python-pip # On Ubuntu/Debian - sudo easy_install pip # On OS X + sudo easy_install pip # On OS X #. To install the latest snapshot of Mopidy, run:: From cfea4be681e010884510effad3266eeff2c8e5dd Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Feb 2012 00:55:19 +0100 Subject: [PATCH 065/116] "brew upgrade" has replaced " brew install `brew outdated`" --- 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 0b0535d7..5543f38e 100644 --- a/docs/installation/libspotify.rst +++ b/docs/installation/libspotify.rst @@ -60,7 +60,7 @@ libspotify:: To update your existing libspotify installation using Homebrew:: brew update - brew install `brew outdated` + brew upgrade When libspotify has been installed, continue with :ref:`pyspotify_installation`. From 5a15964910994720e63ab38fb7e46bcd69dd994f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Feb 2012 00:56:58 +0100 Subject: [PATCH 066/116] Add missing word --- docs/installation/libspotify.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation/libspotify.rst b/docs/installation/libspotify.rst index 5543f38e..223e4ed7 100644 --- a/docs/installation/libspotify.rst +++ b/docs/installation/libspotify.rst @@ -79,8 +79,8 @@ 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:: +http://apt.mopidy.com/ for how to use the Mopidy APT archive as a software +source on your system. Then, simply run:: sudo apt-get install python-spotify From 05b0d20fa5d14dca65dfd9e3c201f5f84c42016b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Feb 2012 01:01:52 +0100 Subject: [PATCH 067/116] Remove use of /usr/local, as Homebrew can be installed in other locations --- docs/installation/gstreamer.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/installation/gstreamer.rst b/docs/installation/gstreamer.rst index 7daac9cf..72b2feb8 100644 --- a/docs/installation/gstreamer.rst +++ b/docs/installation/gstreamer.rst @@ -60,13 +60,13 @@ Python bindings on OS X using Homebrew. #. Download our Homebrew formulas for `pycairo`, `pygobject`, `pygtk`, and `gst-python`:: - wget -O/usr/local/Library/Formula/pycairo.rb \ + wget -O $(brew --prefix)/Library/Formula/pycairo.rb \ https://github.com/jodal/homebrew/raw/gst-python/Library/Formula/pycairo.rb - wget -O/usr/local/Library/Formula/pygobject.rb \ + wget -O $(brew --prefix)/Library/Formula/pygobject.rb \ https://github.com/jodal/homebrew/raw/gst-python/Library/Formula/pygobject.rb - wget -O/usr/local/Library/Formula/pygtk.rb \ + wget -O $(brew --prefix)/Library/Formula/pygtk.rb \ https://github.com/jodal/homebrew/raw/gst-python/Library/Formula/pygtk.rb - wget -O/usr/local/Library/Formula/gst-python.rb \ + wget -O $(brew --prefix)/Library/Formula/gst-python.rb \ https://github.com/jodal/homebrew/raw/gst-python/Library/Formula/gst-python.rb #. Install the required packages:: From 48db56cc887e7242948a0ea785a0bd6a21e4104d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Feb 2012 01:10:46 +0100 Subject: [PATCH 068/116] GitHub is on HTTPS now --- docs/installation/gstreamer.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/gstreamer.rst b/docs/installation/gstreamer.rst index 72b2feb8..82fbab57 100644 --- a/docs/installation/gstreamer.rst +++ b/docs/installation/gstreamer.rst @@ -50,7 +50,7 @@ On OS X from Homebrew very happy to include Python specific packages into Homebrew, even though they are not installable by pip. If you're interested, see the discussion in `Homebrew's issue #1612 - `_ for details. + `_ for details. The following is currently the shortest path to installing GStreamer with Python bindings on OS X using Homebrew. From 5e6fd8d7bca987c8b4c335590d4d6ffb0ffd282d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Feb 2012 01:13:17 +0100 Subject: [PATCH 069/116] Formatting fix --- docs/installation/gstreamer.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation/gstreamer.rst b/docs/installation/gstreamer.rst index 82fbab57..37cc9a6f 100644 --- a/docs/installation/gstreamer.rst +++ b/docs/installation/gstreamer.rst @@ -57,8 +57,8 @@ Python bindings on OS X using Homebrew. #. Install `Homebrew `_. -#. Download our Homebrew formulas for `pycairo`, `pygobject`, `pygtk`, and - `gst-python`:: +#. Download our Homebrew formulas for ``pycairo``, ``pygobject``, ``pygtk``, + and ``gst-python``:: wget -O $(brew --prefix)/Library/Formula/pycairo.rb \ https://github.com/jodal/homebrew/raw/gst-python/Library/Formula/pycairo.rb From bb2c05b215c0b3ae6470cda247e35fea48e3413c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Feb 2012 01:14:11 +0100 Subject: [PATCH 070/116] Pull a couple of section up a level --- docs/installation/gstreamer.rst | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/installation/gstreamer.rst b/docs/installation/gstreamer.rst index 37cc9a6f..d70dd156 100644 --- a/docs/installation/gstreamer.rst +++ b/docs/installation/gstreamer.rst @@ -6,19 +6,16 @@ To use the Mopidy, you first need to install GStreamer and the GStreamer Python bindings. -Installing GStreamer -==================== - -On Linux --------- +Installing GStreamer on Linux +============================= 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. -On Debian/Ubuntu -^^^^^^^^^^^^^^^^ +Debian/Ubuntu +------------- If you use Debian/Ubuntu you can install GStreamer like this:: @@ -29,8 +26,8 @@ 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. -On Arch Linux -^^^^^^^^^^^^^ +Arch Linux +---------- If you use Arch Linux, install the following packages from the official repository:: @@ -39,8 +36,8 @@ repository:: gstreamer0.10-ugly-plugins -On OS X from Homebrew ---------------------- +Installing GStreamer on OS X from Homebrew +------------------------------------------ .. note:: From f84cd6833a101b00b065fb250c0ea650392989da Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Feb 2012 01:23:48 +0100 Subject: [PATCH 071/116] Fix header level --- docs/installation/gstreamer.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation/gstreamer.rst b/docs/installation/gstreamer.rst index d70dd156..fb8df33d 100644 --- a/docs/installation/gstreamer.rst +++ b/docs/installation/gstreamer.rst @@ -36,8 +36,8 @@ repository:: gstreamer0.10-ugly-plugins -Installing GStreamer on OS X from Homebrew ------------------------------------------- +Installing GStreamer on OS X +============================ .. note:: From ca0d2935f8f67ebce30a3ca2a75fd109c764802b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Feb 2012 21:40:26 +0100 Subject: [PATCH 072/116] Fix URLs and use curl instead of wget, as wget isn't installed by default on OS X --- docs/installation/gstreamer.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/installation/gstreamer.rst b/docs/installation/gstreamer.rst index fb8df33d..d0dc0461 100644 --- a/docs/installation/gstreamer.rst +++ b/docs/installation/gstreamer.rst @@ -57,14 +57,14 @@ Python bindings on OS X using Homebrew. #. Download our Homebrew formulas for ``pycairo``, ``pygobject``, ``pygtk``, and ``gst-python``:: - wget -O $(brew --prefix)/Library/Formula/pycairo.rb \ - https://github.com/jodal/homebrew/raw/gst-python/Library/Formula/pycairo.rb - wget -O $(brew --prefix)/Library/Formula/pygobject.rb \ - https://github.com/jodal/homebrew/raw/gst-python/Library/Formula/pygobject.rb - wget -O $(brew --prefix)/Library/Formula/pygtk.rb \ - https://github.com/jodal/homebrew/raw/gst-python/Library/Formula/pygtk.rb - wget -O $(brew --prefix)/Library/Formula/gst-python.rb \ - https://github.com/jodal/homebrew/raw/gst-python/Library/Formula/gst-python.rb + curl -o $(brew --prefix)/Library/Formula/pycairo.rb \ + https://raw.github.com/jodal/homebrew/gst-python/Library/Formula/pycairo.rb + curl -o $(brew --prefix)/Library/Formula/pygobject.rb \ + https://raw.github.com/jodal/homebrew/gst-python/Library/Formula/pygobject.rb + curl -o $(brew --prefix)/Library/Formula/pygtk.rb \ + https://raw.github.com/jodal/homebrew/gst-python/Library/Formula/pygtk.rb + curl -o $(brew --prefix)/Library/Formula/gst-python.rb \ + https://raw.github.com/jodal/homebrew/gst-python/Library/Formula/gst-python.rb #. Install the required packages:: From f1ba8af0dec1089e5199b0d87f7ca474fcb9c801 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 8 Feb 2012 13:24:28 +0100 Subject: [PATCH 073/116] Ignore MPRIS tests on OS X --- tests/frontends/mpris/events_test.py | 10 +++++++++- tests/frontends/mpris/player_interface_test.py | 10 +++++++++- tests/frontends/mpris/root_interface_test.py | 11 +++++++++-- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/tests/frontends/mpris/events_test.py b/tests/frontends/mpris/events_test.py index 90cdab6a..49e56226 100644 --- a/tests/frontends/mpris/events_test.py +++ b/tests/frontends/mpris/events_test.py @@ -1,11 +1,19 @@ +import sys + import mock -from mopidy.frontends.mpris import MprisFrontend, objects +from mopidy import OptionalDependencyError from mopidy.models import Track +try: + from mopidy.frontends.mpris import MprisFrontend, objects +except OptionalDependencyError: + pass + from tests import unittest +@unittest.skipUnless(sys.platform.startswith('linux'), 'requires Linux') class BackendEventsTest(unittest.TestCase): def setUp(self): self.mpris_frontend = MprisFrontend() # As a plain class, not an actor diff --git a/tests/frontends/mpris/player_interface_test.py b/tests/frontends/mpris/player_interface_test.py index a966403e..24c426fb 100644 --- a/tests/frontends/mpris/player_interface_test.py +++ b/tests/frontends/mpris/player_interface_test.py @@ -1,11 +1,18 @@ +import sys + import mock +from mopidy import OptionalDependencyError from mopidy.backends.dummy import DummyBackend from mopidy.backends.base.playback import PlaybackController -from mopidy.frontends.mpris import objects from mopidy.mixers.dummy import DummyMixer from mopidy.models import Album, Artist, Track +try: + from mopidy.frontends.mpris import objects +except OptionalDependencyError: + pass + from tests import unittest PLAYING = PlaybackController.PLAYING @@ -13,6 +20,7 @@ PAUSED = PlaybackController.PAUSED STOPPED = PlaybackController.STOPPED +@unittest.skipUnless(sys.platform.startswith('linux'), 'requires Linux') class PlayerInterfaceTest(unittest.TestCase): def setUp(self): objects.MprisObject._connect_to_dbus = mock.Mock() diff --git a/tests/frontends/mpris/root_interface_test.py b/tests/frontends/mpris/root_interface_test.py index 443efdd3..1e54fc15 100644 --- a/tests/frontends/mpris/root_interface_test.py +++ b/tests/frontends/mpris/root_interface_test.py @@ -1,12 +1,19 @@ +import sys + import mock -from mopidy import settings +from mopidy import OptionalDependencyError, settings from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpris import objects + +try: + from mopidy.frontends.mpris import objects +except OptionalDependencyError: + pass from tests import unittest +@unittest.skipUnless(sys.platform.startswith('linux'), 'requires Linux') class RootInterfaceTest(unittest.TestCase): def setUp(self): objects.exit_process = mock.Mock() From 8c2a333938293f6e6286412648c22bb8af954218 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Thu, 9 Feb 2012 23:12:13 +0100 Subject: [PATCH 074/116] get_or_create_folder should also create intermediate folders --- docs/changes.rst | 4 ++++ mopidy/utils/path.py | 7 +++++-- tests/utils/path_test.py | 20 ++++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 8235e95d..528edaa8 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -22,6 +22,10 @@ v0.7.0 (in development) need for copying the entire current playlist from one thread to another. Thanks to John Bäckstrand for pinpointing the issue. +- Fix crash on creation of config and cache directories if intermediate + directories does not exist. This was especially the case on OS X, where + ``~/.config`` doesn't exist for most users. + v0.6.1 (2011-12-28) =================== diff --git a/mopidy/utils/path.py b/mopidy/utils/path.py index 8bd39f06..5d99ac12 100644 --- a/mopidy/utils/path.py +++ b/mopidy/utils/path.py @@ -8,9 +8,12 @@ logger = logging.getLogger('mopidy.utils.path') def get_or_create_folder(folder): folder = os.path.expanduser(folder) - if not os.path.isdir(folder): + if os.path.isfile(folder): + raise OSError('A file with the same name as the desired ' \ + 'dir, "%s", already exists.' % folder) + elif not os.path.isdir(folder): logger.info(u'Creating dir %s', folder) - os.mkdir(folder, 0755) + os.makedirs(folder, 0755) return folder def get_or_create_file(filename): diff --git a/tests/utils/path_test.py b/tests/utils/path_test.py index ba1fcf97..19bae375 100644 --- a/tests/utils/path_test.py +++ b/tests/utils/path_test.py @@ -28,12 +28,32 @@ class GetOrCreateFolderTest(unittest.TestCase): self.assert_(os.path.isdir(folder)) self.assertEqual(created, folder) + def test_creating_nested_folders(self): + level2_folder = os.path.join(self.parent, 'test') + level3_folder = os.path.join(self.parent, 'test', 'test') + self.assert_(not os.path.exists(level2_folder)) + self.assert_(not os.path.isdir(level2_folder)) + self.assert_(not os.path.exists(level3_folder)) + self.assert_(not os.path.isdir(level3_folder)) + created = get_or_create_folder(level3_folder) + self.assert_(os.path.exists(level2_folder)) + self.assert_(os.path.isdir(level2_folder)) + self.assert_(os.path.exists(level3_folder)) + self.assert_(os.path.isdir(level3_folder)) + self.assertEqual(created, level3_folder) + def test_creating_existing_folder(self): created = get_or_create_folder(self.parent) self.assert_(os.path.exists(self.parent)) self.assert_(os.path.isdir(self.parent)) self.assertEqual(created, self.parent) + def test_create_folder_with_name_of_existing_file_throws_oserror(self): + conflicting_file = os.path.join(self.parent, 'test') + open(conflicting_file, 'w').close() + folder = os.path.join(self.parent, 'test') + self.assertRaises(OSError, get_or_create_folder, folder) + class PathToFileURITest(unittest.TestCase): def test_simple_path(self): From 643f71363fdc9075568461531d4bb738c33d10e9 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 10 Feb 2012 00:02:33 +0100 Subject: [PATCH 075/116] Make pylint ignore TODO/XXX/FIXME in the code, so we can see more important errors through the noise --- pylintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pylintrc b/pylintrc index d2f84b77..98e10416 100644 --- a/pylintrc +++ b/pylintrc @@ -18,6 +18,7 @@ # R0921 - Abstract class not referenced # W0141 - Used builtin function '%s' # W0142 - Used * or ** magic +# W0511 - TODO, FIXME and XXX in the code # W0613 - Unused argument %r # -disable = C0103,C0111,E0102,E0202,E1101,R0201,R0801,R0903,R0904,R0921,W0141,W0142,W0613 +disable = C0103,C0111,E0102,E0202,E1101,R0201,R0801,R0903,R0904,R0921,W0141,W0142,W0511,W0613 From 413603f9817846c74aca844a0c61e15d29fbd596 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 10 Feb 2012 00:02:54 +0100 Subject: [PATCH 076/116] Remove old redundant comment --- tests/gstreamer_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/gstreamer_test.py b/tests/gstreamer_test.py index 66e0995e..012c9002 100644 --- a/tests/gstreamer_test.py +++ b/tests/gstreamer_test.py @@ -6,8 +6,6 @@ from mopidy.utils.path import path_to_uri from tests import unittest, path_to_data_dir -# TODO BaseOutputTest? - @unittest.skipIf(sys.platform == 'win32', 'Our Windows build server does not support GStreamer yet') From 24d9f8f200a48d93c0e64995cc0eb8d37932d1a5 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 10 Feb 2012 00:17:21 +0100 Subject: [PATCH 077/116] Fix a bunch of pylint warnings --- mopidy/backends/base/playback.py | 2 -- mopidy/backends/spotify/__init__.py | 1 - mopidy/backends/spotify/library.py | 1 - mopidy/backends/spotify/session_manager.py | 1 - mopidy/backends/spotify/translator.py | 1 - mopidy/core.py | 6 ++---- mopidy/frontends/mpd/dispatcher.py | 3 ++- mopidy/frontends/mpris/objects.py | 3 ++- mopidy/gstreamer.py | 3 ++- mopidy/utils/settings.py | 1 - 10 files changed, 8 insertions(+), 14 deletions(-) diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 51fe0d3b..16ac75d1 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -2,8 +2,6 @@ import logging import random import time -from pykka.registry import ActorRegistry - from mopidy.listeners import BackendListener logger = logging.getLogger('mopidy.backends.base') diff --git a/mopidy/backends/spotify/__init__.py b/mopidy/backends/spotify/__init__.py index ad45014e..56775926 100644 --- a/mopidy/backends/spotify/__init__.py +++ b/mopidy/backends/spotify/__init__.py @@ -10,7 +10,6 @@ from mopidy.gstreamer import GStreamer logger = logging.getLogger('mopidy.backends.spotify') -ENCODING = 'utf-8' BITRATES = {96: 2, 160: 0, 320: 1} class SpotifyBackend(ThreadingActor, Backend): diff --git a/mopidy/backends/spotify/library.py b/mopidy/backends/spotify/library.py index 59aa9a2c..a080c7bd 100644 --- a/mopidy/backends/spotify/library.py +++ b/mopidy/backends/spotify/library.py @@ -4,7 +4,6 @@ import Queue from spotify import Link, SpotifyError from mopidy.backends.base import BaseLibraryProvider -from mopidy.backends.spotify import ENCODING from mopidy.backends.spotify.translator import SpotifyTranslator from mopidy.models import Playlist diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index 4b81db1f..af731e1b 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -1,4 +1,3 @@ -import glib import logging import os import threading diff --git a/mopidy/backends/spotify/translator.py b/mopidy/backends/spotify/translator.py index 27f4719b..2f47a42b 100644 --- a/mopidy/backends/spotify/translator.py +++ b/mopidy/backends/spotify/translator.py @@ -4,7 +4,6 @@ import logging from spotify import Link, SpotifyError from mopidy import settings -from mopidy.backends.spotify import ENCODING from mopidy.models import Artist, Album, Track, Playlist logger = logging.getLogger('mopidy.backends.spotify.translator') diff --git a/mopidy/core.py b/mopidy/core.py index 08c5e0d7..596e0fe5 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -12,14 +12,12 @@ gobject.threads_init() # so that GStreamer doesn't hijack e.g. ``--help``. # NOTE This naive fix does not support values like ``bar`` in # ``--gst-foo bar``. Use equals to pass values, like ``--gst-foo=bar``. -def is_gst_arg(arg): - return arg.startswith('--gst') or arg == '--help-gst' +def is_gst_arg(argument): + return argument.startswith('--gst') or argument == '--help-gst' gstreamer_args = [arg for arg in sys.argv[1:] if is_gst_arg(arg)] mopidy_args = [arg for arg in sys.argv[1:] if not is_gst_arg(arg)] sys.argv[1:] = gstreamer_args -from pykka.registry import ActorRegistry - from mopidy import (get_version, settings, OptionalDependencyError, SettingsError, DATA_PATH, SETTINGS_PATH, SETTINGS_FILE) from mopidy.gstreamer import GStreamer diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 5ee70a5b..2b012c7c 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -244,7 +244,8 @@ class MpdContext(object): """ if self._backend is None: backend_refs = ActorRegistry.get_by_class(Backend) - assert len(backend_refs) == 1, 'Expected exactly one running backend.' + assert len(backend_refs) == 1, \ + 'Expected exactly one running backend.' self._backend = backend_refs[0].proxy() return self._backend diff --git a/mopidy/frontends/mpris/objects.py b/mopidy/frontends/mpris/objects.py index e0a83e03..9ed1fe2c 100644 --- a/mopidy/frontends/mpris/objects.py +++ b/mopidy/frontends/mpris/objects.py @@ -81,7 +81,8 @@ class MprisObject(dbus.service.Object): def _connect_to_dbus(self): logger.debug(u'Connecting to D-Bus...') mainloop = dbus.mainloop.glib.DBusGMainLoop() - bus_name = dbus.service.BusName(BUS_NAME, dbus.SessionBus(mainloop=mainloop)) + bus_name = dbus.service.BusName(BUS_NAME, + dbus.SessionBus(mainloop=mainloop)) logger.info(u'Connected to D-Bus') return bus_name diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index ffb8c4f1..b6a8ab2b 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -332,7 +332,8 @@ class GStreamer(ThreadingActor): self._tee.send_event(event) def _handle_event_probe(self, teesrc, event): - if event.type == gst.EVENT_CUSTOM_DOWNSTREAM and event.has_name('mopidy-unlink-tee'): + if (event.type == gst.EVENT_CUSTOM_DOWNSTREAM + and event.has_name('mopidy-unlink-tee')): data = self._get_structure_data(event.get_structure()) output = teesrc.get_peer().get_parent() diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index fca4f337..ff449a61 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -2,7 +2,6 @@ from __future__ import absolute_import from copy import copy import getpass -import glib import logging import os from pprint import pformat From 2eae7aaae139be889b60f2806a48b837f0be5529 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 10 Feb 2012 00:30:18 +0100 Subject: [PATCH 078/116] Call ThreadingActor/etc constructors to please pylint --- mopidy/frontends/lastfm.py | 1 + mopidy/frontends/mpd/__init__.py | 1 + mopidy/frontends/mpris/__init__.py | 1 + mopidy/gstreamer.py | 1 + mopidy/mixers/alsa.py | 1 + mopidy/mixers/denon.py | 1 + mopidy/mixers/dummy.py | 1 + mopidy/mixers/gstreamer_software.py | 1 + mopidy/mixers/nad.py | 2 ++ mopidy/utils/network.py | 1 + 10 files changed, 11 insertions(+) diff --git a/mopidy/frontends/lastfm.py b/mopidy/frontends/lastfm.py index 125457cd..0e79024b 100644 --- a/mopidy/frontends/lastfm.py +++ b/mopidy/frontends/lastfm.py @@ -37,6 +37,7 @@ class LastfmFrontend(ThreadingActor, BackendListener): """ def __init__(self): + super(LastfmFrontend, self).__init__() self.lastfm = None self.last_start_time = None diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index b6adc09d..99134012 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -25,6 +25,7 @@ class MpdFrontend(actor.ThreadingActor, listeners.BackendListener): """ def __init__(self): + super(MpdFrontend, self).__init__() hostname = network.format_hostname(settings.MPD_SERVER_HOSTNAME) port = settings.MPD_SERVER_PORT diff --git a/mopidy/frontends/mpris/__init__.py b/mopidy/frontends/mpris/__init__.py index 579038ca..0f5d35c5 100644 --- a/mopidy/frontends/mpris/__init__.py +++ b/mopidy/frontends/mpris/__init__.py @@ -57,6 +57,7 @@ class MprisFrontend(ThreadingActor, BackendListener): """ def __init__(self): + super(MprisFrontend, self).__init__() self.indicate_server = None self.mpris_object = None diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index b6a8ab2b..282acd4e 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -25,6 +25,7 @@ class GStreamer(ThreadingActor): """ def __init__(self): + super(GStreamer, self).__init__() self._default_caps = gst.Caps(""" audio/x-raw-int, endianness=(int)1234, diff --git a/mopidy/mixers/alsa.py b/mopidy/mixers/alsa.py index ae4bd031..acb12e66 100644 --- a/mopidy/mixers/alsa.py +++ b/mopidy/mixers/alsa.py @@ -23,6 +23,7 @@ class AlsaMixer(ThreadingActor, BaseMixer): """ def __init__(self): + super(AlsaMixer, self).__init__() self._mixer = None def on_start(self): diff --git a/mopidy/mixers/denon.py b/mopidy/mixers/denon.py index d0dc5f54..e869dae8 100644 --- a/mopidy/mixers/denon.py +++ b/mopidy/mixers/denon.py @@ -26,6 +26,7 @@ class DenonMixer(ThreadingActor, BaseMixer): """ def __init__(self, *args, **kwargs): + super(DenonMixer, self).__init__(*arg, **kwargs) self._device = kwargs.get('device', None) self._levels = ['99'] + ["%(#)02d" % {'#': v} for v in range(0, 99)] self._volume = 0 diff --git a/mopidy/mixers/dummy.py b/mopidy/mixers/dummy.py index 23f96c4c..7262e83c 100644 --- a/mopidy/mixers/dummy.py +++ b/mopidy/mixers/dummy.py @@ -6,6 +6,7 @@ class DummyMixer(ThreadingActor, BaseMixer): """Mixer which just stores and reports the chosen volume.""" def __init__(self): + super(DummyMixer, self).__init__() self._volume = None def get_volume(self): diff --git a/mopidy/mixers/gstreamer_software.py b/mopidy/mixers/gstreamer_software.py index 523c3387..a38692db 100644 --- a/mopidy/mixers/gstreamer_software.py +++ b/mopidy/mixers/gstreamer_software.py @@ -8,6 +8,7 @@ class GStreamerSoftwareMixer(ThreadingActor, BaseMixer): """Mixer which uses GStreamer to control volume in software.""" def __init__(self): + super(GStreamerSoftwareMixer, self).__init__() self.output = None def on_start(self): diff --git a/mopidy/mixers/nad.py b/mopidy/mixers/nad.py index 4dbf27be..78473308 100644 --- a/mopidy/mixers/nad.py +++ b/mopidy/mixers/nad.py @@ -37,6 +37,7 @@ class NadMixer(ThreadingActor, BaseMixer): """ def __init__(self): + super(NadMixer, self).__init__() self._volume_cache = None self._nad_talker = NadTalker.start().proxy() @@ -71,6 +72,7 @@ class NadTalker(ThreadingActor): _nad_volume = None def __init__(self): + super(NadTalker, self).__init__() self._device = None def on_start(self): diff --git a/mopidy/utils/network.py b/mopidy/utils/network.py index 5079fe7c..0a0928ce 100644 --- a/mopidy/utils/network.py +++ b/mopidy/utils/network.py @@ -297,6 +297,7 @@ class LineProtocol(ThreadingActor): encoding = 'utf-8' def __init__(self, connection): + super(LineProtocol, self).__init__() self.connection = connection self.prevent_timeout = False self.recv_buffer = '' From 9e8e02295dd468b72295ad71258258b7544fd5c7 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 10 Feb 2012 00:31:42 +0100 Subject: [PATCH 079/116] Fix typo --- mopidy/mixers/denon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/mixers/denon.py b/mopidy/mixers/denon.py index e869dae8..02b86c38 100644 --- a/mopidy/mixers/denon.py +++ b/mopidy/mixers/denon.py @@ -26,7 +26,7 @@ class DenonMixer(ThreadingActor, BaseMixer): """ def __init__(self, *args, **kwargs): - super(DenonMixer, self).__init__(*arg, **kwargs) + super(DenonMixer, self).__init__(*args, **kwargs) self._device = kwargs.get('device', None) self._levels = ['99'] + ["%(#)02d" % {'#': v} for v in range(0, 99)] self._volume = 0 From fc5f6df740c4f4444a96d6b06a157a2d862c3984 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 10 Feb 2012 00:34:33 +0100 Subject: [PATCH 080/116] Simplify DenonMixer constructor args --- mopidy/mixers/denon.py | 6 +++--- tests/mixers/denon_test.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mopidy/mixers/denon.py b/mopidy/mixers/denon.py index 02b86c38..b0abbdb9 100644 --- a/mopidy/mixers/denon.py +++ b/mopidy/mixers/denon.py @@ -25,9 +25,9 @@ class DenonMixer(ThreadingActor, BaseMixer): - :attr:`mopidy.settings.MIXER_EXT_PORT` -- Example: ``/dev/ttyUSB0`` """ - def __init__(self, *args, **kwargs): - super(DenonMixer, self).__init__(*args, **kwargs) - self._device = kwargs.get('device', None) + def __init__(self, device=None): + super(DenonMixer, self).__init__() + self._device = device self._levels = ['99'] + ["%(#)02d" % {'#': v} for v in range(0, 99)] self._volume = 0 diff --git a/tests/mixers/denon_test.py b/tests/mixers/denon_test.py index 7fec3c82..cdfe0772 100644 --- a/tests/mixers/denon_test.py +++ b/tests/mixers/denon_test.py @@ -34,7 +34,7 @@ class DenonMixerTest(BaseMixerTest, unittest.TestCase): def setUp(self): self.device = DenonMixerDeviceMock() - self.mixer = DenonMixer(None, device=self.device) + self.mixer = DenonMixer(device=self.device) def test_reopen_device(self): self.device._open = False From 844b219565ed709d8535af552c14c9c281014379 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 10 Feb 2012 00:42:39 +0100 Subject: [PATCH 081/116] Potential fix for gst LinkError (#144) suggested by adamcik --- mopidy/gstreamer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 282acd4e..5d393b66 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -83,6 +83,8 @@ class GStreamer(ThreadingActor): def _on_new_pad(self, source, pad, target_pad): if not pad.is_linked(): + if target_pad.is_linked(): + target_pad.unlink(target_pad.get_peer()) pad.link(target_pad) def _on_message(self, bus, message): From eb8ecc33a2320a9f6aa489180c16defaa6b8b722 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 13 Feb 2012 09:37:21 +0100 Subject: [PATCH 082/116] Switch arguments so that unlink() is passed the sinkpad --- mopidy/gstreamer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 5d393b66..c33dbe03 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -84,7 +84,7 @@ class GStreamer(ThreadingActor): def _on_new_pad(self, source, pad, target_pad): if not pad.is_linked(): if target_pad.is_linked(): - target_pad.unlink(target_pad.get_peer()) + target_pad.get_peer().unlink(target_pad) pad.link(target_pad) def _on_message(self, bus, message): From 904adc938ee4930f2b410c4c5d52689a56d4f079 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 13 Feb 2012 09:52:09 +0100 Subject: [PATCH 083/116] Update changelog --- docs/changes.rst | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 528edaa8..b389b7be 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -26,6 +26,11 @@ v0.7.0 (in development) directories does not exist. This was especially the case on OS X, where ``~/.config`` doesn't exist for most users. +**Bug fixes** + +- Fix ``gst.LinkError`` which appeared when using newer versions of GStreamer, + e.g. on Ubuntu 12.04 Alpha. (Fixes: :issue:`144`) + v0.6.1 (2011-12-28) =================== @@ -220,7 +225,7 @@ This is a bug fix release fixing audio problems on older GStreamer and some minor bugs. -**Bugfixes** +**Bug fixes** - Fix broken audio on at least GStreamer 0.10.30, which affects Ubuntu 10.10. The GStreamer `appsrc` bin wasn't being linked due to lack of default caps. @@ -347,7 +352,7 @@ v0.3.1 (2011-01-22) A couple of fixes to the 0.3.0 release is needed to get a smooth installation. -**Bugfixes** +**Bug fixes** - The Spotify application key was missing from the Python package. @@ -516,7 +521,7 @@ v0.2.1 (2011-01-07) This is a maintenance release without any new features. -**Bugfixes** +**Bug fixes** - 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 @@ -846,7 +851,7 @@ As always, report problems at our IRC channel or our issue tracker. Thanks! - Merged the ``gstreamer`` branch from Thomas Adamcik: - - More than 200 new tests, and thus several bugfixes to existing code. + - More than 200 new tests, and thus several bug fixes to existing code. - Several new generic features, like shuffle, consume, and playlist repeat. (Fixes: :issue:`3`) - **[Work in Progress]** A new backend for playing music from a local music From 219e723974e98d9c6da4e1f94cb6ff4be84c5dab Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 13 Feb 2012 10:30:46 +0100 Subject: [PATCH 084/116] Fix crash on mismatching quotation (fixes #137) --- docs/changes.rst | 3 +++ mopidy/frontends/mpd/protocol/music_db.py | 10 ++++++++-- tests/frontends/mpd/protocol/regression_test.py | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index b389b7be..69de8558 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -31,6 +31,9 @@ v0.7.0 (in development) - Fix ``gst.LinkError`` which appeared when using newer versions of GStreamer, e.g. on Ubuntu 12.04 Alpha. (Fixes: :issue:`144`) +- Fix crash on mismatching quotation in ``list`` MPD queries. (Fixes: + :issue:`137`) + v0.6.1 (2011-12-28) =================== diff --git a/mopidy/frontends/mpd/protocol/music_db.py b/mopidy/frontends/mpd/protocol/music_db.py index 299fce97..cde2754a 100644 --- a/mopidy/frontends/mpd/protocol/music_db.py +++ b/mopidy/frontends/mpd/protocol/music_db.py @@ -189,8 +189,14 @@ def _list_build_query(field, mpd_query): """Converts a ``list`` query to a Mopidy query.""" if mpd_query is None: return {} - # shlex does not seem to be friends with unicode objects - tokens = shlex.split(mpd_query.encode('utf-8')) + try: + # shlex does not seem to be friends with unicode objects + tokens = shlex.split(mpd_query.encode('utf-8')) + except ValueError as error: + if error.message == 'No closing quotation': + raise MpdArgError(u'Invalid unquoted character', command=u'list') + else: + raise error tokens = [t.decode('utf-8') for t in tokens] if len(tokens) == 1: if field == u'album': diff --git a/tests/frontends/mpd/protocol/regression_test.py b/tests/frontends/mpd/protocol/regression_test.py index d4e4b2aa..7f214efa 100644 --- a/tests/frontends/mpd/protocol/regression_test.py +++ b/tests/frontends/mpd/protocol/regression_test.py @@ -146,3 +146,19 @@ class IssueGH113RegressionTest(protocol.BaseTestCase): self.sendRequest( r'listplaylistinfo "all lart spotify:track:\\w\\{22\\} pastes"') self.assertInResponse('OK') + + +class IssueGH137RegressionTest(protocol.BaseTestCase): + """ + The issue: https://github.com/mopidy/mopidy/issues/137 + + How to reproduce: + + - Send "list" query with mismatching quotes + """ + + def test(self): + self.sendRequest(u'list Date Artist "Anita Ward" ' + u'Album "This Is Remixed Hits - Mashups & Rare 12" Mixes"') + + self.assertInResponse('ACK [2@0] {list} Invalid unquoted character') From 86a4d6c36edf2619e45009d995ecf82f7dae908a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 13 Feb 2012 10:33:17 +0100 Subject: [PATCH 085/116] Avoid HTTP to HTTPS redirect --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index b4dbc35b..f8b4ffc3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -241,4 +241,4 @@ latex_documents = [ needs_sphinx = '1.0' -extlinks = {'issue': ('http://github.com/mopidy/mopidy/issues/%s', 'GH-')} +extlinks = {'issue': ('https://github.com/mopidy/mopidy/issues/%s', 'GH-')} From 029192876c2d0d2438b15928dc0685f61f490241 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 13 Feb 2012 10:34:17 +0100 Subject: [PATCH 086/116] Remove 'Bug fixes' header only used for bugfix releases --- docs/changes.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 69de8558..20ad3169 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -26,8 +26,6 @@ v0.7.0 (in development) directories does not exist. This was especially the case on OS X, where ``~/.config`` doesn't exist for most users. -**Bug fixes** - - Fix ``gst.LinkError`` which appeared when using newer versions of GStreamer, e.g. on Ubuntu 12.04 Alpha. (Fixes: :issue:`144`) From 0269686453a5035518106a5e1aa5243684b3d20c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 24 Feb 2012 23:50:45 +0100 Subject: [PATCH 087/116] Fix volume response when capping volume --- docs/changes.rst | 6 ++++++ mopidy/mixers/base.py | 19 ++++++++++++++++--- tests/mixers/dummy_test.py | 7 ++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 20ad3169..d955978d 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -32,6 +32,12 @@ v0.7.0 (in development) - Fix crash on mismatching quotation in ``list`` MPD queries. (Fixes: :issue:`137`) +- Volume is now reported to be the same as the volume was set to, also when + internal rounding have been done due to + :attr:`mopidy.settings.MIXER_MAX_VOLUME` has been set to cap the volume. This + should make it possible to manage capped volume from clients that only + increase volume with one step at a time, like ncmpcpp does. + v0.6.1 (2011-12-28) =================== diff --git a/mopidy/mixers/base.py b/mopidy/mixers/base.py index 8798076a..48df5325 100644 --- a/mopidy/mixers/base.py +++ b/mopidy/mixers/base.py @@ -21,19 +21,32 @@ class BaseMixer(object): Integer in range [0, 100]. :class:`None` if unknown. Values below 0 is equal to 0. Values above 100 is equal to 100. """ + if not hasattr(self, '_user_volume'): + self._user_volume = 0 volume = self.get_volume() if volume is None: return None - return int(volume / self.amplification_factor) + elif not self.amplification_factor < 1: + return volume + else: + user_volume = int(volume / self.amplification_factor) + if (user_volume - 1) <= self._user_volume <= (user_volume + 1): + return self._user_volume + else: + return user_volume @volume.setter def volume(self, volume): - volume = int(int(volume) * self.amplification_factor) + if not hasattr(self, '_user_volume'): + self._user_volume = 0 + volume = int(volume) if volume < 0: volume = 0 elif volume > 100: volume = 100 - self.set_volume(volume) + self._user_volume = volume + real_volume = int(volume * self.amplification_factor) + self.set_volume(real_volume) self._trigger_volume_changed() def get_volume(self): diff --git a/tests/mixers/dummy_test.py b/tests/mixers/dummy_test.py index 8ae8623c..f9418d7a 100644 --- a/tests/mixers/dummy_test.py +++ b/tests/mixers/dummy_test.py @@ -4,7 +4,7 @@ from tests import unittest from tests.mixers.base_test import BaseMixerTest -class DenonMixerTest(BaseMixerTest, unittest.TestCase): +class DummyMixerTest(BaseMixerTest, unittest.TestCase): mixer_class = DummyMixer def test_set_volume_is_capped(self): @@ -16,3 +16,8 @@ class DenonMixerTest(BaseMixerTest, unittest.TestCase): self.mixer.amplification_factor = 0.5 self.mixer._volume = 50 self.assertEquals(self.mixer.volume, 100) + + def test_get_volume_get_the_same_number_as_was_set(self): + self.mixer.amplification_factor = 0.5 + self.mixer.volume = 13 + self.assertEquals(self.mixer.volume, 13) From d0b8d1942f702c544d2de0248314eb54db7abdee Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 25 Feb 2012 00:24:41 +0100 Subject: [PATCH 088/116] Fix error in logger name --- mopidy/mixers/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/mixers/base.py b/mopidy/mixers/base.py index 48df5325..4690be61 100644 --- a/mopidy/mixers/base.py +++ b/mopidy/mixers/base.py @@ -2,7 +2,7 @@ import logging from mopidy import listeners, settings -logger = logging.getLogger('mopdy.mixers') +logger = logging.getLogger('mopidy.mixers') class BaseMixer(object): """ From b82b4d87cecc8eec6411e83e9c3b8b96981ab005 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 25 Feb 2012 00:25:12 +0100 Subject: [PATCH 089/116] Simplify if statement --- mopidy/mixers/base.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mopidy/mixers/base.py b/mopidy/mixers/base.py index 4690be61..82783be1 100644 --- a/mopidy/mixers/base.py +++ b/mopidy/mixers/base.py @@ -24,9 +24,7 @@ class BaseMixer(object): if not hasattr(self, '_user_volume'): self._user_volume = 0 volume = self.get_volume() - if volume is None: - return None - elif not self.amplification_factor < 1: + if volume is None or not self.amplification_factor < 1: return volume else: user_volume = int(volume / self.amplification_factor) From 0f6e6ab4256ae36a5c3d51367a48e1018a325495 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 25 Feb 2012 01:02:41 +0100 Subject: [PATCH 090/116] Update changelog for v0.7.0 release --- docs/changes.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index d955978d..65bd6b4b 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -4,12 +4,13 @@ Changes This change log is used to track all major changes to Mopidy. -v0.7.0 (in development) -======================= +v0.7.0 (2012-02-25) +=================== -**Important changes** - -- Nothing yet. +Not a big release with regard to features, but this release got some +performance improvements over v0.6, especially for slower Atom systems. It also +fixes a couple of other bugs, including one which made Mopidy crash when using +GStreamer from the prereleases of Ubuntu 12.04. **Changes** From 42d41d6fe71caa81aca8c7293d2c136622068c60 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 25 Feb 2012 01:06:52 +0100 Subject: [PATCH 091/116] Prepare for v0.8 development --- docs/changes.rst | 12 ++++++++++++ mopidy/__init__.py | 2 +- tests/version_test.py | 5 +++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 65bd6b4b..4e5f5cb4 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -4,6 +4,18 @@ Changes This change log is used to track all major changes to Mopidy. +v0.8.0 (in development) +======================= + +**Important changes** + +- Nothing so far + +**Changes** + +- Nothing so far + + v0.7.0 (2012-02-25) =================== diff --git a/mopidy/__init__.py b/mopidy/__init__.py index b94378b2..f4167e3f 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -8,7 +8,7 @@ import os from subprocess import PIPE, Popen -VERSION = (0, 7, 0) +VERSION = (0, 8, 0) DATA_PATH = os.path.join(str(glib.get_user_data_dir()), 'mopidy') CACHE_PATH = os.path.join(str(glib.get_user_cache_dir()), 'mopidy') diff --git a/tests/version_test.py b/tests/version_test.py index dbaed0a2..a7fd82a4 100644 --- a/tests/version_test.py +++ b/tests/version_test.py @@ -24,8 +24,9 @@ class VersionTest(unittest.TestCase): self.assert_(SV('0.4.1') < SV('0.5.0')) self.assert_(SV('0.5.0') < SV('0.6.0')) self.assert_(SV('0.6.0') < SV('0.6.1')) - self.assert_(SV('0.6.1') < SV(get_plain_version())) - self.assert_(SV(get_plain_version()) < SV('0.7.1')) + self.assert_(SV('0.6.1') < SV('0.7.0')) + self.assert_(SV('0.7.0') < SV(get_plain_version())) + self.assert_(SV(get_plain_version()) < SV('0.8.1')) def test_get_platform_contains_platform(self): self.assert_(platform.platform() in get_platform()) From ee0c7c1af564465d441af583a1f627a258f4c00d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 4 Mar 2012 23:26:24 +0100 Subject: [PATCH 092/116] Add __version__ to mopidy module --- docs/changes.rst | 7 ++++--- docs/conf.py | 12 +++++++++--- mopidy/__init__.py | 11 ++++------- setup.py | 6 +++++- tests/version_test.py | 8 ++++---- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 4e5f5cb4..a6b7e361 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -4,8 +4,8 @@ Changes This change log is used to track all major changes to Mopidy. -v0.8.0 (in development) -======================= +v0.8 (in development) +===================== **Important changes** @@ -13,7 +13,8 @@ v0.8.0 (in development) **Changes** -- Nothing so far +- Change from version tuple at :attr:`mopidy.VERSION` to :pep:`386` compliant + version string at :attr:`mopidy.__version__` to conform to :pep:`396`. v0.7.0 (2012-02-25) diff --git a/docs/conf.py b/docs/conf.py index f8b4ffc3..a33a8f2d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,7 +11,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import re +import sys class Mock(object): def __init__(self, *args, **kwargs): @@ -49,6 +51,11 @@ MOCK_MODULES = [ for mod_name in MOCK_MODULES: sys.modules[mod_name] = Mock() +def get_version(): + init_py = open('../mopidy/__init__.py').read() + metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", init_py)) + return metadata['version'] + # 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. @@ -87,8 +94,7 @@ copyright = u'2010-2012, Stein Magnus Jodal and contributors' # built documents. # # The full version, including alpha/beta/rc tags. -import mopidy -release = mopidy.get_version() +release = get_version() # The short X.Y version. version = '.'.join(release.split('.')[:2]) diff --git a/mopidy/__init__.py b/mopidy/__init__.py index f4167e3f..b2d9afa0 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -1,14 +1,14 @@ -import platform import sys if not (2, 6) <= sys.version_info < (3,): sys.exit(u'Mopidy requires Python >= 2.6, < 3') -import glib import os - +import platform from subprocess import PIPE, Popen -VERSION = (0, 8, 0) +import glib + +__version__ = '0.8' DATA_PATH = os.path.join(str(glib.get_user_data_dir()), 'mopidy') CACHE_PATH = os.path.join(str(glib.get_user_cache_dir()), 'mopidy') @@ -30,9 +30,6 @@ def get_git_version(): version = version[1:] return version -def get_plain_version(): - return '.'.join(map(str, VERSION)) - def get_platform(): return platform.platform() diff --git a/setup.py b/setup.py index a8cf8ed1..ae6cc699 100644 --- a/setup.py +++ b/setup.py @@ -6,9 +6,13 @@ from distutils.core import setup from distutils.command.install_data import install_data from distutils.command.install import INSTALL_SCHEMES import os +import re import sys -from mopidy import get_version +def get_version(): + init_py = open('mopidy/__init__.py').read() + metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", init_py)) + return metadata['version'] class osx_install_data(install_data): # On MacOS, the platform-specific lib dir is diff --git a/tests/version_test.py b/tests/version_test.py index a7fd82a4..86060693 100644 --- a/tests/version_test.py +++ b/tests/version_test.py @@ -1,14 +1,14 @@ from distutils.version import StrictVersion as SV import platform -from mopidy import get_plain_version, get_platform, get_python +from mopidy import __version__, get_platform, get_python from tests import unittest class VersionTest(unittest.TestCase): def test_current_version_is_parsable_as_a_strict_version_number(self): - SV(get_plain_version()) + SV(__version__) def test_versions_can_be_strictly_ordered(self): self.assert_(SV('0.1.0a0') < SV('0.1.0a1')) @@ -25,8 +25,8 @@ class VersionTest(unittest.TestCase): self.assert_(SV('0.5.0') < SV('0.6.0')) self.assert_(SV('0.6.0') < SV('0.6.1')) self.assert_(SV('0.6.1') < SV('0.7.0')) - self.assert_(SV('0.7.0') < SV(get_plain_version())) - self.assert_(SV(get_plain_version()) < SV('0.8.1')) + self.assert_(SV('0.7.0') < SV(__version__)) + self.assert_(SV(__version__) < SV('0.8.1')) def test_get_platform_contains_platform(self): self.assert_(platform.platform() in get_platform()) From 121ae0c2205ddc19ea0182b6e9d8fb04e8a6cb7c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 6 Mar 2012 11:18:10 +0100 Subject: [PATCH 093/116] Fix use of nonexistant function --- mopidy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/__init__.py b/mopidy/__init__.py index b2d9afa0..e0bce88c 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -19,7 +19,7 @@ def get_version(): try: return get_git_version() except EnvironmentError: - return get_plain_version() + return __version__ def get_git_version(): process = Popen(['git', 'describe'], stdout=PIPE, stderr=PIPE) From a987f7e5c1a3f8983c0346fa80d6fb39cbe0d4bc Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Thu, 8 Mar 2012 09:52:55 +0100 Subject: [PATCH 094/116] It's 'Last.fm', not 'Last.FM' --- docs/installation/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 8e62421c..fae50a1b 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -37,7 +37,7 @@ dependencies installed. - For Spotify support, you need libspotify and pyspotify. See :doc:`libspotify`. - - To scrobble your played tracks to Last.FM, you need pylast:: + - To scrobble your played tracks to Last.fm, you need pylast:: sudo pip install -U pylast From 4ea3bb11674b78480e09ee4e49872dcd7afcf39b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 22 Apr 2012 01:26:27 +0200 Subject: [PATCH 095/116] Don't override notify_main_thread, which has a sensible default implementation --- mopidy/backends/spotify/session_manager.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index af731e1b..2ae4ed2d 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -96,10 +96,6 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): """Callback used by pyspotify""" logger.debug(u'User message: %s', message.strip()) - def notify_main_thread(self, session): - """Callback used by pyspotify""" - logger.debug(u'notify_main_thread() called') - def music_delivery(self, session, frames, frame_size, num_frames, sample_type, sample_rate, channels): """Callback used by pyspotify""" From 1dae3442e0373a2cc6b4d62e99e395a06998ccb4 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 22 Apr 2012 01:30:20 +0200 Subject: [PATCH 096/116] Release v0.7.1 --- docs/changes.rst | 12 ++++++++++++ mopidy/__init__.py | 2 +- tests/version_test.py | 5 +++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 65bd6b4b..5fd5212a 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -4,6 +4,18 @@ Changes This change log is used to track all major changes to Mopidy. + +v0.7.1 (2012-04-22) +=================== + +This is a maintenance release to make Mopidy 0.6 work with pyspotify >= 1.7. + +**Changes** + +- Don't override pyspotify's ``notify_main_thread`` callback. The default + implementation is sensible, while our override did nothing. + + v0.7.0 (2012-02-25) =================== diff --git a/mopidy/__init__.py b/mopidy/__init__.py index b94378b2..c433d47f 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -8,7 +8,7 @@ import os from subprocess import PIPE, Popen -VERSION = (0, 7, 0) +VERSION = (0, 7, 1) DATA_PATH = os.path.join(str(glib.get_user_data_dir()), 'mopidy') CACHE_PATH = os.path.join(str(glib.get_user_cache_dir()), 'mopidy') diff --git a/tests/version_test.py b/tests/version_test.py index dbaed0a2..58965589 100644 --- a/tests/version_test.py +++ b/tests/version_test.py @@ -24,8 +24,9 @@ class VersionTest(unittest.TestCase): self.assert_(SV('0.4.1') < SV('0.5.0')) self.assert_(SV('0.5.0') < SV('0.6.0')) self.assert_(SV('0.6.0') < SV('0.6.1')) - self.assert_(SV('0.6.1') < SV(get_plain_version())) - self.assert_(SV(get_plain_version()) < SV('0.7.1')) + self.assert_(SV('0.6.1') < SV('0.7.0')) + self.assert_(SV('0.7.0') < SV(get_plain_version())) + self.assert_(SV(get_plain_version()) < SV('0.7.2')) def test_get_platform_contains_platform(self): self.assert_(platform.platform() in get_platform()) From aac7f738689400c5d1a30dfdebd9688f0eeeb4b2 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 22 Apr 2012 01:41:12 +0200 Subject: [PATCH 097/116] Fix typo --- docs/changes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index 93c0fdbc..fa316c4d 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -21,7 +21,7 @@ v0.8 (in development) v0.7.1 (2012-04-22) =================== -This is a maintenance release to make Mopidy 0.6 work with pyspotify >= 1.7. +This is a maintenance release to make Mopidy 0.7 work with pyspotify >= 1.7. **Changes** From 1f12951fa260049bc17e30edd0ab4967d085fb4b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 7 May 2012 22:56:09 +0200 Subject: [PATCH 098/116] Prepare for maintenance release --- docs/changes.rst | 9 ++++----- mopidy/__init__.py | 2 +- tests/version_test.py | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index fa316c4d..84f5ffca 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -5,12 +5,11 @@ Changes This change log is used to track all major changes to Mopidy. -v0.8 (in development) -===================== +v0.7.2 (2012-05-07) +=================== -**Important changes** - -- Nothing so far +This is a maintenance release to make Mopidy 0.7 build on systems without all +of Mopidy's runtime dependencies, like Launchpad PPAs. **Changes** diff --git a/mopidy/__init__.py b/mopidy/__init__.py index e0bce88c..8a2b469e 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -8,7 +8,7 @@ from subprocess import PIPE, Popen import glib -__version__ = '0.8' +__version__ = '0.7.2' DATA_PATH = os.path.join(str(glib.get_user_data_dir()), 'mopidy') CACHE_PATH = os.path.join(str(glib.get_user_cache_dir()), 'mopidy') diff --git a/tests/version_test.py b/tests/version_test.py index f3ae3e0b..b1c0b90e 100644 --- a/tests/version_test.py +++ b/tests/version_test.py @@ -27,7 +27,7 @@ class VersionTest(unittest.TestCase): self.assert_(SV('0.6.1') < SV('0.7.0')) self.assert_(SV('0.7.0') < SV('0.7.1')) self.assert_(SV('0.7.1') < SV(__version__)) - self.assert_(SV(__version__) < SV('0.8.1')) + self.assert_(SV(__version__) < SV('0.8.0')) def test_get_platform_contains_platform(self): self.assert_(platform.platform() in get_platform()) From 0e66ffe6a5a7f227fd08ac94a98097939ddef4ab Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 10 Aug 2012 23:12:04 +0200 Subject: [PATCH 099/116] Add locale_decode util function that decodes bytestrings using the current locale's encoding --- mopidy/utils/__init__.py | 7 +++++++ tests/utils/decode_test.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 tests/utils/decode_test.py diff --git a/mopidy/utils/__init__.py b/mopidy/utils/__init__.py index 9d7532a0..00129cdd 100644 --- a/mopidy/utils/__init__.py +++ b/mopidy/utils/__init__.py @@ -1,3 +1,4 @@ +import locale import logging import os import sys @@ -29,3 +30,9 @@ def get_class(name): except (ImportError, AttributeError): raise ImportError("Couldn't load: %s" % name) return class_object + +def locale_decode(bytestr): + try: + return unicode(bytestr) + except UnicodeError: + return str(bytestr).decode(locale.getpreferredencoding()) diff --git a/tests/utils/decode_test.py b/tests/utils/decode_test.py new file mode 100644 index 00000000..edbfe651 --- /dev/null +++ b/tests/utils/decode_test.py @@ -0,0 +1,38 @@ +import mock + +from mopidy.utils import locale_decode + +from tests import unittest + + +@mock.patch('mopidy.utils.locale.getpreferredencoding') +class LocaleDecodeTest(unittest.TestCase): + def test_can_decode_utf8_strings_with_french_content(self, mock): + mock.return_value = 'UTF-8' + + result = locale_decode( + '[Errno 98] Adresse d\xc3\xa9j\xc3\xa0 utilis\xc3\xa9e') + + self.assertEquals(u'[Errno 98] Adresse d\xe9j\xe0 utilis\xe9e', result) + + def test_can_decode_an_ioerror_with_french_content(self, mock): + mock.return_value = 'UTF-8' + + error = IOError(98, 'Adresse d\xc3\xa9j\xc3\xa0 utilis\xc3\xa9e') + result = locale_decode(error) + + self.assertEquals(u'[Errno 98] Adresse d\xe9j\xe0 utilis\xe9e', result) + + def test_does_not_use_locale_to_decode_unicode_strings(self, mock): + mock.return_value = 'UTF-8' + + locale_decode(u'abc') + + self.assertFalse(mock.called) + + def test_does_not_use_locale_to_decode_ascii_bytestrings(self, mock): + mock.return_value = 'UTF-8' + + locale_decode('abc') + + self.assertFalse(mock.called) From d6f17b4cf00868ceaac59d1867ba9ccfabcc8ede Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 10 Aug 2012 23:17:41 +0200 Subject: [PATCH 100/116] Decode messages from IOError before logging them IOError messages are bytestrings, often in the language of the system, so they may include non-ASCII characters. Thus, we must decode them using the locale's preferred encoding to get Unicode objects we safely can pass on for logging the IOError. --- mopidy/backends/local/translator.py | 9 +++++---- mopidy/frontends/mpd/__init__.py | 6 +++--- mopidy/utils/network.py | 6 ++++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/mopidy/backends/local/translator.py b/mopidy/backends/local/translator.py index be7ab8a8..3b610a94 100644 --- a/mopidy/backends/local/translator.py +++ b/mopidy/backends/local/translator.py @@ -4,6 +4,7 @@ import os logger = logging.getLogger('mopidy.backends.local.translator') from mopidy.models import Track, Artist, Album +from mopidy.utils import locale_decode from mopidy.utils.path import path_to_uri def parse_m3u(file_path): @@ -33,8 +34,8 @@ def parse_m3u(file_path): try: with open(file_path) as m3u: contents = m3u.readlines() - except IOError, e: - logger.error('Couldn\'t open m3u: %s', e) + except IOError as error: + logger.error('Couldn\'t open m3u: %s', locale_decode(error)) return uris for line in contents: @@ -61,8 +62,8 @@ def parse_mpd_tag_cache(tag_cache, music_dir=''): try: with open(tag_cache) as library: contents = library.read() - except IOError, e: - logger.error('Could not open tag cache: %s', e) + except IOError as error: + logger.error('Could not open tag cache: %s', locale_decode(error)) return tracks current = {} diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index 99134012..e8b2aabe 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -5,7 +5,7 @@ from pykka import registry, actor from mopidy import listeners, settings from mopidy.frontends.mpd import dispatcher, protocol -from mopidy.utils import network, process, log +from mopidy.utils import locale_decode, log, network, process logger = logging.getLogger('mopidy.frontends.mpd') @@ -32,8 +32,8 @@ class MpdFrontend(actor.ThreadingActor, listeners.BackendListener): try: network.Server(hostname, port, protocol=MpdSession, max_connections=settings.MPD_SERVER_MAX_CONNECTIONS) - except IOError, e: - logger.error(u'MPD server startup failed: %s', e) + except IOError as error: + logger.error(u'MPD server startup failed: %s', locale_decode(error)) sys.exit(1) logger.info(u'MPD server running at [%s]:%s', hostname, port) diff --git a/mopidy/utils/network.py b/mopidy/utils/network.py index 0a0928ce..4b8a9ac9 100644 --- a/mopidy/utils/network.py +++ b/mopidy/utils/network.py @@ -9,6 +9,8 @@ from pykka import ActorDeadError from pykka.actor import ThreadingActor from pykka.registry import ActorRegistry +from mopidy.utils import locale_decode + logger = logging.getLogger('mopidy.utils.server') class ShouldRetrySocketCall(Exception): @@ -21,9 +23,9 @@ def try_ipv6_socket(): try: socket.socket(socket.AF_INET6).close() return True - except IOError, e: + except IOError as error: logger.debug(u'Platform supports IPv6, but socket ' - 'creation failed, disabling: %s', e) + 'creation failed, disabling: %s', locale_decode(error)) return False #: Boolean value that indicates if creating an IPv6 socket will succeed. From f392a7cccb485ce16f64d5b2109e710838351a5a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 11 Aug 2012 00:29:04 +0200 Subject: [PATCH 101/116] Update changelog --- docs/changes.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index 84f5ffca..1e0900d4 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -4,6 +4,14 @@ Changes This change log is used to track all major changes to Mopidy. +v0.7.3 (in development) +======================= + +**Changes** + +- Fixed crash when logging :exc:`IOError` exceptions on systems using languages + with non-ASCII characters, like French. + v0.7.2 (2012-05-07) =================== From 436fd7815d6f967aa0fc53dbe10f7f516e7cd64a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 11 Aug 2012 00:31:58 +0200 Subject: [PATCH 102/116] Move the Spotify cache to a subdir of the Mopidy cache --- docs/changes.rst | 4 ++++ mopidy/backends/spotify/session_manager.py | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 1e0900d4..a93369ea 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -12,6 +12,10 @@ v0.7.3 (in development) - Fixed crash when logging :exc:`IOError` exceptions on systems using languages with non-ASCII characters, like French. +- Move the default location of the Spotify cache from `~/.cache/mopidy` to + `~/.cache/mopidy/spotify`. You can change this by setting + :attr:`mopidy.settings.SPOTIFY_CACHE_PATH`. + v0.7.2 (2012-05-07) =================== diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index 2ae4ed2d..3794513c 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -23,8 +23,9 @@ logger = logging.getLogger('mopidy.backends.spotify.session_manager') class SpotifySessionManager(BaseThread, PyspotifySessionManager): - cache_location = settings.SPOTIFY_CACHE_PATH or CACHE_PATH - settings_location = settings.SPOTIFY_CACHE_PATH or CACHE_PATH + cache_location = (settings.SPOTIFY_CACHE_PATH + or os.path.join(CACHE_PATH, 'spotify')) + settings_location = cache_location appkey_file = os.path.join(os.path.dirname(__file__), 'spotify_appkey.key') user_agent = 'Mopidy %s' % get_version() From d78d62c68ce4c88cd3ac7e6fa89f9936e4ef7521 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 25 Feb 2012 00:52:44 +0100 Subject: [PATCH 103/116] Hack to speed up Spotify backend startup with clean cache from 35s to 12s The time Improvement is probably a magnitude or two larger on outdated caches. --- docs/changes.rst | 4 ++++ mopidy/backends/spotify/session_manager.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index a93369ea..7b923e1a 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -16,6 +16,10 @@ v0.7.3 (in development) `~/.cache/mopidy/spotify`. You can change this by setting :attr:`mopidy.settings.SPOTIFY_CACHE_PATH`. +- Reduce time required to update the Spotify cache on startup. One one + system/Spotify account, the time from clean cache to ready for use was + reduced from 35s to 12s. + v0.7.2 (2012-05-07) =================== diff --git a/mopidy/backends/spotify/session_manager.py b/mopidy/backends/spotify/session_manager.py index 3794513c..481f7a94 100644 --- a/mopidy/backends/spotify/session_manager.py +++ b/mopidy/backends/spotify/session_manager.py @@ -43,6 +43,8 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): self.container_manager = None self.playlist_manager = None + self._initial_data_receive_completed = False + def run_inside_try(self): self.setup() self.connect() @@ -126,6 +128,17 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): def log_message(self, session, data): """Callback used by pyspotify""" logger.debug(u'System message: %s' % data.strip()) + if 'offline-mgr' in data and 'files unlocked' in data: + # XXX This is a very very fragile and ugly hack, but we get no + # proper event when libspotify is done with initial data loading. + # We delay the expensive refresh of Mopidy's stored playlists until + # this message arrives. This way, we avoid doing the refresh once + # for every playlist or other change. This reduces the time from + # startup until the Spotify backend is ready from 35s to 12s in one + # test with clean Spotify cache. In cases with an outdated cache + # the time improvements should be a lot better. + self._initial_data_receive_completed = True + self.refresh_stored_playlists() def end_of_track(self, session): """Callback used by pyspotify""" @@ -135,6 +148,9 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager): def refresh_stored_playlists(self): """Refresh the stored playlists in the backend with fresh meta data from Spotify""" + if not self._initial_data_receive_completed: + logger.debug(u'Still getting data; skipped refresh of playlists') + return playlists = map(SpotifyTranslator.to_mopidy_playlist, self.session.playlist_container()) playlists = filter(None, playlists) From 52e242cbe1055ce65a760d37472b99c21c30de28 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 11 Aug 2012 03:37:55 +0200 Subject: [PATCH 104/116] Update version number to 0.7.3 --- mopidy/__init__.py | 2 +- tests/version_test.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mopidy/__init__.py b/mopidy/__init__.py index 8a2b469e..11293446 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -8,7 +8,7 @@ from subprocess import PIPE, Popen import glib -__version__ = '0.7.2' +__version__ = '0.7.3' DATA_PATH = os.path.join(str(glib.get_user_data_dir()), 'mopidy') CACHE_PATH = os.path.join(str(glib.get_user_cache_dir()), 'mopidy') diff --git a/tests/version_test.py b/tests/version_test.py index b1c0b90e..26045ac1 100644 --- a/tests/version_test.py +++ b/tests/version_test.py @@ -26,7 +26,8 @@ class VersionTest(unittest.TestCase): self.assert_(SV('0.6.0') < SV('0.6.1')) self.assert_(SV('0.6.1') < SV('0.7.0')) self.assert_(SV('0.7.0') < SV('0.7.1')) - self.assert_(SV('0.7.1') < SV(__version__)) + self.assert_(SV('0.7.1') < SV('0.7.2')) + self.assert_(SV('0.7.2') < SV(__version__)) self.assert_(SV(__version__) < SV('0.8.0')) def test_get_platform_contains_platform(self): From 90490375575b7406fb117237b166e46812f16377 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 11 Aug 2012 03:38:07 +0200 Subject: [PATCH 105/116] Update changelog for v0.7.3 --- docs/changes.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 7b923e1a..a4aae058 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -4,8 +4,11 @@ Changes This change log is used to track all major changes to Mopidy. -v0.7.3 (in development) -======================= +v0.7.3 (2012-08-11) +=================== + +A small maintenance release to fix a crash affecting a few users, and a couple +of small adjustments to the Spotify backend. **Changes** From 8f7961064a2632ecab261962db1dcda839a6b637 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 22 Aug 2012 15:46:23 +0200 Subject: [PATCH 106/116] Add debug proxy helper. Tool sits in front of MPD and Mopidy proxying commands to both. Only the reference backend's replies are passed to the client. All requests are logged, but only the response's unified diff is displayed. Intended use case is quick and simple protocol implementation comparisons. --- tools/debug-proxy.py | 177 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100755 tools/debug-proxy.py diff --git a/tools/debug-proxy.py b/tools/debug-proxy.py new file mode 100755 index 00000000..cf84bd54 --- /dev/null +++ b/tools/debug-proxy.py @@ -0,0 +1,177 @@ +#!/usr/bin/python + +import argparse +import difflib +import sys + +from gevent import select +from gevent import server +from gevent import socket + + +def proxy(client, address, reference_address, actual_address): + """Main handler code that gets called for each connection.""" + client.setblocking(False) + + reference = connect(reference_address) + actual = connect(actual_address) + + if reference and actual: + loop(client, address, reference, actual) + else: + print 'Could not connect to one of the backends.' + + for sock in (client, reference, actual): + close(sock) + + +def connect(address): + """Connect to given address and set socket non blocking.""" + try: + sock = socket.socket() + sock.connect(address) + sock.setblocking(False) + except socket.error: + return None + return sock + + +def close(sock): + """Shutdown and close our sockets.""" + try: + sock.shutdown(socket.SHUT_WR) + sock.close() + except socket.error: + pass + + +def loop(client, address, reference, actual): + """Loop that handles one MPD reqeust/response pair per iteration.""" + + # Consume banners from backends + responses = dict() + disconnected = read([reference, actual], responses, find_response_end_token) + diff(address, '', responses[reference], responses[actual]) + + # We lost the a backend, might as well give up. + if disconnected: + return + + client.sendall(responses[reference]) + + while True: + responses = dict() + + # Get the command from the client. Not sure how an if this will handle + # client sending multiple commands currently :/ + disconnected = read([client], responses, find_request_end_token) + + # We lost the client, might as well give up. + if disconnected: + return + + # Send the entire command to both backends. + reference.sendall(responses[client]) + actual.sendall(responses[client]) + + # Get the entire resonse from both backends. + disconnected = read([reference, actual], responses, find_response_end_token) + + # Send the client the complete reference response + client.sendall(responses[reference]) + + # Compare our responses + diff(address, responses[client], responses[reference], responses[actual]) + + # Give up if we lost a backend. + if disconnected: + return + + +def read(sockets, responses, find_end_token): + """Keep reading from sockets until they disconnet or we find our token.""" + + # This function doesn't go to well with idle when backends are out of sync. + disconnected = False + + for sock in sockets: + responses.setdefault(sock, '') + + while sockets: + for sock in select.select(sockets, [], [])[0]: + data = sock.recv(4096) + responses[sock] += data + + if find_end_token(responses[sock]): + sockets.remove(sock) + + if not data: + sockets.remove(sock) + disconnected = True + + return disconnected + + +def find_response_end_token(data): + """Find token that indicates the response is over.""" + for line in data.splitlines(True): + if line.startswith(('OK', 'ACK')) and line.endswith('\n'): + return True + return False + + +def find_request_end_token(data): + """Find token that indicates that request is over.""" + lines = data.splitlines(True) + if not lines: + return False + elif 'command_list_ok_begin' == lines[0].strip(): + return 'command_list_end' == lines[-1].strip() + else: + return lines[0].endswith('\n') + + +def diff(address, command, reference_response, actual_response): + """Print command from client and a unified diff of the responses.""" + sys.stdout.write('[%s]:%s\n%s' % (address[0], address[1], command)) + for line in difflib.unified_diff(reference_response.splitlines(True), + actual_response.splitlines(True), + fromfile='Reference response', + tofile='Actual response'): + sys.stdout.write(line) + sys.stdout.flush() + + +def parse_args(): + """Handle flag parsing.""" + parser = argparse.ArgumentParser( + description='Proxy and compare MPD protocol interactions.') + parser.add_argument('--listen', default=':6600', type=parse_address, + help='address:port to listen on.') + parser.add_argument('--reference', default=':6601', type=parse_address, + help='address:port for the reference backend.') + parser.add_argument('--actual', default=':6602', type=parse_address, + help='address:port for the actual backend.') + + return parser.parse_args() + + +def parse_address(address): + """Convert host:port or port to address to pass to connect.""" + if ':' not in address: + return ('', int(address)) + host, port = address.rsplit(':', 1) + return (host, int(port)) + + +if __name__ == '__main__': + args = parse_args() + + def handle(client, address): + """Wrapper that adds reference and actual backends to proxy calls.""" + return proxy(client, address, args.reference, args.actual) + + try: + server.StreamServer(args.listen, handle).serve_forever() + except (KeyboardInterrupt, SystemExit): + pass From 4ff5c2e992aea195a80c2d9c83449e9dc5d88094 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 22 Aug 2012 23:16:03 +0200 Subject: [PATCH 107/116] Add color to console output and fix some things from review. --- tools/debug-proxy.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tools/debug-proxy.py b/tools/debug-proxy.py index cf84bd54..3ff6f561 100755 --- a/tools/debug-proxy.py +++ b/tools/debug-proxy.py @@ -4,9 +4,12 @@ import argparse import difflib import sys -from gevent import select -from gevent import server -from gevent import socket +from gevent import select, server, socket + +COLORS = ['\033[1;%dm' % (30+i) for i in range(8)] +BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = COLORS +RESET = "\033[0m" +BOLD = "\033[1m" def proxy(client, address, reference_address, actual_address): @@ -53,7 +56,7 @@ def loop(client, address, reference, actual): disconnected = read([reference, actual], responses, find_response_end_token) diff(address, '', responses[reference], responses[actual]) - # We lost the a backend, might as well give up. + # We lost a backend, might as well give up. if disconnected: return @@ -138,7 +141,17 @@ def diff(address, command, reference_response, actual_response): actual_response.splitlines(True), fromfile='Reference response', tofile='Actual response'): + + if line.startswith('+') and not line.startswith('+++'): + sys.stdout.write(GREEN) + elif line.startswith('-') and not line.startswith('---'): + sys.stdout.write(RED) + elif line.startswith('@@'): + sys.stdout.write(CYAN) + sys.stdout.write(line) + sys.stdout.write(RESET) + sys.stdout.flush() From 1649abc410aa8c6b67cded667814ae664fc08666 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 22 Aug 2012 23:42:57 +0200 Subject: [PATCH 108/116] Add debug-proxy to 0.8 changelog. --- docs/changes.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index a4aae058..a2a45960 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -4,6 +4,17 @@ Changes This change log is used to track all major changes to Mopidy. +v0.8 (in development) +===================== + +**Changes** + +- Added tools/debug-proxy.py to tee client requests to two backends and diff + responses. Intended as a developer tool for checking for MPD protocol changes + and various client support. Requires gevent, which currently is not a + dependency of Mopidy. + + v0.7.3 (2012-08-11) =================== From 8849c996754408f9e10e92c582d6f29d8b95bfba Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Thu, 23 Aug 2012 00:24:13 +0200 Subject: [PATCH 109/116] Use recommended shebang for Python scripts --- tools/debug-proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/debug-proxy.py b/tools/debug-proxy.py index 3ff6f561..2f54ea36 100755 --- a/tools/debug-proxy.py +++ b/tools/debug-proxy.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#! /usr/bin/env python import argparse import difflib From f995b2f1deb7914c214def8f8a137ac573b50200 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 23 Aug 2012 01:07:22 +0200 Subject: [PATCH 110/116] Continue ripping out multi output support. --- mopidy/gstreamer.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 8781a4b2..4f36b94f 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -38,12 +38,11 @@ class GStreamer(ThreadingActor): self._source = None self._uridecodebin = None self._volume = None - self._outputs = [] - self._handlers = {} + self._output = None def on_start(self): self._setup_pipeline() - self._setup_outputs() + self._setup_output() self._setup_message_processor() def _setup_pipeline(self): @@ -62,10 +61,16 @@ class GStreamer(ThreadingActor): self._uridecodebin.connect('pad-added', self._on_new_pad, self._pipeline.get_by_name('convert').get_pad('sink')) - def _setup_outputs(self): - for klass in settings.OUTPUTS: - self._outputs.append(get_class(klass)()) - self.connect_output(self._outputs[0].bin) + def _setup_output(self): + self._output = get_class(settings.OUTPUTS[0])() + + if len(settings.OUTPUTS) > 1: + logger.warning('Only first output will be used.') + + self._pipeline.add(self._output.bin) + gst.element_link_many(self._volume, self._output.bin) + + logger.debug('Output set to %s', self._output.get_name()) def _setup_message_processor(self): bus = self._pipeline.get_bus() @@ -287,15 +292,3 @@ class GStreamer(ThreadingActor): event = gst.event_new_tag(taglist) self._pipeline.send_event(event) - - def connect_output(self, output): - """ - Connect output to pipeline. - - :param output: output to connect to the pipeline - :type output: :class:`gst.Bin` - """ - self._pipeline.add(output) - output.sync_state_with_parent() # Required to add to running pipe - gst.element_link_many(self._volume, output) - logger.debug('Output set to %s', output.get_name()) From 5790d0ba07d5429d09d93aa6dc8fe63796c44dd4 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 23 Aug 2012 01:13:08 +0200 Subject: [PATCH 111/116] Add removal of multiple outsputs support to changelog. --- docs/changes.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index a2a45960..ad74ade9 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -14,6 +14,9 @@ v0.8 (in development) and various client support. Requires gevent, which currently is not a dependency of Mopidy. +- Removed most traces of multiple outputs support. Having this feature + currently seems to be more trouble than what it is worth. + v0.7.3 (2012-08-11) =================== From c565e274a52e87f7da37e787854e5862301838df Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Thu, 23 Aug 2012 23:11:59 +0200 Subject: [PATCH 112/116] Replace OUTPUTS with OUTPUT and switch to simple outputs that return a gst.Bin --- mopidy/gstreamer.py | 16 ++----- mopidy/outputs.py | 96 +++++++++++++++++++++++++++++++++++++ mopidy/outputs/__init__.py | 78 ------------------------------ mopidy/outputs/custom.py | 34 ------------- mopidy/outputs/local.py | 20 -------- mopidy/outputs/shoutcast.py | 42 ---------------- mopidy/settings.py | 7 ++- mopidy/utils/__init__.py | 19 ++++++-- mopidy/utils/settings.py | 12 +++-- 9 files changed, 127 insertions(+), 197 deletions(-) create mode 100644 mopidy/outputs.py delete mode 100644 mopidy/outputs/__init__.py delete mode 100644 mopidy/outputs/custom.py delete mode 100644 mopidy/outputs/local.py delete mode 100644 mopidy/outputs/shoutcast.py diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 4f36b94f..8d8bedb4 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -7,8 +7,7 @@ import logging from pykka.actor import ThreadingActor from pykka.registry import ActorRegistry -from mopidy import settings -from mopidy.utils import get_class +from mopidy import settings, utils from mopidy.backends.base import Backend logger = logging.getLogger('mopidy.gstreamer') @@ -62,15 +61,10 @@ class GStreamer(ThreadingActor): self._pipeline.get_by_name('convert').get_pad('sink')) def _setup_output(self): - self._output = get_class(settings.OUTPUTS[0])() - - if len(settings.OUTPUTS) > 1: - logger.warning('Only first output will be used.') - - self._pipeline.add(self._output.bin) - gst.element_link_many(self._volume, self._output.bin) - - logger.debug('Output set to %s', self._output.get_name()) + self._output = utils.get_function(settings.OUTPUT)() + self._pipeline.add(self._output) + gst.element_link_many(self._volume, self._output) + logger.debug('Output set to %s', settings.OUTPUT) def _setup_message_processor(self): bus = self._pipeline.get_bus() diff --git a/mopidy/outputs.py b/mopidy/outputs.py new file mode 100644 index 00000000..d9619fb8 --- /dev/null +++ b/mopidy/outputs.py @@ -0,0 +1,96 @@ +import pygst +pygst.require('0.10') +import gst + +from mopidy import settings + + +def custom(): + """ + Custom output for using alternate setups. + + This output is intended to handle two main cases: + + 1. Simple things like switching which sink to use. Say :class:`LocalOutput` + doesn't work for you and you want to switch to ALSA, simple. Set + :attr:`mopidy.settings.CUSTOM_OUTPUT` to ``alsasink`` and you are good + to go. Some possible sinks include: + + - alsasink + - osssink + - pulsesink + - ...and many more + + 2. Advanced setups that require complete control of the output bin. For + these cases setup :attr:`mopidy.settings.CUSTOM_OUTPUT` with a + :command:`gst-launch` compatible string describing the target setup. + + **Dependencies:** + + - None + + **Settings:** + + - :attr:`mopidy.settings.CUSTOM_OUTPUT` + """ + return gst.parse_bin_from_description(settings.CUSTOM_OUTPUT, True) + + +def local(): + """ + Basic output to local audio sink. + + This output will normally tell GStreamer to choose whatever it thinks is + best for your system. In other words this is usually a sane choice. + + **Dependencies:** + + - None + + **Settings:** + + - None + """ + return gst.parse_bin_from_description('autoaudiosink', True) + + +def shoutcast(): + """ + Shoutcast streaming output. + + This output allows for streaming to an icecast server or anything else that + supports Shoutcast. The output supports setting for: server address, port, + mount point, user, password and encoder to use. Please see + :class:`mopidy.settings` for details about settings. + + **Dependencies:** + + - A SHOUTcast/Icecast server + + **Settings:** + + - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_HOSTNAME` + - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_PORT` + - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_USERNAME` + - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_PASSWORD` + - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_MOUNT` + - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_ENCODER` + """ + encoder = settings.SHOUTCAST_OUTPUT_ENCODER + output = gst.parse_bin_from_description( + '%s ! shout2send name=shoutcast' % encoder, True) + + shoutcast = output.get_by_name('shoutcast') + + properties = { + u'ip': settings.SHOUTCAST_OUTPUT_HOSTNAME, + u'port': settings.SHOUTCAST_OUTPUT_PORT, + u'mount': settings.SHOUTCAST_OUTPUT_MOUNT, + u'username': settings.SHOUTCAST_OUTPUT_USERNAME, + u'password': settings.SHOUTCAST_OUTPUT_PASSWORD, + } + + for name, value in properties.items(): + shoutcast.set_property(name, value) + + return output diff --git a/mopidy/outputs/__init__.py b/mopidy/outputs/__init__.py deleted file mode 100644 index 21179f94..00000000 --- a/mopidy/outputs/__init__.py +++ /dev/null @@ -1,78 +0,0 @@ -import pygst -pygst.require('0.10') -import gst - -import logging - -logger = logging.getLogger('mopidy.outputs') - -class BaseOutput(object): - """Base class for pluggable audio outputs.""" - - MESSAGE_EOS = gst.MESSAGE_EOS - MESSAGE_ERROR = gst.MESSAGE_ERROR - MESSAGE_WARNING = gst.MESSAGE_WARNING - - def __init__(self): - self.bin = self._build_bin() - self.bin.set_name(self.get_name()) - - self.modify_bin() - - def _build_bin(self): - description = self.describe_bin() - logger.debug('Creating new output: %s', description) - return gst.parse_bin_from_description(description, True) - - def get_name(self): - """ - Get name of the output. Defaults to the output's class name. - - *MAY be implemented by subclass.* - - :rtype: string - """ - return self.__class__.__name__ - - def modify_bin(self): - """ - Modifies ``self.bin`` before it is installed if needed. - - Overriding this method allows for outputs to modify the constructed bin - before it is installed. This can for instance be a good place to call - `set_properties` on elements that need to be configured. - - *MAY be implemented by subclass.* - """ - pass - - def describe_bin(self): - """ - Return string describing the output bin in :command:`gst-launch` - format. - - For simple cases this can just be a sink such as ``autoaudiosink``, - or it can be a chain like ``element1 ! element2 ! sink``. See the - manpage of :command:`gst-launch` for details on the format. - - *MUST be implemented by subclass.* - - :rtype: string - """ - raise NotImplementedError - - def set_properties(self, element, properties): - """ - Helper method for setting of properties on elements. - - Will call :meth:`gst.Element.set_property` on ``element`` for each key - in ``properties`` that has a value that is not :class:`None`. - - :param element: element to set properties on - :type element: :class:`gst.Element` - :param properties: properties to set on element - :type properties: dict - """ - for key, value in properties.items(): - if value is not None: - element.set_property(key, value) diff --git a/mopidy/outputs/custom.py b/mopidy/outputs/custom.py deleted file mode 100644 index 09239a44..00000000 --- a/mopidy/outputs/custom.py +++ /dev/null @@ -1,34 +0,0 @@ -from mopidy import settings -from mopidy.outputs import BaseOutput - -class CustomOutput(BaseOutput): - """ - Custom output for using alternate setups. - - This output is intended to handle two main cases: - - 1. Simple things like switching which sink to use. Say :class:`LocalOutput` - doesn't work for you and you want to switch to ALSA, simple. Set - :attr:`mopidy.settings.CUSTOM_OUTPUT` to ``alsasink`` and you are good - to go. Some possible sinks include: - - - alsasink - - osssink - - pulsesink - - ...and many more - - 2. Advanced setups that require complete control of the output bin. For - these cases setup :attr:`mopidy.settings.CUSTOM_OUTPUT` with a - :command:`gst-launch` compatible string describing the target setup. - - **Dependencies:** - - - None - - **Settings:** - - - :attr:`mopidy.settings.CUSTOM_OUTPUT` - """ - - def describe_bin(self): - return settings.CUSTOM_OUTPUT diff --git a/mopidy/outputs/local.py b/mopidy/outputs/local.py deleted file mode 100644 index 8101e026..00000000 --- a/mopidy/outputs/local.py +++ /dev/null @@ -1,20 +0,0 @@ -from mopidy.outputs import BaseOutput - -class LocalOutput(BaseOutput): - """ - Basic output to local audio sink. - - This output will normally tell GStreamer to choose whatever it thinks is - best for your system. In other words this is usually a sane choice. - - **Dependencies:** - - - None - - **Settings:** - - - None - """ - - def describe_bin(self): - return 'autoaudiosink' diff --git a/mopidy/outputs/shoutcast.py b/mopidy/outputs/shoutcast.py deleted file mode 100644 index 0279ae2d..00000000 --- a/mopidy/outputs/shoutcast.py +++ /dev/null @@ -1,42 +0,0 @@ -import logging - -from mopidy import settings -from mopidy.outputs import BaseOutput - -logger = logging.getLogger('mopidy.outputs.shoutcast') - -class ShoutcastOutput(BaseOutput): - """ - Shoutcast streaming output. - - This output allows for streaming to an icecast server or anything else that - supports Shoutcast. The output supports setting for: server address, port, - mount point, user, password and encoder to use. Please see - :class:`mopidy.settings` for details about settings. - - **Dependencies:** - - - A SHOUTcast/Icecast server - - **Settings:** - - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_HOSTNAME` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_PORT` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_USERNAME` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_PASSWORD` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_MOUNT` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_ENCODER` - """ - - def describe_bin(self): - return 'audioconvert ! %s ! shout2send name=shoutcast' \ - % settings.SHOUTCAST_OUTPUT_ENCODER - - def modify_bin(self): - self.set_properties(self.bin.get_by_name('shoutcast'), { - u'ip': settings.SHOUTCAST_OUTPUT_HOSTNAME, - u'port': settings.SHOUTCAST_OUTPUT_PORT, - u'mount': settings.SHOUTCAST_OUTPUT_MOUNT, - u'username': settings.SHOUTCAST_OUTPUT_USERNAME, - u'password': settings.SHOUTCAST_OUTPUT_PASSWORD, - }) diff --git a/mopidy/settings.py b/mopidy/settings.py index a47b389d..07bfda43 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -185,13 +185,12 @@ MPD_SERVER_PASSWORD = None #: Default: 20 MPD_SERVER_MAX_CONNECTIONS = 20 -#: List of outputs to use. See :mod:`mopidy.outputs` for all available -#: backends +#: Output to use. See :mod:`mopidy.outputs` for all available backends #: #: Default:: #: -#: OUTPUTS = (u'mopidy.outputs.local.LocalOutput',) -OUTPUTS = (u'mopidy.outputs.local.LocalOutput',) +#: OUTPUT = u'mopidy.outputs.local' +OUTPUT = u'mopidy.outputs.local' #: Hostname of the SHOUTcast server which Mopidy should stream audio to. #: diff --git a/mopidy/utils/__init__.py b/mopidy/utils/__init__.py index 00129cdd..b1234aec 100644 --- a/mopidy/utils/__init__.py +++ b/mopidy/utils/__init__.py @@ -5,6 +5,8 @@ import sys logger = logging.getLogger('mopidy.utils') + +# TODO: user itertools.chain.from_iterable(the_list)? def flatten(the_list): result = [] for element in the_list: @@ -14,22 +16,31 @@ def flatten(the_list): result.append(element) return result + def import_module(name): __import__(name) return sys.modules[name] -def get_class(name): + +def _get_obj(name): logger.debug('Loading: %s', name) if '.' not in name: raise ImportError("Couldn't load: %s" % name) module_name = name[:name.rindex('.')] - class_name = name[name.rindex('.') + 1:] + obj_name = name[name.rindex('.') + 1:] try: module = import_module(module_name) - class_object = getattr(module, class_name) + obj = getattr(module, obj_name) except (ImportError, AttributeError): raise ImportError("Couldn't load: %s" % name) - return class_object + return obj + + +# We provide both get_class and get_function to make it more obvious what the +# intent of our code really is. +get_class = _get_obj +get_function = _get_obj + def locale_decode(bytestr): try: diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index ff449a61..65548f33 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -120,7 +120,6 @@ def validate_settings(defaults, settings): 'LOCAL_OUTPUT_OVERRIDE': 'CUSTOM_OUTPUT', 'LOCAL_PLAYLIST_FOLDER': 'LOCAL_PLAYLIST_PATH', 'LOCAL_TAG_CACHE': 'LOCAL_TAG_CACHE_FILE', - 'OUTPUT': None, 'SERVER': None, 'SERVER_HOSTNAME': 'MPD_SERVER_HOSTNAME', 'SERVER_PORT': 'MPD_SERVER_PORT', @@ -140,11 +139,16 @@ def validate_settings(defaults, settings): if setting == 'BACKENDS': if 'mopidy.backends.despotify.DespotifyBackend' in value: - errors[setting] = (u'Deprecated setting value. ' + - '"mopidy.backends.despotify.DespotifyBackend" is no ' + - 'longer available.') + errors[setting] = (u'Deprecated setting value. ' + u'"mopidy.backends.despotify.DespotifyBackend" is no ' + u'longer available.') continue + if setting == 'OUTPUTS': + errors[setting] = (u'Deprecated setting, please change to OUTPUT. ' + u'Please note that output values have also changed.') + continue + if setting == 'SPOTIFY_BITRATE': if value not in (96, 160, 320): errors[setting] = (u'Unavailable Spotify bitrate. ' + From 6e9dd194df79bd3102f731ef45f0ce0e3cc0206e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 24 Aug 2012 00:16:33 +0200 Subject: [PATCH 113/116] Use current_playlist.length instead of len(current_playlist.tracks) --- mopidy/frontends/mpd/protocol/current_playlist.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/current_playlist.py b/mopidy/frontends/mpd/protocol/current_playlist.py index 0d61c887..0c2c2d52 100644 --- a/mopidy/frontends/mpd/protocol/current_playlist.py +++ b/mopidy/frontends/mpd/protocol/current_playlist.py @@ -55,8 +55,7 @@ def addid(context, uri, songpos=None): track = context.backend.library.lookup(uri).get() if track is None: raise MpdNoExistError(u'No such song', command=u'addid') - if songpos and songpos > len( - context.backend.current_playlist.tracks.get()): + if songpos and songpos > context.backend.current_playlist.length.get(): raise MpdArgError(u'Bad song index', command=u'addid') cp_track = context.backend.current_playlist.add(track, at_position=songpos).get() @@ -132,7 +131,7 @@ def move_range(context, start, to, end=None): ``TO`` in the playlist. """ if end is None: - end = len(context.backend.current_playlist.tracks.get()) + end = context.backend.current_playlist.length.get() start = int(start) end = int(end) to = int(to) From 2262bf91d5ce3e851d5bf7a3bb3f6a03f5243db5 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 24 Aug 2012 00:21:03 +0200 Subject: [PATCH 114/116] Fix crash in 'playlistinfo' when called with a songpos not matching an CPID (fixes #162) --- docs/changes.rst | 4 ++++ mopidy/frontends/mpd/protocol/current_playlist.py | 2 +- tests/frontends/mpd/protocol/current_playlist_test.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index a2a45960..4dcc8c57 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -14,6 +14,10 @@ v0.8 (in development) and various client support. Requires gevent, which currently is not a dependency of Mopidy. +- Fixed bug when the MPD command `playlistinfo` is used with a track position. + Track position and CPID was intermixed, so it would cause a crash if a CPID + matching the track position didn't exist. (Fixes: :issue:`162`) + v0.7.3 (2012-08-11) =================== diff --git a/mopidy/frontends/mpd/protocol/current_playlist.py b/mopidy/frontends/mpd/protocol/current_playlist.py index 0c2c2d52..c60cbc4a 100644 --- a/mopidy/frontends/mpd/protocol/current_playlist.py +++ b/mopidy/frontends/mpd/protocol/current_playlist.py @@ -243,7 +243,7 @@ def playlistinfo(context, songpos=None, """ if songpos is not None: songpos = int(songpos) - cp_track = context.backend.current_playlist.get(cpid=songpos).get() + cp_track = context.backend.current_playlist.cp_tracks.get()[songpos] return track_to_mpd_format(cp_track, position=songpos) else: if start is None: diff --git a/tests/frontends/mpd/protocol/current_playlist_test.py b/tests/frontends/mpd/protocol/current_playlist_test.py index 321fc6ee..21889e82 100644 --- a/tests/frontends/mpd/protocol/current_playlist_test.py +++ b/tests/frontends/mpd/protocol/current_playlist_test.py @@ -285,6 +285,8 @@ class CurrentPlaylistHandlerTest(protocol.BaseTestCase): self.assertInResponse(u'OK') def test_playlistinfo_with_songpos(self): + # Make the track's CPID not match the playlist position + self.backend.current_playlist.cp_id = 17 self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), From 7948921510f59c5b1d4b40e336db698995f79bc5 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 26 Aug 2012 12:18:28 +0200 Subject: [PATCH 115/116] Make settings.OUTPUT a GStreamer bin description. --- mopidy/gstreamer.py | 2 +- mopidy/settings.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 8d8bedb4..52fe079e 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -61,7 +61,7 @@ class GStreamer(ThreadingActor): self._pipeline.get_by_name('convert').get_pad('sink')) def _setup_output(self): - self._output = utils.get_function(settings.OUTPUT)() + self._output = gst.parse_bin_from_description(settings.OUTPUT, True) self._pipeline.add(self._output) gst.element_link_many(self._volume, self._output) logger.debug('Output set to %s', settings.OUTPUT) diff --git a/mopidy/settings.py b/mopidy/settings.py index 07bfda43..fce729d3 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -189,8 +189,8 @@ MPD_SERVER_MAX_CONNECTIONS = 20 #: #: Default:: #: -#: OUTPUT = u'mopidy.outputs.local' -OUTPUT = u'mopidy.outputs.local' +#: OUTPUT = u'autoaudiosink' +OUTPUT = u'autoaudiosink' #: Hostname of the SHOUTcast server which Mopidy should stream audio to. #: From 343207ebe27f1b172b20e66dc267cc67371bf925 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 26 Aug 2012 12:37:23 +0200 Subject: [PATCH 116/116] Update docs with latest OUTPUT changes and fix issues raised in review of pull request. --- docs/changes.rst | 4 ++ docs/installation/gstreamer.rst | 9 ++-- docs/settings.rst | 19 ++++--- mopidy/outputs.py | 96 --------------------------------- mopidy/settings.py | 8 --- mopidy/utils/__init__.py | 14 ++--- mopidy/utils/settings.py | 14 +++-- 7 files changed, 29 insertions(+), 135 deletions(-) delete mode 100644 mopidy/outputs.py diff --git a/docs/changes.rst b/docs/changes.rst index ad74ade9..db6a1c60 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -16,6 +16,10 @@ v0.8 (in development) - Removed most traces of multiple outputs support. Having this feature currently seems to be more trouble than what it is worth. + :attr:`mopidy.settings.OUTPUTS` setting is no longer supported, and has been + replaced with :attr:`mopidy.settings.OUTPUT` which is a GStreamer + bin descriped in the same format as gst-launch expects. Default value is + ``autoaudiosink``. v0.7.3 (2012-08-11) diff --git a/docs/installation/gstreamer.rst b/docs/installation/gstreamer.rst index c6359f6f..546b53ba 100644 --- a/docs/installation/gstreamer.rst +++ b/docs/installation/gstreamer.rst @@ -112,12 +112,9 @@ Using a custom audio sink ========================= If you for some reason want to use some other GStreamer audio sink than -``autoaudiosink``, you can set :attr:`mopidy.settings.OUTPUTS` to -``mopidy.outputs.custom.CustomOutput``, and set the -:attr:`mopidy.settings.CUSTOM_OUTPUT` setting to a partial GStreamer pipeline -description describing the GStreamer sink you want to use. +``autoaudiosink``, you can set :attr:`mopidy.settings.OUTPUT` to a partial +GStreamer pipeline description describing the GStreamer sink you want to use. Example of ``settings.py`` for OSS4:: - OUTPUTS = (u'mopidy.outputs.custom.CustomOutput',) - CUSTOM_OUTPUT = u'oss4sink' + OUTPUT = u'oss4sink' diff --git a/docs/settings.rst b/docs/settings.rst index 980fcd4c..f754bb5e 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -157,18 +157,17 @@ server simultaneously. To use the SHOUTcast output, do the following: #. Install, configure and start the Icecast server. It can be found in the ``icecast2`` package in Debian/Ubuntu. -#. Set ``mopidy.outputs.shoutcast.ShoutcastOutput`` as the first output in the - :attr:`mopidy.settings.OUTPUTS` setting. +#. Set :attr:`mopidy.settings.OUTPUT` to ``lame ! shout2send`` (an ogg-vorbis + encoder could be used instead of lame). -#. Check the default values for the following settings, and alter them to match - your Icecast setup if needed: +#. You might also need to change the shout2send default settings, run + ``gst-inspect-0.10 shout2send`` to see the available settings. Most likely + you want to change ``ip``, ``username``, ``password`` and ``mount``. For + example, to set the password use: ``lame ! shout2send password="s3cret"``. - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_HOSTNAME` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_PORT` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_USERNAME` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_PASSWORD` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_MOUNT` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_ENCODER` +Other advanced setups are also possible for outputs. Basically anything you can +get a gst-lauch command to output to can be plugged into +:attr:`mopidy.settings.OUTPUT``. Available settings diff --git a/mopidy/outputs.py b/mopidy/outputs.py deleted file mode 100644 index d9619fb8..00000000 --- a/mopidy/outputs.py +++ /dev/null @@ -1,96 +0,0 @@ -import pygst -pygst.require('0.10') -import gst - -from mopidy import settings - - -def custom(): - """ - Custom output for using alternate setups. - - This output is intended to handle two main cases: - - 1. Simple things like switching which sink to use. Say :class:`LocalOutput` - doesn't work for you and you want to switch to ALSA, simple. Set - :attr:`mopidy.settings.CUSTOM_OUTPUT` to ``alsasink`` and you are good - to go. Some possible sinks include: - - - alsasink - - osssink - - pulsesink - - ...and many more - - 2. Advanced setups that require complete control of the output bin. For - these cases setup :attr:`mopidy.settings.CUSTOM_OUTPUT` with a - :command:`gst-launch` compatible string describing the target setup. - - **Dependencies:** - - - None - - **Settings:** - - - :attr:`mopidy.settings.CUSTOM_OUTPUT` - """ - return gst.parse_bin_from_description(settings.CUSTOM_OUTPUT, True) - - -def local(): - """ - Basic output to local audio sink. - - This output will normally tell GStreamer to choose whatever it thinks is - best for your system. In other words this is usually a sane choice. - - **Dependencies:** - - - None - - **Settings:** - - - None - """ - return gst.parse_bin_from_description('autoaudiosink', True) - - -def shoutcast(): - """ - Shoutcast streaming output. - - This output allows for streaming to an icecast server or anything else that - supports Shoutcast. The output supports setting for: server address, port, - mount point, user, password and encoder to use. Please see - :class:`mopidy.settings` for details about settings. - - **Dependencies:** - - - A SHOUTcast/Icecast server - - **Settings:** - - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_HOSTNAME` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_PORT` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_USERNAME` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_PASSWORD` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_MOUNT` - - :attr:`mopidy.settings.SHOUTCAST_OUTPUT_ENCODER` - """ - encoder = settings.SHOUTCAST_OUTPUT_ENCODER - output = gst.parse_bin_from_description( - '%s ! shout2send name=shoutcast' % encoder, True) - - shoutcast = output.get_by_name('shoutcast') - - properties = { - u'ip': settings.SHOUTCAST_OUTPUT_HOSTNAME, - u'port': settings.SHOUTCAST_OUTPUT_PORT, - u'mount': settings.SHOUTCAST_OUTPUT_MOUNT, - u'username': settings.SHOUTCAST_OUTPUT_USERNAME, - u'password': settings.SHOUTCAST_OUTPUT_PASSWORD, - } - - for name, value in properties.items(): - shoutcast.set_property(name, value) - - return output diff --git a/mopidy/settings.py b/mopidy/settings.py index fce729d3..0bb04823 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -26,14 +26,6 @@ BACKENDS = ( #: details on the format. CONSOLE_LOG_FORMAT = u'%(levelname)-8s %(message)s' -#: Which GStreamer bin description to use in -#: :class:`mopidy.outputs.custom.CustomOutput`. -#: -#: Default:: -#: -#: CUSTOM_OUTPUT = u'fakesink' -CUSTOM_OUTPUT = u'fakesink' - #: The log format used for debug logging. #: #: See http://docs.python.org/library/logging.html#formatter-objects for diff --git a/mopidy/utils/__init__.py b/mopidy/utils/__init__.py index b1234aec..567c7301 100644 --- a/mopidy/utils/__init__.py +++ b/mopidy/utils/__init__.py @@ -22,24 +22,18 @@ def import_module(name): return sys.modules[name] -def _get_obj(name): +def get_class(name): logger.debug('Loading: %s', name) if '.' not in name: raise ImportError("Couldn't load: %s" % name) module_name = name[:name.rindex('.')] - obj_name = name[name.rindex('.') + 1:] + cls_name = name[name.rindex('.') + 1:] try: module = import_module(module_name) - obj = getattr(module, obj_name) + cls = getattr(module, cls_name) except (ImportError, AttributeError): raise ImportError("Couldn't load: %s" % name) - return obj - - -# We provide both get_class and get_function to make it more obvious what the -# intent of our code really is. -get_class = _get_obj -get_function = _get_obj + return cls def locale_decode(bytestr): diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 65548f33..8060c667 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -112,6 +112,7 @@ def validate_settings(defaults, settings): errors = {} changed = { + 'CUSTOM_OUTPUT': 'OUTPUT', 'DUMP_LOG_FILENAME': 'DEBUG_LOG_FILENAME', 'DUMP_LOG_FORMAT': 'DEBUG_LOG_FORMAT', 'FRONTEND': 'FRONTENDS', @@ -139,20 +140,23 @@ def validate_settings(defaults, settings): if setting == 'BACKENDS': if 'mopidy.backends.despotify.DespotifyBackend' in value: - errors[setting] = (u'Deprecated setting value. ' + errors[setting] = ( + u'Deprecated setting value. ' u'"mopidy.backends.despotify.DespotifyBackend" is no ' u'longer available.') continue if setting == 'OUTPUTS': - errors[setting] = (u'Deprecated setting, please change to OUTPUT. ' - u'Please note that output values have also changed.') + errors[setting] = ( + u'Deprecated setting, please change to OUTPUT. OUTPUT expectes ' + u'a GStreamer bin describing your desired output.') continue if setting == 'SPOTIFY_BITRATE': if value not in (96, 160, 320): - errors[setting] = (u'Unavailable Spotify bitrate. ' + - u'Available bitrates are 96, 160, and 320.') + errors[setting] = ( + u'Unavailable Spotify bitrate. Available bitrates are 96, ' + u'160, and 320.') if setting not in defaults: errors[setting] = u'Unknown setting. Is it misspelled?'