file-browser: Various changes as discussed in PR 1207

This commit is contained in:
tom roth 2015-07-06 11:11:23 +02:00
parent 307a879a90
commit 1f3a4abab0
4 changed files with 72 additions and 77 deletions

View File

@ -7,7 +7,7 @@ Mopidy-Files
Mopidy-Files is an extension for playing music from your local music archive.
It is bundled with Mopidy and enabled by default.
It allows you to browse through your local file system.
Only files that are considered playable will be shown
Only files that are considered playable will be shown.
This backend handles URIs starting with ``file:``.
@ -27,10 +27,9 @@ See :ref:`config` for general help on configuring Mopidy.
.. confval:: files/media_dir
A list of directories to be browsable.
Each Directory path has to be written in a separate line.
Optionally the path can be followed by : and a name that will be shown for that path.
Optionally the path can be followed by | and a name that will be shown for that path.
.. confval:: files/show_hidden
.. confval:: files/show_dotfiles
Whether to show hidden files and directories that start with a dot.
Default is false.
@ -38,11 +37,11 @@ See :ref:`config` for general help on configuring Mopidy.
.. confval:: files/follow_symlinks
Whether to follow symbolic links found in :confval:`files/media_dir`.
Directories and files that are outside the configured media_dirs will not be shown.
Default is false
Directories and files that are outside the configured directories will not be shown.
Default is false.
.. confval:: files/metadata_timeout
Number of milliseconds before giving up scanning a file and moving on to
the next file. Reducing the value might speed up the directory listing,
but can lead to some tracks not being shown.
but can lead to some tracks not being shown. Must be larger than 1000.

View File

@ -22,7 +22,7 @@ class Extension(ext.Extension):
def get_config_schema(self):
schema = super(Extension, self).get_config_schema()
schema['media_dir'] = config.List(optional=True)
schema['show_hidden'] = config.Boolean(optional=True)
schema['show_dotfiles'] = config.Boolean(optional=True)
schema['follow_symlinks'] = config.Boolean(optional=True)
schema['metadata_timeout'] = config.Integer(
minimum=1000, maximum=1000 * 60 * 60, optional=True)

View File

@ -1,8 +1,8 @@
[files]
enabled = true
media_dir =
$XDG_MUSIC_DIR:Music
~/:Home
show_hidden = false
$XDG_MUSIC_DIR|Music
~/|Home
show_dotfiles = false
follow_symlinks = true
metadata_timeout = 1000
metadata_timeout = 1000

View File

@ -7,7 +7,6 @@ import stat
import sys
import urllib2
from mopidy import backend, exceptions, models
from mopidy.audio import scan, utils
from mopidy.internal import path
@ -33,75 +32,48 @@ class FilesLibraryProvider(backend.LibraryProvider):
def __init__(self, backend, config):
super(FilesLibraryProvider, self).__init__(backend)
self._media_dirs = []
for entry in config['files']['media_dir']:
media_dir = {}
media_dict = entry.split(':')
try:
local_path = path.expand_path(
media_dict[0].encode(sys.getfilesystemencoding()))
except:
pass
if not local_path:
logger.warn('Could not expand path %s' % media_dict[0])
continue
else:
try:
st = os.stat(local_path)
except:
logger.warn('Could not open %s' % local_path)
continue
if not stat.S_ISDIR(st.st_mode):
logger.warn(u'%s is not a directory' % local_path)
continue
media_dir['path'] = local_path
if len(media_dict) == 2:
media_dir['name'] = media_dict[1]
else:
media_dir['name'] = media_dict[0].replace(os.sep, '+')
self._media_dirs.append(media_dir)
self._media_dirs = list(self._get_media_dirs(config))
self._follow_symlinks = config['files']['follow_symlinks']
self._show_hidden = config['files']['show_hidden']
self._show_dotfiles = config['files']['show_dotfiles']
self._scanner = scan.Scanner(
timeout=config['files']['metadata_timeout'])
def browse(self, uri):
logger.debug(u'browse called with uri %s' % uri)
logger.debug('browse called with uri %s', uri)
result = []
localpath = path.uri_to_path(uri)
if localpath == 'root':
result = self._show_media_dirs()
else:
if not self._is_in_basedir(localpath):
logger.warn(u'Not in basedir: %s' % localpath)
return []
for name in os.listdir(localpath):
child = os.path.join(localpath, name)
uri = path.path_to_uri(child)
name = name.decode('ascii', 'ignore')
if self._follow_symlinks:
st = os.stat(child)
else:
st = os.lstat(child)
if not self._show_hidden and name.startswith(b'.'):
continue
elif stat.S_ISDIR(st.st_mode):
result.append(models.Ref.directory(name=name, uri=uri))
elif stat.S_ISREG(st.st_mode) and self._check_audiofile(uri):
result.append(models.Ref.track(name=name, uri=uri))
else:
logger.warn(u'Ignored file: %s' % child.decode('ascii',
'ignore'))
continue
return list(self._get_media_dirs_refs())
if not self._is_in_basedir(localpath):
logger.warn(u'Not in basedir: %s', localpath)
return []
for name in os.listdir(localpath):
child = os.path.join(localpath, name)
uri = path.path_to_uri(child)
name = name.decode(sys.getfilesystemencoding(), 'ignore')
if not self._show_dotfiles and name.startswith(b'.'):
continue
if self._follow_symlinks:
st = os.stat(child)
else:
st = os.lstat(child)
if stat.S_ISDIR(st.st_mode):
result.append(models.Ref.directory(name=name, uri=uri))
elif stat.S_ISREG(st.st_mode) and self._check_audiofile(uri):
result.append(models.Ref.track(name=name, uri=uri))
else:
logger.warn('Ignored file: %s',
child.decode(sys.getfilesystemencoding(),
'ignore'))
continue
result.sort(key=operator.attrgetter('name'))
return result
def lookup(self, uri):
logger.debug(u'looking up uri = %s' % uri)
logger.debug(u'looking up uri = %s', uri)
localpath = path.uri_to_path(uri)
if not self._is_in_basedir(localpath):
logger.warn(u'Not in basedir: %s' % localpath)
logger.warn(u'Not in basedir: %s', localpath)
return []
try:
result = self._scanner.scan(uri)
@ -110,13 +82,38 @@ class FilesLibraryProvider(backend.LibraryProvider):
except exceptions.ScannerError as e:
logger.warning(u'Problem looking up %s: %s', uri, e)
track = models.Track(uri=uri)
pass
if not track.name:
filename = os.path.basename(localpath)
name = urllib2.unquote(filename).decode('ascii', 'ignore')
name = urllib2.unquote(filename).decode(
sys.getfilesystemencoding(), 'ignore')
track = track.copy(name=name)
return [track]
def _get_media_dirs(self, config):
for entry in config['files']['media_dir']:
media_dir = {}
media_dir_split = entry.split('|', 1)
local_path = path.expand_path(
media_dir_split[0].encode(sys.getfilesystemencoding()))
if not local_path:
logger.warn('Could not expand path %s', media_dir_split[0])
continue
elif not os.path.isdir(local_path):
logger.warn('%s is not a directory', local_path)
continue
media_dir['path'] = local_path
if len(media_dir_split) == 2:
media_dir['name'] = media_dir_split[1]
else:
media_dir['name'] = media_dir_split[0].replace(os.sep, '+')
yield media_dir
def _get_media_dirs_refs(self):
for media_dir in self._media_dirs:
yield models.Ref.directory(
name=media_dir['name'],
uri=path.path_to_uri(media_dir['path']))
def _show_media_dirs(self):
result = []
for media_dir in self._media_dirs:
@ -129,13 +126,12 @@ class FilesLibraryProvider(backend.LibraryProvider):
def _check_audiofile(self, uri):
try:
result = self._scanner.scan(uri)
logger.debug(u'got scan result playable: %s for %s' %
(result.uri, str(result.playable)))
res = result.playable
logger.debug('got scan result playable: %s for %s',
result.uri, str(result.playable))
return result.playable
except exceptions.ScannerError as e:
logger.warning(u'Problem scanning %s: %s', uri, e)
res = False
return res
logger.warning('Problem scanning %s: %s', uri, e)
return False
def _is_playlist(self, child):
return os.path.splitext(child)[1] == '.m3u'
@ -153,5 +149,5 @@ class FilesLibraryProvider(backend.LibraryProvider):
except:
pass
if not res:
logger.warn(u'%s not inside any basedir' % localpath)
logger.warn('%s not inside any basedir', localpath)
return res