diff --git a/beets/importer.py b/beets/importer.py index a01c21d1e..f997770c4 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -896,8 +896,7 @@ def manipulate_files(session): try: item.write() except library.FileOperationError as exc: - log.error(u'could not write {0}: {1}'.format( - util.displayable_path(item.path), exc)) + log.error(exc) # Save new paths. with session.lib.transaction(): diff --git a/beets/library.py b/beets/library.py index c4b94b091..945594307 100644 --- a/beets/library.py +++ b/beets/library.py @@ -265,28 +265,39 @@ class FileOperationError(Exception): Possibilities include an unsupported media type, a permissions error, and an unhandled Mutagen exception. """ - def __init__(self, reason): - """Create an exception with the underlying (chained) exception - `reason`. + def __init__(self, path, reason): + """Create an exception describing an operation on the file at + `path` with the underlying (chained) exception `reason`. """ - super(FileOperationError, self).__init__(reason) + super(FileOperationError, self).__init__(path, reason) + self.path = path self.reason = reason - def __str__(self): - """Get a string representing the error. Uses the same string as - the underlying reason. + def __unicode__(self): + """Get a string representing the error. Describes both the + underlying reason and the file path in question. """ - return str(self.reason) + return u'{0}: {1}'.format( + util.displayable_path(self.path), + unicode(self.reason) + ) + + def __str__(self): + return unicode(self).encode('utf8') class ReadError(FileOperationError): """An error while reading a file (i.e. in `Item.read`). """ + def __unicode__(self): + return u'error reading ' + super(ReadError, self).__unicode__() class WriteError(FileOperationError): """An error while writing a file (i.e. in `Item.write`). """ + def __unicode__(self): + return u'error writing ' + super(WriteError, self).__unicode__() @@ -384,7 +395,7 @@ class Item(LibModel): try: f = MediaFile(syspath(read_path)) except (OSError, IOError) as exc: - raise ReadError(exc) + raise ReadError(read_path, exc) for key in ITEM_KEYS_META: value = getattr(f, key) @@ -406,12 +417,12 @@ class Item(LibModel): def write(self): """Write the item's metadata to the associated file. - Can raises either a `ReadError` or a `WriteError`. + Can raise either a `ReadError` or a `WriteError`. """ try: f = MediaFile(syspath(self.path)) except (OSError, IOError) as exc: - raise ReadError(exc) + raise ReadError(self.path, exc) plugins.send('write', item=self) @@ -420,7 +431,7 @@ class Item(LibModel): try: f.save(id3v23=beets.config['id3v23'].get(bool)) except (OSError, IOError, MutagenError) as exc: - raise WriteError(exc) + raise WriteError(self.path, exc) # The file has a new mtime. self.mtime = self.current_mtime() diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 9e4503f92..2df74ea72 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -834,7 +834,7 @@ def vararg_callback(option, opt_str, value, parser): break value.append(arg) - del parser.rargs[:len(value)-1] + del parser.rargs[:len(value) - 1] setattr(parser.values, option.dest, value) @@ -960,8 +960,15 @@ def main(args=None): except util.HumanReadableException as exc: exc.log(log) sys.exit(1) + except library.FileOperationError as exc: + # These errors have reasonable human-readable descriptions, but + # we still want to log their tracebacks for debugging. + log.debug(traceback.format_exc()) + log.error(exc) + sys.exit(1) except confit.ConfigError as exc: log.error(u'configuration error: {0}'.format(exc)) + sys.exit(1) except IOError as exc: if exc.errno == errno.EPIPE: # "Broken pipe". End silently. diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 40c13a998..d0ff320a3 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1137,8 +1137,7 @@ def modify_items(lib, mods, query, write, move, album, confirm): try: item.write() except library.FileOperationError as exc: - log.error(u'could not write {0}: {1}'.format( - util.displayable_path(item.path), exc)) + log.error(exc) modify_cmd = ui.Subcommand('modify', help='change metadata fields', aliases=('mod',)) @@ -1238,8 +1237,7 @@ def write_items(lib, query, pretend): try: item.write() except library.FileOperationError as exc: - log.error(u'could not write {0}: {1}'.format( - util.displayable_path(item.path), exc)) + log.error(exc) write_cmd = ui.Subcommand('write', help='write tag information to files') write_cmd.parser.add_option('-p', '--pretend', action='store_true',