Split local backend into multiple files and flatten logging hierarchy
This commit is contained in:
parent
4588dd2ec2
commit
45a79df0a8
@ -1,211 +1,2 @@
|
|||||||
import glob
|
# flake8: noqa
|
||||||
import logging
|
from .actor import LocalBackend
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from pykka.actor import ThreadingActor
|
|
||||||
|
|
||||||
from mopidy import settings
|
|
||||||
from mopidy.backends import base
|
|
||||||
from mopidy.models import Playlist, Album
|
|
||||||
|
|
||||||
from .translator import parse_m3u, parse_mpd_tag_cache
|
|
||||||
|
|
||||||
logger = logging.getLogger(u'mopidy.backends.local')
|
|
||||||
|
|
||||||
|
|
||||||
class LocalBackend(ThreadingActor, base.Backend):
|
|
||||||
"""
|
|
||||||
A backend for playing music from a local music archive.
|
|
||||||
|
|
||||||
**Dependencies:**
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
||||||
**Settings:**
|
|
||||||
|
|
||||||
- :attr:`mopidy.settings.LOCAL_MUSIC_PATH`
|
|
||||||
- :attr:`mopidy.settings.LOCAL_PLAYLIST_PATH`
|
|
||||||
- :attr:`mopidy.settings.LOCAL_TAG_CACHE_FILE`
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, audio):
|
|
||||||
self.library = LocalLibraryProvider(backend=self)
|
|
||||||
self.playback = base.BasePlaybackProvider(audio=audio, backend=self)
|
|
||||||
self.stored_playlists = LocalStoredPlaylistsProvider(backend=self)
|
|
||||||
|
|
||||||
self.uri_schemes = [u'file']
|
|
||||||
|
|
||||||
|
|
||||||
class LocalStoredPlaylistsProvider(base.BaseStoredPlaylistsProvider):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(LocalStoredPlaylistsProvider, self).__init__(*args, **kwargs)
|
|
||||||
self._folder = settings.LOCAL_PLAYLIST_PATH
|
|
||||||
self.refresh()
|
|
||||||
|
|
||||||
def lookup(self, uri):
|
|
||||||
pass # TODO
|
|
||||||
|
|
||||||
def refresh(self):
|
|
||||||
playlists = []
|
|
||||||
|
|
||||||
logger.info('Loading playlists from %s', self._folder)
|
|
||||||
|
|
||||||
for m3u in glob.glob(os.path.join(self._folder, '*.m3u')):
|
|
||||||
name = os.path.basename(m3u)[:-len('.m3u')]
|
|
||||||
tracks = []
|
|
||||||
for uri in parse_m3u(m3u, settings.LOCAL_MUSIC_PATH):
|
|
||||||
try:
|
|
||||||
tracks.append(self.backend.library.lookup(uri))
|
|
||||||
except LookupError, e:
|
|
||||||
logger.error('Playlist item could not be added: %s', e)
|
|
||||||
playlist = Playlist(tracks=tracks, name=name)
|
|
||||||
|
|
||||||
# FIXME playlist name needs better handling
|
|
||||||
# FIXME tracks should come from lib. lookup
|
|
||||||
|
|
||||||
playlists.append(playlist)
|
|
||||||
|
|
||||||
self.playlists = playlists
|
|
||||||
|
|
||||||
def create(self, name):
|
|
||||||
playlist = Playlist(name=name)
|
|
||||||
self.save(playlist)
|
|
||||||
return playlist
|
|
||||||
|
|
||||||
def delete(self, playlist):
|
|
||||||
if playlist not in self._playlists:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._playlists.remove(playlist)
|
|
||||||
filename = os.path.join(self._folder, playlist.name + '.m3u')
|
|
||||||
|
|
||||||
if os.path.exists(filename):
|
|
||||||
os.remove(filename)
|
|
||||||
|
|
||||||
def rename(self, playlist, name):
|
|
||||||
if playlist not in self._playlists:
|
|
||||||
return
|
|
||||||
|
|
||||||
src = os.path.join(self._folder, playlist.name + '.m3u')
|
|
||||||
dst = os.path.join(self._folder, name + '.m3u')
|
|
||||||
|
|
||||||
renamed = playlist.copy(name=name)
|
|
||||||
index = self._playlists.index(playlist)
|
|
||||||
self._playlists[index] = renamed
|
|
||||||
|
|
||||||
shutil.move(src, dst)
|
|
||||||
|
|
||||||
def save(self, playlist):
|
|
||||||
file_path = os.path.join(self._folder, playlist.name + '.m3u')
|
|
||||||
|
|
||||||
# FIXME this should be a save_m3u function, not inside save
|
|
||||||
with open(file_path, 'w') as file_handle:
|
|
||||||
for track in playlist.tracks:
|
|
||||||
if track.uri.startswith('file://'):
|
|
||||||
file_handle.write(track.uri[len('file://'):] + '\n')
|
|
||||||
else:
|
|
||||||
file_handle.write(track.uri + '\n')
|
|
||||||
|
|
||||||
self._playlists.append(playlist)
|
|
||||||
|
|
||||||
|
|
||||||
class LocalLibraryProvider(base.BaseLibraryProvider):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(LocalLibraryProvider, self).__init__(*args, **kwargs)
|
|
||||||
self._uri_mapping = {}
|
|
||||||
self.refresh()
|
|
||||||
|
|
||||||
def refresh(self, uri=None):
|
|
||||||
tracks = parse_mpd_tag_cache(
|
|
||||||
settings.LOCAL_TAG_CACHE_FILE, settings.LOCAL_MUSIC_PATH)
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
'Loading tracks in %s from %s',
|
|
||||||
settings.LOCAL_MUSIC_PATH, settings.LOCAL_TAG_CACHE_FILE)
|
|
||||||
|
|
||||||
for track in tracks:
|
|
||||||
self._uri_mapping[track.uri] = track
|
|
||||||
|
|
||||||
def lookup(self, uri):
|
|
||||||
try:
|
|
||||||
return self._uri_mapping[uri]
|
|
||||||
except KeyError:
|
|
||||||
logger.debug(u'Failed to lookup "%s"', uri)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def find_exact(self, **query):
|
|
||||||
self._validate_query(query)
|
|
||||||
result_tracks = self._uri_mapping.values()
|
|
||||||
|
|
||||||
for (field, values) in query.iteritems():
|
|
||||||
if not hasattr(values, '__iter__'):
|
|
||||||
values = [values]
|
|
||||||
# FIXME this is bound to be slow for large libraries
|
|
||||||
for value in values:
|
|
||||||
q = value.strip()
|
|
||||||
|
|
||||||
track_filter = lambda t: q == t.name
|
|
||||||
album_filter = lambda t: q == getattr(t, 'album', Album()).name
|
|
||||||
artist_filter = lambda t: filter(
|
|
||||||
lambda a: q == a.name, t.artists)
|
|
||||||
uri_filter = lambda t: q == t.uri
|
|
||||||
any_filter = lambda t: (
|
|
||||||
track_filter(t) or album_filter(t) or
|
|
||||||
artist_filter(t) or uri_filter(t))
|
|
||||||
|
|
||||||
if field == 'track':
|
|
||||||
result_tracks = filter(track_filter, result_tracks)
|
|
||||||
elif field == 'album':
|
|
||||||
result_tracks = filter(album_filter, result_tracks)
|
|
||||||
elif field == 'artist':
|
|
||||||
result_tracks = filter(artist_filter, result_tracks)
|
|
||||||
elif field == 'uri':
|
|
||||||
result_tracks = filter(uri_filter, result_tracks)
|
|
||||||
elif field == 'any':
|
|
||||||
result_tracks = filter(any_filter, result_tracks)
|
|
||||||
else:
|
|
||||||
raise LookupError('Invalid lookup field: %s' % field)
|
|
||||||
return Playlist(tracks=result_tracks)
|
|
||||||
|
|
||||||
def search(self, **query):
|
|
||||||
self._validate_query(query)
|
|
||||||
result_tracks = self._uri_mapping.values()
|
|
||||||
|
|
||||||
for (field, values) in query.iteritems():
|
|
||||||
if not hasattr(values, '__iter__'):
|
|
||||||
values = [values]
|
|
||||||
# FIXME this is bound to be slow for large libraries
|
|
||||||
for value in values:
|
|
||||||
q = value.strip().lower()
|
|
||||||
|
|
||||||
track_filter = lambda t: q in t.name.lower()
|
|
||||||
album_filter = lambda t: q in getattr(
|
|
||||||
t, 'album', Album()).name.lower()
|
|
||||||
artist_filter = lambda t: filter(
|
|
||||||
lambda a: q in a.name.lower(), t.artists)
|
|
||||||
uri_filter = lambda t: q in t.uri.lower()
|
|
||||||
any_filter = lambda t: track_filter(t) or album_filter(t) or \
|
|
||||||
artist_filter(t) or uri_filter(t)
|
|
||||||
|
|
||||||
if field == 'track':
|
|
||||||
result_tracks = filter(track_filter, result_tracks)
|
|
||||||
elif field == 'album':
|
|
||||||
result_tracks = filter(album_filter, result_tracks)
|
|
||||||
elif field == 'artist':
|
|
||||||
result_tracks = filter(artist_filter, result_tracks)
|
|
||||||
elif field == 'uri':
|
|
||||||
result_tracks = filter(uri_filter, result_tracks)
|
|
||||||
elif field == 'any':
|
|
||||||
result_tracks = filter(any_filter, result_tracks)
|
|
||||||
else:
|
|
||||||
raise LookupError('Invalid lookup field: %s' % field)
|
|
||||||
return Playlist(tracks=result_tracks)
|
|
||||||
|
|
||||||
def _validate_query(self, query):
|
|
||||||
for (_, values) in query.iteritems():
|
|
||||||
if not values:
|
|
||||||
raise LookupError('Missing query')
|
|
||||||
for value in values:
|
|
||||||
if not value:
|
|
||||||
raise LookupError('Missing query')
|
|
||||||
|
|||||||
33
mopidy/backends/local/actor.py
Normal file
33
mopidy/backends/local/actor.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from pykka.actor import ThreadingActor
|
||||||
|
|
||||||
|
from mopidy.backends import base
|
||||||
|
|
||||||
|
from .library import LocalLibraryProvider
|
||||||
|
from .stored_playlists import LocalStoredPlaylistsProvider
|
||||||
|
|
||||||
|
logger = logging.getLogger(u'mopidy.backends.local')
|
||||||
|
|
||||||
|
|
||||||
|
class LocalBackend(ThreadingActor, base.Backend):
|
||||||
|
"""
|
||||||
|
A backend for playing music from a local music archive.
|
||||||
|
|
||||||
|
**Dependencies:**
|
||||||
|
|
||||||
|
- None
|
||||||
|
|
||||||
|
**Settings:**
|
||||||
|
|
||||||
|
- :attr:`mopidy.settings.LOCAL_MUSIC_PATH`
|
||||||
|
- :attr:`mopidy.settings.LOCAL_PLAYLIST_PATH`
|
||||||
|
- :attr:`mopidy.settings.LOCAL_TAG_CACHE_FILE`
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, audio):
|
||||||
|
self.library = LocalLibraryProvider(backend=self)
|
||||||
|
self.playback = base.BasePlaybackProvider(audio=audio, backend=self)
|
||||||
|
self.stored_playlists = LocalStoredPlaylistsProvider(backend=self)
|
||||||
|
|
||||||
|
self.uri_schemes = [u'file']
|
||||||
110
mopidy/backends/local/library.py
Normal file
110
mopidy/backends/local/library.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from mopidy import settings
|
||||||
|
from mopidy.backends import base
|
||||||
|
from mopidy.models import Playlist, Album
|
||||||
|
|
||||||
|
from .translator import parse_mpd_tag_cache
|
||||||
|
|
||||||
|
logger = logging.getLogger(u'mopidy.backends.local')
|
||||||
|
|
||||||
|
|
||||||
|
class LocalLibraryProvider(base.BaseLibraryProvider):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(LocalLibraryProvider, self).__init__(*args, **kwargs)
|
||||||
|
self._uri_mapping = {}
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def refresh(self, uri=None):
|
||||||
|
tracks = parse_mpd_tag_cache(
|
||||||
|
settings.LOCAL_TAG_CACHE_FILE, settings.LOCAL_MUSIC_PATH)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
'Loading tracks in %s from %s',
|
||||||
|
settings.LOCAL_MUSIC_PATH, settings.LOCAL_TAG_CACHE_FILE)
|
||||||
|
|
||||||
|
for track in tracks:
|
||||||
|
self._uri_mapping[track.uri] = track
|
||||||
|
|
||||||
|
def lookup(self, uri):
|
||||||
|
try:
|
||||||
|
return self._uri_mapping[uri]
|
||||||
|
except KeyError:
|
||||||
|
logger.debug(u'Failed to lookup "%s"', uri)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_exact(self, **query):
|
||||||
|
self._validate_query(query)
|
||||||
|
result_tracks = self._uri_mapping.values()
|
||||||
|
|
||||||
|
for (field, values) in query.iteritems():
|
||||||
|
if not hasattr(values, '__iter__'):
|
||||||
|
values = [values]
|
||||||
|
# FIXME this is bound to be slow for large libraries
|
||||||
|
for value in values:
|
||||||
|
q = value.strip()
|
||||||
|
|
||||||
|
track_filter = lambda t: q == t.name
|
||||||
|
album_filter = lambda t: q == getattr(t, 'album', Album()).name
|
||||||
|
artist_filter = lambda t: filter(
|
||||||
|
lambda a: q == a.name, t.artists)
|
||||||
|
uri_filter = lambda t: q == t.uri
|
||||||
|
any_filter = lambda t: (
|
||||||
|
track_filter(t) or album_filter(t) or
|
||||||
|
artist_filter(t) or uri_filter(t))
|
||||||
|
|
||||||
|
if field == 'track':
|
||||||
|
result_tracks = filter(track_filter, result_tracks)
|
||||||
|
elif field == 'album':
|
||||||
|
result_tracks = filter(album_filter, result_tracks)
|
||||||
|
elif field == 'artist':
|
||||||
|
result_tracks = filter(artist_filter, result_tracks)
|
||||||
|
elif field == 'uri':
|
||||||
|
result_tracks = filter(uri_filter, result_tracks)
|
||||||
|
elif field == 'any':
|
||||||
|
result_tracks = filter(any_filter, result_tracks)
|
||||||
|
else:
|
||||||
|
raise LookupError('Invalid lookup field: %s' % field)
|
||||||
|
return Playlist(tracks=result_tracks)
|
||||||
|
|
||||||
|
def search(self, **query):
|
||||||
|
self._validate_query(query)
|
||||||
|
result_tracks = self._uri_mapping.values()
|
||||||
|
|
||||||
|
for (field, values) in query.iteritems():
|
||||||
|
if not hasattr(values, '__iter__'):
|
||||||
|
values = [values]
|
||||||
|
# FIXME this is bound to be slow for large libraries
|
||||||
|
for value in values:
|
||||||
|
q = value.strip().lower()
|
||||||
|
|
||||||
|
track_filter = lambda t: q in t.name.lower()
|
||||||
|
album_filter = lambda t: q in getattr(
|
||||||
|
t, 'album', Album()).name.lower()
|
||||||
|
artist_filter = lambda t: filter(
|
||||||
|
lambda a: q in a.name.lower(), t.artists)
|
||||||
|
uri_filter = lambda t: q in t.uri.lower()
|
||||||
|
any_filter = lambda t: track_filter(t) or album_filter(t) or \
|
||||||
|
artist_filter(t) or uri_filter(t)
|
||||||
|
|
||||||
|
if field == 'track':
|
||||||
|
result_tracks = filter(track_filter, result_tracks)
|
||||||
|
elif field == 'album':
|
||||||
|
result_tracks = filter(album_filter, result_tracks)
|
||||||
|
elif field == 'artist':
|
||||||
|
result_tracks = filter(artist_filter, result_tracks)
|
||||||
|
elif field == 'uri':
|
||||||
|
result_tracks = filter(uri_filter, result_tracks)
|
||||||
|
elif field == 'any':
|
||||||
|
result_tracks = filter(any_filter, result_tracks)
|
||||||
|
else:
|
||||||
|
raise LookupError('Invalid lookup field: %s' % field)
|
||||||
|
return Playlist(tracks=result_tracks)
|
||||||
|
|
||||||
|
def _validate_query(self, query):
|
||||||
|
for (_, values) in query.iteritems():
|
||||||
|
if not values:
|
||||||
|
raise LookupError('Missing query')
|
||||||
|
for value in values:
|
||||||
|
if not value:
|
||||||
|
raise LookupError('Missing query')
|
||||||
85
mopidy/backends/local/stored_playlists.py
Normal file
85
mopidy/backends/local/stored_playlists.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import glob
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from mopidy import settings
|
||||||
|
from mopidy.backends import base
|
||||||
|
from mopidy.models import Playlist
|
||||||
|
|
||||||
|
from .translator import parse_m3u
|
||||||
|
|
||||||
|
logger = logging.getLogger(u'mopidy.backends.local')
|
||||||
|
|
||||||
|
|
||||||
|
class LocalStoredPlaylistsProvider(base.BaseStoredPlaylistsProvider):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(LocalStoredPlaylistsProvider, self).__init__(*args, **kwargs)
|
||||||
|
self._folder = settings.LOCAL_PLAYLIST_PATH
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def lookup(self, uri):
|
||||||
|
pass # TODO
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
playlists = []
|
||||||
|
|
||||||
|
logger.info('Loading playlists from %s', self._folder)
|
||||||
|
|
||||||
|
for m3u in glob.glob(os.path.join(self._folder, '*.m3u')):
|
||||||
|
name = os.path.basename(m3u)[:-len('.m3u')]
|
||||||
|
tracks = []
|
||||||
|
for uri in parse_m3u(m3u, settings.LOCAL_MUSIC_PATH):
|
||||||
|
try:
|
||||||
|
tracks.append(self.backend.library.lookup(uri))
|
||||||
|
except LookupError, e:
|
||||||
|
logger.error('Playlist item could not be added: %s', e)
|
||||||
|
playlist = Playlist(tracks=tracks, name=name)
|
||||||
|
|
||||||
|
# FIXME playlist name needs better handling
|
||||||
|
# FIXME tracks should come from lib. lookup
|
||||||
|
|
||||||
|
playlists.append(playlist)
|
||||||
|
|
||||||
|
self.playlists = playlists
|
||||||
|
|
||||||
|
def create(self, name):
|
||||||
|
playlist = Playlist(name=name)
|
||||||
|
self.save(playlist)
|
||||||
|
return playlist
|
||||||
|
|
||||||
|
def delete(self, playlist):
|
||||||
|
if playlist not in self._playlists:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._playlists.remove(playlist)
|
||||||
|
filename = os.path.join(self._folder, playlist.name + '.m3u')
|
||||||
|
|
||||||
|
if os.path.exists(filename):
|
||||||
|
os.remove(filename)
|
||||||
|
|
||||||
|
def rename(self, playlist, name):
|
||||||
|
if playlist not in self._playlists:
|
||||||
|
return
|
||||||
|
|
||||||
|
src = os.path.join(self._folder, playlist.name + '.m3u')
|
||||||
|
dst = os.path.join(self._folder, name + '.m3u')
|
||||||
|
|
||||||
|
renamed = playlist.copy(name=name)
|
||||||
|
index = self._playlists.index(playlist)
|
||||||
|
self._playlists[index] = renamed
|
||||||
|
|
||||||
|
shutil.move(src, dst)
|
||||||
|
|
||||||
|
def save(self, playlist):
|
||||||
|
file_path = os.path.join(self._folder, playlist.name + '.m3u')
|
||||||
|
|
||||||
|
# FIXME this should be a save_m3u function, not inside save
|
||||||
|
with open(file_path, 'w') as file_handle:
|
||||||
|
for track in playlist.tracks:
|
||||||
|
if track.uri.startswith('file://'):
|
||||||
|
file_handle.write(track.uri[len('file://'):] + '\n')
|
||||||
|
else:
|
||||||
|
file_handle.write(track.uri + '\n')
|
||||||
|
|
||||||
|
self._playlists.append(playlist)
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger('mopidy.backends.local.translator')
|
|
||||||
|
|
||||||
from mopidy.models import Track, Artist, Album
|
from mopidy.models import Track, Artist, Album
|
||||||
from mopidy.utils.encoding import locale_decode
|
from mopidy.utils.encoding import locale_decode
|
||||||
from mopidy.utils.path import path_to_uri
|
from mopidy.utils.path import path_to_uri
|
||||||
|
|
||||||
|
logger = logging.getLogger('mopidy.backends.local')
|
||||||
|
|
||||||
|
|
||||||
def parse_m3u(file_path, music_folder):
|
def parse_m3u(file_path, music_folder):
|
||||||
r"""
|
r"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user