Update checker: Try first couple of mirrors in "quick" mode (reduced connection timeo...
[MUtilities.git] / src / UpdateChecker.cpp
blob158fba58e4a47197db9fa8ef22c343e19b17cc4a
1 ///////////////////////////////////////////////////////////////////////////////
2 // MuldeR's Utilities for Qt
3 // Copyright (C) 2004-2017 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.
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
19 // http://www.gnu.org/licenses/lgpl-2.1.txt
20 //////////////////////////////////////////////////////////////////////////////////
22 #include <MUtils/Global.h>
23 #include <MUtils/UpdateChecker.h>
24 #include <MUtils/OSSupport.h>
25 #include <MUtils/Exception.h>
27 #include <QStringList>
28 #include <QFile>
29 #include <QFileInfo>
30 #include <QProcess>
31 #include <QUrl>
32 #include <QEventLoop>
33 #include <QTimer>
34 #include <QElapsedTimer>
36 using namespace MUtils;
38 ///////////////////////////////////////////////////////////////////////////////
39 // CONSTANTS
40 ///////////////////////////////////////////////////////////////////////////////
42 static const char *header_id = "!Update";
44 static const char *mirror_url_postfix[] =
46 "update.ver",
47 "update_beta.ver",
48 NULL
51 static const char *update_mirrors[] =
53 "http://muldersoft.com/",
54 "http://mulder.bplaced.net/",
55 "http://mulder.6te.net/",
56 "http://mulder.webuda.com/",
57 "http://mulder.pe.hu/",
58 "http://muldersoft.square7.ch/",
59 "http://muldersoft.co.nf/",
60 "http://muldersoft.eu.pn/",
61 "http://muldersoft.lima-city.de/",
62 "http://www.muldersoft.keepfree.de/",
63 "http://lamexp.sourceforge.net/",
64 "http://lordmulder.github.io/LameXP/",
65 "http://muldersoft.bitbucket.io/", //http://lord_mulder.bitbucket.org/
66 "http://www.tricksoft.de/",
67 "http://repo.or.cz/LameXP.git/blob_plain/gh-pages:/",
68 "http://gitlab.com/lamexp/lamexp/raw/gh-pages/",
69 NULL
72 static const char *known_hosts[] = //Taken form: http://www.alexa.com/topsites !!!
74 "www.163.com",
75 "www.7-zip.org",
76 "www.ac3filter.net",
77 "clbianco.altervista.org",
78 "status.aws.amazon.com",
79 "build.antergos.com",
80 "www.aol.com",
81 "www.apache.org",
82 "www.apple.com",
83 "www.adobe.com",
84 "archive.org",
85 "www.artlebedev.ru",
86 "web.audacityteam.org",
87 "status.automattic.com",
88 "www.avidemux.org",
89 "www.babylon.com",
90 "www.baidu.com",
91 "bandcamp.com",
92 "www.bbc.co.uk",
93 "www.berlios.de",
94 "www.bing.com",
95 "www.bingeandgrab.com",
96 "www.bucketheadpikes.com",
97 "www.buckethead-coop.com",
98 "www.buzzfeed.com",
99 "www.cam.ac.uk",
100 "www.ccc.de",
101 "home.cern",
102 "www.citizeninsomniac.com",
103 "www.cnet.com",
104 "cnzz.com",
105 "www.cuhk.edu.hk",
106 "www.codeplex.com",
107 "www.codeproject.com",
108 "www.der-postillon.com",
109 "www.ebay.com",
110 "www.equation.com",
111 "www.farbrausch.de",
112 "fc2.com",
113 "fedoraproject.org",
114 "blog.fefe.de",
115 "www.ffmpeg.org",
116 "blog.flickr.net",
117 "www.fraunhofer.de",
118 "free-codecs.com",
119 "git-scm.com",
120 "doc.gitlab.com",
121 "www.gmx.net",
122 "news.gnome.org",
123 "www.gnu.org",
124 "go.com",
125 "code.google.com",
126 "haali.su",
127 "www.harvard.edu",
128 "www.heise.de",
129 "www.helmholtz.de",
130 "www.huffingtonpost.co.uk",
131 "www.hu-berlin.de",
132 "www.iana.org",
133 "www.imdb.com",
134 "www.imgburn.com",
135 "imgur.com",
136 "www.iuj.ac.jp",
137 "www.jd.com",
138 "www.jiscdigitalmedia.ac.uk",
139 "kannmanumdieuhrzeitschonnbierchentrinken.de",
140 "mirrors.kernel.org",
141 "komisar.gin.by",
142 "lame.sourceforge.net",
143 "www.libav.org",
144 "blog.linkedin.com",
145 "www.linuxmint.com",
146 "www.livedoor.com",
147 "www.livejournal.com",
148 "longplayer.org",
149 "go.mail.ru",
150 "marknelson.us",
151 "www.mediafire.com",
152 "web.mit.edu",
153 "www.mod-technologies.com",
154 "ftp.mozilla.org",
155 "www.mpg.de",
156 "mplayerhq.hu",
157 "www.msn.com",
158 "wiki.multimedia.cx",
159 "www.nch.com.au",
160 "neocities.org",
161 "mirror.netcologne.de",
162 "oss.netfarm.it",
163 "blog.netflix.com",
164 "netrenderer.de",
165 "www.nytimes.com",
166 "www.opera.com",
167 "www.oxford.gov.uk",
168 "www.partha.com",
169 "pastebin.com",
170 "pastie.org",
171 "portableapps.com",
172 "www.portablefreeware.com",
173 "support.proboards.com",
174 "www.qq.com",
175 "www.qt.io",
176 "www.quakelive.com",
177 "rationalqm.us",
178 "www.rwth-aachen.de",
179 "www.seamonkey-project.org",
180 "selfhtml.org",
181 "www.sina.com.cn",
182 "www.sohu.com",
183 "help.sogou.com",
184 "sourceforge.net",
185 "www.spiegel.de",
186 "www.sputnikmusic.com",
187 "stackoverflow.com",
188 "www.stanford.edu",
189 "www.t-online.de",
190 "www.tagesschau.de",
191 "tdm-gcc.tdragon.net",
192 "www.tdrsmusic.com",
193 "tu-dresden.de",
194 "www.ubuntu.com",
195 "www.uol.com.br",
196 "www.videohelp.com",
197 "www.videolan.org",
198 "virtualdub.org",
199 "blog.virustotal.com",
200 "www.vkgoeswild.com",
201 "www.warr.org",
202 "www.weibo.com",
203 "status.wikimedia.org",
204 "www.winamp.com",
205 "www.winhoros.de",
206 "wpde.org",
207 "x265.org",
208 "xhmikosr.1f0.de",
209 "xiph.org",
210 "us.mail.yahoo.com",
211 "www.youtube.com",
212 "www.zedo.com",
213 "ffmpeg.zeranoe.com",
214 NULL
217 static const int MIN_CONNSCORE = 5;
218 static const int QUICK_MIRRORS = 3;
219 static const int MAX_CONN_TIMEOUT = 8000;
220 static const int DOWNLOAD_TIMEOUT = 30000;
222 static const int VERSION_INFO_EXPIRES_MONTHS = 6;
223 static char *USER_AGENT_STR = "Mozilla/5.0 (X11; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0"; /*use something innocuous*/
225 #define CHECK_CANCELLED() do \
227 if(m_cancelled) \
229 m_success = false; \
230 log("", "Update check has been cancelled by user!"); \
231 setProgress(m_maxProgress); \
232 setStatus(UpdateStatus_CancelledByUser); \
233 return; \
236 while(0)
238 ////////////////////////////////////////////////////////////
239 // Helper Functions
240 ////////////////////////////////////////////////////////////
242 static int getMaxProgress(void)
244 int counter = 0;
245 while (update_mirrors[counter])
247 counter++;
249 counter += MIN_CONNSCORE + QUICK_MIRRORS + 2;
250 return counter; ;
253 static QStringList buildRandomList(const char *const values[])
255 QStringList list;
256 for (int index = 0; values[index]; index++)
258 const int pos = next_rand_u32() % (index + 1);
259 list.insert(pos, QString::fromLatin1(values[index]));
261 return list;
264 ////////////////////////////////////////////////////////////
265 // Update Info Class
266 ////////////////////////////////////////////////////////////
268 UpdateCheckerInfo::UpdateCheckerInfo(void)
270 resetInfo();
273 void UpdateCheckerInfo::resetInfo(void)
275 m_buildNo = 0;
276 m_buildDate.setDate(1900, 1, 1);
277 m_downloadSite.clear();
278 m_downloadAddress.clear();
279 m_downloadFilename.clear();
280 m_downloadFilecode.clear();
281 m_downloadChecksum.clear();
284 bool UpdateCheckerInfo::isComplete(void)
286 if(this->m_buildNo < 1) return false;
287 if(this->m_buildDate.year() < 2010) return false;
288 if(this->m_downloadSite.isEmpty()) return false;
289 if(this->m_downloadAddress.isEmpty()) return false;
290 if(this->m_downloadFilename.isEmpty()) return false;
291 if(this->m_downloadFilecode.isEmpty()) return false;
292 if(this->m_downloadChecksum.isEmpty()) return false;
294 return true;
297 ////////////////////////////////////////////////////////////
298 // Constructor & Destructor
299 ////////////////////////////////////////////////////////////
301 UpdateChecker::UpdateChecker(const QString &binWGet, const QString &binNC, const QString &binGnuPG, const QString &binKeys, const QString &applicationId, const quint32 &installedBuildNo, const bool betaUpdates, const bool testMode)
303 m_updateInfo(new UpdateCheckerInfo()),
304 m_binaryWGet(binWGet),
305 m_binaryNC(binNC),
306 m_binaryGnuPG(binGnuPG),
307 m_binaryKeys(binKeys),
308 m_applicationId(applicationId),
309 m_installedBuildNo(installedBuildNo),
310 m_betaUpdates(betaUpdates),
311 m_testMode(testMode),
312 m_maxProgress(getMaxProgress())
314 m_success = m_cancelled = false;
315 m_status = UpdateStatus_NotStartedYet;
316 m_progress = 0;
318 if(m_binaryWGet.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryKeys.isEmpty())
320 MUTILS_THROW("Tools not initialized correctly!");
324 UpdateChecker::~UpdateChecker(void)
328 ////////////////////////////////////////////////////////////
329 // Public slots
330 ////////////////////////////////////////////////////////////
332 void UpdateChecker::start(Priority priority)
334 m_cancelled = m_success = false;
335 QThread::start(priority);
338 ////////////////////////////////////////////////////////////
339 // Protected functions
340 ////////////////////////////////////////////////////////////
342 void UpdateChecker::run(void)
344 qDebug("Update checker thread started!");
345 MUTILS_EXCEPTION_HANDLER(m_testMode ? testKnownHosts() : checkForUpdates());
346 qDebug("Update checker thread completed.");
349 void UpdateChecker::checkForUpdates(void)
351 // ----- Initialization ----- //
353 m_updateInfo->resetInfo();
354 setProgress(0);
356 // ----- Test Internet Connection ----- //
358 log("Checking internet connection...", "");
359 setStatus(UpdateStatus_CheckingConnection);
361 const int networkStatus = OS::network_status();
362 if(networkStatus == OS::NETWORK_TYPE_NON)
364 log("Operating system reports that the computer is currently offline !!!");
365 setProgress(m_maxProgress);
366 setStatus(UpdateStatus_ErrorNoConnection);
367 return;
370 setProgress(1);
372 // ----- Test Known Hosts Connectivity ----- //
374 int connectionScore = 0;
375 QStringList mirrorList = buildRandomList(known_hosts);
377 for(int connectionTimout = 125; connectionTimout <= MAX_CONN_TIMEOUT; connectionTimout *= 2)
379 QElapsedTimer elapsedTimer;
380 elapsedTimer.start();
381 const int globalTimout = 2 * MIN_CONNSCORE * connectionTimout;
382 while (!elapsedTimer.hasExpired(globalTimout))
384 const QString hostName = mirrorList.takeFirst();
385 if (tryContactHost(hostName, connectionTimout))
387 connectionScore += 1;
388 setProgress(qBound(1, connectionScore + 1, MIN_CONNSCORE + 1));
389 elapsedTimer.restart();
390 if (connectionScore >= MIN_CONNSCORE)
392 goto endLoop; /*success*/
395 else
397 mirrorList.append(hostName); /*re-schedule*/
399 CHECK_CANCELLED();
400 msleep(1);
404 endLoop:
405 if(connectionScore < MIN_CONNSCORE)
407 log("", "Connectivity test has failed: Internet connection appears to be broken!");
408 setProgress(m_maxProgress);
409 setStatus(UpdateStatus_ErrorConnectionTestFailed);
410 return;
413 // ----- Fetch Update Info From Server ----- //
415 log("----", "", "Checking for updates online...");
416 setStatus(UpdateStatus_FetchingUpdates);
418 int mirrorCount = 0;
419 mirrorList = buildRandomList(update_mirrors);
421 while(!mirrorList.isEmpty())
423 setProgress(m_progress + 1);
424 const QString currentMirror = mirrorList.takeFirst();
425 const bool isQuick = (mirrorCount++ < QUICK_MIRRORS);
426 if(tryUpdateMirror(m_updateInfo.data(), currentMirror, isQuick))
428 m_success = true; /*success*/
429 break;
431 if (isQuick)
433 mirrorList.append(currentMirror); /*re-schedule*/
435 CHECK_CANCELLED();
436 msleep(1);
439 while (m_progress < m_maxProgress)
441 msleep(16);
442 setProgress(m_progress + 1);
443 CHECK_CANCELLED();
446 // ----- Generate final result ----- //
448 if(m_success)
450 if(m_updateInfo->m_buildNo > m_installedBuildNo)
452 setStatus(UpdateStatus_CompletedUpdateAvailable);
454 else if(m_updateInfo->m_buildNo == m_installedBuildNo)
456 setStatus(UpdateStatus_CompletedNoUpdates);
458 else
460 setStatus(UpdateStatus_CompletedNewVersionOlder);
463 else
465 setStatus(UpdateStatus_ErrorFetchUpdateInfo);
469 void UpdateChecker::testKnownHosts(void)
471 QStringList hostList;
472 for(int i = 0; known_hosts[i]; i++)
474 hostList << QString::fromLatin1(known_hosts[i]);
477 qDebug("\n[Known Hosts]");
478 log("Testing all known hosts...", "", "---");
480 int hostCount = hostList.count();
481 while(!hostList.isEmpty())
483 QString currentHost = hostList.takeFirst();
484 qDebug("Testing: %s", currentHost.toLatin1().constData());
485 log("", "Testing:", currentHost, "");
486 if (!tryContactHost(currentHost, DOWNLOAD_TIMEOUT))
488 qWarning("\nConnectivity test FAILED on the following host:\n%s\n", currentHost.toLatin1().constData());
490 log("", "---");
494 ////////////////////////////////////////////////////////////
495 // PRIVATE FUNCTIONS
496 ////////////////////////////////////////////////////////////
498 void UpdateChecker::setStatus(const int status)
500 if(m_status != status)
502 m_status = status;
503 emit statusChanged(status);
507 void UpdateChecker::setProgress(const int progress)
509 if(m_progress != progress)
511 m_progress = progress;
512 emit progressChanged(progress);
516 void UpdateChecker::log(const QString &str1, const QString &str2, const QString &str3, const QString &str4)
518 if(!str1.isNull()) emit messageLogged(str1);
519 if(!str2.isNull()) emit messageLogged(str2);
520 if(!str3.isNull()) emit messageLogged(str3);
521 if(!str4.isNull()) emit messageLogged(str4);
524 bool UpdateChecker::tryUpdateMirror(UpdateCheckerInfo *updateInfo, const QString &url, const bool &quick)
526 bool success = false;
527 log("", "Trying mirror:", url, "");
529 if (!tryContactHost(QUrl(url).host(), quick ? (MAX_CONN_TIMEOUT / 10) : MAX_CONN_TIMEOUT))
531 log("", quick ? "Mirror is too slow!" :"Mirror is unreachable!");
532 return false;
535 const QString randPart = next_rand_str();
536 const QString outFileVers = QString("%1/%2.ver").arg(temp_folder(), randPart);
537 const QString outFileSign = QString("%1/%2.sig").arg(temp_folder(), randPart);
539 if (getUpdateInfo(url, outFileVers, outFileSign))
541 log("", "Download okay, checking signature:");
542 if (checkSignature(outFileVers, outFileSign))
544 log("", "Signature okay, parsing info:");
545 success = parseVersionInfo(outFileVers, updateInfo);
547 else
549 log("", "Bad signature, take care!");
552 else
554 log("", "Download has failed!");
557 QFile::remove(outFileVers);
558 QFile::remove(outFileSign);
560 return success;
563 bool UpdateChecker::getUpdateInfo(const QString &url, const QString &outFileVers, const QString &outFileSign)
565 log("Downloading update info:");
566 if(!getFile(QString("%1%2" ).arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileVers))
568 return false;
571 log("", "Downloading signature:");
572 if(!getFile(QString("%1%2.sig2").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileSign))
574 return false;
577 return true;
580 bool UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *updateInfo)
582 QRegExp value("^(\\w+)=(.+)$");
583 QRegExp section("^\\[(.+)\\]$");
585 QDate updateInfoDate;
586 updateInfo->resetInfo();
588 QFile data(file);
589 if(!data.open(QIODevice::ReadOnly))
591 qWarning("Cannot open update info file for reading!");
592 return false;
595 bool inHdr = false;
596 bool inSec = false;
598 while(!data.atEnd())
600 QString line = QString::fromLatin1(data.readLine()).trimmed();
601 if(section.indexIn(line) >= 0)
603 log(QString("Sec: [%1]").arg(section.cap(1)));
604 inSec = (section.cap(1).compare(m_applicationId, Qt::CaseInsensitive) == 0);
605 inHdr = (section.cap(1).compare(QString::fromLatin1(header_id), Qt::CaseInsensitive) == 0);
606 continue;
608 if(inSec && (value.indexIn(line) >= 0))
610 log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
611 if(value.cap(1).compare("BuildNo", Qt::CaseInsensitive) == 0)
613 bool ok = false;
614 const unsigned int temp = value.cap(2).toUInt(&ok);
615 if(ok) updateInfo->m_buildNo = temp;
617 else if(value.cap(1).compare("BuildDate", Qt::CaseInsensitive) == 0)
619 const QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
620 if(temp.isValid()) updateInfo->m_buildDate = temp;
622 else if(value.cap(1).compare("DownloadSite", Qt::CaseInsensitive) == 0)
624 updateInfo->m_downloadSite = value.cap(2).trimmed();
626 else if(value.cap(1).compare("DownloadAddress", Qt::CaseInsensitive) == 0)
628 updateInfo->m_downloadAddress = value.cap(2).trimmed();
630 else if(value.cap(1).compare("DownloadFilename", Qt::CaseInsensitive) == 0)
632 updateInfo->m_downloadFilename = value.cap(2).trimmed();
634 else if(value.cap(1).compare("DownloadFilecode", Qt::CaseInsensitive) == 0)
636 updateInfo->m_downloadFilecode = value.cap(2).trimmed();
638 else if(value.cap(1).compare("DownloadChecksum", Qt::CaseInsensitive) == 0)
640 updateInfo->m_downloadChecksum = value.cap(2).trimmed();
643 if(inHdr && (value.indexIn(line) >= 0))
645 log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
646 if(value.cap(1).compare("TimestampCreated", Qt::CaseInsensitive) == 0)
648 QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
649 if(temp.isValid()) updateInfoDate = temp;
654 if(!updateInfoDate.isValid())
656 updateInfo->resetInfo();
657 log("WARNING: Version info timestamp is missing!");
658 return false;
661 const QDate currentDate = OS::current_date();
662 if(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS) < currentDate)
664 updateInfo->resetInfo();
665 log(QString::fromLatin1("WARNING: This version info has expired at %1!").arg(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS).toString(Qt::ISODate)));
666 return false;
668 else if(currentDate < updateInfoDate)
670 log("Version info is from the future, take care!");
671 qWarning("Version info is from the future, take care!");
674 if(!updateInfo->isComplete())
676 log("WARNING: Version info is incomplete!");
677 return false;
680 return true;
683 //----------------------------------------------------------
684 // EXTERNAL TOOLS
685 //----------------------------------------------------------
687 bool UpdateChecker::getFile(const QString &url, const QString &outFile, const unsigned int maxRedir)
689 for (int i = 0; i < 2; i++)
691 if (getFile(url, (i > 0), outFile, maxRedir))
693 return true;
696 return false;
699 bool UpdateChecker::getFile(const QString &url, const bool forceIp4, const QString &outFile, const unsigned int maxRedir)
701 QFileInfo output(outFile);
702 output.setCaching(false);
704 if (output.exists())
706 QFile::remove(output.canonicalFilePath());
707 if (output.exists())
709 return false;
713 QProcess process;
714 init_process(process, output.absolutePath());
716 QStringList args;
717 if (forceIp4)
719 args << "-4";
722 args << "--no-config" << "--no-cache" << "--no-dns-cache" << "--no-check-certificate" << "--no-hsts";
723 args << QString().sprintf("--max-redirect=%u", maxRedir) << QString().sprintf("--timeout=%u", DOWNLOAD_TIMEOUT / 1000);
724 args << QString("--referer=%1://%2/").arg(QUrl(url).scheme(), QUrl(url).host()) << "-U" << USER_AGENT_STR;
725 args << "-O" << output.fileName() << url;
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()));
732 QTimer timer;
733 timer.setSingleShot(true);
734 connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
736 const QRegExp httpResponseOK("200 OK$");
738 process.start(m_binaryWGet, args);
740 if (!process.waitForStarted())
742 return false;
745 timer.start(DOWNLOAD_TIMEOUT);
747 while (process.state() != QProcess::NotRunning)
749 loop.exec();
750 const bool bTimeOut = (!timer.isActive());
751 while (process.canReadLine())
753 const QString line = QString::fromLatin1(process.readLine()).simplified();
754 log(line);
756 if (bTimeOut || m_cancelled)
758 qWarning("WGet process timed out <-- killing!");
759 process.kill();
760 process.waitForFinished();
761 log(bTimeOut ? "!!! TIMEOUT !!!": "!!! CANCELLED !!!");
762 return false;
766 timer.stop();
767 timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
769 log(QString().sprintf("Exited with code %d", process.exitCode()));
770 return (process.exitCode() == 0) && output.exists() && output.isFile();
773 bool UpdateChecker::tryContactHost(const QString &hostname, const int &timeoutMsec)
775 log(QString("Connecting to host: %1").arg(hostname));
777 QProcess process;
778 init_process(process, temp_folder());
780 QStringList args;
781 args << "-z" << hostname << QString::number(80);
783 QEventLoop loop;
784 connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
785 connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
786 connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
788 QTimer timer;
789 timer.setSingleShot(true);
790 connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
792 process.start(m_binaryNC, args);
794 if (!process.waitForStarted())
796 return false;
799 timer.start(timeoutMsec);
801 while (process.state() != QProcess::NotRunning)
803 loop.exec();
804 const bool bTimeOut = (!timer.isActive());
805 while (process.canReadLine())
807 QString line = QString::fromLatin1(process.readLine()).simplified();
808 log(line);
810 if (bTimeOut || m_cancelled)
812 qWarning("NC process timed out <-- killing!");
813 process.kill();
814 process.waitForFinished();
815 log(bTimeOut ? "!!! TIMEOUT !!!" : "!!! CANCELLED !!!");
816 return false;
820 timer.stop();
821 timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
823 if (process.exitCode() != 0)
825 log("Connection has failed!");
828 log(QString().sprintf("Exited with code %d", process.exitCode()), "");
829 return (process.exitCode() == 0);
832 bool UpdateChecker::checkSignature(const QString &file, const QString &signature)
834 if (QFileInfo(file).absolutePath().compare(QFileInfo(signature).absolutePath(), Qt::CaseInsensitive) != 0)
836 qWarning("CheckSignature: File and signature should be in same folder!");
837 return false;
840 QString keyRingPath(m_binaryKeys);
841 bool removeKeyring = false;
842 if (QFileInfo(file).absolutePath().compare(QFileInfo(m_binaryKeys).absolutePath(), Qt::CaseInsensitive) != 0)
844 keyRingPath = make_temp_file(QFileInfo(file).absolutePath(), "gpg");
845 removeKeyring = true;
846 if (!QFile::copy(m_binaryKeys, keyRingPath))
848 qWarning("CheckSignature: Failed to copy the key-ring file!");
849 return false;
853 QProcess process;
854 init_process(process, QFileInfo(file).absolutePath());
856 QEventLoop loop;
857 connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
858 connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
859 connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
861 process.start(m_binaryGnuPG, QStringList() << "--homedir" << "." << "--keyring" << QFileInfo(keyRingPath).fileName() << QFileInfo(signature).fileName() << QFileInfo(file).fileName());
863 if (!process.waitForStarted())
865 if (removeKeyring)
867 remove_file(keyRingPath);
869 return false;
872 while (process.state() == QProcess::Running)
874 loop.exec();
875 while (process.canReadLine())
877 log(QString::fromLatin1(process.readLine()).simplified());
881 if (removeKeyring)
883 remove_file(keyRingPath);
886 log(QString().sprintf("Exited with code %d", process.exitCode()));
887 return (process.exitCode() == 0);
890 ////////////////////////////////////////////////////////////
891 // SLOTS
892 ////////////////////////////////////////////////////////////
894 /*NONE*/
896 ////////////////////////////////////////////////////////////
897 // EVENTS
898 ////////////////////////////////////////////////////////////
900 /*NONE*/