Merge branch 'develop' into feature/mpris-frontend

This commit is contained in:
Stein Magnus Jodal 2011-06-07 16:30:05 +02:00
commit 7c6c48feaa
8 changed files with 145 additions and 72 deletions

View File

@ -10,8 +10,16 @@ v0.5.0 (in development)
No description yet.
Please note that 0.5.0 requires some updated dependencies, as listed under
*Important changes* below.
**Important changes**
- If you use the Spotify backend, you *must* upgrade to libspotify 0.0.8 and
pyspotify 1.2. If you install from APT, libspotify and pyspotify will
automatically be upgraded. If you are not installing from APT, follow the
instructions at :doc:`/installation/libspotify/`.
- Mopidy now supports running with 1-n outputs at the same time. This feature
was mainly added to facilitate Shoutcast support, which Mopidy has also
gained. In its current state outputs can not be toggled during runtime.
@ -29,6 +37,13 @@ No description yet.
- Replace not decodable characters returned from Spotify instead of throwing an
exception, as we won't try to figure out the encoding of non-UTF-8-data.
- Spotify backend:
- Thanks to Antoine Pierlot-Garcin's recent work on updating and improving
pyspotify, stored playlists will again load when Mopidy starts. The
workaround of searching and reconnecting to make the playlists appear are
no longer necessary. (Fixes: :issue:`59`)
- MPD frontend:
- Refactoring and cleanup. Most notably, all request handlers now get an

View File

@ -4,8 +4,8 @@ libspotify installation
Mopidy uses `libspotify
<http://developer.spotify.com/en/libspotify/overview/>`_ for playing music from
the Spotify music service. To use :mod:`mopidy.backends.libspotify` you must
install libspotify and `pyspotify <http://github.com/mopidy/pyspotify>`_.
the Spotify music service. To use :mod:`mopidy.backends.spotify` you must
install libspotify and `pyspotify <http://pyspotify.mopidy.com/>`_.
.. note::
@ -30,7 +30,7 @@ If you run a Debian based Linux distribution, like Ubuntu, see
http://apt.mopidy.com/ for how to the Mopidy APT archive as a software source
on your installation. Then, simply run::
sudo apt-get install libspotify7
sudo apt-get install libspotify8
When libspotify has been installed, continue with
:ref:`pyspotify_installation`.
@ -39,14 +39,14 @@ When libspotify has been installed, continue with
On Linux from source
--------------------
Download and install libspotify 0.0.7 for your OS and CPU architecture from
Download and install libspotify 0.0.8 for your OS and CPU architecture from
https://developer.spotify.com/en/libspotify/.
For 64-bit Linux the process is as follows::
wget http://developer.spotify.com/download/libspotify/libspotify-0.0.7-linux6-x86_64.tar.gz
tar zxfv libspotify-0.0.7-linux6-x86_64.tar.gz
cd libspotify-0.0.7-linux6-x86_64/
wget http://developer.spotify.com/download/libspotify/libspotify-0.0.8-linux6-x86_64.tar.gz
tar zxfv libspotify-0.0.8-linux6-x86_64.tar.gz
cd libspotify-0.0.8-linux6-x86_64/
sudo make install prefix=/usr/local
sudo ldconfig
@ -103,14 +103,10 @@ Debian/Ubuntu systems run::
On OS X no additional dependencies are needed.
Get the pyspotify code, and install it::
Then get, build, and install the latest releast of pyspotify using ``pip``::
wget --no-check-certificate -O pyspotify.tar.gz https://github.com/mopidy/pyspotify/tarball/mopidy
tar zxfv pyspotify.tar.gz
cd pyspotify/
sudo python setup.py install
sudo pip install -U pyspotify
It is important that you install pyspotify from the ``mopidy`` branch of the
``mopidy/pyspotify`` repository, as the upstream repository at
``winjer/pyspotify`` is not updated with changes needed to support e.g.
libspotify 0.0.7 and high bitrate audio.
Or using the older ``easy_install``::
sudo easy_install pyspotify

View File

@ -0,0 +1,16 @@
import logging
from spotify.manager import SpotifyContainerManager as PyspotifyContainerManager
logger = logging.getLogger('mopidy.backends.spotify.container_manager')
class SpotifyContainerManager(PyspotifyContainerManager):
def __init__(self, session_manager):
PyspotifyContainerManager.__init__(self)
self.session_manager = session_manager
def container_loaded(self, container, userdata):
"""Callback used by pyspotify."""
logger.debug(u'Container loaded')
self.session_manager.refresh_stored_playlists()

View File

@ -12,6 +12,7 @@ from mopidy.backends.spotify.translator import SpotifyTranslator
from mopidy.models import Playlist
from mopidy.gstreamer import GStreamer
from mopidy.utils.process import BaseThread
from mopidy.backends.spotify.container_manager import SpotifyContainerManager
logger = logging.getLogger('mopidy.backends.spotify.session_manager')
@ -35,6 +36,8 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
self.connected = threading.Event()
self.session = None
self.container_manager = None
def run_inside_try(self):
self.setup()
self.connect()
@ -61,6 +64,8 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
else:
logger.debug(u'Preferring normal bitrate from Spotify')
self.session.set_preferred_bitrate(0)
self.container_manager = SpotifyContainerManager(self)
self.container_manager.watch(self.session.playlist_container())
self.connected.set()
def logged_out(self, session):

View File

@ -1,28 +1,13 @@
import asyncore
import logging
import re
import socket
import sys
from mopidy import settings
from mopidy.utils import network
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`
@ -35,22 +20,14 @@ class MpdServer(asyncore.dispatcher):
def start(self):
"""Start MPD server."""
try:
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(
socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
else:
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket = network.create_socket()
self.set_reuse_addr()
hostname = self._format_hostname(settings.MPD_SERVER_HOSTNAME)
hostname = network.format_hostname(settings.MPD_SERVER_HOSTNAME)
port = settings.MPD_SERVER_PORT
logger.debug(u'MPD server is binding to [%s]:%s', hostname, port)
self.bind((hostname, port))
self.listen(1)
logger.info(u'MPD server running at [%s]:%s',
self._format_hostname(settings.MPD_SERVER_HOSTNAME),
settings.MPD_SERVER_PORT)
logger.info(u'MPD server running at [%s]:%s', hostname, port)
except IOError, e:
logger.error(u'MPD server startup failed: %s' %
str(e).decode('utf-8'))
@ -66,9 +43,3 @@ class MpdServer(asyncore.dispatcher):
def handle_close(self):
"""Called by asyncore when the socket is closed."""
self.close()
def _format_hostname(self, hostname):
if (has_ipv6
and re.match('\d+.\d+.\d+.\d+', hostname) is not None):
hostname = '::ffff:%s' % hostname
return hostname

36
mopidy/utils/network.py Normal file
View File

@ -0,0 +1,36 @@
import logging
import re
import socket
logger = logging.getLogger('mopidy.utils.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
#: Boolean value that indicates if creating an IPv6 socket will succeed.
has_ipv6 = _try_ipv6_socket()
def create_socket():
"""Create a TCP socket with or without IPv6 depending on system support"""
if has_ipv6:
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
# Explicitly configure socket to work for both IPv4 and IPv6
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
else:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
return sock
def format_hostname(hostname):
"""Format hostname for display."""
if (has_ipv6 and re.match('\d+.\d+.\d+.\d+', hostname) is not None):
hostname = '::ffff:%s' % hostname
return hostname

View File

@ -5,29 +5,6 @@ from mopidy.backends.dummy import DummyBackend
from mopidy.frontends.mpd import server
from mopidy.mixers.dummy import DummyMixer
class MpdServerTest(unittest.TestCase):
def setUp(self):
self.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.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.has_ipv6 = False
self.assertEquals(self.server._format_hostname('0.0.0.0'), '0.0.0.0')
class MpdSessionTest(unittest.TestCase):
def setUp(self):
self.backend = DummyBackend.start().proxy()

View File

@ -0,0 +1,57 @@
import mock
import socket
import unittest
from mopidy.utils import network
from tests import SkipTest
class FormatHostnameTest(unittest.TestCase):
@mock.patch('mopidy.utils.network.has_ipv6', True)
def test_format_hostname_prefixes_ipv4_addresses_when_ipv6_available(self):
network.has_ipv6 = True
self.assertEqual(network.format_hostname('0.0.0.0'), '::ffff:0.0.0.0')
self.assertEqual(network.format_hostname('1.0.0.1'), '::ffff:1.0.0.1')
@mock.patch('mopidy.utils.network.has_ipv6', False)
def test_format_hostname_does_nothing_when_only_ipv4_available(self):
network.has_ipv6 = False
self.assertEquals(network.format_hostname('0.0.0.0'), '0.0.0.0')
class TryIPv6SocketTest(unittest.TestCase):
@mock.patch('socket.has_ipv6', False)
def test_system_that_claims_no_ipv6_support(self):
self.assertFalse(network._try_ipv6_socket())
@mock.patch('socket.has_ipv6', True)
@mock.patch('socket.socket')
def test_system_with_broken_ipv6(self, socket_mock):
socket_mock.side_effect = IOError()
self.assertFalse(network._try_ipv6_socket())
@mock.patch('socket.has_ipv6', True)
@mock.patch('socket.socket')
def test_with_working_ipv6(self, socket_mock):
socket_mock.return_value = mock.Mock()
self.assertTrue(network._try_ipv6_socket())
class CreateSocketTest(unittest.TestCase):
@mock.patch('mopidy.utils.network.has_ipv6', False)
@mock.patch('socket.socket')
def test_ipv4_socket(self, socket_mock):
network.create_socket()
self.assertEqual(socket_mock.call_args[0],
(socket.AF_INET, socket.SOCK_STREAM))
@mock.patch('mopidy.utils.network.has_ipv6', True)
@mock.patch('socket.socket')
def test_ipv6_socket(self, socket_mock):
network.create_socket()
self.assertEqual(socket_mock.call_args[0],
(socket.AF_INET6, socket.SOCK_STREAM))
@SkipTest
def test_ipv6_only_is_set(self):
pass