Merge branch 'feature/pykka-actors' into develop
This commit is contained in:
commit
be1e0fa819
@ -1,5 +1,5 @@
|
|||||||
#! /usr/bin/env python
|
#! /usr/bin/env python
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from mopidy.__main__ import main
|
from mopidy.core import main
|
||||||
main()
|
main()
|
||||||
|
|||||||
@ -74,11 +74,11 @@ Running tests
|
|||||||
To run tests, you need a couple of dependencies. They can be installed through
|
To run tests, you need a couple of dependencies. They can be installed through
|
||||||
Debian/Ubuntu package management::
|
Debian/Ubuntu package management::
|
||||||
|
|
||||||
sudo aptitude install python-coverage python-nose
|
sudo aptitude install python-coverage python-mock python-nose
|
||||||
|
|
||||||
Or, they can be installed using ``pip``::
|
Or, they can be installed using ``pip``::
|
||||||
|
|
||||||
sudo pip install -r requirements-tests.txt
|
sudo pip install -r requirements/tests.txt
|
||||||
|
|
||||||
Then, to run all tests, go to the project directory and run::
|
Then, to run all tests, go to the project directory and run::
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,8 @@ Otherwise, make sure you got the required dependencies installed.
|
|||||||
|
|
||||||
- Python >= 2.6, < 3
|
- Python >= 2.6, < 3
|
||||||
|
|
||||||
|
- `Pykka <http://jodal.github.com/pykka/>`_ >= 0.12
|
||||||
|
|
||||||
- GStreamer >= 0.10, with Python bindings. See :doc:`gstreamer`.
|
- GStreamer >= 0.10, with Python bindings. See :doc:`gstreamer`.
|
||||||
|
|
||||||
- Mixer dependencies: The default mixer does not require any additional
|
- Mixer dependencies: The default mixer does not require any additional
|
||||||
|
|||||||
@ -9,7 +9,7 @@ VERSION = (0, 4, 0)
|
|||||||
def get_git_version():
|
def get_git_version():
|
||||||
process = Popen(['git', 'describe'], stdout=PIPE, stderr=PIPE)
|
process = Popen(['git', 'describe'], stdout=PIPE, stderr=PIPE)
|
||||||
if process.wait() != 0:
|
if process.wait() != 0:
|
||||||
raise Exception('Execution of "git describe" failed')
|
raise EnvironmentError('Execution of "git describe" failed')
|
||||||
version = process.stdout.read().strip()
|
version = process.stdout.read().strip()
|
||||||
if version.startswith('v'):
|
if version.startswith('v'):
|
||||||
version = version[1:]
|
version = version[1:]
|
||||||
@ -21,7 +21,7 @@ def get_plain_version():
|
|||||||
def get_version():
|
def get_version():
|
||||||
try:
|
try:
|
||||||
return get_git_version()
|
return get_git_version()
|
||||||
except Exception:
|
except EnvironmentError:
|
||||||
return get_plain_version()
|
return get_plain_version()
|
||||||
|
|
||||||
class MopidyException(Exception):
|
class MopidyException(Exception):
|
||||||
|
|||||||
@ -1,17 +1,10 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# Add ../ to the path so we can run Mopidy from a Git checkout without
|
# Add ../ to the path so we can run Mopidy from a Git checkout without
|
||||||
# installing it on the system.
|
# installing it on the system.
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
sys.path.insert(0,
|
sys.path.insert(0,
|
||||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../')))
|
os.path.abspath(os.path.join(os.path.dirname(__file__), '../')))
|
||||||
|
|
||||||
from mopidy.core import CoreProcess
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Explictly call run() instead of start(), since we don't need to start
|
|
||||||
# another process.
|
|
||||||
CoreProcess().run()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
from mopidy.core import main
|
||||||
main()
|
main()
|
||||||
|
|||||||
@ -1,12 +1,4 @@
|
|||||||
from copy import copy
|
|
||||||
import logging
|
import logging
|
||||||
import random
|
|
||||||
import time
|
|
||||||
|
|
||||||
from mopidy import settings
|
|
||||||
from mopidy.frontends.mpd import translator
|
|
||||||
from mopidy.models import Playlist
|
|
||||||
from mopidy.utils import get_class
|
|
||||||
|
|
||||||
from .current_playlist import CurrentPlaylistController
|
from .current_playlist import CurrentPlaylistController
|
||||||
from .library import LibraryController, BaseLibraryProvider
|
from .library import LibraryController, BaseLibraryProvider
|
||||||
@ -17,30 +9,6 @@ from .stored_playlists import (StoredPlaylistsController,
|
|||||||
logger = logging.getLogger('mopidy.backends.base')
|
logger = logging.getLogger('mopidy.backends.base')
|
||||||
|
|
||||||
class Backend(object):
|
class Backend(object):
|
||||||
"""
|
|
||||||
:param core_queue: a queue for sending messages to
|
|
||||||
:class:`mopidy.process.CoreProcess`
|
|
||||||
:type core_queue: :class:`multiprocessing.Queue`
|
|
||||||
:param output: the audio output
|
|
||||||
:type output: :class:`mopidy.outputs.gstreamer.GStreamerOutput` or similar
|
|
||||||
:param mixer_class: either a mixer class, or :class:`None` to use the mixer
|
|
||||||
defined in settings
|
|
||||||
:type mixer_class: a subclass of :class:`mopidy.mixers.BaseMixer` or
|
|
||||||
:class:`None`
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, core_queue=None, output=None, mixer_class=None):
|
|
||||||
self.core_queue = core_queue
|
|
||||||
self.output = output
|
|
||||||
if mixer_class is None:
|
|
||||||
mixer_class = get_class(settings.MIXER)
|
|
||||||
self.mixer = mixer_class(self)
|
|
||||||
|
|
||||||
#: A :class:`multiprocessing.Queue` which can be used by e.g. library
|
|
||||||
#: callbacks executing in other threads to send messages to the core
|
|
||||||
#: thread, so that action may be taken in the correct thread.
|
|
||||||
core_queue = None
|
|
||||||
|
|
||||||
#: The current playlist controller. An instance of
|
#: The current playlist controller. An instance of
|
||||||
#: :class:`mopidy.backends.base.CurrentPlaylistController`.
|
#: :class:`mopidy.backends.base.CurrentPlaylistController`.
|
||||||
current_playlist = None
|
current_playlist = None
|
||||||
@ -49,9 +17,6 @@ class Backend(object):
|
|||||||
# :class:`mopidy.backends.base.LibraryController`.
|
# :class:`mopidy.backends.base.LibraryController`.
|
||||||
library = None
|
library = None
|
||||||
|
|
||||||
#: The sound mixer. An instance of :class:`mopidy.mixers.BaseMixer`.
|
|
||||||
mixer = None
|
|
||||||
|
|
||||||
#: The playback controller. An instance of
|
#: The playback controller. An instance of
|
||||||
#: :class:`mopidy.backends.base.PlaybackController`.
|
#: :class:`mopidy.backends.base.PlaybackController`.
|
||||||
playback = None
|
playback = None
|
||||||
@ -62,24 +27,3 @@ class Backend(object):
|
|||||||
|
|
||||||
#: List of URI prefixes this backend can handle.
|
#: List of URI prefixes this backend can handle.
|
||||||
uri_handlers = []
|
uri_handlers = []
|
||||||
|
|
||||||
def destroy(self):
|
|
||||||
"""
|
|
||||||
Call destroy on all sub-components in backend so that they can cleanup
|
|
||||||
after themselves.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.current_playlist:
|
|
||||||
self.current_playlist.destroy()
|
|
||||||
|
|
||||||
if self.library:
|
|
||||||
self.library.destroy()
|
|
||||||
|
|
||||||
if self.mixer:
|
|
||||||
self.mixer.destroy()
|
|
||||||
|
|
||||||
if self.playback:
|
|
||||||
self.playback.destroy()
|
|
||||||
|
|
||||||
if self.stored_playlists:
|
|
||||||
self.stored_playlists.destroy()
|
|
||||||
|
|||||||
@ -2,8 +2,6 @@ from copy import copy
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from mopidy.frontends.mpd import translator
|
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.backends.base')
|
logger = logging.getLogger('mopidy.backends.base')
|
||||||
|
|
||||||
class CurrentPlaylistController(object):
|
class CurrentPlaylistController(object):
|
||||||
@ -12,6 +10,8 @@ class CurrentPlaylistController(object):
|
|||||||
:type backend: :class:`mopidy.backends.base.Backend`
|
:type backend: :class:`mopidy.backends.base.Backend`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
pykka_traversable = True
|
||||||
|
|
||||||
def __init__(self, backend):
|
def __init__(self, backend):
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
self._cp_tracks = []
|
self._cp_tracks = []
|
||||||
@ -197,8 +197,3 @@ class CurrentPlaylistController(object):
|
|||||||
random.shuffle(shuffled)
|
random.shuffle(shuffled)
|
||||||
self._cp_tracks = before + shuffled + after
|
self._cp_tracks = before + shuffled + after
|
||||||
self.version += 1
|
self.version += 1
|
||||||
|
|
||||||
def mpd_format(self, *args, **kwargs):
|
|
||||||
"""Not a part of the generic backend API."""
|
|
||||||
kwargs['cpids'] = [ct[0] for ct in self._cp_tracks]
|
|
||||||
return translator.tracks_to_mpd_format(self.tracks, *args, **kwargs)
|
|
||||||
|
|||||||
@ -10,6 +10,8 @@ class LibraryController(object):
|
|||||||
:type provider: instance of :class:`BaseLibraryProvider`
|
:type provider: instance of :class:`BaseLibraryProvider`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
pykka_traversable = True
|
||||||
|
|
||||||
def __init__(self, backend, provider):
|
def __init__(self, backend, provider):
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
self.provider = provider
|
self.provider = provider
|
||||||
@ -82,6 +84,8 @@ class BaseLibraryProvider(object):
|
|||||||
:type backend: :class:`mopidy.backends.base.Backend`
|
:type backend: :class:`mopidy.backends.base.Backend`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
pykka_traversable = True
|
||||||
|
|
||||||
def __init__(self, backend):
|
def __init__(self, backend):
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,10 @@ import logging
|
|||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from pykka.registry import ActorRegistry
|
||||||
|
|
||||||
|
from mopidy.frontends.base import BaseFrontend
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.backends.base')
|
logger = logging.getLogger('mopidy.backends.base')
|
||||||
|
|
||||||
class PlaybackController(object):
|
class PlaybackController(object):
|
||||||
@ -15,6 +19,8 @@ class PlaybackController(object):
|
|||||||
# pylint: disable = R0902
|
# pylint: disable = R0902
|
||||||
# Too many instance attributes
|
# Too many instance attributes
|
||||||
|
|
||||||
|
pykka_traversable = True
|
||||||
|
|
||||||
#: Constant representing the paused state.
|
#: Constant representing the paused state.
|
||||||
PAUSED = u'paused'
|
PAUSED = u'paused'
|
||||||
|
|
||||||
@ -62,8 +68,8 @@ class PlaybackController(object):
|
|||||||
self._state = self.STOPPED
|
self._state = self.STOPPED
|
||||||
self._shuffled = []
|
self._shuffled = []
|
||||||
self._first_shuffle = True
|
self._first_shuffle = True
|
||||||
self._play_time_accumulated = 0
|
self.play_time_accumulated = 0
|
||||||
self._play_time_started = None
|
self.play_time_started = None
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
"""
|
"""
|
||||||
@ -269,7 +275,7 @@ class PlaybackController(object):
|
|||||||
def state(self, new_state):
|
def state(self, new_state):
|
||||||
(old_state, self._state) = (self.state, new_state)
|
(old_state, self._state) = (self.state, new_state)
|
||||||
logger.debug(u'Changing state: %s -> %s', old_state, new_state)
|
logger.debug(u'Changing state: %s -> %s', old_state, new_state)
|
||||||
# FIXME _play_time stuff assumes backend does not have a better way of
|
# FIXME play_time stuff assumes backend does not have a better way of
|
||||||
# handeling this stuff :/
|
# handeling this stuff :/
|
||||||
if (old_state in (self.PLAYING, self.STOPPED)
|
if (old_state in (self.PLAYING, self.STOPPED)
|
||||||
and new_state == self.PLAYING):
|
and new_state == self.PLAYING):
|
||||||
@ -284,23 +290,23 @@ class PlaybackController(object):
|
|||||||
"""Time position in milliseconds."""
|
"""Time position in milliseconds."""
|
||||||
if self.state == self.PLAYING:
|
if self.state == self.PLAYING:
|
||||||
time_since_started = (self._current_wall_time -
|
time_since_started = (self._current_wall_time -
|
||||||
self._play_time_started)
|
self.play_time_started)
|
||||||
return self._play_time_accumulated + time_since_started
|
return self.play_time_accumulated + time_since_started
|
||||||
elif self.state == self.PAUSED:
|
elif self.state == self.PAUSED:
|
||||||
return self._play_time_accumulated
|
return self.play_time_accumulated
|
||||||
elif self.state == self.STOPPED:
|
elif self.state == self.STOPPED:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def _play_time_start(self):
|
def _play_time_start(self):
|
||||||
self._play_time_accumulated = 0
|
self.play_time_accumulated = 0
|
||||||
self._play_time_started = self._current_wall_time
|
self.play_time_started = self._current_wall_time
|
||||||
|
|
||||||
def _play_time_pause(self):
|
def _play_time_pause(self):
|
||||||
time_since_started = self._current_wall_time - self._play_time_started
|
time_since_started = self._current_wall_time - self.play_time_started
|
||||||
self._play_time_accumulated += time_since_started
|
self.play_time_accumulated += time_since_started
|
||||||
|
|
||||||
def _play_time_resume(self):
|
def _play_time_resume(self):
|
||||||
self._play_time_started = self._current_wall_time
|
self.play_time_started = self._current_wall_time
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _current_wall_time(self):
|
def _current_wall_time(self):
|
||||||
@ -433,8 +439,8 @@ class PlaybackController(object):
|
|||||||
self.next()
|
self.next()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
self._play_time_started = self._current_wall_time
|
self.play_time_started = self._current_wall_time
|
||||||
self._play_time_accumulated = time_position
|
self.play_time_accumulated = time_position
|
||||||
|
|
||||||
return self.provider.seek(time_position)
|
return self.provider.seek(time_position)
|
||||||
|
|
||||||
@ -461,9 +467,11 @@ class PlaybackController(object):
|
|||||||
For internal use only. Should be called by the backend directly after a
|
For internal use only. Should be called by the backend directly after a
|
||||||
track has started playing.
|
track has started playing.
|
||||||
"""
|
"""
|
||||||
if self.current_track is not None:
|
if self.current_track is None:
|
||||||
self.backend.core_queue.put({
|
return
|
||||||
'to': 'frontend',
|
frontend_refs = ActorRegistry.get_by_class(BaseFrontend)
|
||||||
|
for frontend_ref in frontend_refs:
|
||||||
|
frontend_ref.send_one_way({
|
||||||
'command': 'started_playing',
|
'command': 'started_playing',
|
||||||
'track': self.current_track,
|
'track': self.current_track,
|
||||||
})
|
})
|
||||||
@ -476,9 +484,11 @@ class PlaybackController(object):
|
|||||||
is stopped playing, e.g. at the next, previous, and stop actions and at
|
is stopped playing, e.g. at the next, previous, and stop actions and at
|
||||||
end-of-track.
|
end-of-track.
|
||||||
"""
|
"""
|
||||||
if self.current_track is not None:
|
if self.current_track is None:
|
||||||
self.backend.core_queue.put({
|
return
|
||||||
'to': 'frontend',
|
frontend_refs = ActorRegistry.get_by_class(BaseFrontend)
|
||||||
|
for frontend_ref in frontend_refs:
|
||||||
|
frontend_ref.send_one_way({
|
||||||
'command': 'stopped_playing',
|
'command': 'stopped_playing',
|
||||||
'track': self.current_track,
|
'track': self.current_track,
|
||||||
'stop_position': self.time_position,
|
'stop_position': self.time_position,
|
||||||
@ -491,6 +501,8 @@ class BasePlaybackProvider(object):
|
|||||||
:type backend: :class:`mopidy.backends.base.Backend`
|
:type backend: :class:`mopidy.backends.base.Backend`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
pykka_traversable = True
|
||||||
|
|
||||||
def __init__(self, backend):
|
def __init__(self, backend):
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,8 @@ class StoredPlaylistsController(object):
|
|||||||
:type provider: instance of :class:`BaseStoredPlaylistsProvider`
|
:type provider: instance of :class:`BaseStoredPlaylistsProvider`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
pykka_traversable = True
|
||||||
|
|
||||||
def __init__(self, backend, provider):
|
def __init__(self, backend, provider):
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
self.provider = provider
|
self.provider = provider
|
||||||
@ -125,6 +127,8 @@ class BaseStoredPlaylistsProvider(object):
|
|||||||
:type backend: :class:`mopidy.backends.base.Backend`
|
:type backend: :class:`mopidy.backends.base.Backend`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
pykka_traversable = True
|
||||||
|
|
||||||
def __init__(self, backend):
|
def __init__(self, backend):
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
self._playlists = []
|
self._playlists = []
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from pykka.actor import ThreadingActor
|
||||||
|
|
||||||
from mopidy.backends.base import (Backend, CurrentPlaylistController,
|
from mopidy.backends.base import (Backend, CurrentPlaylistController,
|
||||||
PlaybackController, BasePlaybackProvider, LibraryController,
|
PlaybackController, BasePlaybackProvider, LibraryController,
|
||||||
BaseLibraryProvider, StoredPlaylistsController,
|
BaseLibraryProvider, StoredPlaylistsController,
|
||||||
@ -5,15 +7,7 @@ from mopidy.backends.base import (Backend, CurrentPlaylistController,
|
|||||||
from mopidy.models import Playlist
|
from mopidy.models import Playlist
|
||||||
|
|
||||||
|
|
||||||
class DummyQueue(object):
|
class DummyBackend(ThreadingActor, Backend):
|
||||||
def __init__(self):
|
|
||||||
self.received_messages = []
|
|
||||||
|
|
||||||
def put(self, message):
|
|
||||||
self.received_messages.append(message)
|
|
||||||
|
|
||||||
|
|
||||||
class DummyBackend(Backend):
|
|
||||||
"""
|
"""
|
||||||
A backend which implements the backend API in the simplest way possible.
|
A backend which implements the backend API in the simplest way possible.
|
||||||
Used in tests of the frontends.
|
Used in tests of the frontends.
|
||||||
@ -24,8 +18,6 @@ class DummyBackend(Backend):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(DummyBackend, self).__init__(*args, **kwargs)
|
super(DummyBackend, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.core_queue = DummyQueue()
|
|
||||||
|
|
||||||
self.current_playlist = CurrentPlaylistController(backend=self)
|
self.current_playlist = CurrentPlaylistController(backend=self)
|
||||||
|
|
||||||
library_provider = DummyLibraryProvider(backend=self)
|
library_provider = DummyLibraryProvider(backend=self)
|
||||||
@ -46,13 +38,13 @@ class DummyBackend(Backend):
|
|||||||
class DummyLibraryProvider(BaseLibraryProvider):
|
class DummyLibraryProvider(BaseLibraryProvider):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(DummyLibraryProvider, self).__init__(*args, **kwargs)
|
super(DummyLibraryProvider, self).__init__(*args, **kwargs)
|
||||||
self._library = []
|
self.dummy_library = []
|
||||||
|
|
||||||
def find_exact(self, **query):
|
def find_exact(self, **query):
|
||||||
return Playlist()
|
return Playlist()
|
||||||
|
|
||||||
def lookup(self, uri):
|
def lookup(self, uri):
|
||||||
matches = filter(lambda t: uri == t.uri, self._library)
|
matches = filter(lambda t: uri == t.uri, self.dummy_library)
|
||||||
if matches:
|
if matches:
|
||||||
return matches[0]
|
return matches[0]
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,24 @@
|
|||||||
import glob
|
import glob
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
from pykka.actor import ThreadingActor
|
||||||
|
from pykka.registry import ActorRegistry
|
||||||
|
|
||||||
from mopidy import settings
|
from mopidy import settings
|
||||||
from mopidy.backends.base import (Backend, CurrentPlaylistController,
|
from mopidy.backends.base import (Backend, CurrentPlaylistController,
|
||||||
LibraryController, BaseLibraryProvider, PlaybackController,
|
LibraryController, BaseLibraryProvider, PlaybackController,
|
||||||
BasePlaybackProvider, StoredPlaylistsController,
|
BasePlaybackProvider, StoredPlaylistsController,
|
||||||
BaseStoredPlaylistsProvider)
|
BaseStoredPlaylistsProvider)
|
||||||
from mopidy.models import Playlist, Track, Album
|
from mopidy.models import Playlist, Track, Album
|
||||||
from mopidy.utils.process import pickle_connection
|
from mopidy.outputs.base import BaseOutput
|
||||||
|
|
||||||
from .translator import parse_m3u, parse_mpd_tag_cache
|
from .translator import parse_m3u, parse_mpd_tag_cache
|
||||||
|
|
||||||
logger = logging.getLogger(u'mopidy.backends.local')
|
logger = logging.getLogger(u'mopidy.backends.local')
|
||||||
|
|
||||||
class LocalBackend(Backend):
|
class LocalBackend(ThreadingActor, Backend):
|
||||||
"""
|
"""
|
||||||
A backend for playing music from a local music archive.
|
A backend for playing music from a local music archive.
|
||||||
|
|
||||||
@ -48,6 +50,13 @@ class LocalBackend(Backend):
|
|||||||
|
|
||||||
self.uri_handlers = [u'file://']
|
self.uri_handlers = [u'file://']
|
||||||
|
|
||||||
|
self.output = None
|
||||||
|
|
||||||
|
def on_start(self):
|
||||||
|
output_refs = ActorRegistry.get_by_class(BaseOutput)
|
||||||
|
assert len(output_refs) == 1, 'Expected exactly one running output.'
|
||||||
|
self.output = output_refs[0].proxy()
|
||||||
|
|
||||||
|
|
||||||
class LocalPlaybackController(PlaybackController):
|
class LocalPlaybackController(PlaybackController):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -58,24 +67,24 @@ class LocalPlaybackController(PlaybackController):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def time_position(self):
|
def time_position(self):
|
||||||
return self.backend.output.get_position()
|
return self.backend.output.get_position().get()
|
||||||
|
|
||||||
|
|
||||||
class LocalPlaybackProvider(BasePlaybackProvider):
|
class LocalPlaybackProvider(BasePlaybackProvider):
|
||||||
def pause(self):
|
def pause(self):
|
||||||
return self.backend.output.set_state('PAUSED')
|
return self.backend.output.set_state('PAUSED').get()
|
||||||
|
|
||||||
def play(self, track):
|
def play(self, track):
|
||||||
return self.backend.output.play_uri(track.uri)
|
return self.backend.output.play_uri(track.uri).get()
|
||||||
|
|
||||||
def resume(self):
|
def resume(self):
|
||||||
return self.backend.output.set_state('PLAYING')
|
return self.backend.output.set_state('PLAYING').get()
|
||||||
|
|
||||||
def seek(self, time_position):
|
def seek(self, time_position):
|
||||||
return self.backend.output.set_position(time_position)
|
return self.backend.output.set_position(time_position).get()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
return self.backend.output.set_state('READY')
|
return self.backend.output.set_state('READY').get()
|
||||||
|
|
||||||
|
|
||||||
class LocalStoredPlaylistsProvider(BaseStoredPlaylistsProvider):
|
class LocalStoredPlaylistsProvider(BaseStoredPlaylistsProvider):
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from pykka.actor import ThreadingActor
|
||||||
|
from pykka.registry import ActorRegistry
|
||||||
|
|
||||||
from mopidy import settings
|
from mopidy import settings
|
||||||
from mopidy.backends.base import (Backend, CurrentPlaylistController,
|
from mopidy.backends.base import (Backend, CurrentPlaylistController,
|
||||||
LibraryController, PlaybackController, StoredPlaylistsController)
|
LibraryController, PlaybackController, StoredPlaylistsController)
|
||||||
|
from mopidy.outputs.base import BaseOutput
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.backends.spotify')
|
logger = logging.getLogger('mopidy.backends.spotify')
|
||||||
|
|
||||||
ENCODING = 'utf-8'
|
ENCODING = 'utf-8'
|
||||||
|
|
||||||
class SpotifyBackend(Backend):
|
class SpotifyBackend(ThreadingActor, Backend):
|
||||||
"""
|
"""
|
||||||
A backend for playing music from the `Spotify <http://www.spotify.com/>`_
|
A backend for playing music from the `Spotify <http://www.spotify.com/>`_
|
||||||
music streaming service. The backend uses the official `libspotify
|
music streaming service. The backend uses the official `libspotify
|
||||||
@ -59,6 +63,14 @@ class SpotifyBackend(Backend):
|
|||||||
|
|
||||||
self.uri_handlers = [u'spotify:', u'http://open.spotify.com/']
|
self.uri_handlers = [u'spotify:', u'http://open.spotify.com/']
|
||||||
|
|
||||||
|
self.output = None
|
||||||
|
self.spotify = None
|
||||||
|
|
||||||
|
def on_start(self):
|
||||||
|
output_refs = ActorRegistry.get_by_class(BaseOutput)
|
||||||
|
assert len(output_refs) == 1, 'Expected exactly one running output.'
|
||||||
|
self.output = output_refs[0].proxy()
|
||||||
|
|
||||||
self.spotify = self._connect()
|
self.spotify = self._connect()
|
||||||
|
|
||||||
def _connect(self):
|
def _connect(self):
|
||||||
@ -67,8 +79,6 @@ class SpotifyBackend(Backend):
|
|||||||
logger.info(u'Mopidy uses SPOTIFY(R) CORE')
|
logger.info(u'Mopidy uses SPOTIFY(R) CORE')
|
||||||
logger.debug(u'Connecting to Spotify')
|
logger.debug(u'Connecting to Spotify')
|
||||||
spotify = SpotifySessionManager(
|
spotify = SpotifySessionManager(
|
||||||
settings.SPOTIFY_USERNAME, settings.SPOTIFY_PASSWORD,
|
settings.SPOTIFY_USERNAME, settings.SPOTIFY_PASSWORD)
|
||||||
core_queue=self.core_queue,
|
|
||||||
output=self.output)
|
|
||||||
spotify.start()
|
spotify.start()
|
||||||
return spotify
|
return spotify
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
import Queue
|
||||||
|
|
||||||
from spotify import Link, SpotifyError
|
from spotify import Link, SpotifyError
|
||||||
|
|
||||||
@ -54,8 +54,9 @@ class SpotifyLibraryProvider(BaseLibraryProvider):
|
|||||||
spotify_query.append(u'%s:"%s"' % (field, value))
|
spotify_query.append(u'%s:"%s"' % (field, value))
|
||||||
spotify_query = u' '.join(spotify_query)
|
spotify_query = u' '.join(spotify_query)
|
||||||
logger.debug(u'Spotify search query: %s' % spotify_query)
|
logger.debug(u'Spotify search query: %s' % spotify_query)
|
||||||
my_end, other_end = multiprocessing.Pipe()
|
queue = Queue.Queue()
|
||||||
self.backend.spotify.search(spotify_query.encode(ENCODING), other_end)
|
self.backend.spotify.search(spotify_query.encode(ENCODING), queue)
|
||||||
my_end.poll(None)
|
try:
|
||||||
playlist = my_end.recv()
|
return queue.get(timeout=3) # XXX What is an reasonable timeout?
|
||||||
return playlist
|
except Queue.Empty:
|
||||||
|
return Playlist(tracks=[])
|
||||||
|
|||||||
@ -2,11 +2,15 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import spotify.manager
|
from spotify.manager import SpotifySessionManager as PyspotifySessionManager
|
||||||
|
|
||||||
|
from pykka.registry import ActorRegistry
|
||||||
|
|
||||||
from mopidy import get_version, settings
|
from mopidy import get_version, settings
|
||||||
|
from mopidy.backends.base import Backend
|
||||||
from mopidy.backends.spotify.translator import SpotifyTranslator
|
from mopidy.backends.spotify.translator import SpotifyTranslator
|
||||||
from mopidy.models import Playlist
|
from mopidy.models import Playlist
|
||||||
|
from mopidy.outputs.base import BaseOutput
|
||||||
from mopidy.utils.process import BaseThread
|
from mopidy.utils.process import BaseThread
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.backends.spotify.session_manager')
|
logger = logging.getLogger('mopidy.backends.spotify.session_manager')
|
||||||
@ -14,24 +18,36 @@ logger = logging.getLogger('mopidy.backends.spotify.session_manager')
|
|||||||
# pylint: disable = R0901
|
# pylint: disable = R0901
|
||||||
# SpotifySessionManager: Too many ancestors (9/7)
|
# SpotifySessionManager: Too many ancestors (9/7)
|
||||||
|
|
||||||
class SpotifySessionManager(spotify.manager.SpotifySessionManager, BaseThread):
|
class SpotifySessionManager(BaseThread, PyspotifySessionManager):
|
||||||
cache_location = settings.SPOTIFY_CACHE_PATH
|
cache_location = settings.SPOTIFY_CACHE_PATH
|
||||||
settings_location = settings.SPOTIFY_CACHE_PATH
|
settings_location = settings.SPOTIFY_CACHE_PATH
|
||||||
appkey_file = os.path.join(os.path.dirname(__file__), 'spotify_appkey.key')
|
appkey_file = os.path.join(os.path.dirname(__file__), 'spotify_appkey.key')
|
||||||
user_agent = 'Mopidy %s' % get_version()
|
user_agent = 'Mopidy %s' % get_version()
|
||||||
|
|
||||||
def __init__(self, username, password, core_queue, output):
|
def __init__(self, username, password):
|
||||||
spotify.manager.SpotifySessionManager.__init__(
|
PyspotifySessionManager.__init__(self, username, password)
|
||||||
self, username, password)
|
BaseThread.__init__(self)
|
||||||
BaseThread.__init__(self, core_queue)
|
|
||||||
self.name = 'SpotifySMThread'
|
self.name = 'SpotifySMThread'
|
||||||
self.output = output
|
|
||||||
|
self.output = None
|
||||||
|
self.backend = None
|
||||||
|
|
||||||
self.connected = threading.Event()
|
self.connected = threading.Event()
|
||||||
self.session = None
|
self.session = None
|
||||||
|
|
||||||
def run_inside_try(self):
|
def run_inside_try(self):
|
||||||
|
self.setup()
|
||||||
self.connect()
|
self.connect()
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
output_refs = ActorRegistry.get_by_class(BaseOutput)
|
||||||
|
assert len(output_refs) == 1, 'Expected exactly one running output.'
|
||||||
|
self.output = output_refs[0].proxy()
|
||||||
|
|
||||||
|
backend_refs = ActorRegistry.get_by_class(Backend)
|
||||||
|
assert len(backend_refs) == 1, 'Expected exactly one running backend.'
|
||||||
|
self.backend = backend_refs[0].proxy()
|
||||||
|
|
||||||
def logged_in(self, session, error):
|
def logged_in(self, session, error):
|
||||||
"""Callback used by pyspotify"""
|
"""Callback used by pyspotify"""
|
||||||
if error:
|
if error:
|
||||||
@ -91,7 +107,7 @@ class SpotifySessionManager(spotify.manager.SpotifySessionManager, BaseThread):
|
|||||||
def play_token_lost(self, session):
|
def play_token_lost(self, session):
|
||||||
"""Callback used by pyspotify"""
|
"""Callback used by pyspotify"""
|
||||||
logger.debug(u'Play token lost')
|
logger.debug(u'Play token lost')
|
||||||
self.core_queue.put({'command': 'stop_playback'})
|
self.backend.playback.pause()
|
||||||
|
|
||||||
def log_message(self, session, data):
|
def log_message(self, session, data):
|
||||||
"""Callback used by pyspotify"""
|
"""Callback used by pyspotify"""
|
||||||
@ -110,19 +126,16 @@ class SpotifySessionManager(spotify.manager.SpotifySessionManager, BaseThread):
|
|||||||
playlists.append(
|
playlists.append(
|
||||||
SpotifyTranslator.to_mopidy_playlist(spotify_playlist))
|
SpotifyTranslator.to_mopidy_playlist(spotify_playlist))
|
||||||
playlists = filter(None, playlists)
|
playlists = filter(None, playlists)
|
||||||
self.core_queue.put({
|
self.backend.stored_playlists.playlists = playlists
|
||||||
'command': 'set_stored_playlists',
|
|
||||||
'playlists': playlists,
|
|
||||||
})
|
|
||||||
logger.debug(u'Refreshed %d stored playlist(s)', len(playlists))
|
logger.debug(u'Refreshed %d stored playlist(s)', len(playlists))
|
||||||
|
|
||||||
def search(self, query, connection):
|
def search(self, query, queue):
|
||||||
"""Search method used by Mopidy backend"""
|
"""Search method used by Mopidy backend"""
|
||||||
def callback(results, userdata=None):
|
def callback(results, userdata=None):
|
||||||
# TODO Include results from results.albums(), etc. too
|
# TODO Include results from results.albums(), etc. too
|
||||||
playlist = Playlist(tracks=[
|
playlist = Playlist(tracks=[
|
||||||
SpotifyTranslator.to_mopidy_track(t)
|
SpotifyTranslator.to_mopidy_track(t)
|
||||||
for t in results.tracks()])
|
for t in results.tracks()])
|
||||||
connection.send(playlist)
|
queue.put(playlist)
|
||||||
self.connected.wait()
|
self.connected.wait()
|
||||||
self.session.search(query, callback)
|
self.session.search(query, callback)
|
||||||
|
|||||||
152
mopidy/core.py
152
mopidy/core.py
@ -1,114 +1,74 @@
|
|||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
|
||||||
import optparse
|
import optparse
|
||||||
import sys
|
import time
|
||||||
|
|
||||||
|
from pykka.registry import ActorRegistry
|
||||||
|
|
||||||
from mopidy import get_version, settings, OptionalDependencyError
|
from mopidy import get_version, settings, OptionalDependencyError
|
||||||
from mopidy.utils import get_class
|
from mopidy.utils import get_class
|
||||||
from mopidy.utils.log import setup_logging
|
from mopidy.utils.log import setup_logging
|
||||||
from mopidy.utils.path import get_or_create_folder, get_or_create_file
|
from mopidy.utils.path import get_or_create_folder, get_or_create_file
|
||||||
from mopidy.utils.process import BaseThread, GObjectEventThread
|
from mopidy.utils.process import GObjectEventThread
|
||||||
from mopidy.utils.settings import list_settings_optparse_callback
|
from mopidy.utils.settings import list_settings_optparse_callback
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.core')
|
logger = logging.getLogger('mopidy.core')
|
||||||
|
|
||||||
class CoreProcess(BaseThread):
|
def main():
|
||||||
def __init__(self):
|
options = parse_options()
|
||||||
self.core_queue = multiprocessing.Queue()
|
setup_logging(options.verbosity_level, options.save_debug_log)
|
||||||
super(CoreProcess, self).__init__(self.core_queue)
|
setup_settings()
|
||||||
self.name = 'CoreProcess'
|
setup_gobject_loop()
|
||||||
self.options = self.parse_options()
|
setup_output()
|
||||||
self.gobject_loop = None
|
setup_mixer()
|
||||||
self.output = None
|
setup_backend()
|
||||||
self.backend = None
|
setup_frontends()
|
||||||
self.frontends = []
|
try:
|
||||||
|
time.sleep(10000*24*60*60)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info(u'Exiting...')
|
||||||
|
ActorRegistry.stop_all()
|
||||||
|
|
||||||
def parse_options(self):
|
def parse_options():
|
||||||
parser = optparse.OptionParser(version='Mopidy %s' % get_version())
|
parser = optparse.OptionParser(version='Mopidy %s' % get_version())
|
||||||
parser.add_option('-q', '--quiet',
|
parser.add_option('-q', '--quiet',
|
||||||
action='store_const', const=0, dest='verbosity_level',
|
action='store_const', const=0, dest='verbosity_level',
|
||||||
help='less output (warning level)')
|
help='less output (warning level)')
|
||||||
parser.add_option('-v', '--verbose',
|
parser.add_option('-v', '--verbose',
|
||||||
action='store_const', const=2, dest='verbosity_level',
|
action='store_const', const=2, dest='verbosity_level',
|
||||||
help='more output (debug level)')
|
help='more output (debug level)')
|
||||||
parser.add_option('--save-debug-log',
|
parser.add_option('--save-debug-log',
|
||||||
action='store_true', dest='save_debug_log',
|
action='store_true', dest='save_debug_log',
|
||||||
help='save debug log to "./mopidy.log"')
|
help='save debug log to "./mopidy.log"')
|
||||||
parser.add_option('--list-settings',
|
parser.add_option('--list-settings',
|
||||||
action='callback', callback=list_settings_optparse_callback,
|
action='callback', callback=list_settings_optparse_callback,
|
||||||
help='list current settings')
|
help='list current settings')
|
||||||
return parser.parse_args()[0]
|
return parser.parse_args()[0]
|
||||||
|
|
||||||
def run_inside_try(self):
|
def setup_settings():
|
||||||
self.setup()
|
get_or_create_folder('~/.mopidy/')
|
||||||
while True:
|
get_or_create_file('~/.mopidy/settings.py')
|
||||||
message = self.core_queue.get()
|
settings.validate()
|
||||||
self.process_message(message)
|
|
||||||
|
|
||||||
def setup(self):
|
def setup_gobject_loop():
|
||||||
self.setup_logging()
|
gobject_loop = GObjectEventThread()
|
||||||
self.setup_settings()
|
gobject_loop.start()
|
||||||
self.gobject_loop = self.setup_gobject_loop(self.core_queue)
|
return gobject_loop
|
||||||
self.output = self.setup_output(self.core_queue)
|
|
||||||
self.backend = self.setup_backend(self.core_queue, self.output)
|
|
||||||
self.frontends = self.setup_frontends(self.core_queue, self.backend)
|
|
||||||
|
|
||||||
def setup_logging(self):
|
def setup_output():
|
||||||
setup_logging(self.options.verbosity_level,
|
return get_class(settings.OUTPUT).start().proxy()
|
||||||
self.options.save_debug_log)
|
|
||||||
logger.info(u'-- Starting Mopidy %s --', get_version())
|
|
||||||
|
|
||||||
def setup_settings(self):
|
def setup_mixer():
|
||||||
get_or_create_folder('~/.mopidy/')
|
return get_class(settings.MIXER).start().proxy()
|
||||||
get_or_create_file('~/.mopidy/settings.py')
|
|
||||||
settings.validate()
|
|
||||||
|
|
||||||
def setup_gobject_loop(self, core_queue):
|
def setup_backend():
|
||||||
gobject_loop = GObjectEventThread(core_queue)
|
return get_class(settings.BACKENDS[0]).start().proxy()
|
||||||
gobject_loop.start()
|
|
||||||
return gobject_loop
|
|
||||||
|
|
||||||
def setup_output(self, core_queue):
|
def setup_frontends():
|
||||||
output = get_class(settings.OUTPUT)(core_queue)
|
frontends = []
|
||||||
output.start()
|
for frontend_class_name in settings.FRONTENDS:
|
||||||
return output
|
try:
|
||||||
|
frontend = get_class(frontend_class_name).start().proxy()
|
||||||
def setup_backend(self, core_queue, output):
|
frontends.append(frontend)
|
||||||
return get_class(settings.BACKENDS[0])(core_queue, output)
|
except OptionalDependencyError as e:
|
||||||
|
logger.info(u'Disabled: %s (%s)', frontend_class_name, e)
|
||||||
def setup_frontends(self, core_queue, backend):
|
return frontends
|
||||||
frontends = []
|
|
||||||
for frontend_class_name in settings.FRONTENDS:
|
|
||||||
try:
|
|
||||||
frontend = get_class(frontend_class_name)(core_queue, backend)
|
|
||||||
frontend.start()
|
|
||||||
frontends.append(frontend)
|
|
||||||
except OptionalDependencyError as e:
|
|
||||||
logger.info(u'Disabled: %s (%s)', frontend_class_name, e)
|
|
||||||
return frontends
|
|
||||||
|
|
||||||
def process_message(self, message):
|
|
||||||
if message.get('to') == 'core':
|
|
||||||
self.process_message_to_core(message)
|
|
||||||
elif message.get('to') == 'output':
|
|
||||||
self.output.process_message(message)
|
|
||||||
elif message.get('to') == 'frontend':
|
|
||||||
for frontend in self.frontends:
|
|
||||||
frontend.process_message(message)
|
|
||||||
elif message['command'] == 'end_of_track':
|
|
||||||
self.backend.playback.on_end_of_track()
|
|
||||||
elif message['command'] == 'stop_playback':
|
|
||||||
self.backend.playback.stop()
|
|
||||||
elif message['command'] == 'set_stored_playlists':
|
|
||||||
self.backend.stored_playlists.playlists = message['playlists']
|
|
||||||
else:
|
|
||||||
logger.warning(u'Cannot handle message: %s', message)
|
|
||||||
|
|
||||||
def process_message_to_core(self, message):
|
|
||||||
assert message['to'] == 'core', u'Message recipient must be "core".'
|
|
||||||
if message['command'] == 'exit':
|
|
||||||
if message['reason'] is not None:
|
|
||||||
logger.info(u'Exiting (%s)', message['reason'])
|
|
||||||
sys.exit(message['status'])
|
|
||||||
else:
|
|
||||||
logger.warning(u'Cannot handle message: %s', message)
|
|
||||||
|
|||||||
@ -1,40 +1,5 @@
|
|||||||
class BaseFrontend(object):
|
class BaseFrontend(object):
|
||||||
"""
|
"""
|
||||||
Base class for frontends.
|
Base class for frontends.
|
||||||
|
|
||||||
:param core_queue: queue for messaging the core
|
|
||||||
:type core_queue: :class:`multiprocessing.Queue`
|
|
||||||
:param backend: the backend
|
|
||||||
:type backend: :class:`mopidy.backends.base.Backend`
|
|
||||||
"""
|
"""
|
||||||
|
pass
|
||||||
def __init__(self, core_queue, backend):
|
|
||||||
self.core_queue = core_queue
|
|
||||||
self.backend = backend
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""
|
|
||||||
Start the frontend.
|
|
||||||
|
|
||||||
*MAY be implemented by subclass.*
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def destroy(self):
|
|
||||||
"""
|
|
||||||
Destroy the frontend.
|
|
||||||
|
|
||||||
*MAY be implemented by subclass.*
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def process_message(self, message):
|
|
||||||
"""
|
|
||||||
Process messages for the frontend.
|
|
||||||
|
|
||||||
*MUST be implemented by subclass.*
|
|
||||||
|
|
||||||
:param message: the message
|
|
||||||
:type message: dict
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -8,16 +7,17 @@ except ImportError as import_error:
|
|||||||
from mopidy import OptionalDependencyError
|
from mopidy import OptionalDependencyError
|
||||||
raise OptionalDependencyError(import_error)
|
raise OptionalDependencyError(import_error)
|
||||||
|
|
||||||
|
from pykka.actor import ThreadingActor
|
||||||
|
|
||||||
from mopidy import settings, SettingsError
|
from mopidy import settings, SettingsError
|
||||||
from mopidy.frontends.base import BaseFrontend
|
from mopidy.frontends.base import BaseFrontend
|
||||||
from mopidy.utils.process import BaseThread
|
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.frontends.lastfm')
|
logger = logging.getLogger('mopidy.frontends.lastfm')
|
||||||
|
|
||||||
API_KEY = '2236babefa8ebb3d93ea467560d00d04'
|
API_KEY = '2236babefa8ebb3d93ea467560d00d04'
|
||||||
API_SECRET = '94d9a09c0cd5be955c4afaeaffcaefcd'
|
API_SECRET = '94d9a09c0cd5be955c4afaeaffcaefcd'
|
||||||
|
|
||||||
class LastfmFrontend(BaseFrontend):
|
class LastfmFrontend(ThreadingActor, BaseFrontend):
|
||||||
"""
|
"""
|
||||||
Frontend which scrobbles the music you play to your `Last.fm
|
Frontend which scrobbles the music you play to your `Last.fm
|
||||||
<http://www.last.fm>`_ profile.
|
<http://www.last.fm>`_ profile.
|
||||||
@ -36,38 +36,11 @@ class LastfmFrontend(BaseFrontend):
|
|||||||
- :attr:`mopidy.settings.LASTFM_PASSWORD`
|
- :attr:`mopidy.settings.LASTFM_PASSWORD`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self):
|
||||||
super(LastfmFrontend, self).__init__(*args, **kwargs)
|
|
||||||
(self.connection, other_end) = multiprocessing.Pipe()
|
|
||||||
self.thread = LastfmFrontendThread(self.core_queue, other_end)
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self.thread.start()
|
|
||||||
|
|
||||||
def destroy(self):
|
|
||||||
self.thread.destroy()
|
|
||||||
|
|
||||||
def process_message(self, message):
|
|
||||||
if self.thread.is_alive():
|
|
||||||
self.connection.send(message)
|
|
||||||
|
|
||||||
|
|
||||||
class LastfmFrontendThread(BaseThread):
|
|
||||||
def __init__(self, core_queue, connection):
|
|
||||||
super(LastfmFrontendThread, self).__init__(core_queue)
|
|
||||||
self.name = u'LastfmFrontendThread'
|
|
||||||
self.connection = connection
|
|
||||||
self.lastfm = None
|
self.lastfm = None
|
||||||
self.last_start_time = None
|
self.last_start_time = None
|
||||||
|
|
||||||
def run_inside_try(self):
|
def on_start(self):
|
||||||
self.setup()
|
|
||||||
while self.lastfm is not None:
|
|
||||||
self.connection.poll(None)
|
|
||||||
message = self.connection.recv()
|
|
||||||
self.process_message(message)
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
try:
|
try:
|
||||||
username = settings.LASTFM_USERNAME
|
username = settings.LASTFM_USERNAME
|
||||||
password_hash = pylast.md5(settings.LASTFM_PASSWORD)
|
password_hash = pylast.md5(settings.LASTFM_PASSWORD)
|
||||||
@ -78,17 +51,19 @@ class LastfmFrontendThread(BaseThread):
|
|||||||
except SettingsError as e:
|
except SettingsError as e:
|
||||||
logger.info(u'Last.fm scrobbler not started')
|
logger.info(u'Last.fm scrobbler not started')
|
||||||
logger.debug(u'Last.fm settings error: %s', e)
|
logger.debug(u'Last.fm settings error: %s', e)
|
||||||
|
self.stop()
|
||||||
except (pylast.NetworkError, pylast.MalformedResponseError,
|
except (pylast.NetworkError, pylast.MalformedResponseError,
|
||||||
pylast.WSError) as e:
|
pylast.WSError) as e:
|
||||||
logger.error(u'Error during Last.fm setup: %s', e)
|
logger.error(u'Error during Last.fm setup: %s', e)
|
||||||
|
self.stop()
|
||||||
|
|
||||||
def process_message(self, message):
|
def on_receive(self, message):
|
||||||
if message['command'] == 'started_playing':
|
if message.get('command') == 'started_playing':
|
||||||
self.started_playing(message['track'])
|
self.started_playing(message['track'])
|
||||||
elif message['command'] == 'stopped_playing':
|
elif message.get('command') == 'stopped_playing':
|
||||||
self.stopped_playing(message['track'], message['stop_position'])
|
self.stopped_playing(message['track'], message['stop_position'])
|
||||||
else:
|
else:
|
||||||
pass # Ignore commands for other frontends
|
pass # Ignore any other messages
|
||||||
|
|
||||||
def started_playing(self, track):
|
def started_playing(self, track):
|
||||||
artists = ', '.join([a.name for a in track.artists])
|
artists = ', '.join([a.name for a in track.artists])
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
|
import asyncore
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from pykka.actor import ThreadingActor
|
||||||
|
|
||||||
from mopidy.frontends.base import BaseFrontend
|
from mopidy.frontends.base import BaseFrontend
|
||||||
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
|
from mopidy.frontends.mpd.server import MpdServer
|
||||||
from mopidy.frontends.mpd.thread import MpdThread
|
from mopidy.utils.process import BaseThread
|
||||||
from mopidy.utils.process import unpickle_connection
|
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.frontends.mpd')
|
logger = logging.getLogger('mopidy.frontends.mpd')
|
||||||
|
|
||||||
class MpdFrontend(BaseFrontend):
|
class MpdFrontend(ThreadingActor, BaseFrontend):
|
||||||
"""
|
"""
|
||||||
The MPD frontend.
|
The MPD frontend.
|
||||||
|
|
||||||
@ -18,32 +20,24 @@ class MpdFrontend(BaseFrontend):
|
|||||||
- :attr:`mopidy.settings.MPD_SERVER_PORT`
|
- :attr:`mopidy.settings.MPD_SERVER_PORT`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self):
|
||||||
super(MpdFrontend, self).__init__(*args, **kwargs)
|
self._thread = None
|
||||||
self.thread = None
|
|
||||||
self.dispatcher = MpdDispatcher(self.backend)
|
|
||||||
|
|
||||||
def start(self):
|
def on_start(self):
|
||||||
"""Starts the MPD server."""
|
self._thread = MpdThread()
|
||||||
self.thread = MpdThread(self.core_queue)
|
self._thread.start()
|
||||||
self.thread.start()
|
|
||||||
|
|
||||||
def destroy(self):
|
def on_receive(self, message):
|
||||||
"""Destroys the MPD server."""
|
pass # Ignore any messages
|
||||||
self.thread.destroy()
|
|
||||||
|
|
||||||
def process_message(self, message):
|
|
||||||
"""
|
|
||||||
Processes messages with the MPD frontend as destination.
|
|
||||||
|
|
||||||
:param message: the message
|
class MpdThread(BaseThread):
|
||||||
:type message: dict
|
def __init__(self):
|
||||||
"""
|
super(MpdThread, self).__init__()
|
||||||
assert message['to'] == 'frontend', \
|
self.name = u'MpdThread'
|
||||||
u'Message recipient must be "frontend".'
|
|
||||||
if message['command'] == 'mpd_request':
|
def run_inside_try(self):
|
||||||
response = self.dispatcher.handle_request(message['request'])
|
logger.debug(u'Starting MPD server thread')
|
||||||
connection = unpickle_connection(message['reply_to'])
|
server = MpdServer()
|
||||||
connection.send(response)
|
server.start()
|
||||||
else:
|
asyncore.loop()
|
||||||
pass # Ignore messages for other frontends
|
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
from pykka.registry import ActorRegistry
|
||||||
|
|
||||||
|
from mopidy.backends.base import Backend
|
||||||
from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdArgError,
|
from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdArgError,
|
||||||
MpdUnknownCommand)
|
MpdUnknownCommand)
|
||||||
from mopidy.frontends.mpd.protocol import mpd_commands, request_handlers
|
from mopidy.frontends.mpd.protocol import mpd_commands, request_handlers
|
||||||
@ -10,15 +13,27 @@ from mopidy.frontends.mpd.protocol import (audio_output, command_list,
|
|||||||
connection, current_playlist, empty, music_db, playback, reflection,
|
connection, current_playlist, empty, music_db, playback, reflection,
|
||||||
status, stickers, stored_playlists)
|
status, stickers, stored_playlists)
|
||||||
# pylint: enable = W0611
|
# pylint: enable = W0611
|
||||||
|
from mopidy.mixers.base import BaseMixer
|
||||||
from mopidy.utils import flatten
|
from mopidy.utils import flatten
|
||||||
|
|
||||||
class MpdDispatcher(object):
|
class MpdDispatcher(object):
|
||||||
"""
|
"""
|
||||||
Dispatches MPD requests to the correct handler.
|
The MPD session feeds the MPD dispatcher with requests. The dispatcher
|
||||||
|
finds the correct handler, processes the request and sends the response
|
||||||
|
back to the MPD session.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, backend=None):
|
# XXX Consider merging MpdDispatcher into MpdSession
|
||||||
self.backend = backend
|
|
||||||
|
def __init__(self):
|
||||||
|
backend_refs = ActorRegistry.get_by_class(Backend)
|
||||||
|
assert len(backend_refs) == 1, 'Expected exactly one running backend.'
|
||||||
|
self.backend = backend_refs[0].proxy()
|
||||||
|
|
||||||
|
mixer_refs = ActorRegistry.get_by_class(BaseMixer)
|
||||||
|
assert len(mixer_refs) == 1, 'Expected exactly one running mixer.'
|
||||||
|
self.mixer = mixer_refs[0].proxy()
|
||||||
|
|
||||||
self.command_list = False
|
self.command_list = False
|
||||||
self.command_list_ok = False
|
self.command_list_ok = False
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,6 @@ def outputs(frontend):
|
|||||||
"""
|
"""
|
||||||
return [
|
return [
|
||||||
('outputid', 0),
|
('outputid', 0),
|
||||||
('outputname', frontend.backend.__class__.__name__),
|
('outputname', None),
|
||||||
('outputenabled', 1),
|
('outputenabled', 1),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from mopidy.frontends.mpd.protocol import handle_pattern
|
|
||||||
from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError,
|
from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError,
|
||||||
MpdNotImplemented)
|
MpdNotImplemented)
|
||||||
|
from mopidy.frontends.mpd.protocol import handle_pattern
|
||||||
|
from mopidy.frontends.mpd.translator import tracks_to_mpd_format
|
||||||
|
|
||||||
@handle_pattern(r'^add "(?P<uri>[^"]*)"$')
|
@handle_pattern(r'^add "(?P<uri>[^"]*)"$')
|
||||||
def add(frontend, uri):
|
def add(frontend, uri):
|
||||||
@ -18,9 +19,9 @@ def add(frontend, uri):
|
|||||||
"""
|
"""
|
||||||
if not uri:
|
if not uri:
|
||||||
return
|
return
|
||||||
for handler_prefix in frontend.backend.uri_handlers:
|
for handler_prefix in frontend.backend.uri_handlers.get():
|
||||||
if uri.startswith(handler_prefix):
|
if uri.startswith(handler_prefix):
|
||||||
track = frontend.backend.library.lookup(uri)
|
track = frontend.backend.library.lookup(uri).get()
|
||||||
if track is not None:
|
if track is not None:
|
||||||
frontend.backend.current_playlist.add(track)
|
frontend.backend.current_playlist.add(track)
|
||||||
return
|
return
|
||||||
@ -50,13 +51,14 @@ def addid(frontend, uri, songpos=None):
|
|||||||
raise MpdNoExistError(u'No such song', command=u'addid')
|
raise MpdNoExistError(u'No such song', command=u'addid')
|
||||||
if songpos is not None:
|
if songpos is not None:
|
||||||
songpos = int(songpos)
|
songpos = int(songpos)
|
||||||
track = frontend.backend.library.lookup(uri)
|
track = frontend.backend.library.lookup(uri).get()
|
||||||
if track is None:
|
if track is None:
|
||||||
raise MpdNoExistError(u'No such song', command=u'addid')
|
raise MpdNoExistError(u'No such song', command=u'addid')
|
||||||
if songpos and songpos > len(frontend.backend.current_playlist.tracks):
|
if songpos and songpos > len(
|
||||||
|
frontend.backend.current_playlist.tracks.get()):
|
||||||
raise MpdArgError(u'Bad song index', command=u'addid')
|
raise MpdArgError(u'Bad song index', command=u'addid')
|
||||||
cp_track = frontend.backend.current_playlist.add(track,
|
cp_track = frontend.backend.current_playlist.add(track,
|
||||||
at_position=songpos)
|
at_position=songpos).get()
|
||||||
return ('Id', cp_track[0])
|
return ('Id', cp_track[0])
|
||||||
|
|
||||||
@handle_pattern(r'^delete "(?P<start>\d+):(?P<end>\d+)*"$')
|
@handle_pattern(r'^delete "(?P<start>\d+):(?P<end>\d+)*"$')
|
||||||
@ -72,8 +74,8 @@ def delete_range(frontend, start, end=None):
|
|||||||
if end is not None:
|
if end is not None:
|
||||||
end = int(end)
|
end = int(end)
|
||||||
else:
|
else:
|
||||||
end = len(frontend.backend.current_playlist.tracks)
|
end = len(frontend.backend.current_playlist.tracks.get())
|
||||||
cp_tracks = frontend.backend.current_playlist.cp_tracks[start:end]
|
cp_tracks = frontend.backend.current_playlist.cp_tracks.get()[start:end]
|
||||||
if not cp_tracks:
|
if not cp_tracks:
|
||||||
raise MpdArgError(u'Bad song index', command=u'delete')
|
raise MpdArgError(u'Bad song index', command=u'delete')
|
||||||
for (cpid, _) in cp_tracks:
|
for (cpid, _) in cp_tracks:
|
||||||
@ -84,7 +86,7 @@ def delete_songpos(frontend, songpos):
|
|||||||
"""See :meth:`delete_range`"""
|
"""See :meth:`delete_range`"""
|
||||||
try:
|
try:
|
||||||
songpos = int(songpos)
|
songpos = int(songpos)
|
||||||
(cpid, _) = frontend.backend.current_playlist.cp_tracks[songpos]
|
(cpid, _) = frontend.backend.current_playlist.cp_tracks.get()[songpos]
|
||||||
frontend.backend.current_playlist.remove(cpid=cpid)
|
frontend.backend.current_playlist.remove(cpid=cpid)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise MpdArgError(u'Bad song index', command=u'delete')
|
raise MpdArgError(u'Bad song index', command=u'delete')
|
||||||
@ -100,9 +102,9 @@ def deleteid(frontend, cpid):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
cpid = int(cpid)
|
cpid = int(cpid)
|
||||||
if frontend.backend.playback.current_cpid == cpid:
|
if frontend.backend.playback.current_cpid.get() == cpid:
|
||||||
frontend.backend.playback.next()
|
frontend.backend.playback.next()
|
||||||
return frontend.backend.current_playlist.remove(cpid=cpid)
|
return frontend.backend.current_playlist.remove(cpid=cpid).get()
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise MpdNoExistError(u'No such song', command=u'deleteid')
|
raise MpdNoExistError(u'No such song', command=u'deleteid')
|
||||||
|
|
||||||
@ -128,7 +130,7 @@ def move_range(frontend, start, to, end=None):
|
|||||||
``TO`` in the playlist.
|
``TO`` in the playlist.
|
||||||
"""
|
"""
|
||||||
if end is None:
|
if end is None:
|
||||||
end = len(frontend.backend.current_playlist.tracks)
|
end = len(frontend.backend.current_playlist.tracks.get())
|
||||||
start = int(start)
|
start = int(start)
|
||||||
end = int(end)
|
end = int(end)
|
||||||
to = int(to)
|
to = int(to)
|
||||||
@ -154,8 +156,9 @@ def moveid(frontend, cpid, to):
|
|||||||
"""
|
"""
|
||||||
cpid = int(cpid)
|
cpid = int(cpid)
|
||||||
to = int(to)
|
to = int(to)
|
||||||
cp_track = frontend.backend.current_playlist.get(cpid=cpid)
|
cp_track = frontend.backend.current_playlist.get(cpid=cpid).get()
|
||||||
position = frontend.backend.current_playlist.cp_tracks.index(cp_track)
|
position = frontend.backend.current_playlist.cp_tracks.get().index(
|
||||||
|
cp_track)
|
||||||
frontend.backend.current_playlist.move(position, position + 1, to)
|
frontend.backend.current_playlist.move(position, position + 1, to)
|
||||||
|
|
||||||
@handle_pattern(r'^playlist$')
|
@handle_pattern(r'^playlist$')
|
||||||
@ -189,9 +192,9 @@ def playlistfind(frontend, tag, needle):
|
|||||||
"""
|
"""
|
||||||
if tag == 'filename':
|
if tag == 'filename':
|
||||||
try:
|
try:
|
||||||
cp_track = frontend.backend.current_playlist.get(uri=needle)
|
cp_track = frontend.backend.current_playlist.get(uri=needle).get()
|
||||||
(cpid, track) = cp_track
|
(cpid, track) = cp_track
|
||||||
position = frontend.backend.current_playlist.cp_tracks.index(
|
position = frontend.backend.current_playlist.cp_tracks.get().index(
|
||||||
cp_track)
|
cp_track)
|
||||||
return track.mpd_format(cpid=cpid, position=position)
|
return track.mpd_format(cpid=cpid, position=position)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
@ -211,14 +214,17 @@ def playlistid(frontend, cpid=None):
|
|||||||
if cpid is not None:
|
if cpid is not None:
|
||||||
try:
|
try:
|
||||||
cpid = int(cpid)
|
cpid = int(cpid)
|
||||||
cp_track = frontend.backend.current_playlist.get(cpid=cpid)
|
cp_track = frontend.backend.current_playlist.get(cpid=cpid).get()
|
||||||
position = frontend.backend.current_playlist.cp_tracks.index(
|
position = frontend.backend.current_playlist.cp_tracks.get().index(
|
||||||
cp_track)
|
cp_track)
|
||||||
return cp_track[1].mpd_format(position=position, cpid=cpid)
|
return cp_track[1].mpd_format(position=position, cpid=cpid)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise MpdNoExistError(u'No such song', command=u'playlistid')
|
raise MpdNoExistError(u'No such song', command=u'playlistid')
|
||||||
else:
|
else:
|
||||||
return frontend.backend.current_playlist.mpd_format()
|
cpids = [ct[0] for ct in
|
||||||
|
frontend.backend.current_playlist.cp_tracks.get()]
|
||||||
|
return tracks_to_mpd_format(
|
||||||
|
frontend.backend.current_playlist.tracks.get(), cpids=cpids)
|
||||||
|
|
||||||
@handle_pattern(r'^playlistinfo$')
|
@handle_pattern(r'^playlistinfo$')
|
||||||
@handle_pattern(r'^playlistinfo "(?P<songpos>-?\d+)"$')
|
@handle_pattern(r'^playlistinfo "(?P<songpos>-?\d+)"$')
|
||||||
@ -248,18 +254,27 @@ def playlistinfo(frontend, songpos=None,
|
|||||||
end = songpos + 1
|
end = songpos + 1
|
||||||
if start == -1:
|
if start == -1:
|
||||||
end = None
|
end = None
|
||||||
return frontend.backend.current_playlist.mpd_format(start, end)
|
cpids = [ct[0] for ct in
|
||||||
|
frontend.backend.current_playlist.cp_tracks.get()]
|
||||||
|
return tracks_to_mpd_format(
|
||||||
|
frontend.backend.current_playlist.tracks.get(),
|
||||||
|
start, end, cpids=cpids)
|
||||||
else:
|
else:
|
||||||
if start is None:
|
if start is None:
|
||||||
start = 0
|
start = 0
|
||||||
start = int(start)
|
start = int(start)
|
||||||
if not (0 <= start <= len(frontend.backend.current_playlist.tracks)):
|
if not (0 <= start <= len(
|
||||||
|
frontend.backend.current_playlist.tracks.get())):
|
||||||
raise MpdArgError(u'Bad song index', command=u'playlistinfo')
|
raise MpdArgError(u'Bad song index', command=u'playlistinfo')
|
||||||
if end is not None:
|
if end is not None:
|
||||||
end = int(end)
|
end = int(end)
|
||||||
if end > len(frontend.backend.current_playlist.tracks):
|
if end > len(frontend.backend.current_playlist.tracks.get()):
|
||||||
end = None
|
end = None
|
||||||
return frontend.backend.current_playlist.mpd_format(start, end)
|
cpids = [ct[0] for ct in
|
||||||
|
frontend.backend.current_playlist.cp_tracks.get()]
|
||||||
|
return tracks_to_mpd_format(
|
||||||
|
frontend.backend.current_playlist.tracks.get(),
|
||||||
|
start, end, cpids=cpids)
|
||||||
|
|
||||||
@handle_pattern(r'^playlistsearch "(?P<tag>[^"]+)" "(?P<needle>[^"]+)"$')
|
@handle_pattern(r'^playlistsearch "(?P<tag>[^"]+)" "(?P<needle>[^"]+)"$')
|
||||||
@handle_pattern(r'^playlistsearch (?P<tag>\S+) "(?P<needle>[^"]+)"$')
|
@handle_pattern(r'^playlistsearch (?P<tag>\S+) "(?P<needle>[^"]+)"$')
|
||||||
@ -298,7 +313,10 @@ def plchanges(frontend, version):
|
|||||||
"""
|
"""
|
||||||
# XXX Naive implementation that returns all tracks as changed
|
# XXX Naive implementation that returns all tracks as changed
|
||||||
if int(version) < frontend.backend.current_playlist.version:
|
if int(version) < frontend.backend.current_playlist.version:
|
||||||
return frontend.backend.current_playlist.mpd_format()
|
cpids = [ct[0] for ct in
|
||||||
|
frontend.backend.current_playlist.cp_tracks.get()]
|
||||||
|
return tracks_to_mpd_format(
|
||||||
|
frontend.backend.current_playlist.tracks.get(), cpids=cpids)
|
||||||
|
|
||||||
@handle_pattern(r'^plchangesposid "(?P<version>\d+)"$')
|
@handle_pattern(r'^plchangesposid "(?P<version>\d+)"$')
|
||||||
def plchangesposid(frontend, version):
|
def plchangesposid(frontend, version):
|
||||||
@ -315,10 +333,10 @@ def plchangesposid(frontend, version):
|
|||||||
``playlistlength`` returned by status command.
|
``playlistlength`` returned by status command.
|
||||||
"""
|
"""
|
||||||
# XXX Naive implementation that returns all tracks as changed
|
# XXX Naive implementation that returns all tracks as changed
|
||||||
if int(version) != frontend.backend.current_playlist.version:
|
if int(version) != frontend.backend.current_playlist.version.get():
|
||||||
result = []
|
result = []
|
||||||
for (position, (cpid, _)) in enumerate(
|
for (position, (cpid, _)) in enumerate(
|
||||||
frontend.backend.current_playlist.cp_tracks):
|
frontend.backend.current_playlist.cp_tracks.get()):
|
||||||
result.append((u'cpos', position))
|
result.append((u'cpos', position))
|
||||||
result.append((u'Id', cpid))
|
result.append((u'Id', cpid))
|
||||||
return result
|
return result
|
||||||
@ -351,7 +369,7 @@ def swap(frontend, songpos1, songpos2):
|
|||||||
"""
|
"""
|
||||||
songpos1 = int(songpos1)
|
songpos1 = int(songpos1)
|
||||||
songpos2 = int(songpos2)
|
songpos2 = int(songpos2)
|
||||||
tracks = frontend.backend.current_playlist.tracks
|
tracks = frontend.backend.current_playlist.tracks.get()
|
||||||
song1 = tracks[songpos1]
|
song1 = tracks[songpos1]
|
||||||
song2 = tracks[songpos2]
|
song2 = tracks[songpos2]
|
||||||
del tracks[songpos1]
|
del tracks[songpos1]
|
||||||
@ -372,8 +390,9 @@ def swapid(frontend, cpid1, cpid2):
|
|||||||
"""
|
"""
|
||||||
cpid1 = int(cpid1)
|
cpid1 = int(cpid1)
|
||||||
cpid2 = int(cpid2)
|
cpid2 = int(cpid2)
|
||||||
cp_track1 = frontend.backend.current_playlist.get(cpid=cpid1)
|
cp_track1 = frontend.backend.current_playlist.get(cpid=cpid1).get()
|
||||||
cp_track2 = frontend.backend.current_playlist.get(cpid=cpid2)
|
cp_track2 = frontend.backend.current_playlist.get(cpid=cpid2).get()
|
||||||
position1 = frontend.backend.current_playlist.cp_tracks.index(cp_track1)
|
cp_tracks = frontend.backend.current_playlist.cp_tracks.get()
|
||||||
position2 = frontend.backend.current_playlist.cp_tracks.index(cp_track2)
|
position1 = cp_tracks.index(cp_track1)
|
||||||
|
position2 = cp_tracks.index(cp_track2)
|
||||||
swap(frontend, position1, position2)
|
swap(frontend, position1, position2)
|
||||||
|
|||||||
@ -68,7 +68,7 @@ def find(frontend, mpd_query):
|
|||||||
- also uses the search type "date".
|
- also uses the search type "date".
|
||||||
"""
|
"""
|
||||||
query = _build_query(mpd_query)
|
query = _build_query(mpd_query)
|
||||||
return frontend.backend.library.find_exact(**query).mpd_format()
|
return frontend.backend.library.find_exact(**query).get().mpd_format()
|
||||||
|
|
||||||
@handle_pattern(r'^findadd '
|
@handle_pattern(r'^findadd '
|
||||||
r'(?P<query>("?([Aa]lbum|[Aa]rtist|[Ff]ilename|[Tt]itle|[Aa]ny)"? '
|
r'(?P<query>("?([Aa]lbum|[Aa]rtist|[Ff]ilename|[Tt]itle|[Aa]ny)"? '
|
||||||
@ -215,7 +215,7 @@ def _list_build_query(field, mpd_query):
|
|||||||
|
|
||||||
def _list_artist(frontend, query):
|
def _list_artist(frontend, query):
|
||||||
artists = set()
|
artists = set()
|
||||||
playlist = frontend.backend.library.find_exact(**query)
|
playlist = frontend.backend.library.find_exact(**query).get()
|
||||||
for track in playlist.tracks:
|
for track in playlist.tracks:
|
||||||
for artist in track.artists:
|
for artist in track.artists:
|
||||||
artists.add((u'Artist', artist.name))
|
artists.add((u'Artist', artist.name))
|
||||||
@ -223,7 +223,7 @@ def _list_artist(frontend, query):
|
|||||||
|
|
||||||
def _list_album(frontend, query):
|
def _list_album(frontend, query):
|
||||||
albums = set()
|
albums = set()
|
||||||
playlist = frontend.backend.library.find_exact(**query)
|
playlist = frontend.backend.library.find_exact(**query).get()
|
||||||
for track in playlist.tracks:
|
for track in playlist.tracks:
|
||||||
if track.album is not None:
|
if track.album is not None:
|
||||||
albums.add((u'Album', track.album.name))
|
albums.add((u'Album', track.album.name))
|
||||||
@ -231,7 +231,7 @@ def _list_album(frontend, query):
|
|||||||
|
|
||||||
def _list_date(frontend, query):
|
def _list_date(frontend, query):
|
||||||
dates = set()
|
dates = set()
|
||||||
playlist = frontend.backend.library.find_exact(**query)
|
playlist = frontend.backend.library.find_exact(**query).get()
|
||||||
for track in playlist.tracks:
|
for track in playlist.tracks:
|
||||||
if track.date is not None:
|
if track.date is not None:
|
||||||
dates.add((u'Date', track.date.strftime('%Y-%m-%d')))
|
dates.add((u'Date', track.date.strftime('%Y-%m-%d')))
|
||||||
@ -324,7 +324,7 @@ def search(frontend, mpd_query):
|
|||||||
- also uses the search type "date".
|
- also uses the search type "date".
|
||||||
"""
|
"""
|
||||||
query = _build_query(mpd_query)
|
query = _build_query(mpd_query)
|
||||||
return frontend.backend.library.search(**query).mpd_format()
|
return frontend.backend.library.search(**query).get().mpd_format()
|
||||||
|
|
||||||
@handle_pattern(r'^update( "(?P<uri>[^"]+)")*$')
|
@handle_pattern(r'^update( "(?P<uri>[^"]+)")*$')
|
||||||
def update(frontend, uri=None, rescan_unmodified_files=False):
|
def update(frontend, uri=None, rescan_unmodified_files=False):
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from mopidy.backends.base import PlaybackController
|
||||||
from mopidy.frontends.mpd.protocol import handle_pattern
|
from mopidy.frontends.mpd.protocol import handle_pattern
|
||||||
from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError,
|
from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError,
|
||||||
MpdNotImplemented)
|
MpdNotImplemented)
|
||||||
@ -86,7 +87,7 @@ def next_(frontend):
|
|||||||
order as the first time.
|
order as the first time.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return frontend.backend.playback.next()
|
return frontend.backend.playback.next().get()
|
||||||
|
|
||||||
@handle_pattern(r'^pause$')
|
@handle_pattern(r'^pause$')
|
||||||
@handle_pattern(r'^pause "(?P<state>[01])"$')
|
@handle_pattern(r'^pause "(?P<state>[01])"$')
|
||||||
@ -103,11 +104,11 @@ def pause(frontend, state=None):
|
|||||||
- Calls ``pause`` without any arguments to toogle pause.
|
- Calls ``pause`` without any arguments to toogle pause.
|
||||||
"""
|
"""
|
||||||
if state is None:
|
if state is None:
|
||||||
if (frontend.backend.playback.state ==
|
if (frontend.backend.playback.state.get() ==
|
||||||
frontend.backend.playback.PLAYING):
|
PlaybackController.PLAYING):
|
||||||
frontend.backend.playback.pause()
|
frontend.backend.playback.pause()
|
||||||
elif (frontend.backend.playback.state ==
|
elif (frontend.backend.playback.state.get() ==
|
||||||
frontend.backend.playback.PAUSED):
|
PlaybackController.PAUSED):
|
||||||
frontend.backend.playback.resume()
|
frontend.backend.playback.resume()
|
||||||
elif int(state):
|
elif int(state):
|
||||||
frontend.backend.playback.pause()
|
frontend.backend.playback.pause()
|
||||||
@ -120,7 +121,7 @@ def play(frontend):
|
|||||||
The original MPD server resumes from the paused state on ``play``
|
The original MPD server resumes from the paused state on ``play``
|
||||||
without arguments.
|
without arguments.
|
||||||
"""
|
"""
|
||||||
return frontend.backend.playback.play()
|
return frontend.backend.playback.play().get()
|
||||||
|
|
||||||
@handle_pattern(r'^playid "(?P<cpid>\d+)"$')
|
@handle_pattern(r'^playid "(?P<cpid>\d+)"$')
|
||||||
@handle_pattern(r'^playid "(?P<cpid>-1)"$')
|
@handle_pattern(r'^playid "(?P<cpid>-1)"$')
|
||||||
@ -145,8 +146,8 @@ def playid(frontend, cpid):
|
|||||||
if cpid == -1:
|
if cpid == -1:
|
||||||
return _play_minus_one(frontend)
|
return _play_minus_one(frontend)
|
||||||
try:
|
try:
|
||||||
cp_track = frontend.backend.current_playlist.get(cpid=cpid)
|
cp_track = frontend.backend.current_playlist.get(cpid=cpid).get()
|
||||||
return frontend.backend.playback.play(cp_track)
|
return frontend.backend.playback.play(cp_track).get()
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise MpdNoExistError(u'No such song', command=u'playid')
|
raise MpdNoExistError(u'No such song', command=u'playid')
|
||||||
|
|
||||||
@ -177,22 +178,22 @@ def playpos(frontend, songpos):
|
|||||||
if songpos == -1:
|
if songpos == -1:
|
||||||
return _play_minus_one(frontend)
|
return _play_minus_one(frontend)
|
||||||
try:
|
try:
|
||||||
cp_track = frontend.backend.current_playlist.cp_tracks[songpos]
|
cp_track = frontend.backend.current_playlist.cp_tracks.get()[songpos]
|
||||||
return frontend.backend.playback.play(cp_track)
|
return frontend.backend.playback.play(cp_track).get()
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise MpdArgError(u'Bad song index', command=u'play')
|
raise MpdArgError(u'Bad song index', command=u'play')
|
||||||
|
|
||||||
def _play_minus_one(frontend):
|
def _play_minus_one(frontend):
|
||||||
if (frontend.backend.playback.state == frontend.backend.playback.PLAYING):
|
if (frontend.backend.playback.state.get() == PlaybackController.PLAYING):
|
||||||
return # Nothing to do
|
return # Nothing to do
|
||||||
elif (frontend.backend.playback.state == frontend.backend.playback.PAUSED):
|
elif (frontend.backend.playback.state.get() == PlaybackController.PAUSED):
|
||||||
return frontend.backend.playback.resume()
|
return frontend.backend.playback.resume().get()
|
||||||
elif frontend.backend.playback.current_cp_track is not None:
|
elif frontend.backend.playback.current_cp_track.get() is not None:
|
||||||
cp_track = frontend.backend.playback.current_cp_track
|
cp_track = frontend.backend.playback.current_cp_track.get()
|
||||||
return frontend.backend.playback.play(cp_track)
|
return frontend.backend.playback.play(cp_track).get()
|
||||||
elif frontend.backend.current_playlist.cp_tracks:
|
elif frontend.backend.current_playlist.cp_tracks.get():
|
||||||
cp_track = frontend.backend.current_playlist.cp_tracks[0]
|
cp_track = frontend.backend.current_playlist.cp_tracks.get()[0]
|
||||||
return frontend.backend.playback.play(cp_track)
|
return frontend.backend.playback.play(cp_track).get()
|
||||||
else:
|
else:
|
||||||
return # Fail silently
|
return # Fail silently
|
||||||
|
|
||||||
@ -240,7 +241,7 @@ def previous(frontend):
|
|||||||
``previous`` should do a seek to time position 0.
|
``previous`` should do a seek to time position 0.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return frontend.backend.playback.previous()
|
return frontend.backend.playback.previous().get()
|
||||||
|
|
||||||
@handle_pattern(r'^random (?P<state>[01])$')
|
@handle_pattern(r'^random (?P<state>[01])$')
|
||||||
@handle_pattern(r'^random "(?P<state>[01])"$')
|
@handle_pattern(r'^random "(?P<state>[01])"$')
|
||||||
@ -351,7 +352,7 @@ def setvol(frontend, volume):
|
|||||||
volume = 0
|
volume = 0
|
||||||
if volume > 100:
|
if volume > 100:
|
||||||
volume = 100
|
volume = 100
|
||||||
frontend.backend.mixer.volume = volume
|
frontend.mixer.volume = volume
|
||||||
|
|
||||||
@handle_pattern(r'^single (?P<state>[01])$')
|
@handle_pattern(r'^single (?P<state>[01])$')
|
||||||
@handle_pattern(r'^single "(?P<state>[01])"$')
|
@handle_pattern(r'^single "(?P<state>[01])"$')
|
||||||
|
|||||||
@ -81,4 +81,4 @@ def urlhandlers(frontend):
|
|||||||
|
|
||||||
Gets a list of available URL handlers.
|
Gets a list of available URL handlers.
|
||||||
"""
|
"""
|
||||||
return [(u'handler', uri) for uri in frontend.backend.uri_handlers]
|
return [(u'handler', uri) for uri in frontend.backend.uri_handlers.get()]
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from mopidy.backends.base import PlaybackController
|
||||||
from mopidy.frontends.mpd.protocol import handle_pattern
|
from mopidy.frontends.mpd.protocol import handle_pattern
|
||||||
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
|
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
|
||||||
|
|
||||||
@ -23,10 +24,11 @@ def currentsong(frontend):
|
|||||||
Displays the song info of the current song (same song that is
|
Displays the song info of the current song (same song that is
|
||||||
identified in status).
|
identified in status).
|
||||||
"""
|
"""
|
||||||
if frontend.backend.playback.current_track is not None:
|
current_cp_track = frontend.backend.playback.current_cp_track.get()
|
||||||
return frontend.backend.playback.current_track.mpd_format(
|
if current_cp_track is not None:
|
||||||
position=frontend.backend.playback.current_playlist_position,
|
return current_cp_track[1].mpd_format(
|
||||||
cpid=frontend.backend.playback.current_cpid)
|
position=frontend.backend.playback.current_playlist_position.get(),
|
||||||
|
cpid=current_cp_track[0])
|
||||||
|
|
||||||
@handle_pattern(r'^idle$')
|
@handle_pattern(r'^idle$')
|
||||||
@handle_pattern(r'^idle (?P<subsystems>.+)$')
|
@handle_pattern(r'^idle (?P<subsystems>.+)$')
|
||||||
@ -90,8 +92,7 @@ def stats(frontend):
|
|||||||
'artists': 0, # TODO
|
'artists': 0, # TODO
|
||||||
'albums': 0, # TODO
|
'albums': 0, # TODO
|
||||||
'songs': 0, # TODO
|
'songs': 0, # TODO
|
||||||
# TODO Does not work after multiprocessing branch merge
|
'uptime': 0, # TODO
|
||||||
'uptime': 0, # frontend.session.stats_uptime(),
|
|
||||||
'db_playtime': 0, # TODO
|
'db_playtime': 0, # TODO
|
||||||
'db_update': 0, # TODO
|
'db_update': 0, # TODO
|
||||||
'playtime': 0, # TODO
|
'playtime': 0, # TODO
|
||||||
@ -140,56 +141,59 @@ def status(frontend):
|
|||||||
('xfade', _status_xfade(frontend)),
|
('xfade', _status_xfade(frontend)),
|
||||||
('state', _status_state(frontend)),
|
('state', _status_state(frontend)),
|
||||||
]
|
]
|
||||||
if frontend.backend.playback.current_track is not None:
|
if frontend.backend.playback.current_track.get() is not None:
|
||||||
result.append(('song', _status_songpos(frontend)))
|
result.append(('song', _status_songpos(frontend)))
|
||||||
result.append(('songid', _status_songid(frontend)))
|
result.append(('songid', _status_songid(frontend)))
|
||||||
if frontend.backend.playback.state in (frontend.backend.playback.PLAYING,
|
if frontend.backend.playback.state.get() in (PlaybackController.PLAYING,
|
||||||
frontend.backend.playback.PAUSED):
|
PlaybackController.PAUSED):
|
||||||
result.append(('time', _status_time(frontend)))
|
result.append(('time', _status_time(frontend)))
|
||||||
result.append(('elapsed', _status_time_elapsed(frontend)))
|
result.append(('elapsed', _status_time_elapsed(frontend)))
|
||||||
result.append(('bitrate', _status_bitrate(frontend)))
|
result.append(('bitrate', _status_bitrate(frontend)))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _status_bitrate(frontend):
|
def _status_bitrate(frontend):
|
||||||
if frontend.backend.playback.current_track is not None:
|
current_track = frontend.backend.playback.current_track.get()
|
||||||
return frontend.backend.playback.current_track.bitrate
|
if current_track is not None:
|
||||||
|
return current_track.bitrate
|
||||||
|
|
||||||
def _status_consume(frontend):
|
def _status_consume(frontend):
|
||||||
if frontend.backend.playback.consume:
|
if frontend.backend.playback.consume.get():
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def _status_playlist_length(frontend):
|
def _status_playlist_length(frontend):
|
||||||
return len(frontend.backend.current_playlist.tracks)
|
return len(frontend.backend.current_playlist.tracks.get())
|
||||||
|
|
||||||
def _status_playlist_version(frontend):
|
def _status_playlist_version(frontend):
|
||||||
return frontend.backend.current_playlist.version
|
return frontend.backend.current_playlist.version.get()
|
||||||
|
|
||||||
def _status_random(frontend):
|
def _status_random(frontend):
|
||||||
return int(frontend.backend.playback.random)
|
return int(frontend.backend.playback.random.get())
|
||||||
|
|
||||||
def _status_repeat(frontend):
|
def _status_repeat(frontend):
|
||||||
return int(frontend.backend.playback.repeat)
|
return int(frontend.backend.playback.repeat.get())
|
||||||
|
|
||||||
def _status_single(frontend):
|
def _status_single(frontend):
|
||||||
return int(frontend.backend.playback.single)
|
return int(frontend.backend.playback.single.get())
|
||||||
|
|
||||||
def _status_songid(frontend):
|
def _status_songid(frontend):
|
||||||
if frontend.backend.playback.current_cpid is not None:
|
current_cpid = frontend.backend.playback.current_cpid.get()
|
||||||
return frontend.backend.playback.current_cpid
|
if current_cpid is not None:
|
||||||
|
return current_cpid
|
||||||
else:
|
else:
|
||||||
return _status_songpos(frontend)
|
return _status_songpos(frontend)
|
||||||
|
|
||||||
def _status_songpos(frontend):
|
def _status_songpos(frontend):
|
||||||
return frontend.backend.playback.current_playlist_position
|
return frontend.backend.playback.current_playlist_position.get()
|
||||||
|
|
||||||
def _status_state(frontend):
|
def _status_state(frontend):
|
||||||
if frontend.backend.playback.state == frontend.backend.playback.PLAYING:
|
state = frontend.backend.playback.state.get()
|
||||||
|
if state == PlaybackController.PLAYING:
|
||||||
return u'play'
|
return u'play'
|
||||||
elif frontend.backend.playback.state == frontend.backend.playback.STOPPED:
|
elif state == PlaybackController.STOPPED:
|
||||||
return u'stop'
|
return u'stop'
|
||||||
elif frontend.backend.playback.state == frontend.backend.playback.PAUSED:
|
elif state == PlaybackController.PAUSED:
|
||||||
return u'pause'
|
return u'pause'
|
||||||
|
|
||||||
def _status_time(frontend):
|
def _status_time(frontend):
|
||||||
@ -197,19 +201,21 @@ def _status_time(frontend):
|
|||||||
_status_time_total(frontend) // 1000)
|
_status_time_total(frontend) // 1000)
|
||||||
|
|
||||||
def _status_time_elapsed(frontend):
|
def _status_time_elapsed(frontend):
|
||||||
return frontend.backend.playback.time_position
|
return frontend.backend.playback.time_position.get()
|
||||||
|
|
||||||
def _status_time_total(frontend):
|
def _status_time_total(frontend):
|
||||||
if frontend.backend.playback.current_track is None:
|
current_track = frontend.backend.playback.current_track.get()
|
||||||
|
if current_track is None:
|
||||||
return 0
|
return 0
|
||||||
elif frontend.backend.playback.current_track.length is None:
|
elif current_track.length is None:
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
return frontend.backend.playback.current_track.length
|
return current_track.length
|
||||||
|
|
||||||
def _status_volume(frontend):
|
def _status_volume(frontend):
|
||||||
if frontend.backend.mixer.volume is not None:
|
volume = frontend.mixer.volume.get()
|
||||||
return frontend.backend.mixer.volume
|
if volume is not None:
|
||||||
|
return volume
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@ -19,8 +19,8 @@ def listplaylist(frontend, name):
|
|||||||
file: relative/path/to/file3.mp3
|
file: relative/path/to/file3.mp3
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return ['file: %s' % t.uri
|
playlist = frontend.backend.stored_playlists.get(name=name).get()
|
||||||
for t in frontend.backend.stored_playlists.get(name=name).tracks]
|
return ['file: %s' % t.uri for t in playlist.tracks]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise MpdNoExistError(u'No such playlist', command=u'listplaylist')
|
raise MpdNoExistError(u'No such playlist', command=u'listplaylist')
|
||||||
|
|
||||||
@ -39,7 +39,8 @@ def listplaylistinfo(frontend, name):
|
|||||||
Album, Artist, Track
|
Album, Artist, Track
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return frontend.backend.stored_playlists.get(name=name).mpd_format()
|
playlist = frontend.backend.stored_playlists.get(name=name).get()
|
||||||
|
return playlist.mpd_format()
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise MpdNoExistError(
|
raise MpdNoExistError(
|
||||||
u'No such playlist', command=u'listplaylistinfo')
|
u'No such playlist', command=u'listplaylistinfo')
|
||||||
@ -66,7 +67,7 @@ def listplaylists(frontend):
|
|||||||
Last-Modified: 2010-02-06T02:11:08Z
|
Last-Modified: 2010-02-06T02:11:08Z
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
for playlist in frontend.backend.stored_playlists.playlists:
|
for playlist in frontend.backend.stored_playlists.playlists.get():
|
||||||
result.append((u'playlist', playlist.name))
|
result.append((u'playlist', playlist.name))
|
||||||
last_modified = (playlist.last_modified or
|
last_modified = (playlist.last_modified or
|
||||||
dt.datetime.now()).isoformat()
|
dt.datetime.now()).isoformat()
|
||||||
@ -92,7 +93,7 @@ def load(frontend, name):
|
|||||||
- ``load`` appends the given playlist to the current playlist.
|
- ``load`` appends the given playlist to the current playlist.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
playlist = frontend.backend.stored_playlists.get(name=name)
|
playlist = frontend.backend.stored_playlists.get(name=name).get()
|
||||||
frontend.backend.current_playlist.append(playlist.tracks)
|
frontend.backend.current_playlist.append(playlist.tracks)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise MpdNoExistError(u'No such playlist', command=u'load')
|
raise MpdNoExistError(u'No such playlist', command=u'load')
|
||||||
|
|||||||
@ -15,9 +15,8 @@ class MpdServer(asyncore.dispatcher):
|
|||||||
for each client connection.
|
for each client connection.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, core_queue):
|
def __init__(self):
|
||||||
asyncore.dispatcher.__init__(self)
|
asyncore.dispatcher.__init__(self)
|
||||||
self.core_queue = core_queue
|
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start MPD server."""
|
"""Start MPD server."""
|
||||||
@ -47,8 +46,7 @@ class MpdServer(asyncore.dispatcher):
|
|||||||
(client_socket, client_socket_address) = self.accept()
|
(client_socket, client_socket_address) = self.accept()
|
||||||
logger.info(u'MPD client connection from [%s]:%s',
|
logger.info(u'MPD client connection from [%s]:%s',
|
||||||
client_socket_address[0], client_socket_address[1])
|
client_socket_address[0], client_socket_address[1])
|
||||||
MpdSession(self, client_socket, client_socket_address,
|
MpdSession(self, client_socket, client_socket_address).start()
|
||||||
self.core_queue).start()
|
|
||||||
|
|
||||||
def handle_close(self):
|
def handle_close(self):
|
||||||
"""Handle end of client connection."""
|
"""Handle end of client connection."""
|
||||||
|
|||||||
@ -1,30 +1,28 @@
|
|||||||
import asynchat
|
import asynchat
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
|
||||||
|
|
||||||
from mopidy import settings
|
from mopidy import settings
|
||||||
|
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
|
||||||
from mopidy.frontends.mpd.protocol import ENCODING, LINE_TERMINATOR, VERSION
|
from mopidy.frontends.mpd.protocol import ENCODING, LINE_TERMINATOR, VERSION
|
||||||
from mopidy.utils.log import indent
|
from mopidy.utils.log import indent
|
||||||
from mopidy.utils.process import pickle_connection
|
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.frontends.mpd.session')
|
logger = logging.getLogger('mopidy.frontends.mpd.session')
|
||||||
|
|
||||||
class MpdSession(asynchat.async_chat):
|
class MpdSession(asynchat.async_chat):
|
||||||
"""
|
"""
|
||||||
The MPD client session. Keeps track of a single client and passes its
|
The MPD client session. Keeps track of a single client session. Any
|
||||||
MPD requests to the dispatcher.
|
requests from the client is passed on to the MPD request dispatcher.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, server, client_socket, client_socket_address,
|
def __init__(self, server, client_socket, client_socket_address):
|
||||||
core_queue):
|
|
||||||
asynchat.async_chat.__init__(self, sock=client_socket)
|
asynchat.async_chat.__init__(self, sock=client_socket)
|
||||||
self.server = server
|
self.server = server
|
||||||
self.client_address = client_socket_address[0]
|
self.client_address = client_socket_address[0]
|
||||||
self.client_port = client_socket_address[1]
|
self.client_port = client_socket_address[1]
|
||||||
self.core_queue = core_queue
|
|
||||||
self.input_buffer = []
|
self.input_buffer = []
|
||||||
self.authenticated = False
|
self.authenticated = False
|
||||||
self.set_terminator(LINE_TERMINATOR.encode(ENCODING))
|
self.set_terminator(LINE_TERMINATOR.encode(ENCODING))
|
||||||
|
self.dispatcher = MpdDispatcher()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start a new client session."""
|
"""Start a new client session."""
|
||||||
@ -53,15 +51,7 @@ class MpdSession(asynchat.async_chat):
|
|||||||
if response is not None:
|
if response is not None:
|
||||||
self.send_response(response)
|
self.send_response(response)
|
||||||
return
|
return
|
||||||
my_end, other_end = multiprocessing.Pipe()
|
response = self.dispatcher.handle_request(request)
|
||||||
self.core_queue.put({
|
|
||||||
'to': 'frontend',
|
|
||||||
'command': 'mpd_request',
|
|
||||||
'request': request,
|
|
||||||
'reply_to': pickle_connection(other_end),
|
|
||||||
})
|
|
||||||
my_end.poll(None)
|
|
||||||
response = my_end.recv()
|
|
||||||
if response is not None:
|
if response is not None:
|
||||||
self.handle_response(response)
|
self.handle_response(response)
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
import asyncore
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from mopidy.frontends.mpd.server import MpdServer
|
|
||||||
from mopidy.utils.process import BaseThread
|
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.frontends.mpd.thread')
|
|
||||||
|
|
||||||
class MpdThread(BaseThread):
|
|
||||||
def __init__(self, core_queue):
|
|
||||||
super(MpdThread, self).__init__(core_queue)
|
|
||||||
self.name = u'MpdThread'
|
|
||||||
|
|
||||||
def run_inside_try(self):
|
|
||||||
logger.debug(u'Starting MPD server thread')
|
|
||||||
server = MpdServer(self.core_queue)
|
|
||||||
server.start()
|
|
||||||
asyncore.loop()
|
|
||||||
@ -1,12 +1,14 @@
|
|||||||
import alsaaudio
|
import alsaaudio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from pykka.actor import ThreadingActor
|
||||||
|
|
||||||
from mopidy import settings
|
from mopidy import settings
|
||||||
from mopidy.mixers.base import BaseMixer
|
from mopidy.mixers.base import BaseMixer
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.mixers.alsa')
|
logger = logging.getLogger('mopidy.mixers.alsa')
|
||||||
|
|
||||||
class AlsaMixer(BaseMixer):
|
class AlsaMixer(ThreadingActor, BaseMixer):
|
||||||
"""
|
"""
|
||||||
Mixer which uses the Advanced Linux Sound Architecture (ALSA) to control
|
Mixer which uses the Advanced Linux Sound Architecture (ALSA) to control
|
||||||
volume.
|
volume.
|
||||||
@ -20,8 +22,10 @@ class AlsaMixer(BaseMixer):
|
|||||||
- :attr:`mopidy.settings.MIXER_ALSA_CONTROL`
|
- :attr:`mopidy.settings.MIXER_ALSA_CONTROL`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self):
|
||||||
super(AlsaMixer, self).__init__(*args, **kwargs)
|
self._mixer = None
|
||||||
|
|
||||||
|
def on_start(self):
|
||||||
self._mixer = alsaaudio.Mixer(self._get_mixer_control())
|
self._mixer = alsaaudio.Mixer(self._get_mixer_control())
|
||||||
assert self._mixer is not None
|
assert self._mixer is not None
|
||||||
|
|
||||||
|
|||||||
@ -2,17 +2,12 @@ from mopidy import settings
|
|||||||
|
|
||||||
class BaseMixer(object):
|
class BaseMixer(object):
|
||||||
"""
|
"""
|
||||||
:param backend: a backend instance
|
|
||||||
:type backend: :class:`mopidy.backends.base.Backend`
|
|
||||||
|
|
||||||
**Settings:**
|
**Settings:**
|
||||||
|
|
||||||
- :attr:`mopidy.settings.MIXER_MAX_VOLUME`
|
- :attr:`mopidy.settings.MIXER_MAX_VOLUME`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, backend, *args, **kwargs):
|
amplification_factor = settings.MIXER_MAX_VOLUME / 100.0
|
||||||
self.backend = backend
|
|
||||||
self.amplification_factor = settings.MIXER_MAX_VOLUME / 100.0
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def volume(self):
|
def volume(self):
|
||||||
@ -35,9 +30,6 @@ class BaseMixer(object):
|
|||||||
volume = 100
|
volume = 100
|
||||||
self._set_volume(volume)
|
self._set_volume(volume)
|
||||||
|
|
||||||
def destroy(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _get_volume(self):
|
def _get_volume(self):
|
||||||
"""
|
"""
|
||||||
Return volume as integer in range [0, 100]. :class:`None` if unknown.
|
Return volume as integer in range [0, 100]. :class:`None` if unknown.
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import logging
|
import logging
|
||||||
from threading import Lock
|
|
||||||
|
from pykka.actor import ThreadingActor
|
||||||
|
|
||||||
from mopidy import settings
|
from mopidy import settings
|
||||||
from mopidy.mixers.base import BaseMixer
|
from mopidy.mixers.base import BaseMixer
|
||||||
|
|
||||||
logger = logging.getLogger(u'mopidy.mixers.denon')
|
logger = logging.getLogger(u'mopidy.mixers.denon')
|
||||||
|
|
||||||
class DenonMixer(BaseMixer):
|
class DenonMixer(ThreadingActor, BaseMixer):
|
||||||
"""
|
"""
|
||||||
Mixer for controlling Denon amplifiers and receivers using the RS-232
|
Mixer for controlling Denon amplifiers and receivers using the RS-232
|
||||||
protocol.
|
protocol.
|
||||||
@ -25,27 +26,19 @@ class DenonMixer(BaseMixer):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""
|
self._device = kwargs.get('device', None)
|
||||||
Connects using the serial specifications from Denon's RS-232 Protocol
|
|
||||||
specification: 9600bps 8N1.
|
|
||||||
"""
|
|
||||||
super(DenonMixer, self).__init__(*args, **kwargs)
|
|
||||||
device = kwargs.get('device', None)
|
|
||||||
if device:
|
|
||||||
self._device = device
|
|
||||||
else:
|
|
||||||
from serial import Serial
|
|
||||||
self._device = Serial(port=settings.MIXER_EXT_PORT, timeout=0.2)
|
|
||||||
self._levels = ['99'] + ["%(#)02d" % {'#': v} for v in range(0, 99)]
|
self._levels = ['99'] + ["%(#)02d" % {'#': v} for v in range(0, 99)]
|
||||||
self._volume = 0
|
self._volume = 0
|
||||||
self._lock = Lock()
|
|
||||||
|
def on_start(self):
|
||||||
|
if self._device is None:
|
||||||
|
from serial import Serial
|
||||||
|
self._device = Serial(port=settings.MIXER_EXT_PORT, timeout=0.2)
|
||||||
|
|
||||||
def _get_volume(self):
|
def _get_volume(self):
|
||||||
self._lock.acquire()
|
self._ensure_open_device()
|
||||||
self.ensure_open_device()
|
|
||||||
self._device.write('MV?\r')
|
self._device.write('MV?\r')
|
||||||
vol = str(self._device.readline()[2:4])
|
vol = str(self._device.readline()[2:4])
|
||||||
self._lock.release()
|
|
||||||
logger.debug(u'_get_volume() = %s' % vol)
|
logger.debug(u'_get_volume() = %s' % vol)
|
||||||
return self._levels.index(vol)
|
return self._levels.index(vol)
|
||||||
|
|
||||||
@ -53,14 +46,12 @@ class DenonMixer(BaseMixer):
|
|||||||
# Clamp according to Denon-spec
|
# Clamp according to Denon-spec
|
||||||
if volume > 99:
|
if volume > 99:
|
||||||
volume = 99
|
volume = 99
|
||||||
self._lock.acquire()
|
self._ensure_open_device()
|
||||||
self.ensure_open_device()
|
|
||||||
self._device.write('MV%s\r'% self._levels[volume])
|
self._device.write('MV%s\r'% self._levels[volume])
|
||||||
vol = self._device.readline()[2:4]
|
vol = self._device.readline()[2:4]
|
||||||
self._lock.release()
|
|
||||||
self._volume = self._levels.index(vol)
|
self._volume = self._levels.index(vol)
|
||||||
|
|
||||||
def ensure_open_device(self):
|
def _ensure_open_device(self):
|
||||||
if not self._device.isOpen():
|
if not self._device.isOpen():
|
||||||
logger.debug(u'(re)connecting to Denon device')
|
logger.debug(u'(re)connecting to Denon device')
|
||||||
self._device.open()
|
self._device.open()
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
|
from pykka.actor import ThreadingActor
|
||||||
|
|
||||||
from mopidy.mixers.base import BaseMixer
|
from mopidy.mixers.base import BaseMixer
|
||||||
|
|
||||||
class DummyMixer(BaseMixer):
|
class DummyMixer(ThreadingActor, BaseMixer):
|
||||||
"""Mixer which just stores and reports the chosen volume."""
|
"""Mixer which just stores and reports the chosen volume."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self):
|
||||||
super(DummyMixer, self).__init__(*args, **kwargs)
|
|
||||||
self._volume = None
|
self._volume = None
|
||||||
|
|
||||||
def _get_volume(self):
|
def _get_volume(self):
|
||||||
|
|||||||
@ -1,13 +1,22 @@
|
|||||||
from mopidy.mixers.base import BaseMixer
|
from pykka.actor import ThreadingActor
|
||||||
|
from pykka.registry import ActorRegistry
|
||||||
|
|
||||||
class GStreamerSoftwareMixer(BaseMixer):
|
from mopidy.mixers.base import BaseMixer
|
||||||
|
from mopidy.outputs.base import BaseOutput
|
||||||
|
|
||||||
|
class GStreamerSoftwareMixer(ThreadingActor, BaseMixer):
|
||||||
"""Mixer which uses GStreamer to control volume in software."""
|
"""Mixer which uses GStreamer to control volume in software."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self):
|
||||||
super(GStreamerSoftwareMixer, self).__init__(*args, **kwargs)
|
self.output = None
|
||||||
|
|
||||||
|
def on_start(self):
|
||||||
|
output_refs = ActorRegistry.get_by_class(BaseOutput)
|
||||||
|
assert len(output_refs) == 1, 'Expected exactly one running output.'
|
||||||
|
self.output = output_refs[0].proxy()
|
||||||
|
|
||||||
def _get_volume(self):
|
def _get_volume(self):
|
||||||
return self.backend.output.get_volume()
|
return self.output.get_volume().get()
|
||||||
|
|
||||||
def _set_volume(self, volume):
|
def _set_volume(self, volume):
|
||||||
self.backend.output.set_volume(volume)
|
self.output.set_volume(volume).get()
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import logging
|
import logging
|
||||||
from serial import Serial
|
import serial
|
||||||
from multiprocessing import Pipe
|
|
||||||
|
from pykka.actor import ThreadingActor
|
||||||
|
|
||||||
from mopidy import settings
|
from mopidy import settings
|
||||||
from mopidy.mixers.base import BaseMixer
|
from mopidy.mixers.base import BaseMixer
|
||||||
from mopidy.utils.process import BaseThread
|
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.mixers.nad')
|
logger = logging.getLogger('mopidy.mixers.nad')
|
||||||
|
|
||||||
class NadMixer(BaseMixer):
|
class NadMixer(ThreadingActor, BaseMixer):
|
||||||
"""
|
"""
|
||||||
Mixer for controlling NAD amplifiers and receivers using the NAD RS-232
|
Mixer for controlling NAD amplifiers and receivers using the NAD RS-232
|
||||||
protocol.
|
protocol.
|
||||||
@ -36,21 +36,19 @@ class NadMixer(BaseMixer):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self):
|
||||||
super(NadMixer, self).__init__(*args, **kwargs)
|
self._volume_cache = None
|
||||||
self._volume = None
|
self._nad_talker = NadTalker.start().proxy()
|
||||||
self._pipe, other_end = Pipe()
|
|
||||||
NadTalker(self.backend.core_queue, pipe=other_end).start()
|
|
||||||
|
|
||||||
def _get_volume(self):
|
def _get_volume(self):
|
||||||
return self._volume
|
return self._volume_cache
|
||||||
|
|
||||||
def _set_volume(self, volume):
|
def _set_volume(self, volume):
|
||||||
self._volume = volume
|
self._volume_cache = volume
|
||||||
self._pipe.send({'command': 'set_volume', 'volume': volume})
|
self._nad_talker.set_volume(volume)
|
||||||
|
|
||||||
|
|
||||||
class NadTalker(BaseThread):
|
class NadTalker(ThreadingActor):
|
||||||
"""
|
"""
|
||||||
Independent process which does the communication with the NAD device.
|
Independent process which does the communication with the NAD device.
|
||||||
|
|
||||||
@ -72,29 +70,20 @@ class NadTalker(BaseThread):
|
|||||||
# Volume in range 0..VOLUME_LEVELS. :class:`None` before calibration.
|
# Volume in range 0..VOLUME_LEVELS. :class:`None` before calibration.
|
||||||
_nad_volume = None
|
_nad_volume = None
|
||||||
|
|
||||||
def __init__(self, core_queue, pipe=None):
|
def __init__(self):
|
||||||
super(NadTalker, self).__init__(core_queue)
|
|
||||||
self.name = u'NadTalker'
|
|
||||||
self.pipe = pipe
|
|
||||||
self._device = None
|
self._device = None
|
||||||
|
|
||||||
def run_inside_try(self):
|
def on_start(self):
|
||||||
self._open_connection()
|
self._open_connection()
|
||||||
self._set_device_to_known_state()
|
self._set_device_to_known_state()
|
||||||
while self.pipe.poll(None):
|
|
||||||
message = self.pipe.recv()
|
|
||||||
if message['command'] == 'set_volume':
|
|
||||||
self._set_volume(message['volume'])
|
|
||||||
elif message['command'] == 'reset_device':
|
|
||||||
self._set_device_to_known_state()
|
|
||||||
|
|
||||||
def _open_connection(self):
|
def _open_connection(self):
|
||||||
# Opens serial connection to the device.
|
# Opens serial connection to the device.
|
||||||
# Communication settings: 115200 bps 8N1
|
# Communication settings: 115200 bps 8N1
|
||||||
logger.info(u'Connecting to serial device "%s"',
|
logger.info(u'Connecting to serial device "%s"',
|
||||||
settings.MIXER_EXT_PORT)
|
settings.MIXER_EXT_PORT)
|
||||||
self._device = Serial(port=settings.MIXER_EXT_PORT, baudrate=115200,
|
self._device = serial.Serial(port=settings.MIXER_EXT_PORT,
|
||||||
timeout=self.TIMEOUT)
|
baudrate=115200, timeout=self.TIMEOUT)
|
||||||
self._get_device_model()
|
self._get_device_model()
|
||||||
|
|
||||||
def _set_device_to_known_state(self):
|
def _set_device_to_known_state(self):
|
||||||
@ -164,7 +153,7 @@ class NadTalker(BaseThread):
|
|||||||
self._nad_volume = 0
|
self._nad_volume = 0
|
||||||
logger.info(u'Done calibrating NAD amplifier')
|
logger.info(u'Done calibrating NAD amplifier')
|
||||||
|
|
||||||
def _set_volume(self, volume):
|
def set_volume(self, volume):
|
||||||
# Increase or decrease the amplifier volume until it matches the given
|
# Increase or decrease the amplifier volume until it matches the given
|
||||||
# target volume.
|
# target volume.
|
||||||
logger.debug(u'Setting volume to %d' % volume)
|
logger.debug(u'Setting volume to %d' % volume)
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from pykka.actor import ThreadingActor
|
||||||
|
|
||||||
from mopidy.mixers.base import BaseMixer
|
from mopidy.mixers.base import BaseMixer
|
||||||
|
|
||||||
class OsaMixer(BaseMixer):
|
class OsaMixer(ThreadingActor, BaseMixer):
|
||||||
"""
|
"""
|
||||||
Mixer which uses ``osascript`` on OS X to control volume.
|
Mixer which uses ``osascript`` on OS X to control volume.
|
||||||
|
|
||||||
@ -14,7 +16,6 @@ class OsaMixer(BaseMixer):
|
|||||||
**Settings:**
|
**Settings:**
|
||||||
|
|
||||||
- None
|
- None
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CACHE_TTL = 30
|
CACHE_TTL = 30
|
||||||
|
|||||||
@ -3,33 +3,6 @@ class BaseOutput(object):
|
|||||||
Base class for audio outputs.
|
Base class for audio outputs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, core_queue):
|
|
||||||
self.core_queue = core_queue
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""
|
|
||||||
Start the output.
|
|
||||||
|
|
||||||
*MAY be implemented by subclasses.*
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def destroy(self):
|
|
||||||
"""
|
|
||||||
Destroy the output.
|
|
||||||
|
|
||||||
*MAY be implemented by subclasses.*
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def process_message(self, message):
|
|
||||||
"""
|
|
||||||
Process messages with the output as destination.
|
|
||||||
|
|
||||||
*MUST be implemented by subclass.*
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def play_uri(self, uri):
|
def play_uri(self, uri):
|
||||||
"""
|
"""
|
||||||
Play URI.
|
Play URI.
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
|
from pykka.actor import ThreadingActor
|
||||||
|
|
||||||
from mopidy.outputs.base import BaseOutput
|
from mopidy.outputs.base import BaseOutput
|
||||||
|
|
||||||
class DummyOutput(BaseOutput):
|
class DummyOutput(ThreadingActor, BaseOutput):
|
||||||
"""
|
"""
|
||||||
Audio output used for testing.
|
Audio output used for testing.
|
||||||
"""
|
"""
|
||||||
@ -8,15 +10,6 @@ class DummyOutput(BaseOutput):
|
|||||||
# pylint: disable = R0902
|
# pylint: disable = R0902
|
||||||
# Too many instance attributes (9/7)
|
# Too many instance attributes (9/7)
|
||||||
|
|
||||||
#: For testing. :class:`True` if :meth:`start` has been called.
|
|
||||||
start_called = False
|
|
||||||
|
|
||||||
#: For testing. :class:`True` if :meth:`destroy` has been called.
|
|
||||||
destroy_called = False
|
|
||||||
|
|
||||||
#: For testing. Contains all messages :meth:`process_message` has received.
|
|
||||||
messages = []
|
|
||||||
|
|
||||||
#: For testing. Contains the last URI passed to :meth:`play_uri`.
|
#: For testing. Contains the last URI passed to :meth:`play_uri`.
|
||||||
uri = None
|
uri = None
|
||||||
|
|
||||||
@ -40,15 +33,6 @@ class DummyOutput(BaseOutput):
|
|||||||
#: For testing. Contains the current volume.
|
#: For testing. Contains the current volume.
|
||||||
volume = 100
|
volume = 100
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self.start_called = True
|
|
||||||
|
|
||||||
def destroy(self):
|
|
||||||
self.destroy_called = True
|
|
||||||
|
|
||||||
def process_message(self, message):
|
|
||||||
self.messages.append(message)
|
|
||||||
|
|
||||||
def play_uri(self, uri):
|
def play_uri(self, uri):
|
||||||
self.uri = uri
|
self.uri = uri
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -3,113 +3,39 @@ pygst.require('0.10')
|
|||||||
import gst
|
import gst
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
|
||||||
|
from pykka.actor import ThreadingActor
|
||||||
|
from pykka.registry import ActorRegistry
|
||||||
|
|
||||||
from mopidy import settings
|
from mopidy import settings
|
||||||
|
from mopidy.backends.base import Backend
|
||||||
from mopidy.outputs.base import BaseOutput
|
from mopidy.outputs.base import BaseOutput
|
||||||
from mopidy.utils.process import (BaseThread, pickle_connection,
|
|
||||||
unpickle_connection)
|
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.outputs.gstreamer')
|
logger = logging.getLogger('mopidy.outputs.gstreamer')
|
||||||
|
|
||||||
class GStreamerOutput(BaseOutput):
|
class GStreamerOutput(ThreadingActor, BaseOutput):
|
||||||
"""
|
"""
|
||||||
Audio output through GStreamer.
|
Audio output through `GStreamer <http://gstreamer.freedesktop.org/>`_.
|
||||||
|
|
||||||
Starts :class:`GStreamerMessagesThread` and :class:`GStreamerPlayerThread`.
|
|
||||||
|
|
||||||
**Settings:**
|
**Settings:**
|
||||||
|
|
||||||
- :attr:`mopidy.settings.GSTREAMER_AUDIO_SINK`
|
- :attr:`mopidy.settings.GSTREAMER_AUDIO_SINK`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self):
|
||||||
super(GStreamerOutput, self).__init__(*args, **kwargs)
|
|
||||||
self.output_queue = multiprocessing.Queue()
|
|
||||||
self.player_thread = GStreamerPlayerThread(self.core_queue,
|
|
||||||
self.output_queue)
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self.player_thread.start()
|
|
||||||
|
|
||||||
def destroy(self):
|
|
||||||
self.player_thread.destroy()
|
|
||||||
|
|
||||||
def process_message(self, message):
|
|
||||||
assert message['to'] == 'output', \
|
|
||||||
u'Message recipient must be "output".'
|
|
||||||
self.output_queue.put(message)
|
|
||||||
|
|
||||||
def _send_recv(self, message):
|
|
||||||
(my_end, other_end) = multiprocessing.Pipe()
|
|
||||||
message['to'] = 'output'
|
|
||||||
message['reply_to'] = pickle_connection(other_end)
|
|
||||||
self.process_message(message)
|
|
||||||
my_end.poll(None)
|
|
||||||
return my_end.recv()
|
|
||||||
|
|
||||||
def _send(self, message):
|
|
||||||
message['to'] = 'output'
|
|
||||||
self.process_message(message)
|
|
||||||
|
|
||||||
def play_uri(self, uri):
|
|
||||||
return self._send_recv({'command': 'play_uri', 'uri': uri})
|
|
||||||
|
|
||||||
def deliver_data(self, capabilities, data):
|
|
||||||
return self._send({
|
|
||||||
'command': 'deliver_data',
|
|
||||||
'caps': capabilities,
|
|
||||||
'data': data,
|
|
||||||
})
|
|
||||||
|
|
||||||
def end_of_data_stream(self):
|
|
||||||
return self._send({'command': 'end_of_data_stream'})
|
|
||||||
|
|
||||||
def get_position(self):
|
|
||||||
return self._send_recv({'command': 'get_position'})
|
|
||||||
|
|
||||||
def set_position(self, position):
|
|
||||||
return self._send_recv({'command': 'set_position',
|
|
||||||
'position': position})
|
|
||||||
|
|
||||||
def set_state(self, state):
|
|
||||||
return self._send_recv({'command': 'set_state', 'state': state})
|
|
||||||
|
|
||||||
def get_volume(self):
|
|
||||||
return self._send_recv({'command': 'get_volume'})
|
|
||||||
|
|
||||||
def set_volume(self, volume):
|
|
||||||
return self._send_recv({'command': 'set_volume', 'volume': volume})
|
|
||||||
|
|
||||||
|
|
||||||
class GStreamerPlayerThread(BaseThread):
|
|
||||||
"""
|
|
||||||
A process for all work related to GStreamer.
|
|
||||||
|
|
||||||
The main loop processes events from both Mopidy and GStreamer.
|
|
||||||
|
|
||||||
This thread requires :class:`mopidy.utils.process.GObjectEventThread` to be
|
|
||||||
running too. This is not enforced in any way by the code.
|
|
||||||
|
|
||||||
Make sure this subprocess is started by the MainThread in the top-most
|
|
||||||
parent process, and not some other thread. If not, we can get into the
|
|
||||||
problems described at
|
|
||||||
http://jameswestby.net/weblog/tech/14-caution-python-multiprocessing-and-glib-dont-mix.html.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, core_queue, output_queue):
|
|
||||||
super(GStreamerPlayerThread, self).__init__(core_queue)
|
|
||||||
self.name = u'GStreamerPlayerThread'
|
|
||||||
self.output_queue = output_queue
|
|
||||||
self.gst_pipeline = None
|
self.gst_pipeline = None
|
||||||
|
|
||||||
def run_inside_try(self):
|
def on_start(self):
|
||||||
self.setup()
|
self._setup_gstreamer()
|
||||||
while True:
|
|
||||||
message = self.output_queue.get()
|
def _setup_gstreamer(self):
|
||||||
self.process_mopidy_message(message)
|
"""
|
||||||
|
**Warning:** :class:`GStreamerOutput` requires
|
||||||
|
:class:`mopidy.utils.process.GObjectEventThread` to be running. This is
|
||||||
|
not enforced by :class:`GStreamerOutput` itself.
|
||||||
|
"""
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
logger.debug(u'Setting up GStreamer pipeline')
|
logger.debug(u'Setting up GStreamer pipeline')
|
||||||
|
|
||||||
self.gst_pipeline = gst.parse_launch(' ! '.join([
|
self.gst_pipeline = gst.parse_launch(' ! '.join([
|
||||||
@ -122,7 +48,7 @@ class GStreamerPlayerThread(BaseThread):
|
|||||||
|
|
||||||
if settings.BACKENDS[0] == 'mopidy.backends.local.LocalBackend':
|
if settings.BACKENDS[0] == 'mopidy.backends.local.LocalBackend':
|
||||||
uri_bin = gst.element_factory_make('uridecodebin', 'uri')
|
uri_bin = gst.element_factory_make('uridecodebin', 'uri')
|
||||||
uri_bin.connect('pad-added', self.process_new_pad, pad)
|
uri_bin.connect('pad-added', self._process_new_pad, pad)
|
||||||
self.gst_pipeline.add(uri_bin)
|
self.gst_pipeline.add(uri_bin)
|
||||||
else:
|
else:
|
||||||
app_src = gst.element_factory_make('appsrc', 'appsrc')
|
app_src = gst.element_factory_make('appsrc', 'appsrc')
|
||||||
@ -141,57 +67,29 @@ class GStreamerPlayerThread(BaseThread):
|
|||||||
# Setup bus and message processor
|
# Setup bus and message processor
|
||||||
gst_bus = self.gst_pipeline.get_bus()
|
gst_bus = self.gst_pipeline.get_bus()
|
||||||
gst_bus.add_signal_watch()
|
gst_bus.add_signal_watch()
|
||||||
gst_bus.connect('message', self.process_gst_message)
|
gst_bus.connect('message', self._process_gstreamer_message)
|
||||||
|
|
||||||
def process_new_pad(self, source, pad, target_pad):
|
def _process_new_pad(self, source, pad, target_pad):
|
||||||
pad.link(target_pad)
|
pad.link(target_pad)
|
||||||
|
|
||||||
def process_mopidy_message(self, message):
|
def _process_gstreamer_message(self, bus, message):
|
||||||
"""Process messages from the rest of Mopidy."""
|
|
||||||
if message['command'] == 'play_uri':
|
|
||||||
response = self.play_uri(message['uri'])
|
|
||||||
connection = unpickle_connection(message['reply_to'])
|
|
||||||
connection.send(response)
|
|
||||||
elif message['command'] == 'deliver_data':
|
|
||||||
self.deliver_data(message['caps'], message['data'])
|
|
||||||
elif message['command'] == 'end_of_data_stream':
|
|
||||||
self.end_of_data_stream()
|
|
||||||
elif message['command'] == 'set_state':
|
|
||||||
response = self.set_state(message['state'])
|
|
||||||
connection = unpickle_connection(message['reply_to'])
|
|
||||||
connection.send(response)
|
|
||||||
elif message['command'] == 'get_volume':
|
|
||||||
volume = self.get_volume()
|
|
||||||
connection = unpickle_connection(message['reply_to'])
|
|
||||||
connection.send(volume)
|
|
||||||
elif message['command'] == 'set_volume':
|
|
||||||
response = self.set_volume(message['volume'])
|
|
||||||
connection = unpickle_connection(message['reply_to'])
|
|
||||||
connection.send(response)
|
|
||||||
elif message['command'] == 'set_position':
|
|
||||||
response = self.set_position(message['position'])
|
|
||||||
connection = unpickle_connection(message['reply_to'])
|
|
||||||
connection.send(response)
|
|
||||||
elif message['command'] == 'get_position':
|
|
||||||
response = self.get_position()
|
|
||||||
connection = unpickle_connection(message['reply_to'])
|
|
||||||
connection.send(response)
|
|
||||||
else:
|
|
||||||
logger.warning(u'Cannot handle message: %s', message)
|
|
||||||
|
|
||||||
def process_gst_message(self, bus, message):
|
|
||||||
"""Process messages from GStreamer."""
|
"""Process messages from GStreamer."""
|
||||||
if message.type == gst.MESSAGE_EOS:
|
if message.type == gst.MESSAGE_EOS:
|
||||||
logger.debug(u'GStreamer signalled end-of-stream. '
|
logger.debug(u'GStreamer signalled end-of-stream. '
|
||||||
'Sending end_of_track to core_queue ...')
|
'Telling backend ...')
|
||||||
self.core_queue.put({'command': 'end_of_track'})
|
self._get_backend().playback.on_end_of_track()
|
||||||
elif message.type == gst.MESSAGE_ERROR:
|
elif message.type == gst.MESSAGE_ERROR:
|
||||||
self.set_state('NULL')
|
self.set_state('NULL')
|
||||||
error, debug = message.parse_error()
|
error, debug = message.parse_error()
|
||||||
logger.error(u'%s %s', error, debug)
|
logger.error(u'%s %s', error, debug)
|
||||||
# FIXME Should we send 'stop_playback' to core here? Can we
|
# FIXME Should we send 'stop_playback' to the backend here? Can we
|
||||||
# differentiate on how serious the error is?
|
# differentiate on how serious the error is?
|
||||||
|
|
||||||
|
def _get_backend(self):
|
||||||
|
backend_refs = ActorRegistry.get_by_class(Backend)
|
||||||
|
assert len(backend_refs) == 1, 'Expected exactly one running backend.'
|
||||||
|
return backend_refs[0].proxy()
|
||||||
|
|
||||||
def play_uri(self, uri):
|
def play_uri(self, uri):
|
||||||
"""Play audio at URI"""
|
"""Play audio at URI"""
|
||||||
self.set_state('READY')
|
self.set_state('READY')
|
||||||
@ -216,6 +114,21 @@ class GStreamerPlayerThread(BaseThread):
|
|||||||
"""
|
"""
|
||||||
self.gst_pipeline.get_by_name('appsrc').emit('end-of-stream')
|
self.gst_pipeline.get_by_name('appsrc').emit('end-of-stream')
|
||||||
|
|
||||||
|
def get_position(self):
|
||||||
|
try:
|
||||||
|
position = self.gst_pipeline.query_position(gst.FORMAT_TIME)[0]
|
||||||
|
return position // gst.MSECOND
|
||||||
|
except gst.QueryError, e:
|
||||||
|
logger.error('time_position failed: %s', e)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def set_position(self, position):
|
||||||
|
self.gst_pipeline.get_state() # block until state changes are done
|
||||||
|
handeled = self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME),
|
||||||
|
gst.SEEK_FLAG_FLUSH, position * gst.MSECOND)
|
||||||
|
self.gst_pipeline.get_state() # block until seek is done
|
||||||
|
return handeled
|
||||||
|
|
||||||
def set_state(self, state_name):
|
def set_state(self, state_name):
|
||||||
"""
|
"""
|
||||||
Set the GStreamer state. Returns :class:`True` if successful.
|
Set the GStreamer state. Returns :class:`True` if successful.
|
||||||
@ -252,18 +165,3 @@ class GStreamerPlayerThread(BaseThread):
|
|||||||
gst_volume = self.gst_pipeline.get_by_name('volume')
|
gst_volume = self.gst_pipeline.get_by_name('volume')
|
||||||
gst_volume.set_property('volume', volume / 100.0)
|
gst_volume.set_property('volume', volume / 100.0)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_position(self, position):
|
|
||||||
self.gst_pipeline.get_state() # block until state changes are done
|
|
||||||
handeled = self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME),
|
|
||||||
gst.SEEK_FLAG_FLUSH, position * gst.MSECOND)
|
|
||||||
self.gst_pipeline.get_state() # block until seek is done
|
|
||||||
return handeled
|
|
||||||
|
|
||||||
def get_position(self):
|
|
||||||
try:
|
|
||||||
position = self.gst_pipeline.query_position(gst.FORMAT_TIME)[0]
|
|
||||||
return position // gst.MSECOND
|
|
||||||
except gst.QueryError, e:
|
|
||||||
logger.error('time_position failed: %s', e)
|
|
||||||
return 0
|
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import logging
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
|
|
||||||
from mopidy import settings
|
from mopidy import get_version, settings
|
||||||
|
|
||||||
def setup_logging(verbosity_level, save_debug_log):
|
def setup_logging(verbosity_level, save_debug_log):
|
||||||
setup_root_logger()
|
setup_root_logger()
|
||||||
setup_console_logging(verbosity_level)
|
setup_console_logging(verbosity_level)
|
||||||
if save_debug_log:
|
if save_debug_log:
|
||||||
setup_debug_logging_to_file()
|
setup_debug_logging_to_file()
|
||||||
|
logger = logging.getLogger('mopidy.utils.log')
|
||||||
|
logger.info(u'-- Starting Mopidy %s --', get_version())
|
||||||
|
|
||||||
def setup_root_logger():
|
def setup_root_logger():
|
||||||
root = logging.getLogger('')
|
root = logging.getLogger('')
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
import threading
|
||||||
import multiprocessing.dummy
|
|
||||||
from multiprocessing.reduction import reduce_connection
|
|
||||||
import pickle
|
|
||||||
|
|
||||||
import gobject
|
import gobject
|
||||||
gobject.threads_init()
|
gobject.threads_init()
|
||||||
@ -11,52 +8,10 @@ from mopidy import SettingsError
|
|||||||
|
|
||||||
logger = logging.getLogger('mopidy.utils.process')
|
logger = logging.getLogger('mopidy.utils.process')
|
||||||
|
|
||||||
def pickle_connection(connection):
|
|
||||||
return pickle.dumps(reduce_connection(connection))
|
|
||||||
|
|
||||||
def unpickle_connection(pickled_connection):
|
class BaseThread(threading.Thread):
|
||||||
# From http://stackoverflow.com/questions/1446004
|
def __init__(self):
|
||||||
(func, args) = pickle.loads(pickled_connection)
|
|
||||||
return func(*args)
|
|
||||||
|
|
||||||
class BaseProcess(multiprocessing.Process):
|
|
||||||
def __init__(self, core_queue):
|
|
||||||
super(BaseProcess, self).__init__()
|
|
||||||
self.core_queue = core_queue
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
logger.debug(u'%s: Starting process', self.name)
|
|
||||||
try:
|
|
||||||
self.run_inside_try()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
logger.info(u'Interrupted by user')
|
|
||||||
self.exit(0, u'Interrupted by user')
|
|
||||||
except SettingsError as e:
|
|
||||||
logger.error(e.message)
|
|
||||||
self.exit(1, u'Settings error')
|
|
||||||
except ImportError as e:
|
|
||||||
logger.error(e)
|
|
||||||
self.exit(2, u'Import error')
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(e)
|
|
||||||
self.exit(3, u'Unknown error')
|
|
||||||
|
|
||||||
def run_inside_try(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def destroy(self):
|
|
||||||
self.terminate()
|
|
||||||
|
|
||||||
def exit(self, status=0, reason=None):
|
|
||||||
self.core_queue.put({'to': 'core', 'command': 'exit',
|
|
||||||
'status': status, 'reason': reason})
|
|
||||||
self.destroy()
|
|
||||||
|
|
||||||
|
|
||||||
class BaseThread(multiprocessing.dummy.Process):
|
|
||||||
def __init__(self, core_queue):
|
|
||||||
super(BaseThread, self).__init__()
|
super(BaseThread, self).__init__()
|
||||||
self.core_queue = core_queue
|
|
||||||
# No thread should block process from exiting
|
# No thread should block process from exiting
|
||||||
self.daemon = True
|
self.daemon = True
|
||||||
|
|
||||||
@ -84,8 +39,6 @@ class BaseThread(multiprocessing.dummy.Process):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def exit(self, status=0, reason=None):
|
def exit(self, status=0, reason=None):
|
||||||
self.core_queue.put({'to': 'core', 'command': 'exit',
|
|
||||||
'status': status, 'reason': reason})
|
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
|
|
||||||
@ -98,8 +51,8 @@ class GObjectEventThread(BaseThread):
|
|||||||
:mod:`mopidy.output.gstreamer`, :mod:`mopidy.frontend.mpris`, etc.
|
:mod:`mopidy.output.gstreamer`, :mod:`mopidy.frontend.mpris`, etc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, core_queue):
|
def __init__(self):
|
||||||
super(GObjectEventThread, self).__init__(core_queue)
|
super(GObjectEventThread, self).__init__()
|
||||||
self.name = u'GObjectEventThread'
|
self.name = u'GObjectEventThread'
|
||||||
self.loop = None
|
self.loop = None
|
||||||
|
|
||||||
|
|||||||
1
requirements/core.txt
Normal file
1
requirements/core.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Pykka >= 0.12
|
||||||
@ -1,3 +1,4 @@
|
|||||||
coverage
|
coverage
|
||||||
|
mock
|
||||||
nose
|
nose
|
||||||
tox
|
tox
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
|
import mock
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from mopidy.mixers.dummy import DummyMixer
|
|
||||||
from mopidy.models import Playlist, Track
|
from mopidy.models import Playlist, Track
|
||||||
from mopidy.outputs.dummy import DummyOutput
|
from mopidy.outputs.base import BaseOutput
|
||||||
|
|
||||||
from tests.backends.base import populate_playlist
|
from tests.backends.base import populate_playlist
|
||||||
|
|
||||||
@ -11,19 +11,13 @@ class CurrentPlaylistControllerTest(object):
|
|||||||
tracks = []
|
tracks = []
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.core_queue = multiprocessing.Queue()
|
self.backend = self.backend_class()
|
||||||
self.output = DummyOutput(self.core_queue)
|
self.backend.output = mock.Mock(spec=BaseOutput)
|
||||||
self.backend = self.backend_class(
|
|
||||||
self.core_queue, self.output, DummyMixer)
|
|
||||||
self.controller = self.backend.current_playlist
|
self.controller = self.backend.current_playlist
|
||||||
self.playback = self.backend.playback
|
self.playback = self.backend.playback
|
||||||
|
|
||||||
assert len(self.tracks) == 3, 'Need three tracks to run tests.'
|
assert len(self.tracks) == 3, 'Need three tracks to run tests.'
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self.backend.destroy()
|
|
||||||
self.output.destroy()
|
|
||||||
|
|
||||||
def test_add(self):
|
def test_add(self):
|
||||||
for track in self.tracks:
|
for track in self.tracks:
|
||||||
cp_track = self.controller.add(track)
|
cp_track = self.controller.add(track)
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
from mopidy.mixers.dummy import DummyMixer
|
|
||||||
from mopidy.models import Playlist, Track, Album, Artist
|
from mopidy.models import Playlist, Track, Album, Artist
|
||||||
|
|
||||||
from tests import SkipTest, data_folder
|
from tests import SkipTest, data_folder
|
||||||
@ -15,12 +14,9 @@ class LibraryControllerTest(object):
|
|||||||
Track()]
|
Track()]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.backend = self.backend_class(mixer_class=DummyMixer)
|
self.backend = self.backend_class()
|
||||||
self.library = self.backend.library
|
self.library = self.backend.library
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self.backend.destroy()
|
|
||||||
|
|
||||||
def test_refresh(self):
|
def test_refresh(self):
|
||||||
self.library.refresh()
|
self.library.refresh()
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
|
import mock
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from mopidy.mixers.dummy import DummyMixer
|
|
||||||
from mopidy.models import Track
|
from mopidy.models import Track
|
||||||
from mopidy.outputs.dummy import DummyOutput
|
from mopidy.outputs.base import BaseOutput
|
||||||
|
|
||||||
from tests import SkipTest
|
from tests import SkipTest
|
||||||
from tests.backends.base import populate_playlist
|
from tests.backends.base import populate_playlist
|
||||||
@ -15,10 +15,8 @@ class PlaybackControllerTest(object):
|
|||||||
tracks = []
|
tracks = []
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.core_queue = multiprocessing.Queue()
|
self.backend = self.backend_class()
|
||||||
self.output = DummyOutput(self.core_queue)
|
self.backend.output = mock.Mock(spec=BaseOutput)
|
||||||
self.backend = self.backend_class(
|
|
||||||
self.core_queue, self.output, DummyMixer)
|
|
||||||
self.playback = self.backend.playback
|
self.playback = self.backend.playback
|
||||||
self.current_playlist = self.backend.current_playlist
|
self.current_playlist = self.backend.current_playlist
|
||||||
|
|
||||||
@ -27,10 +25,6 @@ class PlaybackControllerTest(object):
|
|||||||
assert self.tracks[0].length >= 2000, \
|
assert self.tracks[0].length >= 2000, \
|
||||||
'First song needs to be at least 2000 miliseconds'
|
'First song needs to be at least 2000 miliseconds'
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self.backend.destroy()
|
|
||||||
self.output.destroy()
|
|
||||||
|
|
||||||
def test_initial_state_is_stopped(self):
|
def test_initial_state_is_stopped(self):
|
||||||
self.assertEqual(self.playback.state, self.playback.STOPPED)
|
self.assertEqual(self.playback.state, self.playback.STOPPED)
|
||||||
|
|
||||||
@ -733,10 +727,18 @@ class PlaybackControllerTest(object):
|
|||||||
self.assertEqual(self.playback.stop(), None)
|
self.assertEqual(self.playback.stop(), None)
|
||||||
|
|
||||||
def test_time_position_when_stopped(self):
|
def test_time_position_when_stopped(self):
|
||||||
|
future = mock.Mock()
|
||||||
|
future.get = mock.Mock(return_value=0)
|
||||||
|
self.backend.output.get_position = mock.Mock(return_value=future)
|
||||||
|
|
||||||
self.assertEqual(self.playback.time_position, 0)
|
self.assertEqual(self.playback.time_position, 0)
|
||||||
|
|
||||||
@populate_playlist
|
@populate_playlist
|
||||||
def test_time_position_when_stopped_with_playlist(self):
|
def test_time_position_when_stopped_with_playlist(self):
|
||||||
|
future = mock.Mock()
|
||||||
|
future.get = mock.Mock(return_value=0)
|
||||||
|
self.backend.output.get_position = mock.Mock(return_value=future)
|
||||||
|
|
||||||
self.assertEqual(self.playback.time_position, 0)
|
self.assertEqual(self.playback.time_position, 0)
|
||||||
|
|
||||||
@SkipTest # Uses sleep and does not work with LocalBackend+DummyOutput
|
@SkipTest # Uses sleep and does not work with LocalBackend+DummyOutput
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import shutil
|
|||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from mopidy import settings
|
from mopidy import settings
|
||||||
from mopidy.mixers.dummy import DummyMixer
|
|
||||||
from mopidy.models import Playlist
|
from mopidy.models import Playlist
|
||||||
|
|
||||||
from tests import SkipTest, data_folder
|
from tests import SkipTest, data_folder
|
||||||
@ -14,12 +13,10 @@ class StoredPlaylistsControllerTest(object):
|
|||||||
settings.LOCAL_TAG_CACHE_FILE = data_folder('library_tag_cache')
|
settings.LOCAL_TAG_CACHE_FILE = data_folder('library_tag_cache')
|
||||||
settings.LOCAL_MUSIC_PATH = data_folder('')
|
settings.LOCAL_MUSIC_PATH = data_folder('')
|
||||||
|
|
||||||
self.backend = self.backend_class(mixer_class=DummyMixer)
|
self.backend = self.backend_class()
|
||||||
self.stored = self.backend.stored_playlists
|
self.stored = self.backend.stored_playlists
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.backend.destroy()
|
|
||||||
|
|
||||||
if os.path.exists(settings.LOCAL_PLAYLIST_PATH):
|
if os.path.exists(settings.LOCAL_PLAYLIST_PATH):
|
||||||
shutil.rmtree(settings.LOCAL_PLAYLIST_PATH)
|
shutil.rmtree(settings.LOCAL_PLAYLIST_PATH)
|
||||||
|
|
||||||
|
|||||||
@ -70,8 +70,7 @@ class LocalStoredPlaylistsControllerTest(StoredPlaylistsControllerTest,
|
|||||||
|
|
||||||
self.stored.save(playlist)
|
self.stored.save(playlist)
|
||||||
|
|
||||||
self.backend.destroy()
|
self.backend = self.backend_class()
|
||||||
self.backend = self.backend_class(mixer_class=DummyMixer)
|
|
||||||
self.stored = self.backend.stored_playlists
|
self.stored = self.backend.stored_playlists
|
||||||
|
|
||||||
self.assert_(self.stored.playlists)
|
self.assert_(self.stored.playlists)
|
||||||
|
|||||||
@ -6,8 +6,13 @@ from mopidy.mixers.dummy import DummyMixer
|
|||||||
|
|
||||||
class AudioOutputHandlerTest(unittest.TestCase):
|
class AudioOutputHandlerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_enableoutput(self):
|
def test_enableoutput(self):
|
||||||
result = self.h.handle_request(u'enableoutput "0"')
|
result = self.h.handle_request(u'enableoutput "0"')
|
||||||
@ -20,6 +25,6 @@ class AudioOutputHandlerTest(unittest.TestCase):
|
|||||||
def test_outputs(self):
|
def test_outputs(self):
|
||||||
result = self.h.handle_request(u'outputs')
|
result = self.h.handle_request(u'outputs')
|
||||||
self.assert_(u'outputid: 0' in result)
|
self.assert_(u'outputid: 0' in result)
|
||||||
self.assert_(u'outputname: DummyBackend' in result)
|
self.assert_(u'outputname: None' in result)
|
||||||
self.assert_(u'outputenabled: 1' in result)
|
self.assert_(u'outputenabled: 1' in result)
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|||||||
@ -6,8 +6,13 @@ from mopidy.mixers.dummy import DummyMixer
|
|||||||
|
|
||||||
class CommandListsTest(unittest.TestCase):
|
class CommandListsTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_command_list_begin(self):
|
def test_command_list_begin(self):
|
||||||
result = self.h.handle_request(u'command_list_begin')
|
result = self.h.handle_request(u'command_list_begin')
|
||||||
|
|||||||
@ -7,10 +7,13 @@ from mopidy.mixers.dummy import DummyMixer
|
|||||||
|
|
||||||
class ConnectionHandlerTest(unittest.TestCase):
|
class ConnectionHandlerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
settings.runtime.clear()
|
settings.runtime.clear()
|
||||||
|
|
||||||
def test_close(self):
|
def test_close(self):
|
||||||
|
|||||||
@ -7,26 +7,26 @@ from mopidy.models import Track
|
|||||||
|
|
||||||
class CurrentPlaylistHandlerTest(unittest.TestCase):
|
class CurrentPlaylistHandlerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_add(self):
|
def test_add(self):
|
||||||
needle = Track(uri='dummy://foo')
|
needle = Track(uri='dummy://foo')
|
||||||
self.b.library.provider._library = [Track(), Track(), needle, Track()]
|
self.b.library.provider.dummy_library = [
|
||||||
|
Track(), Track(), needle, Track()]
|
||||||
self.b.current_playlist.append(
|
self.b.current_playlist.append(
|
||||||
[Track(), Track(), Track(), Track(), Track()])
|
[Track(), Track(), Track(), Track(), Track()])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
|
||||||
result = self.h.handle_request(u'add "dummy://foo"')
|
result = self.h.handle_request(u'add "dummy://foo"')
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 6)
|
|
||||||
self.assertEqual(self.b.current_playlist.tracks[5], needle)
|
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assert_(u'OK' in result)
|
self.assertEqual(result[0], u'OK')
|
||||||
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 6)
|
||||||
def test_add_with_uri_not_found_in_library_should_not_call_lookup(self):
|
self.assertEqual(self.b.current_playlist.tracks.get()[5], needle)
|
||||||
self.b.library.lookup = lambda uri: self.fail("Shouldn't run")
|
|
||||||
result = self.h.handle_request(u'add "foo"')
|
|
||||||
self.assertEqual(result[0],
|
|
||||||
u'ACK [50@0] {add} directory or file not found')
|
|
||||||
|
|
||||||
def test_add_with_uri_not_found_in_library_should_ack(self):
|
def test_add_with_uri_not_found_in_library_should_ack(self):
|
||||||
result = self.h.handle_request(u'add "dummy://foo"')
|
result = self.h.handle_request(u'add "dummy://foo"')
|
||||||
@ -40,41 +40,43 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_addid_without_songpos(self):
|
def test_addid_without_songpos(self):
|
||||||
needle = Track(uri='dummy://foo')
|
needle = Track(uri='dummy://foo')
|
||||||
self.b.library.provider._library = [Track(), Track(), needle, Track()]
|
self.b.library.provider.dummy_library = [
|
||||||
|
Track(), Track(), needle, Track()]
|
||||||
self.b.current_playlist.append(
|
self.b.current_playlist.append(
|
||||||
[Track(), Track(), Track(), Track(), Track()])
|
[Track(), Track(), Track(), Track(), Track()])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
|
||||||
result = self.h.handle_request(u'addid "dummy://foo"')
|
result = self.h.handle_request(u'addid "dummy://foo"')
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 6)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 6)
|
||||||
self.assertEqual(self.b.current_playlist.tracks[5], needle)
|
self.assertEqual(self.b.current_playlist.tracks.get()[5], needle)
|
||||||
self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks[5][0]
|
self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks.get()[5][0]
|
||||||
in result)
|
in result)
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_addid_with_empty_uri_does_not_lookup_and_acks(self):
|
def test_addid_with_empty_uri_acks(self):
|
||||||
self.b.library.lookup = lambda uri: self.fail("Shouldn't run")
|
|
||||||
result = self.h.handle_request(u'addid ""')
|
result = self.h.handle_request(u'addid ""')
|
||||||
self.assertEqual(result[0], u'ACK [50@0] {addid} No such song')
|
self.assertEqual(result[0], u'ACK [50@0] {addid} No such song')
|
||||||
|
|
||||||
def test_addid_with_songpos(self):
|
def test_addid_with_songpos(self):
|
||||||
needle = Track(uri='dummy://foo')
|
needle = Track(uri='dummy://foo')
|
||||||
self.b.library.provider._library = [Track(), Track(), needle, Track()]
|
self.b.library.provider.dummy_library = [
|
||||||
|
Track(), Track(), needle, Track()]
|
||||||
self.b.current_playlist.append(
|
self.b.current_playlist.append(
|
||||||
[Track(), Track(), Track(), Track(), Track()])
|
[Track(), Track(), Track(), Track(), Track()])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
|
||||||
result = self.h.handle_request(u'addid "dummy://foo" "3"')
|
result = self.h.handle_request(u'addid "dummy://foo" "3"')
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 6)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 6)
|
||||||
self.assertEqual(self.b.current_playlist.tracks[3], needle)
|
self.assertEqual(self.b.current_playlist.tracks.get()[3], needle)
|
||||||
self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks[3][0]
|
self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks.get()[3][0]
|
||||||
in result)
|
in result)
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_addid_with_songpos_out_of_bounds_should_ack(self):
|
def test_addid_with_songpos_out_of_bounds_should_ack(self):
|
||||||
needle = Track(uri='dummy://foo')
|
needle = Track(uri='dummy://foo')
|
||||||
self.b.library.provider._library = [Track(), Track(), needle, Track()]
|
self.b.library.provider.dummy_library = [
|
||||||
|
Track(), Track(), needle, Track()]
|
||||||
self.b.current_playlist.append(
|
self.b.current_playlist.append(
|
||||||
[Track(), Track(), Track(), Track(), Track()])
|
[Track(), Track(), Track(), Track(), Track()])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
|
||||||
result = self.h.handle_request(u'addid "dummy://foo" "6"')
|
result = self.h.handle_request(u'addid "dummy://foo" "6"')
|
||||||
self.assertEqual(result[0], u'ACK [2@0] {addid} Bad song index')
|
self.assertEqual(result[0], u'ACK [2@0] {addid} Bad song index')
|
||||||
|
|
||||||
@ -85,65 +87,65 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
|||||||
def test_clear(self):
|
def test_clear(self):
|
||||||
self.b.current_playlist.append(
|
self.b.current_playlist.append(
|
||||||
[Track(), Track(), Track(), Track(), Track()])
|
[Track(), Track(), Track(), Track(), Track()])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
|
||||||
result = self.h.handle_request(u'clear')
|
result = self.h.handle_request(u'clear')
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 0)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 0)
|
||||||
self.assertEqual(self.b.playback.current_track, None)
|
self.assertEqual(self.b.playback.current_track.get(), None)
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_delete_songpos(self):
|
def test_delete_songpos(self):
|
||||||
self.b.current_playlist.append(
|
self.b.current_playlist.append(
|
||||||
[Track(), Track(), Track(), Track(), Track()])
|
[Track(), Track(), Track(), Track(), Track()])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
|
||||||
result = self.h.handle_request(u'delete "%d"' %
|
result = self.h.handle_request(u'delete "%d"' %
|
||||||
self.b.current_playlist.cp_tracks[2][0])
|
self.b.current_playlist.cp_tracks.get()[2][0])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 4)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 4)
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_delete_songpos_out_of_bounds(self):
|
def test_delete_songpos_out_of_bounds(self):
|
||||||
self.b.current_playlist.append(
|
self.b.current_playlist.append(
|
||||||
[Track(), Track(), Track(), Track(), Track()])
|
[Track(), Track(), Track(), Track(), Track()])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
|
||||||
result = self.h.handle_request(u'delete "5"')
|
result = self.h.handle_request(u'delete "5"')
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
|
||||||
self.assertEqual(result[0], u'ACK [2@0] {delete} Bad song index')
|
self.assertEqual(result[0], u'ACK [2@0] {delete} Bad song index')
|
||||||
|
|
||||||
def test_delete_open_range(self):
|
def test_delete_open_range(self):
|
||||||
self.b.current_playlist.append(
|
self.b.current_playlist.append(
|
||||||
[Track(), Track(), Track(), Track(), Track()])
|
[Track(), Track(), Track(), Track(), Track()])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
|
||||||
result = self.h.handle_request(u'delete "1:"')
|
result = self.h.handle_request(u'delete "1:"')
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 1)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 1)
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_delete_closed_range(self):
|
def test_delete_closed_range(self):
|
||||||
self.b.current_playlist.append(
|
self.b.current_playlist.append(
|
||||||
[Track(), Track(), Track(), Track(), Track()])
|
[Track(), Track(), Track(), Track(), Track()])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
|
||||||
result = self.h.handle_request(u'delete "1:3"')
|
result = self.h.handle_request(u'delete "1:3"')
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 3)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 3)
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_delete_range_out_of_bounds(self):
|
def test_delete_range_out_of_bounds(self):
|
||||||
self.b.current_playlist.append(
|
self.b.current_playlist.append(
|
||||||
[Track(), Track(), Track(), Track(), Track()])
|
[Track(), Track(), Track(), Track(), Track()])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
|
||||||
result = self.h.handle_request(u'delete "5:7"')
|
result = self.h.handle_request(u'delete "5:7"')
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
|
||||||
self.assertEqual(result[0], u'ACK [2@0] {delete} Bad song index')
|
self.assertEqual(result[0], u'ACK [2@0] {delete} Bad song index')
|
||||||
|
|
||||||
def test_deleteid(self):
|
def test_deleteid(self):
|
||||||
self.b.current_playlist.append([Track(), Track()])
|
self.b.current_playlist.append([Track(), Track()])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 2)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 2)
|
||||||
result = self.h.handle_request(u'deleteid "1"')
|
result = self.h.handle_request(u'deleteid "1"')
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 1)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 1)
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_deleteid_does_not_exist(self):
|
def test_deleteid_does_not_exist(self):
|
||||||
self.b.current_playlist.append([Track(), Track()])
|
self.b.current_playlist.append([Track(), Track()])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 2)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 2)
|
||||||
result = self.h.handle_request(u'deleteid "12345"')
|
result = self.h.handle_request(u'deleteid "12345"')
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 2)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 2)
|
||||||
self.assertEqual(result[0], u'ACK [50@0] {deleteid} No such song')
|
self.assertEqual(result[0], u'ACK [50@0] {deleteid} No such song')
|
||||||
|
|
||||||
def test_move_songpos(self):
|
def test_move_songpos(self):
|
||||||
@ -152,12 +154,13 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
|||||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||||
])
|
])
|
||||||
result = self.h.handle_request(u'move "1" "0"')
|
result = self.h.handle_request(u'move "1" "0"')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'b')
|
tracks = self.b.current_playlist.tracks.get()
|
||||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'a')
|
self.assertEqual(tracks[0].name, 'b')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'c')
|
self.assertEqual(tracks[1].name, 'a')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'd')
|
self.assertEqual(tracks[2].name, 'c')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'e')
|
self.assertEqual(tracks[3].name, 'd')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'f')
|
self.assertEqual(tracks[4].name, 'e')
|
||||||
|
self.assertEqual(tracks[5].name, 'f')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_move_open_range(self):
|
def test_move_open_range(self):
|
||||||
@ -166,12 +169,13 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
|||||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||||
])
|
])
|
||||||
result = self.h.handle_request(u'move "2:" "0"')
|
result = self.h.handle_request(u'move "2:" "0"')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'c')
|
tracks = self.b.current_playlist.tracks.get()
|
||||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'd')
|
self.assertEqual(tracks[0].name, 'c')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'e')
|
self.assertEqual(tracks[1].name, 'd')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'f')
|
self.assertEqual(tracks[2].name, 'e')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'a')
|
self.assertEqual(tracks[3].name, 'f')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'b')
|
self.assertEqual(tracks[4].name, 'a')
|
||||||
|
self.assertEqual(tracks[5].name, 'b')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_move_closed_range(self):
|
def test_move_closed_range(self):
|
||||||
@ -180,12 +184,13 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
|||||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||||
])
|
])
|
||||||
result = self.h.handle_request(u'move "1:3" "0"')
|
result = self.h.handle_request(u'move "1:3" "0"')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'b')
|
tracks = self.b.current_playlist.tracks.get()
|
||||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'c')
|
self.assertEqual(tracks[0].name, 'b')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'a')
|
self.assertEqual(tracks[1].name, 'c')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'd')
|
self.assertEqual(tracks[2].name, 'a')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'e')
|
self.assertEqual(tracks[3].name, 'd')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'f')
|
self.assertEqual(tracks[4].name, 'e')
|
||||||
|
self.assertEqual(tracks[5].name, 'f')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_moveid(self):
|
def test_moveid(self):
|
||||||
@ -194,12 +199,13 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
|||||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||||
])
|
])
|
||||||
result = self.h.handle_request(u'moveid "4" "2"')
|
result = self.h.handle_request(u'moveid "4" "2"')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'a')
|
tracks = self.b.current_playlist.tracks.get()
|
||||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'b')
|
self.assertEqual(tracks[0].name, 'a')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'e')
|
self.assertEqual(tracks[1].name, 'b')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'c')
|
self.assertEqual(tracks[2].name, 'e')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'd')
|
self.assertEqual(tracks[3].name, 'c')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'f')
|
self.assertEqual(tracks[4].name, 'd')
|
||||||
|
self.assertEqual(tracks[5].name, 'f')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_playlist_returns_same_as_playlistinfo(self):
|
def test_playlist_returns_same_as_playlistinfo(self):
|
||||||
@ -361,14 +367,15 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
|||||||
def test_plchangesposid(self):
|
def test_plchangesposid(self):
|
||||||
self.b.current_playlist.append([Track(), Track(), Track()])
|
self.b.current_playlist.append([Track(), Track(), Track()])
|
||||||
result = self.h.handle_request(u'plchangesposid "0"')
|
result = self.h.handle_request(u'plchangesposid "0"')
|
||||||
|
cp_tracks = self.b.current_playlist.cp_tracks.get()
|
||||||
self.assert_(u'cpos: 0' in result)
|
self.assert_(u'cpos: 0' in result)
|
||||||
self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks[0][0]
|
self.assert_(u'Id: %d' % cp_tracks[0][0]
|
||||||
in result)
|
in result)
|
||||||
self.assert_(u'cpos: 2' in result)
|
self.assert_(u'cpos: 2' in result)
|
||||||
self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks[1][0]
|
self.assert_(u'Id: %d' % cp_tracks[1][0]
|
||||||
in result)
|
in result)
|
||||||
self.assert_(u'cpos: 2' in result)
|
self.assert_(u'cpos: 2' in result)
|
||||||
self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks[2][0]
|
self.assert_(u'Id: %d' % cp_tracks[2][0]
|
||||||
in result)
|
in result)
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
@ -377,9 +384,9 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
|||||||
Track(name='a'), Track(name='b'), Track(name='c'),
|
Track(name='a'), Track(name='b'), Track(name='c'),
|
||||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||||
])
|
])
|
||||||
version = self.b.current_playlist.version
|
version = self.b.current_playlist.version.get()
|
||||||
result = self.h.handle_request(u'shuffle')
|
result = self.h.handle_request(u'shuffle')
|
||||||
self.assert_(version < self.b.current_playlist.version)
|
self.assert_(version < self.b.current_playlist.version.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_shuffle_with_open_range(self):
|
def test_shuffle_with_open_range(self):
|
||||||
@ -387,13 +394,14 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
|||||||
Track(name='a'), Track(name='b'), Track(name='c'),
|
Track(name='a'), Track(name='b'), Track(name='c'),
|
||||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||||
])
|
])
|
||||||
version = self.b.current_playlist.version
|
version = self.b.current_playlist.version.get()
|
||||||
result = self.h.handle_request(u'shuffle "4:"')
|
result = self.h.handle_request(u'shuffle "4:"')
|
||||||
self.assert_(version < self.b.current_playlist.version)
|
self.assert_(version < self.b.current_playlist.version.get())
|
||||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'a')
|
tracks = self.b.current_playlist.tracks.get()
|
||||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'b')
|
self.assertEqual(tracks[0].name, 'a')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'c')
|
self.assertEqual(tracks[1].name, 'b')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'd')
|
self.assertEqual(tracks[2].name, 'c')
|
||||||
|
self.assertEqual(tracks[3].name, 'd')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_shuffle_with_closed_range(self):
|
def test_shuffle_with_closed_range(self):
|
||||||
@ -401,13 +409,14 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
|||||||
Track(name='a'), Track(name='b'), Track(name='c'),
|
Track(name='a'), Track(name='b'), Track(name='c'),
|
||||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||||
])
|
])
|
||||||
version = self.b.current_playlist.version
|
version = self.b.current_playlist.version.get()
|
||||||
result = self.h.handle_request(u'shuffle "1:3"')
|
result = self.h.handle_request(u'shuffle "1:3"')
|
||||||
self.assert_(version < self.b.current_playlist.version)
|
self.assert_(version < self.b.current_playlist.version.get())
|
||||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'a')
|
tracks = self.b.current_playlist.tracks.get()
|
||||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'd')
|
self.assertEqual(tracks[0].name, 'a')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'e')
|
self.assertEqual(tracks[3].name, 'd')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'f')
|
self.assertEqual(tracks[4].name, 'e')
|
||||||
|
self.assertEqual(tracks[5].name, 'f')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_swap(self):
|
def test_swap(self):
|
||||||
@ -416,12 +425,13 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
|||||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||||
])
|
])
|
||||||
result = self.h.handle_request(u'swap "1" "4"')
|
result = self.h.handle_request(u'swap "1" "4"')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'a')
|
tracks = self.b.current_playlist.tracks.get()
|
||||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'e')
|
self.assertEqual(tracks[0].name, 'a')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'c')
|
self.assertEqual(tracks[1].name, 'e')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'd')
|
self.assertEqual(tracks[2].name, 'c')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'b')
|
self.assertEqual(tracks[3].name, 'd')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'f')
|
self.assertEqual(tracks[4].name, 'b')
|
||||||
|
self.assertEqual(tracks[5].name, 'f')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_swapid(self):
|
def test_swapid(self):
|
||||||
@ -430,10 +440,11 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
|||||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||||
])
|
])
|
||||||
result = self.h.handle_request(u'swapid "1" "4"')
|
result = self.h.handle_request(u'swapid "1" "4"')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'a')
|
tracks = self.b.current_playlist.tracks.get()
|
||||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'e')
|
self.assertEqual(tracks[0].name, 'a')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'c')
|
self.assertEqual(tracks[1].name, 'e')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'd')
|
self.assertEqual(tracks[2].name, 'c')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'b')
|
self.assertEqual(tracks[3].name, 'd')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'f')
|
self.assertEqual(tracks[4].name, 'b')
|
||||||
|
self.assertEqual(tracks[5].name, 'f')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|||||||
@ -8,8 +8,13 @@ from mopidy.mixers.dummy import DummyMixer
|
|||||||
|
|
||||||
class MpdDispatcherTest(unittest.TestCase):
|
class MpdDispatcherTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_register_same_pattern_twice_fails(self):
|
def test_register_same_pattern_twice_fails(self):
|
||||||
func = lambda: None
|
func = lambda: None
|
||||||
|
|||||||
@ -6,8 +6,13 @@ from mopidy.mixers.dummy import DummyMixer
|
|||||||
|
|
||||||
class MusicDatabaseHandlerTest(unittest.TestCase):
|
class MusicDatabaseHandlerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_count(self):
|
def test_count(self):
|
||||||
result = self.h.handle_request(u'count "tag" "needle"')
|
result = self.h.handle_request(u'count "tag" "needle"')
|
||||||
@ -65,8 +70,13 @@ class MusicDatabaseHandlerTest(unittest.TestCase):
|
|||||||
|
|
||||||
class MusicDatabaseFindTest(unittest.TestCase):
|
class MusicDatabaseFindTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_find_album(self):
|
def test_find_album(self):
|
||||||
result = self.h.handle_request(u'find "album" "what"')
|
result = self.h.handle_request(u'find "album" "what"')
|
||||||
@ -117,8 +127,13 @@ class MusicDatabaseFindTest(unittest.TestCase):
|
|||||||
|
|
||||||
class MusicDatabaseListTest(unittest.TestCase):
|
class MusicDatabaseListTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_list_foo_returns_ack(self):
|
def test_list_foo_returns_ack(self):
|
||||||
result = self.h.handle_request(u'list "foo"')
|
result = self.h.handle_request(u'list "foo"')
|
||||||
@ -308,8 +323,13 @@ class MusicDatabaseListTest(unittest.TestCase):
|
|||||||
|
|
||||||
class MusicDatabaseSearchTest(unittest.TestCase):
|
class MusicDatabaseSearchTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_search_album(self):
|
def test_search_album(self):
|
||||||
result = self.h.handle_request(u'search "album" "analbum"')
|
result = self.h.handle_request(u'search "album" "analbum"')
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from mopidy.backends.base import PlaybackController
|
||||||
from mopidy.backends.dummy import DummyBackend
|
from mopidy.backends.dummy import DummyBackend
|
||||||
from mopidy.frontends.mpd import dispatcher
|
from mopidy.frontends.mpd import dispatcher
|
||||||
from mopidy.mixers.dummy import DummyMixer
|
from mopidy.mixers.dummy import DummyMixer
|
||||||
@ -7,29 +8,38 @@ from mopidy.models import Track
|
|||||||
|
|
||||||
from tests import SkipTest
|
from tests import SkipTest
|
||||||
|
|
||||||
|
PAUSED = PlaybackController.PAUSED
|
||||||
|
PLAYING = PlaybackController.PLAYING
|
||||||
|
STOPPED = PlaybackController.STOPPED
|
||||||
|
|
||||||
class PlaybackOptionsHandlerTest(unittest.TestCase):
|
class PlaybackOptionsHandlerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_consume_off(self):
|
def test_consume_off(self):
|
||||||
result = self.h.handle_request(u'consume "0"')
|
result = self.h.handle_request(u'consume "0"')
|
||||||
self.assertFalse(self.b.playback.consume)
|
self.assertFalse(self.b.playback.consume.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_consume_off_without_quotes(self):
|
def test_consume_off_without_quotes(self):
|
||||||
result = self.h.handle_request(u'consume 0')
|
result = self.h.handle_request(u'consume 0')
|
||||||
self.assertFalse(self.b.playback.consume)
|
self.assertFalse(self.b.playback.consume.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_consume_on(self):
|
def test_consume_on(self):
|
||||||
result = self.h.handle_request(u'consume "1"')
|
result = self.h.handle_request(u'consume "1"')
|
||||||
self.assertTrue(self.b.playback.consume)
|
self.assertTrue(self.b.playback.consume.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_consume_on_without_quotes(self):
|
def test_consume_on_without_quotes(self):
|
||||||
result = self.h.handle_request(u'consume 1')
|
result = self.h.handle_request(u'consume 1')
|
||||||
self.assertTrue(self.b.playback.consume)
|
self.assertTrue(self.b.playback.consume.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_crossfade(self):
|
def test_crossfade(self):
|
||||||
@ -38,97 +48,97 @@ class PlaybackOptionsHandlerTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_random_off(self):
|
def test_random_off(self):
|
||||||
result = self.h.handle_request(u'random "0"')
|
result = self.h.handle_request(u'random "0"')
|
||||||
self.assertFalse(self.b.playback.random)
|
self.assertFalse(self.b.playback.random.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_random_off_without_quotes(self):
|
def test_random_off_without_quotes(self):
|
||||||
result = self.h.handle_request(u'random 0')
|
result = self.h.handle_request(u'random 0')
|
||||||
self.assertFalse(self.b.playback.random)
|
self.assertFalse(self.b.playback.random.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_random_on(self):
|
def test_random_on(self):
|
||||||
result = self.h.handle_request(u'random "1"')
|
result = self.h.handle_request(u'random "1"')
|
||||||
self.assertTrue(self.b.playback.random)
|
self.assertTrue(self.b.playback.random.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_random_on_without_quotes(self):
|
def test_random_on_without_quotes(self):
|
||||||
result = self.h.handle_request(u'random 1')
|
result = self.h.handle_request(u'random 1')
|
||||||
self.assertTrue(self.b.playback.random)
|
self.assertTrue(self.b.playback.random.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_repeat_off(self):
|
def test_repeat_off(self):
|
||||||
result = self.h.handle_request(u'repeat "0"')
|
result = self.h.handle_request(u'repeat "0"')
|
||||||
self.assertFalse(self.b.playback.repeat)
|
self.assertFalse(self.b.playback.repeat.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_repeat_off_without_quotes(self):
|
def test_repeat_off_without_quotes(self):
|
||||||
result = self.h.handle_request(u'repeat 0')
|
result = self.h.handle_request(u'repeat 0')
|
||||||
self.assertFalse(self.b.playback.repeat)
|
self.assertFalse(self.b.playback.repeat.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_repeat_on(self):
|
def test_repeat_on(self):
|
||||||
result = self.h.handle_request(u'repeat "1"')
|
result = self.h.handle_request(u'repeat "1"')
|
||||||
self.assertTrue(self.b.playback.repeat)
|
self.assertTrue(self.b.playback.repeat.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_repeat_on_without_quotes(self):
|
def test_repeat_on_without_quotes(self):
|
||||||
result = self.h.handle_request(u'repeat 1')
|
result = self.h.handle_request(u'repeat 1')
|
||||||
self.assertTrue(self.b.playback.repeat)
|
self.assertTrue(self.b.playback.repeat.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_setvol_below_min(self):
|
def test_setvol_below_min(self):
|
||||||
result = self.h.handle_request(u'setvol "-10"')
|
result = self.h.handle_request(u'setvol "-10"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(0, self.b.mixer.volume)
|
self.assertEqual(0, self.mixer.volume.get())
|
||||||
|
|
||||||
def test_setvol_min(self):
|
def test_setvol_min(self):
|
||||||
result = self.h.handle_request(u'setvol "0"')
|
result = self.h.handle_request(u'setvol "0"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(0, self.b.mixer.volume)
|
self.assertEqual(0, self.mixer.volume.get())
|
||||||
|
|
||||||
def test_setvol_middle(self):
|
def test_setvol_middle(self):
|
||||||
result = self.h.handle_request(u'setvol "50"')
|
result = self.h.handle_request(u'setvol "50"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(50, self.b.mixer.volume)
|
self.assertEqual(50, self.mixer.volume.get())
|
||||||
|
|
||||||
def test_setvol_max(self):
|
def test_setvol_max(self):
|
||||||
result = self.h.handle_request(u'setvol "100"')
|
result = self.h.handle_request(u'setvol "100"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(100, self.b.mixer.volume)
|
self.assertEqual(100, self.mixer.volume.get())
|
||||||
|
|
||||||
def test_setvol_above_max(self):
|
def test_setvol_above_max(self):
|
||||||
result = self.h.handle_request(u'setvol "110"')
|
result = self.h.handle_request(u'setvol "110"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(100, self.b.mixer.volume)
|
self.assertEqual(100, self.mixer.volume.get())
|
||||||
|
|
||||||
def test_setvol_plus_is_ignored(self):
|
def test_setvol_plus_is_ignored(self):
|
||||||
result = self.h.handle_request(u'setvol "+10"')
|
result = self.h.handle_request(u'setvol "+10"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(10, self.b.mixer.volume)
|
self.assertEqual(10, self.mixer.volume.get())
|
||||||
|
|
||||||
def test_setvol_without_quotes(self):
|
def test_setvol_without_quotes(self):
|
||||||
result = self.h.handle_request(u'setvol 50')
|
result = self.h.handle_request(u'setvol 50')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(50, self.b.mixer.volume)
|
self.assertEqual(50, self.mixer.volume.get())
|
||||||
|
|
||||||
def test_single_off(self):
|
def test_single_off(self):
|
||||||
result = self.h.handle_request(u'single "0"')
|
result = self.h.handle_request(u'single "0"')
|
||||||
self.assertFalse(self.b.playback.single)
|
self.assertFalse(self.b.playback.single.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_single_off_without_quotes(self):
|
def test_single_off_without_quotes(self):
|
||||||
result = self.h.handle_request(u'single 0')
|
result = self.h.handle_request(u'single 0')
|
||||||
self.assertFalse(self.b.playback.single)
|
self.assertFalse(self.b.playback.single.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_single_on(self):
|
def test_single_on(self):
|
||||||
result = self.h.handle_request(u'single "1"')
|
result = self.h.handle_request(u'single "1"')
|
||||||
self.assertTrue(self.b.playback.single)
|
self.assertTrue(self.b.playback.single.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_single_on_without_quotes(self):
|
def test_single_on_without_quotes(self):
|
||||||
result = self.h.handle_request(u'single 1')
|
result = self.h.handle_request(u'single 1')
|
||||||
self.assertTrue(self.b.playback.single)
|
self.assertTrue(self.b.playback.single.get())
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
|
|
||||||
def test_replay_gain_mode_off(self):
|
def test_replay_gain_mode_off(self):
|
||||||
@ -176,8 +186,13 @@ class PlaybackOptionsHandlerTest(unittest.TestCase):
|
|||||||
|
|
||||||
class PlaybackControlHandlerTest(unittest.TestCase):
|
class PlaybackControlHandlerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_next(self):
|
def test_next(self):
|
||||||
result = self.h.handle_request(u'next')
|
result = self.h.handle_request(u'next')
|
||||||
@ -189,155 +204,155 @@ class PlaybackControlHandlerTest(unittest.TestCase):
|
|||||||
self.h.handle_request(u'pause "1"')
|
self.h.handle_request(u'pause "1"')
|
||||||
result = self.h.handle_request(u'pause "0"')
|
result = self.h.handle_request(u'pause "0"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
|
|
||||||
def test_pause_on(self):
|
def test_pause_on(self):
|
||||||
self.b.current_playlist.append([Track()])
|
self.b.current_playlist.append([Track()])
|
||||||
self.h.handle_request(u'play "0"')
|
self.h.handle_request(u'play "0"')
|
||||||
result = self.h.handle_request(u'pause "1"')
|
result = self.h.handle_request(u'pause "1"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PAUSED, self.b.playback.state)
|
self.assertEqual(PAUSED, self.b.playback.state.get())
|
||||||
|
|
||||||
def test_pause_toggle(self):
|
def test_pause_toggle(self):
|
||||||
self.b.current_playlist.append([Track()])
|
self.b.current_playlist.append([Track()])
|
||||||
result = self.h.handle_request(u'play "0"')
|
result = self.h.handle_request(u'play "0"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
result = self.h.handle_request(u'pause')
|
result = self.h.handle_request(u'pause')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PAUSED, self.b.playback.state)
|
self.assertEqual(PAUSED, self.b.playback.state.get())
|
||||||
result = self.h.handle_request(u'pause')
|
result = self.h.handle_request(u'pause')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
|
|
||||||
def test_play_without_pos(self):
|
def test_play_without_pos(self):
|
||||||
self.b.current_playlist.append([Track()])
|
self.b.current_playlist.append([Track()])
|
||||||
self.b.playback.state = self.b.playback.PAUSED
|
self.b.playback.state = PAUSED
|
||||||
result = self.h.handle_request(u'play')
|
result = self.h.handle_request(u'play')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
|
|
||||||
def test_play_with_pos(self):
|
def test_play_with_pos(self):
|
||||||
self.b.current_playlist.append([Track()])
|
self.b.current_playlist.append([Track()])
|
||||||
result = self.h.handle_request(u'play "0"')
|
result = self.h.handle_request(u'play "0"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
|
|
||||||
def test_play_with_pos_without_quotes(self):
|
def test_play_with_pos_without_quotes(self):
|
||||||
self.b.current_playlist.append([Track()])
|
self.b.current_playlist.append([Track()])
|
||||||
result = self.h.handle_request(u'play 0')
|
result = self.h.handle_request(u'play 0')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
|
|
||||||
def test_play_with_pos_out_of_bounds(self):
|
def test_play_with_pos_out_of_bounds(self):
|
||||||
self.b.current_playlist.append([])
|
self.b.current_playlist.append([])
|
||||||
result = self.h.handle_request(u'play "0"')
|
result = self.h.handle_request(u'play "0"')
|
||||||
self.assertEqual(result[0], u'ACK [2@0] {play} Bad song index')
|
self.assertEqual(result[0], u'ACK [2@0] {play} Bad song index')
|
||||||
self.assertEqual(self.b.playback.STOPPED, self.b.playback.state)
|
self.assertEqual(STOPPED, self.b.playback.state.get())
|
||||||
|
|
||||||
def test_play_minus_one_plays_first_in_playlist_if_no_current_track(self):
|
def test_play_minus_one_plays_first_in_playlist_if_no_current_track(self):
|
||||||
self.assertEqual(self.b.playback.current_track, None)
|
self.assertEqual(self.b.playback.current_track.get(), None)
|
||||||
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
|
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
|
||||||
result = self.h.handle_request(u'play "-1"')
|
result = self.h.handle_request(u'play "-1"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
self.assertEqual(self.b.playback.current_track.uri, 'a')
|
self.assertEqual(self.b.playback.current_track.get().uri, 'a')
|
||||||
|
|
||||||
def test_play_minus_one_plays_current_track_if_current_track_is_set(self):
|
def test_play_minus_one_plays_current_track_if_current_track_is_set(self):
|
||||||
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
|
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
|
||||||
self.assertEqual(self.b.playback.current_track, None)
|
self.assertEqual(self.b.playback.current_track.get(), None)
|
||||||
self.b.playback.play()
|
self.b.playback.play()
|
||||||
self.b.playback.next()
|
self.b.playback.next()
|
||||||
self.b.playback.stop()
|
self.b.playback.stop()
|
||||||
self.assertNotEqual(self.b.playback.current_track, None)
|
self.assertNotEqual(self.b.playback.current_track.get(), None)
|
||||||
result = self.h.handle_request(u'play "-1"')
|
result = self.h.handle_request(u'play "-1"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
self.assertEqual(self.b.playback.current_track.uri, 'b')
|
self.assertEqual(self.b.playback.current_track.get().uri, 'b')
|
||||||
|
|
||||||
def test_play_minus_one_on_empty_playlist_does_not_ack(self):
|
def test_play_minus_one_on_empty_playlist_does_not_ack(self):
|
||||||
self.b.current_playlist.clear()
|
self.b.current_playlist.clear()
|
||||||
result = self.h.handle_request(u'play "-1"')
|
result = self.h.handle_request(u'play "-1"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.STOPPED, self.b.playback.state)
|
self.assertEqual(STOPPED, self.b.playback.state.get())
|
||||||
self.assertEqual(self.b.playback.current_track, None)
|
self.assertEqual(self.b.playback.current_track.get(), None)
|
||||||
|
|
||||||
def test_play_minus_is_ignored_if_playing(self):
|
def test_play_minus_is_ignored_if_playing(self):
|
||||||
self.b.current_playlist.append([Track(length=40000)])
|
self.b.current_playlist.append([Track(length=40000)])
|
||||||
self.b.playback.seek(30000)
|
self.b.playback.seek(30000)
|
||||||
self.assert_(self.b.playback.time_position >= 30000)
|
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||||
self.assertEquals(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEquals(PLAYING, self.b.playback.state.get())
|
||||||
result = self.h.handle_request(u'play "-1"')
|
result = self.h.handle_request(u'play "-1"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
self.assert_(self.b.playback.time_position >= 30000)
|
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||||
|
|
||||||
def test_play_minus_one_resumes_if_paused(self):
|
def test_play_minus_one_resumes_if_paused(self):
|
||||||
self.b.current_playlist.append([Track(length=40000)])
|
self.b.current_playlist.append([Track(length=40000)])
|
||||||
self.b.playback.seek(30000)
|
self.b.playback.seek(30000)
|
||||||
self.assert_(self.b.playback.time_position >= 30000)
|
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||||
self.assertEquals(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEquals(PLAYING, self.b.playback.state.get())
|
||||||
self.b.playback.pause()
|
self.b.playback.pause()
|
||||||
self.assertEquals(self.b.playback.PAUSED, self.b.playback.state)
|
self.assertEquals(PAUSED, self.b.playback.state.get())
|
||||||
result = self.h.handle_request(u'play "-1"')
|
result = self.h.handle_request(u'play "-1"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
self.assert_(self.b.playback.time_position >= 30000)
|
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||||
|
|
||||||
def test_playid(self):
|
def test_playid(self):
|
||||||
self.b.current_playlist.append([Track()])
|
self.b.current_playlist.append([Track()])
|
||||||
result = self.h.handle_request(u'playid "0"')
|
result = self.h.handle_request(u'playid "0"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
|
|
||||||
def test_playid_minus_one_plays_first_in_playlist_if_no_current_track(self):
|
def test_playid_minus_one_plays_first_in_playlist_if_no_current_track(self):
|
||||||
self.assertEqual(self.b.playback.current_track, None)
|
self.assertEqual(self.b.playback.current_track.get(), None)
|
||||||
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
|
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
|
||||||
result = self.h.handle_request(u'playid "-1"')
|
result = self.h.handle_request(u'playid "-1"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
self.assertEqual(self.b.playback.current_track.uri, 'a')
|
self.assertEqual(self.b.playback.current_track.get().uri, 'a')
|
||||||
|
|
||||||
def test_playid_minus_one_plays_current_track_if_current_track_is_set(self):
|
def test_playid_minus_one_plays_current_track_if_current_track_is_set(self):
|
||||||
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
|
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
|
||||||
self.assertEqual(self.b.playback.current_track, None)
|
self.assertEqual(self.b.playback.current_track.get(), None)
|
||||||
self.b.playback.play()
|
self.b.playback.play()
|
||||||
self.b.playback.next()
|
self.b.playback.next()
|
||||||
self.b.playback.stop()
|
self.b.playback.stop()
|
||||||
self.assertNotEqual(self.b.playback.current_track, None)
|
self.assertNotEqual(self.b.playback.current_track.get(), None)
|
||||||
result = self.h.handle_request(u'playid "-1"')
|
result = self.h.handle_request(u'playid "-1"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
self.assertEqual(self.b.playback.current_track.uri, 'b')
|
self.assertEqual(self.b.playback.current_track.get().uri, 'b')
|
||||||
|
|
||||||
def test_playid_minus_one_on_empty_playlist_does_not_ack(self):
|
def test_playid_minus_one_on_empty_playlist_does_not_ack(self):
|
||||||
self.b.current_playlist.clear()
|
self.b.current_playlist.clear()
|
||||||
result = self.h.handle_request(u'playid "-1"')
|
result = self.h.handle_request(u'playid "-1"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.STOPPED, self.b.playback.state)
|
self.assertEqual(STOPPED, self.b.playback.state.get())
|
||||||
self.assertEqual(self.b.playback.current_track, None)
|
self.assertEqual(self.b.playback.current_track.get(), None)
|
||||||
|
|
||||||
def test_playid_minus_is_ignored_if_playing(self):
|
def test_playid_minus_is_ignored_if_playing(self):
|
||||||
self.b.current_playlist.append([Track(length=40000)])
|
self.b.current_playlist.append([Track(length=40000)])
|
||||||
self.b.playback.seek(30000)
|
self.b.playback.seek(30000)
|
||||||
self.assert_(self.b.playback.time_position >= 30000)
|
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||||
self.assertEquals(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEquals(PLAYING, self.b.playback.state.get())
|
||||||
result = self.h.handle_request(u'playid "-1"')
|
result = self.h.handle_request(u'playid "-1"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
self.assert_(self.b.playback.time_position >= 30000)
|
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||||
|
|
||||||
def test_playid_minus_one_resumes_if_paused(self):
|
def test_playid_minus_one_resumes_if_paused(self):
|
||||||
self.b.current_playlist.append([Track(length=40000)])
|
self.b.current_playlist.append([Track(length=40000)])
|
||||||
self.b.playback.seek(30000)
|
self.b.playback.seek(30000)
|
||||||
self.assert_(self.b.playback.time_position >= 30000)
|
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||||
self.assertEquals(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEquals(PLAYING, self.b.playback.state.get())
|
||||||
self.b.playback.pause()
|
self.b.playback.pause()
|
||||||
self.assertEquals(self.b.playback.PAUSED, self.b.playback.state)
|
self.assertEquals(PAUSED, self.b.playback.state.get())
|
||||||
result = self.h.handle_request(u'playid "-1"')
|
result = self.h.handle_request(u'playid "-1"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||||
self.assert_(self.b.playback.time_position >= 30000)
|
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||||
|
|
||||||
def test_playid_which_does_not_exist(self):
|
def test_playid_which_does_not_exist(self):
|
||||||
self.b.current_playlist.append([Track()])
|
self.b.current_playlist.append([Track()])
|
||||||
@ -361,20 +376,20 @@ class PlaybackControlHandlerTest(unittest.TestCase):
|
|||||||
[Track(uri='1', length=40000), seek_track])
|
[Track(uri='1', length=40000), seek_track])
|
||||||
result = self.h.handle_request(u'seek "1" "30"')
|
result = self.h.handle_request(u'seek "1" "30"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.current_track, seek_track)
|
self.assertEqual(self.b.playback.current_track.get(), seek_track)
|
||||||
|
|
||||||
def test_seek_without_quotes(self):
|
def test_seek_without_quotes(self):
|
||||||
self.b.current_playlist.append([Track(length=40000)])
|
self.b.current_playlist.append([Track(length=40000)])
|
||||||
self.h.handle_request(u'seek 0')
|
self.h.handle_request(u'seek 0')
|
||||||
result = self.h.handle_request(u'seek 0 30')
|
result = self.h.handle_request(u'seek 0 30')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assert_(self.b.playback.time_position >= 30000)
|
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||||
|
|
||||||
def test_seekid(self):
|
def test_seekid(self):
|
||||||
self.b.current_playlist.append([Track(length=40000)])
|
self.b.current_playlist.append([Track(length=40000)])
|
||||||
result = self.h.handle_request(u'seekid "0" "30"')
|
result = self.h.handle_request(u'seekid "0" "30"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assert_(self.b.playback.time_position >= 30000)
|
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||||
|
|
||||||
def test_seekid_with_cpid(self):
|
def test_seekid_with_cpid(self):
|
||||||
seek_track = Track(uri='2', length=40000)
|
seek_track = Track(uri='2', length=40000)
|
||||||
@ -382,10 +397,10 @@ class PlaybackControlHandlerTest(unittest.TestCase):
|
|||||||
[Track(length=40000), seek_track])
|
[Track(length=40000), seek_track])
|
||||||
result = self.h.handle_request(u'seekid "1" "30"')
|
result = self.h.handle_request(u'seekid "1" "30"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.current_cpid, 1)
|
self.assertEqual(self.b.playback.current_cpid.get(), 1)
|
||||||
self.assertEqual(self.b.playback.current_track, seek_track)
|
self.assertEqual(self.b.playback.current_track.get(), seek_track)
|
||||||
|
|
||||||
def test_stop(self):
|
def test_stop(self):
|
||||||
result = self.h.handle_request(u'stop')
|
result = self.h.handle_request(u'stop')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(self.b.playback.STOPPED, self.b.playback.state)
|
self.assertEqual(STOPPED, self.b.playback.state.get())
|
||||||
|
|||||||
@ -6,8 +6,13 @@ from mopidy.mixers.dummy import DummyMixer
|
|||||||
|
|
||||||
class ReflectionHandlerTest(unittest.TestCase):
|
class ReflectionHandlerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_commands_returns_list_of_all_commands(self):
|
def test_commands_returns_list_of_all_commands(self):
|
||||||
result = self.h.handle_request(u'commands')
|
result = self.h.handle_request(u'commands')
|
||||||
|
|||||||
@ -18,26 +18,31 @@ class IssueGH17RegressionTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.backend = DummyBackend(mixer_class=DummyMixer)
|
self.backend = DummyBackend.start().proxy()
|
||||||
self.backend.current_playlist.append([
|
self.backend.current_playlist.append([
|
||||||
Track(uri='a'), Track(uri='b'), None,
|
Track(uri='a'), Track(uri='b'), None,
|
||||||
Track(uri='d'), Track(uri='e'), Track(uri='f')])
|
Track(uri='d'), Track(uri='e'), Track(uri='f')])
|
||||||
self.mpd = dispatcher.MpdDispatcher(backend=self.backend)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.mpd = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.backend.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
random.seed(1) # Playlist order: abcfde
|
random.seed(1) # Playlist order: abcfde
|
||||||
self.mpd.handle_request(u'play')
|
self.mpd.handle_request(u'play')
|
||||||
self.assertEquals('a', self.backend.playback.current_track.uri)
|
self.assertEquals('a', self.backend.playback.current_track.get().uri)
|
||||||
self.mpd.handle_request(u'random "1"')
|
self.mpd.handle_request(u'random "1"')
|
||||||
self.mpd.handle_request(u'next')
|
self.mpd.handle_request(u'next')
|
||||||
self.assertEquals('b', self.backend.playback.current_track.uri)
|
self.assertEquals('b', self.backend.playback.current_track.get().uri)
|
||||||
self.mpd.handle_request(u'next')
|
self.mpd.handle_request(u'next')
|
||||||
# Should now be at track 'c', but playback fails and it skips ahead
|
# Should now be at track 'c', but playback fails and it skips ahead
|
||||||
self.assertEquals('f', self.backend.playback.current_track.uri)
|
self.assertEquals('f', self.backend.playback.current_track.get().uri)
|
||||||
self.mpd.handle_request(u'next')
|
self.mpd.handle_request(u'next')
|
||||||
self.assertEquals('d', self.backend.playback.current_track.uri)
|
self.assertEquals('d', self.backend.playback.current_track.get().uri)
|
||||||
self.mpd.handle_request(u'next')
|
self.mpd.handle_request(u'next')
|
||||||
self.assertEquals('e', self.backend.playback.current_track.uri)
|
self.assertEquals('e', self.backend.playback.current_track.get().uri)
|
||||||
|
|
||||||
|
|
||||||
class IssueGH18RegressionTest(unittest.TestCase):
|
class IssueGH18RegressionTest(unittest.TestCase):
|
||||||
@ -52,11 +57,16 @@ class IssueGH18RegressionTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.backend = DummyBackend(mixer_class=DummyMixer)
|
self.backend = DummyBackend.start().proxy()
|
||||||
self.backend.current_playlist.append([
|
self.backend.current_playlist.append([
|
||||||
Track(uri='a'), Track(uri='b'), Track(uri='c'),
|
Track(uri='a'), Track(uri='b'), Track(uri='c'),
|
||||||
Track(uri='d'), Track(uri='e'), Track(uri='f')])
|
Track(uri='d'), Track(uri='e'), Track(uri='f')])
|
||||||
self.mpd = dispatcher.MpdDispatcher(backend=self.backend)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.mpd = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.backend.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
random.seed(1)
|
random.seed(1)
|
||||||
@ -67,11 +77,11 @@ class IssueGH18RegressionTest(unittest.TestCase):
|
|||||||
self.mpd.handle_request(u'next')
|
self.mpd.handle_request(u'next')
|
||||||
|
|
||||||
self.mpd.handle_request(u'next')
|
self.mpd.handle_request(u'next')
|
||||||
cp_track_1 = self.backend.playback.current_cp_track
|
cp_track_1 = self.backend.playback.current_cp_track.get()
|
||||||
self.mpd.handle_request(u'next')
|
self.mpd.handle_request(u'next')
|
||||||
cp_track_2 = self.backend.playback.current_cp_track
|
cp_track_2 = self.backend.playback.current_cp_track.get()
|
||||||
self.mpd.handle_request(u'next')
|
self.mpd.handle_request(u'next')
|
||||||
cp_track_3 = self.backend.playback.current_cp_track
|
cp_track_3 = self.backend.playback.current_cp_track.get()
|
||||||
|
|
||||||
self.assertNotEqual(cp_track_1, cp_track_2)
|
self.assertNotEqual(cp_track_1, cp_track_2)
|
||||||
self.assertNotEqual(cp_track_2, cp_track_3)
|
self.assertNotEqual(cp_track_2, cp_track_3)
|
||||||
@ -91,11 +101,16 @@ class IssueGH22RegressionTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.backend = DummyBackend(mixer_class=DummyMixer)
|
self.backend = DummyBackend.start().proxy()
|
||||||
self.backend.current_playlist.append([
|
self.backend.current_playlist.append([
|
||||||
Track(uri='a'), Track(uri='b'), Track(uri='c'),
|
Track(uri='a'), Track(uri='b'), Track(uri='c'),
|
||||||
Track(uri='d'), Track(uri='e'), Track(uri='f')])
|
Track(uri='d'), Track(uri='e'), Track(uri='f')])
|
||||||
self.mpd = dispatcher.MpdDispatcher(backend=self.backend)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.mpd = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.backend.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
random.seed(1)
|
random.seed(1)
|
||||||
|
|||||||
@ -1,11 +1,19 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from mopidy import settings
|
from mopidy import settings
|
||||||
|
from mopidy.backends.dummy import DummyBackend
|
||||||
from mopidy.frontends.mpd import server
|
from mopidy.frontends.mpd import server
|
||||||
|
from mopidy.mixers.dummy import DummyMixer
|
||||||
|
|
||||||
class MpdServerTest(unittest.TestCase):
|
class MpdServerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.server = server.MpdServer(None)
|
self.backend = DummyBackend.start().proxy()
|
||||||
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.server = server.MpdServer()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.backend.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_format_hostname_prefixes_ipv4_addresses_when_ipv6_available(self):
|
def test_format_hostname_prefixes_ipv4_addresses_when_ipv6_available(self):
|
||||||
server.socket.has_ipv6 = True
|
server.socket.has_ipv6 = True
|
||||||
@ -20,9 +28,13 @@ class MpdServerTest(unittest.TestCase):
|
|||||||
|
|
||||||
class MpdSessionTest(unittest.TestCase):
|
class MpdSessionTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.session = server.MpdSession(None, None, (None, None), None)
|
self.backend = DummyBackend.start().proxy()
|
||||||
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.session = server.MpdSession(None, None, (None, None))
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
self.backend.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
settings.runtime.clear()
|
settings.runtime.clear()
|
||||||
|
|
||||||
def test_found_terminator_catches_decode_error(self):
|
def test_found_terminator_catches_decode_error(self):
|
||||||
|
|||||||
@ -1,14 +1,24 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from mopidy.backends.base import PlaybackController
|
||||||
from mopidy.backends.dummy import DummyBackend
|
from mopidy.backends.dummy import DummyBackend
|
||||||
from mopidy.frontends.mpd import dispatcher
|
from mopidy.frontends.mpd import dispatcher
|
||||||
from mopidy.mixers.dummy import DummyMixer
|
from mopidy.mixers.dummy import DummyMixer
|
||||||
from mopidy.models import Track
|
from mopidy.models import Track
|
||||||
|
|
||||||
|
PAUSED = PlaybackController.PAUSED
|
||||||
|
PLAYING = PlaybackController.PLAYING
|
||||||
|
STOPPED = PlaybackController.STOPPED
|
||||||
|
|
||||||
class StatusHandlerTest(unittest.TestCase):
|
class StatusHandlerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_clearerror(self):
|
def test_clearerror(self):
|
||||||
result = self.h.handle_request(u'clearerror')
|
result = self.h.handle_request(u'clearerror')
|
||||||
@ -77,7 +87,7 @@ class StatusHandlerTest(unittest.TestCase):
|
|||||||
self.assertEqual(int(result['volume']), 0)
|
self.assertEqual(int(result['volume']), 0)
|
||||||
|
|
||||||
def test_status_method_contains_volume(self):
|
def test_status_method_contains_volume(self):
|
||||||
self.b.mixer.volume = 17
|
self.mixer.volume = 17
|
||||||
result = dict(dispatcher.status.status(self.h))
|
result = dict(dispatcher.status.status(self.h))
|
||||||
self.assert_('volume' in result)
|
self.assert_('volume' in result)
|
||||||
self.assertEqual(int(result['volume']), 17)
|
self.assertEqual(int(result['volume']), 17)
|
||||||
@ -136,20 +146,20 @@ class StatusHandlerTest(unittest.TestCase):
|
|||||||
self.assert_(int(result['xfade']) >= 0)
|
self.assert_(int(result['xfade']) >= 0)
|
||||||
|
|
||||||
def test_status_method_contains_state_is_play(self):
|
def test_status_method_contains_state_is_play(self):
|
||||||
self.b.playback.state = self.b.playback.PLAYING
|
self.b.playback.state = PLAYING
|
||||||
result = dict(dispatcher.status.status(self.h))
|
result = dict(dispatcher.status.status(self.h))
|
||||||
self.assert_('state' in result)
|
self.assert_('state' in result)
|
||||||
self.assertEqual(result['state'], 'play')
|
self.assertEqual(result['state'], 'play')
|
||||||
|
|
||||||
def test_status_method_contains_state_is_stop(self):
|
def test_status_method_contains_state_is_stop(self):
|
||||||
self.b.playback.state = self.b.playback.STOPPED
|
self.b.playback.state = STOPPED
|
||||||
result = dict(dispatcher.status.status(self.h))
|
result = dict(dispatcher.status.status(self.h))
|
||||||
self.assert_('state' in result)
|
self.assert_('state' in result)
|
||||||
self.assertEqual(result['state'], 'stop')
|
self.assertEqual(result['state'], 'stop')
|
||||||
|
|
||||||
def test_status_method_contains_state_is_pause(self):
|
def test_status_method_contains_state_is_pause(self):
|
||||||
self.b.playback.state = self.b.playback.PLAYING
|
self.b.playback.state = PLAYING
|
||||||
self.b.playback.state = self.b.playback.PAUSED
|
self.b.playback.state = PAUSED
|
||||||
result = dict(dispatcher.status.status(self.h))
|
result = dict(dispatcher.status.status(self.h))
|
||||||
self.assert_('state' in result)
|
self.assert_('state' in result)
|
||||||
self.assertEqual(result['state'], 'pause')
|
self.assertEqual(result['state'], 'pause')
|
||||||
@ -189,8 +199,8 @@ class StatusHandlerTest(unittest.TestCase):
|
|||||||
self.assert_(position <= total)
|
self.assert_(position <= total)
|
||||||
|
|
||||||
def test_status_method_when_playing_contains_elapsed(self):
|
def test_status_method_when_playing_contains_elapsed(self):
|
||||||
self.b.playback.state = self.b.playback.PAUSED
|
self.b.playback.state = PAUSED
|
||||||
self.b.playback._play_time_accumulated = 59123
|
self.b.playback.play_time_accumulated = 59123
|
||||||
result = dict(dispatcher.status.status(self.h))
|
result = dict(dispatcher.status.status(self.h))
|
||||||
self.assert_('elapsed' in result)
|
self.assert_('elapsed' in result)
|
||||||
self.assertEqual(int(result['elapsed']), 59123)
|
self.assertEqual(int(result['elapsed']), 59123)
|
||||||
|
|||||||
@ -6,8 +6,13 @@ from mopidy.mixers.dummy import DummyMixer
|
|||||||
|
|
||||||
class StickersHandlerTest(unittest.TestCase):
|
class StickersHandlerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_sticker_get(self):
|
def test_sticker_get(self):
|
||||||
result = self.h.handle_request(
|
result = self.h.handle_request(
|
||||||
|
|||||||
@ -8,8 +8,13 @@ from mopidy.models import Track, Playlist
|
|||||||
|
|
||||||
class StoredPlaylistsHandlerTest(unittest.TestCase):
|
class StoredPlaylistsHandlerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
self.b = DummyBackend.start().proxy()
|
||||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
self.mixer = DummyMixer.start().proxy()
|
||||||
|
self.h = dispatcher.MpdDispatcher()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.b.stop().get()
|
||||||
|
self.mixer.stop().get()
|
||||||
|
|
||||||
def test_listplaylist(self):
|
def test_listplaylist(self):
|
||||||
self.b.stored_playlists.playlists = [
|
self.b.stored_playlists.playlists = [
|
||||||
@ -49,22 +54,23 @@ class StoredPlaylistsHandlerTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_load_known_playlist_appends_to_current_playlist(self):
|
def test_load_known_playlist_appends_to_current_playlist(self):
|
||||||
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
|
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 2)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 2)
|
||||||
self.b.stored_playlists.playlists = [Playlist(name='A-list',
|
self.b.stored_playlists.playlists = [Playlist(name='A-list',
|
||||||
tracks=[Track(uri='c'), Track(uri='d'), Track(uri='e')])]
|
tracks=[Track(uri='c'), Track(uri='d'), Track(uri='e')])]
|
||||||
result = self.h.handle_request(u'load "A-list"')
|
result = self.h.handle_request(u'load "A-list"')
|
||||||
self.assert_(u'OK' in result)
|
self.assert_(u'OK' in result)
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
tracks = self.b.current_playlist.tracks.get()
|
||||||
self.assertEqual(self.b.current_playlist.tracks[0].uri, 'a')
|
self.assertEqual(len(tracks), 5)
|
||||||
self.assertEqual(self.b.current_playlist.tracks[1].uri, 'b')
|
self.assertEqual(tracks[0].uri, 'a')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[2].uri, 'c')
|
self.assertEqual(tracks[1].uri, 'b')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[3].uri, 'd')
|
self.assertEqual(tracks[2].uri, 'c')
|
||||||
self.assertEqual(self.b.current_playlist.tracks[4].uri, 'e')
|
self.assertEqual(tracks[3].uri, 'd')
|
||||||
|
self.assertEqual(tracks[4].uri, 'e')
|
||||||
|
|
||||||
def test_load_unknown_playlist_acks(self):
|
def test_load_unknown_playlist_acks(self):
|
||||||
result = self.h.handle_request(u'load "unknown playlist"')
|
result = self.h.handle_request(u'load "unknown playlist"')
|
||||||
self.assert_(u'ACK [50@0] {load} No such playlist' in result)
|
self.assert_(u'ACK [50@0] {load} No such playlist' in result)
|
||||||
self.assertEqual(len(self.b.current_playlist.tracks), 0)
|
self.assertEqual(len(self.b.current_playlist.tracks.get()), 0)
|
||||||
|
|
||||||
def test_playlistadd(self):
|
def test_playlistadd(self):
|
||||||
result = self.h.handle_request(
|
result = self.h.handle_request(
|
||||||
|
|||||||
@ -11,7 +11,7 @@ class BaseMixerTest(object):
|
|||||||
assert self.mixer_class is not None, \
|
assert self.mixer_class is not None, \
|
||||||
"mixer_class must be set in subclass"
|
"mixer_class must be set in subclass"
|
||||||
# pylint: disable = E1102
|
# pylint: disable = E1102
|
||||||
self.mixer = self.mixer_class(None)
|
self.mixer = self.mixer_class()
|
||||||
# pylint: enable = E1102
|
# pylint: enable = E1102
|
||||||
|
|
||||||
def test_initial_volume(self):
|
def test_initial_volume(self):
|
||||||
|
|||||||
@ -18,12 +18,10 @@ class GStreamerOutputTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
settings.BACKENDS = ('mopidy.backends.local.LocalBackend',)
|
settings.BACKENDS = ('mopidy.backends.local.LocalBackend',)
|
||||||
self.song_uri = path_to_uri(data_folder('song1.wav'))
|
self.song_uri = path_to_uri(data_folder('song1.wav'))
|
||||||
self.core_queue = multiprocessing.Queue()
|
self.output = GStreamerOutput()
|
||||||
self.output = GStreamerOutput(self.core_queue)
|
self.output.on_start()
|
||||||
self.output.start()
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.output.destroy()
|
|
||||||
settings.runtime.clear()
|
settings.runtime.clear()
|
||||||
|
|
||||||
def test_play_uri_existing_file(self):
|
def test_play_uri_existing_file(self):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user