Merge branch 'develop' of github.com:mopidy/mopidy into develop

Conflicts:
	docs/changelog.rst
This commit is contained in:
Stein Magnus Jodal 2013-08-05 23:33:08 +02:00
commit 80d122ff92
21 changed files with 179 additions and 200 deletions

View File

@ -36,6 +36,14 @@ v0.15.0 (UNRELEASED)
the next file. This fixes some hangs on non-media files, like logs. (Fixes: the next file. This fixes some hangs on non-media files, like logs. (Fixes:
:issue:`476`, :issue:`483`) :issue:`476`, :issue:`483`)
- Added support for plugable library updaters. This allows extension writers
to start providing their own custom libraries instead of being stuck with
just our tag cache as the only option.
- Converted local backend to use new `local:playlist:path` and
`local:track:path` uri scheme. Also moves support of `file://` to streaming
backend.
**Spotify backend** **Spotify backend**
- Prepend playlist folder names to the playlist name, so that the playlist - Prepend playlist folder names to the playlist name, so that the playlist

View File

@ -15,11 +15,6 @@ class Backend(object):
#: the backend doesn't provide a library. #: the backend doesn't provide a library.
library = None library = None
#: The library update provider. An instance of
#: :class:`~mopidy.backends.base.BaseLibraryUpdateProvider`, or
#: :class:`None` if the backend doesn't provide a library.
updater = None
#: The playback provider. An instance of #: The playback provider. An instance of
#: :class:`~mopidy.backends.base.BasePlaybackProvider`, or :class:`None` if #: :class:`~mopidy.backends.base.BasePlaybackProvider`, or :class:`None` if
#: the backend doesn't provide playback. #: the backend doesn't provide playback.
@ -40,9 +35,6 @@ class Backend(object):
def has_library(self): def has_library(self):
return self.library is not None return self.library is not None
def has_updater(self):
return self.updater is not None
def has_playback(self): def has_playback(self):
return self.playback is not None return self.playback is not None
@ -96,15 +88,7 @@ class BaseLibraryProvider(object):
class BaseLibraryUpdateProvider(object): class BaseLibraryUpdateProvider(object):
""" uri_schemes = []
:param backend: backend the controller is a part of
:type backend: :class:`mopidy.backends.base.Backend`
"""
pykka_traversable = True
def __init__(self, backend):
self.backend = backend
def load(self): def load(self):
"""Loads the library and returns all tracks in it. """Loads the library and returns all tracks in it.
@ -172,9 +156,22 @@ class BasePlaybackProvider(object):
:rtype: :class:`True` if successful, else :class:`False` :rtype: :class:`True` if successful, else :class:`False`
""" """
self.audio.prepare_change() self.audio.prepare_change()
self.audio.set_uri(track.uri).get() self.change_track(track)
return self.audio.start_playback().get() return self.audio.start_playback().get()
def change_track(self, track):
"""
Swith to provided track.
*MAY be reimplemented by subclass.*
:param track: the track to play
:type track: :class:`mopidy.models.Track`
:rtype: :class:`True` if successful, else :class:`False`
"""
self.audio.set_uri(track.uri).get()
return True
def resume(self): def resume(self):
""" """
Resume playback at the same time position playback was paused. Resume playback at the same time position playback was paused.

View File

@ -30,3 +30,7 @@ class Extension(ext.Extension):
def get_backend_classes(self): def get_backend_classes(self):
from .actor import LocalBackend from .actor import LocalBackend
return [LocalBackend] return [LocalBackend]
def get_library_updaters(self):
from .library import LocalLibraryUpdateProvider
return [LocalLibraryUpdateProvider]

View File

@ -8,8 +8,9 @@ import pykka
from mopidy.backends import base from mopidy.backends import base
from mopidy.utils import encoding, path from mopidy.utils import encoding, path
from .library import LocalLibraryProvider, LocalLibraryUpdateProvider from .library import LocalLibraryProvider
from .playlists import LocalPlaylistsProvider from .playlists import LocalPlaylistsProvider
from .playback import LocalPlaybackProvider
logger = logging.getLogger('mopidy.backends.local') logger = logging.getLogger('mopidy.backends.local')
@ -23,11 +24,10 @@ class LocalBackend(pykka.ThreadingActor, base.Backend):
self.check_dirs_and_files() self.check_dirs_and_files()
self.library = LocalLibraryProvider(backend=self) self.library = LocalLibraryProvider(backend=self)
self.updater = LocalLibraryUpdateProvider(backend=self) self.playback = LocalPlaybackProvider(audio=audio, backend=self)
self.playback = base.BasePlaybackProvider(audio=audio, backend=self)
self.playlists = LocalPlaylistsProvider(backend=self) self.playlists = LocalPlaylistsProvider(backend=self)
self.uri_schemes = ['file'] self.uri_schemes = ['local']
def check_dirs_and_files(self): def check_dirs_and_files(self):
if not os.path.isdir(self.config['local']['media_dir']): if not os.path.isdir(self.config['local']['media_dir']):

View File

@ -81,7 +81,8 @@ class LocalLibraryProvider(base.BaseLibraryProvider):
result_tracks = filter(any_filter, result_tracks) result_tracks = filter(any_filter, result_tracks)
else: else:
raise LookupError('Invalid lookup field: %s' % field) raise LookupError('Invalid lookup field: %s' % field)
return SearchResult(uri='file:search', tracks=result_tracks) # TODO: add local:search:<query>
return SearchResult(uri='local:search', tracks=result_tracks)
def search(self, query=None, uris=None): def search(self, query=None, uris=None):
# TODO Only return results within URI roots given by ``uris`` # TODO Only return results within URI roots given by ``uris``
@ -122,7 +123,8 @@ class LocalLibraryProvider(base.BaseLibraryProvider):
result_tracks = filter(any_filter, result_tracks) result_tracks = filter(any_filter, result_tracks)
else: else:
raise LookupError('Invalid lookup field: %s' % field) raise LookupError('Invalid lookup field: %s' % field)
return SearchResult(uri='file:search', tracks=result_tracks) # TODO: add local:search:<query>
return SearchResult(uri='local:search', tracks=result_tracks)
def _validate_query(self, query): def _validate_query(self, query):
for (_, values) in query.iteritems(): for (_, values) in query.iteritems():
@ -135,11 +137,12 @@ class LocalLibraryProvider(base.BaseLibraryProvider):
# TODO: rename and move to tagcache extension. # TODO: rename and move to tagcache extension.
class LocalLibraryUpdateProvider(base.BaseLibraryProvider): class LocalLibraryUpdateProvider(base.BaseLibraryProvider):
def __init__(self, *args, **kwargs): uri_schemes = ['local']
super(LocalLibraryUpdateProvider, self).__init__(*args, **kwargs)
def __init__(self, config):
self._tracks = {} self._tracks = {}
self._media_dir = self.backend.config['local']['media_dir'] self._media_dir = config['local']['media_dir']
self._tag_cache_file = self.backend.config['local']['tag_cache_file'] self._tag_cache_file = config['local']['tag_cache_file']
def load(self): def load(self):
tracks = parse_mpd_tag_cache(self._tag_cache_file, self._media_dir) tracks = parse_mpd_tag_cache(self._tag_cache_file, self._media_dir)
@ -156,6 +159,8 @@ class LocalLibraryUpdateProvider(base.BaseLibraryProvider):
def commit(self): def commit(self):
directory, basename = os.path.split(self._tag_cache_file) directory, basename = os.path.split(self._tag_cache_file)
# TODO: cleanup directory/basename.* files.
tmp = tempfile.NamedTemporaryFile( tmp = tempfile.NamedTemporaryFile(
prefix=basename + '.', dir=directory, delete=False) prefix=basename + '.', dir=directory, delete=False)

View File

@ -0,0 +1,19 @@
from __future__ import unicode_literals
import logging
import os
from mopidy.backends import base
from mopidy.utils import path
logger = logging.getLogger('mopidy.backends.local')
class LocalPlaybackProvider(base.BasePlaybackProvider):
def change_track(self, track):
media_dir = self.backend.config['local']['media_dir']
# TODO: check that type is correct.
file_path = path.uri_to_path(track.uri).split(':', 1)[1]
file_path = os.path.join(media_dir, file_path)
track = track.copy(uri=path.path_to_uri(file_path))
return super(LocalPlaybackProvider, self).change_track(track)

View File

@ -24,7 +24,7 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
def create(self, name): def create(self, name):
name = formatting.slugify(name) name = formatting.slugify(name)
uri = path.path_to_uri(self._get_m3u_path(name)) uri = 'local:playlist:%s.m3u' % name
playlist = Playlist(uri=uri, name=name) playlist = Playlist(uri=uri, name=name)
return self.save(playlist) return self.save(playlist)
@ -37,6 +37,7 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
self._delete_m3u(playlist.uri) self._delete_m3u(playlist.uri)
def lookup(self, uri): def lookup(self, uri):
# TODO: store as {uri: playlist}?
for playlist in self._playlists: for playlist in self._playlists:
if playlist.uri == uri: if playlist.uri == uri:
return playlist return playlist
@ -45,8 +46,8 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
playlists = [] playlists = []
for m3u in glob.glob(os.path.join(self._playlists_dir, '*.m3u')): for m3u in glob.glob(os.path.join(self._playlists_dir, '*.m3u')):
uri = path.path_to_uri(m3u)
name = os.path.splitext(os.path.basename(m3u))[0] name = os.path.splitext(os.path.basename(m3u))[0]
uri = 'local:playlist:%s' % name
tracks = [] tracks = []
for track_uri in parse_m3u(m3u, self._media_dir): for track_uri in parse_m3u(m3u, self._media_dir):
@ -61,6 +62,7 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
playlists.append(playlist) playlists.append(playlist)
self.playlists = playlists self.playlists = playlists
# TODO: send what scheme we loaded them for?
listener.BackendListener.send('playlists_loaded') listener.BackendListener.send('playlists_loaded')
logger.info( logger.info(
@ -86,38 +88,30 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
return playlist return playlist
def _get_m3u_path(self, name): def _m3u_uri_to_path(self, uri):
name = formatting.slugify(name) # TODO: create uri handling helpers for local uri types.
file_path = os.path.join(self._playlists_dir, name + '.m3u') file_path = path.uri_to_path(uri).split(':', 1)[1]
file_path = os.path.join(self._playlists_dir, file_path)
path.check_file_path_is_inside_base_dir(file_path, self._playlists_dir) path.check_file_path_is_inside_base_dir(file_path, self._playlists_dir)
return file_path return file_path
def _save_m3u(self, playlist): def _save_m3u(self, playlist):
file_path = path.uri_to_path(playlist.uri) file_path = self._m3u_uri_to_path(playlist.uri)
path.check_file_path_is_inside_base_dir(file_path, self._playlists_dir)
with open(file_path, 'w') as file_handle: with open(file_path, 'w') as file_handle:
for track in playlist.tracks: for track in playlist.tracks:
if track.uri.startswith('file://'): file_handle.write(track.uri + '\n')
uri = path.uri_to_path(track.uri)
else:
uri = track.uri
file_handle.write(uri + '\n')
def _delete_m3u(self, uri): def _delete_m3u(self, uri):
file_path = path.uri_to_path(uri) file_path = self._m3u_uri_to_path(uri)
path.check_file_path_is_inside_base_dir(file_path, self._playlists_dir)
if os.path.exists(file_path): if os.path.exists(file_path):
os.remove(file_path) os.remove(file_path)
def _rename_m3u(self, playlist): def _rename_m3u(self, playlist):
src_file_path = path.uri_to_path(playlist.uri) dst_name = formatting.slugify(playlist.name)
path.check_file_path_is_inside_base_dir( dst_uri = 'local:playlist:%s.m3u' % dst_name
src_file_path, self._playlists_dir)
dst_file_path = self._get_m3u_path(playlist.name) src_file_path = self._m3u_uri_to_path(playlist.uri)
path.check_file_path_is_inside_base_dir( dst_file_path = self._m3u_uri_to_path(dst_uri)
dst_file_path, self._playlists_dir)
shutil.move(src_file_path, dst_file_path) shutil.move(src_file_path, dst_file_path)
return playlist.copy(uri=dst_uri)
return playlist.copy(uri=path.path_to_uri(dst_file_path))

View File

@ -1,7 +1,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import logging import logging
import os
import urllib import urllib
import urlparse
from mopidy.models import Track, Artist, Album from mopidy.models import Track, Artist, Album
from mopidy.utils.encoding import locale_decode from mopidy.utils.encoding import locale_decode
@ -30,7 +32,6 @@ def parse_m3u(file_path, media_dir):
- m3u files are latin-1. - m3u files are latin-1.
- This function does not bother with Extended M3U directives. - This function does not bother with Extended M3U directives.
""" """
# TODO: uris as bytes # TODO: uris as bytes
uris = [] uris = []
try: try:
@ -46,16 +47,19 @@ def parse_m3u(file_path, media_dir):
if line.startswith('#'): if line.startswith('#'):
continue continue
# FIXME what about other URI types? if urlparse.urlsplit(line).scheme:
if line.startswith('file://'):
uris.append(line) uris.append(line)
elif os.path.normpath(line) == os.path.abspath(line):
path = path_to_uri(line)
uris.append(path)
else: else:
path = path_to_uri(media_dir, line) path = path_to_uri(os.path.join(media_dir, line))
uris.append(path) uris.append(path)
return uris return uris
# TODO: remove music_dir from API
def parse_mpd_tag_cache(tag_cache, music_dir=''): def parse_mpd_tag_cache(tag_cache, music_dir=''):
""" """
Converts a MPD tag_cache into a lists of tracks, artists and albums. Converts a MPD tag_cache into a lists of tracks, artists and albums.
@ -86,17 +90,17 @@ def parse_mpd_tag_cache(tag_cache, music_dir=''):
key, value = line.split(b': ', 1) key, value = line.split(b': ', 1)
if key == b'key': if key == b'key':
_convert_mpd_data(current, tracks, music_dir) _convert_mpd_data(current, tracks)
current.clear() current.clear()
current[key.lower()] = value.decode('utf-8') current[key.lower()] = value.decode('utf-8')
_convert_mpd_data(current, tracks, music_dir) _convert_mpd_data(current, tracks)
return tracks return tracks
def _convert_mpd_data(data, tracks, music_dir): def _convert_mpd_data(data, tracks):
if not data: if not data:
return return
@ -160,15 +164,8 @@ def _convert_mpd_data(data, tracks, music_dir):
path = data['file'][1:] path = data['file'][1:]
else: else:
path = data['file'] path = data['file']
path = urllib.unquote(path.encode('utf-8'))
if isinstance(music_dir, unicode):
music_dir = music_dir.encode('utf-8')
# Make sure we only pass bytestrings to path_to_uri to avoid implicit
# decoding of bytestrings to unicode strings
track_kwargs['uri'] = path_to_uri(music_dir, path)
track_kwargs['uri'] = 'local:track:%s' % path
track_kwargs['length'] = int(data.get('time', 0)) * 1000 track_kwargs['length'] = int(data.get('time', 0)) * 1000
track = Track(**track_kwargs) track = Track(**track_kwargs)

View File

@ -1,6 +1,7 @@
[stream] [stream]
enabled = true enabled = true
protocols = protocols =
file
http http
https https
mms mms

View File

@ -401,6 +401,7 @@ class PlaybackController(object):
if self.random and self._shuffled: if self.random and self._shuffled:
self._shuffled.remove(tl_track) self._shuffled.remove(tl_track)
if on_error_step == 1: if on_error_step == 1:
# TODO: can cause an endless loop for single track repeat.
self.next() self.next()
elif on_error_step == -1: elif on_error_step == -1:
self.previous() self.previous()

View File

@ -79,6 +79,14 @@ class Extension(object):
""" """
return [] return []
def get_library_updaters(self):
"""List of library updater classes
:returns: list of :class:`~mopidy.backends.base.BaseLibraryUpdateProvider`
subclasses
"""
return []
def register_gstreamer_elements(self): def register_gstreamer_elements(self):
"""Hook for registering custom GStreamer elements """Hook for registering custom GStreamer elements

View File

@ -266,6 +266,7 @@ class MpdContext(object):
for playlist in self.core.playlists.playlists.get(): for playlist in self.core.playlists.playlists.get():
if not playlist.name: if not playlist.name:
continue continue
# TODO: add scheme to name perhaps 'foo (spotify)' etc.
name = self.create_unique_name(playlist.name) name = self.create_unique_name(playlist.name)
self._playlist_uri_from_name[name] = playlist.uri self._playlist_uri_from_name[name] = playlist.uri
self._playlist_name_from_uri[playlist.uri] = name self._playlist_name_from_uri[playlist.uri] = name

View File

@ -27,7 +27,6 @@ pygst.require('0.10')
import gst import gst
from mopidy import config as config_lib, ext from mopidy import config as config_lib, ext
from mopidy.audio import dummy as dummy_audio
from mopidy.models import Track, Artist, Album from mopidy.models import Track, Artist, Album
from mopidy.utils import log, path, versioning from mopidy.utils import log, path, versioning
@ -45,9 +44,9 @@ def main():
log.setup_root_logger() log.setup_root_logger()
log.setup_console_logging(logging_config, args.verbosity_level) log.setup_console_logging(logging_config, args.verbosity_level)
extensions = dict((e.ext_name, e) for e in ext.load_extensions()) extensions = ext.load_extensions()
config, errors = config_lib.load( config, errors = config_lib.load(
config_files, extensions.values(), config_overrides) config_files, extensions, config_overrides)
log.setup_log_levels(config) log.setup_log_levels(config)
if not config['local']['media_dir']: if not config['local']['media_dir']:
@ -60,10 +59,21 @@ def main():
# TODO: missing config error checking and other default setup code. # TODO: missing config error checking and other default setup code.
audio = dummy_audio.DummyAudio() updaters = {}
local_backend_classes = extensions['local'].get_backend_classes() for e in extensions:
local_backend = local_backend_classes[0](config, audio) for updater_class in e.get_library_updaters():
local_updater = local_backend.updater if updater_class and 'local' in updater_class.uri_schemes:
updaters[e.ext_name] = updater_class
if not updaters:
logging.error('No usable library updaters found.')
return
elif len(updaters) > 1:
logging.error('More than one library updater found. '
'Provided by: %s', ', '.join(updaters.keys()))
return
local_updater = updaters.values()[0](config) # TODO: switch to actor?
media_dir = config['local']['media_dir'] media_dir = config['local']['media_dir']

View File

@ -2,12 +2,11 @@ from __future__ import unicode_literals
import logging import logging
import os import os
import re
# pylint: disable = W0402 # pylint: disable = W0402
import string import string
# pylint: enable = W0402 # pylint: enable = W0402
import sys
import urllib import urllib
import urlparse
import glib import glib
@ -51,7 +50,7 @@ def get_or_create_file(file_path):
return file_path return file_path
def path_to_uri(*paths): def path_to_uri(path):
""" """
Convert OS specific path to file:// URI. Convert OS specific path to file:// URI.
@ -61,17 +60,15 @@ def path_to_uri(*paths):
Returns a file:// URI as an unicode string. Returns a file:// URI as an unicode string.
""" """
path = os.path.join(*paths)
if isinstance(path, unicode): if isinstance(path, unicode):
path = path.encode('utf-8') path = path.encode('utf-8')
if sys.platform == 'win32': path = urllib.quote(path)
return 'file:' + urllib.quote(path) return urlparse.urlunsplit((b'file', b'', path, b'', b''))
return 'file://' + urllib.quote(path)
def uri_to_path(uri): def uri_to_path(uri):
""" """
Convert the file:// to a OS specific path. Convert an URI to a OS specific path.
Returns a bytestring, since the file path can contain chars with other Returns a bytestring, since the file path can contain chars with other
encoding than UTF-8. encoding than UTF-8.
@ -82,10 +79,7 @@ def uri_to_path(uri):
""" """
if isinstance(uri, unicode): if isinstance(uri, unicode):
uri = uri.encode('utf-8') uri = uri.encode('utf-8')
if sys.platform == 'win32': return urllib.unquote(urlparse.urlsplit(uri).path)
return urllib.unquote(re.sub(b'^file:', b'', uri))
else:
return urllib.unquote(re.sub(b'^file://', b'', uri))
def split_path(path): def split_path(path):

View File

@ -7,8 +7,6 @@ import pykka
from mopidy import core from mopidy import core
from mopidy.models import Track, Album, Artist from mopidy.models import Track, Album, Artist
from tests import path_to_data_dir
class LibraryControllerTest(object): class LibraryControllerTest(object):
artists = [Artist(name='artist1'), Artist(name='artist2'), Artist()] artists = [Artist(name='artist1'), Artist(name='artist2'), Artist()]
@ -17,13 +15,10 @@ class LibraryControllerTest(object):
Album(name='album2', artists=artists[1:2]), Album(name='album2', artists=artists[1:2]),
Album()] Album()]
tracks = [ tracks = [
Track( Track(uri='local:track:path1', name='track1', artists=artists[:1],
uri='file://' + path_to_data_dir('uri1'), name='track1', album=albums[0], date='2001-02-03', length=4000),
artists=artists[:1], album=albums[0], date='2001-02-03', Track(uri='local:track:path2', name='track2', artists=artists[1:2],
length=4000), album=albums[1], date='2002', length=4000),
Track(
uri='file://' + path_to_data_dir('uri2'), name='track2',
artists=artists[1:2], album=albums[1], date='2002', length=4000),
Track()] Track()]
config = {} config = {}
@ -66,11 +61,11 @@ class LibraryControllerTest(object):
self.assertEqual(list(result[0].tracks), []) self.assertEqual(list(result[0].tracks), [])
def test_find_exact_uri(self): def test_find_exact_uri(self):
track_1_uri = 'file://' + path_to_data_dir('uri1') track_1_uri = 'local:track:path1'
result = self.library.find_exact(uri=track_1_uri) result = self.library.find_exact(uri=track_1_uri)
self.assertEqual(list(result[0].tracks), self.tracks[:1]) self.assertEqual(list(result[0].tracks), self.tracks[:1])
track_2_uri = 'file://' + path_to_data_dir('uri2') track_2_uri = 'local:track:path2'
result = self.library.find_exact(uri=track_2_uri) result = self.library.find_exact(uri=track_2_uri)
self.assertEqual(list(result[0].tracks), self.tracks[1:2]) self.assertEqual(list(result[0].tracks), self.tracks[1:2])
@ -136,10 +131,10 @@ class LibraryControllerTest(object):
self.assertEqual(list(result[0].tracks), []) self.assertEqual(list(result[0].tracks), [])
def test_search_uri(self): def test_search_uri(self):
result = self.library.search(uri=['RI1']) result = self.library.search(uri=['TH1'])
self.assertEqual(list(result[0].tracks), self.tracks[:1]) self.assertEqual(list(result[0].tracks), self.tracks[:1])
result = self.library.search(uri=['RI2']) result = self.library.search(uri=['TH2'])
self.assertEqual(list(result[0].tracks), self.tracks[1:2]) self.assertEqual(list(result[0].tracks), self.tracks[1:2])
def test_search_track(self): def test_search_track(self):
@ -183,7 +178,7 @@ class LibraryControllerTest(object):
self.assertEqual(list(result[0].tracks), self.tracks[:1]) self.assertEqual(list(result[0].tracks), self.tracks[:1])
result = self.library.search(any=['Bum1']) result = self.library.search(any=['Bum1'])
self.assertEqual(list(result[0].tracks), self.tracks[:1]) self.assertEqual(list(result[0].tracks), self.tracks[:1])
result = self.library.search(any=['RI1']) result = self.library.search(any=['TH1'])
self.assertEqual(list(result[0].tracks), self.tracks[:1]) self.assertEqual(list(result[0].tracks), self.tracks[:1])
def test_search_wrong_type(self): def test_search_wrong_type(self):

View File

@ -1,9 +1,4 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from mopidy.utils.path import path_to_uri
from tests import path_to_data_dir generate_song = lambda i: 'local:track:song%s.wav' % i
song = path_to_data_dir('song%s.wav')
generate_song = lambda i: path_to_uri(song % i)

View File

@ -5,7 +5,6 @@ import unittest
from mopidy.backends.local import actor from mopidy.backends.local import actor
from mopidy.core import PlaybackState from mopidy.core import PlaybackState
from mopidy.models import Track from mopidy.models import Track
from mopidy.utils.path import path_to_uri
from tests import path_to_data_dir from tests import path_to_data_dir
from tests.backends.base.playback import PlaybackControllerTest from tests.backends.base.playback import PlaybackControllerTest
@ -24,25 +23,25 @@ class LocalPlaybackControllerTest(PlaybackControllerTest, unittest.TestCase):
tracks = [ tracks = [
Track(uri=generate_song(i), length=4464) for i in range(1, 4)] Track(uri=generate_song(i), length=4464) for i in range(1, 4)]
def add_track(self, path): def add_track(self, uri):
uri = path_to_uri(path_to_data_dir(path))
track = Track(uri=uri, length=4464) track = Track(uri=uri, length=4464)
self.tracklist.add([track]) self.tracklist.add([track])
def test_uri_scheme(self): def test_uri_scheme(self):
self.assertIn('file', self.core.uri_schemes) self.assertNotIn('file', self.core.uri_schemes)
self.assertIn('local', self.core.uri_schemes)
def test_play_mp3(self): def test_play_mp3(self):
self.add_track('blank.mp3') self.add_track('local:track:blank.mp3')
self.playback.play() self.playback.play()
self.assertEqual(self.playback.state, PlaybackState.PLAYING) self.assertEqual(self.playback.state, PlaybackState.PLAYING)
def test_play_ogg(self): def test_play_ogg(self):
self.add_track('blank.ogg') self.add_track('local:track:blank.ogg')
self.playback.play() self.playback.play()
self.assertEqual(self.playback.state, PlaybackState.PLAYING) self.assertEqual(self.playback.state, PlaybackState.PLAYING)
def test_play_flac(self): def test_play_flac(self):
self.add_track('blank.flac') self.add_track('local:track:blank.flac')
self.playback.play() self.playback.play()
self.assertEqual(self.playback.state, PlaybackState.PLAYING) self.assertEqual(self.playback.state, PlaybackState.PLAYING)

View File

@ -7,7 +7,7 @@ import unittest
from mopidy.backends.local import actor from mopidy.backends.local import actor
from mopidy.models import Track from mopidy.models import Track
from mopidy.utils.path import path_to_uri from mopidy.utils.path import path_to_uri, uri_to_path
from tests import path_to_data_dir from tests import path_to_data_dir
from tests.backends.base.playlists import ( from tests.backends.base.playlists import (
@ -89,21 +89,20 @@ class LocalPlaylistsControllerTest(
def test_playlist_contents_is_written_to_disk(self): def test_playlist_contents_is_written_to_disk(self):
track = Track(uri=generate_song(1)) track = Track(uri=generate_song(1))
track_path = track.uri[len('file://'):]
playlist = self.core.playlists.create('test') playlist = self.core.playlists.create('test')
playlist_path = playlist.uri[len('file://'):] playlist_path = os.path.join(self.playlists_dir, 'test.m3u')
playlist = playlist.copy(tracks=[track]) playlist = playlist.copy(tracks=[track])
playlist = self.core.playlists.save(playlist) playlist = self.core.playlists.save(playlist)
with open(playlist_path) as playlist_file: with open(playlist_path) as playlist_file:
contents = playlist_file.read() contents = playlist_file.read()
self.assertEqual(track_path, contents.strip()) self.assertEqual(track.uri, contents.strip())
def test_playlists_are_loaded_at_startup(self): def test_playlists_are_loaded_at_startup(self):
playlist_path = os.path.join(self.playlists_dir, 'test.m3u') playlist_path = os.path.join(self.playlists_dir, 'test.m3u')
track = Track(uri=path_to_uri(path_to_data_dir('uri2'))) track = Track(uri='local:track:path2')
playlist = self.core.playlists.create('test') playlist = self.core.playlists.create('test')
playlist = playlist.copy(tracks=[track]) playlist = playlist.copy(tracks=[track])
playlist = self.core.playlists.save(playlist) playlist = self.core.playlists.save(playlist)
@ -112,8 +111,7 @@ class LocalPlaylistsControllerTest(
self.assert_(backend.playlists.playlists) self.assert_(backend.playlists.playlists)
self.assertEqual( self.assertEqual(
path_to_uri(playlist_path), 'local:playlist:test', backend.playlists.playlists[0].uri)
backend.playlists.playlists[0].uri)
self.assertEqual( self.assertEqual(
playlist.name, backend.playlists.playlists[0].name) playlist.name, backend.playlists.playlists[0].name)
self.assertEqual( self.assertEqual(

View File

@ -98,7 +98,7 @@ expected_tracks = []
def generate_track(path, ident): def generate_track(path, ident):
uri = path_to_uri(path_to_data_dir(path)) uri = 'local:track:%s' % path
track = Track( track = Track(
uri=uri, name='trackname', artists=expected_artists, uri=uri, name='trackname', artists=expected_artists,
album=expected_albums[0], track_no=1, date='2006', length=4000, album=expected_albums[0], track_no=1, date='2006', length=4000,
@ -126,11 +126,10 @@ class MPDTagCacheToTracksTest(unittest.TestCase):
def test_simple_cache(self): def test_simple_cache(self):
tracks = parse_mpd_tag_cache( tracks = parse_mpd_tag_cache(
path_to_data_dir('simple_tag_cache'), path_to_data_dir('')) path_to_data_dir('simple_tag_cache'), path_to_data_dir(''))
uri = path_to_uri(path_to_data_dir('song1.mp3'))
track = Track( track = Track(
uri=uri, name='trackname', artists=expected_artists, track_no=1, uri='local:track:song1.mp3', name='trackname',
album=expected_albums[0], date='2006', length=4000, artists=expected_artists, track_no=1, album=expected_albums[0],
last_modified=1272319626) date='2006', length=4000, last_modified=1272319626)
self.assertEqual(set([track]), tracks) self.assertEqual(set([track]), tracks)
def test_advanced_cache(self): def test_advanced_cache(self):
@ -142,12 +141,11 @@ class MPDTagCacheToTracksTest(unittest.TestCase):
tracks = parse_mpd_tag_cache( tracks = parse_mpd_tag_cache(
path_to_data_dir('utf8_tag_cache'), path_to_data_dir('')) path_to_data_dir('utf8_tag_cache'), path_to_data_dir(''))
uri = path_to_uri(path_to_data_dir('song1.mp3'))
artists = [Artist(name='æøå')] artists = [Artist(name='æøå')]
album = Album(name='æøå', artists=artists) album = Album(name='æøå', artists=artists)
track = Track( track = Track(
uri=uri, name='æøå', artists=artists, album=album, length=4000, uri='local:track:song1.mp3', name='æøå', artists=artists,
last_modified=1272319626) album=album, length=4000, last_modified=1272319626)
self.assertEqual(track, list(tracks)[0]) self.assertEqual(track, list(tracks)[0])
@ -159,8 +157,8 @@ class MPDTagCacheToTracksTest(unittest.TestCase):
def test_cache_with_blank_track_info(self): def test_cache_with_blank_track_info(self):
tracks = parse_mpd_tag_cache( tracks = parse_mpd_tag_cache(
path_to_data_dir('blank_tag_cache'), path_to_data_dir('')) path_to_data_dir('blank_tag_cache'), path_to_data_dir(''))
uri = path_to_uri(path_to_data_dir('song1.mp3')) expected = Track(
expected = Track(uri=uri, length=4000, last_modified=1272319626) uri='local:track:song1.mp3', length=4000, last_modified=1272319626)
self.assertEqual(set([expected]), tracks) self.assertEqual(set([expected]), tracks)
def test_musicbrainz_tagcache(self): def test_musicbrainz_tagcache(self):
@ -183,10 +181,10 @@ class MPDTagCacheToTracksTest(unittest.TestCase):
def test_albumartist_tag_cache(self): def test_albumartist_tag_cache(self):
tracks = parse_mpd_tag_cache( tracks = parse_mpd_tag_cache(
path_to_data_dir('albumartist_tag_cache'), path_to_data_dir('')) path_to_data_dir('albumartist_tag_cache'), path_to_data_dir(''))
uri = path_to_uri(path_to_data_dir('song1.mp3'))
artist = Artist(name='albumartistname') artist = Artist(name='albumartistname')
album = expected_albums[0].copy(artists=[artist]) album = expected_albums[0].copy(artists=[artist])
track = Track( track = Track(
uri=uri, name='trackname', artists=expected_artists, track_no=1, uri='local:track:song1.mp3', name='trackname',
album=album, date='2006', length=4000, last_modified=1272319626) artists=expected_artists, track_no=1, album=album, date='2006',
length=4000, last_modified=1272319626)
self.assertEqual(track, list(tracks)[0]) self.assertEqual(track, list(tracks)[0])

View File

@ -3,22 +3,22 @@ mpd_version: 0.14.2
fs_charset: UTF-8 fs_charset: UTF-8
info_end info_end
songList begin songList begin
key: uri1 key: key1
file: /uri1 file: /path1
Artist: artist1 Artist: artist1
Title: track1 Title: track1
Album: album1 Album: album1
Date: 2001-02-03 Date: 2001-02-03
Time: 4 Time: 4
key: uri2 key: key1
file: /uri2 file: /path2
Artist: artist2 Artist: artist2
Title: track2 Title: track2
Album: album2 Album: album2
Date: 2002 Date: 2002
Time: 4 Time: 4
key: uri3 key: key3
file: /uri3 file: /path3
Artist: artist3 Artist: artist3
Title: track3 Title: track3
Album: album3 Album: album3

View File

@ -4,7 +4,6 @@ from __future__ import unicode_literals
import os import os
import shutil import shutil
import sys
import tempfile import tempfile
import unittest import unittest
@ -117,86 +116,42 @@ class GetOrCreateFileTest(unittest.TestCase):
class PathToFileURITest(unittest.TestCase): class PathToFileURITest(unittest.TestCase):
def test_simple_path(self): def test_simple_path(self):
if sys.platform == 'win32': result = path.path_to_uri('/etc/fstab')
result = path.path_to_uri('C:/WINDOWS/clock.avi') self.assertEqual(result, 'file:///etc/fstab')
self.assertEqual(result, 'file:///C://WINDOWS/clock.avi')
else:
result = path.path_to_uri('/etc/fstab')
self.assertEqual(result, 'file:///etc/fstab')
def test_dir_and_path(self):
if sys.platform == 'win32':
result = path.path_to_uri('C:/WINDOWS/', 'clock.avi')
self.assertEqual(result, 'file:///C://WINDOWS/clock.avi')
else:
result = path.path_to_uri('/etc', 'fstab')
self.assertEqual(result, 'file:///etc/fstab')
def test_space_in_path(self): def test_space_in_path(self):
if sys.platform == 'win32': result = path.path_to_uri('/tmp/test this')
result = path.path_to_uri('C:/test this') self.assertEqual(result, 'file:///tmp/test%20this')
self.assertEqual(result, 'file:///C://test%20this')
else:
result = path.path_to_uri('/tmp/test this')
self.assertEqual(result, 'file:///tmp/test%20this')
def test_unicode_in_path(self): def test_unicode_in_path(self):
if sys.platform == 'win32': result = path.path_to_uri('/tmp/æøå')
result = path.path_to_uri('C:/æøå') self.assertEqual(result, 'file:///tmp/%C3%A6%C3%B8%C3%A5')
self.assertEqual(result, 'file:///C://%C3%A6%C3%B8%C3%A5')
else:
result = path.path_to_uri('/tmp/æøå')
self.assertEqual(result, 'file:///tmp/%C3%A6%C3%B8%C3%A5')
def test_utf8_in_path(self): def test_utf8_in_path(self):
if sys.platform == 'win32': result = path.path_to_uri('/tmp/æøå'.encode('utf-8'))
result = path.path_to_uri('C:/æøå'.encode('utf-8')) self.assertEqual(result, 'file:///tmp/%C3%A6%C3%B8%C3%A5')
self.assertEqual(result, 'file:///C://%C3%A6%C3%B8%C3%A5')
else:
result = path.path_to_uri('/tmp/æøå'.encode('utf-8'))
self.assertEqual(result, 'file:///tmp/%C3%A6%C3%B8%C3%A5')
def test_latin1_in_path(self): def test_latin1_in_path(self):
if sys.platform == 'win32': result = path.path_to_uri('/tmp/æøå'.encode('latin-1'))
result = path.path_to_uri('C:/æøå'.encode('latin-1')) self.assertEqual(result, 'file:///tmp/%E6%F8%E5')
self.assertEqual(result, 'file:///C://%E6%F8%E5')
else:
result = path.path_to_uri('/tmp/æøå'.encode('latin-1'))
self.assertEqual(result, 'file:///tmp/%E6%F8%E5')
class UriToPathTest(unittest.TestCase): class UriToPathTest(unittest.TestCase):
def test_simple_uri(self): def test_simple_uri(self):
if sys.platform == 'win32': result = path.uri_to_path('file:///etc/fstab')
result = path.uri_to_path('file:///C://WINDOWS/clock.avi') self.assertEqual(result, '/etc/fstab'.encode('utf-8'))
self.assertEqual(result, 'C:/WINDOWS/clock.avi'.encode('utf-8'))
else:
result = path.uri_to_path('file:///etc/fstab')
self.assertEqual(result, '/etc/fstab'.encode('utf-8'))
def test_space_in_uri(self): def test_space_in_uri(self):
if sys.platform == 'win32': result = path.uri_to_path('file:///tmp/test%20this')
result = path.uri_to_path('file:///C://test%20this') self.assertEqual(result, '/tmp/test this'.encode('utf-8'))
self.assertEqual(result, 'C:/test this'.encode('utf-8'))
else:
result = path.uri_to_path('file:///tmp/test%20this')
self.assertEqual(result, '/tmp/test this'.encode('utf-8'))
def test_unicode_in_uri(self): def test_unicode_in_uri(self):
if sys.platform == 'win32': result = path.uri_to_path('file:///tmp/%C3%A6%C3%B8%C3%A5')
result = path.uri_to_path('file:///C://%C3%A6%C3%B8%C3%A5') self.assertEqual(result, '/tmp/æøå'.encode('utf-8'))
self.assertEqual(result, 'C:/æøå'.encode('utf-8'))
else:
result = path.uri_to_path('file:///tmp/%C3%A6%C3%B8%C3%A5')
self.assertEqual(result, '/tmp/æøå'.encode('utf-8'))
def test_latin1_in_uri(self): def test_latin1_in_uri(self):
if sys.platform == 'win32': result = path.uri_to_path('file:///tmp/%E6%F8%E5')
result = path.uri_to_path('file:///C://%E6%F8%E5') self.assertEqual(result, '/tmp/æøå'.encode('latin-1'))
self.assertEqual(result, 'C:/æøå'.encode('latin-1'))
else:
result = path.uri_to_path('file:///tmp/%E6%F8%E5')
self.assertEqual(result, '/tmp/æøå'.encode('latin-1'))
class SplitPathTest(unittest.TestCase): class SplitPathTest(unittest.TestCase):