1 /*******************************************************************************************************
2 nomacs is a fast and small image viewer with the capability of synchronizing multiple instances
3
4 Copyright (C) 2011-2016 Markus Diem <markus@nomacs.org>
5 Copyright (C) 2011-2016 Stefan Fiel <stefan@nomacs.org>
6 Copyright (C) 2011-2016 Florian Kleber <florian@nomacs.org>
7
8 This file is part of nomacs.
9
10 nomacs is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
14
15 nomacs is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program. If not, see <http://www.gnu.org/licenses/>.
22
23 related links:
24 [1] https://nomacs.org/
25 [2] https://github.com/nomacs/
26 [3] http://download.nomacs.org
27 *******************************************************************************************************/
28
29 #include "DkUpdater.h"
30
31 #include "DkSettings.h"
32 #include "DkTimer.h"
33 #include "DkUtils.h"
34
35 #pragma warning(push, 0) // no warnings from includes
36 #include <QVector>
37 #include <QDebug>
38 #include <QXmlStreamReader>
39 #include <QNetworkProxyQuery>
40 #include <QFile>
41 #include <QCoreApplication>
42 #include <QMessageBox>
43 #include <QApplication>
44 #include <QPushButton>
45 #include <QFileInfo>
46 #include <QNetworkCookieJar>
47 #include <QDir>
48 #include <QProcess>
49 #include <QStandardPaths>
50
51 #ifdef Q_OS_WIN
52 #include <windows.h>
53 #endif
54
55 #pragma warning(pop)
56
57 namespace nmc {
58
59 // DkPackage --------------------------------------------------------------------
DkPackage(const QString & name,const QString & version)60 DkPackage::DkPackage(const QString& name, const QString& version) {
61 mName = name;
62 mVersion = version;
63 }
64
isEmpty() const65 bool DkPackage::isEmpty() const {
66 return mName.isEmpty();
67 }
68
operator ==(const DkPackage & o) const69 bool DkPackage::operator==(const DkPackage& o) const {
70
71 return mName == o.name();
72 }
73
version() const74 QString DkPackage::version() const {
75 return mVersion;
76 }
77
name() const78 QString DkPackage::name() const {
79 return mName;
80 }
81
82 // DkXmlUpdateChecker --------------------------------------------------------------------
DkXmlUpdateChecker()83 DkXmlUpdateChecker::DkXmlUpdateChecker() {
84 }
85
updatesAvailable(QXmlStreamReader & localXml,QXmlStreamReader & remoteXml) const86 QVector<DkPackage> DkXmlUpdateChecker::updatesAvailable(QXmlStreamReader& localXml, QXmlStreamReader& remoteXml) const {
87
88 QVector<DkPackage> localPackages = parse(localXml);
89 QVector<DkPackage> remotePackages = parse(remoteXml);
90 QVector<DkPackage> updatePackages;
91
92 for (const DkPackage& p : localPackages) {
93
94 int idx = remotePackages.indexOf(p);
95
96 if (idx != -1) {
97 bool isEqual = remotePackages[idx].version() == p.version();
98 qDebug() << "checking" << p.name() << "v" << p.version();
99
100 if (!isEqual) // we assume that the remote is _always_ newer than the local version
101 updatePackages.append(remotePackages[idx]);
102 else
103 qDebug() << "up-to-date";
104 }
105 else
106 qDebug() << "I could not find" << p.name() << "in the repository";
107 }
108
109 if (localPackages.empty() || remotePackages.empty())
110 qDebug() << "WARNING: I could not find any packages. local (" << localPackages.size() << ") remote (" << remotePackages.size() << ")";
111
112 return updatePackages;
113 }
114
parse(QXmlStreamReader & reader) const115 QVector<DkPackage> DkXmlUpdateChecker::parse(QXmlStreamReader& reader) const {
116
117 QVector<DkPackage> packages;
118 QString pName;
119
120 while (!reader.atEnd()) {
121
122 // e.g. <Name>nomacs</Name>
123 if (reader.tokenType() == QXmlStreamReader::StartElement && reader.qualifiedName() == "Name") {
124 reader.readNext();
125 pName = reader.text().toString();
126 }
127 // e.g. <Version>3.0.0-3</Version>
128 else if (reader.tokenType() == QXmlStreamReader::StartElement && reader.qualifiedName() == "Version") {
129 reader.readNext();
130
131 if (!pName.isEmpty()) {
132 packages.append(DkPackage(pName, reader.text().toString()));
133 pName = ""; // reset
134 }
135 else {
136 qWarning() << "version: " << reader.text().toString() << "without a valid package name detected";
137 }
138 }
139
140 reader.readNext();
141 }
142
143 return packages;
144 }
145
146 // DkUpdater --------------------------------------------------------------------
DkUpdater(QObject * parent)147 DkUpdater::DkUpdater(QObject* parent) : QObject(parent) {
148
149 silent = true;
150 mCookie = new QNetworkCookieJar(this);
151 mAccessManagerSetup.setCookieJar(mCookie);
152 connect(&mAccessManagerSetup, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinishedSlot(QNetworkReply*)));
153 mUpdateAborted = false;
154 }
155
checkForUpdates()156 void DkUpdater::checkForUpdates() {
157
158 if (DkSettingsManager::param().sync().disableUpdateInteraction) {
159 QMessageBox::critical(
160 DkUtils::getMainWindow(),
161 tr("Updates Disabled"),
162 tr("nomacs updates are disabled.\nPlease contact your system administrator for further information."),
163 QMessageBox::Ok);
164 return;
165 }
166
167 DkSettingsManager::param().sync().lastUpdateCheck = QDate::currentDate();
168 DkSettingsManager::param().save();
169
170 #ifdef Q_OS_WIN
171 QUrl url ("http://nomacs.org/version/version_win_stable");
172 #elif defined Q_OS_LINUX
173 QUrl url ("http://nomacs.org/version/version_linux");
174 #elif defined Q_OS_MAC
175 QUrl url ("http://nomacs.org/version/version_mac_stable");
176 #else
177 QUrl url ("http://nomacs.org/version/version");
178 #endif
179
180 // the proxy settings take > 2 sec on Win7
181 // that is why proxy settings are only set
182 // for manual updates
183 if (!silent) {
184 DkTimer dt;
185 QNetworkProxyQuery npq(QUrl("http://www.google.com"));
186 QList<QNetworkProxy> listOfProxies = QNetworkProxyFactory::systemProxyForQuery(npq);
187
188 if (!listOfProxies.empty() && listOfProxies[0].hostName() != "") {
189 mAccessManagerSetup.setProxy(listOfProxies[0]);
190 mAccessManagerVersion.setProxy(listOfProxies[0]);
191 }
192 qDebug() << "checking for proxy takes: " << dt;
193 }
194
195 qDebug() << "checking for updates";
196 connect(&mAccessManagerVersion, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
197 mReply = mAccessManagerVersion.get(QNetworkRequest(url));
198 connect(mReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(replyError(QNetworkReply::NetworkError)));
199 }
200
replyFinished(QNetworkReply * reply)201 void DkUpdater::replyFinished(QNetworkReply* reply) {
202
203 if (reply->error())
204 return;
205
206 QString replyData = reply->readAll();
207
208 QStringList sl = replyData.split('\n', QString::SkipEmptyParts);
209
210 QString version, x64, x86, url, mac, XPx86;
211 for(int i = 0; i < sl.length();i++) {
212 QStringList values = sl[i].split(" ");
213 if (values[0] == "version")
214 version = values[1];
215 else if (values[0] == "x64")
216 x64 = values[1];
217 else if (values[0] == "XPx86")
218 XPx86 = values[1];
219 else if (values[0] == "x86")
220 x86 = values[1];
221 else if (values[0] == "mac")
222 mac = values[1];
223 }
224
225
226 #if _MSC_VER == 1600
227 url = XPx86; // for WinXP packages
228 #elif defined _WIN64
229 url = x64;
230 #elif _WIN32
231 url = x86;
232 #elif defined Q_OS_MAC
233 url = mac;
234 #endif
235
236 qDebug() << "version:" << version;
237 qDebug() << "x64:" << x64;
238 qDebug() << "x86:" << x86;
239 qDebug() << "mac:" << mac;
240
241 if ((!version.isEmpty() && !x64.isEmpty()) || !x86.isEmpty()) {
242 QStringList cVersion = QApplication::applicationVersion().split('.');
243 QStringList nVersion = version.split('.');
244
245 if (cVersion.size() < 3 || nVersion.size() < 3) {
246 qDebug() << "sorry, I could not parse the version number...";
247
248 if (!silent)
249 emit showUpdaterMessage(tr("sorry, I could not check for newer versions"), tr("Updates"));
250
251 return;
252 }
253
254 if (nVersion[0].toInt() > cVersion[0].toInt() || // major release
255 (nVersion[0].toInt() == cVersion[0].toInt() && // major release
256 nVersion[1].toInt() > cVersion[1].toInt()) || // minor release
257 (nVersion[0].toInt() == cVersion[0].toInt() && // major release
258 nVersion[1].toInt() == cVersion[1].toInt() && // minor release
259 nVersion[2].toInt() > cVersion[2].toInt())) { // minor-minor release
260
261 QString msg = tr("A new version") + " (" + sl[0] + ") " + tr("is available");
262 msg = msg + "<br>" + tr("Do you want to download and install it now?");
263 msg = msg + "<br>" + tr("For more information see ") + " <a href=\"https://nomacs.org\">https://nomacs.org</a>";
264 mNomacsSetupUrl = url;
265 mSetupVersion = version;
266 qDebug() << "nomacs setup url:" << mNomacsSetupUrl;
267
268 if (!url.isEmpty())
269 emit displayUpdateDialog(msg, tr("updates"));
270 }
271 else if (!silent)
272 emit showUpdaterMessage(tr("nomacs is up-to-date"), tr("updates"));
273 }
274
275 }
276
startDownload(QUrl downloadUrl)277 void DkUpdater::startDownload(QUrl downloadUrl) {
278
279 if (downloadUrl.isEmpty())
280 emit showUpdaterMessage(tr("sorry, unable to download the new version"), tr("updates"));
281
282 qDebug() << "-----------------------------------------------------";
283 qDebug() << "starting to download update from " << downloadUrl ;
284
285 //updateAborted = false; // reset - it may have been canceled before
286 QNetworkRequest req(downloadUrl);
287 req.setRawHeader("User-Agent", "Auto-Updater");
288 mReply = mAccessManagerSetup.get(req);
289 connect(mReply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(updateDownloadProgress(qint64, qint64)));
290 }
291
downloadFinishedSlot(QNetworkReply * data)292 void DkUpdater::downloadFinishedSlot(QNetworkReply* data) {
293 QUrl redirect = data->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
294 if (!redirect.isEmpty() ) {
295 qDebug() << "redirecting: " << redirect;
296 startDownload(redirect);
297 return;
298 }
299
300 if (!mUpdateAborted) {
301 QString basename = "nomacs-setup";
302 QString extension = ".msi";
303 QString absoluteFilePath = QDir::tempPath() + "/" + basename + extension;
304 if (QFile::exists(absoluteFilePath)) {
305 qDebug() << "File already exists - searching for new name";
306 // already exists, don't overwrite
307 int i = 0;
308 while (QFile::exists(absoluteFilePath)) {
309 absoluteFilePath = QDir::tempPath() + "/" + basename + "-" + QString::number(i) + extension;
310 ++i;
311 }
312 }
313
314 QFile file(absoluteFilePath);
315 if (!file.open(QIODevice::WriteOnly)) {
316 qDebug() << "Could not open " << QFileInfo(file).absoluteFilePath() << "for writing";
317 return;
318 }
319
320 file.write(data->readAll());
321 qDebug() << "saved new version: " << " " << QFileInfo(file).absoluteFilePath();
322
323 file.close();
324
325 DkSettingsManager::param().global().setupVersion = mSetupVersion;
326 DkSettingsManager::param().global().setupPath = absoluteFilePath;
327 DkSettingsManager::param().save();
328
329 emit downloadFinished(absoluteFilePath);
330 }
331 mUpdateAborted = false;
332 qDebug() << "downloadFinishedSlot complete";
333 }
334
performUpdate()335 void DkUpdater::performUpdate() {
336 if(mNomacsSetupUrl.isEmpty())
337 qDebug() << "unable to perform update because the nomacsSetupUrl is empty";
338 else
339 startDownload(mNomacsSetupUrl);
340 }
341
cancelUpdate()342 void DkUpdater::cancelUpdate() {
343 qDebug() << "abort update";
344 mUpdateAborted = true;
345 mReply->abort();
346 }
347
replyError(QNetworkReply::NetworkError)348 void DkUpdater::replyError(QNetworkReply::NetworkError) {
349 if (!silent)
350 emit showUpdaterMessage(tr("Unable to connect to server ... please try again later"), tr("updates"));
351 }
352
353 // DkTranslationUpdater --------------------------------------------------------------------
DkTranslationUpdater(bool silent,QObject * parent)354 DkTranslationUpdater::DkTranslationUpdater(bool silent, QObject* parent) : QObject(parent) {
355
356 this->silent = silent;
357 connect(&mAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
358
359 updateAborted = false;
360 updateAbortedQt = false;
361 }
362
checkForUpdates()363 void DkTranslationUpdater::checkForUpdates() {
364
365 if (DkSettingsManager::param().sync().disableUpdateInteraction) {
366 QMessageBox::critical(
367 DkUtils::getMainWindow(),
368 tr("Updates Disabled"),
369 tr("nomacs updates are disabled.\nPlease contact your system administrator for further information."),
370 QMessageBox::Ok);
371 return;
372 }
373
374 mTotal = -1;
375 mTotalQt = -1;
376 mReceived = 0;
377 mReceivedQt = 0;
378 updateAborted = false;
379 updateAbortedQt = false;
380
381 // that line takes 2 secs on win7!
382 QNetworkProxyQuery npq(QUrl("http://www.google.com"));
383 QList<QNetworkProxy> listOfProxies = QNetworkProxyFactory::systemProxyForQuery(npq);
384 if (!listOfProxies.empty() && listOfProxies[0].hostName() != "") {
385 mAccessManager.setProxy(listOfProxies[0]);
386 }
387
388 QUrl url ("http://nomacs.org/translations/" + DkSettingsManager::param().global().language + "/nomacs_" + DkSettingsManager::param().global().language + ".qm");
389 qInfo() << "checking for new translations at " << url;
390 mReply = mAccessManager.get(QNetworkRequest(url));
391 connect(mReply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(updateDownloadProgress(qint64, qint64)));
392
393 url=QUrl("http://nomacs.org/translations/qt/qt_" + DkSettingsManager::param().global().language + ".qm");
394 qDebug() << "checking for new translations at " << url;
395 mReplyQt = mAccessManager.get(QNetworkRequest(url));
396 connect(mReplyQt, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(updateDownloadProgressQt(qint64, qint64)));
397 }
398
replyFinished(QNetworkReply * reply)399 void DkTranslationUpdater::replyFinished(QNetworkReply* reply) {
400
401 bool qtTranslation = false;
402 if (reply->url().toString().contains("qt_"))
403 qtTranslation = true;
404
405 if (updateAbortedQt && updateAborted) {
406 emit downloadFinished();
407 return;
408 }
409
410 if (reply->error() == QNetworkReply::OperationCanceledError)
411 return;
412
413 if (reply->error()) {
414 qDebug() << "network reply error : url: " << reply->url();
415 if (!qtTranslation && !silent)
416 emit showUpdaterMessage(tr("Unable to download translation"), tr("update"));
417 return;
418 }
419
420 QDateTime lastModifiedRemote = reply->header(QNetworkRequest::LastModifiedHeader).toDateTime();
421
422 QDir storageLocation = DkUtils::getTranslationPath();
423 QString translationName = qtTranslation ? "qt_"+ DkSettingsManager::param().global().language + ".qm" : "nomacs_"+ DkSettingsManager::param().global().language + ".qm";
424
425 if (isRemoteFileNewer(lastModifiedRemote, translationName)) {
426 QString basename = qtTranslation ? "qt_" + DkSettingsManager::param().global().language : "nomacs_" + DkSettingsManager::param().global().language;
427 QString extension = ".qm";
428
429 if (!storageLocation.exists()) {
430 if (!storageLocation.mkpath(storageLocation.absolutePath())) {
431 qDebug() << "unable to create storage location ... aborting";
432 if (!qtTranslation && !silent)
433 emit showUpdaterMessage(tr("Unable to update translation"), tr("update"));
434 return;
435 }
436 }
437
438 QString absoluteFilePath = storageLocation.absolutePath() + "/" + basename + extension;
439 if (QFile::exists(absoluteFilePath)) {
440 qInfo() << "File already exists - overwriting";
441 }
442
443 QFile file(absoluteFilePath);
444 if (!file.open(QIODevice::WriteOnly)) {
445 qWarning() << "Could not open " << QFileInfo(file).absoluteFilePath() << "for writing";
446 return;
447 }
448
449 file.write(reply->readAll());
450 qDebug() << "saved new translation: " << " " << QFileInfo(file).absoluteFilePath();
451
452 file.close();
453
454 if (!qtTranslation && !silent)
455 emit showUpdaterMessage(tr("Translation updated"), tr("update"));
456 qDebug() << "translation updated";
457 } else {
458 qDebug() << "no newer translations available";
459 if (!silent)
460 emit showUpdaterMessage(tr("No newer translations found"), tr("update"));
461 }
462 if (reply->isFinished() && mReplyQt->isFinished()) {
463 qDebug() << "emitting downloadFinished";
464 emit downloadFinished();
465 }
466
467 }
468
updateDownloadProgress(qint64 received,qint64 total)469 void DkTranslationUpdater::updateDownloadProgress(qint64 received, qint64 total) {
470 if (total == -1) // if file does not exist
471 return;
472
473 QDateTime lastModifiedRemote = mReply->header(QNetworkRequest::LastModifiedHeader).toDateTime();
474
475 QString translationName = "nomacs_"+ DkSettingsManager::param().global().language + ".qm";
476 qDebug() << "isRemoteFileNewer:" << isRemoteFileNewer(lastModifiedRemote, translationName);
477 if (!isRemoteFileNewer(lastModifiedRemote, translationName)) {
478 updateAborted = true;
479 this->mTotal = 0;
480 this->mReceived = 0;
481 mReply->abort();
482 return;
483 }
484
485 this->mReceived = received;
486 this->mTotal = total;
487 qDebug() << "total:" << total;
488 emit downloadProgress(this->mReceived + this->mReceivedQt, this->mTotal + this->mTotalQt);
489 }
490
updateDownloadProgressQt(qint64 received,qint64 total)491 void DkTranslationUpdater::updateDownloadProgressQt(qint64 received, qint64 total) {
492 if (total == -1) // if file does not exist
493 return;
494
495 QDateTime lastModifiedRemote = mReplyQt->header(QNetworkRequest::LastModifiedHeader).toDateTime();
496 QString translationName = "qt_"+ DkSettingsManager::param().global().language + ".qm";
497 qDebug() << "isRemoteFileNewer:" << isRemoteFileNewer(lastModifiedRemote, translationName);
498 if (!isRemoteFileNewer(lastModifiedRemote, translationName)) {
499 updateAbortedQt = true;
500 this->mTotalQt = 0;
501 this->mReceivedQt = 0;
502 mReplyQt->abort();
503 return;
504 }
505
506 this->mReceivedQt = received;
507 this->mTotalQt = total;
508 qDebug() << "totalQt:" << mTotalQt;
509 emit downloadProgress(this->mReceived + this->mReceivedQt, this->mTotal + this->mTotalQt);
510 }
511
isRemoteFileNewer(QDateTime lastModifiedRemote,const QString & localTranslationName)512 bool DkTranslationUpdater::isRemoteFileNewer(QDateTime lastModifiedRemote, const QString& localTranslationName) {
513
514 if (!lastModifiedRemote.isValid())
515 return false;
516
517 QString trPath = DkUtils::getTranslationPath();
518 QFileInfo trFile(trPath, localTranslationName);
519
520 return !trFile.exists() || (QFileInfo(trFile).lastModified() < lastModifiedRemote);
521 }
522
cancelUpdate()523 void DkTranslationUpdater::cancelUpdate() {
524 mReply->abort();
525 mReplyQt->abort();
526 updateAborted = true;
527 updateAbortedQt = true;
528 }
529
530 }