OSDN Git Service

Improved progress indicator + refactored update info parsing code.
[mutilities/MUtilities.git] / src / UpdateChecker.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // MuldeR's Utilities for Qt
3 // Copyright (C) 2004-2018 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 //
19 // http://www.gnu.org/licenses/lgpl-2.1.txt
20 //////////////////////////////////////////////////////////////////////////////////
21
22 #include <MUtils/Global.h>
23 #include <MUtils/UpdateChecker.h>
24 #include <MUtils/OSSupport.h>
25 #include <MUtils/Exception.h>
26
27 #include <QStringList>
28 #include <QFile>
29 #include <QFileInfo>
30 #include <QDir>
31 #include <QProcess>
32 #include <QUrl>
33 #include <QEventLoop>
34 #include <QTimer>
35 #include <QElapsedTimer>
36 #include <QSet>
37 #include <QHash>
38
39 #include "Mirrors.h"
40
41 ///////////////////////////////////////////////////////////////////////////////
42 // CONSTANTS
43 ///////////////////////////////////////////////////////////////////////////////
44
45 static const char *HEADER_ID = "!Update";
46
47 static const char *MIRROR_URL_POSTFIX[] = 
48 {
49         "update.ver",
50         "update_beta.ver",
51         NULL
52 };
53
54 static const int MIN_CONNSCORE = 5;
55 static const int QUICK_MIRRORS = 3;
56 static const int MAX_CONN_TIMEOUT = 16000;
57 static const int DOWNLOAD_TIMEOUT = 30000;
58
59 static const int VERSION_INFO_EXPIRES_MONTHS = 6;
60 static char *USER_AGENT_STR = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0"; /*use something innocuous*/
61
62 #define CHECK_CANCELLED() do \
63 { \
64         if(MUTILS_BOOLIFY(m_cancelled)) \
65         { \
66                 m_success.fetchAndStoreOrdered(0); \
67                 log("", "Update check has been cancelled by user!"); \
68                 setProgress(m_maxProgress); \
69                 setStatus(UpdateStatus_CancelledByUser); \
70                 return; \
71         } \
72 } \
73 while(0)
74
75 #define LOG_MESSAGE_HELPER(X) do \
76 { \
77         if (!(X).isNull()) \
78         { \
79                 emit messageLogged((X)); \
80         } \
81 } \
82 while(0)
83
84 #define STRICMP(X,Y) ((X).compare((Y), Qt::CaseInsensitive) == 0)
85
86 ////////////////////////////////////////////////////////////
87 // Helper Functions
88 ////////////////////////////////////////////////////////////
89
90 static QStringList buildRandomList(const char *const values[])
91 {
92         QStringList list;
93         for (int index = 0; values[index]; index++)
94         {
95                 const int pos = MUtils::next_rand_u32() % (index + 1);
96                 list.insert(pos, QString::fromLatin1(values[index]));
97         }
98         return list;
99 }
100
101 static const QHash<QString, QString> *initEnvVars(void)
102 {
103         QHash<QString, QString> *const environment = new QHash<QString, QString>();
104         environment->insert(QLatin1String("CURL_HOME"), QDir::toNativeSeparators(MUtils::temp_folder()));
105         return environment;
106 }
107
108 ////////////////////////////////////////////////////////////
109 // Update Info Class
110 ////////////////////////////////////////////////////////////
111
112 MUtils::UpdateCheckerInfo::UpdateCheckerInfo(void)
113 {
114         resetInfo();
115 }
116         
117 void MUtils::UpdateCheckerInfo::resetInfo(void)
118 {
119         m_buildNo = 0;
120         m_buildDate.setDate(1900, 1, 1);
121         m_downloadSite.clear();
122         m_downloadAddress.clear();
123         m_downloadFilename.clear();
124         m_downloadFilecode.clear();
125         m_downloadChecksum.clear();
126 }
127
128 bool MUtils::UpdateCheckerInfo::isComplete(void)
129 {
130         return (this->m_buildNo > 0) &&
131                 (this->m_buildDate.year() >= 2010) &&
132                 (!this->m_downloadSite.isEmpty()) &&
133                 (!this->m_downloadAddress.isEmpty()) &&
134                 (!this->m_downloadFilename.isEmpty()) &&
135                 (!this->m_downloadFilecode.isEmpty()) &&
136                 (!this->m_downloadChecksum.isEmpty());
137 }
138
139 ////////////////////////////////////////////////////////////
140 // Constructor & Destructor
141 ////////////////////////////////////////////////////////////
142
143 MUtils::UpdateChecker::UpdateChecker(const QString &binCurl, const QString &binGnuPG, const QString &binKeys, const QString &applicationId, const quint32 &installedBuildNo, const bool betaUpdates, const bool testMode)
144 :
145         m_updateInfo(new UpdateCheckerInfo()),
146         m_binaryCurl(binCurl),
147         m_binaryGnuPG(binGnuPG),
148         m_binaryKeys(binKeys),
149         m_applicationId(applicationId),
150         m_installedBuildNo(installedBuildNo),
151         m_betaUpdates(betaUpdates),
152         m_testMode(testMode),
153         m_maxProgress(MIN_CONNSCORE + 5),
154         m_environment(initEnvVars())
155 {
156         m_status = UpdateStatus_NotStartedYet;
157         m_progress = 0;
158
159         if(m_binaryCurl.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryKeys.isEmpty())
160         {
161                 MUTILS_THROW("Tools not initialized correctly!");
162         }
163 }
164
165 MUtils::UpdateChecker::~UpdateChecker(void)
166 {
167 }
168
169 ////////////////////////////////////////////////////////////
170 // Public slots
171 ////////////////////////////////////////////////////////////
172
173 void MUtils::UpdateChecker::start(Priority priority)
174 {
175         m_success.fetchAndStoreOrdered(0);
176         m_cancelled.fetchAndStoreOrdered(0);
177         QThread::start(priority);
178 }
179
180 ////////////////////////////////////////////////////////////
181 // Protected functions
182 ////////////////////////////////////////////////////////////
183
184 void MUtils::UpdateChecker::run(void)
185 {
186         qDebug("Update checker thread started!");
187         MUTILS_EXCEPTION_HANDLER(m_testMode ? testMirrorsList() : checkForUpdates());
188         qDebug("Update checker thread completed.");
189 }
190
191 void MUtils::UpdateChecker::checkForUpdates(void)
192 {
193         // ----- Initialization ----- //
194
195         m_updateInfo->resetInfo();
196         setProgress(0);
197
198         // ----- Test Internet Connection ----- //
199
200         log("Checking your Internet connection...", "");
201         setStatus(UpdateStatus_CheckingConnection);
202
203         const int networkStatus = OS::network_status();
204         if(networkStatus == OS::NETWORK_TYPE_NON)
205         {
206                 if (!MUtils::OS::arguments().contains("ignore-network-status"))
207                 {
208                         log("Operating system reports that the computer is currently offline !!!");
209                         setProgress(m_maxProgress);
210                         setStatus(UpdateStatus_ErrorNoConnection);
211                         return;
212                 }
213         }
214         
215         msleep(333);
216         setProgress(1);
217
218         // ----- Test Known Hosts Connectivity ----- //
219
220         int connectionScore = 0;
221         QStringList mirrorList = buildRandomList(known_hosts);
222
223         for(int connectionTimout = 1000; connectionTimout <= MAX_CONN_TIMEOUT; connectionTimout *= 2)
224         {
225                 QElapsedTimer elapsedTimer;
226                 elapsedTimer.start();
227                 const int globalTimout = 2 * MIN_CONNSCORE * connectionTimout;
228                 while (!elapsedTimer.hasExpired(globalTimout))
229                 {
230                         const QString hostName = mirrorList.takeFirst();
231                         if (tryContactHost(hostName, connectionTimout))
232                         {
233                                 setProgress(1 + (connectionScore += 1));
234                                 elapsedTimer.restart();
235                                 if (connectionScore >= MIN_CONNSCORE)
236                                 {
237                                         goto endLoop; /*success*/
238                                 }
239                         }
240                         else
241                         {
242                                 mirrorList.append(hostName); /*re-schedule*/
243                         }
244                         CHECK_CANCELLED();
245                         msleep(1);
246                 }
247         }
248
249 endLoop:
250         if(connectionScore < MIN_CONNSCORE)
251         {
252                 log("", "Connectivity test has failed: Internet connection appears to be broken!");
253                 setProgress(m_maxProgress);
254                 setStatus(UpdateStatus_ErrorConnectionTestFailed);
255                 return;
256         }
257
258         // ----- Fetch Update Info From Server ----- //
259
260         log("----", "", "Internet connection is operational, checking for updates online...");
261         setStatus(UpdateStatus_FetchingUpdates);
262
263         int mirrorCount = 0;
264         mirrorList = buildRandomList(update_mirrors);
265
266         while(!mirrorList.isEmpty())
267         {
268                 const QString currentMirror = mirrorList.takeFirst();
269                 const bool isQuick = (mirrorCount++ < QUICK_MIRRORS);
270                 if(tryUpdateMirror(m_updateInfo.data(), currentMirror, isQuick))
271                 {
272                         m_success.ref(); /*success*/
273                         break;
274                 }
275                 if (isQuick)
276                 {
277                         mirrorList.append(currentMirror); /*re-schedule*/
278                 }
279                 CHECK_CANCELLED();
280                 msleep(1);
281         }
282
283         msleep(333);
284         setProgress(MIN_CONNSCORE + 5);
285
286         // ----- Generate final result ----- //
287
288         if(MUTILS_BOOLIFY(m_success))
289         {
290                 if(m_updateInfo->m_buildNo > m_installedBuildNo)
291                 {
292                         setStatus(UpdateStatus_CompletedUpdateAvailable);
293                 }
294                 else if(m_updateInfo->m_buildNo == m_installedBuildNo)
295                 {
296                         setStatus(UpdateStatus_CompletedNoUpdates);
297                 }
298                 else
299                 {
300                         setStatus(UpdateStatus_CompletedNewVersionOlder);
301                 }
302         }
303         else
304         {
305                 setStatus(UpdateStatus_ErrorFetchUpdateInfo);
306         }
307 }
308
309 void MUtils::UpdateChecker::testMirrorsList(void)
310 {
311         QStringList mirrorList;
312         for(int i = 0; update_mirrors[i]; i++)
313         {
314                 mirrorList << QString::fromLatin1(update_mirrors[i]);
315         }
316
317         // ----- Test update mirrors ----- //
318
319         qDebug("\n[Mirror Sites]");
320         log("Testing all known mirror sites...", "", "---");
321
322         UpdateCheckerInfo updateInfo;
323         while (!mirrorList.isEmpty())
324         {
325                 const QString currentMirror = mirrorList.takeFirst();
326                 bool success = false;
327                 qDebug("Testing: %s", MUTILS_L1STR(currentMirror));
328                 log("", "Testing:", currentMirror, "");
329                 for (quint8 attempt = 0; attempt < 3; ++attempt)
330                 {
331                         updateInfo.resetInfo();
332                         if (tryUpdateMirror(&updateInfo, currentMirror, (!attempt)))
333                         {
334                                 success = true;
335                                 break;
336                         }
337                 }
338                 if (!success)
339                 {
340                         qWarning("\nUpdate mirror seems to be unavailable:\n%s\n", MUTILS_L1STR(currentMirror));
341                 }
342                 log("", "---");
343         }
344
345         // ----- Test known hosts ----- //
346
347         QStringList knownHostList;
348         for (int i = 0; known_hosts[i]; i++)
349         {
350                 knownHostList << QString::fromLatin1(known_hosts[i]);
351         }
352
353         qDebug("\n[Known Hosts]");
354         log("Testing all known hosts...", "", "---");
355
356         while(!knownHostList.isEmpty())
357         {
358                 const QString currentHost = knownHostList.takeFirst();
359                 qDebug("Testing: %s", MUTILS_L1STR(currentHost));
360                 log(QLatin1String(""), "Testing:", currentHost, "");
361                 if (!tryContactHost(currentHost, DOWNLOAD_TIMEOUT))
362                 {
363                         qWarning("\nConnectivity test FAILED on the following host:\n%s\n", MUTILS_L1STR(currentHost));
364                 }
365                 log("---");
366         }
367 }
368
369 ////////////////////////////////////////////////////////////
370 // PRIVATE FUNCTIONS
371 ////////////////////////////////////////////////////////////
372
373 void MUtils::UpdateChecker::setStatus(const int status)
374 {
375         if(m_status != status)
376         {
377                 m_status = status;
378                 emit statusChanged(status);
379         }
380 }
381
382 void MUtils::UpdateChecker::setProgress(const int progress)
383 {
384         const int value = qBound(0, progress, m_maxProgress);
385         if(m_progress != value)
386         {
387                 emit progressChanged(m_progress = value);
388         }
389 }
390
391 void MUtils::UpdateChecker::log(const QString &str1, const QString &str2, const QString &str3, const QString &str4)
392 {
393         LOG_MESSAGE_HELPER(str1);
394         LOG_MESSAGE_HELPER(str2);
395         LOG_MESSAGE_HELPER(str3);
396         LOG_MESSAGE_HELPER(str4);
397 }
398
399 bool MUtils::UpdateChecker::tryUpdateMirror(UpdateCheckerInfo *updateInfo, const QString &url, const bool &quick)
400 {
401         bool success = false;
402         log("", "Trying update mirror:", url, "");
403
404         if (quick)
405         {
406                 setProgress(MIN_CONNSCORE + 1);
407                 if (!tryContactHost(QUrl(url).host(), (MAX_CONN_TIMEOUT / 8)))
408                 {
409                         log("", "Mirror is too slow, skipping!");
410                         return false;
411                 }
412         }
413
414         const QString randPart = next_rand_str();
415         const QString outFileVers = QString("%1/%2.ver").arg(temp_folder(), randPart);
416         const QString outFileSign = QString("%1/%2.sig").arg(temp_folder(), randPart);
417
418         if (!getUpdateInfo(url, outFileVers, outFileSign))
419         {
420                 log("", "Oops: Download of update information has failed!");
421                 goto cleanUp;
422         }
423
424         log("Download completed, verifying signature:", "");
425         setProgress(MIN_CONNSCORE + 4);
426         if (!checkSignature(outFileVers, outFileSign))
427         {
428                 log("", "Bad signature detected, take care !!!");
429                 goto cleanUp;
430         }
431
432         log("", "Signature is valid, parsing update information:", "");
433         success = parseVersionInfo(outFileVers, updateInfo);
434
435 cleanUp:
436         QFile::remove(outFileVers);
437         QFile::remove(outFileSign);
438         return success;
439 }
440
441 bool MUtils::UpdateChecker::getUpdateInfo(const QString &url, const QString &outFileVers, const QString &outFileSign)
442 {
443         log("Downloading update information:", "");
444         setProgress(MIN_CONNSCORE + 2);
445         if(getFile(QUrl(QString("%1%2").arg(url, MIRROR_URL_POSTFIX[m_betaUpdates ? 1 : 0])), outFileVers))
446         {
447                 if (!m_cancelled)
448                 {
449                         log( "Downloading signature file:", "");
450                         setProgress(MIN_CONNSCORE + 3);
451                         if (getFile(QUrl(QString("%1%2.sig2").arg(url, MIRROR_URL_POSTFIX[m_betaUpdates ? 1 : 0])), outFileSign))
452                         {
453                                 return true; /*completed*/
454                         }
455                 }
456         }
457         return false;
458 }
459
460 //----------------------------------------------------------
461 // PARSE UPDATE INFO
462 //----------------------------------------------------------
463
464 bool MUtils::UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *const updateInfo)
465 {
466         updateInfo->resetInfo();
467
468         QFile data(file);
469         if(!data.open(QIODevice::ReadOnly))
470         {
471                 qWarning("Cannot open update info file for reading!");
472                 return false;
473         }
474         
475         QDate updateInfoDate;
476         int sectionId = 0;
477         QRegExp regex_sec("^\\[(.+)\\]$"), regex_val("^([^=]+)=(.+)$");
478
479         while(!data.atEnd())
480         {
481                 QString line = QString::fromLatin1(data.readLine()).trimmed();
482                 if (regex_sec.indexIn(line) >= 0)
483                 {
484                         sectionId = parseSectionHeaderStr(regex_sec.cap(1).trimmed());
485                         continue;
486                 }
487                 if (regex_val.indexIn(line) >= 0)
488                 {
489                         const QString key = regex_val.cap(1).trimmed();
490                         const QString val = regex_val.cap(2).trimmed();
491                         switch (sectionId)
492                         {
493                         case 1:
494                                 parseHeaderValue(key, val, updateInfoDate);
495                                 break;
496                         case 2:
497                                 parseUpdateInfoValue(key, val, updateInfo);
498                                 break;
499                         }
500                 }
501         }
502
503         if(updateInfoDate.isValid())
504         {
505                 const QDate expiredDate = updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS);
506                 if (expiredDate < OS::current_date())
507                 {
508                         log(QString("WARNING: Update information has expired at %1!").arg(expiredDate.toString(Qt::ISODate)));
509                         goto cleanUp;
510                 }
511         }
512         else
513         {
514                 log("WARNING: Timestamp is missing from update information header!");
515                 goto cleanUp;
516         }
517         
518         if(!updateInfo->isComplete())
519         {
520                 log("WARNING: Update information is incomplete!");
521                 goto cleanUp;
522         }
523
524         log("", "Success: Update information is complete.");
525         return true; /*success*/
526
527 cleanUp:
528         updateInfo->resetInfo();
529         return false;
530 }
531
532 int MUtils::UpdateChecker::parseSectionHeaderStr(const QString &name)
533 {
534         log(QString("Sec: [%1]").arg(name));
535
536         if (STRICMP(name, HEADER_ID))
537         {
538                 return 1;
539         }
540         if (STRICMP(name, m_applicationId))
541         {
542                 return 2;
543         }
544
545         //Unknonw section encountered!
546         return 0;
547 }
548
549 void MUtils::UpdateChecker::parseHeaderValue(const QString &key, const QString &val, QDate &updateInfoDate)
550 {
551         log(QString("Hdr: \"%1\"=\"%2\"").arg(key, val));
552
553         if (STRICMP(key, "TimestampCreated"))
554         {
555                 const QDate temp = QDate::fromString(val, Qt::ISODate);
556                 if (temp.isValid())
557                 {
558                         updateInfoDate = temp;
559                 }
560                 return;
561         }
562
563         //Unknown entry encountered!
564         qWarning("Unknown header value: %s", MUTILS_L1STR(key));
565 }
566
567 void MUtils::UpdateChecker::parseUpdateInfoValue(const QString &key, const QString &val, UpdateCheckerInfo *const updateInfo)
568 {
569         log(QString("Val: \"%1\"=\"%2\"").arg(key, val));
570
571         if (STRICMP(key, "BuildNo"))
572         {
573                 bool ok = false;
574                 const unsigned int temp = val.toUInt(&ok);
575                 if (ok)
576                 {
577                         updateInfo->m_buildNo = temp;
578                 }
579                 return;
580         }
581         if (STRICMP(key, "BuildDate"))
582         {
583                 const QDate temp = QDate::fromString(val, Qt::ISODate);
584                 if (temp.isValid())
585                 {
586                         updateInfo->m_buildDate = temp;
587                 }
588                 return;
589         }
590         if (STRICMP(key, "DownloadSite"))
591         {
592                 updateInfo->m_downloadSite = val;
593                 return;
594         }
595         if (STRICMP(key, "DownloadAddress"))
596         {
597                 updateInfo->m_downloadAddress = val;
598                 return;
599         }
600         if (STRICMP(key, "DownloadFilename"))
601         {
602                 updateInfo->m_downloadFilename = val;
603                 return;
604         }
605         if (STRICMP(key, "DownloadFilecode"))
606         {
607                 updateInfo->m_downloadFilecode = val;
608                 return;
609         }
610         if (STRICMP(key, "DownloadChecksum"))
611         {
612                 updateInfo->m_downloadChecksum = val;
613                 return;
614         }
615
616         //Unknown entry encountered!
617         qWarning("Unknown update value: %s", MUTILS_L1STR(key));
618 }
619
620 //----------------------------------------------------------
621 // EXTERNAL TOOLS
622 //----------------------------------------------------------
623
624 bool MUtils::UpdateChecker::getFile(const QUrl &url, const QString &outFile, const unsigned int maxRedir)
625 {
626         QFileInfo output(outFile);
627         output.setCaching(false);
628
629         if (output.exists())
630         {
631                 QFile::remove(output.canonicalFilePath());
632                 if (output.exists())
633                 {
634                         qWarning("Existing output file could not be found!");
635                         return false;
636                 }
637         }
638         
639         QStringList args(QLatin1String("-vsSNqkfL"));
640         args << "-m" << QString::number(DOWNLOAD_TIMEOUT / 1000);
641         args << "--max-redirs" << QString::number(maxRedir);
642         args << "-A" << USER_AGENT_STR;
643         args << "-e" << QString("%1://%2/;auto").arg(url.scheme(), url.host());
644         args << "-o" << output.fileName() << url.toString();
645
646         return execCurl(args, output.absolutePath(), DOWNLOAD_TIMEOUT);
647 }
648
649 bool MUtils::UpdateChecker::tryContactHost(const QString &hostname, const int &timeoutMsec)
650 {
651         log(QString("Connecting to host: %1").arg(hostname), "");
652
653         QStringList args(QLatin1String("-vsSNqkI"));
654         args << "-m" << QString::number(qMax(1, timeoutMsec / 1000));
655         args << "-A" << USER_AGENT_STR;
656         args << "-o" << OS::null_device() << QString("http://%1/").arg(hostname);
657         
658         return execCurl(args, temp_folder(), timeoutMsec);
659 }
660
661 bool MUtils::UpdateChecker::checkSignature(const QString &file, const QString &signature)
662 {
663         if (QFileInfo(file).absolutePath().compare(QFileInfo(signature).absolutePath(), Qt::CaseInsensitive) != 0)
664         {
665                 qWarning("CheckSignature: File and signature should be in same folder!");
666                 return false;
667         }
668
669         QString keyRingPath(m_binaryKeys);
670         bool removeKeyring = false;
671         if (QFileInfo(file).absolutePath().compare(QFileInfo(m_binaryKeys).absolutePath(), Qt::CaseInsensitive) != 0)
672         {
673                 keyRingPath = make_temp_file(QFileInfo(file).absolutePath(), "gpg");
674                 removeKeyring = true;
675                 if (!QFile::copy(m_binaryKeys, keyRingPath))
676                 {
677                         qWarning("CheckSignature: Failed to copy the key-ring file!");
678                         return false;
679                 }
680         }
681
682         QStringList args;
683         args << QStringList() << "--homedir" << ".";
684         args << "--keyring" << QFileInfo(keyRingPath).fileName();
685         args << QFileInfo(signature).fileName();
686         args << QFileInfo(file).fileName();
687
688         const int exitCode = execProcess(m_binaryGnuPG, args, QFileInfo(file).absolutePath(), DOWNLOAD_TIMEOUT);
689         if (exitCode != INT_MAX)
690         {
691                 log(QString().sprintf("Exited with code %d", exitCode));
692         }
693
694         if (removeKeyring)
695         {
696                 remove_file(keyRingPath);
697         }
698
699         return (exitCode == 0); /*completed*/
700 }
701
702 bool MUtils::UpdateChecker::execCurl(const QStringList &args, const QString &workingDir, const int timeout)
703 {
704         const int exitCode = execProcess(m_binaryCurl, args, workingDir, timeout + (timeout / 2));
705         if (exitCode != INT_MAX)
706         {
707                 switch (exitCode)
708                 {
709                         case -1:
710                         case  0: log(QLatin1String("DONE: Transfer completed successfully."), "");                     break;
711                         case  6: log(QLatin1String("ERROR: Remote host could not be resolved!"), "");                  break;
712                         case  7: log(QLatin1String("ERROR: Connection to remote host could not be established!"), ""); break;
713                         case 22: log(QLatin1String("ERROR: Requested URL was not found or returned an error!"), "");   break;
714                         case 28: log(QLatin1String("ERROR: Operation timed out !!!"), "");                             break;
715                         default: log(QString().sprintf("ERROR: Terminated with unknown code %d", exitCode), "");       break;
716                 }
717         }
718
719         return (exitCode == 0); /*completed*/
720 }
721
722 int MUtils::UpdateChecker::execProcess(const QString &programFile, const QStringList &args, const QString &workingDir, const int timeout)
723 {
724         QProcess process;
725         init_process(process, workingDir, true, NULL, m_environment.data());
726
727         QEventLoop loop;
728         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
729         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
730         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
731
732         QTimer timer;
733         timer.setSingleShot(true);
734         connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
735
736         process.start(programFile, args);
737         if (!process.waitForStarted())
738         {
739                 log("PROCESS FAILED TO START !!!", "");
740                 qWarning("WARNING: %s process could not be created!", MUTILS_UTF8(QFileInfo(programFile).fileName()));
741                 return INT_MAX; /*failed to start*/
742         }
743
744         bool bAborted = false;
745         timer.start(qMax(timeout, 1500));
746
747         while (process.state() != QProcess::NotRunning)
748         {
749                 loop.exec();
750                 while (process.canReadLine())
751                 {
752                         const QString line = QString::fromLatin1(process.readLine()).simplified();
753                         if (line.length() > 1)
754                         {
755                                 log(line);
756                         }
757                 }
758                 const bool bCancelled = MUTILS_BOOLIFY(m_cancelled);
759                 if (bAborted = (bCancelled || ((!timer.isActive()) && (!process.waitForFinished(125)))))
760                 {
761                         log(bCancelled ? "CANCELLED BY USER !!!" : "PROCESS TIMEOUT !!!", "");
762                         qWarning("WARNING: %s process %s!", MUTILS_UTF8(QFileInfo(programFile).fileName()), bCancelled ? "cancelled" : "timed out");
763                         break; /*abort process*/
764                 }
765         }
766
767         timer.stop();
768         timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
769
770         if (bAborted)
771         {
772                 process.kill();
773                 process.waitForFinished(-1);
774         }
775
776         while (process.canReadLine())
777         {
778                 const QString line = QString::fromLatin1(process.readLine()).simplified();
779                 if (line.length() > 1)
780                 {
781                         log(line);
782                 }
783         }
784
785         return bAborted ? INT_MAX : process.exitCode();
786 }
787
788 ////////////////////////////////////////////////////////////
789 // SLOTS
790 ////////////////////////////////////////////////////////////
791
792 /*NONE*/
793
794 ////////////////////////////////////////////////////////////
795 // EVENTS
796 ////////////////////////////////////////////////////////////
797
798 /*NONE*/