mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-12 08:39:01 +08:00
Make an extra early crash dialog
CURA-4895 An early crash dialog showing options to backup the current user data and reset the configuration files.
This commit is contained in:
parent
1d946085d3
commit
6bbc18d51e
@ -12,9 +12,10 @@ import json
|
|||||||
import ssl
|
import ssl
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.error
|
import urllib.error
|
||||||
|
import shutil
|
||||||
|
|
||||||
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR
|
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR
|
||||||
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox
|
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
@ -67,15 +68,120 @@ class CrashHandler:
|
|||||||
if not CuraDebugMode and exception_type not in fatal_exception_types:
|
if not CuraDebugMode and exception_type not in fatal_exception_types:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._send_report_checkbox = None
|
||||||
|
self.early_crash_dialog = self._createEarlyCrashDialog()
|
||||||
self.dialog = QDialog()
|
self.dialog = QDialog()
|
||||||
self._createDialog()
|
self._createDialog()
|
||||||
|
|
||||||
|
def _createEarlyCrashDialog(self):
|
||||||
|
dialog = QDialog()
|
||||||
|
dialog.setMinimumWidth(500)
|
||||||
|
dialog.setMinimumHeight(170)
|
||||||
|
dialog.setWindowTitle(catalog.i18nc("@title:window", "Cura Crashed"))
|
||||||
|
dialog.finished.connect(self._closeEarlyCrashDialog)
|
||||||
|
|
||||||
|
layout = QVBoxLayout(dialog)
|
||||||
|
|
||||||
|
label = QLabel()
|
||||||
|
label.setText(catalog.i18nc("@label crash message", """<p><b>A fatal error has occurred.</p></b>
|
||||||
|
<p>Unfortunately, Cura encountered an unrecoverable error during start up. It was possibly caused by some incorrect configuration files. We suggest to backup and reset your configuration.</p>
|
||||||
|
<p>Please send us this Crash Report to fix the problem.</p>
|
||||||
|
"""))
|
||||||
|
label.setWordWrap(True)
|
||||||
|
layout.addWidget(label)
|
||||||
|
|
||||||
|
# "send report" check box and show details
|
||||||
|
self._send_report_checkbox = QCheckBox(catalog.i18nc("@action:button", "Send crash report to Ultimaker"), dialog)
|
||||||
|
self._send_report_checkbox.setChecked(True)
|
||||||
|
|
||||||
|
show_details_button = QPushButton(catalog.i18nc("@action:button", "Show detailed crash report"), dialog)
|
||||||
|
show_details_button.setMaximumWidth(180)
|
||||||
|
show_details_button.clicked.connect(self._showDetailedReport)
|
||||||
|
|
||||||
|
layout.addWidget(self._send_report_checkbox)
|
||||||
|
layout.addWidget(show_details_button)
|
||||||
|
|
||||||
|
# "backup and start clean" and "close" buttons
|
||||||
|
buttons = QDialogButtonBox()
|
||||||
|
buttons.addButton(QDialogButtonBox.Close)
|
||||||
|
buttons.addButton(catalog.i18nc("@action:button", "Backup and Reset Configuration"), QDialogButtonBox.AcceptRole)
|
||||||
|
buttons.rejected.connect(self._closeEarlyCrashDialog)
|
||||||
|
buttons.accepted.connect(self._backupAndStartClean)
|
||||||
|
|
||||||
|
layout.addWidget(buttons)
|
||||||
|
|
||||||
|
return dialog
|
||||||
|
|
||||||
|
def _closeEarlyCrashDialog(self):
|
||||||
|
if self._send_report_checkbox.isChecked():
|
||||||
|
self._sendCrashReport()
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
def _backupAndStartClean(self):
|
||||||
|
# TODO: backup the current cura directories and create clean ones
|
||||||
|
from cura.CuraVersion import CuraVersion
|
||||||
|
from UM.Resources import Resources
|
||||||
|
# The early crash may happen before those information is set in Resources, so we need to set them here to
|
||||||
|
# make sure that Resources can find the correct place.
|
||||||
|
Resources.ApplicationIdentifier = "cura"
|
||||||
|
Resources.ApplicationVersion = CuraVersion
|
||||||
|
config_path = Resources.getConfigStoragePath()
|
||||||
|
data_path = Resources.getDataStoragePath()
|
||||||
|
cache_path = Resources.getCacheStoragePath()
|
||||||
|
|
||||||
|
folders_to_backup = []
|
||||||
|
folders_to_remove = [] # only cache folder needs to be removed
|
||||||
|
|
||||||
|
folders_to_backup.append(config_path)
|
||||||
|
if data_path != config_path:
|
||||||
|
folders_to_backup.append(data_path)
|
||||||
|
|
||||||
|
# Only remove the cache folder if it's not the same as data or config
|
||||||
|
if cache_path not in (config_path, data_path):
|
||||||
|
folders_to_remove.append(cache_path)
|
||||||
|
|
||||||
|
for folder in folders_to_remove:
|
||||||
|
shutil.rmtree(folder, ignore_errors = True)
|
||||||
|
for folder in folders_to_backup:
|
||||||
|
base_name = os.path.basename(folder)
|
||||||
|
root_dir = os.path.dirname(folder)
|
||||||
|
|
||||||
|
idx = 0
|
||||||
|
file_name = base_name + "_" + str(time.time())
|
||||||
|
zip_file_path = os.path.join(root_dir, file_name + ".zip")
|
||||||
|
while os.path.exists(zip_file_path):
|
||||||
|
idx += 1
|
||||||
|
file_name = base_name + "_" + str(time.time()) + "_" + idx
|
||||||
|
zip_file_path = os.path.join(root_dir, file_name + ".zip")
|
||||||
|
try:
|
||||||
|
# remove the .zip extension because make_archive() adds it
|
||||||
|
zip_file_path = zip_file_path[:-4]
|
||||||
|
shutil.make_archive(zip_file_path, "zip", root_dir = root_dir, base_dir = base_name)
|
||||||
|
|
||||||
|
# remove the folder only when the backup is successful
|
||||||
|
shutil.rmtree(folder)
|
||||||
|
# create an empty folder so Resources will not try to copy the old ones
|
||||||
|
os.makedirs(folder, 0o0755, exist_ok=True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
Logger.logException("e", "Failed to backup [%s] to file [%s]", folder, zip_file_path)
|
||||||
|
if not self.has_started:
|
||||||
|
print("Failed to backup [%s] to file [%s]: %s", folder, zip_file_path, e)
|
||||||
|
|
||||||
|
self.early_crash_dialog.close()
|
||||||
|
|
||||||
|
def _showDetailedReport(self):
|
||||||
|
self.dialog.exec_()
|
||||||
|
|
||||||
## Creates a modal dialog.
|
## Creates a modal dialog.
|
||||||
def _createDialog(self):
|
def _createDialog(self):
|
||||||
self.dialog.setMinimumWidth(640)
|
self.dialog.setMinimumWidth(640)
|
||||||
self.dialog.setMinimumHeight(640)
|
self.dialog.setMinimumHeight(640)
|
||||||
self.dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report"))
|
self.dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report"))
|
||||||
self.dialog.finished.connect(self._close)
|
# if the application has not fully started, this will be a detailed report dialog which should not
|
||||||
|
# close the application when it's closed.
|
||||||
|
if self.has_started:
|
||||||
|
self.dialog.finished.connect(self._close)
|
||||||
|
|
||||||
layout = QVBoxLayout(self.dialog)
|
layout = QVBoxLayout(self.dialog)
|
||||||
|
|
||||||
@ -249,9 +355,13 @@ class CrashHandler:
|
|||||||
def _buttonsWidget(self):
|
def _buttonsWidget(self):
|
||||||
buttons = QDialogButtonBox()
|
buttons = QDialogButtonBox()
|
||||||
buttons.addButton(QDialogButtonBox.Close)
|
buttons.addButton(QDialogButtonBox.Close)
|
||||||
buttons.addButton(catalog.i18nc("@action:button", "Send report"), QDialogButtonBox.AcceptRole)
|
# Like above, this will be served as a separate detailed report dialog if the application has not yet been
|
||||||
|
# fully loaded. In this case, "send report" will be a check box in the early crash dialog, so there is no
|
||||||
|
# need for this extra button.
|
||||||
|
if self.has_started:
|
||||||
|
buttons.addButton(catalog.i18nc("@action:button", "Send report"), QDialogButtonBox.AcceptRole)
|
||||||
|
buttons.accepted.connect(self._sendCrashReport)
|
||||||
buttons.rejected.connect(self.dialog.close)
|
buttons.rejected.connect(self.dialog.close)
|
||||||
buttons.accepted.connect(self._sendCrashReport)
|
|
||||||
|
|
||||||
return buttons
|
return buttons
|
||||||
|
|
||||||
@ -269,15 +379,23 @@ class CrashHandler:
|
|||||||
kwoptions["context"] = ssl._create_unverified_context()
|
kwoptions["context"] = ssl._create_unverified_context()
|
||||||
|
|
||||||
Logger.log("i", "Sending crash report info to [%s]...", self.crash_url)
|
Logger.log("i", "Sending crash report info to [%s]...", self.crash_url)
|
||||||
|
if not self.has_started:
|
||||||
|
print("Sending crash report info to [%s]...\n" % self.crash_url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
f = urllib.request.urlopen(self.crash_url, **kwoptions)
|
f = urllib.request.urlopen(self.crash_url, **kwoptions)
|
||||||
Logger.log("i", "Sent crash report info.")
|
Logger.log("i", "Sent crash report info.")
|
||||||
|
if not self.has_started:
|
||||||
|
print("Sent crash report info.\n")
|
||||||
f.close()
|
f.close()
|
||||||
except urllib.error.HTTPError:
|
except urllib.error.HTTPError as e:
|
||||||
Logger.logException("e", "An HTTP error occurred while trying to send crash report")
|
Logger.logException("e", "An HTTP error occurred while trying to send crash report")
|
||||||
except Exception: # We don't want any exception to cause problems
|
if not self.has_started:
|
||||||
|
print("An HTTP error occurred while trying to send crash report: %s" % e)
|
||||||
|
except Exception as e: # We don't want any exception to cause problems
|
||||||
Logger.logException("e", "An exception occurred while trying to send crash report")
|
Logger.logException("e", "An exception occurred while trying to send crash report")
|
||||||
|
if not self.has_started:
|
||||||
|
print("An exception occurred while trying to send crash report: %s" % e)
|
||||||
|
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
|
|
||||||
|
@ -134,7 +134,6 @@ class CuraApplication(QtApplication):
|
|||||||
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
|
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
|
||||||
# this list of dir names will be used by UM to detect an old cura directory
|
# this list of dir names will be used by UM to detect an old cura directory
|
||||||
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
|
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
|
||||||
Resources.addExpectedDirNameInData(dir_name)
|
Resources.addExpectedDirNameInData(dir_name)
|
||||||
|
@ -103,7 +103,7 @@ def exceptHook(hook_type, value, traceback):
|
|||||||
else:
|
else:
|
||||||
application = QApplication(sys.argv)
|
application = QApplication(sys.argv)
|
||||||
_crash_handler = CrashHandler(hook_type, value, traceback, has_started)
|
_crash_handler = CrashHandler(hook_type, value, traceback, has_started)
|
||||||
_crash_handler.dialog.show()
|
_crash_handler.early_crash_dialog.show()
|
||||||
sys.exit(application.exec_())
|
sys.exit(application.exec_())
|
||||||
|
|
||||||
if not known_args["debug"]:
|
if not known_args["debug"]:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user