local: Remove tag cache support

- Updates doc references to tag cache
- Removes tag cache from config and marks it deprecated
- Removes tag cache from setup.py
- Removes tag cache from config converter
- Removes tag cache from tests
- Converts local library tests to use JSON.
This commit is contained in:
Thomas Adamcik 2013-12-04 21:12:28 +01:00
parent da63942b48
commit 9c2d38e989
29 changed files with 28 additions and 1142 deletions

View File

@ -66,11 +66,8 @@ Sample session::
-OK
+ACK [2@0] {listallinfo} incorrect arguments
To ensure that Mopidy and MPD have comparable state it is suggested you setup
both to use ``tests/data/advanced_tag_cache`` for their tag cache and
``tests/data/scanner/advanced/`` for the music folder and ``tests/data`` for
playlists.
To ensure that Mopidy and MPD have comparable state it is suggested you scan
the same media directory with both servers.
Documentation writing
=====================

View File

@ -43,10 +43,6 @@ Configuration values
Path to playlists directory with m3u files for local media.
.. confval:: local/tag_cache_file
Path to tag cache for local media.
.. confval:: local/scan_timeout
Number of milliseconds before giving up scanning a file and moving on to

View File

@ -20,7 +20,7 @@ class Extension(ext.Extension):
schema = super(Extension, self).get_config_schema()
schema['media_dir'] = config.Path()
schema['playlists_dir'] = config.Path()
schema['tag_cache_file'] = config.Path()
schema['tag_cache_file'] = config.Deprecated()
schema['scan_timeout'] = config.Integer(
minimum=1000, maximum=1000*60*60)
schema['excluded_file_extensions'] = config.List(optional=True)

View File

@ -2,7 +2,6 @@
enabled = true
media_dir = $XDG_MUSIC_DIR
playlists_dir = $XDG_DATA_DIR/mopidy/local/playlists
tag_cache_file = $XDG_DATA_DIR/mopidy/local/tag_cache
scan_timeout = 1000
excluded_file_extensions =
.html

View File

@ -1,28 +0,0 @@
from __future__ import unicode_literals
import os
import mopidy
from mopidy import config, ext
class Extension(ext.Extension):
dist_name = 'Mopidy-Local-Tagcache'
ext_name = 'local-tagcache'
version = mopidy.__version__
def get_default_config(self):
conf_file = os.path.join(os.path.dirname(__file__), 'ext.conf')
return config.read(conf_file)
# Config only contains local-tagcache/enabled since we are not setting our
# own schema.
def get_backend_classes(self):
from .actor import LocalTagcacheBackend
return [LocalTagcacheBackend]
def get_library_updaters(self):
from .library import LocalTagcacheLibraryUpdateProvider
return [LocalTagcacheLibraryUpdateProvider]

View File

@ -1,30 +0,0 @@
from __future__ import unicode_literals
import logging
import pykka
from mopidy.backends import base
from mopidy.utils import encoding, path
from .library import LocalTagcacheLibraryProvider
logger = logging.getLogger('mopidy.backends.local.tagcache')
class LocalTagcacheBackend(pykka.ThreadingActor, base.Backend):
def __init__(self, config, audio):
super(LocalTagcacheBackend, self).__init__()
self.config = config
self.check_dirs_and_files()
self.library = LocalTagcacheLibraryProvider(backend=self)
self.uri_schemes = ['local']
def check_dirs_and_files(self):
try:
path.get_or_create_file(self.config['local']['tag_cache_file'])
except EnvironmentError as error:
logger.warning(
'Could not create empty tag cache file: %s',
encoding.locale_decode(error))

View File

@ -1,2 +0,0 @@
[local-tagcache]
enabled = false

View File

@ -1,101 +0,0 @@
from __future__ import unicode_literals
import logging
import os
import tempfile
from mopidy.backends import base
from mopidy.backends.local import search
from mopidy.backends.local.translator import local_to_file_uri
from .translator import parse_mpd_tag_cache, tracks_to_tag_cache_format
logger = logging.getLogger('mopidy.backends.local.tagcache')
class LocalTagcacheLibraryProvider(base.BaseLibraryProvider):
def __init__(self, *args, **kwargs):
super(LocalTagcacheLibraryProvider, self).__init__(*args, **kwargs)
self._uri_mapping = {}
self._media_dir = self.backend.config['local']['media_dir']
self._tag_cache_file = self.backend.config['local']['tag_cache_file']
self.refresh()
def refresh(self, uri=None):
logger.debug(
'Loading local tracks from %s using %s',
self._media_dir, self._tag_cache_file)
tracks = parse_mpd_tag_cache(self._tag_cache_file, self._media_dir)
uris_to_remove = set(self._uri_mapping)
for track in tracks:
self._uri_mapping[track.uri] = track
uris_to_remove.discard(track.uri)
for uri in uris_to_remove:
del self._uri_mapping[uri]
logger.info(
'Loaded %d local tracks from %s using %s',
len(tracks), self._media_dir, self._tag_cache_file)
def lookup(self, uri):
try:
return [self._uri_mapping[uri]]
except KeyError:
logger.debug('Failed to lookup %r', uri)
return []
def find_exact(self, query=None, uris=None):
tracks = self._uri_mapping.values()
return search.find_exact(tracks, query=query, uris=uris)
def search(self, query=None, uris=None):
tracks = self._uri_mapping.values()
return search.search(tracks, query=query, uris=uris)
class LocalTagcacheLibraryUpdateProvider(base.BaseLibraryProvider):
uri_schemes = ['local']
def __init__(self, config):
self._tracks = {}
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)
for track in tracks:
# TODO: this should use uris as is, i.e. hack that should go away
# with tag caches.
uri = local_to_file_uri(track.uri, self._media_dir)
self._tracks[uri] = track.copy(uri=uri)
return tracks
def add(self, track):
self._tracks[track.uri] = track
def remove(self, uri):
if uri in self._tracks:
del self._tracks[uri]
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)
try:
for row in tracks_to_tag_cache_format(
self._tracks.values(), self._media_dir):
if len(row) == 1:
tmp.write(('%s\n' % row).encode('utf-8'))
else:
tmp.write(('%s: %s\n' % row).encode('utf-8'))
os.rename(tmp.name, self._tag_cache_file)
finally:
if os.path.exists(tmp.name):
os.remove(tmp.name)

View File

@ -1,246 +0,0 @@
from __future__ import unicode_literals
import logging
import os
import re
import urllib
from mopidy.frontends.mpd import translator as mpd, protocol
from mopidy.models import Track, Artist, Album
from mopidy.utils.encoding import locale_decode
from mopidy.utils.path import mtime as get_mtime, split_path, uri_to_path
logger = logging.getLogger('mopidy.backends.local.tagcache')
# 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.
"""
tracks = set()
try:
with open(tag_cache) as library:
contents = library.read()
except IOError as error:
logger.warning('Could not open tag cache: %s', locale_decode(error))
return tracks
current = {}
state = None
# TODO: uris as bytes
for line in contents.split(b'\n'):
if line == b'songList begin':
state = 'songs'
continue
elif line == b'songList end':
state = None
continue
elif not state:
continue
key, value = line.split(b': ', 1)
if key == b'key':
_convert_mpd_data(current, tracks)
current.clear()
current[key.lower()] = value.decode('utf-8')
_convert_mpd_data(current, tracks)
return tracks
def _convert_mpd_data(data, tracks):
if not data:
return
track_kwargs = {}
album_kwargs = {}
artist_kwargs = {}
albumartist_kwargs = {}
if 'track' in data:
if '/' in data['track']:
album_kwargs['num_tracks'] = int(data['track'].split('/')[1])
track_kwargs['track_no'] = int(data['track'].split('/')[0])
else:
track_kwargs['track_no'] = int(data['track'])
if 'mtime' in data:
track_kwargs['last_modified'] = int(data['mtime'])
if 'artist' in data:
artist_kwargs['name'] = data['artist']
if 'albumartist' in data:
albumartist_kwargs['name'] = data['albumartist']
if 'composer' in data:
track_kwargs['composers'] = [Artist(name=data['composer'])]
if 'performer' in data:
track_kwargs['performers'] = [Artist(name=data['performer'])]
if 'album' in data:
album_kwargs['name'] = data['album']
if 'title' in data:
track_kwargs['name'] = data['title']
if 'genre' in data:
track_kwargs['genre'] = data['genre']
if 'date' in data:
track_kwargs['date'] = data['date']
if 'comment' in data:
track_kwargs['comment'] = data['comment']
if 'musicbrainz_trackid' in data:
track_kwargs['musicbrainz_id'] = data['musicbrainz_trackid']
if 'musicbrainz_albumid' in data:
album_kwargs['musicbrainz_id'] = data['musicbrainz_albumid']
if 'musicbrainz_artistid' in data:
artist_kwargs['musicbrainz_id'] = data['musicbrainz_artistid']
if 'musicbrainz_albumartistid' in data:
albumartist_kwargs['musicbrainz_id'] = (
data['musicbrainz_albumartistid'])
if artist_kwargs:
artist = Artist(**artist_kwargs)
track_kwargs['artists'] = [artist]
if albumartist_kwargs:
albumartist = Artist(**albumartist_kwargs)
album_kwargs['artists'] = [albumartist]
if album_kwargs:
album = Album(**album_kwargs)
track_kwargs['album'] = album
if data['file'][0] == '/':
path = data['file'][1:]
else:
path = data['file']
track_kwargs['uri'] = 'local:track:%s' % path
track_kwargs['length'] = int(data.get('time', 0)) * 1000
track = Track(**track_kwargs)
tracks.add(track)
def tracks_to_tag_cache_format(tracks, media_dir):
"""
Format list of tracks for output to MPD tag cache
:param tracks: the tracks
:type tracks: list of :class:`mopidy.models.Track`
:param media_dir: the path to the music dir
:type media_dir: string
:rtype: list of lists of two-tuples
"""
result = [
('info_begin',),
('mpd_version', protocol.VERSION),
('fs_charset', protocol.ENCODING),
('info_end',)
]
tracks.sort(key=lambda t: t.uri)
dirs, files = tracks_to_directory_tree(tracks, media_dir)
_add_to_tag_cache(result, dirs, files, media_dir)
return result
# TODO: bytes only
def _add_to_tag_cache(result, dirs, files, media_dir):
base_path = media_dir.encode('utf-8')
for path, (entry_dirs, entry_files) in dirs.items():
try:
text_path = path.decode('utf-8')
except UnicodeDecodeError:
text_path = urllib.quote(path).decode('utf-8')
name = os.path.split(text_path)[1]
result.append(('directory', text_path))
result.append(('mtime', get_mtime(os.path.join(base_path, path))))
result.append(('begin', name))
_add_to_tag_cache(result, entry_dirs, entry_files, media_dir)
result.append(('end', name))
result.append(('songList begin',))
for track in files:
track_result = dict(mpd.track_to_mpd_format(track))
# XXX Don't save comments to the tag cache as they may span multiple
# lines. We'll start saving track comments when we move from tag_cache
# to a JSON file. See #579 for details.
if 'Comment' in track_result:
del track_result['Comment']
path = uri_to_path(track_result['file'])
try:
text_path = path.decode('utf-8')
except UnicodeDecodeError:
text_path = urllib.quote(path).decode('utf-8')
relative_path = os.path.relpath(path, base_path)
relative_uri = urllib.quote(relative_path)
# TODO: use track.last_modified
track_result['file'] = relative_uri
track_result['mtime'] = get_mtime(path)
track_result['key'] = os.path.basename(text_path)
track_result = order_mpd_track_info(track_result.items())
result.extend(track_result)
result.append(('songList end',))
def tracks_to_directory_tree(tracks, media_dir):
directories = ({}, [])
for track in tracks:
path = b''
current = directories
absolute_track_dir_path = os.path.dirname(uri_to_path(track.uri))
relative_track_dir_path = re.sub(
'^' + re.escape(media_dir), b'', absolute_track_dir_path)
for part in split_path(relative_track_dir_path):
path = os.path.join(path, part)
if path not in current[0]:
current[0][path] = ({}, [])
current = current[0][path]
current[1].append(track)
return directories
MPD_KEY_ORDER = '''
key file Time Artist Album AlbumArtist Title Track Genre Date Composer
Performer Comment Disc MUSICBRAINZ_ALBUMID MUSICBRAINZ_ALBUMARTISTID
MUSICBRAINZ_ARTISTID MUSICBRAINZ_TRACKID mtime
'''.split()
def order_mpd_track_info(result):
"""
Order results from
:func:`mopidy.frontends.mpd.translator.track_to_mpd_format` so that it
matches MPD's ordering. Simply a cosmetic fix for easier diffing of
tag_caches.
:param result: the track info
:type result: list of tuples
:rtype: list of tuples
"""
return sorted(result, key=lambda i: MPD_KEY_ORDER.index(i[0]))

View File

@ -45,7 +45,6 @@ def convert(settings):
helper('local/media_dir', 'LOCAL_MUSIC_PATH')
helper('local/playlists_dir', 'LOCAL_PLAYLIST_PATH')
helper('local/tag_cache_file', 'LOCAL_TAG_CACHE_FILE')
helper('spotify/username', 'SPOTIFY_USERNAME')
helper('spotify/password', 'SPOTIFY_PASSWORD')

View File

@ -43,7 +43,6 @@ setup(
'mopidy.ext': [
'http = mopidy.frontends.http:Extension [http]',
'local = mopidy.backends.local:Extension',
'local-tagcache = mopidy.backends.local.tagcache:Extension',
'local-json = mopidy.backends.local.json:Extension',
'mpd = mopidy.frontends.mpd:Extension',
'stream = mopidy.backends.stream:Extension',

View File

@ -18,7 +18,6 @@ class LocalBackendEventsTest(unittest.TestCase):
'local': {
'media_dir': path_to_data_dir(''),
'playlists_dir': b'',
'tag_cache_file': path_to_data_dir('empty_tag_cache'),
}
}

View File

@ -1,12 +1,13 @@
from __future__ import unicode_literals
import copy
import tempfile
import unittest
import pykka
from mopidy import core
from mopidy.backends.local.tagcache import actor
from mopidy.backends.local.json import actor
from mopidy.models import Track, Album, Artist
from tests import path_to_data_dir
@ -61,12 +62,14 @@ class LocalLibraryProviderTest(unittest.TestCase):
'local': {
'media_dir': path_to_data_dir(''),
'playlists_dir': b'',
'tag_cache_file': path_to_data_dir('library_tag_cache'),
}
},
'local-json': {
'json_file': path_to_data_dir('library.json.gz'),
},
}
def setUp(self):
self.backend = actor.LocalTagcacheBackend.start(
self.backend = actor.LocalJsonBackend.start(
config=self.config, audio=None).proxy()
self.core = core.Core(backends=[self.backend])
self.library = self.core.library
@ -85,27 +88,27 @@ class LocalLibraryProviderTest(unittest.TestCase):
# Verifies that https://github.com/mopidy/mopidy/issues/500
# has been fixed.
tag_cache = tempfile.NamedTemporaryFile()
with open(self.config['local']['tag_cache_file']) as fh:
tag_cache.write(fh.read())
tag_cache.flush()
with tempfile.NamedTemporaryFile() as library:
with open(self.config['local-json']['json_file']) as fh:
library.write(fh.read())
library.flush()
config = {'local': self.config['local'].copy()}
config['local']['tag_cache_file'] = tag_cache.name
backend = actor.LocalTagcacheBackend(config=config, audio=None)
config = copy.deepcopy(self.config)
config['local-json']['json_file'] = library.name
backend = actor.LocalJsonBackend(config=config, audio=None)
# Sanity check that value is in tag cache
result = backend.library.lookup(self.tracks[0].uri)
self.assertEqual(result, self.tracks[0:1])
# Sanity check that value is in the library
result = backend.library.lookup(self.tracks[0].uri)
self.assertEqual(result, self.tracks[0:1])
# Clear tag cache and refresh
tag_cache.seek(0)
tag_cache.truncate()
backend.library.refresh()
# Clear library and refresh
library.seek(0)
library.truncate()
backend.library.refresh()
# Now it should be gone.
result = backend.library.lookup(self.tracks[0].uri)
self.assertEqual(result, [])
# Now it should be gone.
result = backend.library.lookup(self.tracks[0].uri)
self.assertEqual(result, [])
def test_lookup(self):
tracks = self.library.lookup(self.tracks[0].uri)
@ -115,6 +118,7 @@ class LocalLibraryProviderTest(unittest.TestCase):
tracks = self.library.lookup('fake uri')
self.assertEqual(tracks, [])
# TODO: move to search_test module
def test_find_exact_no_hits(self):
result = self.library.find_exact(track_name=['unknown track'])
self.assertEqual(list(result[0].tracks), [])

View File

@ -23,7 +23,6 @@ class LocalPlaybackProviderTest(unittest.TestCase):
'local': {
'media_dir': path_to_data_dir(''),
'playlists_dir': b'',
'tag_cache_file': path_to_data_dir('empty_tag_cache'),
}
}

View File

@ -20,7 +20,6 @@ class LocalPlaylistsProviderTest(unittest.TestCase):
config = {
'local': {
'media_dir': path_to_data_dir(''),
'tag_cache_file': path_to_data_dir('library_tag_cache'),
}
}

View File

@ -1,346 +0,0 @@
# encoding: utf-8
from __future__ import unicode_literals
import os
import unittest
from mopidy.backends.local.tagcache import translator
from mopidy.frontends.mpd import translator as mpd, protocol
from mopidy.models import Album, Artist, Track
from mopidy.utils.path import mtime, uri_to_path
from tests import path_to_data_dir
class TracksToTagCacheFormatTest(unittest.TestCase):
def setUp(self):
self.media_dir = '/dir/subdir'
mtime.set_fake_time(1234567)
def tearDown(self):
mtime.undo_fake()
def translate(self, track):
base_path = self.media_dir.encode('utf-8')
result = dict(mpd.track_to_mpd_format(track))
result['file'] = uri_to_path(result['file'])[len(base_path) + 1:]
result['key'] = os.path.basename(result['file'])
result['mtime'] = mtime('')
return translator.order_mpd_track_info(result.items())
def consume_headers(self, result):
self.assertEqual(('info_begin',), result[0])
self.assertEqual(('mpd_version', protocol.VERSION), result[1])
self.assertEqual(('fs_charset', protocol.ENCODING), result[2])
self.assertEqual(('info_end',), result[3])
return result[4:]
def consume_song_list(self, result):
self.assertEqual(('songList begin',), result[0])
for i, row in enumerate(result):
if row == ('songList end',):
return result[1:i], result[i + 1:]
self.fail("Couldn't find songList end in result")
def consume_directory(self, result):
self.assertEqual('directory', result[0][0])
self.assertEqual(('mtime', mtime('.')), result[1])
self.assertEqual(('begin', os.path.split(result[0][1])[1]), result[2])
directory = result[2][1]
for i, row in enumerate(result):
if row == ('end', directory):
return result[3:i], result[i + 1:]
self.fail("Couldn't find end %s in result" % directory)
def test_empty_tag_cache_has_header(self):
result = translator.tracks_to_tag_cache_format([], self.media_dir)
result = self.consume_headers(result)
def test_empty_tag_cache_has_song_list(self):
result = translator.tracks_to_tag_cache_format([], self.media_dir)
result = self.consume_headers(result)
song_list, result = self.consume_song_list(result)
self.assertEqual(len(song_list), 0)
self.assertEqual(len(result), 0)
def test_tag_cache_has_header(self):
track = Track(uri='file:///dir/subdir/song.mp3')
result = translator.tracks_to_tag_cache_format([track], self.media_dir)
result = self.consume_headers(result)
def test_tag_cache_has_song_list(self):
track = Track(uri='file:///dir/subdir/song.mp3')
result = translator.tracks_to_tag_cache_format([track], self.media_dir)
result = self.consume_headers(result)
song_list, result = self.consume_song_list(result)
self.assert_(song_list)
self.assertEqual(len(result), 0)
def test_tag_cache_has_formated_track(self):
track = Track(uri='file:///dir/subdir/song.mp3')
formated = self.translate(track)
result = translator.tracks_to_tag_cache_format([track], self.media_dir)
result = self.consume_headers(result)
song_list, result = self.consume_song_list(result)
self.assertEqual(formated, song_list)
self.assertEqual(len(result), 0)
def test_tag_cache_has_formated_track_with_key_and_mtime(self):
track = Track(uri='file:///dir/subdir/song.mp3')
formated = self.translate(track)
result = translator.tracks_to_tag_cache_format([track], self.media_dir)
result = self.consume_headers(result)
song_list, result = self.consume_song_list(result)
self.assertEqual(formated, song_list)
self.assertEqual(len(result), 0)
def test_tag_cache_supports_directories(self):
track = Track(uri='file:///dir/subdir/folder/song.mp3')
formated = self.translate(track)
result = translator.tracks_to_tag_cache_format([track], self.media_dir)
result = self.consume_headers(result)
dir_data, result = self.consume_directory(result)
song_list, result = self.consume_song_list(result)
self.assertEqual(len(song_list), 0)
self.assertEqual(len(result), 0)
song_list, result = self.consume_song_list(dir_data)
self.assertEqual(len(result), 0)
self.assertEqual(formated, song_list)
def test_tag_cache_diretory_header_is_right(self):
track = Track(uri='file:///dir/subdir/folder/sub/song.mp3')
result = translator.tracks_to_tag_cache_format([track], self.media_dir)
result = self.consume_headers(result)
dir_data, result = self.consume_directory(result)
self.assertEqual(('directory', 'folder/sub'), dir_data[0])
self.assertEqual(('mtime', mtime('.')), dir_data[1])
self.assertEqual(('begin', 'sub'), dir_data[2])
def test_tag_cache_suports_sub_directories(self):
track = Track(uri='file:///dir/subdir/folder/sub/song.mp3')
formated = self.translate(track)
result = translator.tracks_to_tag_cache_format([track], self.media_dir)
result = self.consume_headers(result)
dir_data, result = self.consume_directory(result)
song_list, result = self.consume_song_list(result)
self.assertEqual(len(song_list), 0)
self.assertEqual(len(result), 0)
dir_data, result = self.consume_directory(dir_data)
song_list, result = self.consume_song_list(result)
self.assertEqual(len(result), 0)
self.assertEqual(len(song_list), 0)
song_list, result = self.consume_song_list(dir_data)
self.assertEqual(len(result), 0)
self.assertEqual(formated, song_list)
def test_tag_cache_supports_multiple_tracks(self):
tracks = [
Track(uri='file:///dir/subdir/song1.mp3'),
Track(uri='file:///dir/subdir/song2.mp3'),
]
formated = []
formated.extend(self.translate(tracks[0]))
formated.extend(self.translate(tracks[1]))
result = translator.tracks_to_tag_cache_format(tracks, self.media_dir)
result = self.consume_headers(result)
song_list, result = self.consume_song_list(result)
self.assertEqual(formated, song_list)
self.assertEqual(len(result), 0)
def test_tag_cache_supports_multiple_tracks_in_dirs(self):
tracks = [
Track(uri='file:///dir/subdir/song1.mp3'),
Track(uri='file:///dir/subdir/folder/song2.mp3'),
]
formated = []
formated.append(self.translate(tracks[0]))
formated.append(self.translate(tracks[1]))
result = translator.tracks_to_tag_cache_format(tracks, self.media_dir)
result = self.consume_headers(result)
dir_data, result = self.consume_directory(result)
song_list, song_result = self.consume_song_list(dir_data)
self.assertEqual(formated[1], song_list)
self.assertEqual(len(song_result), 0)
song_list, result = self.consume_song_list(result)
self.assertEqual(len(result), 0)
self.assertEqual(formated[0], song_list)
class TracksToDirectoryTreeTest(unittest.TestCase):
def setUp(self):
self.media_dir = '/root'
def test_no_tracks_gives_emtpy_tree(self):
tree = translator.tracks_to_directory_tree([], self.media_dir)
self.assertEqual(tree, ({}, []))
def test_top_level_files(self):
tracks = [
Track(uri='file:///root/file1.mp3'),
Track(uri='file:///root/file2.mp3'),
Track(uri='file:///root/file3.mp3'),
]
tree = translator.tracks_to_directory_tree(tracks, self.media_dir)
self.assertEqual(tree, ({}, tracks))
def test_single_file_in_subdir(self):
tracks = [Track(uri='file:///root/dir/file1.mp3')]
tree = translator.tracks_to_directory_tree(tracks, self.media_dir)
expected = ({'dir': ({}, tracks)}, [])
self.assertEqual(tree, expected)
def test_single_file_in_sub_subdir(self):
tracks = [Track(uri='file:///root/dir1/dir2/file1.mp3')]
tree = translator.tracks_to_directory_tree(tracks, self.media_dir)
expected = ({'dir1': ({'dir1/dir2': ({}, tracks)}, [])}, [])
self.assertEqual(tree, expected)
def test_complex_file_structure(self):
tracks = [
Track(uri='file:///root/file1.mp3'),
Track(uri='file:///root/dir1/file2.mp3'),
Track(uri='file:///root/dir1/file3.mp3'),
Track(uri='file:///root/dir2/file4.mp3'),
Track(uri='file:///root/dir2/sub/file5.mp3'),
]
tree = translator.tracks_to_directory_tree(tracks, self.media_dir)
expected = (
{
'dir1': ({}, [tracks[1], tracks[2]]),
'dir2': (
{
'dir2/sub': ({}, [tracks[4]])
},
[tracks[3]]
),
},
[tracks[0]]
)
self.assertEqual(tree, expected)
expected_artists = [Artist(name='name')]
expected_albums = [
Album(name='albumname', artists=expected_artists, num_tracks=2),
Album(name='albumname', num_tracks=2),
]
expected_tracks = []
def generate_track(path, ident, album_id):
uri = 'local:track:%s' % path
track = Track(
uri=uri, name='trackname', artists=expected_artists,
album=expected_albums[album_id], track_no=1, date='2006', length=4000,
last_modified=1272319626)
expected_tracks.append(track)
generate_track('song1.mp3', 6, 0)
generate_track('song2.mp3', 7, 0)
generate_track('song3.mp3', 8, 1)
generate_track('subdir1/song4.mp3', 2, 0)
generate_track('subdir1/song5.mp3', 3, 0)
generate_track('subdir2/song6.mp3', 4, 1)
generate_track('subdir2/song7.mp3', 5, 1)
generate_track('subdir1/subsubdir/song8.mp3', 0, 0)
generate_track('subdir1/subsubdir/song9.mp3', 1, 1)
class MPDTagCacheToTracksTest(unittest.TestCase):
def test_emtpy_cache(self):
tracks = translator.parse_mpd_tag_cache(
path_to_data_dir('empty_tag_cache'), path_to_data_dir(''))
self.assertEqual(set(), tracks)
def test_simple_cache(self):
tracks = translator.parse_mpd_tag_cache(
path_to_data_dir('simple_tag_cache'), path_to_data_dir(''))
track = Track(
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):
tracks = translator.parse_mpd_tag_cache(
path_to_data_dir('advanced_tag_cache'), path_to_data_dir(''))
self.assertEqual(set(expected_tracks), tracks)
def test_unicode_cache(self):
tracks = translator.parse_mpd_tag_cache(
path_to_data_dir('utf8_tag_cache'), path_to_data_dir(''))
artists = [Artist(name='æøå')]
album = Album(name='æøå', artists=artists)
track = Track(
uri='local:track:song1.mp3', name='æøå', artists=artists,
composers=artists, performers=artists, genre='æøå',
album=album, length=4000, last_modified=1272319626,
comment='æøå&^`ൂ㔶')
self.assertEqual(track, list(tracks)[0])
@unittest.SkipTest
def test_misencoded_cache(self):
# FIXME not sure if this can happen
pass
def test_cache_with_blank_track_info(self):
tracks = translator.parse_mpd_tag_cache(
path_to_data_dir('blank_tag_cache'), path_to_data_dir(''))
expected = Track(
uri='local:track:song1.mp3', length=4000, last_modified=1272319626)
self.assertEqual(set([expected]), tracks)
def test_musicbrainz_tagcache(self):
tracks = translator.parse_mpd_tag_cache(
path_to_data_dir('musicbrainz_tag_cache'), path_to_data_dir(''))
artist = list(expected_tracks[0].artists)[0].copy(
musicbrainz_id='7364dea6-ca9a-48e3-be01-b44ad0d19897')
albumartist = list(expected_tracks[0].artists)[0].copy(
name='albumartistname',
musicbrainz_id='7364dea6-ca9a-48e3-be01-b44ad0d19897')
album = expected_tracks[0].album.copy(
artists=[albumartist],
musicbrainz_id='cb5f1603-d314-4c9c-91e5-e295cfb125d2')
track = expected_tracks[0].copy(
artists=[artist], album=album,
musicbrainz_id='90488461-8c1f-4a4e-826b-4c6dc70801f0')
self.assertEqual(track, list(tracks)[0])
def test_albumartist_tag_cache(self):
tracks = translator.parse_mpd_tag_cache(
path_to_data_dir('albumartist_tag_cache'), path_to_data_dir(''))
artist = Artist(name='albumartistname')
album = expected_albums[0].copy(artists=[artist])
track = Track(
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

@ -19,7 +19,6 @@ class LocalTracklistProviderTest(unittest.TestCase):
'local': {
'media_dir': path_to_data_dir(''),
'playlists_dir': b'',
'tag_cache_file': path_to_data_dir('empty_tag_cache'),
}
}
tracks = [

View File

@ -1,107 +0,0 @@
info_begin
mpd_version: 0.14.2
fs_charset: UTF-8
info_end
directory: subdir1
begin: subdir1
directory: subsubdir
begin: subdir1/subsubdir
songList begin
key: song8.mp3
file: subdir1/subsubdir/song8.mp3
Time: 4
Artist: name
AlbumArtist: name
Title: trackname
Album: albumname
Track: 1/2
Date: 2006
mtime: 1272319626
key: song9.mp3
file: subdir1/subsubdir/song9.mp3
Time: 4
Artist: name
Title: trackname
Album: albumname
Track: 1/2
Date: 2006
mtime: 1272319626
songList end
end: subdir1/subsubdir
songList begin
key: song4.mp3
file: subdir1/song4.mp3
Time: 4
Artist: name
AlbumArtist: name
Title: trackname
Album: albumname
Track: 1/2
Date: 2006
mtime: 1272319626
key: song5.mp3
file: subdir1/song5.mp3
Time: 4
Artist: name
AlbumArtist: name
Title: trackname
Album: albumname
Track: 1/2
Date: 2006
mtime: 1272319626
songList end
end: subdir1
directory: subdir2
begin: subdir2
songList begin
key: song6.mp3
file: subdir2/song6.mp3
Time: 4
Artist: name
Title: trackname
Album: albumname
Track: 1/2
Date: 2006
mtime: 1272319626
key: song7.mp3
file: subdir2/song7.mp3
Time: 4
Artist: name
Title: trackname
Album: albumname
Track: 1/2
Date: 2006
mtime: 1272319626
songList end
end: subdir2
songList begin
key: song1.mp3
file: /song1.mp3
Time: 4
Artist: name
AlbumArtist: name
Title: trackname
Album: albumname
Track: 1/2
Date: 2006
mtime: 1272319626
key: song2.mp3
file: /song2.mp3
Time: 4
Artist: name
AlbumArtist: name
Title: trackname
Album: albumname
Track: 1/2
Date: 2006
mtime: 1272319626
key: song3.mp3
file: /song3.mp3
Time: 4
Artist: name
Title: trackname
Album: albumname
Track: 1/2
Date: 2006
mtime: 1272319626
songList end

View File

@ -1,16 +0,0 @@
info_begin
mpd_version: 0.14.2
fs_charset: UTF-8
info_end
songList begin
key: song1.mp3
file: /song1.mp3
Time: 4
Artist: name
Title: trackname
Album: albumname
AlbumArtist: albumartistname
Track: 1/2
Date: 2006
mtime: 1272319626
songList end

View File

@ -1,10 +0,0 @@
info_begin
mpd_version: 0.14.2
fs_charset: UTF-8
info_end
songList begin
key: song1.mp3
file: /song1.mp3
Time: 4
mtime: 1272319626
songList end

View File

@ -1,6 +0,0 @@
info_begin
mpd_version: 0.14.2
fs_charset: UTF-8
info_end
songList begin
songList end

BIN
tests/data/library.json.gz Normal file

Binary file not shown.

View File

@ -1,56 +0,0 @@
info_begin
mpd_version: 0.14.2
fs_charset: UTF-8
info_end
songList begin
key: key1
file: /path1
Artist: artist1
AlbumArtist: artist1
Title: track1
Album: album1
Date: 2001-02-03
Track: 1
Time: 4
key: key2
file: /path2
Artist: artist2
AlbumArtist: artist2
Title: track2
Album: album2
Date: 2002
Track: 2
Time: 4
key: key3
file: /path3
Artist: artist4
AlbumArtist: artist3
Title: track3
Album: album3
Date: 2003
Track: 3
Time: 4
key: key4
file: /path4
Artist: artist3
Title: track4
Album: album4
Date: 2004
Track: 4
Comment: This is a fantastic track
Time: 60
key: key5
file: /path5
Composer: artist5
Title: track5
Album: album4
Genre: genre1
Time: 4
key: key6
file: /path6
Performer: artist6
Title: track6
Album: album4
Genre: genre2
Time: 4
songList end

View File

@ -1,20 +0,0 @@
info_begin
mpd_version: 0.16.0
fs_charset: UTF-8
info_end
songList begin
key: song1.mp3
file: /song1.mp3
Time: 4
Artist: name
Title: trackname
Album: albumname
AlbumArtist: albumartistname
Track: 1/2
Date: 2006
MUSICBRAINZ_ALBUMID: cb5f1603-d314-4c9c-91e5-e295cfb125d2
MUSICBRAINZ_ALBUMARTISTID: 7364dea6-ca9a-48e3-be01-b44ad0d19897
MUSICBRAINZ_ARTISTID: 7364dea6-ca9a-48e3-be01-b44ad0d19897
MUSICBRAINZ_TRACKID: 90488461-8c1f-4a4e-826b-4c6dc70801f0
mtime: 1272319626
songList end

View File

@ -1,81 +0,0 @@
info_begin
mpd_version: 0.15.4
fs_charset: UTF-8
info_end
directory: subdir1
mtime: 1288121499
begin: subdir1
songList begin
key: song4.mp3
file: subdir1/song4.mp3
Time: 5
Artist: name
Title: trackname
Album: albumname
Track: 01/02
Date: 2006
mtime: 1288121370
key: song5.mp3
file: subdir1/song5.mp3
Time: 5
Artist: name
Title: trackname
Album: albumname
Track: 01/02
Date: 2006
mtime: 1288121370
songList end
end: subdir1
directory: subdir2
mtime: 1288121499
begin: subdir2
songList begin
key: song6.mp3
file: subdir2/song6.mp3
Time: 5
Artist: name
Title: trackname
Album: albumname
Track: 01/02
Date: 2006
mtime: 1288121370
key: song7.mp3
file: subdir2/song7.mp3
Time: 5
Artist: name
Title: trackname
Album: albumname
Track: 01/02
Date: 2006
mtime: 1288121370
songList end
end: subdir2
songList begin
key: song1.mp3
file: /song1.mp3
Time: 5
Artist: name
Title: trackname
Album: albumname
Track: 01/02
Date: 2006
mtime: 1288121370
key: song2.mp3
file: /song2.mp3
Time: 5
Artist: name
Title: trackname
Album: albumname
Track: 01/02
Date: 2006
mtime: 1288121370
key: song3.mp3
file: /song3.mp3
Time: 5
Artist: name
Title: trackname
Album: albumname
Track: 01/02
Date: 2006
mtime: 1288121370
songList end

View File

@ -1,6 +0,0 @@
info_begin
mpd_version: 0.15.4
fs_charset: UTF-8
info_end
songList begin
songList end

View File

@ -1,15 +0,0 @@
info_begin
mpd_version: 0.15.4
fs_charset: UTF-8
info_end
songList begin
key: song1.mp3
file: /song1.mp3
Time: 5
Artist: name
Title: trackname
Album: albumname
Track: 01/02
Date: 2006
mtime: 1288121370
songList end

View File

@ -1,16 +0,0 @@
info_begin
mpd_version: 0.14.2
fs_charset: UTF-8
info_end
songList begin
key: song1.mp3
file: /song1.mp3
Time: 4
Artist: name
AlbumArtist: name
Title: trackname
Album: albumname
Track: 1/2
Date: 2006
mtime: 1272319626
songList end

View File

@ -1,18 +0,0 @@
info_begin
mpd_version: 0.14.2
fs_charset: UTF-8
info_end
songList begin
key: song1.mp3
file: /song1.mp3
Time: 4
Artist: æøå
AlbumArtist: æøå
Composer: æøå
Performer: æøå
Title: æøå
Album: æøå
Genre: æøå
Comment: æøå&^`ൂ㔶
mtime: 1272319626
songList end