Merge pull request #482 from adamcik/feature/local-uris

Switch local over to custom uris and make library updater plugable.
This commit is contained in:
Stein Magnus Jodal 2013-08-05 13:52:22 -07:00
commit 22a0e1bd25
21 changed files with 179 additions and 200 deletions

View File

@ -32,6 +32,14 @@ v0.15.0 (UNRELEASED)
- An album's number of discs and a track's disc number are now extracted when
scanning your music collection.
- 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**
- 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.
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
#: :class:`~mopidy.backends.base.BasePlaybackProvider`, or :class:`None` if
#: the backend doesn't provide playback.
@ -40,9 +35,6 @@ class Backend(object):
def has_library(self):
return self.library is not None
def has_updater(self):
return self.updater is not None
def has_playback(self):
return self.playback is not None
@ -96,15 +88,7 @@ class BaseLibraryProvider(object):
class BaseLibraryUpdateProvider(object):
"""
: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
uri_schemes = []
def load(self):
"""Loads the library and returns all tracks in it.
@ -172,9 +156,22 @@ class BasePlaybackProvider(object):
:rtype: :class:`True` if successful, else :class:`False`
"""
self.audio.prepare_change()
self.audio.set_uri(track.uri).get()
self.change_track(track)
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):
"""
Resume playback at the same time position playback was paused.

View File

@ -29,3 +29,7 @@ class Extension(ext.Extension):
def get_backend_classes(self):
from .actor import 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.utils import encoding, path
from .library import LocalLibraryProvider, LocalLibraryUpdateProvider
from .library import LocalLibraryProvider
from .playlists import LocalPlaylistsProvider
from .playback import LocalPlaybackProvider
logger = logging.getLogger('mopidy.backends.local')
@ -23,11 +24,10 @@ class LocalBackend(pykka.ThreadingActor, base.Backend):
self.check_dirs_and_files()
self.library = LocalLibraryProvider(backend=self)
self.updater = LocalLibraryUpdateProvider(backend=self)
self.playback = base.BasePlaybackProvider(audio=audio, backend=self)
self.playback = LocalPlaybackProvider(audio=audio, backend=self)
self.playlists = LocalPlaylistsProvider(backend=self)
self.uri_schemes = ['file']
self.uri_schemes = ['local']
def check_dirs_and_files(self):
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)
else:
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):
# 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)
else:
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):
for (_, values) in query.iteritems():
@ -135,11 +137,12 @@ class LocalLibraryProvider(base.BaseLibraryProvider):
# TODO: rename and move to tagcache extension.
class LocalLibraryUpdateProvider(base.BaseLibraryProvider):
def __init__(self, *args, **kwargs):
super(LocalLibraryUpdateProvider, self).__init__(*args, **kwargs)
uri_schemes = ['local']
def __init__(self, config):
self._tracks = {}
self._media_dir = self.backend.config['local']['media_dir']
self._tag_cache_file = self.backend.config['local']['tag_cache_file']
self._media_dir = config['local']['media_dir']
self._tag_cache_file = config['local']['tag_cache_file']
def load(self):
tracks = parse_mpd_tag_cache(self._tag_cache_file, self._media_dir)
@ -156,6 +159,8 @@ class LocalLibraryUpdateProvider(base.BaseLibraryProvider):
def commit(self):
directory, basename = os.path.split(self._tag_cache_file)
# TODO: cleanup directory/basename.* files.
tmp = tempfile.NamedTemporaryFile(
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):
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)
return self.save(playlist)
@ -37,6 +37,7 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
self._delete_m3u(playlist.uri)
def lookup(self, uri):
# TODO: store as {uri: playlist}?
for playlist in self._playlists:
if playlist.uri == uri:
return playlist
@ -45,8 +46,8 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
playlists = []
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]
uri = 'local:playlist:%s' % name
tracks = []
for track_uri in parse_m3u(m3u, self._media_dir):
@ -61,6 +62,7 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
playlists.append(playlist)
self.playlists = playlists
# TODO: send what scheme we loaded them for?
listener.BackendListener.send('playlists_loaded')
logger.info(
@ -86,38 +88,30 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
return playlist
def _get_m3u_path(self, name):
name = formatting.slugify(name)
file_path = os.path.join(self._playlists_dir, name + '.m3u')
def _m3u_uri_to_path(self, uri):
# TODO: create uri handling helpers for local uri types.
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)
return file_path
def _save_m3u(self, playlist):
file_path = path.uri_to_path(playlist.uri)
path.check_file_path_is_inside_base_dir(file_path, self._playlists_dir)
file_path = self._m3u_uri_to_path(playlist.uri)
with open(file_path, 'w') as file_handle:
for track in playlist.tracks:
if track.uri.startswith('file://'):
uri = path.uri_to_path(track.uri)
else:
uri = track.uri
file_handle.write(uri + '\n')
file_handle.write(track.uri + '\n')
def _delete_m3u(self, uri):
file_path = path.uri_to_path(uri)
path.check_file_path_is_inside_base_dir(file_path, self._playlists_dir)
file_path = self._m3u_uri_to_path(uri)
if os.path.exists(file_path):
os.remove(file_path)
def _rename_m3u(self, playlist):
src_file_path = path.uri_to_path(playlist.uri)
path.check_file_path_is_inside_base_dir(
src_file_path, self._playlists_dir)
dst_name = formatting.slugify(playlist.name)
dst_uri = 'local:playlist:%s.m3u' % dst_name
dst_file_path = self._get_m3u_path(playlist.name)
path.check_file_path_is_inside_base_dir(
dst_file_path, self._playlists_dir)
src_file_path = self._m3u_uri_to_path(playlist.uri)
dst_file_path = self._m3u_uri_to_path(dst_uri)
shutil.move(src_file_path, dst_file_path)
return playlist.copy(uri=path.path_to_uri(dst_file_path))
return playlist.copy(uri=dst_uri)

View File

@ -1,7 +1,9 @@
from __future__ import unicode_literals
import logging
import os
import urllib
import urlparse
from mopidy.models import Track, Artist, Album
from mopidy.utils.encoding import locale_decode
@ -30,7 +32,6 @@ def parse_m3u(file_path, media_dir):
- m3u files are latin-1.
- This function does not bother with Extended M3U directives.
"""
# TODO: uris as bytes
uris = []
try:
@ -46,16 +47,19 @@ def parse_m3u(file_path, media_dir):
if line.startswith('#'):
continue
# FIXME what about other URI types?
if line.startswith('file://'):
if urlparse.urlsplit(line).scheme:
uris.append(line)
elif os.path.normpath(line) == os.path.abspath(line):
path = path_to_uri(line)
uris.append(path)
else:
path = path_to_uri(media_dir, line)
path = path_to_uri(os.path.join(media_dir, line))
uris.append(path)
return uris
# TODO: remove music_dir from API
def parse_mpd_tag_cache(tag_cache, music_dir=''):
"""
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)
if key == b'key':
_convert_mpd_data(current, tracks, music_dir)
_convert_mpd_data(current, tracks)
current.clear()
current[key.lower()] = value.decode('utf-8')
_convert_mpd_data(current, tracks, music_dir)
_convert_mpd_data(current, tracks)
return tracks
def _convert_mpd_data(data, tracks, music_dir):
def _convert_mpd_data(data, tracks):
if not data:
return
@ -160,15 +164,8 @@ def _convert_mpd_data(data, tracks, music_dir):
path = data['file'][1:]
else:
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 = Track(**track_kwargs)

View File

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

View File

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

View File

@ -79,6 +79,14 @@ class Extension(object):
"""
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):
"""Hook for registering custom GStreamer elements

View File

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

View File

@ -27,7 +27,6 @@ pygst.require('0.10')
import gst
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.utils import log, path, versioning
@ -45,9 +44,9 @@ def main():
log.setup_root_logger()
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_files, extensions.values(), config_overrides)
config_files, extensions, config_overrides)
log.setup_log_levels(config)
if not config['local']['media_dir']:
@ -56,10 +55,21 @@ def main():
# TODO: missing config error checking and other default setup code.
audio = dummy_audio.DummyAudio()
local_backend_classes = extensions['local'].get_backend_classes()
local_backend = local_backend_classes[0](config, audio)
local_updater = local_backend.updater
updaters = {}
for e in extensions:
for updater_class in e.get_library_updaters():
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']

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import unittest
from mopidy.backends.local import actor
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.backends.base.playlists import (
@ -89,21 +89,20 @@ class LocalPlaylistsControllerTest(
def test_playlist_contents_is_written_to_disk(self):
track = Track(uri=generate_song(1))
track_path = track.uri[len('file://'):]
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 = self.core.playlists.save(playlist)
with open(playlist_path) as playlist_file:
contents = playlist_file.read()
self.assertEqual(track_path, contents.strip())
self.assertEqual(track.uri, contents.strip())
def test_playlists_are_loaded_at_startup(self):
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 = playlist.copy(tracks=[track])
playlist = self.core.playlists.save(playlist)
@ -112,8 +111,7 @@ class LocalPlaylistsControllerTest(
self.assert_(backend.playlists.playlists)
self.assertEqual(
path_to_uri(playlist_path),
backend.playlists.playlists[0].uri)
'local:playlist:test', backend.playlists.playlists[0].uri)
self.assertEqual(
playlist.name, backend.playlists.playlists[0].name)
self.assertEqual(

View File

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

View File

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

View File

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