diff --git a/picard/__init__.py b/picard/__init__.py index 1fab03c3b..dcb0712bd 100644 --- a/picard/__init__.py +++ b/picard/__init__.py @@ -88,3 +88,50 @@ api_versions = [ ] api_versions_tuple = [Version.from_string(v) for v in api_versions] + + +def crash_handler(): + """Implements minimal handling of an exception crashing the application. + This function tries to log the exception to a log file and display + a minimal crash dialog to the user. + This function is supposed to be called from inside an except blog. + """ + # First try to get traceback information and write it to a log file + # with minimum chance to fail. + import sys + from tempfile import NamedTemporaryFile + import traceback + trace = traceback.format_exc() + logfile = None + try: + with NamedTemporaryFile(suffix='.log', prefix='picard-crash-', delete=False) as f: + f.write(trace.encode(errors="replace")) + logfile = f.name + except: # noqa: E722,F722 # pylint: disable=bare-except + print("Failed writing log file {0}".format(logfile), file=sys.stderr) + logfile = None + + # Display the crash information to the user as a dialog. This requires + # importing Qt5 and has some potential to fail if things are broken. + from PyQt5.QtCore import QCoreApplication, Qt, QUrl + from PyQt5.QtWidgets import QApplication, QMessageBox + app = QCoreApplication.instance() + if not app: + app = QApplication(sys.argv) + msgbox = QMessageBox() + msgbox.setIcon(QMessageBox.Critical) + msgbox.setWindowTitle("Picard terminated unexpectedly") + msgbox.setTextFormat(Qt.RichText) + msgbox.setText( + 'An unexpected error has caused Picard to crash. ' + 'Please report this issue on the MusicBrainz bug tracker.') + if logfile: + logfile_url = QUrl.fromLocalFile(logfile) + msgbox.setInformativeText( + 'A logfile has been written to {1}.' + .format(logfile_url.url(), logfile)) + msgbox.setDetailedText(trace) + msgbox.setStandardButtons(QMessageBox.Close) + msgbox.setDefaultButton(QMessageBox.Close) + msgbox.exec_() + app.quit() diff --git a/scripts/picard.in b/scripts/picard.in index 7b67f8ecf..ab04f5546 100644 --- a/scripts/picard.in +++ b/scripts/picard.in @@ -1,3 +1,11 @@ #!/usr/bin/env python3 -from picard.tagger import main -main('%(localedir)s', %(autoupdate)s) + +try: + from picard.tagger import main + main('%(localedir)s', %(autoupdate)s) +except SystemExit: + raise # Just continue with a normal application exit +except: # noqa: E722,F722 # pylint: disable=bare-except + from picard import crash_handler + crash_handler() + raise diff --git a/tagger.py.in b/tagger.py.in index 35cb4f204..ceaf22262 100644 --- a/tagger.py.in +++ b/tagger.py.in @@ -15,5 +15,12 @@ else: if sys.platform == 'win32': os.environ['PATH'] = basedir + ';' + os.environ['PATH'] -from picard.tagger import main -main(os.path.join(basedir, 'locale'), %(autoupdate)s) +try: + from picard.tagger import main + main(os.path.join(basedir, 'locale'), %(autoupdate)s) +except SystemExit: + raise # Just continue with a normal application exit +except: # noqa: E722,F722 # pylint: disable=bare-except + from picard import crash_handler + crash_handler() + raise