Merge branch 'develop' into feature/mpris-frontend

This commit is contained in:
Stein Magnus Jodal 2011-04-27 00:45:17 +02:00
commit ba1ab11b7b
12 changed files with 116 additions and 45 deletions

View File

@ -1,4 +1,4 @@
include LICENSE pylintrc *.rst data/mopidy.desktop
include LICENSE pylintrc *.rst *.ini data/mopidy.desktop
include mopidy/backends/spotify/spotify_appkey.key
recursive-include docs *
prune docs/_build

View File

@ -5,11 +5,35 @@ Changes
This change log is used to track all major changes to Mopidy.
0.4.0 (in development)
0.5.0 (in development)
======================
No description yet.
**Changes**
No changes yet.
0.4.0 (2011-04-27)
==================
Mopidy 0.4.0 is another release without major feature additions. In 0.4.0 we've
fixed a bunch of issues and bugs, with the help of several new contributors
who are credited in the changelog below. The major change of 0.4.0 is an
internal refactoring which clears way for future features, and which also make
Mopidy work on Python 2.7. In other words, Mopidy 0.4.0 works on Ubuntu 11.04
and Arch Linux.
Please note that 0.4.0 requires some updated dependencies, as listed under
*Important changes* below. Also, the known bug in the Spotify playlist
loading from Mopidy 0.3.0 is still present.
.. warning:: Known bug in Spotify playlist loading
There is a known bug in the loading of Spotify playlists. To avoid the bug,
follow the simple workaround described at :issue:`59`.
**Important changes**
@ -30,7 +54,7 @@ No description yet.
- Mopidy now use Pykka actors for thread management and inter-thread
communication. The immediate advantage of this is that Mopidy now works on
Python 2.7. (Fixes: :issue:`66`)
Python 2.7, which is the default on e.g. Ubuntu 11.04. (Fixes: :issue:`66`)
- Spotify backend:
@ -43,6 +67,8 @@ No description yet.
- Reduce log level for trivial log messages from warning to info. (Fixes:
:issue:`71`)
- Pause playback on network connection errors. (Fixes: :issue:`65`)
- Local backend:
- Fix crash in :command:`mopidy-scan` if a track has no artist name. Thanks
@ -64,6 +90,14 @@ No description yet.
- Fix bug where ``status`` returned ``song: None``, which caused MPDroid to
crash. (Fixes: :issue:`69`)
- Gracefully fallback to IPv4 sockets on systems that supports IPv6, but has
turned it off. (Fixes: :issue:`75`)
- GStreamer output:
- Use ``uridecodebin`` for playing audio from both Spotify and the local
backend. This contributes to support for multiple backends simultaneously.
- Settings:
- Fix crash on ``--list-settings`` on clean installation. Thanks to Martins
@ -74,6 +108,11 @@ No description yet.
- Replace test data symlinks with real files to avoid symlink issues when
installing with pip. (Fixes: :issue:`68`)
- Debugging:
- Include platform, architecture, Linux distribution, and Python version in
the debug log, to ease debugging of issues with attached debug logs.
0.3.1 (2010-01-22)
==================

View File

@ -1,10 +1,17 @@
import platform
import sys
if not (2, 6) <= sys.version_info < (3,):
sys.exit(u'Mopidy requires Python >= 2.6, < 3')
from subprocess import PIPE, Popen
VERSION = (0, 4, 0)
VERSION = (0, 5, 0)
def get_version():
try:
return get_git_version()
except EnvironmentError:
return get_plain_version()
def get_git_version():
process = Popen(['git', 'describe'], stdout=PIPE, stderr=PIPE)
@ -18,11 +25,13 @@ def get_git_version():
def get_plain_version():
return '.'.join(map(str, VERSION))
def get_version():
try:
return get_git_version()
except EnvironmentError:
return get_plain_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):

View File

@ -20,7 +20,7 @@ class SpotifyPlaybackProvider(BasePlaybackProvider):
self.backend.spotify.session.load(
Link.from_string(track.uri).as_track())
self.backend.spotify.session.play(1)
self.backend.output.set_state('PLAYING')
self.backend.output.play_uri('appsrc://')
return True
except SpotifyError as e:
logger.info('Playback of %s failed: %s', track.uri, e)

View File

@ -74,7 +74,11 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
def connection_error(self, session, error):
"""Callback used by pyspotify"""
logger.error(u'Spotify connection error: %s', error)
if error is None:
logger.info(u'Spotify connection error resolved')
else:
logger.error(u'Spotify connection error: %s', error)
self.backend.playback.pause()
def message_to_user(self, session, message):
"""Callback used by pyspotify"""

View File

@ -57,7 +57,9 @@ class SpotifyTranslator(object):
return Playlist(
uri=str(Link.from_playlist(spotify_playlist)),
name=spotify_playlist.name().decode(ENCODING),
tracks=[cls.to_mopidy_track(t) for t in spotify_playlist],
# 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))],
)
except SpotifyError, e:
logger.info(u'Failed translating Spotify playlist '

View File

@ -23,13 +23,15 @@ def main():
setup_backend()
setup_frontends()
try:
time.sleep(10000*24*60*60)
while ActorRegistry.get_all():
time.sleep(1)
logger.info(u'No actors left. Exiting...')
except KeyboardInterrupt:
logger.info(u'Exiting...')
logger.info(u'User interrupt. Exiting...')
ActorRegistry.stop_all()
def parse_options():
parser = optparse.OptionParser(version='Mopidy %s' % get_version())
parser = optparse.OptionParser(version=u'Mopidy %s' % get_version())
parser.add_option('-q', '--quiet',
action='store_const', const=0, dest='verbosity_level',
help='less output (warning level)')

View File

@ -9,6 +9,20 @@ from .session import MpdSession
logger = logging.getLogger('mopidy.frontends.mpd.server')
def _try_ipv6_socket():
"""Determine if system really supports IPv6"""
if not socket.has_ipv6:
return False
try:
socket.socket(socket.AF_INET6).close()
return True
except IOError, e:
logger.debug(u'Platform supports IPv6, but socket '
'creation failed, disabling: %s', e)
return False
has_ipv6 = _try_ipv6_socket()
class MpdServer(asyncore.dispatcher):
"""
The MPD server. Creates a :class:`mopidy.frontends.mpd.session.MpdSession`
@ -21,7 +35,7 @@ class MpdServer(asyncore.dispatcher):
def start(self):
"""Start MPD server."""
try:
if socket.has_ipv6:
if has_ipv6:
self.create_socket(socket.AF_INET6, socket.SOCK_STREAM)
# Explicitly configure socket to work for both IPv4 and IPv6
self.socket.setsockopt(
@ -53,7 +67,7 @@ class MpdServer(asyncore.dispatcher):
self.close()
def _format_hostname(self, hostname):
if (socket.has_ipv6
if (has_ipv6
and re.match('\d+.\d+.\d+.\d+', hostname) is not None):
hostname = '::ffff:%s' % hostname
return hostname

View File

@ -46,23 +46,9 @@ class GStreamerOutput(ThreadingActor, BaseOutput):
pad = self.gst_pipeline.get_by_name('convert').get_pad('sink')
if settings.BACKENDS[0] == 'mopidy.backends.local.LocalBackend':
uri_bin = gst.element_factory_make('uridecodebin', 'uri')
uri_bin.connect('pad-added', self._process_new_pad, pad)
self.gst_pipeline.add(uri_bin)
else:
app_src = gst.element_factory_make('appsrc', 'appsrc')
app_src_caps = gst.Caps("""
audio/x-raw-int,
endianness=(int)1234,
channels=(int)2,
width=(int)16,
depth=(int)16,
signed=(boolean)true,
rate=(int)44100""")
app_src.set_property('caps', app_src_caps)
self.gst_pipeline.add(app_src)
app_src.get_pad('src').link(pad)
uridecodebin = gst.element_factory_make('uridecodebin', 'uri')
uridecodebin.connect('pad-added', self._process_new_pad, pad)
self.gst_pipeline.add(uridecodebin)
# Setup bus and message processor
gst_bus = self.gst_pipeline.get_bus()
@ -98,12 +84,12 @@ class GStreamerOutput(ThreadingActor, BaseOutput):
def deliver_data(self, caps_string, data):
"""Deliver audio data to be played"""
app_src = self.gst_pipeline.get_by_name('appsrc')
source = self.gst_pipeline.get_by_name('source')
caps = gst.caps_from_string(caps_string)
buffer_ = gst.Buffer(buffer(data))
buffer_.set_caps(caps)
app_src.set_property('caps', caps)
app_src.emit('push-buffer', buffer_)
source.set_property('caps', caps)
source.emit('push-buffer', buffer_)
def end_of_data_stream(self):
"""
@ -112,7 +98,7 @@ class GStreamerOutput(ThreadingActor, BaseOutput):
We will get a GStreamer message when the stream playback reaches the
token, and can then do any end-of-stream related tasks.
"""
self.gst_pipeline.get_by_name('appsrc').emit('end-of-stream')
self.gst_pipeline.get_by_name('source').emit('end-of-stream')
def get_position(self):
try:

View File

@ -1,7 +1,8 @@
import logging
import logging.handlers
import platform
from mopidy import get_version, settings
from mopidy import get_version, get_platform, get_python, settings
def setup_logging(verbosity_level, save_debug_log):
setup_root_logger()
@ -9,7 +10,8 @@ def setup_logging(verbosity_level, save_debug_log):
if save_debug_log:
setup_debug_logging_to_file()
logger = logging.getLogger('mopidy.utils.log')
logger.info(u'-- Starting Mopidy %s --', get_version())
logger.info(u'Starting Mopidy %s on %s %s',
get_version(), get_platform(), get_python())
def setup_root_logger():
root = logging.getLogger('')

View File

@ -10,20 +10,22 @@ class MpdServerTest(unittest.TestCase):
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.server = server.MpdServer()
self.has_ipv6 = server.has_ipv6
def tearDown(self):
self.backend.stop().get()
self.mixer.stop().get()
server.has_ipv6 = self.has_ipv6
def test_format_hostname_prefixes_ipv4_addresses_when_ipv6_available(self):
server.socket.has_ipv6 = True
server.has_ipv6 = True
self.assertEqual(self.server._format_hostname('0.0.0.0'),
'::ffff:0.0.0.0')
self.assertEqual(self.server._format_hostname('127.0.0.1'),
'::ffff:127.0.0.1')
def test_format_hostname_does_nothing_when_only_ipv4_available(self):
server.socket.has_ipv6 = False
server.has_ipv6 = False
self.assertEquals(self.server._format_hostname('0.0.0.0'), '0.0.0.0')
class MpdSessionTest(unittest.TestCase):

View File

@ -1,7 +1,8 @@
from distutils.version import StrictVersion as SV
import unittest
import platform
from mopidy import get_plain_version
from mopidy import get_version, get_plain_version, get_platform, get_python
class VersionTest(unittest.TestCase):
def test_current_version_is_parsable_as_a_strict_version_number(self):
@ -16,5 +17,15 @@ class VersionTest(unittest.TestCase):
self.assert_(SV('0.1.0') < SV('1.0.0'))
self.assert_(SV('0.2.0') < SV('0.3.0'))
self.assert_(SV('0.3.0') < SV('0.3.1'))
self.assert_(SV('0.3.1') < SV(get_plain_version()))
self.assert_(SV(get_plain_version()) < SV('0.4.1'))
self.assert_(SV('0.3.1') < SV('0.4.0'))
self.assert_(SV('0.4.0') < SV(get_plain_version()))
self.assert_(SV(get_plain_version()) < SV('0.5.1'))
def test_get_platform_contains_platform(self):
self.assert_(platform.platform() in get_platform())
def test_get_python_contains_python_implementation(self):
self.assert_(platform.python_implementation() in get_python())
def test_get_python_contains_python_version(self):
self.assert_(platform.python_version() in get_python())