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. Mopidy-Files is an extension for playing music from your local music archive.
It is bundled with Mopidy and enabled by default. It is bundled with Mopidy and enabled by default.
It allows you to browse through your local file system. 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:``. This backend handles URIs starting with ``file:``.
@ -27,10 +27,9 @@ See :ref:`config` for general help on configuring Mopidy.
.. confval:: files/media_dir .. confval:: files/media_dir
A list of directories to be browsable. 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. Whether to show hidden files and directories that start with a dot.
Default is false. Default is false.
@ -38,11 +37,11 @@ See :ref:`config` for general help on configuring Mopidy.
.. confval:: files/follow_symlinks .. confval:: files/follow_symlinks
Whether to follow symbolic links found in :confval:`files/media_dir`. 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. Directories and files that are outside the configured directories will not be shown.
Default is false Default is false.
.. confval:: files/metadata_timeout .. confval:: files/metadata_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
the next file. Reducing the value might speed up the directory listing, 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): def get_config_schema(self):
schema = super(Extension, self).get_config_schema() schema = super(Extension, self).get_config_schema()
schema['media_dir'] = config.List(optional=True) 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['follow_symlinks'] = config.Boolean(optional=True)
schema['metadata_timeout'] = config.Integer( schema['metadata_timeout'] = config.Integer(
minimum=1000, maximum=1000 * 60 * 60, optional=True) minimum=1000, maximum=1000 * 60 * 60, optional=True)

View File

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

View File

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