diff --git a/mopidy/file/__init__.py b/mopidy/file/__init__.py index 089cf6e6..ea4dea12 100644 --- a/mopidy/file/__init__.py +++ b/mopidy/file/__init__.py @@ -28,5 +28,5 @@ class Extension(ext.Extension): return schema def setup(self, registry): - from .backend import FilesBackend - registry.add('backend', FilesBackend) + from .backend import FileBackend + registry.add('backend', FileBackend) diff --git a/mopidy/file/backend.py b/mopidy/file/backend.py index 74b029e5..bc5af48b 100644 --- a/mopidy/file/backend.py +++ b/mopidy/file/backend.py @@ -11,12 +11,11 @@ from mopidy.file import library logger = logging.getLogger(__name__) -class FilesBackend(pykka.ThreadingActor, backend.Backend): +class FileBackend(pykka.ThreadingActor, backend.Backend): uri_schemes = ['file'] def __init__(self, config, audio): - super(FilesBackend, self).__init__() - self.library = library.FilesLibraryProvider(backend=self, - config=config) + super(FileBackend, self).__init__() + self.library = library.FileLibraryProvider(backend=self, config=config) self.playback = backend.PlaybackProvider(audio=audio, backend=self) self.playlists = None diff --git a/mopidy/file/library.py b/mopidy/file/library.py index f9c4ad97..c20b92b6 100644 --- a/mopidy/file/library.py +++ b/mopidy/file/library.py @@ -10,12 +10,14 @@ from mopidy import backend, exceptions, models from mopidy.audio import scan, utils from mopidy.internal import path + logger = logging.getLogger(__name__) FS_ENCODING = sys.getfilesystemencoding() -class FilesLibraryProvider(backend.LibraryProvider): +class FileLibraryProvider(backend.LibraryProvider): """Library for browsing local files.""" + # TODO: get_images that can pull from metadata and/or .folder.png etc? # TODO: handle playlists? @@ -24,14 +26,13 @@ class FilesLibraryProvider(backend.LibraryProvider): if not self._media_dirs: return None elif len(self._media_dirs) == 1: - local_path = self._media_dirs[0]['path'] - uri = path.path_to_uri(local_path) + uri = path.path_to_uri(self._media_dirs[0]['path']) else: uri = 'file:root' return models.Ref.directory(name='Files', uri=uri) def __init__(self, backend, config): - super(FilesLibraryProvider, self).__init__(backend) + super(FileLibraryProvider, self).__init__(backend) self._media_dirs = list(self._get_media_dirs(config)) self._follow_symlinks = config['file']['follow_symlinks'] self._show_dotfiles = config['file']['show_dotfiles'] @@ -42,19 +43,21 @@ class FilesLibraryProvider(backend.LibraryProvider): logger.debug('Browsing files at: %s', uri) result = [] local_path = path.uri_to_path(uri) + if local_path == 'root': return list(self._get_media_dirs_refs()) + if not self._is_in_basedir(os.path.realpath(local_path)): logger.warning( 'Rejected attempt to browse path (%s) outside dirs defined ' 'in file/media_dirs config.', local_path.decode(FS_ENCODING, 'replace')) return [] + for dir_entry in os.listdir(local_path): child_path = os.path.join(local_path, dir_entry) uri = path.path_to_uri(child_path) - printable_path = child_path.decode(FS_ENCODING, - 'replace') + printable_path = child_path.decode(FS_ENCODING, 'replace') if os.path.islink(child_path) and not self._follow_symlinks: logger.debug('Ignoring symlink: %s', printable_path) @@ -68,13 +71,12 @@ class FilesLibraryProvider(backend.LibraryProvider): if not self._show_dotfiles and dir_entry.startswith(b'.'): continue - dir_entry = dir_entry.decode(FS_ENCODING, - 'replace') + name = dir_entry.decode(FS_ENCODING, 'replace') if os.path.isdir(child_path): - result.append(models.Ref.directory(name=dir_entry, uri=uri)) + result.append(models.Ref.directory(name=name, uri=uri)) elif os.path.isfile(child_path): if self._is_audiofile(uri): - result.append(models.Ref.track(name=dir_entry, uri=uri)) + result.append(models.Ref.track(name=name, uri=uri)) else: logger.debug('Ignoring non-audiofile: %s', printable_path) @@ -84,9 +86,11 @@ class FilesLibraryProvider(backend.LibraryProvider): def lookup(self, uri): logger.debug('Looking up file URI: %s', uri) local_path = path.uri_to_path(uri) + if not self._is_in_basedir(local_path): logger.warning('Ignoring URI outside base dir: %s', local_path) return [] + try: result = self._scanner.scan(uri) track = utils.convert_tags_to_track(result.tags).copy( @@ -94,11 +98,13 @@ class FilesLibraryProvider(backend.LibraryProvider): except exceptions.ScannerError as e: logger.warning('Failed looking up %s: %s', uri, e) track = models.Track(uri=uri) + if not track.name: filename = os.path.basename(local_path) name = urllib2.unquote(filename).decode( FS_ENCODING, 'replace') track = track.copy(name=name) + return [track] def _get_media_dirs(self, config): @@ -107,6 +113,7 @@ class FilesLibraryProvider(backend.LibraryProvider): media_dir_split = entry.split('|', 1) local_path = path.expand_path( media_dir_split[0].encode(FS_ENCODING)) + if not local_path: logger.warning('Failed expanding path (%s) from' 'file/media_dirs config value.', @@ -115,12 +122,14 @@ class FilesLibraryProvider(backend.LibraryProvider): elif not os.path.isdir(local_path): logger.warning('%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: # TODO Mpd client should accept / in dir name media_dir['name'] = media_dir_split[0].replace(os.sep, '+') + yield media_dir def _get_media_dirs_refs(self): @@ -137,7 +146,7 @@ class FilesLibraryProvider(backend.LibraryProvider): result.uri, result.playable and 'playable' or 'unplayable') return result.playable except exceptions.ScannerError as e: - logger.debug("Failed scanning %s: %s", uri, e) + logger.debug('Failed scanning %s: %s', uri, e) return False def _is_in_basedir(self, local_path):