Use QHttpMultiPart, move the crash sender into its own file
This commit is contained in:
parent
273d3260a0
commit
39b88e1577
|
@ -185,7 +185,10 @@ if(WIN32)
|
|||
option(ENABLE_WIN32_CONSOLE "Show the windows console even outside Debug mode" OFF)
|
||||
endif(WIN32)
|
||||
|
||||
optional_component(BREAKPAD OFF "Crash reporting")
|
||||
# Crash reporting requires Qt 4.8 because of the use of QHttpMultiPart
|
||||
optional_component(BREAKPAD OFF "Crash reporting"
|
||||
DEPENDS "Qt 4.8 or newer" "QTVERSION VERSION_GREATER 4.7.999"
|
||||
)
|
||||
|
||||
optional_component(GOOGLE_DRIVE ON "Google Drive support"
|
||||
DEPENDS "Google sparsehash" SPARSEHASH_INCLUDE_DIRS
|
||||
|
@ -434,6 +437,8 @@ if(IMOBILEDEVICE_FOUND AND PLIST_FOUND)
|
|||
endif(IMOBILEDEVICE_FOUND AND PLIST_FOUND)
|
||||
|
||||
if(HAVE_BREAKPAD)
|
||||
set(CRASHREPORTING_HOSTNAME crashes.clementine-player.org
|
||||
CACHE STRING "Server to send crash reports to")
|
||||
add_subdirectory(3rdparty/google-breakpad)
|
||||
endif(HAVE_BREAKPAD)
|
||||
|
||||
|
|
|
@ -384,7 +384,6 @@ set(HEADERS
|
|||
|
||||
core/application.h
|
||||
core/backgroundstreams.h
|
||||
core/crashreporting.h
|
||||
core/database.h
|
||||
core/deletefiles.h
|
||||
core/filesystemwatcherinterface.h
|
||||
|
@ -1093,6 +1092,8 @@ optional_source(LINUX
|
|||
|
||||
# Breakpad source files
|
||||
if(HAVE_BREAKPAD)
|
||||
list(APPEND SOURCES core/crashsender.cpp)
|
||||
list(APPEND HEADERS core/crashsender.h)
|
||||
if(LINUX)
|
||||
list(APPEND SOURCES core/crashreporting_linux.cpp)
|
||||
elseif(APPLE)
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#define CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}"
|
||||
#define CMAKE_EXECUTABLE_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}"
|
||||
#define CRASHREPORTING_HOSTNAME "${CRASHREPORTING_HOSTNAME}"
|
||||
|
||||
#cmakedefine ENABLE_VISUALISATIONS
|
||||
#cmakedefine HAVE_AUDIOCD
|
||||
|
|
|
@ -17,195 +17,34 @@
|
|||
|
||||
#include "config.h"
|
||||
#include "crashreporting.h"
|
||||
#include "version.h"
|
||||
#include "core/logging.h"
|
||||
#include "crashsender.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QCryptographicHash>
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QProgressDialog>
|
||||
#include <QSysInfo>
|
||||
#include <QUrl>
|
||||
#include <QtDebug>
|
||||
|
||||
#if QT_VERSION >= 0x040800
|
||||
#include <QHttpMultiPart>
|
||||
#endif
|
||||
|
||||
|
||||
const char* CrashSender::kUploadURL = "http://crashes.clementine-player.org/getuploadurl";
|
||||
const char* CrashReporting::kSendCrashReportOption = "--send-crash-report";
|
||||
char* CrashReporting::sPath = NULL;
|
||||
|
||||
|
||||
bool CrashReporting::SendCrashReport(int argc, char** argv) {
|
||||
if (argc != 4 || strcmp(argv[1], kSendCrashReportOption) != 0) {
|
||||
#ifdef HAVE_BREAKPAD
|
||||
if (argc != 3 || strcmp(argv[1], kSendCrashReportOption) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QApplication a(argc, argv);
|
||||
|
||||
CrashSender sender(QString("%1/%2.dmp").arg(argv[2], argv[3]));
|
||||
CrashSender sender(argv[2]);
|
||||
if (sender.Start()) {
|
||||
a.exec();
|
||||
}
|
||||
|
||||
return true;
|
||||
#else // HAVE_BREAKPAD
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void CrashReporting::SetApplicationPath(const QString& path) {
|
||||
sPath = strdup(path.toLocal8Bit().constData());
|
||||
}
|
||||
|
||||
|
||||
CrashSender::CrashSender(const QString& path)
|
||||
: network_(new QNetworkAccessManager(this)),
|
||||
path_(path),
|
||||
file_(new QFile(path_, this)),
|
||||
progress_(NULL) {
|
||||
}
|
||||
|
||||
bool CrashSender::Start() {
|
||||
if (!file_->open(QIODevice::ReadOnly)) {
|
||||
qLog(Warning) << "Failed to open crash report" << path_;
|
||||
return false;
|
||||
}
|
||||
|
||||
// No tr() here.
|
||||
QMessageBox prompt(QMessageBox::Warning, "Clementine has crashed!",
|
||||
"Clementine has crashed! A crash report has been created and saved to "
|
||||
"disk. With your permission it can be automatically sent to our server "
|
||||
"so the developers can find out what happened.");
|
||||
prompt.addButton("Don't send", QMessageBox::RejectRole);
|
||||
prompt.addButton("Send crash report", QMessageBox::AcceptRole);
|
||||
if (prompt.exec() == QDialog::Rejected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
progress_ = new QProgressDialog("Uploading crash report", "Cancel", 0, 0);
|
||||
progress_->show();
|
||||
|
||||
// We'll get a redirect first, so don't start POSTing data yet.
|
||||
QNetworkReply* reply = network_->get(QNetworkRequest(QUrl(kUploadURL)));
|
||||
connect(reply, SIGNAL(finished()), SLOT(RedirectFinished()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CrashSender::RedirectFinished() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
if (!reply) {
|
||||
progress_->close();
|
||||
return;
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
QUrl url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
|
||||
if (!url.isValid()) {
|
||||
printf("Response didn't have a redirection target - HTTP %d\n",
|
||||
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
progress_->close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get some information about the thing that crashed.
|
||||
url.setQueryItems(ClientInfo());
|
||||
|
||||
printf("Uploading crash report to %s\n", url.toEncoded().constData());
|
||||
QNetworkRequest req(url);
|
||||
|
||||
#if QT_VERSION >= 0x040800
|
||||
QHttpPart part;
|
||||
part.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
"form-data; name=\"data\", filename=\"data.dmp\"");
|
||||
part.setBodyDevice(file_);
|
||||
|
||||
QHttpMultiPart* multi_part = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
multi_part->append(part);
|
||||
|
||||
reply = network_->post(req, multi_part);
|
||||
#else
|
||||
// Read the file's data
|
||||
QByteArray file_data = file_->readAll();
|
||||
|
||||
// Find a boundary that doesn't exist in the file
|
||||
QByteArray boundary;
|
||||
forever {
|
||||
boundary = "--------------" + QString::number(qrand(), 16).toAscii();
|
||||
if (!file_data.contains(boundary)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader,
|
||||
QString("multipart/form-data; boundary=" + boundary));
|
||||
|
||||
// Construct the multipart/form-data
|
||||
QByteArray form_data;
|
||||
form_data.reserve(file_data.size() + 1024);
|
||||
form_data.append("--");
|
||||
form_data.append(boundary);
|
||||
form_data.append("\nContent-Disposition: form-data; name=\"data\"; filename=\"data.dmp\"\n");
|
||||
form_data.append("Content-Type: application/octet-stream\n\n");
|
||||
form_data.append(file_data);
|
||||
form_data.append("\n--");
|
||||
form_data.append(boundary);
|
||||
form_data.append("--");
|
||||
|
||||
progress_->setMaximum(form_data.size());
|
||||
|
||||
// Upload the data
|
||||
reply = network_->post(req, form_data);
|
||||
#endif
|
||||
|
||||
connect(reply, SIGNAL(uploadProgress(qint64,qint64)), SLOT(UploadProgress(qint64,qint64)));
|
||||
connect(reply, SIGNAL(finished()), progress_, SLOT(close()));
|
||||
}
|
||||
|
||||
void CrashSender::UploadProgress(qint64 bytes, qint64 total) {
|
||||
printf("Uploaded %lld of %lld bytes\n", bytes, total);
|
||||
progress_->setValue(bytes);
|
||||
}
|
||||
|
||||
QList<QPair<QString, QString> > CrashSender::ClientInfo() const {
|
||||
typedef QPair<QString, QString> Pair;
|
||||
QList<Pair> ret;
|
||||
|
||||
ret.append(Pair("version", CLEMENTINE_VERSION_DISPLAY));
|
||||
ret.append(Pair("qt_version", qVersion()));
|
||||
|
||||
// Hash the binary
|
||||
QFile executable(QCoreApplication::applicationFilePath());
|
||||
if (executable.open(QIODevice::ReadOnly)) {
|
||||
QCryptographicHash hash(QCryptographicHash::Md5);
|
||||
while (!executable.atEnd()) {
|
||||
hash.addData(executable.read(4096));
|
||||
}
|
||||
ret.append(Pair("exe_md5", hash.result().toHex()));
|
||||
}
|
||||
|
||||
// Get the OS version
|
||||
#if defined(Q_OS_MAC)
|
||||
ret.append(Pair("os", "mac"));
|
||||
ret.append(Pair("os_version", QString::number(QSysInfo::MacintoshVersion)));
|
||||
#elif defined(Q_OS_WIN)
|
||||
ret.append(Pair("os", "win"));
|
||||
ret.append(Pair("os_version", QString::number(QSysInfo::WindowsVersion)));
|
||||
#else
|
||||
ret.append(Pair("os", "linux"));
|
||||
|
||||
QFile lsb_release("/etc/lsb-release");
|
||||
if (lsb_release.open(QIODevice::ReadOnly)) {
|
||||
ret.append(Pair("os_version",
|
||||
QString::fromUtf8(lsb_release.readAll()).simplified()));
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -18,15 +18,10 @@
|
|||
#ifndef CRASHREPORTING_H
|
||||
#define CRASHREPORTING_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariantMap>
|
||||
#include <QString>
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
class QFile;
|
||||
class QNetworkAccessManager;
|
||||
class QProgressDialog;
|
||||
|
||||
namespace google_breakpad {
|
||||
class ExceptionHandler;
|
||||
}
|
||||
|
@ -39,6 +34,8 @@ public:
|
|||
CrashReporting();
|
||||
~CrashReporting();
|
||||
|
||||
static const char* kSendCrashReportOption;
|
||||
|
||||
// If the commandline contains the --send-crash-report option, the user will
|
||||
// be prompted to send the crash report and the function will return true
|
||||
// (in which case the caller should exit the program). Otherwise the function
|
||||
|
@ -49,52 +46,18 @@ public:
|
|||
// --send-crash-report when a crash happens.
|
||||
static void SetApplicationPath(const QString& path);
|
||||
|
||||
private:
|
||||
// Prints the message to stdout without using libc.
|
||||
static void Print(const char* message);
|
||||
|
||||
// Breakpad callback.
|
||||
static bool Handler(const char* dump_path,
|
||||
const char* minidump_id,
|
||||
void* context,
|
||||
bool succeeded);
|
||||
static const char* application_path() { return sPath; }
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(CrashReporting);
|
||||
|
||||
static const char* kSendCrashReportOption;
|
||||
static char* sPath;
|
||||
|
||||
boost::scoped_ptr<google_breakpad::ExceptionHandler> handler_;
|
||||
};
|
||||
|
||||
|
||||
// Asks the user if he wants to send a crash report, and displays a progress
|
||||
// dialog while uploading it if he does.
|
||||
class CrashSender : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CrashSender(const QString& path);
|
||||
|
||||
// Returns false if the user doesn't want to send the crash report (caller
|
||||
// should exit), or true if he does (caller should start the Qt event loop).
|
||||
bool Start();
|
||||
|
||||
private slots:
|
||||
void RedirectFinished();
|
||||
void UploadProgress(qint64 bytes, qint64 total);
|
||||
|
||||
QList<QPair<QString, QString> > ClientInfo() const;
|
||||
|
||||
private:
|
||||
static const char* kUploadURL;
|
||||
|
||||
QNetworkAccessManager* network_;
|
||||
|
||||
QString path_;
|
||||
QFile* file_;
|
||||
QProgressDialog* progress_;
|
||||
};
|
||||
|
||||
#endif // CRASHREPORTING_H
|
||||
|
|
|
@ -28,11 +28,3 @@ CrashReporting::CrashReporting() {
|
|||
|
||||
CrashReporting::~CrashReporting() {
|
||||
}
|
||||
|
||||
bool CrashReporting::Handler(const char* dump_path,
|
||||
const char* minidump_id,
|
||||
void* context,
|
||||
bool succeeded) {
|
||||
}
|
||||
|
||||
#endif // HAVE_BREAKPAD
|
||||
|
|
|
@ -24,33 +24,48 @@
|
|||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
namespace {
|
||||
|
||||
bool Handler(const google_breakpad::MinidumpDescriptor& dump,
|
||||
void* context, bool succeeded) {
|
||||
CrashReporting::Print(
|
||||
"Clementine has crashed! A crash report has been saved to:\n ");
|
||||
CrashReporting::Print(dump.path());
|
||||
CrashReporting::Print(
|
||||
"\n\nPlease send this to the developers so they can fix the problem:\n"
|
||||
" http://code.google.com/p/clementine-player/issues/entry\n\n");
|
||||
|
||||
if (CrashReporting::application_path()) {
|
||||
// We know the path to clementine, so exec it again to prompt the user to
|
||||
// upload the report.
|
||||
const char* argv[] = {
|
||||
CrashReporting::application_path(),
|
||||
CrashReporting::kSendCrashReportOption,
|
||||
dump.path(),
|
||||
NULL
|
||||
};
|
||||
|
||||
sys_execv(CrashReporting::application_path(), argv);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
CrashReporting::CrashReporting()
|
||||
: handler_(new google_breakpad::ExceptionHandler(
|
||||
QDir::tempPath().toLocal8Bit().constData(), NULL,
|
||||
CrashReporting::Handler, this, true)) {
|
||||
google_breakpad::MinidumpDescriptor(
|
||||
QDir::tempPath().toLocal8Bit().constData()),
|
||||
NULL, Handler, this, true, -1)) {
|
||||
}
|
||||
|
||||
CrashReporting::~CrashReporting() {
|
||||
}
|
||||
|
||||
bool CrashReporting::Handler(const char* dump_path,
|
||||
const char* minidump_id,
|
||||
void* context,
|
||||
bool succeeded) {
|
||||
Print("Clementine has crashed! A crash report has been saved to:\n ");
|
||||
Print(dump_path);
|
||||
Print("/");
|
||||
Print(minidump_id);
|
||||
Print("\n\nPlease send this to the developers so they can fix the problem:\n"
|
||||
" http://code.google.com/p/clementine-player/issues/entry\n\n");
|
||||
|
||||
if (sPath) {
|
||||
// We know the path to clementine, so exec it again to prompt the user to
|
||||
// upload the report.
|
||||
const char* argv[] = {sPath, kSendCrashReportOption, dump_path, minidump_id, NULL};
|
||||
|
||||
sys_execv(sPath, argv);
|
||||
void CrashReporting::Print(const char* message) {
|
||||
if (message) {
|
||||
sys_write(1, message, strlen(message));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "crashsender.h"
|
||||
#include "version.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QCryptographicHash>
|
||||
#include <QFile>
|
||||
#include <QHttpMultiPart>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QProgressDialog>
|
||||
#include <QSysInfo>
|
||||
#include <QUrl>
|
||||
|
||||
|
||||
const char* CrashSender::kUploadURL =
|
||||
"http://" CRASHREPORTING_HOSTNAME "/getuploadurl";
|
||||
|
||||
CrashSender::CrashSender(const QString& path)
|
||||
: network_(new QNetworkAccessManager(this)),
|
||||
path_(path),
|
||||
file_(new QFile(path_, this)),
|
||||
progress_(NULL) {
|
||||
}
|
||||
|
||||
bool CrashSender::Start() {
|
||||
if (!file_->open(QIODevice::ReadOnly)) {
|
||||
qLog(Warning) << "Failed to open crash report" << path_;
|
||||
return false;
|
||||
}
|
||||
|
||||
// No tr() here.
|
||||
QMessageBox prompt(QMessageBox::Warning, "Clementine has crashed!",
|
||||
"Clementine has crashed! A crash report has been created and saved to "
|
||||
"disk. With your permission it can be automatically sent to our server "
|
||||
"so the developers can find out what happened.");
|
||||
prompt.addButton("Don't send", QMessageBox::RejectRole);
|
||||
prompt.addButton("Send crash report", QMessageBox::AcceptRole);
|
||||
if (prompt.exec() == QDialog::Rejected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
progress_ = new QProgressDialog("Uploading crash report", "Cancel", 0, 0);
|
||||
progress_->show();
|
||||
|
||||
// We'll get a redirect first, so don't start POSTing data yet.
|
||||
QNetworkReply* reply = network_->get(QNetworkRequest(QUrl(kUploadURL)));
|
||||
connect(reply, SIGNAL(finished()), SLOT(RedirectFinished()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CrashSender::RedirectFinished() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
if (!reply) {
|
||||
progress_->close();
|
||||
return;
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
QUrl url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
|
||||
if (!url.isValid()) {
|
||||
printf("Response didn't have a redirection target - HTTP %d\n",
|
||||
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
progress_->close();
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Uploading crash report to %s\n", url.toEncoded().constData());
|
||||
QNetworkRequest req(url);
|
||||
|
||||
// Create the HTTP part for the crash report file
|
||||
QHttpPart part;
|
||||
part.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
"form-data; name=\"data\"; filename=\"data.dmp\"");
|
||||
part.setBodyDevice(file_);
|
||||
|
||||
QHttpMultiPart* multi_part = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
multi_part->append(part);
|
||||
|
||||
// Get some information about the thing that crashed and add that to the
|
||||
// request as well.
|
||||
QList<ClientInfoPair> info(ClientInfo());
|
||||
foreach (const ClientInfoPair& pair, info) {
|
||||
QHttpPart part;
|
||||
part.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
QString("form-data; name=\"" + pair.first + "\""));
|
||||
part.setBody(pair.second.toUtf8());
|
||||
multi_part->append(part);
|
||||
}
|
||||
|
||||
// Start uploading the crash report
|
||||
reply = network_->post(req, multi_part);
|
||||
|
||||
connect(reply, SIGNAL(uploadProgress(qint64,qint64)), SLOT(UploadProgress(qint64,qint64)));
|
||||
connect(reply, SIGNAL(finished()), SLOT(UploadFinished()));
|
||||
}
|
||||
|
||||
void CrashSender::UploadProgress(qint64 bytes, qint64 total) {
|
||||
printf("Uploaded %lld of %lld bytes\n", bytes, total);
|
||||
progress_->setValue(bytes);
|
||||
}
|
||||
|
||||
void CrashSender::UploadFinished() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
Q_ASSERT(reply);
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
int code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (code > 400) {
|
||||
printf("Upload caused HTTP %d: %s\n", code, reply->readAll().constData());
|
||||
}
|
||||
|
||||
progress_->close();
|
||||
}
|
||||
|
||||
QList<CrashSender::ClientInfoPair> CrashSender::ClientInfo() const {
|
||||
QList<ClientInfoPair> ret;
|
||||
|
||||
ret.append(ClientInfoPair("version", CLEMENTINE_VERSION_DISPLAY));
|
||||
ret.append(ClientInfoPair("qt_version", qVersion()));
|
||||
|
||||
// Hash the binary
|
||||
QFile executable(QCoreApplication::applicationFilePath());
|
||||
if (executable.open(QIODevice::ReadOnly)) {
|
||||
QCryptographicHash hash(QCryptographicHash::Md5);
|
||||
while (!executable.atEnd()) {
|
||||
hash.addData(executable.read(4096));
|
||||
}
|
||||
ret.append(ClientInfoPair("exe_md5", hash.result().toHex()));
|
||||
}
|
||||
|
||||
// Get the OS version
|
||||
#if defined(Q_OS_MAC)
|
||||
ret.append(ClientInfoPair("os", "mac"));
|
||||
ret.append(ClientInfoPair("os_version", QString::number(QSysInfo::MacintoshVersion)));
|
||||
#elif defined(Q_OS_WIN)
|
||||
ret.append(ClientInfoPair("os", "win"));
|
||||
ret.append(ClientInfoPair("os_version", QString::number(QSysInfo::WindowsVersion)));
|
||||
#else
|
||||
ret.append(ClientInfoPair("os", "linux"));
|
||||
|
||||
QFile lsb_release("/etc/lsb-release");
|
||||
if (lsb_release.open(QIODevice::ReadOnly)) {
|
||||
ret.append(ClientInfoPair("os_version",
|
||||
QString::fromUtf8(lsb_release.readAll()).simplified()));
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CRASHSENDER_H
|
||||
#define CRASHSENDER_H
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
|
||||
class QFile;
|
||||
class QNetworkAccessManager;
|
||||
class QProgressDialog;
|
||||
|
||||
|
||||
// Asks the user if he wants to send a crash report, and displays a progress
|
||||
// dialog while uploading it if he does.
|
||||
class CrashSender : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CrashSender(const QString& path);
|
||||
|
||||
// Returns false if the user doesn't want to send the crash report (caller
|
||||
// should exit), or true if he does (caller should start the Qt event loop).
|
||||
bool Start();
|
||||
|
||||
private:
|
||||
typedef QPair<QString, QString> ClientInfoPair;
|
||||
|
||||
private slots:
|
||||
void RedirectFinished();
|
||||
void UploadProgress(qint64 bytes, qint64 total);
|
||||
void UploadFinished();
|
||||
|
||||
QList<ClientInfoPair> ClientInfo() const;
|
||||
|
||||
private:
|
||||
static const char* kUploadURL;
|
||||
|
||||
QNetworkAccessManager* network_;
|
||||
|
||||
QString path_;
|
||||
QFile* file_;
|
||||
QProgressDialog* progress_;
|
||||
};
|
||||
|
||||
#endif // CRASHSENDER_H
|
Loading…
Reference in New Issue