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:
parent
da63942b48
commit
9c2d38e989
@ -66,11 +66,8 @@ Sample session::
|
|||||||
-OK
|
-OK
|
||||||
+ACK [2@0] {listallinfo} incorrect arguments
|
+ACK [2@0] {listallinfo} incorrect arguments
|
||||||
|
|
||||||
To ensure that Mopidy and MPD have comparable state it is suggested you setup
|
To ensure that Mopidy and MPD have comparable state it is suggested you scan
|
||||||
both to use ``tests/data/advanced_tag_cache`` for their tag cache and
|
the same media directory with both servers.
|
||||||
``tests/data/scanner/advanced/`` for the music folder and ``tests/data`` for
|
|
||||||
playlists.
|
|
||||||
|
|
||||||
|
|
||||||
Documentation writing
|
Documentation writing
|
||||||
=====================
|
=====================
|
||||||
|
|||||||
@ -43,10 +43,6 @@ Configuration values
|
|||||||
|
|
||||||
Path to playlists directory with m3u files for local media.
|
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
|
.. confval:: local/scan_timeout
|
||||||
|
|
||||||
Number of milliseconds before giving up scanning a file and moving on to
|
Number of milliseconds before giving up scanning a file and moving on to
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class Extension(ext.Extension):
|
|||||||
schema = super(Extension, self).get_config_schema()
|
schema = super(Extension, self).get_config_schema()
|
||||||
schema['media_dir'] = config.Path()
|
schema['media_dir'] = config.Path()
|
||||||
schema['playlists_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(
|
schema['scan_timeout'] = config.Integer(
|
||||||
minimum=1000, maximum=1000*60*60)
|
minimum=1000, maximum=1000*60*60)
|
||||||
schema['excluded_file_extensions'] = config.List(optional=True)
|
schema['excluded_file_extensions'] = config.List(optional=True)
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
enabled = true
|
enabled = true
|
||||||
media_dir = $XDG_MUSIC_DIR
|
media_dir = $XDG_MUSIC_DIR
|
||||||
playlists_dir = $XDG_DATA_DIR/mopidy/local/playlists
|
playlists_dir = $XDG_DATA_DIR/mopidy/local/playlists
|
||||||
tag_cache_file = $XDG_DATA_DIR/mopidy/local/tag_cache
|
|
||||||
scan_timeout = 1000
|
scan_timeout = 1000
|
||||||
excluded_file_extensions =
|
excluded_file_extensions =
|
||||||
.html
|
.html
|
||||||
|
|||||||
@ -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]
|
|
||||||
@ -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))
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
[local-tagcache]
|
|
||||||
enabled = false
|
|
||||||
@ -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)
|
|
||||||
@ -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]))
|
|
||||||
@ -45,7 +45,6 @@ def convert(settings):
|
|||||||
|
|
||||||
helper('local/media_dir', 'LOCAL_MUSIC_PATH')
|
helper('local/media_dir', 'LOCAL_MUSIC_PATH')
|
||||||
helper('local/playlists_dir', 'LOCAL_PLAYLIST_PATH')
|
helper('local/playlists_dir', 'LOCAL_PLAYLIST_PATH')
|
||||||
helper('local/tag_cache_file', 'LOCAL_TAG_CACHE_FILE')
|
|
||||||
|
|
||||||
helper('spotify/username', 'SPOTIFY_USERNAME')
|
helper('spotify/username', 'SPOTIFY_USERNAME')
|
||||||
helper('spotify/password', 'SPOTIFY_PASSWORD')
|
helper('spotify/password', 'SPOTIFY_PASSWORD')
|
||||||
|
|||||||
1
setup.py
1
setup.py
@ -43,7 +43,6 @@ setup(
|
|||||||
'mopidy.ext': [
|
'mopidy.ext': [
|
||||||
'http = mopidy.frontends.http:Extension [http]',
|
'http = mopidy.frontends.http:Extension [http]',
|
||||||
'local = mopidy.backends.local:Extension',
|
'local = mopidy.backends.local:Extension',
|
||||||
'local-tagcache = mopidy.backends.local.tagcache:Extension',
|
|
||||||
'local-json = mopidy.backends.local.json:Extension',
|
'local-json = mopidy.backends.local.json:Extension',
|
||||||
'mpd = mopidy.frontends.mpd:Extension',
|
'mpd = mopidy.frontends.mpd:Extension',
|
||||||
'stream = mopidy.backends.stream:Extension',
|
'stream = mopidy.backends.stream:Extension',
|
||||||
|
|||||||
@ -18,7 +18,6 @@ class LocalBackendEventsTest(unittest.TestCase):
|
|||||||
'local': {
|
'local': {
|
||||||
'media_dir': path_to_data_dir(''),
|
'media_dir': path_to_data_dir(''),
|
||||||
'playlists_dir': b'',
|
'playlists_dir': b'',
|
||||||
'tag_cache_file': path_to_data_dir('empty_tag_cache'),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import copy
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import pykka
|
import pykka
|
||||||
|
|
||||||
from mopidy import core
|
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 mopidy.models import Track, Album, Artist
|
||||||
|
|
||||||
from tests import path_to_data_dir
|
from tests import path_to_data_dir
|
||||||
@ -61,12 +62,14 @@ class LocalLibraryProviderTest(unittest.TestCase):
|
|||||||
'local': {
|
'local': {
|
||||||
'media_dir': path_to_data_dir(''),
|
'media_dir': path_to_data_dir(''),
|
||||||
'playlists_dir': b'',
|
'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):
|
def setUp(self):
|
||||||
self.backend = actor.LocalTagcacheBackend.start(
|
self.backend = actor.LocalJsonBackend.start(
|
||||||
config=self.config, audio=None).proxy()
|
config=self.config, audio=None).proxy()
|
||||||
self.core = core.Core(backends=[self.backend])
|
self.core = core.Core(backends=[self.backend])
|
||||||
self.library = self.core.library
|
self.library = self.core.library
|
||||||
@ -85,27 +88,27 @@ class LocalLibraryProviderTest(unittest.TestCase):
|
|||||||
# Verifies that https://github.com/mopidy/mopidy/issues/500
|
# Verifies that https://github.com/mopidy/mopidy/issues/500
|
||||||
# has been fixed.
|
# has been fixed.
|
||||||
|
|
||||||
tag_cache = tempfile.NamedTemporaryFile()
|
with tempfile.NamedTemporaryFile() as library:
|
||||||
with open(self.config['local']['tag_cache_file']) as fh:
|
with open(self.config['local-json']['json_file']) as fh:
|
||||||
tag_cache.write(fh.read())
|
library.write(fh.read())
|
||||||
tag_cache.flush()
|
library.flush()
|
||||||
|
|
||||||
config = {'local': self.config['local'].copy()}
|
config = copy.deepcopy(self.config)
|
||||||
config['local']['tag_cache_file'] = tag_cache.name
|
config['local-json']['json_file'] = library.name
|
||||||
backend = actor.LocalTagcacheBackend(config=config, audio=None)
|
backend = actor.LocalJsonBackend(config=config, audio=None)
|
||||||
|
|
||||||
# Sanity check that value is in tag cache
|
# Sanity check that value is in the library
|
||||||
result = backend.library.lookup(self.tracks[0].uri)
|
result = backend.library.lookup(self.tracks[0].uri)
|
||||||
self.assertEqual(result, self.tracks[0:1])
|
self.assertEqual(result, self.tracks[0:1])
|
||||||
|
|
||||||
# Clear tag cache and refresh
|
# Clear library and refresh
|
||||||
tag_cache.seek(0)
|
library.seek(0)
|
||||||
tag_cache.truncate()
|
library.truncate()
|
||||||
backend.library.refresh()
|
backend.library.refresh()
|
||||||
|
|
||||||
# Now it should be gone.
|
# Now it should be gone.
|
||||||
result = backend.library.lookup(self.tracks[0].uri)
|
result = backend.library.lookup(self.tracks[0].uri)
|
||||||
self.assertEqual(result, [])
|
self.assertEqual(result, [])
|
||||||
|
|
||||||
def test_lookup(self):
|
def test_lookup(self):
|
||||||
tracks = self.library.lookup(self.tracks[0].uri)
|
tracks = self.library.lookup(self.tracks[0].uri)
|
||||||
@ -115,6 +118,7 @@ class LocalLibraryProviderTest(unittest.TestCase):
|
|||||||
tracks = self.library.lookup('fake uri')
|
tracks = self.library.lookup('fake uri')
|
||||||
self.assertEqual(tracks, [])
|
self.assertEqual(tracks, [])
|
||||||
|
|
||||||
|
# TODO: move to search_test module
|
||||||
def test_find_exact_no_hits(self):
|
def test_find_exact_no_hits(self):
|
||||||
result = self.library.find_exact(track_name=['unknown track'])
|
result = self.library.find_exact(track_name=['unknown track'])
|
||||||
self.assertEqual(list(result[0].tracks), [])
|
self.assertEqual(list(result[0].tracks), [])
|
||||||
|
|||||||
@ -23,7 +23,6 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
|||||||
'local': {
|
'local': {
|
||||||
'media_dir': path_to_data_dir(''),
|
'media_dir': path_to_data_dir(''),
|
||||||
'playlists_dir': b'',
|
'playlists_dir': b'',
|
||||||
'tag_cache_file': path_to_data_dir('empty_tag_cache'),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,6 @@ class LocalPlaylistsProviderTest(unittest.TestCase):
|
|||||||
config = {
|
config = {
|
||||||
'local': {
|
'local': {
|
||||||
'media_dir': path_to_data_dir(''),
|
'media_dir': path_to_data_dir(''),
|
||||||
'tag_cache_file': path_to_data_dir('library_tag_cache'),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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])
|
|
||||||
@ -19,7 +19,6 @@ class LocalTracklistProviderTest(unittest.TestCase):
|
|||||||
'local': {
|
'local': {
|
||||||
'media_dir': path_to_data_dir(''),
|
'media_dir': path_to_data_dir(''),
|
||||||
'playlists_dir': b'',
|
'playlists_dir': b'',
|
||||||
'tag_cache_file': path_to_data_dir('empty_tag_cache'),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tracks = [
|
tracks = [
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
BIN
tests/data/library.json.gz
Normal file
Binary file not shown.
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
info_begin
|
|
||||||
mpd_version: 0.15.4
|
|
||||||
fs_charset: UTF-8
|
|
||||||
info_end
|
|
||||||
songList begin
|
|
||||||
songList end
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
Loading…
Reference in New Issue
Block a user