/* * Copyright (c) 2013 Mark Liversedge (liversedge@gmail.com) * * 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 2 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, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "GcCrashDialog.h" #include "Settings.h" #include "GcUpgrade.h" #include #include #include #include #include "RideMetric.h" #include #include #define GCC_VERSION QString("%1.%2.%3").arg(__GNUC__).arg(__GNUC_MINOR__).arg(__GNUC_PATCHLEVEL__) #ifndef Q_OS_MAC #include "VideoWindow.h" #else #include "QtMacVideoWindow.h" #endif #ifdef GC_HAVE_QWTPLOT3D #include "qwt3d_global.h" #endif #ifdef GC_HAVE_ICAL #include "ICalendar.h" #endif #ifdef GC_HAVE_D2XX #include "D2XX.h" #endif #ifdef GC_HAVE_SRMIO #include "srmio.h" #endif #ifdef GC_HAVE_KQOAUTH #include "kqoauthmanager.h" #endif #ifdef GC_HAVE_WFAPI #include "WFApi.h" #endif #ifdef GC_HAVE_SAMPLERATE #include #endif GcCrashDialog::GcCrashDialog(QDir homeDir) : QDialog(NULL, Qt::Dialog), home(homeDir) { setAttribute(Qt::WA_DeleteOnClose, true); // caller must delete me, once they've extracted the name setWindowTitle(QString(tr("%1 Crash Recovery").arg(home.root().dirName()))); //check if the problem occured when adding to RideCache - by checking if there are files in /tmpActivities newFilesInQuarantine = false; files = home.tmpActivities().entryList(QDir::Files); if (!files.empty()) { // yes there are files which caused problems - so quarantine them newFilesInQuarantine = true; foreach (QString file, files) { // just try to move by renaming QString source = home.tmpActivities().canonicalPath() + "/" + file; // create a unique file name in case of multipe crashes with the same file QString target = home.quarantine().canonicalPath() + "/" + file; QFile s(source); QFile t(target); if (t.exists()) { // remove target first if target already exists in /quarantine - from previous crash - only keep the last crashed version t.remove(); } s.rename(target); } } QVBoxLayout *layout = new QVBoxLayout(this); QHBoxLayout *toprow = new QHBoxLayout; QPushButton *critical = new QPushButton(style()->standardIcon(QStyle::SP_MessageBoxCritical), "", this); critical->setFixedSize(128,128); critical->setFlat(true); critical->setIconSize(QSize(120,120)); critical->setAutoFillBackground(false); critical->setFocusPolicy(Qt::NoFocus); QLabel *header = new QLabel(this); header->setWordWrap(true); header->setTextFormat(Qt::RichText); header->setText(tr("GoldenCheetah appears to have PREVIOUSLY crashed for this athlete. " "

The report below gives some diagnostic information " "that will be useful to the developers. Feel free to post this with a " "short description of what was occurring when the crash happened to the " "" "GoldenCheetah forums
" "
We respect privacy - this log does NOT contain ids, passwords or personal information.

" "
When this dialog is closed the athlete will be opened.
")); toprow->addWidget(critical); toprow->addWidget(header); layout->addLayout(toprow); report = new QWebView(this); report->setContentsMargins(0,0,0,0); report->page()->view()->setContentsMargins(0,0,0,0); report->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); report->setAcceptDrops(false); QFont defaultFont; // mainwindow sets up the defaults.. we need to apply report->settings()->setFontSize(QWebSettings::DefaultFontSize, defaultFont.pointSize()+1); report->settings()->setFontFamily(QWebSettings::StandardFont, defaultFont.family()); layout->addWidget(report); QHBoxLayout *lastRow = new QHBoxLayout; QPushButton *saveAsButton = new QPushButton(tr("Save Crash Report..."), this); connect(saveAsButton, SIGNAL(clicked()), this, SLOT(saveAs())); QPushButton *OKButton = new QPushButton(tr("Close"), this); connect(OKButton, SIGNAL(clicked()), this, SLOT(accept())); lastRow->addWidget(saveAsButton); lastRow->addStretch(); lastRow->addWidget(OKButton); layout->addLayout(lastRow); setHTML(); } QString GcCrashDialog::versionHTML() { // -- OS ---- QString os = ""; #ifdef Q_OS_LINUX os = "Linux"; #endif #ifdef WIN32 os = "Win"; #endif #ifdef Q_OS_MAC os = QString("Mac OS X 10.%1").arg(QSysInfo::MacintoshVersion - 2); if (QSysInfo::MacintoshVersion == QSysInfo::MV_SNOWLEOPARD) os += " Snow Leopard"; else if (QSysInfo::MacintoshVersion == QSysInfo::MV_LION) os += " Lion"; else if (QSysInfo::MacintoshVersion == 10) os += " Mountain Lion"; else if (QSysInfo::MacintoshVersion == 11) os += " Mavericks"; else if (QSysInfo::MacintoshVersion == 12) os += " Yosemite"; #endif // -- SCHEMA VERSION ---- QString schemaVersion = QString("%1").arg(DBSchemaVersion); // -- SRMIO ---- QString srmio = "none"; #ifdef GC_HAVE_SRMIO #ifdef SRMIO_VERSION srmio = QString("%1 %2").arg(SRMIO_VERSION).arg(srmio_commit); #else srmio = "yes"; #endif #endif // -- D2XX ---- QString d2xx = "none"; #ifdef GC_HAVE_D2XX d2xx = "yes"; #endif // -- LIBOAUTH ---- QString oauth = "none"; #ifdef GC_HAVE_KQOAUTH #ifdef KQOAUTH_VERSION oauth = KQOAUTH_VERSION; #else oauth = "yes"; #endif #endif // -- QWTPLOT3D ---- QString qwtplot3d = "none"; #ifdef GC_HAVE_QWTPLOT3D qwtplot3d = QString::number(QWT3D_MAJOR_VERSION) + "." + QString::number(QWT3D_MINOR_VERSION) + "." + QString::number(QWT3D_PATCH_VERSION); #endif // -- KML ---- QString kml = "none"; #ifdef GC_HAVE_KML kml = "yes"; #endif // -- ICAL ---- QString ical = "none"; #ifdef GC_HAVE_ICAL ical = ICAL_VERSION; #endif // -- USBXPRESS ---- QString usbxpress = "none"; #ifdef GC_HAVE_USBXPRESS usbxpress = "yes"; #endif // -- LIBUSB ---- QString libusb = "none"; #ifdef GC_HAVE_LIBUSB libusb = "yes"; #endif // -- VLC ---- QString vlc = "none"; #ifdef GC_HAVE_VLC vlc = "yes"; #endif #ifdef GC_HAVE_WFAPI QString wfapi = WFApi::getInstance()->apiVersion(); #else QString wfapi = QString("none"); #endif #ifdef GC_HAVE_SAMPLERATE QString src = QString(src_get_version()).mid(14,6); #else QString src = "none"; #endif const RideMetricFactory &factory = RideMetricFactory::instance(); QString gc_version = tr( "

Build date: %1 %2" "
Build id: %3" "
Version: %4" "
DB Schema: %5" "
Metrics: %7" "
OS: %6" "
") .arg(__DATE__) .arg(__TIME__) .arg(GcUpgrade::version()) #ifdef GC_VERSION .arg(GC_VERSION) #else .arg("(developer build)") #endif .arg(schemaVersion) .arg(os) .arg(factory.metricCount()); QString lib_version = tr( "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "
QT%1
QWT%2
GCC%3
SRMIO%4
OAUTH%5
D2XX%6
QWTPLOT3D%7
KML%8
ICAL%9
USBXPRESS%10
LIBUSB%11
Wahoo API%12
VLC%13
VIDEO%14
SAMPLERATE%15
SSL%16
" ) .arg(QT_VERSION_STR) .arg(QWT_VERSION_STR) .arg(GCC_VERSION) .arg(srmio) .arg(oauth) .arg(d2xx) .arg(qwtplot3d) .arg(kml) .arg(ical) .arg(usbxpress) .arg(libusb) .arg(wfapi) .arg(vlc) #ifdef GC_VIDEO_NONE .arg("none") #elif defined GC_VIDEO_QUICKTIME .arg("quicktime") #elif defined GC_VIDEO_QT5 .arg("qt5") #elif defined GC_VIDEO_VLC .arg("vlc") #endif .arg(src) .arg(QSslSocket::supportsSsl() ? "yes" : "none") ; QString versionText = QString("

" + gc_version + lib_version + "
"); return versionText; } void GcCrashDialog::setHTML() { QString text; // the cyclist... text += QString("

Cyclist: \"%1\"


").arg(home.root().dirName()); // version info text += "

Version Info

"; text += versionHTML(); // quarantine info if (newFilesInQuarantine) { text += "

Quarantine Info

"; text += "The following file(s) created by 'Import from file' or 'Download from device' have been moved to subdirectory '/quarantine' " "since they most likely caused the crash - e.g. because of corrupt data - during the creation of the GoldenCheetah metric cache. " "Please check both the GoldenCheetah .JSON files and the associated source files." ; text += "
"; foreach (QString file, files) { text += ""; } text += "
" + file + "
"; } // get the complete file list which are already in /quarantine QStringList quarantine = home.quarantine().entryList(QDir::Files); if (!quarantine.empty()) { text += "

All Quarantined Files

"; text += "
"; foreach (QString file, quarantine) { text += ""; } text += "
" + file + "
"; } // metric log... text += "

Metric Log

"; text += "
"; QFile metriclog(home.logs().canonicalPath() + "/" + "metric.log"); if (metriclog.open(QIODevice::ReadOnly)) { // read in line by line and add to diag file QTextStream in(&metriclog); while (!in.atEnd()) { QString lines = in.readLine(); foreach(QString line, lines.split('\r')) { text += ""; } } metriclog.close(); } text += "
" + line + "
"; // files... text += "

Athlete Directory - Activities

"; text += "
"; foreach(QString file, home.activities().entryList(QDir::NoDotAndDotDot, QDir::Time)) { text += QString("") .arg(file) .arg(QFileInfo(home.activities().canonicalPath() + "/" + file).lastModified().toString()); } text += "
%1%2
"; text += "

Athlete Directory - Cache

"; text += "
"; foreach(QString file, home.cache().entryList(QDir::NoDotAndDotDot, QDir::Time)) { text += QString("") .arg(file) .arg(QFileInfo(home.cache().canonicalPath() + "/" + file).lastModified().toString()); } text += "
%1%2
"; text += "

Athlete Directory - Config

"; text += "
"; foreach(QString file, home.config().entryList(QDir::NoDotAndDotDot, QDir::Time)) { text += QString("") .arg(file) .arg(QFileInfo(home.config().canonicalPath() + "/" + file).lastModified().toString()); } text += "
%1%2
"; text += "

Athlete Directory - Workouts

"; text += "
"; foreach(QString file, home.workouts().entryList(QDir::NoDotAndDotDot, QDir::Time)) { text += QString("") .arg(file) .arg(QFileInfo(home.workouts().canonicalPath() + "/" + file).lastModified().toString()); } text += "
%1%2
"; text += "

Athlete Directory - Imports

"; text += "
"; foreach(QString file, home.imports().entryList(QDir::NoDotAndDotDot, QDir::Time)) { text += QString("") .arg(file) .arg(QFileInfo(home.imports().canonicalPath() + "/" + file).lastModified().toString()); } text += "
%1%2
"; text += "

Athlete Directory - Downloads

"; text += "
"; foreach(QString file, home.downloads().entryList(QDir::NoDotAndDotDot, QDir::Time)) { text += QString("") .arg(file) .arg(QFileInfo(home.downloads().canonicalPath() + "/" + file).lastModified().toString()); } text += "
%1%2
"; // settings... text += "

All Settings

"; text += "
"; foreach(QString key, appsettings->allKeys()) { // RESPECT PRIVACY // we do not disclose user names and passwords or key ids if (key.endsWith("/user") || key.endsWith("/pass") || key.endsWith("/key") || key.endsWith("_token") || key.endsWith("_secret") || key.endsWith("googlecalid")) continue; // we do not disclose personally identifiable information if (key.endsWith("dob") || key.endsWith("weight") || key.endsWith("sex") || key.endsWith("bio") || key.endsWith("height") || key.endsWith("nickname")) continue; if (key.startsWith("") .arg(key) .arg(appsettings->cvalue(home.root().dirName(), key).toString().leftJustified(60, ' ', true)); } else { text += QString("") .arg(key) .arg(appsettings->value(NULL, key).toString().leftJustified(60, ' ', true)); } } text += "
%1" "%2
%1" "%2
"; report->page()->mainFrame()->setHtml(text); } void GcCrashDialog::saveAs() { QString fileName = QFileDialog::getSaveFileName( this, tr("Save Diagnostics"), QDir::homePath(), tr("Text File (*.txt)")); // now write to it QFile file(fileName); file.resize(0); QTextStream out(&file); out.setCodec("UTF-8"); if (file.open(QIODevice::WriteOnly)) { // write the texts out << report->page()->mainFrame()->toPlainText(); } file.close(); }