File indexing completed on 2025-01-05 03:56:35

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2020-12-31
0007  * Description : Online version checker
0008  *
0009  * SPDX-FileCopyrightText: 2020-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "onlineversionchecker.h"
0016 
0017 // Qt includes
0018 
0019 #include <QLocale>
0020 #include <QSysInfo>
0021 #include <QTextStream>
0022 #include <QNetworkAccessManager>
0023 
0024 // KDE includes
0025 
0026 #include <klocalizedstring.h>
0027 #include <kconfiggroup.h>
0028 #include <ksharedconfig.h>
0029 
0030 // Local includes
0031 
0032 #include "digikam_version.h"
0033 #include "digikam_debug.h"
0034 
0035 namespace Digikam
0036 {
0037 
0038 class Q_DECL_HIDDEN OnlineVersionChecker::Private
0039 {
0040 public:
0041 
0042     explicit Private()
0043       : preRelease  (false),
0044         redirects   (0),
0045         curVersion  (QLatin1String(digikam_version_short)),
0046         curBuildDt  (digiKamBuildDate()),
0047         reply       (nullptr),
0048         manager     (nullptr)
0049     {
0050     }
0051 
0052     bool                   preRelease;          ///< Flag to check pre-releases
0053     int                    redirects;           ///< Count of redirected url
0054 
0055     QString                curVersion;          ///< Current application version string
0056     QString                preReleaseFileName;  ///< Pre-release file name get from remote server
0057 
0058     QDateTime              curBuildDt;          ///< Current application build date
0059 
0060     QNetworkReply*         reply;               ///< Current network request reply
0061     QNetworkAccessManager* manager;             ///< Network manager instance
0062 };
0063 
0064 OnlineVersionChecker::OnlineVersionChecker(QObject* const parent, bool checkPreRelease)
0065     : QObject(parent),
0066       d      (new Private)
0067 {
0068     d->preRelease = checkPreRelease;
0069     d->manager    = new QNetworkAccessManager(this);
0070     d->manager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
0071 
0072     connect(d->manager, SIGNAL(finished(QNetworkReply*)),
0073             this, SLOT(slotDownloadFinished(QNetworkReply*)));
0074 }
0075 
0076 OnlineVersionChecker::~OnlineVersionChecker()
0077 {
0078     cancelCheck();
0079     delete d;
0080 }
0081 
0082 void OnlineVersionChecker::cancelCheck()
0083 {
0084     if (d->reply)
0085     {
0086         d->reply->abort();
0087     }
0088 }
0089 
0090 void OnlineVersionChecker::setCurrentVersion(const QString& version)
0091 {
0092     d->curVersion = version;
0093 }
0094 
0095 void OnlineVersionChecker::setCurrentBuildDate(const QDateTime& dt)
0096 {
0097     d->curBuildDt = dt;
0098 }
0099 
0100 QString OnlineVersionChecker::preReleaseFileName() const
0101 {
0102     return d->preReleaseFileName;
0103 }
0104 
0105 QString OnlineVersionChecker::lastCheckDate()
0106 {
0107     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0108     KConfigGroup group        = config->group(QLatin1String("Updates"));
0109     QDateTime dt              = group.readEntry(QLatin1String("Last Check For New Version"), QDateTime());
0110     QString dts               = QLocale().toString(dt, QLocale::ShortFormat);
0111 
0112     return (!dts.isEmpty() ? dts : i18nc("@info: check of new online version never done yet", "never"));
0113 }
0114 
0115 void OnlineVersionChecker::checkForNewVersion()
0116 {
0117     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0118     KConfigGroup group        = config->group(QLatin1String("Updates"));
0119     group.writeEntry(QLatin1String("Last Check For New Version"), QDateTime::currentDateTime());
0120     config->sync();
0121 
0122     QUrl rUrl;
0123 
0124     if (d->preRelease)
0125     {
0126         rUrl = QUrl(QLatin1String("https://files.kde.org/digikam/FILES"));
0127     }
0128     else
0129     {
0130         rUrl = QUrl(QLatin1String("https://digikam.org/release.yml"));
0131     }
0132 
0133     d->redirects = 0;
0134     download(rUrl);
0135 }
0136 
0137 void OnlineVersionChecker::download(const QUrl& url)
0138 {
0139     qCDebug(DIGIKAM_GENERAL_LOG) << "Downloading: " << url;
0140 
0141     d->redirects++;
0142     d->reply = d->manager->get(QNetworkRequest(url));
0143 
0144     connect(d->reply, SIGNAL(sslErrors(QList<QSslError>)),
0145             d->reply, SLOT(ignoreSslErrors()));
0146 
0147     if (d->reply->error())
0148     {
0149         Q_EMIT signalNewVersionCheckError(d->reply->errorString());
0150     }
0151 }
0152 
0153 void OnlineVersionChecker::slotDownloadFinished(QNetworkReply* reply)
0154 {
0155     if (reply != d->reply)
0156     {
0157         return;
0158     }
0159 
0160     // mark for deletion
0161 
0162     reply->deleteLater();
0163     d->reply = nullptr;
0164 
0165     if ((reply->error() != QNetworkReply::NoError)             &&
0166         (reply->error() != QNetworkReply::InsecureRedirectError))
0167     {
0168         qCDebug(DIGIKAM_GENERAL_LOG) << "Error: " << reply->errorString();
0169         Q_EMIT signalNewVersionCheckError(reply->errorString());
0170 
0171         return;
0172     }
0173 
0174     QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
0175 
0176     if (redirectUrl.isValid()         &&
0177         (reply->url() != redirectUrl) &&
0178         (d->redirects < 10))
0179     {
0180         download(redirectUrl);
0181 
0182         return;
0183     }
0184 
0185     QString data     = QString::fromUtf8(reply->readAll());
0186 
0187     if (data.isEmpty())
0188     {
0189         qCWarning(DIGIKAM_GENERAL_LOG) << "No data returned from the remote connection.";
0190         Q_EMIT signalNewVersionCheckError(i18n("No data returned from the remote connection."));
0191 
0192         return;
0193     }
0194 
0195     if (d->preRelease)
0196     {
0197         // NOTE: pre-release files list from files.kde.org is a simple text
0198         // file of remote directory contents where we will extract build date string.
0199 
0200         QString arch;
0201         QString ext;
0202 
0203         if (!OnlineVersionChecker::bundleProperties(arch, ext))
0204         {
0205             Q_EMIT signalNewVersionCheckError(i18n("Unsupported Architecture: %1", QSysInfo::buildAbi()));
0206 
0207             qCDebug(DIGIKAM_GENERAL_LOG) << "Unsupported architecture";
0208 
0209             return;
0210         }
0211 
0212         QTextStream in(&data);
0213         QString line;
0214 
0215         do
0216         {
0217             line = in.readLine();
0218 
0219             if (line.contains(ext) && line.contains(arch))
0220             {
0221                 d->preReleaseFileName = line.simplified();
0222                 break;
0223             }
0224         }
0225         while (!line.isNull());
0226 
0227         QStringList sections = d->preReleaseFileName.split(QLatin1Char('-'));
0228 
0229         if (sections.size() < 4)
0230         {
0231             qCWarning(DIGIKAM_GENERAL_LOG) << "Invalid file name format returned from the remote connection.";
0232             Q_EMIT signalNewVersionCheckError(i18n("Invalid file name format returned from the remote connection."));
0233 
0234             return;
0235         }
0236 
0237         // Tow possibles places exists where to find the date in file name.
0238 
0239         // Check 1 - the fila name include a pre release suffix as -beta or -rc
0240 
0241         QString dtStr      = sections[3];
0242         QDateTime onlineDt = QDateTime::fromString(dtStr, QLatin1String("yyyyMMddTHHmmss"));
0243         onlineDt.setTimeSpec(Qt::UTC);
0244 
0245         if (!onlineDt.isValid())
0246         {
0247             // Check 2 - the file name do not include a pre release suffix
0248 
0249             dtStr    = sections[2];
0250             onlineDt = QDateTime::fromString(dtStr, QLatin1String("yyyyMMddTHHmmss"));
0251             onlineDt.setTimeSpec(Qt::UTC);
0252         }
0253 
0254         if (!onlineDt.isValid())
0255         {
0256             qCWarning(DIGIKAM_GENERAL_LOG) << "Invalid pre-release date from the remote list.";
0257             Q_EMIT signalNewVersionCheckError(i18n("Invalid pre-release date from the remote list."));
0258 
0259             return;
0260         }
0261 
0262         qCDebug(DIGIKAM_GENERAL_LOG) << "Pre-release file Name :" << preReleaseFileName();
0263         qCDebug(DIGIKAM_GENERAL_LOG) << "Pre-release build date:" << onlineDt;
0264         qCDebug(DIGIKAM_GENERAL_LOG) << "Current build date:"     << d->curBuildDt;
0265 
0266         if (onlineDt > d->curBuildDt)
0267         {
0268             Q_EMIT signalNewVersionAvailable(dtStr);            // Forward pre-release build date from remote file.
0269         }
0270         else
0271         {
0272             Q_EMIT signalNewVersionCheckError(QString());             // Report error to GUI
0273         }
0274     }
0275     else
0276     {
0277         // NOTE: stable files list from digikam.org is a Yaml config file where we will extract version string.
0278 
0279         QString tag            = QLatin1String("version: ");
0280         int start              = data.indexOf(tag) + tag.size();
0281         QString rightVer       = data.mid(start);
0282         int end                = rightVer.indexOf(QLatin1Char('\n'));
0283         QString onlineVer      = rightVer.mid(0, end);
0284         QStringList onlineVals = onlineVer.split(QLatin1Char('.'));
0285 
0286         if (onlineVals.size() != 3)
0287         {
0288             qCWarning(DIGIKAM_GENERAL_LOG) << "Invalid format returned from the remote connection.";
0289             Q_EMIT signalNewVersionCheckError(i18n("Invalid format returned from the remote connection."));
0290 
0291             return;
0292         }
0293 
0294         QStringList currVals = d->curVersion.split(QLatin1Char('.'));
0295 
0296         qCDebug(DIGIKAM_GENERAL_LOG) << "Online Version:" << onlineVer;
0297 
0298         if (digiKamMakeIntegerVersion(onlineVals[0].toInt(),
0299                                       onlineVals[1].toInt(),
0300                                       onlineVals[2].toInt()) >
0301             digiKamMakeIntegerVersion(currVals[0].toInt(),
0302                                       currVals[1].toInt(),
0303                                       currVals[2].toInt()))
0304         {
0305             Q_EMIT signalNewVersionAvailable(onlineVer);
0306         }
0307         else
0308         {
0309             Q_EMIT signalNewVersionCheckError(QString());
0310         }
0311     }
0312 }
0313 
0314 bool OnlineVersionChecker::bundleProperties(QString& arch, QString& ext)
0315 {
0316 
0317 #if defined Q_OS_MACOS
0318 
0319     ext  = QLatin1String("pkg");
0320 
0321 #   if defined Q_PROCESSOR_X86_64
0322 
0323     arch = QLatin1String("x86-64");
0324 
0325 #   elif defined Q_PROCESSOR_ARM
0326 
0327 /*  Native Apple silicon is not yet supported
0328     arch = QLatin1String("arm-64");
0329 */
0330 
0331     // NOTE: Intel 64 version work fine with Apple Rosetta 2 emulator.
0332     arch = QLatin1String("x86-64");
0333 
0334 #   endif
0335 
0336 #endif
0337 
0338 #if defined Q_OS_WIN
0339 
0340     ext  = QLatin1String("exe");
0341 
0342 #   if defined Q_PROCESSOR_X86_64
0343 
0344     arch = QLatin1String("Win64");
0345 
0346 #   elif defined Q_PROCESSOR_ARM
0347 
0348 /*  Windows Arm is not yet supported
0349     arch = QLatin1String("arm-64");
0350 */
0351 
0352 #   elif defined Q_PROCESSOR_X86_32
0353 
0354     // 32 bits is not supported
0355 
0356 #   endif
0357 
0358 #endif
0359 
0360 #if defined Q_OS_LINUX
0361 
0362     ext  = QLatin1String("appimage");
0363 
0364 #   ifdef Q_PROCESSOR_X86_64
0365 
0366     arch = QLatin1String("x86-64");
0367 
0368 #   elif defined Q_PROCESSOR_ARM
0369 
0370 /*  Linux Arm is not yet supported
0371     arch = QLatin1String("arm-64");
0372 */
0373 
0374 #   elif defined Q_PROCESSOR_X86_32
0375 
0376     // 32 bits is not supported
0377 
0378 #   endif
0379 
0380 #endif
0381 
0382     return (!ext.isEmpty() && !arch.isEmpty());
0383 }
0384 
0385 } // namespace Digikam
0386 
0387 #include "moc_onlineversionchecker.cpp"