/* UpdaterWindow * * Copyright (c) 2011-2016 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the Linux * distribution for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SleepLib/profiles.h" #include #include #include "UpdaterWindow.h" #include "ui_UpdaterWindow.h" #include "version.h" #include "mainwindow.h" #include "common_gui.h" extern MainWindow *mainwin; UpdaterWindow::UpdaterWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::UpdaterWindow) { ui->setupUi(this); QDesktopWidget *desktop = QApplication::desktop(); int screenWidth = desktop->width(); // get width of screen int screenHeight = desktop->height(); // get height of screen QSize windowSize = size(); // size of our application window int width = windowSize.width(); int height = windowSize.height(); // little computations int x = (screenWidth - width) / 2; int y = (screenHeight - height) / 2; y -= 50; // move window to desired coordinates move(x, y); requestmode = RM_None; netmanager = new QNetworkAccessManager(this); update = nullptr; ui->stackedWidget->setCurrentIndex(0); } UpdaterWindow::~UpdaterWindow() { disconnect(netmanager, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinished(QNetworkReply *))); delete ui; } void UpdaterWindow::checkForUpdates() { QString filename = QApplication::applicationDirPath() + "/Updates.xml"; // Check updates.xml file if it's still recent.. if (QFile::exists(filename)) { QFileInfo fi(filename); QDateTime created = fi.created(); int age = created.secsTo(QDateTime::currentDateTime()); if (age < 0) { // 7200) { QFile file(filename); file.open(QFile::ReadOnly); ParseUpdateXML(&file); file.close(); return; } } mainwin->Notify(tr("Checking for SleepyHead Updates")); // language code? update_url = QUrl(QString("http://sourceforge.net/projects/sleepyhead/files/AutoUpdate/%1/Updates.xml/download").arg(PlatformString)); downloadUpdateXML(); } void UpdaterWindow::downloadUpdateXML() { requestmode = RM_CheckUpdates; QNetworkRequest req = QNetworkRequest(update_url); req.setRawHeader("User-Agent", "Wget/1.12 (linux-gnu)"); reply = netmanager->get(req); ui->plainTextEdit->appendPlainText(tr("Requesting ") + update_url.toString()); // netmanager->connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this,SLOT(downloadProgress(qint64, qint64))); connect(netmanager, SIGNAL(finished(QNetworkReply *)), this, SLOT(updateFinished(QNetworkReply *))); dltime.start(); } void UpdaterWindow::updateFinished(QNetworkReply *reply) { if (reply->error() != QNetworkReply::NoError) { qDebug() << "Update Check Error: "+reply->errorString(); // netmanager->disconnect(reply, // SIGNAL(downloadProgress(qint64, qint64)), this, // SLOT(downloadProgress(qint64, qint64))); disconnect(netmanager, SIGNAL(finished(QNetworkReply *)), this, SLOT(updateFinished(QNetworkReply *))); mainwin->Notify(tr("SleepyHead Updates are currently unvailable for this platform"),tr("SleepyHead Updates")); } else { QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (!redirectUrl.isEmpty() && (redirectUrl != reply->url())) { update_url = redirectUrl; reply->deleteLater(); QTimer::singleShot(100, this, SLOT(downloadUpdateXML())); return; } // netmanager->disconnect(reply, // SIGNAL(downloadProgress(qint64, qint64)), this, // SLOT(downloadProgress(qint64, qint64))); disconnect(netmanager, SIGNAL(finished(QNetworkReply *)), this, SLOT(updateFinished(QNetworkReply *))); ui->plainTextEdit->appendPlainText(tr("%1 bytes received").arg(reply->size())); QString filename = QApplication::applicationDirPath() + "/Updates.xml"; qDebug() << filename; QFile file(filename); file.open(QFile::WriteOnly); file.write(reply->readAll()); file.close(); file.open(QFile::ReadOnly); //QTextStream ts(&file); ParseUpdatesXML(&file); file.close(); reply->deleteLater(); } } void UpdaterWindow::dataReceived() { QString rs = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString(); if (rs != "200") { return; } QByteArray read = reply->read(reply->bytesAvailable()); qDebug() << "Received" << read.size() << "bytes"; file.write(read); } void UpdaterWindow::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { QString rs = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString(); if (rs != "200") { return; } if (ui->tableWidget->rowCount() > 0) { double f = (double(bytesReceived) / double(bytesTotal)) * 100.0; QProgressBar *bar = qobject_cast(ui->tableWidget->cellWidget(current_row, 3)); if (bar) { bar->setValue(f); } ui->tableWidget->item(current_row, 2)->setText(QString::number(bytesTotal / 1048576.0, 'f', 3) + "MB"); } //ui->progressBar->setValue(f); // int elapsed=dltime.elapsed(); } void UpdaterWindow::requestFile() { if (!update) { return; } QProgressBar *bar = qobject_cast(ui->tableWidget->cellWidget(current_row, 3)); QString style = "QProgressBar{\ border: 1px solid gray;\ border-radius: 3px;\ text-align: center;\ text-decoration: bold;\ color: yellow;\ }\ QProgressBar::chunk {\ background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 \"light green\", stop: 1 green);\ width: 10px;\ margin: 0px;\ }"; if (bar) { bar->setStyleSheet(style); } QString filename = update->filename; ui->plainTextEdit->appendPlainText(tr("Requesting ") + update->url); requestmode = RM_GetFile; QString path = QApplication::applicationDirPath() + "/Download"; QDir().mkpath(path); path += "/" + filename; ui->plainTextEdit->appendPlainText(tr("Saving as ") + path); file.setFileName(path); file.open(QFile::WriteOnly); dltime.start(); QNetworkRequest req = QNetworkRequest(QUrl(update->url)); req.setRawHeader("User-Agent", "Wget/1.12 (linux-gnu)"); reply = netmanager->get(req); connect(reply, SIGNAL(readyRead()), this, SLOT(dataReceived())); connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); } int checkVersionStatus(QString statusstr) { bool ok; // because Qt Install Framework is dumb and doesn't handle beta/release strings in version numbers, // so we store them numerically instead int v =statusstr.toInt(&ok); if (ok) { return v; } if (statusstr.compare("testing", Qt::CaseInsensitive) == 0) return 0; else if (statusstr.compare("beta", Qt::CaseInsensitive) == 0) return 1; else if (statusstr.compare("rc", Qt::CaseInsensitive) == 0) return 2; else if (statusstr.compare("r", Qt::CaseInsensitive) == 0) return 3; return 0; } struct VersionStruct { short major; short minor; short revision; short status; short build; }; VersionStruct parseVersion(QString versionstring) { static VersionStruct version; QStringList parts = versionstring.split("."); bool ok, dodgy = false; if (parts.size() < 3) dodgy = true; short major = parts[0].toInt(&ok); if (!ok) dodgy = true; short minor = parts[1].toInt(&ok); if (!ok) dodgy = true; QStringList patchver = parts[2].split("-"); if (patchver.size() < 3) dodgy = true; short rev = patchver[0].toInt(&ok); if (!ok) dodgy = true; short build = patchver[2].toInt(&ok); if (!ok) dodgy = true; int status = checkVersionStatus(patchver[1]); if (!dodgy) { version.major = major; version.minor = minor; version.revision = rev; version.status = status; version.build = build; } return version; } // Compare supplied version string with current version // < 0 = this one is newer or version supplied is dodgy, 0 = same, and > 0 there is a newer version int compareVersion(QString version) { // v1.0.0-beta-2 QStringList parts = version.split("."); bool ok; if (parts.size() < 3) { // dodgy version string supplied. return -1; } int major = parts[0].toInt(&ok); if (!ok) return -1; int minor = parts[1].toInt(&ok); if (!ok) return -1; if (major > major_version) { return 1; } else if (major < major_version) { return -1; } if (minor > minor_version) { return 1; } else if (minor < minor_version) { return -1; } int build_index = 1; int build = 0; int status = 0; QStringList patchver = parts[2].split("-"); if (patchver.size() >= 3) { build_index = 2; status = checkVersionStatus(patchver[1]); } else if (patchver.size() < 2) { return -1; // dodgy version string supplied. } int rev = patchver[0].toInt(&ok); if (!ok) return -1; if (rev > revision_number) { return 1; } else if (rev < revision_number) { return -1; } build = patchver[build_index].toInt(&ok); if (!ok) return -1; int rstatus = checkVersionStatus(ReleaseStatus); if (patchver.size() == 3) { // read it if it's actually present. } if (status > rstatus) { return 1; } else if (status < rstatus) { return -1; } if (build > build_number) { return 1; } else if (build < build_number) { return -1; } // Versions match return 0; } const QString UPDATE_SleepyHead = "com.jedimark.sleepyhead"; const QString UPDATE_QT = "com.jedimark.sleepyhead.qtlibraries"; const QString UPDATE_Translations = "com.jedimark.sleepyhead.translations"; bool SpawnApp(QString apppath, QStringList args = QStringList()) { #ifdef Q_OS_MAC // In Mac OS the full path of aplication binary is: // /myApp.app/Contents/MacOS/myApp QStringList arglist; arglist << "-n"; arglist << apppath; arglist.append(args); return QProcess::startDetached("/usr/bin/open", arglist); #else return QProcess::startDetached(apppath, args); #endif } void StartMaintenanceTool() { QString mt_path = QApplication::applicationDirPath()+"/MaintenanceTool.exe"; SpawnApp(mt_path); #ifdef Q_OS_WINDOWS #endif } //New, Qt Installer framework version void UpdaterWindow::ParseUpdatesXML(QIODevice *dev) { if (updatesparser.read(dev)) { qDebug() << " XML update structure parsed cleanly"; QHash CurrentVersion; CurrentVersion[UPDATE_SleepyHead] = VersionString; CurrentVersion[UPDATE_QT] = QT_VERSION_STR; CurrentVersion[UPDATE_Translations] = VersionString; QList updateList; QHash::iterator it; for (it = updatesparser.packages.begin(); it!=updatesparser.packages.end(); ++it) { const PackageUpdate & update = it.value(); if (it.key() == UPDATE_SleepyHead) { if (compareVersion(update.versionString)>0) { updateList.push_back(update); } } else if (it.key() == UPDATE_QT) { bool ok; QStringList chunks = update.versionString.split("."); int major = chunks[0].toInt(&ok); int minor = chunks[1].toInt(&ok); int patch = chunks[2].toInt(&ok); if (QT_VERSION_CHECK(major, minor, patch) > QT_VERSION) { updateList.push_back(update); } } else if (it.key() == UPDATE_Translations) { if (compareVersion(update.versionString)>0) { updateList.push_back(update); } } } if (updateList.size()==0) { mainwin->Notify(tr("No updates were found for your platform."), tr("SleepyHead Updates"), 5000); PREF[STR_GEN_UpdatesLastChecked] = QDateTime::currentDateTime(); close(); return; } else { if (QMessageBox::question(mainwin, tr("SleepyHead Updates"), tr("New SleepyHead Updates are avilable:")+"\n\n"+ tr("Would you like to download and install them now?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { StartMaintenanceTool(); QApplication::instance()->quit(); } } } else { qDebug() << "Couldn't parse Updates.xml file"; } } // Old void UpdaterWindow::ParseUpdateXML(QIODevice *dev) { QXmlInputSource src(dev); QXmlSimpleReader reader; reader.setContentHandler(&updateparser); UpdateStatus AcceptUpdates = PREF[STR_PREF_AllowEarlyUpdates].toBool() ? UPDATE_TESTING : UPDATE_BETA; if (reader.parse(src)) { ui->plainTextEdit->appendPlainText(tr("XML update structure parsed cleanly")); QStringList versions; for (QHash::iterator it = updateparser.releases.begin(); it != updateparser.releases.end(); ++it) { versions.push_back(it.key()); } // Um... not optimal. qSort(versions); QString platform = PlatformString.toLower(); release = nullptr; // Find the highest version number available for this platform for (int i = versions.size() - 1; i >= 0; i--) { QString verstr = versions[i]; release = &updateparser.releases[verstr]; if (release->updates.contains(platform) // Valid Release? && (release->status >= AcceptUpdates) && (release->version >= VersionString)) { break; } else { release = nullptr; } } if (!release) { mainwin->Notify(tr("No updates were found for your platform."), tr("SleepyHead Updates"), 5000); PREF[STR_GEN_UpdatesLastChecked] = QDateTime::currentDateTime(); close(); return; } qDebug() << "Version" << release->version << "has release section" << platform; QString latestapp = "", latestqt = ""; updates.clear(); Update *upd = nullptr; Update *upq = nullptr; for (int i = 0; i < release->updates[platform].size(); i++) { update = &release->updates[platform][i]; if (update->type == "qtlibs") { qDebug() << "QT Version" << update->version; if (update->version > latestqt) { if (update->status >= AcceptUpdates) { latestqt = update->version; upq = update; } } } else if (update->type == "application") { qDebug() << "Application Version" << update->version; if (update->version > latestapp) { if (update->status >= AcceptUpdates) { latestapp = update->version; upd = update; } } } } if (!upq && !upd) { mainwin->Notify(tr("No new updates were found for your platform."), tr("SleepyHead Updates"), 5000); PREF[STR_GEN_UpdatesLastChecked] = QDateTime::currentDateTime(); close(); return; } if (upq && (upq->version > QT_VERSION_STR)) { updates.push_back(upq); } if (upd && upd->version > VersionString) { updates.push_back(upd); } if (updates.size() > 0) { QString html = "

" + tr("SleepyHead v%1, codename \"%2\"").arg(release->version). arg(release->codename) + "

" + release->notes[""] + "

"; html += platform.left(1).toUpper() + platform.mid(1); html += " " + tr("platform notes") + "

" + release->notes[platform] + "

"; ui->webView->setHtml(html); QString info; if (compareVersion(release->version)) { ui->Title->setText("" + tr("A new version of SleepyHead is available!") + ""); info = tr("Shiny new v%1 is available. You're running old and busted v%2"). arg(latestapp).arg(VersionString); ui->notesTabWidget->setCurrentIndex(0); } else { ui->Title->setText("" + tr("An update for SleepyHead is available.") + ""); info = tr("Version %1 is available. You're currently running v%1"). arg(latestapp).arg(VersionString); ui->notesTabWidget->setCurrentIndex(1); } ui->versionInfo->setText(info); QString notes; for (int i = 0; i < release->updates[platform].size(); i++) { update = &release->updates[platform][i]; if ((update->type == "application") && (update->version > VersionString)) { notes += "" + tr("SleepyHead v%1 build notes").arg(update->version) + "
" + update->notes.trimmed() + "

"; } else if ((update->type == "qtlibs") && (update->version > QT_VERSION_STR)) { notes += "" + tr("Update to QtLibs (v%1)").arg(update->version) + "
" + update->notes.trimmed(); } } ui->buildNotes->setText(notes); setWindowModality(Qt::ApplicationModal); show(); } } else { mainwin->Notify(tr("There was an error parsing the XML Update file.")); } } void UpdaterWindow::replyFinished(QNetworkReply *reply) { netmanager->disconnect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); if (reply->error() == QNetworkReply::NoError) { if (requestmode == RM_CheckUpdates) { QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (!redirectUrl.isEmpty() && (redirectUrl != reply->url())) { update_url = redirectUrl; reply->deleteLater(); QTimer::singleShot(100, this, SLOT(downloadUpdateXML())); return; } ui->plainTextEdit->appendPlainText(tr("%1 bytes received").arg(reply->size())); QString filename = QApplication::applicationDirPath() + "/Updates.xml"; qDebug() << filename; QFile file(filename); file.open(QFile::WriteOnly); file.write(reply->readAll()); file.close(); file.open(QFile::ReadOnly); //QTextStream ts(&file); ParseUpdatesXML(&file); file.close(); reply->deleteLater(); } else if (requestmode == RM_GetFile) { disconnect(reply, SIGNAL(readyRead()), this, SLOT(dataReceived())); file.close(); //HttpStatusCodeAttribute QString rs = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString(); qDebug() << "HTTP Status Code" << rs; bool failed = false; if (rs == "404") { qDebug() << "File not found"; failed = true; } else { QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (!redirectUrl.isEmpty() && (redirectUrl != reply->url())) { file.open(QFile::WriteOnly); //reopen file.. update->url = redirectUrl.toString(); ui->plainTextEdit->appendPlainText(tr("Redirected to ") + update->url); QTimer::singleShot(100, this, SLOT(requestFile())); reply->deleteLater(); return; } ui->plainTextEdit->appendPlainText("Received " + QString::number(file.size()) + " bytes"); if (update->size > 0) { double s1 = update->size / 1048576.0; double s2 = ui->tableWidget->item(current_row, 2)->text().toDouble(); if (s1 != s2) { failed = true; ui->plainTextEdit->appendPlainText(tr("File size mismatch for %1").arg(update->filename)); } } else { QString path = QApplication::applicationDirPath() + "/Download/" + update->filename; QFile f(path); f.open(QFile::ReadOnly); QCryptographicHash hash(QCryptographicHash::Sha1); hash.addData(f.readAll()); QString res = hash.result().toHex(); if (res != update->hash) { ui->plainTextEdit->appendPlainText(tr("File integrity check failed for %1").arg(update->filename)); failed = true; } } } reply->deleteLater(); QProgressBar *bar = qobject_cast(ui->tableWidget->cellWidget(current_row, 3)); if (!failed) { //file.open(QFile::ReadOnly); QuaZip zip(&file); if (!zip.open(QuaZip::mdUnzip)) { failed = true; } else { QStringList files = zip.getFileNameList(); QFile f; int errors = 0; int fsize = files.size(); QByteArray ba; QStringList update_txt; QString apppath = QApplication::applicationDirPath() + "/"; QString backups = apppath + "Backups/"; QString downloads = apppath + "Downloads/"; QDir().mkpath(backups); for (int i = 0; i < fsize; i++) { ui->plainTextEdit->appendPlainText(tr("Extracting ") + files.at(i)); QuaZipFile qzf(file.fileName(), files.at(i)); qzf.open(QuaZipFile::ReadOnly); QString path = downloads + files.at(i); if (path.endsWith("/") || path.endsWith("\\")) { QDir().mkpath(path); if (update->unzipped_path.isEmpty()) { update->unzipped_path = path; } } else { ba = qzf.readAll(); if (qzf.getZipError()) { errors++; } else if (files.at(i) == "update.txt") { QTextStream ts(ba); QString line; do { line = ts.readLine(); if (!line.isNull()) { update_txt.append(line); } } while (!line.isNull()); } else { QString fn = files.at(i).section("/", -1); QFile::Permissions perm = QFile::permissions(apppath + fn); // delete backups if (f.exists(backups + fn)) { f.remove(backups + fn); } // rename (move) current file to backup if (!f.rename(apppath + fn, backups + fn)) { errors++; } //Save zip data as new file f.setFileName(apppath + fn); f.open(QFile::WriteOnly); f.write(ba); f.close(); f.setPermissions(perm); } } if (bar) { bar->setValue((1.0 / float(fsize)*float(i + 1)) * 100.0); QApplication::processEvents(); } qzf.close(); } zip.close(); if (errors) { // gone and wrecked the install here.. // probably should wait till get here before replacing files.. // but then again, this is probably what would screw up mainwin->Notify(tr("You might need to reinstall manually. Sorry :("), tr("Ugh.. Something went wrong with unzipping."), 5000); // TODO: Roll back from the backup folder failed = true; } } } ui->tableWidget->item(current_row, 0)->setCheckState(Qt::Checked); if (failed) { qDebug() << "File is corrupted"; if (bar) { bar->setFormat(tr("Failed")); QString style = "QProgressBar{\ border: 1px solid gray;\ border-radius: 3px;\ text-align: center;\ text-decoration: bold;\ color: yellow;\ }\ QProgressBar::chunk {\ background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 \"dark red\", stop: 1 red);\ width: 10px;\ margin: 0px;\ }"; //: qlineargradient(x1: 0, y1: 0.5, x2: 1, y2: 0.5, stop: 0 red, stop: 1 white); bar->setStyleSheet(style); } } ui->tableWidget->item(current_row, 0)->setData(Qt::UserRole + 1, failed); QTimer::singleShot(100, this, SLOT(upgradeNext())); ui->plainTextEdit->appendPlainText(tr("Download Complete")); } } else { mainwin->Notify(tr("There was an error completing a network request:\n\n(") + reply->errorString() + ")"); } } void UpdaterWindow::on_CloseButton_clicked() { close(); } void UpdaterWindow::upgradeNext() { QTableWidgetItem *item; bool fnd = false; for (current_row = 0; current_row < ui->tableWidget->rowCount(); current_row++) { item = ui->tableWidget->item(current_row, 0); bool complete = item->checkState() == Qt::Checked; if (complete) { continue; } update = item->data(Qt::UserRole).value(); qDebug() << "Processing" << update->url; fnd = true; requestFile(); break; } if (!fnd) { bool ok = true; for (current_row = 0; current_row < ui->tableWidget->rowCount(); current_row++) { bool failed = ui->tableWidget->item(current_row, 0)->data(Qt::UserRole + 1).toBool(); if (failed) { ok = false; break; } } if (ok) { success = true; //QMessageBox::information(this,tr("Updates Complete"),tr("SleepyHead has been updated and needs to restart."),QMessageBox::Ok); ui->downloadTitle->setText(tr("Update Complete!")); ui->FinishedButton->setVisible(true); ui->downloadLabel->setText( tr("Updates Complete. SleepyHead needs to restart now, click Finished to do so.")); PREF[STR_GEN_UpdatesLastChecked] = QDateTime::currentDateTime(); } else { ui->downloadTitle->setText(tr("Update Failed :(")); success = false; ui->downloadLabel->setText(tr("Download Error. Sorry, try again later.")); ui->FinishedButton->setVisible(true); //QMessageBox::warning(this,tr("Download Error"),tr("Sorry, could not get all necessary files for upgrade.. Try again later."),QMessageBox::Ok); //close(); } } } void UpdaterWindow::on_upgradeButton_clicked() { if (!updates.size()) { return; } ui->tableWidget->clearContents(); ui->tableWidget->setColumnHidden(4, true); ui->tableWidget->setColumnHidden(5, true); ui->FinishedButton->setVisible(false); ui->downloadLabel->setText(tr("Downloading & Installing Updates...")); ui->downloadTitle->setText(tr("Please wait while downloading and installing updates.")); success = false; for (int i = 0; i < updates.size(); i++) { update = updates.at(i); ui->tableWidget->insertRow(i); QTableWidgetItem *item = new QTableWidgetItem(update->type); QVariant av; av.setValue(update); item->setData(Qt::UserRole, av); item->setCheckState(Qt::Unchecked); item->setFlags(Qt::ItemIsEnabled); ui->tableWidget->setItem(i, 0, item); ui->tableWidget->setItem(i, 1, new QTableWidgetItem(update->version)); ui->tableWidget->setItem(i, 2, new QTableWidgetItem(QString::number(update->size / 1048576.0, 'f', 3) + "MB")); QProgressBar *bar = new QProgressBar(ui->tableWidget); bar->setMaximum(100); bar->setValue(0); ui->tableWidget->setCellWidget(i, 3, bar); ui->tableWidget->setItem(i, 4, new QTableWidgetItem(update->url)); } ui->stackedWidget->setCurrentIndex(1); upgradeNext(); } void UpdaterWindow::on_FinishedButton_clicked() { if (success) { mainwin->RestartApplication(); } else { close(); } }