Merge branch 'develop' into feature/glib-loop
Conflicts: mopidy/core.py mopidy/frontends/mpd/__init__.py
This commit is contained in:
commit
75984dda6a
@ -15,7 +15,6 @@ The backend
|
||||
|
||||
.. autoclass:: mopidy.backends.base.Backend
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Playback controller
|
||||
@ -26,7 +25,6 @@ seek.
|
||||
|
||||
.. autoclass:: mopidy.backends.base.PlaybackController
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Mixer controller
|
||||
@ -42,7 +40,6 @@ Manages everything related to the currently loaded playlist.
|
||||
|
||||
.. autoclass:: mopidy.backends.base.CurrentPlaylistController
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Stored playlists controller
|
||||
@ -52,7 +49,6 @@ Manages stored playlist.
|
||||
|
||||
.. autoclass:: mopidy.backends.base.StoredPlaylistsController
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Library controller
|
||||
@ -62,4 +58,3 @@ Manages the music library, e.g. searching for tracks to be added to a playlist.
|
||||
|
||||
.. autoclass:: mopidy.backends.base.LibraryController
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
@ -14,7 +14,6 @@ Playback provider
|
||||
|
||||
.. autoclass:: mopidy.backends.base.BasePlaybackProvider
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Stored playlists provider
|
||||
@ -22,7 +21,6 @@ Stored playlists provider
|
||||
|
||||
.. autoclass:: mopidy.backends.base.BaseStoredPlaylistsProvider
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Library provider
|
||||
@ -30,7 +28,6 @@ Library provider
|
||||
|
||||
.. autoclass:: mopidy.backends.base.BaseLibraryProvider
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Backend provider implementations
|
||||
|
||||
@ -2,22 +2,26 @@
|
||||
Frontend API
|
||||
************
|
||||
|
||||
A frontend may do whatever it wants to, including creating threads, opening TCP
|
||||
ports and exposing Mopidy for a type of clients.
|
||||
|
||||
Frontends got one main limitation: they are restricted to passing messages
|
||||
through the ``core_queue`` for all communication with the rest of Mopidy. Thus,
|
||||
the frontend API is very small and reveals little of what a frontend may do.
|
||||
|
||||
.. warning::
|
||||
|
||||
A stable frontend API is not available yet, as we've only implemented a
|
||||
couple of frontend modules.
|
||||
|
||||
.. automodule:: mopidy.frontends.base
|
||||
:synopsis: Base class for frontends
|
||||
:members:
|
||||
The following requirements applies to any frontend implementation:
|
||||
|
||||
- A frontend MAY do mostly whatever it wants to, including creating threads,
|
||||
opening TCP ports and exposing Mopidy for a group of clients.
|
||||
- A frontend MUST implement at least one `Pykka
|
||||
<http://jodal.github.com/pykka/>`_ actor, called the "main actor" from here
|
||||
on.
|
||||
- It MAY use additional actors to implement whatever it does, and using actors
|
||||
in frontend implementations is encouraged.
|
||||
- The frontend is activated by including its main actor in the
|
||||
:attr:`mopidy.settings.FRONTENDS` setting.
|
||||
- The main actor MUST be able to start and stop the frontend when the main
|
||||
actor is started and stopped.
|
||||
- The frontend MAY require additional settings to be set for it to
|
||||
work.
|
||||
- Such settings MUST be documented.
|
||||
- The main actor MUST stop itself if the defined settings are not adequate for
|
||||
the frontend to work properly.
|
||||
- Any actor which is part of the frontend MAY implement any listener interface
|
||||
from :mod:`mopidy.listeners` to receive notification of the specified events.
|
||||
|
||||
Frontend implementations
|
||||
========================
|
||||
|
||||
7
docs/api/listeners.rst
Normal file
7
docs/api/listeners.rst
Normal file
@ -0,0 +1,7 @@
|
||||
************
|
||||
Listener API
|
||||
************
|
||||
|
||||
.. automodule:: mopidy.listeners
|
||||
:synopsis: Listener API
|
||||
:members:
|
||||
@ -30,7 +30,6 @@ methods as described below.
|
||||
.. automodule:: mopidy.mixers.base
|
||||
:synopsis: Mixer API
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Mixer implementations
|
||||
|
||||
@ -25,4 +25,3 @@ Data model API
|
||||
.. automodule:: mopidy.models
|
||||
:synopsis: Data model API
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
@ -8,9 +8,18 @@ This change log is used to track all major changes to Mopidy.
|
||||
v0.6.0 (in development)
|
||||
=======================
|
||||
|
||||
**Important changes**
|
||||
|
||||
- Pykka 0.12.3 or greater is required.
|
||||
|
||||
**Changes**
|
||||
|
||||
- None yet
|
||||
- Replace :attr:`mopidy.backends.base.Backend.uri_handlers` with
|
||||
:attr:`mopidy.backends.base.Backend.uri_schemes`, which just takes the part
|
||||
up to the colon of an URI, and not any prefix.
|
||||
- Add Listener API, :mod:`mopidy.listeners`, to be implemented by actors
|
||||
wanting to receive events from the backend. This is a formalization of the
|
||||
ad hoc events the Last.fm scrobbler has already been using for some time.
|
||||
|
||||
|
||||
v0.5.0 (2011-06-15)
|
||||
|
||||
@ -25,7 +25,7 @@ Otherwise, make sure you got the required dependencies installed.
|
||||
|
||||
- Python >= 2.6, < 3
|
||||
|
||||
- `Pykka <http://jodal.github.com/pykka/>`_ >= 0.12
|
||||
- `Pykka <http://jodal.github.com/pykka/>`_ >= 0.12.3
|
||||
|
||||
- GStreamer >= 0.10, with Python bindings. See :doc:`gstreamer`.
|
||||
|
||||
|
||||
@ -5,9 +5,8 @@
|
||||
.. inheritance-diagram:: mopidy.frontends.mpd
|
||||
|
||||
.. automodule:: mopidy.frontends.mpd
|
||||
:synopsis: MPD frontend
|
||||
:synopsis: MPD server frontend
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
MPD server
|
||||
@ -18,7 +17,6 @@ MPD server
|
||||
.. automodule:: mopidy.frontends.mpd.server
|
||||
:synopsis: MPD server
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
MPD session
|
||||
@ -29,7 +27,6 @@ MPD session
|
||||
.. automodule:: mopidy.frontends.mpd.session
|
||||
:synopsis: MPD client session
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
MPD dispatcher
|
||||
@ -40,7 +37,6 @@ MPD dispatcher
|
||||
.. automodule:: mopidy.frontends.mpd.dispatcher
|
||||
:synopsis: MPD request dispatcher
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
MPD protocol
|
||||
|
||||
@ -151,4 +151,3 @@ Available settings
|
||||
.. automodule:: mopidy.settings
|
||||
:synopsis: Available settings and their default values
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
@ -25,5 +25,5 @@ class Backend(object):
|
||||
#: :class:`mopidy.backends.base.StoredPlaylistsController`.
|
||||
stored_playlists = None
|
||||
|
||||
#: List of URI prefixes this backend can handle.
|
||||
uri_handlers = []
|
||||
#: List of URI schemes this backend can handle.
|
||||
uri_schemes = []
|
||||
|
||||
@ -4,7 +4,7 @@ import time
|
||||
|
||||
from pykka.registry import ActorRegistry
|
||||
|
||||
from mopidy.frontends.base import BaseFrontend
|
||||
from mopidy.listeners import BackendListener
|
||||
|
||||
logger = logging.getLogger('mopidy.backends.base')
|
||||
|
||||
@ -461,38 +461,30 @@ class PlaybackController(object):
|
||||
self.current_cp_track = None
|
||||
|
||||
def _trigger_started_playing_event(self):
|
||||
"""
|
||||
Notifies frontends that a track has started playing.
|
||||
|
||||
For internal use only. Should be called by the backend directly after a
|
||||
track has started playing.
|
||||
"""
|
||||
logger.debug(u'Triggering started playing event')
|
||||
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,
|
||||
})
|
||||
ActorRegistry.broadcast({
|
||||
'command': 'pykka_call',
|
||||
'attr_path': ('started_playing',),
|
||||
'args': [],
|
||||
'kwargs': {'track': self.current_track},
|
||||
}, target_class=BackendListener)
|
||||
|
||||
def _trigger_stopped_playing_event(self):
|
||||
"""
|
||||
Notifies frontends that a track has stopped playing.
|
||||
|
||||
For internal use only. Should be called by the backend before a track
|
||||
is stopped playing, e.g. at the next, previous, and stop actions and at
|
||||
end-of-track.
|
||||
"""
|
||||
# TODO Test that this is called on next/prev/end-of-track
|
||||
logger.debug(u'Triggering stopped playing event')
|
||||
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',
|
||||
ActorRegistry.broadcast({
|
||||
'command': 'pykka_call',
|
||||
'attr_path': ('stopped_playing',),
|
||||
'args': [],
|
||||
'kwargs': {
|
||||
'track': self.current_track,
|
||||
'stop_position': self.time_position,
|
||||
})
|
||||
'time_position': self.time_position,
|
||||
},
|
||||
}, target_class=BackendListener)
|
||||
|
||||
|
||||
class BasePlaybackProvider(object):
|
||||
|
||||
@ -32,7 +32,7 @@ class DummyBackend(ThreadingActor, Backend):
|
||||
self.stored_playlists = StoredPlaylistsController(backend=self,
|
||||
provider=stored_playlists_provider)
|
||||
|
||||
self.uri_handlers = [u'dummy:']
|
||||
self.uri_schemes = [u'dummy']
|
||||
|
||||
|
||||
class DummyLibraryProvider(BaseLibraryProvider):
|
||||
|
||||
@ -52,7 +52,7 @@ class LocalBackend(ThreadingActor, Backend):
|
||||
self.stored_playlists = StoredPlaylistsController(backend=self,
|
||||
provider=stored_playlists_provider)
|
||||
|
||||
self.uri_handlers = [u'file://']
|
||||
self.uri_schemes = [u'file']
|
||||
|
||||
self.gstreamer = None
|
||||
|
||||
|
||||
@ -67,7 +67,7 @@ class SpotifyBackend(ThreadingActor, Backend):
|
||||
self.stored_playlists = StoredPlaylistsController(backend=self,
|
||||
provider=stored_playlists_provider)
|
||||
|
||||
self.uri_handlers = [u'spotify:', u'http://open.spotify.com/']
|
||||
self.uri_schemes = [u'spotify']
|
||||
|
||||
self.gstreamer = None
|
||||
self.spotify = None
|
||||
|
||||
@ -4,7 +4,7 @@ import logging
|
||||
from spotify import Link, SpotifyError
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.backends.spotify import ENCODING, BITRATES
|
||||
from mopidy.backends.spotify import ENCODING
|
||||
from mopidy.models import Artist, Album, Track, Playlist
|
||||
|
||||
logger = logging.getLogger('mopidy.backends.spotify.translator')
|
||||
@ -44,7 +44,7 @@ class SpotifyTranslator(object):
|
||||
track_no=spotify_track.index(),
|
||||
date=date,
|
||||
length=spotify_track.duration(),
|
||||
bitrate=BITRATES[settings.SPOTIFY_BITRATE],
|
||||
bitrate=settings.SPOTIFY_BITRATE,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -25,7 +25,8 @@ from mopidy.gstreamer import GStreamer
|
||||
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 exit_handler, stop_all_actors
|
||||
from mopidy.utils.process import (exit_handler, stop_remaining_actors,
|
||||
stop_actors_by_class)
|
||||
from mopidy.utils.settings import list_settings_optparse_callback
|
||||
|
||||
logger = logging.getLogger('mopidy.core')
|
||||
@ -50,7 +51,11 @@ def main():
|
||||
logger.exception(e)
|
||||
finally:
|
||||
loop.quit()
|
||||
stop_all_actors()
|
||||
stop_frontends()
|
||||
stop_backend()
|
||||
stop_mixer()
|
||||
stop_gstreamer()
|
||||
stop_remaining_actors()
|
||||
|
||||
def parse_options():
|
||||
parser = optparse.OptionParser(version=u'Mopidy %s' % get_version())
|
||||
@ -59,7 +64,7 @@ def parse_options():
|
||||
help='show GStreamer help options')
|
||||
parser.add_option('-i', '--interactive',
|
||||
action='store_true', dest='interactive',
|
||||
help='ask interactively for required settings which is missing')
|
||||
help='ask interactively for required settings which are missing')
|
||||
parser.add_option('-q', '--quiet',
|
||||
action='store_const', const=0, dest='verbosity_level',
|
||||
help='less output (warning level)')
|
||||
@ -86,15 +91,31 @@ def setup_settings(interactive):
|
||||
def setup_gstreamer():
|
||||
GStreamer.start()
|
||||
|
||||
def stop_gstreamer():
|
||||
stop_actors_by_class(GStreamer)
|
||||
|
||||
def setup_mixer():
|
||||
get_class(settings.MIXER).start()
|
||||
|
||||
def stop_mixer():
|
||||
stop_actors_by_class(get_class(settings.MIXER))
|
||||
|
||||
def setup_backend():
|
||||
get_class(settings.BACKENDS[0]).start()
|
||||
|
||||
def stop_backend():
|
||||
stop_actors_by_class(get_class(settings.BACKENDS[0]))
|
||||
|
||||
def setup_frontends():
|
||||
for frontend_class_name in settings.FRONTENDS:
|
||||
try:
|
||||
get_class(frontend_class_name).start()
|
||||
except OptionalDependencyError as e:
|
||||
logger.info(u'Disabled: %s (%s)', frontend_class_name, e)
|
||||
|
||||
def stop_frontends():
|
||||
for frontend_class_name in settings.FRONTENDS:
|
||||
try:
|
||||
stop_actors_by_class(get_class(frontend_class_name))
|
||||
except OptionalDependencyError:
|
||||
pass
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
class BaseFrontend(object):
|
||||
"""
|
||||
Base class for frontends.
|
||||
"""
|
||||
pass
|
||||
@ -10,14 +10,14 @@ except ImportError as import_error:
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy import settings, SettingsError
|
||||
from mopidy.frontends.base import BaseFrontend
|
||||
from mopidy.listeners import BackendListener
|
||||
|
||||
logger = logging.getLogger('mopidy.frontends.lastfm')
|
||||
|
||||
API_KEY = '2236babefa8ebb3d93ea467560d00d04'
|
||||
API_SECRET = '94d9a09c0cd5be955c4afaeaffcaefcd'
|
||||
|
||||
class LastfmFrontend(ThreadingActor, BaseFrontend):
|
||||
class LastfmFrontend(ThreadingActor, BackendListener):
|
||||
"""
|
||||
Frontend which scrobbles the music you play to your `Last.fm
|
||||
<http://www.last.fm>`_ profile.
|
||||
@ -57,14 +57,6 @@ class LastfmFrontend(ThreadingActor, BaseFrontend):
|
||||
logger.error(u'Error during Last.fm setup: %s', e)
|
||||
self.stop()
|
||||
|
||||
def on_receive(self, message):
|
||||
if message.get('command') == 'started_playing':
|
||||
self.started_playing(message['track'])
|
||||
elif message.get('command') == 'stopped_playing':
|
||||
self.stopped_playing(message['track'], message['stop_position'])
|
||||
else:
|
||||
pass # Ignore any other messages
|
||||
|
||||
def started_playing(self, track):
|
||||
artists = ', '.join([a.name for a in track.artists])
|
||||
duration = track.length and track.length // 1000 or 0
|
||||
|
||||
@ -3,7 +3,6 @@ import sys
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy.frontends.base import BaseFrontend
|
||||
from mopidy import settings
|
||||
from mopidy.utils import network
|
||||
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
|
||||
@ -11,8 +10,7 @@ from mopidy.frontends.mpd.protocol import ENCODING, VERSION, LINE_TERMINATOR
|
||||
|
||||
logger = logging.getLogger('mopidy.frontends.mpd')
|
||||
|
||||
# FIXME no real need for frontend to be threading actor
|
||||
class MpdFrontend(ThreadingActor, BaseFrontend):
|
||||
class MpdFrontend(ThreadingActor):
|
||||
"""
|
||||
The MPD frontend.
|
||||
|
||||
|
||||
@ -128,7 +128,7 @@ class MpdDispatcher(object):
|
||||
return self._call_next_filter(request, response, filter_chain)
|
||||
except ActorDeadError as e:
|
||||
logger.warning(u'Tried to communicate with dead actor.')
|
||||
raise exceptions.MpdSystemError(e.message)
|
||||
raise exceptions.MpdSystemError(e)
|
||||
|
||||
def _call_handler(self, request):
|
||||
(handler, kwargs) = self._find_handler(request)
|
||||
|
||||
@ -19,8 +19,8 @@ def add(context, uri):
|
||||
"""
|
||||
if not uri:
|
||||
return
|
||||
for handler_prefix in context.backend.uri_handlers.get():
|
||||
if uri.startswith(handler_prefix):
|
||||
for uri_scheme in context.backend.uri_schemes.get():
|
||||
if uri.startswith(uri_scheme):
|
||||
track = context.backend.library.lookup(uri).get()
|
||||
if track is not None:
|
||||
context.backend.current_playlist.add(track)
|
||||
|
||||
@ -95,4 +95,5 @@ def urlhandlers(context):
|
||||
|
||||
Gets a list of available URL handlers.
|
||||
"""
|
||||
return [(u'handler', uri) for uri in context.backend.uri_handlers.get()]
|
||||
return [(u'handler', uri_scheme)
|
||||
for uri_scheme in context.backend.uri_schemes.get()]
|
||||
|
||||
34
mopidy/listeners.py
Normal file
34
mopidy/listeners.py
Normal file
@ -0,0 +1,34 @@
|
||||
class BackendListener(object):
|
||||
"""
|
||||
Marker interface for recipients of events sent by the backend.
|
||||
|
||||
Any Pykka actor that mixes in this class will receive calls to the methods
|
||||
defined here when the corresponding events happen in the backend. This
|
||||
interface is used both for looking up what actors to notify of the events,
|
||||
and for providing default implementations for those listeners that are not
|
||||
interested in all events.
|
||||
"""
|
||||
|
||||
def started_playing(self, track):
|
||||
"""
|
||||
Called whenever a new track starts playing.
|
||||
|
||||
*MAY* be implemented by actor.
|
||||
|
||||
:param track: the track that just started playing
|
||||
:type track: :class:`mopidy.models.Track`
|
||||
"""
|
||||
pass
|
||||
|
||||
def stopped_playing(self, track, time_position):
|
||||
"""
|
||||
Called whenever playback is stopped.
|
||||
|
||||
*MAY* be implemented by actor.
|
||||
|
||||
:param track: the track that was played before playback stopped
|
||||
:type track: :class:`mopidy.models.Track`
|
||||
:param time_position: the time position in milliseconds
|
||||
:type time_position: int
|
||||
"""
|
||||
pass
|
||||
@ -22,9 +22,17 @@ def exit_handler(signum, frame):
|
||||
logger.info(u'Got %s signal', signals[signum])
|
||||
exit_process()
|
||||
|
||||
def stop_all_actors():
|
||||
def stop_actors_by_class(klass):
|
||||
actors = ActorRegistry.get_by_class(klass)
|
||||
logger.debug(u'Stopping %d instance(s) of %s', len(actors), klass.__name__)
|
||||
for actor in actors:
|
||||
actor.stop()
|
||||
|
||||
def stop_remaining_actors():
|
||||
num_actors = len(ActorRegistry.get_all())
|
||||
while num_actors:
|
||||
logger.error(
|
||||
u'There are actor threads still running, this is probably a bug')
|
||||
logger.debug(u'Seeing %d actor and %d non-actor thread(s): %s',
|
||||
num_actors, threading.active_count() - num_actors,
|
||||
', '.join([t.name for t in threading.enumerate()]))
|
||||
|
||||
@ -1 +1 @@
|
||||
Pykka >= 0.12
|
||||
Pykka >= 0.12.3
|
||||
|
||||
45
tests/backends/events_test.py
Normal file
45
tests/backends/events_test.py
Normal file
@ -0,0 +1,45 @@
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
from pykka.registry import ActorRegistry
|
||||
|
||||
from mopidy.backends.dummy import DummyBackend
|
||||
from mopidy.listeners import BackendListener
|
||||
from mopidy.models import Track
|
||||
|
||||
class BackendEventsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.events = {
|
||||
'started_playing': threading.Event(),
|
||||
'stopped_playing': threading.Event(),
|
||||
}
|
||||
self.backend = DummyBackend.start().proxy()
|
||||
self.listener = DummyBackendListener.start(self.events).proxy()
|
||||
|
||||
def tearDown(self):
|
||||
ActorRegistry.stop_all()
|
||||
|
||||
def test_play_sends_started_playing_event(self):
|
||||
self.backend.current_playlist.add([Track(uri='a')])
|
||||
self.backend.playback.play()
|
||||
self.events['started_playing'].wait(timeout=1)
|
||||
self.assertTrue(self.events['started_playing'].is_set())
|
||||
|
||||
def test_stop_sends_stopped_playing_event(self):
|
||||
self.backend.current_playlist.add([Track(uri='a')])
|
||||
self.backend.playback.play()
|
||||
self.backend.playback.stop()
|
||||
self.events['stopped_playing'].wait(timeout=1)
|
||||
self.assertTrue(self.events['stopped_playing'].is_set())
|
||||
|
||||
|
||||
class DummyBackendListener(ThreadingActor, BackendListener):
|
||||
def __init__(self, events):
|
||||
self.events = events
|
||||
|
||||
def started_playing(self, track):
|
||||
self.events['started_playing'].set()
|
||||
|
||||
def stopped_playing(self, track, time_position):
|
||||
self.events['stopped_playing'].set()
|
||||
@ -36,8 +36,8 @@ class LocalPlaybackControllerTest(PlaybackControllerTest, unittest.TestCase):
|
||||
track = Track(uri=uri, length=4464)
|
||||
self.backend.current_playlist.add(track)
|
||||
|
||||
def test_uri_handler(self):
|
||||
self.assert_('file://' in self.backend.uri_handlers)
|
||||
def test_uri_scheme(self):
|
||||
self.assertIn('file', self.backend.uri_schemes)
|
||||
|
||||
def test_play_mp3(self):
|
||||
self.add_track('blank.mp3')
|
||||
|
||||
@ -76,4 +76,4 @@ class ReflectionHandlerTest(unittest.TestCase):
|
||||
def test_urlhandlers(self):
|
||||
result = self.dispatcher.handle_request(u'urlhandlers')
|
||||
self.assert_(u'OK' in result)
|
||||
self.assert_(u'handler: dummy:' in result)
|
||||
self.assert_(u'handler: dummy' in result)
|
||||
|
||||
14
tests/listeners_test.py
Normal file
14
tests/listeners_test.py
Normal file
@ -0,0 +1,14 @@
|
||||
import unittest
|
||||
|
||||
from mopidy.listeners import BackendListener
|
||||
from mopidy.models import Track
|
||||
|
||||
class BackendListenerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.listener = BackendListener()
|
||||
|
||||
def test_listener_has_default_impl_for_the_started_playing_event(self):
|
||||
self.listener.started_playing(Track())
|
||||
|
||||
def test_listener_has_default_impl_for_the_stopped_playing_event(self):
|
||||
self.listener.stopped_playing(Track(), 0)
|
||||
Loading…
Reference in New Issue
Block a user