From 1f3a4abab0a1eb3018dfa724425d4a6b858cabda Mon Sep 17 00:00:00 2001 From: tom roth Date: Mon, 6 Jul 2015 11:11:23 +0200 Subject: [PATCH] file-browser: Various changes as discussed in PR 1207 --- docs/ext/files.rst | 13 ++-- mopidy/files/__init__.py | 2 +- mopidy/files/ext.conf | 8 +-- mopidy/files/library.py | 126 +++++++++++++++++++-------------------- 4 files changed, 72 insertions(+), 77 deletions(-) diff --git a/docs/ext/files.rst b/docs/ext/files.rst index a4508689..c952ba14 100644 --- a/docs/ext/files.rst +++ b/docs/ext/files.rst @@ -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. diff --git a/mopidy/files/__init__.py b/mopidy/files/__init__.py index cd5e41c1..1e2e961a 100644 --- a/mopidy/files/__init__.py +++ b/mopidy/files/__init__.py @@ -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) diff --git a/mopidy/files/ext.conf b/mopidy/files/ext.conf index 9bc229ff..836db665 100644 --- a/mopidy/files/ext.conf +++ b/mopidy/files/ext.conf @@ -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 \ No newline at end of file +metadata_timeout = 1000 diff --git a/mopidy/files/library.py b/mopidy/files/library.py index 8bd04272..09d898ae 100644 --- a/mopidy/files/library.py +++ b/mopidy/files/library.py @@ -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