Files
oak-editor/app/dialog/crashhandler/crashhandler.cpp
T
itsmattkc 75d4cd899b use hardcoded namespace
The macro defined namespaces confused the hell out of lupdate and more or less broke translations permanently. Looks like the only way we can do it is to have a hardcoded namespace, which goes against my instinct, but honestly how likely is it that we'll change the namespace anyway (I guess forks might want to do it, but that's their problem ;) )
2020-11-17 20:24:42 +11:00

212 lines
6.9 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2020 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "crashhandler.h"
#include <QApplication>
#include <QDir>
#include <QFile>
#include <QFontDatabase>
#include <QLabel>
#include <QHttpMultiPart>
#include <QMessageBox>
#include <QNetworkAccessManager>
#include <QProcess>
#include <QScrollBar>
#include <QThread>
#include <QTimer>
#include <QVBoxLayout>
#include "common/crashpadutils.h"
namespace olive {
CrashHandlerDialog::CrashHandlerDialog(const char *report_dir, const char* crash_time)
{
setWindowTitle(tr("Olive"));
setWindowFlags(Qt::WindowStaysOnTopHint);
crash_time_ = QString(crash_time).toULongLong();
report_dir_ = report_dir;
QVBoxLayout* layout = new QVBoxLayout(this);
layout->addWidget(new QLabel(tr("We're sorry, Olive has crashed. Please help us fix it by "
"sending an error report.")));
summary_edit_ = new QTextEdit();
summary_edit_->setPlaceholderText(tr("Describe what you were doing in as much detail as "
"possible. If you can, provide steps to reproduce this crash."));
layout->addWidget(summary_edit_);
layout->addWidget(new QLabel(tr("Crash Report:")));
crash_report_ = new QTextEdit();
crash_report_->setReadOnly(true);
crash_report_->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
layout->addWidget(crash_report_);
QHBoxLayout* btn_layout = new QHBoxLayout();
btn_layout->setMargin(0);
btn_layout->addStretch();
send_report_btn_ = new QPushButton(tr("Send Error Report"));
connect(send_report_btn_, &QPushButton::clicked, this, &CrashHandlerDialog::SendErrorReport);
btn_layout->addWidget(send_report_btn_);
dont_send_btn_ = new QPushButton(tr("Don't Send"));
connect(dont_send_btn_, &QPushButton::clicked, this, &CrashHandlerDialog::reject);
btn_layout->addWidget(dont_send_btn_);
layout->addLayout(btn_layout);
crash_report_->setEnabled(false);
send_report_btn_->setEnabled(false);
crash_report_->setText(tr("Waiting for crash report to be generated..."));
AttemptToFindReport();
}
void CrashHandlerDialog::SetGUIObjectsEnabled(bool e)
{
summary_edit_->setEnabled(e);
crash_report_->setEnabled(e);
send_report_btn_->setEnabled(e);
dont_send_btn_->setEnabled(e);
}
void CrashHandlerDialog::GenerateReport()
{
QProcess* p = new QProcess();
connect(p, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, &CrashHandlerDialog::ReadProcessFinished);
connect(p, &QProcess::readyReadStandardOutput, this, &CrashHandlerDialog::ReadProcessHasData);
QString stackwalk_filename;
#if defined(OS_WIN)
stackwalk_filename = QStringLiteral("minidump_stackwalk.exe");
#else
stackwalk_filename = QStringLiteral("minidump_stackwalk");
#endif
QString stackwalk_bin = QDir(qApp->applicationDirPath()).filePath(stackwalk_filename);
p->start(stackwalk_bin, {report_filename_});
crash_report_->setText(QStringLiteral("Trying to run: %1").arg(stackwalk_bin));
}
void CrashHandlerDialog::ReplyFinished(QNetworkReply* reply)
{
if (reply->error() == QNetworkReply::NoError) {
// Close dialog
QDialog::accept();
} else {
QMessageBox::critical(this, tr("Upload Failed"),
tr("Failed to send error report. Please try again later."),
QMessageBox::Ok);
SetGUIObjectsEnabled(true);
}
}
void CrashHandlerDialog::AttemptToFindReport()
{
// Retrieve reports from Crashpad database
std::unique_ptr<crashpad::CrashReportDatabase> database = crashpad::CrashReportDatabase::Initialize(base::FilePath(QSTRING_TO_BASE_STRING(QString(report_dir_))));
std::vector<crashpad::CrashReportDatabase::Report> reports;
database->GetCompletedReports(&reports);
// Find report that was made after the crash time
foreach (const crashpad::CrashReportDatabase::Report& report, reports) {
if (report.creation_time >= crash_time_) {
report_filename_ = BASE_STRING_TO_QSTRING(report.file_path.value());
break;
}
}
// If we found it, use it, otherwise wait a second and try again
if (report_filename_.isEmpty()) {
// Couldn't find report, try again in one second
QTimer::singleShot(500, this, &CrashHandlerDialog::AttemptToFindReport);
} else {
GenerateReport();
}
}
void CrashHandlerDialog::ReadProcessHasData()
{
report_data_.append(static_cast<QProcess*>(sender())->readAllStandardOutput());
}
void CrashHandlerDialog::ReadProcessFinished()
{
SetGUIObjectsEnabled(true);
crash_report_->setText(report_data_);
delete sender();
}
void CrashHandlerDialog::SendErrorReport()
{
if (summary_edit_->document()->isEmpty()) {
if (QMessageBox::question(this,
tr("No Crash Summary"),
tr("Are you sure you want to send an error report with no crash summary?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
return;
}
}
SetGUIObjectsEnabled(false);
QNetworkAccessManager* manager = new QNetworkAccessManager();
connect(manager, &QNetworkAccessManager::finished, this, &CrashHandlerDialog::ReplyFinished);
QNetworkRequest request;
request.setSslConfiguration(QSslConfiguration::defaultConfiguration());
request.setUrl(QStringLiteral("https://olivevideoeditor.org/crashpad/report.php"));
// Create HTTP form
QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
// Create description section
QHttpPart desc_part;
desc_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("text/plain"));
desc_part.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"description\""));
desc_part.setBody(summary_edit_->toPlainText().toUtf8());
multipart->append(desc_part);
// Create file section
QHttpPart file_part;
file_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/octet-stream"));
file_part.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"upload_file_minidump\"; filename=\"%1\"")
.arg(QFileInfo(report_filename_).fileName()));
QFile* file = new QFile(report_filename_);
file->open(QFile::ReadOnly);
file_part.setBodyDevice(file);
file->setParent(multipart); // Delete file with multipart
multipart->append(file_part);
manager->post(request, multipart);
}
}