Fix most flake8 warnings (#211)
This commit is contained in:
parent
cef3f73d9a
commit
666800ec57
@ -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
|
||||
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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__()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -22,7 +22,7 @@ class BaseStoredPlaylistsProvider(object):
|
||||
"""
|
||||
return copy(self._playlists)
|
||||
|
||||
@playlists.setter
|
||||
@playlists.setter # noqa
|
||||
def playlists(self, playlists):
|
||||
self._playlists = playlists
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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=[])
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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"""
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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):
|
||||
"""
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ mpd_commands = set()
|
||||
|
||||
request_handlers = {}
|
||||
|
||||
|
||||
def handle_request(pattern, auth_required=True):
|
||||
"""
|
||||
Decorator for connecting command handlers to command requests.
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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()`."""
|
||||
|
||||
@ -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):
|
||||
"""
|
||||
|
||||
@ -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):
|
||||
"""
|
||||
|
||||
@ -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."""
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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):
|
||||
"""
|
||||
|
||||
@ -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()]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
@ -2,7 +2,6 @@ from __future__ import division
|
||||
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
logger = logging.getLogger('mopidy.utils')
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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__()
|
||||
|
||||
@ -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]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user