FileOperationError: also include the file path

This makes the errors fully self-descriptive, which simplifies logging them as
errors (you just have to `log.error(exc)` to get a reasonable message). We
also now handle these at the top level in case someone forgets to add a
handler. But in this case, we also send the full traceback to the debug log.
This commit is contained in:
Adrian Sampson 2014-03-25 22:41:28 -07:00
parent 3a26845f5e
commit 05ebd6d28e
4 changed files with 34 additions and 19 deletions

View file

@ -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():

View file

@ -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()

View file

@ -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.

View file

@ -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',