/* CheckUpdates * * Copyright (c) 2011-2018 Mark Watkins * Copyright (c) 2020-2024 OSCAR Team * * 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 source code * for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "checkupdates.h" #include "version.h" #include "mainwindow.h" extern MainWindow *mainwin; struct VersionInfo { QString group; // test or release QString platform; // All or Requested platform QString version; // version number QString urlInstaller; // URL for installer page QString notes; // any notes }; CheckUpdates::CheckUpdates(QWidget *parent) : QMainWindow(parent) { manager = new QNetworkAccessManager(this); } CheckUpdates::~CheckUpdates() { disconnect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinished(QNetworkReply *))); } QString platformStr() { static QString platform; #if defined(Q_OS_WIN64) platform="win64"; #elif defined(Q_OS_WIN) platform="win32"; #elif defined(Q_OS_MACOS) platform="macOS"; #elif defined(Q_OS_LINUX) platform="linux"; #else platform="unknown"; #endif return platform; } static const QString OSCAR_Version_File = "http://apneaboard.net/OSCAR/versions.xml"; static QString versionXML; /*! \fn readLocalVersions \brief Reads versions.xml from local OSCAR Data directory */ QString readLocalVersions() { // Connect and read XML file from disk QString filename = GetAppData() + "/versions.xml"; qDebug() << "Local version control file at" << filename; QFile file(filename); if(!file.open(QFile::ReadOnly | QFile::Text)) { qDebug() << "Cannot open local version control file" << filename << "-" << file.errorString() << "version check disabled"; return QString(); } QByteArray qba = file.readAll(); QFileDevice::FileError error = file.error(); file.close(); if (error != QFile::NoError) { qDebug() << "Error reading local version control file" << filename << "-" << file.errorString() << "version check disabled"; qDebug() << "versionXML" << versionXML; return QString(); } return QString(qba).toLower(); } /*! \fn GetVersionInfo \brief Extracts newer version info for this platform If returned versionInfo.version is empty, no newer version was found */ VersionInfo getVersionInfo (QString type, QString platform) { VersionInfo foundInfo; QXmlStreamReader reader(versionXML); if (reader.readNextStartElement()) { if (reader.name() == "oscar"){ //qDebug() << "expecting oscar, read" << reader.name(); while(reader.readNextStartElement()){ //qDebug() << "expecting group, read" << reader.name() << "with id" << reader.attributes().value("id").toString(); if(reader.name() == "group" && reader.attributes().hasAttribute("id")){ if (reader.attributes().value("id").toString() == type) { while(reader.readNextStartElement()) { //qDebug() << "expecting url or platform, read" << reader.name(); if (reader.name() == "installers") foundInfo.urlInstaller = reader.readElementText(); if (reader.name() == "platform") { QString plat=reader.attributes().value("id").toString(); //qDebug() << "expecting platform, read " << reader.name() << "with id" << reader.attributes().value("id").toString(); if ((plat == platform) || (plat == "all" && foundInfo.platform.length() == 0)) { foundInfo.platform = plat; while(reader.readNextStartElement()) { //qDebug() << "expecting version or notes, read" << reader.name(); if (reader.name() == "version") { QString fileVersion = reader.readElementText(); Version fv(fileVersion); if (!fv.IsValid()) { qWarning() << "Server file versions.xml contains an invalid version code" << fileVersion; } else { if (fv > getVersion()) foundInfo.version = fileVersion; // We found a more recent version } } else if (reader.name() == "notes") { foundInfo.notes = reader.readElementText(); } else reader.skipCurrentElement(); } } } } } else reader.skipCurrentElement(); } else reader.skipCurrentElement(); } } else { qWarning() << "Versions.xml file improperly formed --" << reader.errorString(); reader.raiseError(QObject::tr("New versions file improperly formed")); } } return foundInfo; } void CheckUpdates::compareVersions () { #ifndef NO_CHECKUPDATES // Get any more recent versions available VersionInfo releaseVersion = getVersionInfo ("release", platformStr()); VersionInfo testVersion = getVersionInfo ("test", platformStr()); msg = ""; if (!showTestVersion) testVersion.version = ""; if (testVersion.version.length() == 0 && releaseVersion.version.length() == 0) { if (showIfCurrent) { QString txt = getVersion().IsReleaseVersion()?QObject::tr("release"):QObject::tr("test version"); QString txt2 = QObject::tr("You are running the latest %1 of OSCAR").arg(txt); msg = txt2 + "

" + QObject::tr("You are running OSCAR %1").arg(getVersion()) + "

"; } } else { msg = QObject::tr("A more recent version of OSCAR is available"); msg += "

" + QObject::tr("You are running OSCAR %1").arg(getVersion()) + "

"; if (releaseVersion.version.length() > 0) { msg += "

" + QObject::tr("OSCAR %1 is available here.").arg(releaseVersion.version).arg(releaseVersion.urlInstaller) + "

"; } if (showTestVersion && (testVersion.version.length() > 0)) { msg += "

" + QObject::tr("Information about more recent test version %1 is available at %2").arg(testVersion.version).arg(testVersion.urlInstaller) + "

"; } } if (msg.length() > 0) { // Add elapsed time in test versions only // if (elapsedTime > 0 && !getVersion().IsReleaseVersion()) // msg += "

" + QString(QObject::tr("(Reading %1 took %2 seconds)")).arg("versions.xml").arg(elapsedTime) + "

"; msgIsReady = true; } AppSetting->setUpdatesLastChecked(QDateTime::currentDateTime()); return; #endif } void CheckUpdates::showMessage() { if (!msgIsReady) return; if (showIfCurrent) { if (checkingBox != nullptr) checkingBox->reset(); } QMessageBox msgBox; msgBox.setWindowTitle(QObject::tr("Check for OSCAR Updates")); msgBox.setTextFormat(Qt::RichText); msgBox.setText(msg); msgBox.exec(); msgIsReady = false; } void CheckUpdates::checkForUpdates(bool showWhenCurrent) { showIfCurrent = showWhenCurrent; showTestVersion = false; // If running a test version of OSCAR, try reading versions.xml from OSCAR_Data directory // and force display of any new test version if (!getVersion().IsReleaseVersion()) { showTestVersion = true; versionXML = readLocalVersions(); if (versionXML.length() > 0) { compareVersions(); elapsedTime = 0; checkingBox = nullptr; showMessage(); return; } } else { showTestVersion = AppSetting->allowEarlyUpdates(); } readTimer.start(); connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*))); manager->get(QNetworkRequest(QUrl(OSCAR_Version_File))); if (showIfCurrent) { checkingBox = new QProgressDialog (this); // checkingBox->setWindowModality(Qt::WindowModal); checkingBox->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); checkingBox->setWindowFlags(this->windowFlags() & ~Qt::WindowMinMaxButtonsHint); checkingBox->setLabelText(tr("Checking for newer OSCAR versions")); checkingBox->setMinimumDuration(500); checkingBox->setRange(0,0); checkingBox->setCancelButton(nullptr); checkingBox->setWindowTitle(getAppName()); checkingBox->exec(); } qDebug() << "Starting network request for" << OSCAR_Version_File; return; } void CheckUpdates::replyFinished(QNetworkReply *reply) { if (showIfCurrent) { if (checkingBox != nullptr) checkingBox->reset(); } if (reply->error() != QNetworkReply::NoError) { qWarning() << "Update Check Error:" << reply->errorString(); reply->deleteLater(); if (!showIfCurrent) { // for automatic checks, don't show anything return; } msg = QObject::tr("Unable to check for updates. Please try again later."); msgIsReady = true; } else { // qDebug() << reply->header(QNetworkRequest::ContentTypeHeader).toString(); // qDebug() << reply->header(QNetworkRequest::LastModifiedHeader).toDateTime().toString(); // qDebug() << reply->header(QNetworkRequest::ContentLengthHeader).toULongLong(); qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); qDebug() << reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); versionXML = reply->readAll().toLower(); reply->deleteLater(); // Only calculate elapsed time for Help/Check for Updates // (Auto-update time would include profile opening time) if (showIfCurrent) { elapsedTime = readTimer.elapsed() / 1000.0; qDebug() << "Elapsed time to read versions.XML from web:" << elapsedTime << "seconds"; } else elapsedTime = 0; compareVersions(); } if (showIfCurrent) showMessage(); return; }