#include #include #include #include "../ui_qt.h" #ifndef CXX_BUILD extern "C" { #endif #include #include #include #include "../../../tasks/tasks_internal.h" #include "../../../verbosity.h" #include "../../../config.def.h" #ifndef CXX_BUILD } #endif #define USER_AGENT "RetroArch-WIMP/1.0" #define PARTIAL_EXTENSION ".partial" #define TEMP_EXTENSION ".update_tmp" #define RETROARCH_NIGHTLY_UPDATE_PATH "../RetroArch_update.zip" static void extractUpdateCB(void *task_data, void *user_data, const char *err) { decompress_task_data_t *dec = (decompress_task_data_t*)task_data; MainWindow *mainwindow = (MainWindow*)user_data; if (err) RARCH_ERR("%s", err); if (dec) { if (filestream_exists(dec->source_file)) filestream_delete(dec->source_file); free(dec->source_file); free(dec); } mainwindow->onUpdateRetroArchFinished(string_is_empty(err)); } void MainWindow::removeUpdateTempFiles() { /* a QDir with no path means the current working directory */ QDir dir; QStringList dirList = dir.entryList(QStringList(), QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System, QDir::Name); int i; for (i = 0; i < dirList.count(); i++) { QString path(dir.path() + "/" + dirList.at(i)); QFile file(path); if (path.endsWith(TEMP_EXTENSION)) { QByteArray pathArray = path.toUtf8(); const char *pathData = pathArray.constData(); if (file.remove()) RARCH_LOG("[Qt]: removed temporary update file %s\n", pathData); else RARCH_LOG("[Qt]: could not remove temporary update file %s\n", pathData); } } } void MainWindow::onUpdateNetworkError(QNetworkReply::NetworkError code) { QNetworkReply *reply = m_updateReply.data(); QByteArray errorStringArray; const char *errorStringData = NULL; m_updateProgressDialog->cancel(); if (!reply) return; errorStringArray = reply->errorString().toUtf8(); errorStringData = errorStringArray.constData(); RARCH_ERR("[Qt]: Network error code %d received: %s\n", code, errorStringData); /* Deleting the reply here seems to cause a strange heap-use-after-free crash. */ /* reply->disconnect(); reply->abort(); reply->deleteLater(); */ } void MainWindow::onUpdateNetworkSslErrors(const QList &errors) { QNetworkReply *reply = m_updateReply.data(); int i; if (!reply) return; for (i = 0; i < errors.count(); i++) { const QSslError &error = errors.at(i); QString string = QString("Ignoring SSL error code ") + QString::number(error.error()) + ": " + error.errorString(); QByteArray stringArray = string.toUtf8(); const char *stringData = stringArray.constData(); RARCH_ERR("[Qt]: %s\n", stringData); } /* ignore all SSL errors for now, like self-signed, expired etc. */ reply->ignoreSslErrors(); } void MainWindow::onUpdateDownloadCanceled() { m_updateProgressDialog->cancel(); } void MainWindow::onRetroArchUpdateDownloadFinished() { QNetworkReply *reply = m_updateReply.data(); QNetworkReply::NetworkError error; int code; m_updateProgressDialog->cancel(); /* At least on Linux, the progress dialog will refuse to hide itself and will stay on screen in a corrupted way if we happen to show an error message in this function. processEvents() will sometimes fix it, other times not... seems random. */ qApp->processEvents(); if (!reply) return; error = reply->error(); code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (m_updateFile.isOpen()) m_updateFile.close(); if (code != 200) { emit showErrorMessageDeferred(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NETWORK_ERROR)) + ": HTTP Code " + QString::number(code)); RARCH_ERR("[Qt]: RetroArch update failed with HTTP status code: %d\n", code); reply->disconnect(); reply->abort(); reply->deleteLater(); return; } if (error == QNetworkReply::NoError) { int index = m_updateFile.fileName().lastIndexOf(PARTIAL_EXTENSION); QString newFileName = m_updateFile.fileName().left(index); QFile newFile(newFileName); /* rename() requires the old file to be deleted first if it exists */ if (newFile.exists() && !newFile.remove()) RARCH_ERR("[Qt]: RetroArch update finished, but old file could not be deleted.\n"); else { if (m_updateFile.rename(newFileName)) { RARCH_LOG("[Qt]: RetroArch update finished downloading successfully.\n"); emit extractArchiveDeferred(newFileName, ".", TEMP_EXTENSION, extractUpdateCB); } else { RARCH_ERR("[Qt]: RetroArch update finished, but temp file could not be renamed.\n"); emit showErrorMessageDeferred(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_COULD_NOT_RENAME_FILE)); } } } else { QByteArray errorArray = reply->errorString().toUtf8(); const char *errorData = errorArray.constData(); m_updateFile.remove(); RARCH_ERR("[Qt]: RetroArch update ended prematurely: %s\n", errorData); emit showErrorMessageDeferred(QString(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_NETWORK_ERROR)) + ": Code " + QString::number(code) + ": " + errorData); } reply->disconnect(); reply->close(); reply->deleteLater(); } void MainWindow::onUpdateRetroArchFinished(bool success) { m_updateProgressDialog->cancel(); if (!success) { RARCH_ERR("[Qt]: RetroArch update failed.\n"); emit showErrorMessageDeferred(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_FAILED)); return; } RARCH_LOG("[Qt]: RetroArch update finished successfully.\n"); emit showInfoMessageDeferred(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_UPDATE_RETROARCH_FINISHED)); } void MainWindow::onUpdateDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { QNetworkReply *reply = m_updateReply.data(); int progress = (bytesReceived / (float)bytesTotal) * 100.0f; if (!reply) return; m_updateProgressDialog->setValue(progress); } void MainWindow::onUpdateDownloadReadyRead() { QNetworkReply *reply = m_updateReply.data(); if (!reply) return; m_updateFile.write(reply->readAll()); } void MainWindow::updateRetroArchNightly() { QUrl url(QUrl(buildbot_server_url).resolved(QUrl(RETROARCH_NIGHTLY_UPDATE_PATH))); QNetworkRequest request(url); QNetworkReply *reply = NULL; QByteArray urlArray = url.toString().toUtf8(); const char *urlData = urlArray.constData(); if (m_updateFile.isOpen()) { RARCH_ERR("[Qt]: File is already open.\n"); return; } else { QString fileName = QFileInfo(url.toString()).fileName() + PARTIAL_EXTENSION; QByteArray fileNameArray = fileName.toUtf8(); const char *fileNameData = fileNameArray.constData(); m_updateFile.setFileName(fileName); if (!m_updateFile.open(QIODevice::WriteOnly)) { showMessageBox(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_QT_FILE_WRITE_OPEN_FAILED), MainWindow::MSGBOX_TYPE_ERROR, Qt::ApplicationModal, false); RARCH_ERR("[Qt]: Could not open file for writing: %s\n", fileNameData); return; } } RARCH_LOG("[Qt]: Starting update of RetroArch...\n"); RARCH_LOG("[Qt]: Downloading URL %s\n", urlData); request.setHeader(QNetworkRequest::UserAgentHeader, USER_AGENT); m_updateProgressDialog->setWindowModality(Qt::NonModal); m_updateProgressDialog->setMinimumDuration(0); m_updateProgressDialog->setRange(0, 100); m_updateProgressDialog->setAutoClose(true); m_updateProgressDialog->setAutoReset(true); m_updateProgressDialog->setValue(0); m_updateProgressDialog->setLabelText(QString(msg_hash_to_str(MSG_DOWNLOADING)) + "..."); m_updateProgressDialog->setCancelButtonText(tr("Cancel")); m_updateProgressDialog->show(); m_updateReply = m_networkManager->get(request); reply = m_updateReply.data(); /* make sure any previous connection is removed first */ disconnect(m_updateProgressDialog, SIGNAL(canceled()), reply, SLOT(abort())); disconnect(m_updateProgressDialog, SIGNAL(canceled()), m_updateProgressDialog, SLOT(cancel())); connect(m_updateProgressDialog, SIGNAL(canceled()), reply, SLOT(abort())); connect(m_updateProgressDialog, SIGNAL(canceled()), m_updateProgressDialog, SLOT(cancel())); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onUpdateNetworkError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(sslErrors(const QList&)), this, SLOT(onUpdateNetworkSslErrors(const QList&))); connect(reply, SIGNAL(finished()), this, SLOT(onRetroArchUpdateDownloadFinished())); connect(reply, SIGNAL(readyRead()), this, SLOT(onUpdateDownloadReadyRead())); connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onUpdateDownloadProgress(qint64, qint64))); }