Release v0.7.3

This commit is contained in:
Stein Magnus Jodal 2012-08-11 03:38:38 +02:00
commit ce60030fe5
9 changed files with 98 additions and 13 deletions

View File

@ -4,6 +4,25 @@ Changes
This change log is used to track all major changes to Mopidy.
v0.7.3 (2012-08-11)
===================
A small maintenance release to fix a crash affecting a few users, and a couple
of small adjustments to the Spotify backend.
**Changes**
- Fixed crash when logging :exc:`IOError` exceptions on systems using languages
with non-ASCII characters, like French.
- Move the default location of the Spotify cache from `~/.cache/mopidy` to
`~/.cache/mopidy/spotify`. You can change this by setting
:attr:`mopidy.settings.SPOTIFY_CACHE_PATH`.
- Reduce time required to update the Spotify cache on startup. One one
system/Spotify account, the time from clean cache to ready for use was
reduced from 35s to 12s.
v0.7.2 (2012-05-07)
===================

View File

@ -8,7 +8,7 @@ from subprocess import PIPE, Popen
import glib
__version__ = '0.7.2'
__version__ = '0.7.3'
DATA_PATH = os.path.join(str(glib.get_user_data_dir()), 'mopidy')
CACHE_PATH = os.path.join(str(glib.get_user_cache_dir()), 'mopidy')

View File

@ -4,6 +4,7 @@ import os
logger = logging.getLogger('mopidy.backends.local.translator')
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):
@ -33,8 +34,8 @@ def parse_m3u(file_path):
try:
with open(file_path) as m3u:
contents = m3u.readlines()
except IOError, e:
logger.error('Couldn\'t open m3u: %s', e)
except IOError as error:
logger.error('Couldn\'t open m3u: %s', locale_decode(error))
return uris
for line in contents:
@ -61,8 +62,8 @@ def parse_mpd_tag_cache(tag_cache, music_dir=''):
try:
with open(tag_cache) as library:
contents = library.read()
except IOError, e:
logger.error('Could not open tag cache: %s', e)
except IOError as error:
logger.error('Could not open tag cache: %s', locale_decode(error))
return tracks
current = {}

View File

@ -23,8 +23,9 @@ logger = logging.getLogger('mopidy.backends.spotify.session_manager')
class SpotifySessionManager(BaseThread, PyspotifySessionManager):
cache_location = settings.SPOTIFY_CACHE_PATH or CACHE_PATH
settings_location = settings.SPOTIFY_CACHE_PATH or CACHE_PATH
cache_location = (settings.SPOTIFY_CACHE_PATH
or os.path.join(CACHE_PATH, 'spotify'))
settings_location = cache_location
appkey_file = os.path.join(os.path.dirname(__file__), 'spotify_appkey.key')
user_agent = 'Mopidy %s' % get_version()
@ -42,6 +43,8 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
self.container_manager = None
self.playlist_manager = None
self._initial_data_receive_completed = False
def run_inside_try(self):
self.setup()
self.connect()
@ -125,6 +128,17 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
def log_message(self, session, data):
"""Callback used by pyspotify"""
logger.debug(u'System message: %s' % data.strip())
if 'offline-mgr' in data and 'files unlocked' in data:
# XXX This is a very very fragile and ugly hack, but we get no
# proper event when libspotify is done with initial data loading.
# We delay the expensive refresh of Mopidy's stored playlists until
# this message arrives. This way, we avoid doing the refresh once
# for every playlist or other change. This reduces the time from
# startup until the Spotify backend is ready from 35s to 12s in one
# test with clean Spotify cache. In cases with an outdated cache
# the time improvements should be a lot better.
self._initial_data_receive_completed = True
self.refresh_stored_playlists()
def end_of_track(self, session):
"""Callback used by pyspotify"""
@ -134,6 +148,9 @@ class SpotifySessionManager(BaseThread, PyspotifySessionManager):
def refresh_stored_playlists(self):
"""Refresh the stored playlists in the backend with fresh meta data
from Spotify"""
if not self._initial_data_receive_completed:
logger.debug(u'Still getting data; skipped refresh of playlists')
return
playlists = map(SpotifyTranslator.to_mopidy_playlist,
self.session.playlist_container())
playlists = filter(None, playlists)

View File

@ -5,7 +5,7 @@ from pykka import registry, actor
from mopidy import listeners, settings
from mopidy.frontends.mpd import dispatcher, protocol
from mopidy.utils import network, process, log
from mopidy.utils import locale_decode, log, network, process
logger = logging.getLogger('mopidy.frontends.mpd')
@ -32,8 +32,8 @@ class MpdFrontend(actor.ThreadingActor, listeners.BackendListener):
try:
network.Server(hostname, port, protocol=MpdSession,
max_connections=settings.MPD_SERVER_MAX_CONNECTIONS)
except IOError, e:
logger.error(u'MPD server startup failed: %s', e)
except IOError as 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)

View File

@ -1,3 +1,4 @@
import locale
import logging
import os
import sys
@ -29,3 +30,9 @@ def get_class(name):
except (ImportError, AttributeError):
raise ImportError("Couldn't load: %s" % name)
return class_object
def locale_decode(bytestr):
try:
return unicode(bytestr)
except UnicodeError:
return str(bytestr).decode(locale.getpreferredencoding())

View File

@ -9,6 +9,8 @@ from pykka import ActorDeadError
from pykka.actor import ThreadingActor
from pykka.registry import ActorRegistry
from mopidy.utils import locale_decode
logger = logging.getLogger('mopidy.utils.server')
class ShouldRetrySocketCall(Exception):
@ -21,9 +23,9 @@ def try_ipv6_socket():
try:
socket.socket(socket.AF_INET6).close()
return True
except IOError, e:
except IOError as error:
logger.debug(u'Platform supports IPv6, but socket '
'creation failed, disabling: %s', e)
'creation failed, disabling: %s', locale_decode(error))
return False
#: Boolean value that indicates if creating an IPv6 socket will succeed.

View File

@ -0,0 +1,38 @@
import mock
from mopidy.utils import locale_decode
from tests import unittest
@mock.patch('mopidy.utils.locale.getpreferredencoding')
class LocaleDecodeTest(unittest.TestCase):
def test_can_decode_utf8_strings_with_french_content(self, mock):
mock.return_value = 'UTF-8'
result = locale_decode(
'[Errno 98] Adresse d\xc3\xa9j\xc3\xa0 utilis\xc3\xa9e')
self.assertEquals(u'[Errno 98] Adresse d\xe9j\xe0 utilis\xe9e', result)
def test_can_decode_an_ioerror_with_french_content(self, mock):
mock.return_value = 'UTF-8'
error = IOError(98, 'Adresse d\xc3\xa9j\xc3\xa0 utilis\xc3\xa9e')
result = locale_decode(error)
self.assertEquals(u'[Errno 98] Adresse d\xe9j\xe0 utilis\xe9e', result)
def test_does_not_use_locale_to_decode_unicode_strings(self, mock):
mock.return_value = 'UTF-8'
locale_decode(u'abc')
self.assertFalse(mock.called)
def test_does_not_use_locale_to_decode_ascii_bytestrings(self, mock):
mock.return_value = 'UTF-8'
locale_decode('abc')
self.assertFalse(mock.called)

View File

@ -26,7 +26,8 @@ class VersionTest(unittest.TestCase):
self.assert_(SV('0.6.0') < SV('0.6.1'))
self.assert_(SV('0.6.1') < SV('0.7.0'))
self.assert_(SV('0.7.0') < SV('0.7.1'))
self.assert_(SV('0.7.1') < SV(__version__))
self.assert_(SV('0.7.1') < SV('0.7.2'))
self.assert_(SV('0.7.2') < SV(__version__))
self.assert_(SV(__version__) < SV('0.8.0'))
def test_get_platform_contains_platform(self):