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"