Release v0.7.3
This commit is contained in:
commit
ce60030fe5
@ -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)
|
||||
===================
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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 = {}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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.
|
||||
|
||||
38
tests/utils/decode_test.py
Normal file
38
tests/utils/decode_test.py
Normal 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)
|
||||
@ -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):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user