Fix most flake8 warnings (#211)

This commit is contained in:
Stein Magnus Jodal 2012-10-16 14:00:34 +02:00
parent cef3f73d9a
commit 666800ec57
47 changed files with 531 additions and 320 deletions

View File

@ -20,12 +20,14 @@ CACHE_PATH = os.path.join(str(glib.get_user_cache_dir()), 'mopidy')
SETTINGS_PATH = os.path.join(str(glib.get_user_config_dir()), 'mopidy')
SETTINGS_FILE = os.path.join(SETTINGS_PATH, 'settings.py')
def get_version():
try:
return get_git_version()
except EnvironmentError:
return __version__
def get_git_version():
process = Popen(['git', 'describe'], stdout=PIPE, stderr=PIPE)
if process.wait() != 0:
@ -35,14 +37,17 @@ def get_git_version():
version = version[1:]
return version
def get_platform():
return platform.platform()
def get_python():
implementation = platform.python_implementation()
version = platform.python_version()
return u' '.join([implementation, version])
class MopidyException(Exception):
def __init__(self, message, *args, **kwargs):
super(MopidyException, self).__init__(message, *args, **kwargs)
@ -53,13 +58,15 @@ class MopidyException(Exception):
"""Reimplement message field that was deprecated in Python 2.6"""
return self._message
@message.setter
@message.setter # noqa
def message(self, message):
self._message = message
class SettingsError(MopidyException):
pass
class OptionalDependencyError(MopidyException):
pass

View File

@ -24,8 +24,8 @@ sys.argv[1:] = gstreamer_args
# Add ../ to the path so we can run Mopidy from a Git checkout without
# installing it on the system.
sys.path.insert(0,
os.path.abspath(os.path.join(os.path.dirname(__file__), '../')))
sys.path.insert(
0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../')))
import mopidy
@ -46,10 +46,10 @@ def main():
log.setup_logging(options.verbosity_level, options.save_debug_log)
check_old_folders()
setup_settings(options.interactive)
audio = setup_audio()
backend = setup_backend(audio)
core = setup_core(audio, backend)
setup_frontends(core)
audio_ref = setup_audio()
backend_ref = setup_backend(audio_ref)
core_ref = setup_core(audio_ref, backend_ref)
setup_frontends(core_ref)
loop.run()
except mopidy.SettingsError as ex:
logger.error(ex.message)
@ -68,25 +68,32 @@ def main():
def parse_options():
parser = optparse.OptionParser(version=u'Mopidy %s' % mopidy.get_version())
parser.add_option('--help-gst',
parser.add_option(
'--help-gst',
action='store_true', dest='help_gst',
help='show GStreamer help options')
parser.add_option('-i', '--interactive',
parser.add_option(
'-i', '--interactive',
action='store_true', dest='interactive',
help='ask interactively for required settings which are missing')
parser.add_option('-q', '--quiet',
parser.add_option(
'-q', '--quiet',
action='store_const', const=0, dest='verbosity_level',
help='less output (warning level)')
parser.add_option('-v', '--verbose',
parser.add_option(
'-v', '--verbose',
action='count', default=1, dest='verbosity_level',
help='more output (debug level)')
parser.add_option('--save-debug-log',
parser.add_option(
'--save-debug-log',
action='store_true', dest='save_debug_log',
help='save debug log to "./mopidy.log"')
parser.add_option('--list-settings',
parser.add_option(
'--list-settings',
action='callback', callback=list_settings_optparse_callback,
help='list current settings')
parser.add_option('--list-deps',
parser.add_option(
'--list-deps',
action='callback', callback=list_deps_optparse_callback,
help='list dependencies and their versions')
return parser.parse_args(args=mopidy_args)[0]
@ -98,9 +105,10 @@ def check_old_folders():
if not os.path.isdir(old_settings_folder):
return
logger.warning(u'Old settings folder found at %s, settings.py should be '
'moved to %s, any cache data should be deleted. See release notes '
'for further instructions.', old_settings_folder, mopidy.SETTINGS_PATH)
logger.warning(
u'Old settings folder found at %s, settings.py should be moved '
u'to %s, any cache data should be deleted. See release notes for '
u'further instructions.', old_settings_folder, mopidy.SETTINGS_PATH)
def setup_settings(interactive):

View File

@ -70,8 +70,8 @@ class Audio(ThreadingActor):
self._playbin.set_property('audio-sink', output)
logger.info('Output set to %s', settings.OUTPUT)
except gobject.GError as ex:
logger.error('Failed to create output "%s": %s',
settings.OUTPUT, ex)
logger.error(
'Failed to create output "%s": %s', settings.OUTPUT, ex)
process.exit_process()
def _setup_mixer(self):
@ -85,11 +85,11 @@ class Audio(ThreadingActor):
return
try:
mixerbin = gst.parse_bin_from_description(settings.MIXER,
ghost_unconnected_pads=False)
mixerbin = gst.parse_bin_from_description(
settings.MIXER, ghost_unconnected_pads=False)
except gobject.GError as ex:
logger.warning('Failed to create mixer "%s": %s',
settings.MIXER, ex)
logger.warning(
'Failed to create mixer "%s": %s', settings.MIXER, ex)
return
# We assume that the bin will contain a single mixer.
@ -215,10 +215,11 @@ class Audio(ThreadingActor):
:type volume: int
:rtype: :class:`True` if successful, else :class:`False`
"""
self._playbin.get_state() # block until state changes are done
handeled = self._playbin.seek_simple(gst.Format(gst.FORMAT_TIME),
gst.SEEK_FLAG_FLUSH, position * gst.MSECOND)
self._playbin.get_state() # block until seek is done
self._playbin.get_state() # block until state changes are done
handeled = self._playbin.seek_simple(
gst.Format(gst.FORMAT_TIME), gst.SEEK_FLAG_FLUSH,
position * gst.MSECOND)
self._playbin.get_state() # block until seek is done
return handeled
def start_playback(self):
@ -279,16 +280,16 @@ class Audio(ThreadingActor):
"""
result = self._playbin.set_state(state)
if result == gst.STATE_CHANGE_FAILURE:
logger.warning('Setting GStreamer state to %s: failed',
state.value_name)
logger.warning(
'Setting GStreamer state to %s: failed', state.value_name)
return False
elif result == gst.STATE_CHANGE_ASYNC:
logger.debug('Setting GStreamer state to %s: async',
state.value_name)
logger.debug(
'Setting GStreamer state to %s: async', state.value_name)
return True
else:
logger.debug('Setting GStreamer state to %s: OK',
state.value_name)
logger.debug(
'Setting GStreamer state to %s: OK', state.value_name)
return True
def get_volume(self):
@ -316,7 +317,8 @@ class Audio(ThreadingActor):
avg_volume = float(sum(volumes)) / len(volumes)
new_scale = (0, 100)
old_scale = (self._mixer_track.min_volume, self._mixer_track.max_volume)
old_scale = (
self._mixer_track.min_volume, self._mixer_track.max_volume)
return utils.rescale(avg_volume, old=old_scale, new=new_scale)
def set_volume(self, volume):
@ -335,7 +337,8 @@ class Audio(ThreadingActor):
return False
old_scale = (0, 100)
new_scale = (self._mixer_track.min_volume, self._mixer_track.max_volume)
new_scale = (
self._mixer_track.min_volume, self._mixer_track.max_volume)
volume = utils.rescale(volume, old=old_scale, new=new_scale)

View File

@ -5,7 +5,8 @@ import gobject
def create_track(label, initial_volume, min_volume, max_volume,
num_channels, flags):
num_channels, flags):
class Track(gst.interfaces.MixerTrack):
def __init__(self):
super(Track, self).__init__()

View File

@ -10,10 +10,11 @@ logger = logging.getLogger('mopidy.audio.mixers.auto')
# TODO: we might want to add some ranking to the mixers we know about?
class AutoAudioMixer(gst.Bin):
__gstdetails__ = ('AutoAudioMixer',
'Mixer',
'Element automatically selects a mixer.',
'Thomas Adamcik')
__gstdetails__ = (
'AutoAudioMixer',
'Mixer',
'Element automatically selects a mixer.',
'Thomas Adamcik')
def __init__(self):
gst.Bin.__init__(self)

View File

@ -7,19 +7,19 @@ from mopidy.audio.mixers import create_track
class FakeMixer(gst.Element, gst.ImplementsInterface, gst.interfaces.Mixer):
__gstdetails__ = ('FakeMixer',
'Mixer',
'Fake mixer for use in tests.',
'Thomas Adamcik')
__gstdetails__ = (
'FakeMixer',
'Mixer',
'Fake mixer for use in tests.',
'Thomas Adamcik')
track_label = gobject.property(type=str, default='Master')
track_initial_volume = gobject.property(type=int, default=0)
track_min_volume = gobject.property(type=int, default=0)
track_max_volume = gobject.property(type=int, default=100)
track_num_channels = gobject.property(type=int, default=2)
track_flags = gobject.property(type=int,
default=(gst.interfaces.MIXER_TRACK_MASTER |
gst.interfaces.MIXER_TRACK_OUTPUT))
track_flags = gobject.property(type=int, default=(
gst.interfaces.MIXER_TRACK_MASTER | gst.interfaces.MIXER_TRACK_OUTPUT))
def __init__(self):
gst.Element.__init__(self)

View File

@ -8,7 +8,7 @@ import gst
try:
import serial
except ImportError:
serial = None
serial = None # noqa
from pykka.actor import ThreadingActor
@ -19,10 +19,11 @@ logger = logging.getLogger('mopidy.audio.mixers.nad')
class NadMixer(gst.Element, gst.ImplementsInterface, gst.interfaces.Mixer):
__gstdetails__ = ('NadMixer',
'Mixer',
'Mixer to control NAD amplifiers using a serial link',
'Stein Magnus Jodal')
__gstdetails__ = (
'NadMixer',
'Mixer',
'Mixer to control NAD amplifiers using a serial link',
'Stein Magnus Jodal')
port = gobject.property(type=str, default='/dev/ttyUSB0')
source = gobject.property(type=str)
@ -41,8 +42,9 @@ class NadMixer(gst.Element, gst.ImplementsInterface, gst.interfaces.Mixer):
min_volume=0,
max_volume=100,
num_channels=1,
flags=(gst.interfaces.MIXER_TRACK_MASTER |
gst.interfaces.MIXER_TRACK_OUTPUT))
flags=(
gst.interfaces.MIXER_TRACK_MASTER |
gst.interfaces.MIXER_TRACK_OUTPUT))
return [track]
def get_volume(self, track):
@ -121,8 +123,7 @@ class NadTalker(ThreadingActor):
self._set_device_to_known_state()
def _open_connection(self):
logger.info(u'NAD amplifier: Connecting through "%s"',
self.port)
logger.info(u'NAD amplifier: Connecting through "%s"', self.port)
self._device = serial.Serial(
port=self.port,
baudrate=self.BAUDRATE,
@ -200,11 +201,13 @@ class NadTalker(ThreadingActor):
for attempt in range(1, 4):
if self._ask_device(key) == value:
return
logger.info(u'NAD amplifier: Setting "%s" to "%s" (attempt %d/3)',
logger.info(
u'NAD amplifier: Setting "%s" to "%s" (attempt %d/3)',
key, value, attempt)
self._command_device(key, value)
if self._ask_device(key) != value:
logger.info(u'NAD amplifier: Gave up on setting "%s" to "%s"',
logger.info(
u'NAD amplifier: Gave up on setting "%s" to "%s"',
key, value)
def _ask_device(self, key):

View File

@ -22,7 +22,7 @@ class BaseStoredPlaylistsProvider(object):
"""
return copy(self._playlists)
@playlists.setter
@playlists.setter # noqa
def playlists(self, playlists):
self._playlists = playlists

View File

@ -1,5 +1,4 @@
import glob
import glib
import logging
import os
import shutil
@ -8,7 +7,7 @@ from pykka.actor import ThreadingActor
from mopidy import settings
from mopidy.backends import base
from mopidy.models import Playlist, Track, Album
from mopidy.models import Playlist, Album
from .translator import parse_m3u, parse_mpd_tag_cache
@ -45,7 +44,7 @@ class LocalStoredPlaylistsProvider(base.BaseStoredPlaylistsProvider):
self.refresh()
def lookup(self, uri):
pass # TODO
pass # TODO
def refresh(self):
playlists = []
@ -118,11 +117,12 @@ class LocalLibraryProvider(base.BaseLibraryProvider):
self.refresh()
def refresh(self, uri=None):
tracks = parse_mpd_tag_cache(settings.LOCAL_TAG_CACHE_FILE,
settings.LOCAL_MUSIC_PATH)
tracks = parse_mpd_tag_cache(
settings.LOCAL_TAG_CACHE_FILE, settings.LOCAL_MUSIC_PATH)
logger.info('Loading tracks in %s from %s', settings.LOCAL_MUSIC_PATH,
settings.LOCAL_TAG_CACHE_FILE)
logger.info(
'Loading tracks in %s from %s',
settings.LOCAL_MUSIC_PATH, settings.LOCAL_TAG_CACHE_FILE)
for track in tracks:
self._uri_mapping[track.uri] = track
@ -150,7 +150,8 @@ class LocalLibraryProvider(base.BaseLibraryProvider):
artist_filter = lambda t: filter(
lambda a: q == a.name, t.artists)
uri_filter = lambda t: q == t.uri
any_filter = lambda t: (track_filter(t) or album_filter(t) or
any_filter = lambda t: (
track_filter(t) or album_filter(t) or
artist_filter(t) or uri_filter(t))
if field == 'track':
@ -178,7 +179,7 @@ class LocalLibraryProvider(base.BaseLibraryProvider):
for value in values:
q = value.strip().lower()
track_filter = lambda t: q in t.name.lower()
track_filter = lambda t: q in t.name.lower()
album_filter = lambda t: q in getattr(
t, 'album', Album()).name.lower()
artist_filter = lambda t: filter(

View File

@ -1,5 +1,4 @@
import logging
import os
logger = logging.getLogger('mopidy.backends.local.translator')
@ -7,6 +6,7 @@ from mopidy.models import Track, Artist, Album
from mopidy.utils import locale_decode
from mopidy.utils.path import path_to_uri
def parse_m3u(file_path, music_folder):
"""
Convert M3U file list of uris
@ -51,6 +51,7 @@ def parse_m3u(file_path, music_folder):
return uris
def parse_mpd_tag_cache(tag_cache, music_dir=''):
"""
Converts a MPD tag_cache into a lists of tracks, artists and albums.
@ -89,6 +90,7 @@ def parse_mpd_tag_cache(tag_cache, music_dir=''):
return tracks
def _convert_mpd_data(data, tracks, music_dir):
if not data:
return
@ -128,7 +130,8 @@ def _convert_mpd_data(data, tracks, music_dir):
artist_kwargs['musicbrainz_id'] = data['musicbrainz_artistid']
if 'musicbrainz_albumartistid' in data:
albumartist_kwargs['musicbrainz_id'] = data['musicbrainz_albumartistid']
albumartist_kwargs['musicbrainz_id'] = (
data['musicbrainz_albumartistid'])
if data['file'][0] == '/':
path = data['file'][1:]
@ -142,7 +145,7 @@ def _convert_mpd_data(data, tracks, music_dir):
if albumartist_kwargs:
albumartist = Artist(**albumartist_kwargs)
album_kwargs['artists'] = [albumartist]
if album_kwargs:
album = Album(**album_kwargs)
track_kwargs['album'] = album

View File

@ -9,6 +9,7 @@ logger = logging.getLogger('mopidy.backends.spotify')
BITRATES = {96: 2, 160: 0, 320: 1}
class SpotifyBackend(ThreadingActor, base.Backend):
"""
A backend for playing music from the `Spotify <http://www.spotify.com/>`_
@ -57,8 +58,8 @@ class SpotifyBackend(ThreadingActor, base.Backend):
username = settings.SPOTIFY_USERNAME
password = settings.SPOTIFY_PASSWORD
self.spotify = SpotifySessionManager(username, password,
audio=audio, backend_ref=self.actor_ref)
self.spotify = SpotifySessionManager(
username, password, audio=audio, backend_ref=self.actor_ref)
def on_start(self):
logger.info(u'Mopidy uses SPOTIFY(R) CORE')

View File

@ -5,6 +5,7 @@ from spotify.manager import SpotifyContainerManager as \
logger = logging.getLogger('mopidy.backends.spotify.container_manager')
class SpotifyContainerManager(PyspotifyContainerManager):
def __init__(self, session_manager):
PyspotifyContainerManager.__init__(self)
@ -25,13 +26,13 @@ class SpotifyContainerManager(PyspotifyContainerManager):
def playlist_added(self, container, playlist, position, userdata):
"""Callback used by pyspotify"""
logger.debug(u'Callback called: playlist added at position %d',
position)
logger.debug(
u'Callback called: playlist added at position %d', position)
# container_loaded() is called after this callback, so we do not need
# to handle this callback.
def playlist_moved(self, container, playlist, old_position, new_position,
userdata):
userdata):
"""Callback used by pyspotify"""
logger.debug(
u'Callback called: playlist "%s" moved from position %d to %d',

View File

@ -22,7 +22,8 @@ class SpotifyTrack(Track):
if self._track:
return self._track
elif self._spotify_track.is_loaded():
self._track = SpotifyTranslator.to_mopidy_track(self._spotify_track)
self._track = SpotifyTranslator.to_mopidy_track(
self._spotify_track)
return self._track
else:
return self._unloaded_track
@ -59,7 +60,7 @@ class SpotifyLibraryProvider(BaseLibraryProvider):
return None
def refresh(self, uri=None):
pass # TODO
pass # TODO
def search(self, **query):
if not query:
@ -81,7 +82,7 @@ class SpotifyLibraryProvider(BaseLibraryProvider):
if field == u'any':
spotify_query.append(value)
elif field == u'year':
value = int(value.split('-')[0]) # Extract year
value = int(value.split('-')[0]) # Extract year
spotify_query.append(u'%s:%d' % (field, value))
else:
spotify_query.append(u'%s:"%s"' % (field, value))
@ -90,6 +91,6 @@ class SpotifyLibraryProvider(BaseLibraryProvider):
queue = Queue.Queue()
self.backend.spotify.search(spotify_query, queue)
try:
return queue.get(timeout=3) # XXX What is an reasonable timeout?
return queue.get(timeout=3) # XXX What is an reasonable timeout?
except Queue.Empty:
return Playlist(tracks=[])

View File

@ -5,6 +5,7 @@ from spotify.manager import SpotifyPlaylistManager as PyspotifyPlaylistManager
logger = logging.getLogger('mopidy.backends.spotify.playlist_manager')
class SpotifyPlaylistManager(PyspotifyPlaylistManager):
def __init__(self, session_manager):
PyspotifyPlaylistManager.__init__(self)
@ -12,48 +13,55 @@ class SpotifyPlaylistManager(PyspotifyPlaylistManager):
def tracks_added(self, playlist, tracks, position, userdata):
"""Callback used by pyspotify"""
logger.debug(u'Callback called: '
logger.debug(
u'Callback called: '
u'%d track(s) added to position %d in playlist "%s"',
len(tracks), position, playlist.name())
self.session_manager.refresh_stored_playlists()
def tracks_moved(self, playlist, tracks, new_position, userdata):
"""Callback used by pyspotify"""
logger.debug(u'Callback called: '
logger.debug(
u'Callback called: '
u'%d track(s) moved to position %d in playlist "%s"',
len(tracks), new_position, playlist.name())
self.session_manager.refresh_stored_playlists()
def tracks_removed(self, playlist, tracks, userdata):
"""Callback used by pyspotify"""
logger.debug(u'Callback called: '
logger.debug(
u'Callback called: '
u'%d track(s) removed from playlist "%s"',
len(tracks), playlist.name())
self.session_manager.refresh_stored_playlists()
def playlist_renamed(self, playlist, userdata):
"""Callback used by pyspotify"""
logger.debug(u'Callback called: Playlist renamed to "%s"',
playlist.name())
logger.debug(
u'Callback called: Playlist renamed to "%s"', playlist.name())
self.session_manager.refresh_stored_playlists()
def playlist_state_changed(self, playlist, userdata):
"""Callback used by pyspotify"""
logger.debug(u'Callback called: The state of playlist "%s" changed',
logger.debug(
u'Callback called: The state of playlist "%s" changed',
playlist.name())
def playlist_update_in_progress(self, playlist, done, userdata):
"""Callback used by pyspotify"""
if done:
logger.debug(u'Callback called: '
u'Update of playlist "%s" done', playlist.name())
logger.debug(
u'Callback called: Update of playlist "%s" done',
playlist.name())
else:
logger.debug(u'Callback called: '
u'Update of playlist "%s" in progress', playlist.name())
logger.debug(
u'Callback called: Update of playlist "%s" in progress',
playlist.name())
def playlist_metadata_updated(self, playlist, userdata):
"""Callback used by pyspotify"""
logger.debug(u'Callback called: Metadata updated for playlist "%s"',
logger.debug(
u'Callback called: Metadata updated for playlist "%s"',
playlist.name())
def track_created_changed(self, playlist, position, user, when, userdata):
@ -90,5 +98,6 @@ class SpotifyPlaylistManager(PyspotifyPlaylistManager):
def image_changed(self, playlist, image, userdata):
"""Callback used by pyspotify"""
logger.debug(u'Callback called: Image changed for playlist "%s"',
logger.debug(
u'Callback called: Image changed for playlist "%s"',
playlist.name())

View File

@ -53,7 +53,8 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
logger.info(u'Connected to Spotify')
self.session = session
logger.debug(u'Preferred Spotify bitrate is %s kbps',
logger.debug(
u'Preferred Spotify bitrate is %s kbps',
settings.SPOTIFY_BITRATE)
self.session.set_preferred_bitrate(BITRATES[settings.SPOTIFY_BITRATE])
@ -85,7 +86,7 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
logger.debug(u'User message: %s', message.strip())
def music_delivery(self, session, frames, frame_size, num_frames,
sample_type, sample_rate, channels):
sample_type, sample_rate, channels):
"""Callback used by pyspotify"""
# pylint: disable = R0913
# Too many arguments (8/5)
@ -136,7 +137,8 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
if not self._initial_data_receive_completed:
logger.debug(u'Still getting data; skipped refresh of playlists')
return
playlists = map(SpotifyTranslator.to_mopidy_playlist,
playlists = map(
SpotifyTranslator.to_mopidy_playlist,
self.session.playlist_container())
playlists = filter(None, playlists)
self.backend.stored_playlists.playlists = playlists
@ -153,8 +155,8 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
for t in results.tracks()])
queue.put(playlist)
self.connected.wait()
self.session.search(query, callback, track_count=100,
album_count=0, artist_count=0)
self.session.search(
query, callback, track_count=100, album_count=0, artist_count=0)
def logout(self):
"""Log out from spotify"""

View File

@ -1,20 +1,21 @@
from mopidy.backends.base import BaseStoredPlaylistsProvider
class SpotifyStoredPlaylistsProvider(BaseStoredPlaylistsProvider):
def create(self, name):
pass # TODO
pass # TODO
def delete(self, playlist):
pass # TODO
pass # TODO
def lookup(self, uri):
pass # TODO
pass # TODO
def refresh(self):
pass # TODO
pass # TODO
def rename(self, playlist, new_name):
pass # TODO
pass # TODO
def save(self, playlist):
pass # TODO
pass # TODO

View File

@ -7,6 +7,7 @@ from mopidy.models import Artist, Album, Track, Playlist
logger = logging.getLogger('mopidy.backends.spotify.translator')
class SpotifyTranslator(object):
@classmethod
def to_mopidy_artist(cls, spotify_artist):
@ -57,7 +58,7 @@ class SpotifyTranslator(object):
name=spotify_playlist.name(),
# FIXME if check on link is a hackish workaround for is_local
tracks=[cls.to_mopidy_track(t) for t in spotify_playlist
if str(Link.from_track(t, 0))],
if str(Link.from_track(t, 0))],
)
except SpotifyError, e:
logger.warning(u'Failed translating Spotify playlist: %s', e)

View File

@ -6,7 +6,6 @@ from mopidy.models import CpTrack
from . import listener
logger = logging.getLogger('mopidy.core')
@ -57,7 +56,7 @@ class CurrentPlaylistController(object):
"""
return self._version
@version.setter
@version.setter # noqa
def version(self, version):
self._version = version
self.core.playback.on_current_playlist_change()
@ -128,8 +127,8 @@ class CurrentPlaylistController(object):
if key == 'cpid':
matches = filter(lambda ct: ct.cpid == value, matches)
else:
matches = filter(lambda ct: getattr(ct.track, key) == value,
matches)
matches = filter(
lambda ct: getattr(ct.track, key) == value, matches)
if len(matches) == 1:
return matches[0]
criteria_string = ', '.join(

View File

@ -283,7 +283,7 @@ class PlaybackController(object):
"""
return self._state
@state.setter
@state.setter # noqa
def state(self, new_state):
(old_state, self._state) = (self.state, new_state)
logger.debug(u'Changing state: %s -> %s', old_state, new_state)
@ -304,7 +304,7 @@ class PlaybackController(object):
# For testing
return self._volume
@volume.setter
@volume.setter # noqa
def volume(self, volume):
if self.audio:
self.audio.set_volume(volume)
@ -488,36 +488,37 @@ class PlaybackController(object):
logger.debug(u'Triggering track playback paused event')
if self.current_track is None:
return
listener.CoreListener.send('track_playback_paused',
track=self.current_track,
time_position=self.time_position)
listener.CoreListener.send(
'track_playback_paused',
track=self.current_track, time_position=self.time_position)
def _trigger_track_playback_resumed(self):
logger.debug(u'Triggering track playback resumed event')
if self.current_track is None:
return
listener.CoreListener.send('track_playback_resumed',
track=self.current_track,
time_position=self.time_position)
listener.CoreListener.send(
'track_playback_resumed',
track=self.current_track, time_position=self.time_position)
def _trigger_track_playback_started(self):
logger.debug(u'Triggering track playback started event')
if self.current_track is None:
return
listener.CoreListener.send('track_playback_started',
track=self.current_track)
listener.CoreListener.send(
'track_playback_started', track=self.current_track)
def _trigger_track_playback_ended(self):
logger.debug(u'Triggering track playback ended event')
if self.current_track is None:
return
listener.CoreListener.send('track_playback_ended',
track=self.current_track,
time_position=self.time_position)
listener.CoreListener.send(
'track_playback_ended',
track=self.current_track, time_position=self.time_position)
def _trigger_playback_state_changed(self, old_state, new_state):
logger.debug(u'Triggering playback state change event')
listener.CoreListener.send('playback_state_changed',
listener.CoreListener.send(
'playback_state_changed',
old_state=old_state, new_state=new_state)
def _trigger_options_changed(self):

View File

@ -21,7 +21,7 @@ class StoredPlaylistsController(object):
"""
return self.backend.stored_playlists.playlists.get()
@playlists.setter
@playlists.setter # noqa
def playlists(self, playlists):
self.backend.stored_playlists.playlists = playlists
@ -71,8 +71,8 @@ class StoredPlaylistsController(object):
if len(matches) == 0:
raise LookupError('"%s" match no playlists' % criteria_string)
else:
raise LookupError('"%s" match multiple playlists'
% criteria_string)
raise LookupError(
'"%s" match multiple playlists' % criteria_string)
def lookup(self, uri):
"""

View File

@ -7,7 +7,6 @@ from mopidy import core, settings
from mopidy.frontends.mpd import dispatcher, protocol
from mopidy.utils import locale_decode, log, network, process
logger = logging.getLogger('mopidy.frontends.mpd')
@ -32,11 +31,13 @@ class MpdFrontend(actor.ThreadingActor, core.CoreListener):
port = settings.MPD_SERVER_PORT
try:
network.Server(hostname, port,
network.Server(
hostname, port,
protocol=MpdSession, protocol_kwargs={'core': core},
max_connections=settings.MPD_SERVER_MAX_CONNECTIONS)
except IOError as error:
logger.error(u'MPD server startup failed: %s', locale_decode(error))
logger.error(
u'MPD server startup failed: %s', locale_decode(error))
sys.exit(1)
logger.info(u'MPD server running at [%s]:%s', hostname, port)
@ -86,15 +87,18 @@ class MpdSession(network.LineProtocol):
self.send_lines([u'OK MPD %s' % protocol.VERSION])
def on_line_received(self, line):
logger.debug(u'Request from [%s]:%s to %s: %s', self.host, self.port,
self.actor_urn, line)
logger.debug(
u'Request from [%s]:%s to %s: %s',
self.host, self.port, self.actor_urn, line)
response = self.dispatcher.handle_request(line)
if not response:
return
logger.debug(u'Response to [%s]:%s from %s: %s', self.host, self.port,
self.actor_urn, log.indent(self.terminator.join(response)))
logger.debug(
u'Response to [%s]:%s from %s: %s',
self.host, self.port, self.actor_urn,
log.indent(self.terminator.join(response)))
self.send_lines(response)
@ -105,8 +109,9 @@ class MpdSession(network.LineProtocol):
try:
return super(MpdSession, self).decode(line.decode('string_escape'))
except ValueError:
logger.warning(u'Stopping actor due to unescaping error, data '
'supplied by client was not valid.')
logger.warning(
u'Stopping actor due to unescaping error, data '
u'supplied by client was not valid.')
self.stop()
def close(self):

View File

@ -2,22 +2,22 @@ import logging
import re
from pykka import ActorDeadError
from pykka.registry import ActorRegistry
from mopidy import core, settings
from mopidy import settings
from mopidy.frontends.mpd import exceptions
from mopidy.frontends.mpd.protocol import mpd_commands, request_handlers
# Do not remove the following import. The protocol modules must be imported to
# get them registered as request handlers.
# pylint: disable = W0611
from mopidy.frontends.mpd.protocol import (audio_output, command_list,
connection, current_playlist, empty, music_db, playback, reflection,
status, stickers, stored_playlists)
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.utils import flatten
logger = logging.getLogger('mopidy.frontends.mpd.dispatcher')
class MpdDispatcher(object):
"""
The MPD session feeds the MPD dispatcher with requests. The dispatcher
@ -71,7 +71,6 @@ class MpdDispatcher(object):
else:
return response
### Filter: catch MPD ACK errors
def _catch_mpd_ack_errors_filter(self, request, response, filter_chain):
@ -82,7 +81,6 @@ class MpdDispatcher(object):
mpd_ack_error.index = self.command_list_index
return [mpd_ack_error.get_mpd_ack()]
### Filter: authenticate
def _authenticate_filter(self, request, response, filter_chain):
@ -101,7 +99,6 @@ class MpdDispatcher(object):
else:
raise exceptions.MpdPermissionError(command=command_name)
### Filter: command list
def _command_list_filter(self, request, response, filter_chain):
@ -117,25 +114,27 @@ class MpdDispatcher(object):
return response
def _is_receiving_command_list(self, request):
return (self.command_list is not False
and request != u'command_list_end')
return (
self.command_list is not False and
request != u'command_list_end')
def _is_processing_command_list(self, request):
return (self.command_list_index is not None
and request != u'command_list_end')
return (
self.command_list_index is not None and
request != u'command_list_end')
### Filter: idle
def _idle_filter(self, request, response, filter_chain):
if self._is_currently_idle() and not self._noidle.match(request):
logger.debug(u'Client sent us %s, only %s is allowed while in '
'the idle state', repr(request), repr(u'noidle'))
logger.debug(
u'Client sent us %s, only %s is allowed while in '
u'the idle state', repr(request), repr(u'noidle'))
self.context.session.close()
return []
if not self._is_currently_idle() and self._noidle.match(request):
return [] # noidle was called before idle
return [] # noidle was called before idle
response = self._call_next_filter(request, response, filter_chain)
@ -147,7 +146,6 @@ class MpdDispatcher(object):
def _is_currently_idle(self):
return bool(self.context.subscriptions)
### Filter: add OK
def _add_ok_filter(self, request, response, filter_chain):
@ -159,7 +157,6 @@ class MpdDispatcher(object):
def _has_error(self, response):
return response and response[-1].startswith(u'ACK')
### Filter: call handler
def _call_handler_filter(self, request, response, filter_chain):
@ -181,8 +178,8 @@ class MpdDispatcher(object):
return (request_handlers[pattern], matches.groupdict())
command_name = request.split(' ')[0]
if command_name in [command.name for command in mpd_commands]:
raise exceptions.MpdArgError(u'incorrect arguments',
command=command_name)
raise exceptions.MpdArgError(
u'incorrect arguments', command=command_name)
raise exceptions.MpdUnknownCommand(command=command_name)
def _format_response(self, response):

View File

@ -1,5 +1,6 @@
from mopidy import MopidyException
class MpdAckError(MopidyException):
"""See fields on this class for available MPD error codes"""
@ -33,12 +34,15 @@ class MpdAckError(MopidyException):
return u'ACK [%i@%i] {%s} %s' % (
self.__class__.error_code, self.index, self.command, self.message)
class MpdArgError(MpdAckError):
error_code = MpdAckError.ACK_ERROR_ARG
class MpdPasswordError(MpdAckError):
error_code = MpdAckError.ACK_ERROR_PASSWORD
class MpdPermissionError(MpdAckError):
error_code = MpdAckError.ACK_ERROR_PERMISSION
@ -46,6 +50,7 @@ class MpdPermissionError(MpdAckError):
super(MpdPermissionError, self).__init__(*args, **kwargs)
self.message = u'you don\'t have permission for "%s"' % self.command
class MpdUnknownCommand(MpdAckError):
error_code = MpdAckError.ACK_ERROR_UNKNOWN
@ -54,12 +59,15 @@ class MpdUnknownCommand(MpdAckError):
self.message = u'unknown command "%s"' % self.command
self.command = u''
class MpdNoExistError(MpdAckError):
error_code = MpdAckError.ACK_ERROR_NO_EXIST
class MpdSystemError(MpdAckError):
error_code = MpdAckError.ACK_ERROR_SYSTEM
class MpdNotImplemented(MpdAckError):
error_code = 0

View File

@ -29,6 +29,7 @@ mpd_commands = set()
request_handlers = {}
def handle_request(pattern, auth_required=True):
"""
Decorator for connecting command handlers to command requests.

View File

@ -1,6 +1,7 @@
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
@handle_request(r'^disableoutput "(?P<outputid>\d+)"$')
def disableoutput(context, outputid):
"""
@ -10,7 +11,8 @@ def disableoutput(context, outputid):
Turns an output off.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^enableoutput "(?P<outputid>\d+)"$')
def enableoutput(context, outputid):
@ -21,7 +23,8 @@ def enableoutput(context, outputid):
Turns an output on.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^outputs$')
def outputs(context):

View File

@ -1,6 +1,7 @@
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import MpdUnknownCommand
@handle_request(r'^command_list_begin$')
def command_list_begin(context):
"""
@ -21,6 +22,7 @@ def command_list_begin(context):
context.dispatcher.command_list = []
context.dispatcher.command_list_ok = False
@handle_request(r'^command_list_end$')
def command_list_end(context):
"""See :meth:`command_list_begin()`."""
@ -43,6 +45,7 @@ def command_list_end(context):
command_list_response.append(u'list_OK')
return command_list_response
@handle_request(r'^command_list_ok_begin$')
def command_list_ok_begin(context):
"""See :meth:`command_list_begin()`."""

View File

@ -1,7 +1,8 @@
from mopidy import settings
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import (MpdPasswordError,
MpdPermissionError)
from mopidy.frontends.mpd.exceptions import (
MpdPasswordError, MpdPermissionError)
@handle_request(r'^close$', auth_required=False)
def close(context):
@ -14,6 +15,7 @@ def close(context):
"""
context.session.close()
@handle_request(r'^kill$')
def kill(context):
"""
@ -25,6 +27,7 @@ def kill(context):
"""
raise MpdPermissionError(command=u'kill')
@handle_request(r'^password "(?P<password>[^"]+)"$', auth_required=False)
def password_(context, password):
"""
@ -40,6 +43,7 @@ def password_(context, password):
else:
raise MpdPasswordError(u'incorrect password', command=u'password')
@handle_request(r'^ping$', auth_required=False)
def ping(context):
"""

View File

@ -1,8 +1,8 @@
from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError,
MpdNotImplemented)
from mopidy.frontends.mpd import translator
from mopidy.frontends.mpd.exceptions import (
MpdArgError, MpdNoExistError, MpdNotImplemented)
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.translator import (track_to_mpd_format,
tracks_to_mpd_format)
@handle_request(r'^add "(?P<uri>[^"]*)"$')
def add(context, uri):
@ -29,6 +29,7 @@ def add(context, uri):
raise MpdNoExistError(
u'directory or file not found', command=u'add')
@handle_request(r'^addid "(?P<uri>[^"]*)"( "(?P<songpos>\d+)")*$')
def addid(context, uri, songpos=None):
"""
@ -57,10 +58,11 @@ def addid(context, uri, songpos=None):
raise MpdNoExistError(u'No such song', command=u'addid')
if songpos and songpos > context.core.current_playlist.length.get():
raise MpdArgError(u'Bad song index', command=u'addid')
cp_track = context.core.current_playlist.add(track,
at_position=songpos).get()
cp_track = context.core.current_playlist.add(
track, at_position=songpos).get()
return ('Id', cp_track.cpid)
@handle_request(r'^delete "(?P<start>\d+):(?P<end>\d+)*"$')
def delete_range(context, start, end=None):
"""
@ -81,6 +83,7 @@ def delete_range(context, start, end=None):
for (cpid, _) in cp_tracks:
context.core.current_playlist.remove(cpid=cpid)
@handle_request(r'^delete "(?P<songpos>\d+)"$')
def delete_songpos(context, songpos):
"""See :meth:`delete_range`"""
@ -92,6 +95,7 @@ def delete_songpos(context, songpos):
except IndexError:
raise MpdArgError(u'Bad song index', command=u'delete')
@handle_request(r'^deleteid "(?P<cpid>\d+)"$')
def deleteid(context, cpid):
"""
@ -109,6 +113,7 @@ def deleteid(context, cpid):
except LookupError:
raise MpdNoExistError(u'No such song', command=u'deleteid')
@handle_request(r'^clear$')
def clear(context):
"""
@ -120,6 +125,7 @@ def clear(context):
"""
context.core.current_playlist.clear()
@handle_request(r'^move "(?P<start>\d+):(?P<end>\d+)*" "(?P<to>\d+)"$')
def move_range(context, start, to, end=None):
"""
@ -137,6 +143,7 @@ def move_range(context, start, to, end=None):
to = int(to)
context.core.current_playlist.move(start, end, to)
@handle_request(r'^move "(?P<songpos>\d+)" "(?P<to>\d+)"$')
def move_songpos(context, songpos, to):
"""See :meth:`move_range`."""
@ -144,6 +151,7 @@ def move_songpos(context, songpos, to):
to = int(to)
context.core.current_playlist.move(songpos, songpos + 1, to)
@handle_request(r'^moveid "(?P<cpid>\d+)" "(?P<to>\d+)"$')
def moveid(context, cpid, to):
"""
@ -161,6 +169,7 @@ def moveid(context, cpid, to):
position = context.core.current_playlist.index(cp_track).get()
context.core.current_playlist.move(position, position + 1, to)
@handle_request(r'^playlist$')
def playlist(context):
"""
@ -176,6 +185,7 @@ def playlist(context):
"""
return playlistinfo(context)
@handle_request(r'^playlistfind (?P<tag>[^"]+) "(?P<needle>[^"]+)"$')
@handle_request(r'^playlistfind "(?P<tag>[^"]+)" "(?P<needle>[^"]+)"$')
def playlistfind(context, tag, needle):
@ -194,10 +204,11 @@ def playlistfind(context, tag, needle):
try:
cp_track = context.core.current_playlist.get(uri=needle).get()
position = context.core.current_playlist.index(cp_track).get()
return track_to_mpd_format(cp_track, position=position)
return translator.track_to_mpd_format(cp_track, position=position)
except LookupError:
return None
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^playlistid( "(?P<cpid>\d+)")*$')
def playlistid(context, cpid=None):
@ -214,19 +225,19 @@ def playlistid(context, cpid=None):
cpid = int(cpid)
cp_track = context.core.current_playlist.get(cpid=cpid).get()
position = context.core.current_playlist.index(cp_track).get()
return track_to_mpd_format(cp_track, position=position)
return translator.track_to_mpd_format(cp_track, position=position)
except LookupError:
raise MpdNoExistError(u'No such song', command=u'playlistid')
else:
return tracks_to_mpd_format(
return translator.tracks_to_mpd_format(
context.core.current_playlist.cp_tracks.get())
@handle_request(r'^playlistinfo$')
@handle_request(r'^playlistinfo "-1"$')
@handle_request(r'^playlistinfo "(?P<songpos>-?\d+)"$')
@handle_request(r'^playlistinfo "(?P<start>\d+):(?P<end>\d+)*"$')
def playlistinfo(context, songpos=None,
start=None, end=None):
def playlistinfo(context, songpos=None, start=None, end=None):
"""
*musicpd.org, current playlist section:*
@ -244,7 +255,7 @@ def playlistinfo(context, songpos=None,
if songpos is not None:
songpos = int(songpos)
cp_track = context.core.current_playlist.cp_tracks.get()[songpos]
return track_to_mpd_format(cp_track, position=songpos)
return translator.track_to_mpd_format(cp_track, position=songpos)
else:
if start is None:
start = 0
@ -256,7 +267,8 @@ def playlistinfo(context, songpos=None,
if end > context.core.current_playlist.length.get():
end = None
cp_tracks = context.core.current_playlist.cp_tracks.get()
return tracks_to_mpd_format(cp_tracks, start, end)
return translator.tracks_to_mpd_format(cp_tracks, start, end)
@handle_request(r'^playlistsearch "(?P<tag>[^"]+)" "(?P<needle>[^"]+)"$')
@handle_request(r'^playlistsearch (?P<tag>\S+) "(?P<needle>[^"]+)"$')
@ -274,7 +286,8 @@ def playlistsearch(context, tag, needle):
- does not add quotes around the tag
- uses ``filename`` and ``any`` as tags
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^plchanges (?P<version>-?\d+)$')
@handle_request(r'^plchanges "(?P<version>-?\d+)"$')
@ -295,9 +308,10 @@ def plchanges(context, version):
"""
# XXX Naive implementation that returns all tracks as changed
if int(version) < context.core.current_playlist.version:
return tracks_to_mpd_format(
return translator.tracks_to_mpd_format(
context.core.current_playlist.cp_tracks.get())
@handle_request(r'^plchangesposid "(?P<version>\d+)"$')
def plchangesposid(context, version):
"""
@ -321,6 +335,7 @@ def plchangesposid(context, version):
result.append((u'Id', cpid))
return result
@handle_request(r'^shuffle$')
@handle_request(r'^shuffle "(?P<start>\d+):(?P<end>\d+)*"$')
def shuffle(context, start=None, end=None):
@ -338,6 +353,7 @@ def shuffle(context, start=None, end=None):
end = int(end)
context.core.current_playlist.shuffle(start, end)
@handle_request(r'^swap "(?P<songpos1>\d+)" "(?P<songpos2>\d+)"$')
def swap(context, songpos1, songpos2):
"""
@ -359,6 +375,7 @@ def swap(context, songpos1, songpos2):
context.core.current_playlist.clear()
context.core.current_playlist.append(tracks)
@handle_request(r'^swapid "(?P<cpid1>\d+)" "(?P<cpid2>\d+)"$')
def swapid(context, cpid1, cpid2):
"""

View File

@ -1,5 +1,6 @@
from mopidy.frontends.mpd.protocol import handle_request
@handle_request(r'^[ ]*$')
def empty(context):
"""The original MPD server returns ``OK`` on an empty request."""

View File

@ -5,6 +5,7 @@ from mopidy.frontends.mpd.exceptions import MpdArgError, MpdNotImplemented
from mopidy.frontends.mpd.protocol import handle_request, stored_playlists
from mopidy.frontends.mpd.translator import playlist_to_mpd_format
def _build_query(mpd_query):
"""
Parses a MPD query string and converts it to the Mopidy query format.
@ -21,7 +22,7 @@ def _build_query(mpd_query):
field = m.groupdict()['field'].lower()
if field == u'title':
field = u'track'
field = str(field) # Needed for kwargs keys on OS X and Windows
field = str(field) # Needed for kwargs keys on OS X and Windows
what = m.groupdict()['what'].lower()
if field in query:
query[field].append(what)
@ -29,6 +30,7 @@ def _build_query(mpd_query):
query[field] = [what]
return query
@handle_request(r'^count "(?P<tag>[^"]+)" "(?P<needle>[^"]*)"$')
def count(context, tag, needle):
"""
@ -39,11 +41,12 @@ def count(context, tag, needle):
Counts the number of songs and their total playtime in the db
matching ``TAG`` exactly.
"""
return [('songs', 0), ('playtime', 0)] # TODO
return [('songs', 0), ('playtime', 0)] # TODO
@handle_request(r'^find '
r'(?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ilename|'
r'[Tt]itle|[Aa]ny)"? "[^"]+"\s?)+)$')
@handle_request(
r'^find (?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ilename|'
r'[Tt]itle|[Aa]ny)"? "[^"]+"\s?)+)$')
def find(context, mpd_query):
"""
*musicpd.org, music database section:*
@ -72,9 +75,11 @@ def find(context, mpd_query):
return playlist_to_mpd_format(
context.core.library.find_exact(**query).get())
@handle_request(r'^findadd '
r'(?P<query>("?([Aa]lbum|[Aa]rtist|[Ff]ilename|[Tt]itle|[Aa]ny)"? '
'"[^"]+"\s?)+)$')
@handle_request(
r'^findadd '
r'(?P<query>("?([Aa]lbum|[Aa]rtist|[Ff]ilename|[Tt]itle|[Aa]ny)"? '
r'"[^"]+"\s?)+)$')
def findadd(context, query):
"""
*musicpd.org, music database section:*
@ -88,8 +93,10 @@ def findadd(context, query):
# TODO Add result to current playlist
#result = context.find(query)
@handle_request(r'^list "?(?P<field>([Aa]rtist|[Aa]lbum|[Dd]ate|[Gg]enre))"?'
'( (?P<mpd_query>.*))?$')
@handle_request(
r'^list "?(?P<field>([Aa]rtist|[Aa]lbum|[Dd]ate|[Gg]enre))"?'
r'( (?P<mpd_query>.*))?$')
def list_(context, field, mpd_query=None):
"""
*musicpd.org, music database section:*
@ -183,7 +190,8 @@ def list_(context, field, mpd_query=None):
elif field == u'date':
return _list_date(context, query)
elif field == u'genre':
pass # TODO We don't have genre in our internal data structures yet
pass # TODO We don't have genre in our internal data structures yet
def _list_build_query(field, mpd_query):
"""Converts a ``list`` query to a Mopidy query."""
@ -208,7 +216,7 @@ def _list_build_query(field, mpd_query):
query = {}
while tokens:
key = tokens[0].lower()
key = str(key) # Needed for kwargs keys on OS X and Windows
key = str(key) # Needed for kwargs keys on OS X and Windows
value = tokens[1]
tokens = tokens[2:]
if key not in (u'artist', u'album', u'date', u'genre'):
@ -221,6 +229,7 @@ def _list_build_query(field, mpd_query):
else:
raise MpdArgError(u'not able to parse args', command=u'list')
def _list_artist(context, query):
artists = set()
playlist = context.core.library.find_exact(**query).get()
@ -229,6 +238,7 @@ def _list_artist(context, query):
artists.add((u'Artist', artist.name))
return artists
def _list_album(context, query):
albums = set()
playlist = context.core.library.find_exact(**query).get()
@ -237,6 +247,7 @@ def _list_album(context, query):
albums.add((u'Album', track.album.name))
return albums
def _list_date(context, query):
dates = set()
playlist = context.core.library.find_exact(**query).get()
@ -245,6 +256,7 @@ def _list_date(context, query):
dates.add((u'Date', track.date))
return dates
@handle_request(r'^listall "(?P<uri>[^"]+)"')
def listall(context, uri):
"""
@ -254,7 +266,8 @@ def listall(context, uri):
Lists all songs and directories in ``URI``.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^listallinfo "(?P<uri>[^"]+)"')
def listallinfo(context, uri):
@ -266,7 +279,8 @@ def listallinfo(context, uri):
Same as ``listall``, except it also returns metadata info in the
same format as ``lsinfo``.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^lsinfo$')
@handle_request(r'^lsinfo "(?P<uri>[^"]*)"$')
@ -288,7 +302,8 @@ def lsinfo(context, uri=None):
"""
if uri is None or uri == u'/' or uri == u'':
return stored_playlists.listplaylists(context)
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^rescan( "(?P<uri>[^"]+)")*$')
def rescan(context, uri=None):
@ -301,9 +316,10 @@ def rescan(context, uri=None):
"""
return update(context, uri, rescan_unmodified_files=True)
@handle_request(r'^search '
r'(?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ilename|'
r'[Tt]itle|[Aa]ny)"? "[^"]+"\s?)+)$')
@handle_request(
r'^search (?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ilename|'
r'[Tt]itle|[Aa]ny)"? "[^"]+"\s?)+)$')
def search(context, mpd_query):
"""
*musicpd.org, music database section:*
@ -335,6 +351,7 @@ def search(context, mpd_query):
return playlist_to_mpd_format(
context.core.library.search(**query).get())
@handle_request(r'^update( "(?P<uri>[^"]+)")*$')
def update(context, uri=None, rescan_unmodified_files=False):
"""
@ -352,4 +369,4 @@ def update(context, uri=None, rescan_unmodified_files=False):
identifying the update job. You can read the current job id in the
``status`` response.
"""
return {'updating_db': 0} # TODO
return {'updating_db': 0} # TODO

View File

@ -1,7 +1,8 @@
from mopidy.core import PlaybackState
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError,
MpdNotImplemented)
from mopidy.frontends.mpd.exceptions import (
MpdArgError, MpdNoExistError, MpdNotImplemented)
@handle_request(r'^consume (?P<state>[01])$')
@handle_request(r'^consume "(?P<state>[01])"$')
@ -20,6 +21,7 @@ def consume(context, state):
else:
context.core.playback.consume = False
@handle_request(r'^crossfade "(?P<seconds>\d+)"$')
def crossfade(context, seconds):
"""
@ -30,7 +32,8 @@ def crossfade(context, seconds):
Sets crossfading between songs.
"""
seconds = int(seconds)
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^next$')
def next_(context):
@ -89,6 +92,7 @@ def next_(context):
"""
return context.core.playback.next().get()
@handle_request(r'^pause$')
@handle_request(r'^pause "(?P<state>[01])"$')
def pause(context, state=None):
@ -113,6 +117,7 @@ def pause(context, state=None):
else:
context.core.playback.resume()
@handle_request(r'^play$')
def play(context):
"""
@ -121,6 +126,7 @@ def play(context):
"""
return context.core.playback.play().get()
@handle_request(r'^playid (?P<cpid>-?\d+)$')
@handle_request(r'^playid "(?P<cpid>-?\d+)"$')
def playid(context, cpid):
@ -149,6 +155,7 @@ def playid(context, cpid):
except LookupError:
raise MpdNoExistError(u'No such song', command=u'playid')
@handle_request(r'^play (?P<songpos>-?\d+)$')
@handle_request(r'^play "(?P<songpos>-?\d+)"$')
def playpos(context, songpos):
@ -182,9 +189,10 @@ def playpos(context, songpos):
except IndexError:
raise MpdArgError(u'Bad song index', command=u'play')
def _play_minus_one(context):
if (context.core.playback.state.get() == PlaybackState.PLAYING):
return # Nothing to do
return # Nothing to do
elif (context.core.playback.state.get() == PlaybackState.PAUSED):
return context.core.playback.resume().get()
elif context.core.playback.current_cp_track.get() is not None:
@ -194,7 +202,8 @@ def _play_minus_one(context):
cp_track = context.core.current_playlist.slice(0, 1).get()[0]
return context.core.playback.play(cp_track).get()
else:
return # Fail silently
return # Fail silently
@handle_request(r'^previous$')
def previous(context):
@ -242,6 +251,7 @@ def previous(context):
"""
return context.core.playback.previous().get()
@handle_request(r'^random (?P<state>[01])$')
@handle_request(r'^random "(?P<state>[01])"$')
def random(context, state):
@ -257,6 +267,7 @@ def random(context, state):
else:
context.core.playback.random = False
@handle_request(r'^repeat (?P<state>[01])$')
@handle_request(r'^repeat "(?P<state>[01])"$')
def repeat(context, state):
@ -272,6 +283,7 @@ def repeat(context, state):
else:
context.core.playback.repeat = False
@handle_request(r'^replay_gain_mode "(?P<mode>(off|track|album))"$')
def replay_gain_mode(context, mode):
"""
@ -286,7 +298,8 @@ def replay_gain_mode(context, mode):
This command triggers the options idle event.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^replay_gain_status$')
def replay_gain_status(context):
@ -298,7 +311,8 @@ def replay_gain_status(context):
Prints replay gain options. Currently, only the variable
``replay_gain_mode`` is returned.
"""
return u'off' # TODO
return u'off' # TODO
@handle_request(r'^seek (?P<songpos>\d+) (?P<seconds>\d+)$')
@handle_request(r'^seek "(?P<songpos>\d+)" "(?P<seconds>\d+)"$')
@ -319,6 +333,7 @@ def seek(context, songpos, seconds):
playpos(context, songpos)
context.core.playback.seek(int(seconds) * 1000)
@handle_request(r'^seekid "(?P<cpid>\d+)" "(?P<seconds>\d+)"$')
def seekid(context, cpid, seconds):
"""
@ -332,6 +347,7 @@ def seekid(context, cpid, seconds):
playid(context, cpid)
context.core.playback.seek(int(seconds) * 1000)
@handle_request(r'^setvol (?P<volume>[-+]*\d+)$')
@handle_request(r'^setvol "(?P<volume>[-+]*\d+)"$')
def setvol(context, volume):
@ -353,6 +369,7 @@ def setvol(context, volume):
volume = 100
context.core.playback.volume = volume
@handle_request(r'^single (?P<state>[01])$')
@handle_request(r'^single "(?P<state>[01])"$')
def single(context, state):
@ -370,6 +387,7 @@ def single(context, state):
else:
context.core.playback.single = False
@handle_request(r'^stop$')
def stop(context):
"""

View File

@ -1,6 +1,7 @@
from mopidy.frontends.mpd.protocol import handle_request, mpd_commands
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
@handle_request(r'^commands$', auth_required=False)
def commands(context):
"""
@ -13,16 +14,20 @@ def commands(context):
if context.dispatcher.authenticated:
command_names = set([command.name for command in mpd_commands])
else:
command_names = set([command.name for command in mpd_commands
command_names = set([
command.name for command in mpd_commands
if not command.auth_required])
# No one is permited to use kill, rest of commands are not listed by MPD,
# so we shouldn't either.
command_names = command_names - set(['kill', 'command_list_begin',
'command_list_ok_begin', 'command_list_ok_begin', 'command_list_end',
'idle', 'noidle', 'sticker'])
command_names = command_names - set([
'kill', 'command_list_begin', 'command_list_ok_begin',
'command_list_ok_begin', 'command_list_end', 'idle', 'noidle',
'sticker'])
return [
('command', command_name) for command_name in sorted(command_names)]
return [('command', command_name) for command_name in sorted(command_names)]
@handle_request(r'^decoders$')
def decoders(context):
@ -41,7 +46,8 @@ def decoders(context):
plugin: mpcdec
suffix: mpc
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^notcommands$', auth_required=False)
def notcommands(context):
@ -55,13 +61,15 @@ def notcommands(context):
if context.dispatcher.authenticated:
command_names = []
else:
command_names = [command.name for command in mpd_commands
if command.auth_required]
command_names = [
command.name for command in mpd_commands if command.auth_required]
# No permission to use
command_names.append('kill')
return [('command', command_name) for command_name in sorted(command_names)]
return [
('command', command_name) for command_name in sorted(command_names)]
@handle_request(r'^tagtypes$')
def tagtypes(context):
@ -72,7 +80,8 @@ def tagtypes(context):
Shows a list of available song metadata.
"""
pass # TODO
pass # TODO
@handle_request(r'^urlhandlers$')
def urlhandlers(context):
@ -83,5 +92,6 @@ def urlhandlers(context):
Gets a list of available URL handlers.
"""
return [(u'handler', uri_scheme)
return [
(u'handler', uri_scheme)
for uri_scheme in context.core.uri_schemes.get()]

View File

@ -6,8 +6,10 @@ from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.translator import track_to_mpd_format
#: Subsystems that can be registered with idle command.
SUBSYSTEMS = ['database', 'mixer', 'options', 'output',
'player', 'playlist', 'stored_playlist', 'update', ]
SUBSYSTEMS = [
'database', 'mixer', 'options', 'output', 'player', 'playlist',
'stored_playlist', 'update']
@handle_request(r'^clearerror$')
def clearerror(context):
@ -19,7 +21,8 @@ def clearerror(context):
Clears the current error message in status (this is also
accomplished by any command that starts playback).
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^currentsong$')
def currentsong(context):
@ -36,6 +39,7 @@ def currentsong(context):
position = context.core.playback.current_playlist_position.get()
return track_to_mpd_format(current_cp_track, position=position)
@handle_request(r'^idle$')
@handle_request(r'^idle (?P<subsystems>.+)$')
def idle(context, subsystems=None):
@ -93,6 +97,7 @@ def idle(context, subsystems=None):
response.append(u'changed: %s' % subsystem)
return response
@handle_request(r'^noidle$')
def noidle(context):
"""See :meth:`_status_idle`."""
@ -102,6 +107,7 @@ def noidle(context):
context.events = set()
context.session.prevent_timeout = False
@handle_request(r'^stats$')
def stats(context):
"""
@ -119,15 +125,16 @@ def stats(context):
- ``playtime``: time length of music played
"""
return {
'artists': 0, # TODO
'albums': 0, # TODO
'songs': 0, # TODO
'uptime': 0, # TODO
'db_playtime': 0, # TODO
'db_update': 0, # TODO
'playtime': 0, # TODO
'artists': 0, # TODO
'albums': 0, # TODO
'songs': 0, # TODO
'uptime': 0, # TODO
'db_playtime': 0, # TODO
'db_update': 0, # TODO
'playtime': 0, # TODO
}
@handle_request(r'^status$')
def status(context):
"""
@ -153,7 +160,7 @@ def status(context):
- ``nextsongid``: playlist songid of the next song to be played
- ``time``: total time elapsed (of current playing/paused song)
- ``elapsed``: Total time elapsed within the current song, but with
higher resolution.
higher resolution.
- ``bitrate``: instantaneous bitrate in kbps
- ``xfade``: crossfade in seconds
- ``audio``: sampleRate``:bits``:channels
@ -175,8 +182,8 @@ def status(context):
'playback.single': context.core.playback.single,
'playback.state': context.core.playback.state,
'playback.current_cp_track': context.core.playback.current_cp_track,
'playback.current_playlist_position':
context.core.playback.current_playlist_position,
'playback.current_playlist_position': (
context.core.playback.current_playlist_position),
'playback.time_position': context.core.playback.time_position,
}
pykka.future.get_all(futures.values())
@ -194,39 +201,47 @@ def status(context):
if futures['playback.current_cp_track'].get() is not None:
result.append(('song', _status_songpos(futures)))
result.append(('songid', _status_songid(futures)))
if futures['playback.state'].get() in (PlaybackState.PLAYING,
PlaybackState.PAUSED):
if futures['playback.state'].get() in (
PlaybackState.PLAYING, PlaybackState.PAUSED):
result.append(('time', _status_time(futures)))
result.append(('elapsed', _status_time_elapsed(futures)))
result.append(('bitrate', _status_bitrate(futures)))
return result
def _status_bitrate(futures):
current_cp_track = futures['playback.current_cp_track'].get()
if current_cp_track is not None:
return current_cp_track.track.bitrate
def _status_consume(futures):
if futures['playback.consume'].get():
return 1
else:
return 0
def _status_playlist_length(futures):
return futures['current_playlist.length'].get()
def _status_playlist_version(futures):
return futures['current_playlist.version'].get()
def _status_random(futures):
return int(futures['playback.random'].get())
def _status_repeat(futures):
return int(futures['playback.repeat'].get())
def _status_single(futures):
return int(futures['playback.single'].get())
def _status_songid(futures):
current_cp_track = futures['playback.current_cp_track'].get()
if current_cp_track is not None:
@ -234,9 +249,11 @@ def _status_songid(futures):
else:
return _status_songpos(futures)
def _status_songpos(futures):
return futures['playback.current_playlist_position'].get()
def _status_state(futures):
state = futures['playback.state'].get()
if state == PlaybackState.PLAYING:
@ -246,13 +263,17 @@ def _status_state(futures):
elif state == PlaybackState.PAUSED:
return u'pause'
def _status_time(futures):
return u'%d:%d' % (futures['playback.time_position'].get() // 1000,
return u'%d:%d' % (
futures['playback.time_position'].get() // 1000,
_status_time_total(futures) // 1000)
def _status_time_elapsed(futures):
return u'%.3f' % (futures['playback.time_position'].get() / 1000.0)
def _status_time_total(futures):
current_cp_track = futures['playback.current_cp_track'].get()
if current_cp_track is None:
@ -262,6 +283,7 @@ def _status_time_total(futures):
else:
return current_cp_track.track.length
def _status_volume(futures):
volume = futures['playback.volume'].get()
if volume is not None:
@ -269,5 +291,6 @@ def _status_volume(futures):
else:
return -1
def _status_xfade(futures):
return 0 # Not supported
return 0 # Not supported

View File

@ -1,7 +1,9 @@
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
@handle_request(r'^sticker delete "(?P<field>[^"]+)" '
@handle_request(
r'^sticker delete "(?P<field>[^"]+)" '
r'"(?P<uri>[^"]+)"( "(?P<name>[^"]+)")*$')
def sticker_delete(context, field, uri, name=None):
"""
@ -12,9 +14,11 @@ def sticker_delete(context, field, uri, name=None):
Deletes a sticker value from the specified object. If you do not
specify a sticker name, all sticker values are deleted.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^sticker find "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
@handle_request(
r'^sticker find "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
r'"(?P<name>[^"]+)"$')
def sticker_find(context, field, uri, name):
"""
@ -26,9 +30,11 @@ def sticker_find(context, field, uri, name):
below the specified directory (``URI``). For each matching song, it
prints the ``URI`` and that one sticker's value.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^sticker get "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
@handle_request(
r'^sticker get "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
r'"(?P<name>[^"]+)"$')
def sticker_get(context, field, uri, name):
"""
@ -38,7 +44,8 @@ def sticker_get(context, field, uri, name):
Reads a sticker value for the specified object.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^sticker list "(?P<field>[^"]+)" "(?P<uri>[^"]+)"$')
def sticker_list(context, field, uri):
@ -49,9 +56,11 @@ def sticker_list(context, field, uri):
Lists the stickers for the specified object.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^sticker set "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
@handle_request(
r'^sticker set "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
r'"(?P<name>[^"]+)" "(?P<value>[^"]+)"$')
def sticker_set(context, field, uri, name, value):
"""
@ -62,4 +71,4 @@ def sticker_set(context, field, uri, name, value):
Adds a sticker value to the specified object. If a sticker item
with that name already exists, it is replaced.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO

View File

@ -4,6 +4,7 @@ from mopidy.frontends.mpd.exceptions import MpdNoExistError, MpdNotImplemented
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.translator import playlist_to_mpd_format
@handle_request(r'^listplaylist "(?P<name>[^"]+)"$')
def listplaylist(context, name):
"""
@ -25,6 +26,7 @@ def listplaylist(context, name):
except LookupError:
raise MpdNoExistError(u'No such playlist', command=u'listplaylist')
@handle_request(r'^listplaylistinfo "(?P<name>[^"]+)"$')
def listplaylistinfo(context, name):
"""
@ -46,6 +48,7 @@ def listplaylistinfo(context, name):
raise MpdNoExistError(
u'No such playlist', command=u'listplaylistinfo')
@handle_request(r'^listplaylists$')
def listplaylists(context):
"""
@ -70,8 +73,8 @@ def listplaylists(context):
result = []
for playlist in context.core.stored_playlists.playlists.get():
result.append((u'playlist', playlist.name))
last_modified = (playlist.last_modified or
dt.datetime.now()).isoformat()
last_modified = (
playlist.last_modified or dt.datetime.now()).isoformat()
# Remove microseconds
last_modified = last_modified.split('.')[0]
# Add time zone information
@ -80,6 +83,7 @@ def listplaylists(context):
result.append((u'Last-Modified', last_modified))
return result
@handle_request(r'^load "(?P<name>[^"]+)"$')
def load(context, name):
"""
@ -99,6 +103,7 @@ def load(context, name):
except LookupError:
raise MpdNoExistError(u'No such playlist', command=u'load')
@handle_request(r'^playlistadd "(?P<name>[^"]+)" "(?P<uri>[^"]+)"$')
def playlistadd(context, name, uri):
"""
@ -110,7 +115,8 @@ def playlistadd(context, name, uri):
``NAME.m3u`` will be created if it does not exist.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^playlistclear "(?P<name>[^"]+)"$')
def playlistclear(context, name):
@ -121,7 +127,8 @@ def playlistclear(context, name):
Clears the playlist ``NAME.m3u``.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^playlistdelete "(?P<name>[^"]+)" "(?P<songpos>\d+)"$')
def playlistdelete(context, name, songpos):
@ -132,9 +139,11 @@ def playlistdelete(context, name, songpos):
Deletes ``SONGPOS`` from the playlist ``NAME.m3u``.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^playlistmove "(?P<name>[^"]+)" '
@handle_request(
r'^playlistmove "(?P<name>[^"]+)" '
r'"(?P<from_pos>\d+)" "(?P<to_pos>\d+)"$')
def playlistmove(context, name, from_pos, to_pos):
"""
@ -151,7 +160,8 @@ def playlistmove(context, name, from_pos, to_pos):
documentation, but just the ``SONGPOS`` to move *from*, i.e.
``playlistmove {NAME} {FROM_SONGPOS} {TO_SONGPOS}``.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^rename "(?P<old_name>[^"]+)" "(?P<new_name>[^"]+)"$')
def rename(context, old_name, new_name):
@ -162,7 +172,8 @@ def rename(context, old_name, new_name):
Renames the playlist ``NAME.m3u`` to ``NEW_NAME.m3u``.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^rm "(?P<name>[^"]+)"$')
def rm(context, name):
@ -173,7 +184,8 @@ def rm(context, name):
Removes the playlist ``NAME.m3u`` from the playlist directory.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO
@handle_request(r'^save "(?P<name>[^"]+)"$')
def save(context, name):
@ -185,4 +197,4 @@ def save(context, name):
Saves the current playlist to ``NAME.m3u`` in the playlist
directory.
"""
raise MpdNotImplemented # TODO
raise MpdNotImplemented # TODO

View File

@ -6,6 +6,7 @@ from mopidy.frontends.mpd import protocol
from mopidy.models import CpTrack
from mopidy.utils.path import mtime as get_mtime, uri_to_path, split_path
def track_to_mpd_format(track, position=None):
"""
Format track for output to MPD client.
@ -48,8 +49,8 @@ def track_to_mpd_format(track, position=None):
# FIXME don't use first and best artist?
# FIXME don't duplicate following code?
if track.album is not None and track.album.artists:
artists = filter(lambda a: a.musicbrainz_id is not None,
track.album.artists)
artists = filter(
lambda a: a.musicbrainz_id is not None, track.album.artists)
if artists:
result.append(
('MUSICBRAINZ_ALBUMARTISTID', artists[0].musicbrainz_id))
@ -61,16 +62,19 @@ def track_to_mpd_format(track, position=None):
result.append(('MUSICBRAINZ_TRACKID', track.musicbrainz_id))
return result
MPD_KEY_ORDER = '''
key file Time Artist AlbumArtist Title Album Track Date MUSICBRAINZ_ALBUMID
MUSICBRAINZ_ALBUMARTISTID MUSICBRAINZ_ARTISTID MUSICBRAINZ_TRACKID mtime
'''.split()
def order_mpd_track_info(result):
"""
Order results from :func:`mopidy.frontends.mpd.translator.track_to_mpd_format`
so that it matches MPD's ordering. Simply a cosmetic fix for easier
diffing of tag_caches.
Order results from
:func:`mopidy.frontends.mpd.translator.track_to_mpd_format` so that it
matches MPD's ordering. Simply a cosmetic fix for easier diffing of
tag_caches.
:param result: the track info
:type result: list of tuples
@ -78,6 +82,7 @@ def order_mpd_track_info(result):
"""
return sorted(result, key=lambda i: MPD_KEY_ORDER.index(i[0]))
def artists_to_mpd_format(artists):
"""
Format track artists for output to MPD client.
@ -90,6 +95,7 @@ def artists_to_mpd_format(artists):
artists.sort(key=lambda a: a.name)
return u', '.join([a.name for a in artists if a.name])
def tracks_to_mpd_format(tracks, start=0, end=None):
"""
Format list of tracks for output to MPD client.
@ -115,6 +121,7 @@ def tracks_to_mpd_format(tracks, start=0, end=None):
result.append(track_to_mpd_format(track, position))
return result
def playlist_to_mpd_format(playlist, *args, **kwargs):
"""
Format playlist for output to MPD client.
@ -123,6 +130,7 @@ def playlist_to_mpd_format(playlist, *args, **kwargs):
"""
return tracks_to_mpd_format(playlist.tracks, *args, **kwargs)
def tracks_to_tag_cache_format(tracks):
"""
Format list of tracks for output to MPD tag cache
@ -141,6 +149,7 @@ def tracks_to_tag_cache_format(tracks):
_add_to_tag_cache(result, *tracks_to_directory_tree(tracks))
return result
def _add_to_tag_cache(result, folders, files):
music_folder = settings.LOCAL_MUSIC_PATH
regexp = '^' + re.escape(music_folder).rstrip('/') + '/?'
@ -165,6 +174,7 @@ def _add_to_tag_cache(result, folders, files):
result.extend(track_result)
result.append(('songList end',))
def tracks_to_directory_tree(tracks):
directories = ({}, [])
for track in tracks:

View File

@ -5,7 +5,7 @@ logger = logging.getLogger('mopidy.frontends.mpris')
try:
import indicate
except ImportError as import_error:
indicate = None
indicate = None # noqa
logger.debug(u'Startup notification will not be sent (%s)', import_error)
from pykka.actor import ThreadingActor
@ -100,8 +100,8 @@ class MprisFrontend(ThreadingActor, core.CoreListener):
props_with_new_values = [
(p, self.mpris_object.Get(objects.PLAYER_IFACE, p))
for p in changed_properties]
self.mpris_object.PropertiesChanged(objects.PLAYER_IFACE,
dict(props_with_new_values), [])
self.mpris_object.PropertiesChanged(
objects.PLAYER_IFACE, dict(props_with_new_values), [])
def track_playback_paused(self, track, time_position):
logger.debug(u'Received track playback paused event')

View File

@ -77,8 +77,8 @@ class MprisObject(dbus.service.Object):
def _connect_to_dbus(self):
logger.debug(u'Connecting to D-Bus...')
mainloop = dbus.mainloop.glib.DBusGMainLoop()
bus_name = dbus.service.BusName(BUS_NAME,
dbus.SessionBus(mainloop=mainloop))
bus_name = dbus.service.BusName(
BUS_NAME, dbus.SessionBus(mainloop=mainloop))
logger.info(u'Connected to D-Bus')
return bus_name
@ -92,9 +92,10 @@ class MprisObject(dbus.service.Object):
### Properties interface
@dbus.service.method(dbus_interface=dbus.PROPERTIES_IFACE,
in_signature='ss', out_signature='v')
in_signature='ss', out_signature='v')
def Get(self, interface, prop):
logger.debug(u'%s.Get(%s, %s) called',
logger.debug(
u'%s.Get(%s, %s) called',
dbus.PROPERTIES_IFACE, repr(interface), repr(prop))
(getter, setter) = self.properties[interface][prop]
if callable(getter):
@ -103,35 +104,36 @@ class MprisObject(dbus.service.Object):
return getter
@dbus.service.method(dbus_interface=dbus.PROPERTIES_IFACE,
in_signature='s', out_signature='a{sv}')
in_signature='s', out_signature='a{sv}')
def GetAll(self, interface):
logger.debug(u'%s.GetAll(%s) called',
dbus.PROPERTIES_IFACE, repr(interface))
logger.debug(
u'%s.GetAll(%s) called', dbus.PROPERTIES_IFACE, repr(interface))
getters = {}
for key, (getter, setter) in self.properties[interface].iteritems():
getters[key] = getter() if callable(getter) else getter
return getters
@dbus.service.method(dbus_interface=dbus.PROPERTIES_IFACE,
in_signature='ssv', out_signature='')
in_signature='ssv', out_signature='')
def Set(self, interface, prop, value):
logger.debug(u'%s.Set(%s, %s, %s) called',
logger.debug(
u'%s.Set(%s, %s, %s) called',
dbus.PROPERTIES_IFACE, repr(interface), repr(prop), repr(value))
getter, setter = self.properties[interface][prop]
if setter is not None:
setter(value)
self.PropertiesChanged(interface,
{prop: self.Get(interface, prop)}, [])
self.PropertiesChanged(
interface, {prop: self.Get(interface, prop)}, [])
@dbus.service.signal(dbus_interface=dbus.PROPERTIES_IFACE,
signature='sa{sv}as')
signature='sa{sv}as')
def PropertiesChanged(self, interface, changed_properties,
invalidated_properties):
logger.debug(u'%s.PropertiesChanged(%s, %s, %s) signaled',
invalidated_properties):
logger.debug(
u'%s.PropertiesChanged(%s, %s, %s) signaled',
dbus.PROPERTIES_IFACE, interface, changed_properties,
invalidated_properties)
### Root interface methods
@dbus.service.method(dbus_interface=ROOT_IFACE)
@ -144,7 +146,6 @@ class MprisObject(dbus.service.Object):
logger.debug(u'%s.Quit called', ROOT_IFACE)
exit_process()
### Root interface properties
def get_DesktopEntry(self):
@ -153,7 +154,6 @@ class MprisObject(dbus.service.Object):
def get_SupportedUriSchemes(self):
return dbus.Array(self.core.uri_schemes.get(), signature='s')
### Player interface methods
@dbus.service.method(dbus_interface=PLAYER_IFACE)
@ -263,7 +263,6 @@ class MprisObject(dbus.service.Object):
else:
logger.debug(u'Track with URI "%s" not found in library.', uri)
### Player interface signals
@dbus.service.signal(dbus_interface=PLAYER_IFACE, signature='x')
@ -271,7 +270,6 @@ class MprisObject(dbus.service.Object):
logger.debug(u'%s.Seeked signaled', PLAYER_IFACE)
# Do nothing, as just calling the method is enough to emit the signal.
### Player interface properties
def get_PlaybackStatus(self):
@ -383,20 +381,23 @@ class MprisObject(dbus.service.Object):
def get_CanGoNext(self):
if not self.get_CanControl():
return False
return (self.core.playback.cp_track_at_next.get() !=
return (
self.core.playback.cp_track_at_next.get() !=
self.core.playback.current_cp_track.get())
def get_CanGoPrevious(self):
if not self.get_CanControl():
return False
return (self.core.playback.cp_track_at_previous.get() !=
return (
self.core.playback.cp_track_at_previous.get() !=
self.core.playback.current_cp_track.get())
def get_CanPlay(self):
if not self.get_CanControl():
return False
return (self.core.playback.current_track.get() is not None
or self.core.playback.track_at_next.get() is not None)
return (
self.core.playback.current_track.get() is not None or
self.core.playback.track_at_next.get() is not None)
def get_CanPause(self):
if not self.get_CanControl():

View File

@ -13,8 +13,9 @@ class ImmutableObject(object):
def __init__(self, *args, **kwargs):
for key, value in kwargs.items():
if not hasattr(self, key):
raise TypeError('__init__() got an unexpected keyword ' + \
'argument \'%s\'' % key)
raise TypeError(
u"__init__() got an unexpected keyword argument '%s'" %
key)
self.__dict__[key] = value
def __setattr__(self, name, value):
@ -71,8 +72,8 @@ class ImmutableObject(object):
if hasattr(self, key):
data[key] = values.pop(key)
if values:
raise TypeError("copy() got an unexpected keyword argument '%s'"
% key)
raise TypeError(
u"copy() got an unexpected keyword argument '%s'" % key)
return self.__class__(**data)
def serialize(self):

View File

@ -10,6 +10,7 @@ import datetime
from mopidy.utils.path import path_to_uri, find_files
from mopidy.models import Track, Artist, Album
def translator(data):
albumartist_kwargs = {}
album_kwargs = {}
@ -37,7 +38,8 @@ def translator(data):
_retrieve('musicbrainz-trackid', 'musicbrainz_id', track_kwargs)
_retrieve('musicbrainz-artistid', 'musicbrainz_id', artist_kwargs)
_retrieve('musicbrainz-albumid', 'musicbrainz_id', album_kwargs)
_retrieve('musicbrainz-albumartistid', 'musicbrainz_id', albumartist_kwargs)
_retrieve(
'musicbrainz-albumartistid', 'musicbrainz_id', albumartist_kwargs)
if albumartist_kwargs:
album_kwargs['artists'] = [Artist(**albumartist_kwargs)]
@ -61,8 +63,8 @@ class Scanner(object):
self.uribin = gst.element_factory_make('uridecodebin')
self.uribin.set_property('caps', gst.Caps('audio/x-raw-int'))
self.uribin.connect('pad-added', self.process_new_pad,
fakesink.get_pad('sink'))
self.uribin.connect(
'pad-added', self.process_new_pad, fakesink.get_pad('sink'))
self.pipe = gst.element_factory_make('pipeline')
self.pipe.add(self.uribin)
@ -106,7 +108,7 @@ class Scanner(object):
self.next_uri()
def get_duration(self):
self.pipe.get_state() # Block until state change is done.
self.pipe.get_state() # Block until state change is done.
try:
return self.pipe.query_duration(
gst.FORMAT_TIME, None)[0] // gst.MSECOND

View File

@ -2,7 +2,6 @@ from __future__ import division
import locale
import logging
import os
import sys
logger = logging.getLogger('mopidy.utils')

View File

@ -61,8 +61,8 @@ def platform_info():
def python_info():
return {
'name': 'Python',
'version': '%s %s' % (platform.python_implementation(),
platform.python_version()),
'version': '%s %s' % (
platform.python_implementation(), platform.python_version()),
'path': platform.__file__,
}
@ -125,9 +125,11 @@ def _gstreamer_check_elements():
# Shoutcast output
'shout2send',
]
known_elements = [factory.get_name() for factory in
known_elements = [
factory.get_name() for factory in
gst.registry_get_default().get_feature_list(gst.TYPE_ELEMENT_FACTORY)]
return [(element, element in known_elements) for element in elements_to_check]
return [
(element, element in known_elements) for element in elements_to_check]
def pykka_info():

View File

@ -3,6 +3,7 @@ import logging.handlers
from mopidy import get_version, get_platform, get_python, settings
def setup_logging(verbosity_level, save_debug_log):
setup_root_logger()
setup_console_logging(verbosity_level)
@ -13,10 +14,12 @@ def setup_logging(verbosity_level, save_debug_log):
logger.info(u'Platform: %s', get_platform())
logger.info(u'Python: %s', get_python())
def setup_root_logger():
root = logging.getLogger('')
root.setLevel(logging.DEBUG)
def setup_console_logging(verbosity_level):
if verbosity_level == 0:
log_level = logging.WARNING
@ -37,6 +40,7 @@ def setup_console_logging(verbosity_level):
if verbosity_level < 3:
logging.getLogger('pykka').setLevel(logging.INFO)
def setup_debug_logging_to_file():
formatter = logging.Formatter(settings.DEBUG_LOG_FORMAT)
handler = logging.handlers.RotatingFileHandler(
@ -46,6 +50,7 @@ def setup_debug_logging_to_file():
root = logging.getLogger('')
root.addHandler(handler)
def indent(string, places=4, linebreak='\n'):
lines = string.split(linebreak)
if len(lines) == 1:

View File

@ -27,8 +27,10 @@ def try_ipv6_socket():
socket.socket(socket.AF_INET6).close()
return True
except IOError as error:
logger.debug(u'Platform supports IPv6, but socket '
'creation failed, disabling: %s', locale_decode(error))
logger.debug(
u'Platform supports IPv6, but socket creation failed, '
u'disabling: %s',
locale_decode(error))
return False
@ -59,7 +61,7 @@ class Server(object):
"""Setup listener and register it with gobject's event loop."""
def __init__(self, host, port, protocol, protocol_kwargs=None,
max_connections=5, timeout=30):
max_connections=5, timeout=30):
self.protocol = protocol
self.protocol_kwargs = protocol_kwargs or {}
self.max_connections = max_connections
@ -114,8 +116,8 @@ class Server(object):
pass
def init_connection(self, sock, addr):
Connection(self.protocol, self.protocol_kwargs,
sock, addr, self.timeout)
Connection(
self.protocol, self.protocol_kwargs, sock, addr, self.timeout)
class Connection(object):
@ -130,7 +132,7 @@ class Connection(object):
def __init__(self, protocol, protocol_kwargs, sock, addr, timeout):
sock.setblocking(False)
self.host, self.port = addr[:2] # IPv6 has larger addr
self.host, self.port = addr[:2] # IPv6 has larger addr
self.sock = sock
self.protocol = protocol
@ -214,7 +216,8 @@ class Connection(object):
return
try:
self.recv_id = gobject.io_add_watch(self.sock.fileno(),
self.recv_id = gobject.io_add_watch(
self.sock.fileno(),
gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP,
self.recv_callback)
except socket.error as e:
@ -231,7 +234,8 @@ class Connection(object):
return
try:
self.send_id = gobject.io_add_watch(self.sock.fileno(),
self.send_id = gobject.io_add_watch(
self.sock.fileno(),
gobject.IO_OUT | gobject.IO_ERR | gobject.IO_HUP,
self.send_callback)
except socket.error as e:
@ -372,8 +376,10 @@ class LineProtocol(ThreadingActor):
try:
return line.encode(self.encoding)
except UnicodeError:
logger.warning(u'Stopping actor due to encode problem, data '
'supplied by client was not valid %s', self.encoding)
logger.warning(
u'Stopping actor due to encode problem, data '
u'supplied by client was not valid %s',
self.encoding)
self.stop()
def decode(self, line):
@ -385,8 +391,10 @@ class LineProtocol(ThreadingActor):
try:
return line.decode(self.encoding)
except UnicodeError:
logger.warning(u'Stopping actor due to decode problem, data '
'supplied by client was not valid %s', self.encoding)
logger.warning(
u'Stopping actor due to decode problem, data '
u'supplied by client was not valid %s',
self.encoding)
self.stop()
def join_lines(self, lines):

View File

@ -18,8 +18,9 @@ XDG_DIRS = {
def get_or_create_folder(folder):
folder = os.path.expanduser(folder)
if os.path.isfile(folder):
raise OSError('A file with the same name as the desired ' \
'dir, "%s", already exists.' % folder)
raise OSError(
u'A file with the same name as the desired dir, '
u'"%s", already exists.' % folder)
elif not os.path.isdir(folder):
logger.info(u'Creating dir %s', folder)
os.makedirs(folder, 0755)
@ -47,7 +48,7 @@ def uri_to_path(uri):
path = urllib.url2pathname(re.sub('^file:', '', uri))
else:
path = urllib.url2pathname(re.sub('^file://', '', uri))
return path.encode('latin1').decode('utf-8') # Undo double encoding
return path.encode('latin1').decode('utf-8') # Undo double encoding
def split_path(path):

View File

@ -10,30 +10,35 @@ from mopidy import SettingsError
logger = logging.getLogger('mopidy.utils.process')
def exit_process():
logger.debug(u'Interrupting main...')
thread.interrupt_main()
logger.debug(u'Interrupted main')
def exit_handler(signum, frame):
"""A :mod:`signal` handler which will exit the program on signal."""
signals = dict((k, v) for v, k in signal.__dict__.iteritems()
if v.startswith('SIG') and not v.startswith('SIG_'))
if v.startswith('SIG') and not v.startswith('SIG_'))
logger.info(u'Got %s signal', signals[signum])
exit_process()
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',
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()]))
logger.debug(u'Stopping %d actor(s)...', num_actors)
@ -41,6 +46,7 @@ def stop_remaining_actors():
num_actors = len(ActorRegistry.get_all())
logger.debug(u'All actors stopped.')
class BaseThread(threading.Thread):
def __init__(self):
super(BaseThread, self).__init__()

View File

@ -32,7 +32,8 @@ class SettingsProxy(object):
return self._get_settings_dict_from_module(local_settings_module)
def _get_settings_dict_from_module(self, module):
settings = filter(lambda (key, value): self._is_setting(key),
settings = filter(
lambda (key, value): self._is_setting(key),
module.__dict__.iteritems())
return dict(settings)
@ -50,7 +51,7 @@ class SettingsProxy(object):
if not self._is_setting(attr):
return
current = self.current # bind locally to avoid copying+updates
current = self.current # bind locally to avoid copying+updates
if attr not in current:
raise SettingsError(u'Setting "%s" is not set.' % attr)
@ -73,7 +74,8 @@ class SettingsProxy(object):
if interactive:
self._read_missing_settings_from_stdin(self.current, self.runtime)
if self.get_errors():
logger.error(u'Settings validation errors: %s',
logger.error(
u'Settings validation errors: %s',
log.indent(self.get_errors_as_string()))
raise SettingsError(u'Settings validation failed.')
@ -84,11 +86,13 @@ class SettingsProxy(object):
def _read_from_stdin(self, prompt):
if u'_PASSWORD' in prompt:
return (getpass.getpass(prompt)
return (
getpass.getpass(prompt)
.decode(sys.stdin.encoding, 'ignore'))
else:
sys.stdout.write(prompt)
return (sys.stdin.readline().strip()
return (
sys.stdin.readline().strip()
.decode(sys.stdin.encoding, 'ignore'))
def get_errors(self):
@ -201,7 +205,8 @@ def format_settings_list(settings):
lines.append(u'%s: %s' % (
key, log.indent(pprint.pformat(masked_value), places=2)))
if value != default_value and default_value is not None:
lines.append(u' Default: %s' %
lines.append(
u' Default: %s' %
log.indent(pprint.pformat(default_value), places=4))
if errors.get(key) is not None:
lines.append(u' Error: %s' % errors[key])
@ -235,13 +240,13 @@ def levenshtein(a, b, max=3):
if n > m:
return levenshtein(b, a)
current = xrange(n+1)
for i in xrange(1, m+1):
current = xrange(n + 1)
for i in xrange(1, m + 1):
previous, current = current, [i] + [0] * n
for j in xrange(1, n+1):
add, delete = previous[j] + 1, current[j-1] + 1
change = previous[j-1]
if a[j-1] != b[i-1]:
for j in xrange(1, n + 1):
add, delete = previous[j] + 1, current[j - 1] + 1
change = previous[j - 1]
if a[j - 1] != b[i - 1]:
change += 1
current[j] = min(add, delete, change)
return current[n]