Merge branch 'feature/pykka-actors' into develop
This commit is contained in:
commit
be1e0fa819
@ -1,5 +1,5 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
if __name__ == '__main__':
|
||||
from mopidy.__main__ import main
|
||||
from mopidy.core import main
|
||||
main()
|
||||
|
||||
@ -74,11 +74,11 @@ Running tests
|
||||
To run tests, you need a couple of dependencies. They can be installed through
|
||||
Debian/Ubuntu package management::
|
||||
|
||||
sudo aptitude install python-coverage python-nose
|
||||
sudo aptitude install python-coverage python-mock python-nose
|
||||
|
||||
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::
|
||||
|
||||
|
||||
@ -25,6 +25,8 @@ Otherwise, make sure you got the required dependencies installed.
|
||||
|
||||
- Python >= 2.6, < 3
|
||||
|
||||
- `Pykka <http://jodal.github.com/pykka/>`_ >= 0.12
|
||||
|
||||
- GStreamer >= 0.10, with Python bindings. See :doc:`gstreamer`.
|
||||
|
||||
- Mixer dependencies: The default mixer does not require any additional
|
||||
|
||||
@ -9,7 +9,7 @@ VERSION = (0, 4, 0)
|
||||
def get_git_version():
|
||||
process = Popen(['git', 'describe'], stdout=PIPE, stderr=PIPE)
|
||||
if process.wait() != 0:
|
||||
raise Exception('Execution of "git describe" failed')
|
||||
raise EnvironmentError('Execution of "git describe" failed')
|
||||
version = process.stdout.read().strip()
|
||||
if version.startswith('v'):
|
||||
version = version[1:]
|
||||
@ -21,7 +21,7 @@ def get_plain_version():
|
||||
def get_version():
|
||||
try:
|
||||
return get_git_version()
|
||||
except Exception:
|
||||
except EnvironmentError:
|
||||
return get_plain_version()
|
||||
|
||||
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
|
||||
# installing it on the system.
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0,
|
||||
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__':
|
||||
from mopidy.core import main
|
||||
main()
|
||||
|
||||
@ -1,12 +1,4 @@
|
||||
from copy import copy
|
||||
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 .library import LibraryController, BaseLibraryProvider
|
||||
@ -17,30 +9,6 @@ from .stored_playlists import (StoredPlaylistsController,
|
||||
logger = logging.getLogger('mopidy.backends.base')
|
||||
|
||||
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
|
||||
#: :class:`mopidy.backends.base.CurrentPlaylistController`.
|
||||
current_playlist = None
|
||||
@ -49,9 +17,6 @@ class Backend(object):
|
||||
# :class:`mopidy.backends.base.LibraryController`.
|
||||
library = None
|
||||
|
||||
#: The sound mixer. An instance of :class:`mopidy.mixers.BaseMixer`.
|
||||
mixer = None
|
||||
|
||||
#: The playback controller. An instance of
|
||||
#: :class:`mopidy.backends.base.PlaybackController`.
|
||||
playback = None
|
||||
@ -62,24 +27,3 @@ class Backend(object):
|
||||
|
||||
#: List of URI prefixes this backend can handle.
|
||||
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 random
|
||||
|
||||
from mopidy.frontends.mpd import translator
|
||||
|
||||
logger = logging.getLogger('mopidy.backends.base')
|
||||
|
||||
class CurrentPlaylistController(object):
|
||||
@ -12,6 +10,8 @@ class CurrentPlaylistController(object):
|
||||
:type backend: :class:`mopidy.backends.base.Backend`
|
||||
"""
|
||||
|
||||
pykka_traversable = True
|
||||
|
||||
def __init__(self, backend):
|
||||
self.backend = backend
|
||||
self._cp_tracks = []
|
||||
@ -197,8 +197,3 @@ class CurrentPlaylistController(object):
|
||||
random.shuffle(shuffled)
|
||||
self._cp_tracks = before + shuffled + after
|
||||
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`
|
||||
"""
|
||||
|
||||
pykka_traversable = True
|
||||
|
||||
def __init__(self, backend, provider):
|
||||
self.backend = backend
|
||||
self.provider = provider
|
||||
@ -82,6 +84,8 @@ class BaseLibraryProvider(object):
|
||||
:type backend: :class:`mopidy.backends.base.Backend`
|
||||
"""
|
||||
|
||||
pykka_traversable = True
|
||||
|
||||
def __init__(self, backend):
|
||||
self.backend = backend
|
||||
|
||||
|
||||
@ -2,6 +2,10 @@ import logging
|
||||
import random
|
||||
import time
|
||||
|
||||
from pykka.registry import ActorRegistry
|
||||
|
||||
from mopidy.frontends.base import BaseFrontend
|
||||
|
||||
logger = logging.getLogger('mopidy.backends.base')
|
||||
|
||||
class PlaybackController(object):
|
||||
@ -15,6 +19,8 @@ class PlaybackController(object):
|
||||
# pylint: disable = R0902
|
||||
# Too many instance attributes
|
||||
|
||||
pykka_traversable = True
|
||||
|
||||
#: Constant representing the paused state.
|
||||
PAUSED = u'paused'
|
||||
|
||||
@ -62,8 +68,8 @@ class PlaybackController(object):
|
||||
self._state = self.STOPPED
|
||||
self._shuffled = []
|
||||
self._first_shuffle = True
|
||||
self._play_time_accumulated = 0
|
||||
self._play_time_started = None
|
||||
self.play_time_accumulated = 0
|
||||
self.play_time_started = None
|
||||
|
||||
def destroy(self):
|
||||
"""
|
||||
@ -269,7 +275,7 @@ class PlaybackController(object):
|
||||
def state(self, new_state):
|
||||
(old_state, self._state) = (self.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 :/
|
||||
if (old_state in (self.PLAYING, self.STOPPED)
|
||||
and new_state == self.PLAYING):
|
||||
@ -284,23 +290,23 @@ class PlaybackController(object):
|
||||
"""Time position in milliseconds."""
|
||||
if self.state == self.PLAYING:
|
||||
time_since_started = (self._current_wall_time -
|
||||
self._play_time_started)
|
||||
return self._play_time_accumulated + time_since_started
|
||||
self.play_time_started)
|
||||
return self.play_time_accumulated + time_since_started
|
||||
elif self.state == self.PAUSED:
|
||||
return self._play_time_accumulated
|
||||
return self.play_time_accumulated
|
||||
elif self.state == self.STOPPED:
|
||||
return 0
|
||||
|
||||
def _play_time_start(self):
|
||||
self._play_time_accumulated = 0
|
||||
self._play_time_started = self._current_wall_time
|
||||
self.play_time_accumulated = 0
|
||||
self.play_time_started = self._current_wall_time
|
||||
|
||||
def _play_time_pause(self):
|
||||
time_since_started = self._current_wall_time - self._play_time_started
|
||||
self._play_time_accumulated += time_since_started
|
||||
time_since_started = self._current_wall_time - self.play_time_started
|
||||
self.play_time_accumulated += time_since_started
|
||||
|
||||
def _play_time_resume(self):
|
||||
self._play_time_started = self._current_wall_time
|
||||
self.play_time_started = self._current_wall_time
|
||||
|
||||
@property
|
||||
def _current_wall_time(self):
|
||||
@ -433,8 +439,8 @@ class PlaybackController(object):
|
||||
self.next()
|
||||
return True
|
||||
|
||||
self._play_time_started = self._current_wall_time
|
||||
self._play_time_accumulated = time_position
|
||||
self.play_time_started = self._current_wall_time
|
||||
self.play_time_accumulated = 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
|
||||
track has started playing.
|
||||
"""
|
||||
if self.current_track is not None:
|
||||
self.backend.core_queue.put({
|
||||
'to': 'frontend',
|
||||
if self.current_track is None:
|
||||
return
|
||||
frontend_refs = ActorRegistry.get_by_class(BaseFrontend)
|
||||
for frontend_ref in frontend_refs:
|
||||
frontend_ref.send_one_way({
|
||||
'command': 'started_playing',
|
||||
'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
|
||||
end-of-track.
|
||||
"""
|
||||
if self.current_track is not None:
|
||||
self.backend.core_queue.put({
|
||||
'to': 'frontend',
|
||||
if self.current_track is None:
|
||||
return
|
||||
frontend_refs = ActorRegistry.get_by_class(BaseFrontend)
|
||||
for frontend_ref in frontend_refs:
|
||||
frontend_ref.send_one_way({
|
||||
'command': 'stopped_playing',
|
||||
'track': self.current_track,
|
||||
'stop_position': self.time_position,
|
||||
@ -491,6 +501,8 @@ class BasePlaybackProvider(object):
|
||||
:type backend: :class:`mopidy.backends.base.Backend`
|
||||
"""
|
||||
|
||||
pykka_traversable = True
|
||||
|
||||
def __init__(self, backend):
|
||||
self.backend = backend
|
||||
|
||||
|
||||
@ -11,6 +11,8 @@ class StoredPlaylistsController(object):
|
||||
:type provider: instance of :class:`BaseStoredPlaylistsProvider`
|
||||
"""
|
||||
|
||||
pykka_traversable = True
|
||||
|
||||
def __init__(self, backend, provider):
|
||||
self.backend = backend
|
||||
self.provider = provider
|
||||
@ -125,6 +127,8 @@ class BaseStoredPlaylistsProvider(object):
|
||||
:type backend: :class:`mopidy.backends.base.Backend`
|
||||
"""
|
||||
|
||||
pykka_traversable = True
|
||||
|
||||
def __init__(self, backend):
|
||||
self.backend = backend
|
||||
self._playlists = []
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy.backends.base import (Backend, CurrentPlaylistController,
|
||||
PlaybackController, BasePlaybackProvider, LibraryController,
|
||||
BaseLibraryProvider, StoredPlaylistsController,
|
||||
@ -5,15 +7,7 @@ from mopidy.backends.base import (Backend, CurrentPlaylistController,
|
||||
from mopidy.models import Playlist
|
||||
|
||||
|
||||
class DummyQueue(object):
|
||||
def __init__(self):
|
||||
self.received_messages = []
|
||||
|
||||
def put(self, message):
|
||||
self.received_messages.append(message)
|
||||
|
||||
|
||||
class DummyBackend(Backend):
|
||||
class DummyBackend(ThreadingActor, Backend):
|
||||
"""
|
||||
A backend which implements the backend API in the simplest way possible.
|
||||
Used in tests of the frontends.
|
||||
@ -24,8 +18,6 @@ class DummyBackend(Backend):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DummyBackend, self).__init__(*args, **kwargs)
|
||||
|
||||
self.core_queue = DummyQueue()
|
||||
|
||||
self.current_playlist = CurrentPlaylistController(backend=self)
|
||||
|
||||
library_provider = DummyLibraryProvider(backend=self)
|
||||
@ -46,13 +38,13 @@ class DummyBackend(Backend):
|
||||
class DummyLibraryProvider(BaseLibraryProvider):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DummyLibraryProvider, self).__init__(*args, **kwargs)
|
||||
self._library = []
|
||||
self.dummy_library = []
|
||||
|
||||
def find_exact(self, **query):
|
||||
return Playlist()
|
||||
|
||||
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:
|
||||
return matches[0]
|
||||
|
||||
|
||||
@ -1,22 +1,24 @@
|
||||
import glob
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
from pykka.registry import ActorRegistry
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.backends.base import (Backend, CurrentPlaylistController,
|
||||
LibraryController, BaseLibraryProvider, PlaybackController,
|
||||
BasePlaybackProvider, StoredPlaylistsController,
|
||||
BaseStoredPlaylistsProvider)
|
||||
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
|
||||
|
||||
logger = logging.getLogger(u'mopidy.backends.local')
|
||||
|
||||
class LocalBackend(Backend):
|
||||
class LocalBackend(ThreadingActor, Backend):
|
||||
"""
|
||||
A backend for playing music from a local music archive.
|
||||
|
||||
@ -48,6 +50,13 @@ class LocalBackend(Backend):
|
||||
|
||||
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):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -58,24 +67,24 @@ class LocalPlaybackController(PlaybackController):
|
||||
|
||||
@property
|
||||
def time_position(self):
|
||||
return self.backend.output.get_position()
|
||||
return self.backend.output.get_position().get()
|
||||
|
||||
|
||||
class LocalPlaybackProvider(BasePlaybackProvider):
|
||||
def pause(self):
|
||||
return self.backend.output.set_state('PAUSED')
|
||||
return self.backend.output.set_state('PAUSED').get()
|
||||
|
||||
def play(self, track):
|
||||
return self.backend.output.play_uri(track.uri)
|
||||
return self.backend.output.play_uri(track.uri).get()
|
||||
|
||||
def resume(self):
|
||||
return self.backend.output.set_state('PLAYING')
|
||||
return self.backend.output.set_state('PLAYING').get()
|
||||
|
||||
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):
|
||||
return self.backend.output.set_state('READY')
|
||||
return self.backend.output.set_state('READY').get()
|
||||
|
||||
|
||||
class LocalStoredPlaylistsProvider(BaseStoredPlaylistsProvider):
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
import logging
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
from pykka.registry import ActorRegistry
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.backends.base import (Backend, CurrentPlaylistController,
|
||||
LibraryController, PlaybackController, StoredPlaylistsController)
|
||||
from mopidy.outputs.base import BaseOutput
|
||||
|
||||
logger = logging.getLogger('mopidy.backends.spotify')
|
||||
|
||||
ENCODING = 'utf-8'
|
||||
|
||||
class SpotifyBackend(Backend):
|
||||
class SpotifyBackend(ThreadingActor, Backend):
|
||||
"""
|
||||
A backend for playing music from the `Spotify <http://www.spotify.com/>`_
|
||||
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.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()
|
||||
|
||||
def _connect(self):
|
||||
@ -67,8 +79,6 @@ class SpotifyBackend(Backend):
|
||||
logger.info(u'Mopidy uses SPOTIFY(R) CORE')
|
||||
logger.debug(u'Connecting to Spotify')
|
||||
spotify = SpotifySessionManager(
|
||||
settings.SPOTIFY_USERNAME, settings.SPOTIFY_PASSWORD,
|
||||
core_queue=self.core_queue,
|
||||
output=self.output)
|
||||
settings.SPOTIFY_USERNAME, settings.SPOTIFY_PASSWORD)
|
||||
spotify.start()
|
||||
return spotify
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import logging
|
||||
import multiprocessing
|
||||
import Queue
|
||||
|
||||
from spotify import Link, SpotifyError
|
||||
|
||||
@ -54,8 +54,9 @@ class SpotifyLibraryProvider(BaseLibraryProvider):
|
||||
spotify_query.append(u'%s:"%s"' % (field, value))
|
||||
spotify_query = u' '.join(spotify_query)
|
||||
logger.debug(u'Spotify search query: %s' % spotify_query)
|
||||
my_end, other_end = multiprocessing.Pipe()
|
||||
self.backend.spotify.search(spotify_query.encode(ENCODING), other_end)
|
||||
my_end.poll(None)
|
||||
playlist = my_end.recv()
|
||||
return playlist
|
||||
queue = Queue.Queue()
|
||||
self.backend.spotify.search(spotify_query.encode(ENCODING), queue)
|
||||
try:
|
||||
return queue.get(timeout=3) # XXX What is an reasonable timeout?
|
||||
except Queue.Empty:
|
||||
return Playlist(tracks=[])
|
||||
|
||||
@ -2,11 +2,15 @@ import logging
|
||||
import os
|
||||
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.backends.base import Backend
|
||||
from mopidy.backends.spotify.translator import SpotifyTranslator
|
||||
from mopidy.models import Playlist
|
||||
from mopidy.outputs.base import BaseOutput
|
||||
from mopidy.utils.process import BaseThread
|
||||
|
||||
logger = logging.getLogger('mopidy.backends.spotify.session_manager')
|
||||
@ -14,24 +18,36 @@ logger = logging.getLogger('mopidy.backends.spotify.session_manager')
|
||||
# pylint: disable = R0901
|
||||
# SpotifySessionManager: Too many ancestors (9/7)
|
||||
|
||||
class SpotifySessionManager(spotify.manager.SpotifySessionManager, BaseThread):
|
||||
class SpotifySessionManager(BaseThread, PyspotifySessionManager):
|
||||
cache_location = settings.SPOTIFY_CACHE_PATH
|
||||
settings_location = settings.SPOTIFY_CACHE_PATH
|
||||
appkey_file = os.path.join(os.path.dirname(__file__), 'spotify_appkey.key')
|
||||
user_agent = 'Mopidy %s' % get_version()
|
||||
|
||||
def __init__(self, username, password, core_queue, output):
|
||||
spotify.manager.SpotifySessionManager.__init__(
|
||||
self, username, password)
|
||||
BaseThread.__init__(self, core_queue)
|
||||
def __init__(self, username, password):
|
||||
PyspotifySessionManager.__init__(self, username, password)
|
||||
BaseThread.__init__(self)
|
||||
self.name = 'SpotifySMThread'
|
||||
self.output = output
|
||||
|
||||
self.output = None
|
||||
self.backend = None
|
||||
|
||||
self.connected = threading.Event()
|
||||
self.session = None
|
||||
|
||||
def run_inside_try(self):
|
||||
self.setup()
|
||||
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):
|
||||
"""Callback used by pyspotify"""
|
||||
if error:
|
||||
@ -91,7 +107,7 @@ class SpotifySessionManager(spotify.manager.SpotifySessionManager, BaseThread):
|
||||
def play_token_lost(self, session):
|
||||
"""Callback used by pyspotify"""
|
||||
logger.debug(u'Play token lost')
|
||||
self.core_queue.put({'command': 'stop_playback'})
|
||||
self.backend.playback.pause()
|
||||
|
||||
def log_message(self, session, data):
|
||||
"""Callback used by pyspotify"""
|
||||
@ -110,19 +126,16 @@ class SpotifySessionManager(spotify.manager.SpotifySessionManager, BaseThread):
|
||||
playlists.append(
|
||||
SpotifyTranslator.to_mopidy_playlist(spotify_playlist))
|
||||
playlists = filter(None, playlists)
|
||||
self.core_queue.put({
|
||||
'command': 'set_stored_playlists',
|
||||
'playlists': playlists,
|
||||
})
|
||||
self.backend.stored_playlists.playlists = 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"""
|
||||
def callback(results, userdata=None):
|
||||
# TODO Include results from results.albums(), etc. too
|
||||
playlist = Playlist(tracks=[
|
||||
SpotifyTranslator.to_mopidy_track(t)
|
||||
for t in results.tracks()])
|
||||
connection.send(playlist)
|
||||
queue.put(playlist)
|
||||
self.connected.wait()
|
||||
self.session.search(query, callback)
|
||||
|
||||
152
mopidy/core.py
152
mopidy/core.py
@ -1,114 +1,74 @@
|
||||
import logging
|
||||
import multiprocessing
|
||||
import optparse
|
||||
import sys
|
||||
import time
|
||||
|
||||
from pykka.registry import ActorRegistry
|
||||
|
||||
from mopidy import get_version, settings, OptionalDependencyError
|
||||
from mopidy.utils import get_class
|
||||
from mopidy.utils.log import setup_logging
|
||||
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
|
||||
|
||||
logger = logging.getLogger('mopidy.core')
|
||||
|
||||
class CoreProcess(BaseThread):
|
||||
def __init__(self):
|
||||
self.core_queue = multiprocessing.Queue()
|
||||
super(CoreProcess, self).__init__(self.core_queue)
|
||||
self.name = 'CoreProcess'
|
||||
self.options = self.parse_options()
|
||||
self.gobject_loop = None
|
||||
self.output = None
|
||||
self.backend = None
|
||||
self.frontends = []
|
||||
def main():
|
||||
options = parse_options()
|
||||
setup_logging(options.verbosity_level, options.save_debug_log)
|
||||
setup_settings()
|
||||
setup_gobject_loop()
|
||||
setup_output()
|
||||
setup_mixer()
|
||||
setup_backend()
|
||||
setup_frontends()
|
||||
try:
|
||||
time.sleep(10000*24*60*60)
|
||||
except KeyboardInterrupt:
|
||||
logger.info(u'Exiting...')
|
||||
ActorRegistry.stop_all()
|
||||
|
||||
def parse_options(self):
|
||||
parser = optparse.OptionParser(version='Mopidy %s' % get_version())
|
||||
parser.add_option('-q', '--quiet',
|
||||
action='store_const', const=0, dest='verbosity_level',
|
||||
help='less output (warning level)')
|
||||
parser.add_option('-v', '--verbose',
|
||||
action='store_const', const=2, dest='verbosity_level',
|
||||
help='more output (debug level)')
|
||||
parser.add_option('--save-debug-log',
|
||||
action='store_true', dest='save_debug_log',
|
||||
help='save debug log to "./mopidy.log"')
|
||||
parser.add_option('--list-settings',
|
||||
action='callback', callback=list_settings_optparse_callback,
|
||||
help='list current settings')
|
||||
return parser.parse_args()[0]
|
||||
def parse_options():
|
||||
parser = optparse.OptionParser(version='Mopidy %s' % get_version())
|
||||
parser.add_option('-q', '--quiet',
|
||||
action='store_const', const=0, dest='verbosity_level',
|
||||
help='less output (warning level)')
|
||||
parser.add_option('-v', '--verbose',
|
||||
action='store_const', const=2, dest='verbosity_level',
|
||||
help='more output (debug level)')
|
||||
parser.add_option('--save-debug-log',
|
||||
action='store_true', dest='save_debug_log',
|
||||
help='save debug log to "./mopidy.log"')
|
||||
parser.add_option('--list-settings',
|
||||
action='callback', callback=list_settings_optparse_callback,
|
||||
help='list current settings')
|
||||
return parser.parse_args()[0]
|
||||
|
||||
def run_inside_try(self):
|
||||
self.setup()
|
||||
while True:
|
||||
message = self.core_queue.get()
|
||||
self.process_message(message)
|
||||
def setup_settings():
|
||||
get_or_create_folder('~/.mopidy/')
|
||||
get_or_create_file('~/.mopidy/settings.py')
|
||||
settings.validate()
|
||||
|
||||
def setup(self):
|
||||
self.setup_logging()
|
||||
self.setup_settings()
|
||||
self.gobject_loop = self.setup_gobject_loop(self.core_queue)
|
||||
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_gobject_loop():
|
||||
gobject_loop = GObjectEventThread()
|
||||
gobject_loop.start()
|
||||
return gobject_loop
|
||||
|
||||
def setup_logging(self):
|
||||
setup_logging(self.options.verbosity_level,
|
||||
self.options.save_debug_log)
|
||||
logger.info(u'-- Starting Mopidy %s --', get_version())
|
||||
def setup_output():
|
||||
return get_class(settings.OUTPUT).start().proxy()
|
||||
|
||||
def setup_settings(self):
|
||||
get_or_create_folder('~/.mopidy/')
|
||||
get_or_create_file('~/.mopidy/settings.py')
|
||||
settings.validate()
|
||||
def setup_mixer():
|
||||
return get_class(settings.MIXER).start().proxy()
|
||||
|
||||
def setup_gobject_loop(self, core_queue):
|
||||
gobject_loop = GObjectEventThread(core_queue)
|
||||
gobject_loop.start()
|
||||
return gobject_loop
|
||||
def setup_backend():
|
||||
return get_class(settings.BACKENDS[0]).start().proxy()
|
||||
|
||||
def setup_output(self, core_queue):
|
||||
output = get_class(settings.OUTPUT)(core_queue)
|
||||
output.start()
|
||||
return output
|
||||
|
||||
def setup_backend(self, core_queue, output):
|
||||
return get_class(settings.BACKENDS[0])(core_queue, output)
|
||||
|
||||
def setup_frontends(self, core_queue, backend):
|
||||
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)
|
||||
def setup_frontends():
|
||||
frontends = []
|
||||
for frontend_class_name in settings.FRONTENDS:
|
||||
try:
|
||||
frontend = get_class(frontend_class_name).start().proxy()
|
||||
frontends.append(frontend)
|
||||
except OptionalDependencyError as e:
|
||||
logger.info(u'Disabled: %s (%s)', frontend_class_name, e)
|
||||
return frontends
|
||||
|
||||
@ -1,40 +1,5 @@
|
||||
class BaseFrontend(object):
|
||||
"""
|
||||
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`
|
||||
"""
|
||||
|
||||
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
|
||||
pass
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import logging
|
||||
import multiprocessing
|
||||
import time
|
||||
|
||||
try:
|
||||
@ -8,16 +7,17 @@ except ImportError as import_error:
|
||||
from mopidy import OptionalDependencyError
|
||||
raise OptionalDependencyError(import_error)
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy import settings, SettingsError
|
||||
from mopidy.frontends.base import BaseFrontend
|
||||
from mopidy.utils.process import BaseThread
|
||||
|
||||
logger = logging.getLogger('mopidy.frontends.lastfm')
|
||||
|
||||
API_KEY = '2236babefa8ebb3d93ea467560d00d04'
|
||||
API_SECRET = '94d9a09c0cd5be955c4afaeaffcaefcd'
|
||||
|
||||
class LastfmFrontend(BaseFrontend):
|
||||
class LastfmFrontend(ThreadingActor, BaseFrontend):
|
||||
"""
|
||||
Frontend which scrobbles the music you play to your `Last.fm
|
||||
<http://www.last.fm>`_ profile.
|
||||
@ -36,38 +36,11 @@ class LastfmFrontend(BaseFrontend):
|
||||
- :attr:`mopidy.settings.LASTFM_PASSWORD`
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
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
|
||||
def __init__(self):
|
||||
self.lastfm = None
|
||||
self.last_start_time = None
|
||||
|
||||
def run_inside_try(self):
|
||||
self.setup()
|
||||
while self.lastfm is not None:
|
||||
self.connection.poll(None)
|
||||
message = self.connection.recv()
|
||||
self.process_message(message)
|
||||
|
||||
def setup(self):
|
||||
def on_start(self):
|
||||
try:
|
||||
username = settings.LASTFM_USERNAME
|
||||
password_hash = pylast.md5(settings.LASTFM_PASSWORD)
|
||||
@ -78,17 +51,19 @@ class LastfmFrontendThread(BaseThread):
|
||||
except SettingsError as e:
|
||||
logger.info(u'Last.fm scrobbler not started')
|
||||
logger.debug(u'Last.fm settings error: %s', e)
|
||||
self.stop()
|
||||
except (pylast.NetworkError, pylast.MalformedResponseError,
|
||||
pylast.WSError) as e:
|
||||
logger.error(u'Error during Last.fm setup: %s', e)
|
||||
self.stop()
|
||||
|
||||
def process_message(self, message):
|
||||
if message['command'] == 'started_playing':
|
||||
def on_receive(self, message):
|
||||
if message.get('command') == 'started_playing':
|
||||
self.started_playing(message['track'])
|
||||
elif message['command'] == 'stopped_playing':
|
||||
elif message.get('command') == 'stopped_playing':
|
||||
self.stopped_playing(message['track'], message['stop_position'])
|
||||
else:
|
||||
pass # Ignore commands for other frontends
|
||||
pass # Ignore any other messages
|
||||
|
||||
def started_playing(self, track):
|
||||
artists = ', '.join([a.name for a in track.artists])
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import asyncore
|
||||
import logging
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy.frontends.base import BaseFrontend
|
||||
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
|
||||
from mopidy.frontends.mpd.thread import MpdThread
|
||||
from mopidy.utils.process import unpickle_connection
|
||||
from mopidy.frontends.mpd.server import MpdServer
|
||||
from mopidy.utils.process import BaseThread
|
||||
|
||||
logger = logging.getLogger('mopidy.frontends.mpd')
|
||||
|
||||
class MpdFrontend(BaseFrontend):
|
||||
class MpdFrontend(ThreadingActor, BaseFrontend):
|
||||
"""
|
||||
The MPD frontend.
|
||||
|
||||
@ -18,32 +20,24 @@ class MpdFrontend(BaseFrontend):
|
||||
- :attr:`mopidy.settings.MPD_SERVER_PORT`
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MpdFrontend, self).__init__(*args, **kwargs)
|
||||
self.thread = None
|
||||
self.dispatcher = MpdDispatcher(self.backend)
|
||||
def __init__(self):
|
||||
self._thread = None
|
||||
|
||||
def start(self):
|
||||
"""Starts the MPD server."""
|
||||
self.thread = MpdThread(self.core_queue)
|
||||
self.thread.start()
|
||||
def on_start(self):
|
||||
self._thread = MpdThread()
|
||||
self._thread.start()
|
||||
|
||||
def destroy(self):
|
||||
"""Destroys the MPD server."""
|
||||
self.thread.destroy()
|
||||
def on_receive(self, message):
|
||||
pass # Ignore any messages
|
||||
|
||||
def process_message(self, message):
|
||||
"""
|
||||
Processes messages with the MPD frontend as destination.
|
||||
|
||||
:param message: the message
|
||||
:type message: dict
|
||||
"""
|
||||
assert message['to'] == 'frontend', \
|
||||
u'Message recipient must be "frontend".'
|
||||
if message['command'] == 'mpd_request':
|
||||
response = self.dispatcher.handle_request(message['request'])
|
||||
connection = unpickle_connection(message['reply_to'])
|
||||
connection.send(response)
|
||||
else:
|
||||
pass # Ignore messages for other frontends
|
||||
class MpdThread(BaseThread):
|
||||
def __init__(self):
|
||||
super(MpdThread, self).__init__()
|
||||
self.name = u'MpdThread'
|
||||
|
||||
def run_inside_try(self):
|
||||
logger.debug(u'Starting MPD server thread')
|
||||
server = MpdServer()
|
||||
server.start()
|
||||
asyncore.loop()
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import re
|
||||
|
||||
from pykka.registry import ActorRegistry
|
||||
|
||||
from mopidy.backends.base import Backend
|
||||
from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdArgError,
|
||||
MpdUnknownCommand)
|
||||
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,
|
||||
status, stickers, stored_playlists)
|
||||
# pylint: enable = W0611
|
||||
from mopidy.mixers.base import BaseMixer
|
||||
from mopidy.utils import flatten
|
||||
|
||||
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):
|
||||
self.backend = backend
|
||||
# XXX Consider merging MpdDispatcher into MpdSession
|
||||
|
||||
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_ok = False
|
||||
|
||||
|
||||
@ -34,6 +34,6 @@ def outputs(frontend):
|
||||
"""
|
||||
return [
|
||||
('outputid', 0),
|
||||
('outputname', frontend.backend.__class__.__name__),
|
||||
('outputname', None),
|
||||
('outputenabled', 1),
|
||||
]
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from mopidy.frontends.mpd.protocol import handle_pattern
|
||||
from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError,
|
||||
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>[^"]*)"$')
|
||||
def add(frontend, uri):
|
||||
@ -18,9 +19,9 @@ def add(frontend, uri):
|
||||
"""
|
||||
if not uri:
|
||||
return
|
||||
for handler_prefix in frontend.backend.uri_handlers:
|
||||
for handler_prefix in frontend.backend.uri_handlers.get():
|
||||
if uri.startswith(handler_prefix):
|
||||
track = frontend.backend.library.lookup(uri)
|
||||
track = frontend.backend.library.lookup(uri).get()
|
||||
if track is not None:
|
||||
frontend.backend.current_playlist.add(track)
|
||||
return
|
||||
@ -50,13 +51,14 @@ def addid(frontend, uri, songpos=None):
|
||||
raise MpdNoExistError(u'No such song', command=u'addid')
|
||||
if songpos is not None:
|
||||
songpos = int(songpos)
|
||||
track = frontend.backend.library.lookup(uri)
|
||||
track = frontend.backend.library.lookup(uri).get()
|
||||
if track is None:
|
||||
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')
|
||||
cp_track = frontend.backend.current_playlist.add(track,
|
||||
at_position=songpos)
|
||||
at_position=songpos).get()
|
||||
return ('Id', cp_track[0])
|
||||
|
||||
@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:
|
||||
end = int(end)
|
||||
else:
|
||||
end = len(frontend.backend.current_playlist.tracks)
|
||||
cp_tracks = frontend.backend.current_playlist.cp_tracks[start:end]
|
||||
end = len(frontend.backend.current_playlist.tracks.get())
|
||||
cp_tracks = frontend.backend.current_playlist.cp_tracks.get()[start:end]
|
||||
if not cp_tracks:
|
||||
raise MpdArgError(u'Bad song index', command=u'delete')
|
||||
for (cpid, _) in cp_tracks:
|
||||
@ -84,7 +86,7 @@ def delete_songpos(frontend, songpos):
|
||||
"""See :meth:`delete_range`"""
|
||||
try:
|
||||
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)
|
||||
except IndexError:
|
||||
raise MpdArgError(u'Bad song index', command=u'delete')
|
||||
@ -100,9 +102,9 @@ def deleteid(frontend, cpid):
|
||||
"""
|
||||
try:
|
||||
cpid = int(cpid)
|
||||
if frontend.backend.playback.current_cpid == cpid:
|
||||
if frontend.backend.playback.current_cpid.get() == cpid:
|
||||
frontend.backend.playback.next()
|
||||
return frontend.backend.current_playlist.remove(cpid=cpid)
|
||||
return frontend.backend.current_playlist.remove(cpid=cpid).get()
|
||||
except LookupError:
|
||||
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.
|
||||
"""
|
||||
if end is None:
|
||||
end = len(frontend.backend.current_playlist.tracks)
|
||||
end = len(frontend.backend.current_playlist.tracks.get())
|
||||
start = int(start)
|
||||
end = int(end)
|
||||
to = int(to)
|
||||
@ -154,8 +156,9 @@ def moveid(frontend, cpid, to):
|
||||
"""
|
||||
cpid = int(cpid)
|
||||
to = int(to)
|
||||
cp_track = frontend.backend.current_playlist.get(cpid=cpid)
|
||||
position = frontend.backend.current_playlist.cp_tracks.index(cp_track)
|
||||
cp_track = frontend.backend.current_playlist.get(cpid=cpid).get()
|
||||
position = frontend.backend.current_playlist.cp_tracks.get().index(
|
||||
cp_track)
|
||||
frontend.backend.current_playlist.move(position, position + 1, to)
|
||||
|
||||
@handle_pattern(r'^playlist$')
|
||||
@ -189,9 +192,9 @@ def playlistfind(frontend, tag, needle):
|
||||
"""
|
||||
if tag == 'filename':
|
||||
try:
|
||||
cp_track = frontend.backend.current_playlist.get(uri=needle)
|
||||
cp_track = frontend.backend.current_playlist.get(uri=needle).get()
|
||||
(cpid, track) = cp_track
|
||||
position = frontend.backend.current_playlist.cp_tracks.index(
|
||||
position = frontend.backend.current_playlist.cp_tracks.get().index(
|
||||
cp_track)
|
||||
return track.mpd_format(cpid=cpid, position=position)
|
||||
except LookupError:
|
||||
@ -211,14 +214,17 @@ def playlistid(frontend, cpid=None):
|
||||
if cpid is not None:
|
||||
try:
|
||||
cpid = int(cpid)
|
||||
cp_track = frontend.backend.current_playlist.get(cpid=cpid)
|
||||
position = frontend.backend.current_playlist.cp_tracks.index(
|
||||
cp_track = frontend.backend.current_playlist.get(cpid=cpid).get()
|
||||
position = frontend.backend.current_playlist.cp_tracks.get().index(
|
||||
cp_track)
|
||||
return cp_track[1].mpd_format(position=position, cpid=cpid)
|
||||
except LookupError:
|
||||
raise MpdNoExistError(u'No such song', command=u'playlistid')
|
||||
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 "(?P<songpos>-?\d+)"$')
|
||||
@ -248,18 +254,27 @@ def playlistinfo(frontend, songpos=None,
|
||||
end = songpos + 1
|
||||
if start == -1:
|
||||
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:
|
||||
if start is None:
|
||||
start = 0
|
||||
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')
|
||||
if end is not None:
|
||||
end = int(end)
|
||||
if end > len(frontend.backend.current_playlist.tracks):
|
||||
if end > len(frontend.backend.current_playlist.tracks.get()):
|
||||
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>\S+) "(?P<needle>[^"]+)"$')
|
||||
@ -298,7 +313,10 @@ def plchanges(frontend, version):
|
||||
"""
|
||||
# XXX Naive implementation that returns all tracks as changed
|
||||
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+)"$')
|
||||
def plchangesposid(frontend, version):
|
||||
@ -315,10 +333,10 @@ def plchangesposid(frontend, version):
|
||||
``playlistlength`` returned by status command.
|
||||
"""
|
||||
# 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 = []
|
||||
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'Id', cpid))
|
||||
return result
|
||||
@ -351,7 +369,7 @@ def swap(frontend, songpos1, songpos2):
|
||||
"""
|
||||
songpos1 = int(songpos1)
|
||||
songpos2 = int(songpos2)
|
||||
tracks = frontend.backend.current_playlist.tracks
|
||||
tracks = frontend.backend.current_playlist.tracks.get()
|
||||
song1 = tracks[songpos1]
|
||||
song2 = tracks[songpos2]
|
||||
del tracks[songpos1]
|
||||
@ -372,8 +390,9 @@ def swapid(frontend, cpid1, cpid2):
|
||||
"""
|
||||
cpid1 = int(cpid1)
|
||||
cpid2 = int(cpid2)
|
||||
cp_track1 = frontend.backend.current_playlist.get(cpid=cpid1)
|
||||
cp_track2 = frontend.backend.current_playlist.get(cpid=cpid2)
|
||||
position1 = frontend.backend.current_playlist.cp_tracks.index(cp_track1)
|
||||
position2 = frontend.backend.current_playlist.cp_tracks.index(cp_track2)
|
||||
cp_track1 = frontend.backend.current_playlist.get(cpid=cpid1).get()
|
||||
cp_track2 = frontend.backend.current_playlist.get(cpid=cpid2).get()
|
||||
cp_tracks = frontend.backend.current_playlist.cp_tracks.get()
|
||||
position1 = cp_tracks.index(cp_track1)
|
||||
position2 = cp_tracks.index(cp_track2)
|
||||
swap(frontend, position1, position2)
|
||||
|
||||
@ -68,7 +68,7 @@ def find(frontend, mpd_query):
|
||||
- also uses the search type "date".
|
||||
"""
|
||||
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 '
|
||||
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):
|
||||
artists = set()
|
||||
playlist = frontend.backend.library.find_exact(**query)
|
||||
playlist = frontend.backend.library.find_exact(**query).get()
|
||||
for track in playlist.tracks:
|
||||
for artist in track.artists:
|
||||
artists.add((u'Artist', artist.name))
|
||||
@ -223,7 +223,7 @@ def _list_artist(frontend, query):
|
||||
|
||||
def _list_album(frontend, query):
|
||||
albums = set()
|
||||
playlist = frontend.backend.library.find_exact(**query)
|
||||
playlist = frontend.backend.library.find_exact(**query).get()
|
||||
for track in playlist.tracks:
|
||||
if track.album is not None:
|
||||
albums.add((u'Album', track.album.name))
|
||||
@ -231,7 +231,7 @@ def _list_album(frontend, query):
|
||||
|
||||
def _list_date(frontend, query):
|
||||
dates = set()
|
||||
playlist = frontend.backend.library.find_exact(**query)
|
||||
playlist = frontend.backend.library.find_exact(**query).get()
|
||||
for track in playlist.tracks:
|
||||
if track.date is not None:
|
||||
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".
|
||||
"""
|
||||
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>[^"]+)")*$')
|
||||
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.exceptions import (MpdArgError, MpdNoExistError,
|
||||
MpdNotImplemented)
|
||||
@ -86,7 +87,7 @@ def next_(frontend):
|
||||
order as the first time.
|
||||
|
||||
"""
|
||||
return frontend.backend.playback.next()
|
||||
return frontend.backend.playback.next().get()
|
||||
|
||||
@handle_pattern(r'^pause$')
|
||||
@handle_pattern(r'^pause "(?P<state>[01])"$')
|
||||
@ -103,11 +104,11 @@ def pause(frontend, state=None):
|
||||
- Calls ``pause`` without any arguments to toogle pause.
|
||||
"""
|
||||
if state is None:
|
||||
if (frontend.backend.playback.state ==
|
||||
frontend.backend.playback.PLAYING):
|
||||
if (frontend.backend.playback.state.get() ==
|
||||
PlaybackController.PLAYING):
|
||||
frontend.backend.playback.pause()
|
||||
elif (frontend.backend.playback.state ==
|
||||
frontend.backend.playback.PAUSED):
|
||||
elif (frontend.backend.playback.state.get() ==
|
||||
PlaybackController.PAUSED):
|
||||
frontend.backend.playback.resume()
|
||||
elif int(state):
|
||||
frontend.backend.playback.pause()
|
||||
@ -120,7 +121,7 @@ def play(frontend):
|
||||
The original MPD server resumes from the paused state on ``play``
|
||||
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>-1)"$')
|
||||
@ -145,8 +146,8 @@ def playid(frontend, cpid):
|
||||
if cpid == -1:
|
||||
return _play_minus_one(frontend)
|
||||
try:
|
||||
cp_track = frontend.backend.current_playlist.get(cpid=cpid)
|
||||
return frontend.backend.playback.play(cp_track)
|
||||
cp_track = frontend.backend.current_playlist.get(cpid=cpid).get()
|
||||
return frontend.backend.playback.play(cp_track).get()
|
||||
except LookupError:
|
||||
raise MpdNoExistError(u'No such song', command=u'playid')
|
||||
|
||||
@ -177,22 +178,22 @@ def playpos(frontend, songpos):
|
||||
if songpos == -1:
|
||||
return _play_minus_one(frontend)
|
||||
try:
|
||||
cp_track = frontend.backend.current_playlist.cp_tracks[songpos]
|
||||
return frontend.backend.playback.play(cp_track)
|
||||
cp_track = frontend.backend.current_playlist.cp_tracks.get()[songpos]
|
||||
return frontend.backend.playback.play(cp_track).get()
|
||||
except IndexError:
|
||||
raise MpdArgError(u'Bad song index', command=u'play')
|
||||
|
||||
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
|
||||
elif (frontend.backend.playback.state == frontend.backend.playback.PAUSED):
|
||||
return frontend.backend.playback.resume()
|
||||
elif frontend.backend.playback.current_cp_track is not None:
|
||||
cp_track = frontend.backend.playback.current_cp_track
|
||||
return frontend.backend.playback.play(cp_track)
|
||||
elif frontend.backend.current_playlist.cp_tracks:
|
||||
cp_track = frontend.backend.current_playlist.cp_tracks[0]
|
||||
return frontend.backend.playback.play(cp_track)
|
||||
elif (frontend.backend.playback.state.get() == PlaybackController.PAUSED):
|
||||
return frontend.backend.playback.resume().get()
|
||||
elif frontend.backend.playback.current_cp_track.get() is not None:
|
||||
cp_track = frontend.backend.playback.current_cp_track.get()
|
||||
return frontend.backend.playback.play(cp_track).get()
|
||||
elif frontend.backend.current_playlist.cp_tracks.get():
|
||||
cp_track = frontend.backend.current_playlist.cp_tracks.get()[0]
|
||||
return frontend.backend.playback.play(cp_track).get()
|
||||
else:
|
||||
return # Fail silently
|
||||
|
||||
@ -240,7 +241,7 @@ def previous(frontend):
|
||||
``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])"$')
|
||||
@ -351,7 +352,7 @@ def setvol(frontend, volume):
|
||||
volume = 0
|
||||
if 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])"$')
|
||||
|
||||
@ -81,4 +81,4 @@ def urlhandlers(frontend):
|
||||
|
||||
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.exceptions import MpdNotImplemented
|
||||
|
||||
@ -23,10 +24,11 @@ def currentsong(frontend):
|
||||
Displays the song info of the current song (same song that is
|
||||
identified in status).
|
||||
"""
|
||||
if frontend.backend.playback.current_track is not None:
|
||||
return frontend.backend.playback.current_track.mpd_format(
|
||||
position=frontend.backend.playback.current_playlist_position,
|
||||
cpid=frontend.backend.playback.current_cpid)
|
||||
current_cp_track = frontend.backend.playback.current_cp_track.get()
|
||||
if current_cp_track is not None:
|
||||
return current_cp_track[1].mpd_format(
|
||||
position=frontend.backend.playback.current_playlist_position.get(),
|
||||
cpid=current_cp_track[0])
|
||||
|
||||
@handle_pattern(r'^idle$')
|
||||
@handle_pattern(r'^idle (?P<subsystems>.+)$')
|
||||
@ -90,8 +92,7 @@ def stats(frontend):
|
||||
'artists': 0, # TODO
|
||||
'albums': 0, # TODO
|
||||
'songs': 0, # TODO
|
||||
# TODO Does not work after multiprocessing branch merge
|
||||
'uptime': 0, # frontend.session.stats_uptime(),
|
||||
'uptime': 0, # TODO
|
||||
'db_playtime': 0, # TODO
|
||||
'db_update': 0, # TODO
|
||||
'playtime': 0, # TODO
|
||||
@ -140,56 +141,59 @@ def status(frontend):
|
||||
('xfade', _status_xfade(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(('songid', _status_songid(frontend)))
|
||||
if frontend.backend.playback.state in (frontend.backend.playback.PLAYING,
|
||||
frontend.backend.playback.PAUSED):
|
||||
if frontend.backend.playback.state.get() in (PlaybackController.PLAYING,
|
||||
PlaybackController.PAUSED):
|
||||
result.append(('time', _status_time(frontend)))
|
||||
result.append(('elapsed', _status_time_elapsed(frontend)))
|
||||
result.append(('bitrate', _status_bitrate(frontend)))
|
||||
return result
|
||||
|
||||
def _status_bitrate(frontend):
|
||||
if frontend.backend.playback.current_track is not None:
|
||||
return frontend.backend.playback.current_track.bitrate
|
||||
current_track = frontend.backend.playback.current_track.get()
|
||||
if current_track is not None:
|
||||
return current_track.bitrate
|
||||
|
||||
def _status_consume(frontend):
|
||||
if frontend.backend.playback.consume:
|
||||
if frontend.backend.playback.consume.get():
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
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):
|
||||
return frontend.backend.current_playlist.version
|
||||
return frontend.backend.current_playlist.version.get()
|
||||
|
||||
def _status_random(frontend):
|
||||
return int(frontend.backend.playback.random)
|
||||
return int(frontend.backend.playback.random.get())
|
||||
|
||||
def _status_repeat(frontend):
|
||||
return int(frontend.backend.playback.repeat)
|
||||
return int(frontend.backend.playback.repeat.get())
|
||||
|
||||
def _status_single(frontend):
|
||||
return int(frontend.backend.playback.single)
|
||||
return int(frontend.backend.playback.single.get())
|
||||
|
||||
def _status_songid(frontend):
|
||||
if frontend.backend.playback.current_cpid is not None:
|
||||
return frontend.backend.playback.current_cpid
|
||||
current_cpid = frontend.backend.playback.current_cpid.get()
|
||||
if current_cpid is not None:
|
||||
return current_cpid
|
||||
else:
|
||||
return _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):
|
||||
if frontend.backend.playback.state == frontend.backend.playback.PLAYING:
|
||||
state = frontend.backend.playback.state.get()
|
||||
if state == PlaybackController.PLAYING:
|
||||
return u'play'
|
||||
elif frontend.backend.playback.state == frontend.backend.playback.STOPPED:
|
||||
elif state == PlaybackController.STOPPED:
|
||||
return u'stop'
|
||||
elif frontend.backend.playback.state == frontend.backend.playback.PAUSED:
|
||||
elif state == PlaybackController.PAUSED:
|
||||
return u'pause'
|
||||
|
||||
def _status_time(frontend):
|
||||
@ -197,19 +201,21 @@ def _status_time(frontend):
|
||||
_status_time_total(frontend) // 1000)
|
||||
|
||||
def _status_time_elapsed(frontend):
|
||||
return frontend.backend.playback.time_position
|
||||
return frontend.backend.playback.time_position.get()
|
||||
|
||||
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
|
||||
elif frontend.backend.playback.current_track.length is None:
|
||||
elif current_track.length is None:
|
||||
return 0
|
||||
else:
|
||||
return frontend.backend.playback.current_track.length
|
||||
return current_track.length
|
||||
|
||||
def _status_volume(frontend):
|
||||
if frontend.backend.mixer.volume is not None:
|
||||
return frontend.backend.mixer.volume
|
||||
volume = frontend.mixer.volume.get()
|
||||
if volume is not None:
|
||||
return volume
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
@ -19,8 +19,8 @@ def listplaylist(frontend, name):
|
||||
file: relative/path/to/file3.mp3
|
||||
"""
|
||||
try:
|
||||
return ['file: %s' % t.uri
|
||||
for t in frontend.backend.stored_playlists.get(name=name).tracks]
|
||||
playlist = frontend.backend.stored_playlists.get(name=name).get()
|
||||
return ['file: %s' % t.uri for t in playlist.tracks]
|
||||
except LookupError:
|
||||
raise MpdNoExistError(u'No such playlist', command=u'listplaylist')
|
||||
|
||||
@ -39,7 +39,8 @@ def listplaylistinfo(frontend, name):
|
||||
Album, Artist, Track
|
||||
"""
|
||||
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:
|
||||
raise MpdNoExistError(
|
||||
u'No such playlist', command=u'listplaylistinfo')
|
||||
@ -66,7 +67,7 @@ def listplaylists(frontend):
|
||||
Last-Modified: 2010-02-06T02:11:08Z
|
||||
"""
|
||||
result = []
|
||||
for playlist in frontend.backend.stored_playlists.playlists:
|
||||
for playlist in frontend.backend.stored_playlists.playlists.get():
|
||||
result.append((u'playlist', playlist.name))
|
||||
last_modified = (playlist.last_modified or
|
||||
dt.datetime.now()).isoformat()
|
||||
@ -92,7 +93,7 @@ def load(frontend, name):
|
||||
- ``load`` appends the given playlist to the current playlist.
|
||||
"""
|
||||
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)
|
||||
except LookupError:
|
||||
raise MpdNoExistError(u'No such playlist', command=u'load')
|
||||
|
||||
@ -15,9 +15,8 @@ class MpdServer(asyncore.dispatcher):
|
||||
for each client connection.
|
||||
"""
|
||||
|
||||
def __init__(self, core_queue):
|
||||
def __init__(self):
|
||||
asyncore.dispatcher.__init__(self)
|
||||
self.core_queue = core_queue
|
||||
|
||||
def start(self):
|
||||
"""Start MPD server."""
|
||||
@ -47,8 +46,7 @@ class MpdServer(asyncore.dispatcher):
|
||||
(client_socket, client_socket_address) = self.accept()
|
||||
logger.info(u'MPD client connection from [%s]:%s',
|
||||
client_socket_address[0], client_socket_address[1])
|
||||
MpdSession(self, client_socket, client_socket_address,
|
||||
self.core_queue).start()
|
||||
MpdSession(self, client_socket, client_socket_address).start()
|
||||
|
||||
def handle_close(self):
|
||||
"""Handle end of client connection."""
|
||||
|
||||
@ -1,30 +1,28 @@
|
||||
import asynchat
|
||||
import logging
|
||||
import multiprocessing
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
|
||||
from mopidy.frontends.mpd.protocol import ENCODING, LINE_TERMINATOR, VERSION
|
||||
from mopidy.utils.log import indent
|
||||
from mopidy.utils.process import pickle_connection
|
||||
|
||||
logger = logging.getLogger('mopidy.frontends.mpd.session')
|
||||
|
||||
class MpdSession(asynchat.async_chat):
|
||||
"""
|
||||
The MPD client session. Keeps track of a single client and passes its
|
||||
MPD requests to the dispatcher.
|
||||
The MPD client session. Keeps track of a single client session. Any
|
||||
requests from the client is passed on to the MPD request dispatcher.
|
||||
"""
|
||||
|
||||
def __init__(self, server, client_socket, client_socket_address,
|
||||
core_queue):
|
||||
def __init__(self, server, client_socket, client_socket_address):
|
||||
asynchat.async_chat.__init__(self, sock=client_socket)
|
||||
self.server = server
|
||||
self.client_address = client_socket_address[0]
|
||||
self.client_port = client_socket_address[1]
|
||||
self.core_queue = core_queue
|
||||
self.input_buffer = []
|
||||
self.authenticated = False
|
||||
self.set_terminator(LINE_TERMINATOR.encode(ENCODING))
|
||||
self.dispatcher = MpdDispatcher()
|
||||
|
||||
def start(self):
|
||||
"""Start a new client session."""
|
||||
@ -53,15 +51,7 @@ class MpdSession(asynchat.async_chat):
|
||||
if response is not None:
|
||||
self.send_response(response)
|
||||
return
|
||||
my_end, other_end = multiprocessing.Pipe()
|
||||
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()
|
||||
response = self.dispatcher.handle_request(request)
|
||||
if response is not None:
|
||||
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 logging
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.mixers.base import BaseMixer
|
||||
|
||||
logger = logging.getLogger('mopidy.mixers.alsa')
|
||||
|
||||
class AlsaMixer(BaseMixer):
|
||||
class AlsaMixer(ThreadingActor, BaseMixer):
|
||||
"""
|
||||
Mixer which uses the Advanced Linux Sound Architecture (ALSA) to control
|
||||
volume.
|
||||
@ -20,8 +22,10 @@ class AlsaMixer(BaseMixer):
|
||||
- :attr:`mopidy.settings.MIXER_ALSA_CONTROL`
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AlsaMixer, self).__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
self._mixer = None
|
||||
|
||||
def on_start(self):
|
||||
self._mixer = alsaaudio.Mixer(self._get_mixer_control())
|
||||
assert self._mixer is not None
|
||||
|
||||
|
||||
@ -2,17 +2,12 @@ from mopidy import settings
|
||||
|
||||
class BaseMixer(object):
|
||||
"""
|
||||
:param backend: a backend instance
|
||||
:type backend: :class:`mopidy.backends.base.Backend`
|
||||
|
||||
**Settings:**
|
||||
|
||||
- :attr:`mopidy.settings.MIXER_MAX_VOLUME`
|
||||
"""
|
||||
|
||||
def __init__(self, backend, *args, **kwargs):
|
||||
self.backend = backend
|
||||
self.amplification_factor = settings.MIXER_MAX_VOLUME / 100.0
|
||||
amplification_factor = settings.MIXER_MAX_VOLUME / 100.0
|
||||
|
||||
@property
|
||||
def volume(self):
|
||||
@ -35,9 +30,6 @@ class BaseMixer(object):
|
||||
volume = 100
|
||||
self._set_volume(volume)
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def _get_volume(self):
|
||||
"""
|
||||
Return volume as integer in range [0, 100]. :class:`None` if unknown.
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import logging
|
||||
from threading import Lock
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.mixers.base import BaseMixer
|
||||
|
||||
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
|
||||
protocol.
|
||||
@ -25,27 +26,19 @@ class DenonMixer(BaseMixer):
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
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._device = kwargs.get('device', None)
|
||||
self._levels = ['99'] + ["%(#)02d" % {'#': v} for v in range(0, 99)]
|
||||
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):
|
||||
self._lock.acquire()
|
||||
self.ensure_open_device()
|
||||
self._ensure_open_device()
|
||||
self._device.write('MV?\r')
|
||||
vol = str(self._device.readline()[2:4])
|
||||
self._lock.release()
|
||||
logger.debug(u'_get_volume() = %s' % vol)
|
||||
return self._levels.index(vol)
|
||||
|
||||
@ -53,14 +46,12 @@ class DenonMixer(BaseMixer):
|
||||
# Clamp according to Denon-spec
|
||||
if volume > 99:
|
||||
volume = 99
|
||||
self._lock.acquire()
|
||||
self.ensure_open_device()
|
||||
self._ensure_open_device()
|
||||
self._device.write('MV%s\r'% self._levels[volume])
|
||||
vol = self._device.readline()[2:4]
|
||||
self._lock.release()
|
||||
self._volume = self._levels.index(vol)
|
||||
|
||||
def ensure_open_device(self):
|
||||
def _ensure_open_device(self):
|
||||
if not self._device.isOpen():
|
||||
logger.debug(u'(re)connecting to Denon device')
|
||||
self._device.open()
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy.mixers.base import BaseMixer
|
||||
|
||||
class DummyMixer(BaseMixer):
|
||||
class DummyMixer(ThreadingActor, BaseMixer):
|
||||
"""Mixer which just stores and reports the chosen volume."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DummyMixer, self).__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
self._volume = None
|
||||
|
||||
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."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(GStreamerSoftwareMixer, self).__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
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):
|
||||
return self.backend.output.get_volume()
|
||||
return self.output.get_volume().get()
|
||||
|
||||
def _set_volume(self, volume):
|
||||
self.backend.output.set_volume(volume)
|
||||
self.output.set_volume(volume).get()
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import logging
|
||||
from serial import Serial
|
||||
from multiprocessing import Pipe
|
||||
import serial
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.mixers.base import BaseMixer
|
||||
from mopidy.utils.process import BaseThread
|
||||
|
||||
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
|
||||
protocol.
|
||||
@ -36,21 +36,19 @@ class NadMixer(BaseMixer):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NadMixer, self).__init__(*args, **kwargs)
|
||||
self._volume = None
|
||||
self._pipe, other_end = Pipe()
|
||||
NadTalker(self.backend.core_queue, pipe=other_end).start()
|
||||
def __init__(self):
|
||||
self._volume_cache = None
|
||||
self._nad_talker = NadTalker.start().proxy()
|
||||
|
||||
def _get_volume(self):
|
||||
return self._volume
|
||||
return self._volume_cache
|
||||
|
||||
def _set_volume(self, volume):
|
||||
self._volume = volume
|
||||
self._pipe.send({'command': 'set_volume', 'volume': volume})
|
||||
self._volume_cache = volume
|
||||
self._nad_talker.set_volume(volume)
|
||||
|
||||
|
||||
class NadTalker(BaseThread):
|
||||
class NadTalker(ThreadingActor):
|
||||
"""
|
||||
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.
|
||||
_nad_volume = None
|
||||
|
||||
def __init__(self, core_queue, pipe=None):
|
||||
super(NadTalker, self).__init__(core_queue)
|
||||
self.name = u'NadTalker'
|
||||
self.pipe = pipe
|
||||
def __init__(self):
|
||||
self._device = None
|
||||
|
||||
def run_inside_try(self):
|
||||
def on_start(self):
|
||||
self._open_connection()
|
||||
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):
|
||||
# Opens serial connection to the device.
|
||||
# Communication settings: 115200 bps 8N1
|
||||
logger.info(u'Connecting to serial device "%s"',
|
||||
settings.MIXER_EXT_PORT)
|
||||
self._device = Serial(port=settings.MIXER_EXT_PORT, baudrate=115200,
|
||||
timeout=self.TIMEOUT)
|
||||
self._device = serial.Serial(port=settings.MIXER_EXT_PORT,
|
||||
baudrate=115200, timeout=self.TIMEOUT)
|
||||
self._get_device_model()
|
||||
|
||||
def _set_device_to_known_state(self):
|
||||
@ -164,7 +153,7 @@ class NadTalker(BaseThread):
|
||||
self._nad_volume = 0
|
||||
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
|
||||
# target volume.
|
||||
logger.debug(u'Setting volume to %d' % volume)
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
from subprocess import Popen, PIPE
|
||||
import time
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy.mixers.base import BaseMixer
|
||||
|
||||
class OsaMixer(BaseMixer):
|
||||
class OsaMixer(ThreadingActor, BaseMixer):
|
||||
"""
|
||||
Mixer which uses ``osascript`` on OS X to control volume.
|
||||
|
||||
@ -14,7 +16,6 @@ class OsaMixer(BaseMixer):
|
||||
**Settings:**
|
||||
|
||||
- None
|
||||
|
||||
"""
|
||||
|
||||
CACHE_TTL = 30
|
||||
|
||||
@ -3,33 +3,6 @@ class BaseOutput(object):
|
||||
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):
|
||||
"""
|
||||
Play URI.
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy.outputs.base import BaseOutput
|
||||
|
||||
class DummyOutput(BaseOutput):
|
||||
class DummyOutput(ThreadingActor, BaseOutput):
|
||||
"""
|
||||
Audio output used for testing.
|
||||
"""
|
||||
@ -8,15 +10,6 @@ class DummyOutput(BaseOutput):
|
||||
# pylint: disable = R0902
|
||||
# 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`.
|
||||
uri = None
|
||||
|
||||
@ -40,15 +33,6 @@ class DummyOutput(BaseOutput):
|
||||
#: For testing. Contains the current volume.
|
||||
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):
|
||||
self.uri = uri
|
||||
return True
|
||||
|
||||
@ -3,113 +3,39 @@ pygst.require('0.10')
|
||||
import gst
|
||||
|
||||
import logging
|
||||
import multiprocessing
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
from pykka.registry import ActorRegistry
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.backends.base import Backend
|
||||
from mopidy.outputs.base import BaseOutput
|
||||
from mopidy.utils.process import (BaseThread, pickle_connection,
|
||||
unpickle_connection)
|
||||
|
||||
logger = logging.getLogger('mopidy.outputs.gstreamer')
|
||||
|
||||
class GStreamerOutput(BaseOutput):
|
||||
class GStreamerOutput(ThreadingActor, BaseOutput):
|
||||
"""
|
||||
Audio output through GStreamer.
|
||||
|
||||
Starts :class:`GStreamerMessagesThread` and :class:`GStreamerPlayerThread`.
|
||||
Audio output through `GStreamer <http://gstreamer.freedesktop.org/>`_.
|
||||
|
||||
**Settings:**
|
||||
|
||||
- :attr:`mopidy.settings.GSTREAMER_AUDIO_SINK`
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
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
|
||||
def __init__(self):
|
||||
self.gst_pipeline = None
|
||||
|
||||
def run_inside_try(self):
|
||||
self.setup()
|
||||
while True:
|
||||
message = self.output_queue.get()
|
||||
self.process_mopidy_message(message)
|
||||
def on_start(self):
|
||||
self._setup_gstreamer()
|
||||
|
||||
def _setup_gstreamer(self):
|
||||
"""
|
||||
**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')
|
||||
|
||||
self.gst_pipeline = gst.parse_launch(' ! '.join([
|
||||
@ -122,7 +48,7 @@ class GStreamerPlayerThread(BaseThread):
|
||||
|
||||
if settings.BACKENDS[0] == 'mopidy.backends.local.LocalBackend':
|
||||
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)
|
||||
else:
|
||||
app_src = gst.element_factory_make('appsrc', 'appsrc')
|
||||
@ -141,57 +67,29 @@ class GStreamerPlayerThread(BaseThread):
|
||||
# Setup bus and message processor
|
||||
gst_bus = self.gst_pipeline.get_bus()
|
||||
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)
|
||||
|
||||
def process_mopidy_message(self, 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):
|
||||
def _process_gstreamer_message(self, bus, message):
|
||||
"""Process messages from GStreamer."""
|
||||
if message.type == gst.MESSAGE_EOS:
|
||||
logger.debug(u'GStreamer signalled end-of-stream. '
|
||||
'Sending end_of_track to core_queue ...')
|
||||
self.core_queue.put({'command': 'end_of_track'})
|
||||
'Telling backend ...')
|
||||
self._get_backend().playback.on_end_of_track()
|
||||
elif message.type == gst.MESSAGE_ERROR:
|
||||
self.set_state('NULL')
|
||||
error, debug = message.parse_error()
|
||||
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?
|
||||
|
||||
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):
|
||||
"""Play audio at URI"""
|
||||
self.set_state('READY')
|
||||
@ -216,6 +114,21 @@ class GStreamerPlayerThread(BaseThread):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
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.set_property('volume', volume / 100.0)
|
||||
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.handlers
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy import get_version, settings
|
||||
|
||||
def setup_logging(verbosity_level, save_debug_log):
|
||||
setup_root_logger()
|
||||
setup_console_logging(verbosity_level)
|
||||
if save_debug_log:
|
||||
setup_debug_logging_to_file()
|
||||
logger = logging.getLogger('mopidy.utils.log')
|
||||
logger.info(u'-- Starting Mopidy %s --', get_version())
|
||||
|
||||
def setup_root_logger():
|
||||
root = logging.getLogger('')
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import logging
|
||||
import multiprocessing
|
||||
import multiprocessing.dummy
|
||||
from multiprocessing.reduction import reduce_connection
|
||||
import pickle
|
||||
import threading
|
||||
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
@ -11,52 +8,10 @@ from mopidy import SettingsError
|
||||
|
||||
logger = logging.getLogger('mopidy.utils.process')
|
||||
|
||||
def pickle_connection(connection):
|
||||
return pickle.dumps(reduce_connection(connection))
|
||||
|
||||
def unpickle_connection(pickled_connection):
|
||||
# From http://stackoverflow.com/questions/1446004
|
||||
(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):
|
||||
class BaseThread(threading.Thread):
|
||||
def __init__(self):
|
||||
super(BaseThread, self).__init__()
|
||||
self.core_queue = core_queue
|
||||
# No thread should block process from exiting
|
||||
self.daemon = True
|
||||
|
||||
@ -84,8 +39,6 @@ class BaseThread(multiprocessing.dummy.Process):
|
||||
pass
|
||||
|
||||
def exit(self, status=0, reason=None):
|
||||
self.core_queue.put({'to': 'core', 'command': 'exit',
|
||||
'status': status, 'reason': reason})
|
||||
self.destroy()
|
||||
|
||||
|
||||
@ -98,8 +51,8 @@ class GObjectEventThread(BaseThread):
|
||||
:mod:`mopidy.output.gstreamer`, :mod:`mopidy.frontend.mpris`, etc.
|
||||
"""
|
||||
|
||||
def __init__(self, core_queue):
|
||||
super(GObjectEventThread, self).__init__(core_queue)
|
||||
def __init__(self):
|
||||
super(GObjectEventThread, self).__init__()
|
||||
self.name = u'GObjectEventThread'
|
||||
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
|
||||
mock
|
||||
nose
|
||||
tox
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import mock
|
||||
import multiprocessing
|
||||
import random
|
||||
|
||||
from mopidy.mixers.dummy import DummyMixer
|
||||
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
|
||||
|
||||
@ -11,19 +11,13 @@ class CurrentPlaylistControllerTest(object):
|
||||
tracks = []
|
||||
|
||||
def setUp(self):
|
||||
self.core_queue = multiprocessing.Queue()
|
||||
self.output = DummyOutput(self.core_queue)
|
||||
self.backend = self.backend_class(
|
||||
self.core_queue, self.output, DummyMixer)
|
||||
self.backend = self.backend_class()
|
||||
self.backend.output = mock.Mock(spec=BaseOutput)
|
||||
self.controller = self.backend.current_playlist
|
||||
self.playback = self.backend.playback
|
||||
|
||||
assert len(self.tracks) == 3, 'Need three tracks to run tests.'
|
||||
|
||||
def tearDown(self):
|
||||
self.backend.destroy()
|
||||
self.output.destroy()
|
||||
|
||||
def test_add(self):
|
||||
for track in self.tracks:
|
||||
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 tests import SkipTest, data_folder
|
||||
@ -15,12 +14,9 @@ class LibraryControllerTest(object):
|
||||
Track()]
|
||||
|
||||
def setUp(self):
|
||||
self.backend = self.backend_class(mixer_class=DummyMixer)
|
||||
self.backend = self.backend_class()
|
||||
self.library = self.backend.library
|
||||
|
||||
def tearDown(self):
|
||||
self.backend.destroy()
|
||||
|
||||
def test_refresh(self):
|
||||
self.library.refresh()
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import mock
|
||||
import multiprocessing
|
||||
import random
|
||||
import time
|
||||
|
||||
from mopidy.mixers.dummy import DummyMixer
|
||||
from mopidy.models import Track
|
||||
from mopidy.outputs.dummy import DummyOutput
|
||||
from mopidy.outputs.base import BaseOutput
|
||||
|
||||
from tests import SkipTest
|
||||
from tests.backends.base import populate_playlist
|
||||
@ -15,10 +15,8 @@ class PlaybackControllerTest(object):
|
||||
tracks = []
|
||||
|
||||
def setUp(self):
|
||||
self.core_queue = multiprocessing.Queue()
|
||||
self.output = DummyOutput(self.core_queue)
|
||||
self.backend = self.backend_class(
|
||||
self.core_queue, self.output, DummyMixer)
|
||||
self.backend = self.backend_class()
|
||||
self.backend.output = mock.Mock(spec=BaseOutput)
|
||||
self.playback = self.backend.playback
|
||||
self.current_playlist = self.backend.current_playlist
|
||||
|
||||
@ -27,10 +25,6 @@ class PlaybackControllerTest(object):
|
||||
assert self.tracks[0].length >= 2000, \
|
||||
'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):
|
||||
self.assertEqual(self.playback.state, self.playback.STOPPED)
|
||||
|
||||
@ -733,10 +727,18 @@ class PlaybackControllerTest(object):
|
||||
self.assertEqual(self.playback.stop(), None)
|
||||
|
||||
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)
|
||||
|
||||
@populate_playlist
|
||||
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)
|
||||
|
||||
@SkipTest # Uses sleep and does not work with LocalBackend+DummyOutput
|
||||
|
||||
@ -3,7 +3,6 @@ import shutil
|
||||
import tempfile
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.mixers.dummy import DummyMixer
|
||||
from mopidy.models import Playlist
|
||||
|
||||
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_MUSIC_PATH = data_folder('')
|
||||
|
||||
self.backend = self.backend_class(mixer_class=DummyMixer)
|
||||
self.backend = self.backend_class()
|
||||
self.stored = self.backend.stored_playlists
|
||||
|
||||
def tearDown(self):
|
||||
self.backend.destroy()
|
||||
|
||||
if os.path.exists(settings.LOCAL_PLAYLIST_PATH):
|
||||
shutil.rmtree(settings.LOCAL_PLAYLIST_PATH)
|
||||
|
||||
|
||||
@ -70,8 +70,7 @@ class LocalStoredPlaylistsControllerTest(StoredPlaylistsControllerTest,
|
||||
|
||||
self.stored.save(playlist)
|
||||
|
||||
self.backend.destroy()
|
||||
self.backend = self.backend_class(mixer_class=DummyMixer)
|
||||
self.backend = self.backend_class()
|
||||
self.stored = self.backend.stored_playlists
|
||||
|
||||
self.assert_(self.stored.playlists)
|
||||
|
||||
@ -6,8 +6,13 @@ from mopidy.mixers.dummy import DummyMixer
|
||||
|
||||
class AudioOutputHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
self.mixer = DummyMixer.start().proxy()
|
||||
self.h = dispatcher.MpdDispatcher()
|
||||
|
||||
def tearDown(self):
|
||||
self.b.stop().get()
|
||||
self.mixer.stop().get()
|
||||
|
||||
def test_enableoutput(self):
|
||||
result = self.h.handle_request(u'enableoutput "0"')
|
||||
@ -20,6 +25,6 @@ class AudioOutputHandlerTest(unittest.TestCase):
|
||||
def test_outputs(self):
|
||||
result = self.h.handle_request(u'outputs')
|
||||
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'OK' in result)
|
||||
|
||||
@ -6,8 +6,13 @@ from mopidy.mixers.dummy import DummyMixer
|
||||
|
||||
class CommandListsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
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):
|
||||
result = self.h.handle_request(u'command_list_begin')
|
||||
|
||||
@ -7,10 +7,13 @@ from mopidy.mixers.dummy import DummyMixer
|
||||
|
||||
class ConnectionHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
self.mixer = DummyMixer.start().proxy()
|
||||
self.h = dispatcher.MpdDispatcher()
|
||||
|
||||
def tearDown(self):
|
||||
self.b.stop().get()
|
||||
self.mixer.stop().get()
|
||||
settings.runtime.clear()
|
||||
|
||||
def test_close(self):
|
||||
|
||||
@ -7,26 +7,26 @@ from mopidy.models import Track
|
||||
|
||||
class CurrentPlaylistHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
self.mixer = DummyMixer.start().proxy()
|
||||
self.h = dispatcher.MpdDispatcher()
|
||||
|
||||
def tearDown(self):
|
||||
self.b.stop().get()
|
||||
self.mixer.stop().get()
|
||||
|
||||
def test_add(self):
|
||||
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(
|
||||
[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"')
|
||||
self.assertEqual(len(self.b.current_playlist.tracks), 6)
|
||||
self.assertEqual(self.b.current_playlist.tracks[5], needle)
|
||||
self.assertEqual(len(result), 1)
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
def test_add_with_uri_not_found_in_library_should_not_call_lookup(self):
|
||||
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')
|
||||
self.assertEqual(result[0], u'OK')
|
||||
self.assertEqual(len(self.b.current_playlist.tracks.get()), 6)
|
||||
self.assertEqual(self.b.current_playlist.tracks.get()[5], needle)
|
||||
|
||||
def test_add_with_uri_not_found_in_library_should_ack(self):
|
||||
result = self.h.handle_request(u'add "dummy://foo"')
|
||||
@ -40,41 +40,43 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
||||
|
||||
def test_addid_without_songpos(self):
|
||||
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(
|
||||
[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"')
|
||||
self.assertEqual(len(self.b.current_playlist.tracks), 6)
|
||||
self.assertEqual(self.b.current_playlist.tracks[5], needle)
|
||||
self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks[5][0]
|
||||
self.assertEqual(len(self.b.current_playlist.tracks.get()), 6)
|
||||
self.assertEqual(self.b.current_playlist.tracks.get()[5], needle)
|
||||
self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks.get()[5][0]
|
||||
in result)
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
def test_addid_with_empty_uri_does_not_lookup_and_acks(self):
|
||||
self.b.library.lookup = lambda uri: self.fail("Shouldn't run")
|
||||
def test_addid_with_empty_uri_acks(self):
|
||||
result = self.h.handle_request(u'addid ""')
|
||||
self.assertEqual(result[0], u'ACK [50@0] {addid} No such song')
|
||||
|
||||
def test_addid_with_songpos(self):
|
||||
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(
|
||||
[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"')
|
||||
self.assertEqual(len(self.b.current_playlist.tracks), 6)
|
||||
self.assertEqual(self.b.current_playlist.tracks[3], needle)
|
||||
self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks[3][0]
|
||||
self.assertEqual(len(self.b.current_playlist.tracks.get()), 6)
|
||||
self.assertEqual(self.b.current_playlist.tracks.get()[3], needle)
|
||||
self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks.get()[3][0]
|
||||
in result)
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
def test_addid_with_songpos_out_of_bounds_should_ack(self):
|
||||
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(
|
||||
[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"')
|
||||
self.assertEqual(result[0], u'ACK [2@0] {addid} Bad song index')
|
||||
|
||||
@ -85,65 +87,65 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
||||
def test_clear(self):
|
||||
self.b.current_playlist.append(
|
||||
[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')
|
||||
self.assertEqual(len(self.b.current_playlist.tracks), 0)
|
||||
self.assertEqual(self.b.playback.current_track, None)
|
||||
self.assertEqual(len(self.b.current_playlist.tracks.get()), 0)
|
||||
self.assertEqual(self.b.playback.current_track.get(), None)
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
def test_delete_songpos(self):
|
||||
self.b.current_playlist.append(
|
||||
[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"' %
|
||||
self.b.current_playlist.cp_tracks[2][0])
|
||||
self.assertEqual(len(self.b.current_playlist.tracks), 4)
|
||||
self.b.current_playlist.cp_tracks.get()[2][0])
|
||||
self.assertEqual(len(self.b.current_playlist.tracks.get()), 4)
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
def test_delete_songpos_out_of_bounds(self):
|
||||
self.b.current_playlist.append(
|
||||
[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"')
|
||||
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')
|
||||
|
||||
def test_delete_open_range(self):
|
||||
self.b.current_playlist.append(
|
||||
[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:"')
|
||||
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)
|
||||
|
||||
def test_delete_closed_range(self):
|
||||
self.b.current_playlist.append(
|
||||
[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"')
|
||||
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)
|
||||
|
||||
def test_delete_range_out_of_bounds(self):
|
||||
self.b.current_playlist.append(
|
||||
[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"')
|
||||
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')
|
||||
|
||||
def test_deleteid(self):
|
||||
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"')
|
||||
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)
|
||||
|
||||
def test_deleteid_does_not_exist(self):
|
||||
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"')
|
||||
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')
|
||||
|
||||
def test_move_songpos(self):
|
||||
@ -152,12 +154,13 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||
])
|
||||
result = self.h.handle_request(u'move "1" "0"')
|
||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'b')
|
||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'a')
|
||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'c')
|
||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'd')
|
||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'e')
|
||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'f')
|
||||
tracks = self.b.current_playlist.tracks.get()
|
||||
self.assertEqual(tracks[0].name, 'b')
|
||||
self.assertEqual(tracks[1].name, 'a')
|
||||
self.assertEqual(tracks[2].name, 'c')
|
||||
self.assertEqual(tracks[3].name, 'd')
|
||||
self.assertEqual(tracks[4].name, 'e')
|
||||
self.assertEqual(tracks[5].name, 'f')
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
def test_move_open_range(self):
|
||||
@ -166,12 +169,13 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||
])
|
||||
result = self.h.handle_request(u'move "2:" "0"')
|
||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'c')
|
||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'd')
|
||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'e')
|
||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'f')
|
||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'a')
|
||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'b')
|
||||
tracks = self.b.current_playlist.tracks.get()
|
||||
self.assertEqual(tracks[0].name, 'c')
|
||||
self.assertEqual(tracks[1].name, 'd')
|
||||
self.assertEqual(tracks[2].name, 'e')
|
||||
self.assertEqual(tracks[3].name, 'f')
|
||||
self.assertEqual(tracks[4].name, 'a')
|
||||
self.assertEqual(tracks[5].name, 'b')
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
def test_move_closed_range(self):
|
||||
@ -180,12 +184,13 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||
])
|
||||
result = self.h.handle_request(u'move "1:3" "0"')
|
||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'b')
|
||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'c')
|
||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'a')
|
||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'd')
|
||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'e')
|
||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'f')
|
||||
tracks = self.b.current_playlist.tracks.get()
|
||||
self.assertEqual(tracks[0].name, 'b')
|
||||
self.assertEqual(tracks[1].name, 'c')
|
||||
self.assertEqual(tracks[2].name, 'a')
|
||||
self.assertEqual(tracks[3].name, 'd')
|
||||
self.assertEqual(tracks[4].name, 'e')
|
||||
self.assertEqual(tracks[5].name, 'f')
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
def test_moveid(self):
|
||||
@ -194,12 +199,13 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||
])
|
||||
result = self.h.handle_request(u'moveid "4" "2"')
|
||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'a')
|
||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'b')
|
||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'e')
|
||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'c')
|
||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'd')
|
||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'f')
|
||||
tracks = self.b.current_playlist.tracks.get()
|
||||
self.assertEqual(tracks[0].name, 'a')
|
||||
self.assertEqual(tracks[1].name, 'b')
|
||||
self.assertEqual(tracks[2].name, 'e')
|
||||
self.assertEqual(tracks[3].name, 'c')
|
||||
self.assertEqual(tracks[4].name, 'd')
|
||||
self.assertEqual(tracks[5].name, 'f')
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
def test_playlist_returns_same_as_playlistinfo(self):
|
||||
@ -361,14 +367,15 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
||||
def test_plchangesposid(self):
|
||||
self.b.current_playlist.append([Track(), Track(), Track()])
|
||||
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'Id: %d' % self.b.current_playlist.cp_tracks[0][0]
|
||||
self.assert_(u'Id: %d' % cp_tracks[0][0]
|
||||
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)
|
||||
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)
|
||||
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='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')
|
||||
self.assert_(version < self.b.current_playlist.version)
|
||||
self.assert_(version < self.b.current_playlist.version.get())
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
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='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:"')
|
||||
self.assert_(version < self.b.current_playlist.version)
|
||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'a')
|
||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'b')
|
||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'c')
|
||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'd')
|
||||
self.assert_(version < self.b.current_playlist.version.get())
|
||||
tracks = self.b.current_playlist.tracks.get()
|
||||
self.assertEqual(tracks[0].name, 'a')
|
||||
self.assertEqual(tracks[1].name, 'b')
|
||||
self.assertEqual(tracks[2].name, 'c')
|
||||
self.assertEqual(tracks[3].name, 'd')
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
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='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"')
|
||||
self.assert_(version < self.b.current_playlist.version)
|
||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'a')
|
||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'd')
|
||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'e')
|
||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'f')
|
||||
self.assert_(version < self.b.current_playlist.version.get())
|
||||
tracks = self.b.current_playlist.tracks.get()
|
||||
self.assertEqual(tracks[0].name, 'a')
|
||||
self.assertEqual(tracks[3].name, 'd')
|
||||
self.assertEqual(tracks[4].name, 'e')
|
||||
self.assertEqual(tracks[5].name, 'f')
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
def test_swap(self):
|
||||
@ -416,12 +425,13 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||
])
|
||||
result = self.h.handle_request(u'swap "1" "4"')
|
||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'a')
|
||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'e')
|
||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'c')
|
||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'd')
|
||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'b')
|
||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'f')
|
||||
tracks = self.b.current_playlist.tracks.get()
|
||||
self.assertEqual(tracks[0].name, 'a')
|
||||
self.assertEqual(tracks[1].name, 'e')
|
||||
self.assertEqual(tracks[2].name, 'c')
|
||||
self.assertEqual(tracks[3].name, 'd')
|
||||
self.assertEqual(tracks[4].name, 'b')
|
||||
self.assertEqual(tracks[5].name, 'f')
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
def test_swapid(self):
|
||||
@ -430,10 +440,11 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
|
||||
Track(name='d'), Track(name='e'), Track(name='f'),
|
||||
])
|
||||
result = self.h.handle_request(u'swapid "1" "4"')
|
||||
self.assertEqual(self.b.current_playlist.tracks[0].name, 'a')
|
||||
self.assertEqual(self.b.current_playlist.tracks[1].name, 'e')
|
||||
self.assertEqual(self.b.current_playlist.tracks[2].name, 'c')
|
||||
self.assertEqual(self.b.current_playlist.tracks[3].name, 'd')
|
||||
self.assertEqual(self.b.current_playlist.tracks[4].name, 'b')
|
||||
self.assertEqual(self.b.current_playlist.tracks[5].name, 'f')
|
||||
tracks = self.b.current_playlist.tracks.get()
|
||||
self.assertEqual(tracks[0].name, 'a')
|
||||
self.assertEqual(tracks[1].name, 'e')
|
||||
self.assertEqual(tracks[2].name, 'c')
|
||||
self.assertEqual(tracks[3].name, 'd')
|
||||
self.assertEqual(tracks[4].name, 'b')
|
||||
self.assertEqual(tracks[5].name, 'f')
|
||||
self.assert_(u'OK' in result)
|
||||
|
||||
@ -8,8 +8,13 @@ from mopidy.mixers.dummy import DummyMixer
|
||||
|
||||
class MpdDispatcherTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
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):
|
||||
func = lambda: None
|
||||
|
||||
@ -6,8 +6,13 @@ from mopidy.mixers.dummy import DummyMixer
|
||||
|
||||
class MusicDatabaseHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
self.mixer = DummyMixer.start().proxy()
|
||||
self.h = dispatcher.MpdDispatcher()
|
||||
|
||||
def tearDown(self):
|
||||
self.b.stop().get()
|
||||
self.mixer.stop().get()
|
||||
|
||||
def test_count(self):
|
||||
result = self.h.handle_request(u'count "tag" "needle"')
|
||||
@ -65,8 +70,13 @@ class MusicDatabaseHandlerTest(unittest.TestCase):
|
||||
|
||||
class MusicDatabaseFindTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
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):
|
||||
result = self.h.handle_request(u'find "album" "what"')
|
||||
@ -117,8 +127,13 @@ class MusicDatabaseFindTest(unittest.TestCase):
|
||||
|
||||
class MusicDatabaseListTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
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):
|
||||
result = self.h.handle_request(u'list "foo"')
|
||||
@ -308,8 +323,13 @@ class MusicDatabaseListTest(unittest.TestCase):
|
||||
|
||||
class MusicDatabaseSearchTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
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):
|
||||
result = self.h.handle_request(u'search "album" "analbum"')
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import unittest
|
||||
|
||||
from mopidy.backends.base import PlaybackController
|
||||
from mopidy.backends.dummy import DummyBackend
|
||||
from mopidy.frontends.mpd import dispatcher
|
||||
from mopidy.mixers.dummy import DummyMixer
|
||||
@ -7,29 +8,38 @@ from mopidy.models import Track
|
||||
|
||||
from tests import SkipTest
|
||||
|
||||
PAUSED = PlaybackController.PAUSED
|
||||
PLAYING = PlaybackController.PLAYING
|
||||
STOPPED = PlaybackController.STOPPED
|
||||
|
||||
class PlaybackOptionsHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
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):
|
||||
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)
|
||||
|
||||
def test_consume_off_without_quotes(self):
|
||||
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)
|
||||
|
||||
def test_consume_on(self):
|
||||
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)
|
||||
|
||||
def test_consume_on_without_quotes(self):
|
||||
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)
|
||||
|
||||
def test_crossfade(self):
|
||||
@ -38,97 +48,97 @@ class PlaybackOptionsHandlerTest(unittest.TestCase):
|
||||
|
||||
def test_random_off(self):
|
||||
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)
|
||||
|
||||
def test_random_off_without_quotes(self):
|
||||
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)
|
||||
|
||||
def test_random_on(self):
|
||||
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)
|
||||
|
||||
def test_random_on_without_quotes(self):
|
||||
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)
|
||||
|
||||
def test_repeat_off(self):
|
||||
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)
|
||||
|
||||
def test_repeat_off_without_quotes(self):
|
||||
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)
|
||||
|
||||
def test_repeat_on(self):
|
||||
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)
|
||||
|
||||
def test_repeat_on_without_quotes(self):
|
||||
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)
|
||||
|
||||
def test_setvol_below_min(self):
|
||||
result = self.h.handle_request(u'setvol "-10"')
|
||||
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):
|
||||
result = self.h.handle_request(u'setvol "0"')
|
||||
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):
|
||||
result = self.h.handle_request(u'setvol "50"')
|
||||
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):
|
||||
result = self.h.handle_request(u'setvol "100"')
|
||||
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):
|
||||
result = self.h.handle_request(u'setvol "110"')
|
||||
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):
|
||||
result = self.h.handle_request(u'setvol "+10"')
|
||||
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):
|
||||
result = self.h.handle_request(u'setvol 50')
|
||||
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):
|
||||
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)
|
||||
|
||||
def test_single_off_without_quotes(self):
|
||||
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)
|
||||
|
||||
def test_single_on(self):
|
||||
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)
|
||||
|
||||
def test_single_on_without_quotes(self):
|
||||
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)
|
||||
|
||||
def test_replay_gain_mode_off(self):
|
||||
@ -176,8 +186,13 @@ class PlaybackOptionsHandlerTest(unittest.TestCase):
|
||||
|
||||
class PlaybackControlHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
self.mixer = DummyMixer.start().proxy()
|
||||
self.h = dispatcher.MpdDispatcher()
|
||||
|
||||
def tearDown(self):
|
||||
self.b.stop().get()
|
||||
self.mixer.stop().get()
|
||||
|
||||
def test_next(self):
|
||||
result = self.h.handle_request(u'next')
|
||||
@ -189,155 +204,155 @@ class PlaybackControlHandlerTest(unittest.TestCase):
|
||||
self.h.handle_request(u'pause "1"')
|
||||
result = self.h.handle_request(u'pause "0"')
|
||||
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):
|
||||
self.b.current_playlist.append([Track()])
|
||||
self.h.handle_request(u'play "0"')
|
||||
result = self.h.handle_request(u'pause "1"')
|
||||
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):
|
||||
self.b.current_playlist.append([Track()])
|
||||
result = self.h.handle_request(u'play "0"')
|
||||
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')
|
||||
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')
|
||||
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):
|
||||
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')
|
||||
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):
|
||||
self.b.current_playlist.append([Track()])
|
||||
result = self.h.handle_request(u'play "0"')
|
||||
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):
|
||||
self.b.current_playlist.append([Track()])
|
||||
result = self.h.handle_request(u'play 0')
|
||||
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):
|
||||
self.b.current_playlist.append([])
|
||||
result = self.h.handle_request(u'play "0"')
|
||||
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):
|
||||
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')])
|
||||
result = self.h.handle_request(u'play "-1"')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
||||
self.assertEqual(self.b.playback.current_track.uri, 'a')
|
||||
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||
self.assertEqual(self.b.playback.current_track.get().uri, 'a')
|
||||
|
||||
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.assertEqual(self.b.playback.current_track, None)
|
||||
self.assertEqual(self.b.playback.current_track.get(), None)
|
||||
self.b.playback.play()
|
||||
self.b.playback.next()
|
||||
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"')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
||||
self.assertEqual(self.b.playback.current_track.uri, 'b')
|
||||
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||
self.assertEqual(self.b.playback.current_track.get().uri, 'b')
|
||||
|
||||
def test_play_minus_one_on_empty_playlist_does_not_ack(self):
|
||||
self.b.current_playlist.clear()
|
||||
result = self.h.handle_request(u'play "-1"')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assertEqual(self.b.playback.STOPPED, self.b.playback.state)
|
||||
self.assertEqual(self.b.playback.current_track, None)
|
||||
self.assertEqual(STOPPED, self.b.playback.state.get())
|
||||
self.assertEqual(self.b.playback.current_track.get(), None)
|
||||
|
||||
def test_play_minus_is_ignored_if_playing(self):
|
||||
self.b.current_playlist.append([Track(length=40000)])
|
||||
self.b.playback.seek(30000)
|
||||
self.assert_(self.b.playback.time_position >= 30000)
|
||||
self.assertEquals(self.b.playback.PLAYING, self.b.playback.state)
|
||||
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||
self.assertEquals(PLAYING, self.b.playback.state.get())
|
||||
result = self.h.handle_request(u'play "-1"')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
||||
self.assert_(self.b.playback.time_position >= 30000)
|
||||
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||
|
||||
def test_play_minus_one_resumes_if_paused(self):
|
||||
self.b.current_playlist.append([Track(length=40000)])
|
||||
self.b.playback.seek(30000)
|
||||
self.assert_(self.b.playback.time_position >= 30000)
|
||||
self.assertEquals(self.b.playback.PLAYING, self.b.playback.state)
|
||||
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||
self.assertEquals(PLAYING, self.b.playback.state.get())
|
||||
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"')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
||||
self.assert_(self.b.playback.time_position >= 30000)
|
||||
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||
|
||||
def test_playid(self):
|
||||
self.b.current_playlist.append([Track()])
|
||||
result = self.h.handle_request(u'playid "0"')
|
||||
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):
|
||||
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')])
|
||||
result = self.h.handle_request(u'playid "-1"')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
||||
self.assertEqual(self.b.playback.current_track.uri, 'a')
|
||||
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||
self.assertEqual(self.b.playback.current_track.get().uri, 'a')
|
||||
|
||||
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.assertEqual(self.b.playback.current_track, None)
|
||||
self.assertEqual(self.b.playback.current_track.get(), None)
|
||||
self.b.playback.play()
|
||||
self.b.playback.next()
|
||||
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"')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
||||
self.assertEqual(self.b.playback.current_track.uri, 'b')
|
||||
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||
self.assertEqual(self.b.playback.current_track.get().uri, 'b')
|
||||
|
||||
def test_playid_minus_one_on_empty_playlist_does_not_ack(self):
|
||||
self.b.current_playlist.clear()
|
||||
result = self.h.handle_request(u'playid "-1"')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assertEqual(self.b.playback.STOPPED, self.b.playback.state)
|
||||
self.assertEqual(self.b.playback.current_track, None)
|
||||
self.assertEqual(STOPPED, self.b.playback.state.get())
|
||||
self.assertEqual(self.b.playback.current_track.get(), None)
|
||||
|
||||
def test_playid_minus_is_ignored_if_playing(self):
|
||||
self.b.current_playlist.append([Track(length=40000)])
|
||||
self.b.playback.seek(30000)
|
||||
self.assert_(self.b.playback.time_position >= 30000)
|
||||
self.assertEquals(self.b.playback.PLAYING, self.b.playback.state)
|
||||
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||
self.assertEquals(PLAYING, self.b.playback.state.get())
|
||||
result = self.h.handle_request(u'playid "-1"')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
||||
self.assert_(self.b.playback.time_position >= 30000)
|
||||
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||
|
||||
def test_playid_minus_one_resumes_if_paused(self):
|
||||
self.b.current_playlist.append([Track(length=40000)])
|
||||
self.b.playback.seek(30000)
|
||||
self.assert_(self.b.playback.time_position >= 30000)
|
||||
self.assertEquals(self.b.playback.PLAYING, self.b.playback.state)
|
||||
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||
self.assertEquals(PLAYING, self.b.playback.state.get())
|
||||
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"')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assertEqual(self.b.playback.PLAYING, self.b.playback.state)
|
||||
self.assert_(self.b.playback.time_position >= 30000)
|
||||
self.assertEqual(PLAYING, self.b.playback.state.get())
|
||||
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||
|
||||
def test_playid_which_does_not_exist(self):
|
||||
self.b.current_playlist.append([Track()])
|
||||
@ -361,20 +376,20 @@ class PlaybackControlHandlerTest(unittest.TestCase):
|
||||
[Track(uri='1', length=40000), seek_track])
|
||||
result = self.h.handle_request(u'seek "1" "30"')
|
||||
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):
|
||||
self.b.current_playlist.append([Track(length=40000)])
|
||||
self.h.handle_request(u'seek 0')
|
||||
result = self.h.handle_request(u'seek 0 30')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assert_(self.b.playback.time_position >= 30000)
|
||||
self.assert_(self.b.playback.time_position.get() >= 30000)
|
||||
|
||||
def test_seekid(self):
|
||||
self.b.current_playlist.append([Track(length=40000)])
|
||||
result = self.h.handle_request(u'seekid "0" "30"')
|
||||
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):
|
||||
seek_track = Track(uri='2', length=40000)
|
||||
@ -382,10 +397,10 @@ class PlaybackControlHandlerTest(unittest.TestCase):
|
||||
[Track(length=40000), seek_track])
|
||||
result = self.h.handle_request(u'seekid "1" "30"')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assertEqual(self.b.playback.current_cpid, 1)
|
||||
self.assertEqual(self.b.playback.current_track, seek_track)
|
||||
self.assertEqual(self.b.playback.current_cpid.get(), 1)
|
||||
self.assertEqual(self.b.playback.current_track.get(), seek_track)
|
||||
|
||||
def test_stop(self):
|
||||
result = self.h.handle_request(u'stop')
|
||||
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):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
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):
|
||||
result = self.h.handle_request(u'commands')
|
||||
|
||||
@ -18,26 +18,31 @@ class IssueGH17RegressionTest(unittest.TestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.backend = DummyBackend(mixer_class=DummyMixer)
|
||||
self.backend = DummyBackend.start().proxy()
|
||||
self.backend.current_playlist.append([
|
||||
Track(uri='a'), Track(uri='b'), None,
|
||||
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):
|
||||
random.seed(1) # Playlist order: abcfde
|
||||
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'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')
|
||||
# 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.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.assertEquals('e', self.backend.playback.current_track.uri)
|
||||
self.assertEquals('e', self.backend.playback.current_track.get().uri)
|
||||
|
||||
|
||||
class IssueGH18RegressionTest(unittest.TestCase):
|
||||
@ -52,11 +57,16 @@ class IssueGH18RegressionTest(unittest.TestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.backend = DummyBackend(mixer_class=DummyMixer)
|
||||
self.backend = DummyBackend.start().proxy()
|
||||
self.backend.current_playlist.append([
|
||||
Track(uri='a'), Track(uri='b'), Track(uri='c'),
|
||||
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):
|
||||
random.seed(1)
|
||||
@ -67,11 +77,11 @@ class IssueGH18RegressionTest(unittest.TestCase):
|
||||
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')
|
||||
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')
|
||||
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_2, cp_track_3)
|
||||
@ -91,11 +101,16 @@ class IssueGH22RegressionTest(unittest.TestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.backend = DummyBackend(mixer_class=DummyMixer)
|
||||
self.backend = DummyBackend.start().proxy()
|
||||
self.backend.current_playlist.append([
|
||||
Track(uri='a'), Track(uri='b'), Track(uri='c'),
|
||||
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):
|
||||
random.seed(1)
|
||||
|
||||
@ -1,11 +1,19 @@
|
||||
import unittest
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.backends.dummy import DummyBackend
|
||||
from mopidy.frontends.mpd import server
|
||||
from mopidy.mixers.dummy import DummyMixer
|
||||
|
||||
class MpdServerTest(unittest.TestCase):
|
||||
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):
|
||||
server.socket.has_ipv6 = True
|
||||
@ -20,9 +28,13 @@ class MpdServerTest(unittest.TestCase):
|
||||
|
||||
class MpdSessionTest(unittest.TestCase):
|
||||
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):
|
||||
self.backend.stop().get()
|
||||
self.mixer.stop().get()
|
||||
settings.runtime.clear()
|
||||
|
||||
def test_found_terminator_catches_decode_error(self):
|
||||
|
||||
@ -1,14 +1,24 @@
|
||||
import unittest
|
||||
|
||||
from mopidy.backends.base import PlaybackController
|
||||
from mopidy.backends.dummy import DummyBackend
|
||||
from mopidy.frontends.mpd import dispatcher
|
||||
from mopidy.mixers.dummy import DummyMixer
|
||||
from mopidy.models import Track
|
||||
|
||||
PAUSED = PlaybackController.PAUSED
|
||||
PLAYING = PlaybackController.PLAYING
|
||||
STOPPED = PlaybackController.STOPPED
|
||||
|
||||
class StatusHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
self.mixer = DummyMixer.start().proxy()
|
||||
self.h = dispatcher.MpdDispatcher()
|
||||
|
||||
def tearDown(self):
|
||||
self.b.stop().get()
|
||||
self.mixer.stop().get()
|
||||
|
||||
def test_clearerror(self):
|
||||
result = self.h.handle_request(u'clearerror')
|
||||
@ -77,7 +87,7 @@ class StatusHandlerTest(unittest.TestCase):
|
||||
self.assertEqual(int(result['volume']), 0)
|
||||
|
||||
def test_status_method_contains_volume(self):
|
||||
self.b.mixer.volume = 17
|
||||
self.mixer.volume = 17
|
||||
result = dict(dispatcher.status.status(self.h))
|
||||
self.assert_('volume' in result)
|
||||
self.assertEqual(int(result['volume']), 17)
|
||||
@ -136,20 +146,20 @@ class StatusHandlerTest(unittest.TestCase):
|
||||
self.assert_(int(result['xfade']) >= 0)
|
||||
|
||||
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))
|
||||
self.assert_('state' in result)
|
||||
self.assertEqual(result['state'], 'play')
|
||||
|
||||
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))
|
||||
self.assert_('state' in result)
|
||||
self.assertEqual(result['state'], 'stop')
|
||||
|
||||
def test_status_method_contains_state_is_pause(self):
|
||||
self.b.playback.state = self.b.playback.PLAYING
|
||||
self.b.playback.state = self.b.playback.PAUSED
|
||||
self.b.playback.state = PLAYING
|
||||
self.b.playback.state = PAUSED
|
||||
result = dict(dispatcher.status.status(self.h))
|
||||
self.assert_('state' in result)
|
||||
self.assertEqual(result['state'], 'pause')
|
||||
@ -189,8 +199,8 @@ class StatusHandlerTest(unittest.TestCase):
|
||||
self.assert_(position <= total)
|
||||
|
||||
def test_status_method_when_playing_contains_elapsed(self):
|
||||
self.b.playback.state = self.b.playback.PAUSED
|
||||
self.b.playback._play_time_accumulated = 59123
|
||||
self.b.playback.state = PAUSED
|
||||
self.b.playback.play_time_accumulated = 59123
|
||||
result = dict(dispatcher.status.status(self.h))
|
||||
self.assert_('elapsed' in result)
|
||||
self.assertEqual(int(result['elapsed']), 59123)
|
||||
|
||||
@ -6,8 +6,13 @@ from mopidy.mixers.dummy import DummyMixer
|
||||
|
||||
class StickersHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
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):
|
||||
result = self.h.handle_request(
|
||||
|
||||
@ -8,8 +8,13 @@ from mopidy.models import Track, Playlist
|
||||
|
||||
class StoredPlaylistsHandlerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.b = DummyBackend(mixer_class=DummyMixer)
|
||||
self.h = dispatcher.MpdDispatcher(backend=self.b)
|
||||
self.b = DummyBackend.start().proxy()
|
||||
self.mixer = DummyMixer.start().proxy()
|
||||
self.h = dispatcher.MpdDispatcher()
|
||||
|
||||
def tearDown(self):
|
||||
self.b.stop().get()
|
||||
self.mixer.stop().get()
|
||||
|
||||
def test_listplaylist(self):
|
||||
self.b.stored_playlists.playlists = [
|
||||
@ -49,22 +54,23 @@ class StoredPlaylistsHandlerTest(unittest.TestCase):
|
||||
|
||||
def test_load_known_playlist_appends_to_current_playlist(self):
|
||||
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',
|
||||
tracks=[Track(uri='c'), Track(uri='d'), Track(uri='e')])]
|
||||
result = self.h.handle_request(u'load "A-list"')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assertEqual(len(self.b.current_playlist.tracks), 5)
|
||||
self.assertEqual(self.b.current_playlist.tracks[0].uri, 'a')
|
||||
self.assertEqual(self.b.current_playlist.tracks[1].uri, 'b')
|
||||
self.assertEqual(self.b.current_playlist.tracks[2].uri, 'c')
|
||||
self.assertEqual(self.b.current_playlist.tracks[3].uri, 'd')
|
||||
self.assertEqual(self.b.current_playlist.tracks[4].uri, 'e')
|
||||
tracks = self.b.current_playlist.tracks.get()
|
||||
self.assertEqual(len(tracks), 5)
|
||||
self.assertEqual(tracks[0].uri, 'a')
|
||||
self.assertEqual(tracks[1].uri, 'b')
|
||||
self.assertEqual(tracks[2].uri, 'c')
|
||||
self.assertEqual(tracks[3].uri, 'd')
|
||||
self.assertEqual(tracks[4].uri, 'e')
|
||||
|
||||
def test_load_unknown_playlist_acks(self):
|
||||
result = self.h.handle_request(u'load "unknown playlist"')
|
||||
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):
|
||||
result = self.h.handle_request(
|
||||
|
||||
@ -11,7 +11,7 @@ class BaseMixerTest(object):
|
||||
assert self.mixer_class is not None, \
|
||||
"mixer_class must be set in subclass"
|
||||
# pylint: disable = E1102
|
||||
self.mixer = self.mixer_class(None)
|
||||
self.mixer = self.mixer_class()
|
||||
# pylint: enable = E1102
|
||||
|
||||
def test_initial_volume(self):
|
||||
|
||||
@ -18,12 +18,10 @@ class GStreamerOutputTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
settings.BACKENDS = ('mopidy.backends.local.LocalBackend',)
|
||||
self.song_uri = path_to_uri(data_folder('song1.wav'))
|
||||
self.core_queue = multiprocessing.Queue()
|
||||
self.output = GStreamerOutput(self.core_queue)
|
||||
self.output.start()
|
||||
self.output = GStreamerOutput()
|
||||
self.output.on_start()
|
||||
|
||||
def tearDown(self):
|
||||
self.output.destroy()
|
||||
settings.runtime.clear()
|
||||
|
||||
def test_play_uri_existing_file(self):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user