OSDN Git Service

Removed seed_rand() function. Seeding will now be done automatically, if needed.
[mutilities/MUtilities.git] / src / UpdateChecker.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // MuldeR's Utilities for Qt
3 // Copyright (C) 2004-2016 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 <QProcess>
31 #include <QUrl>
32 #include <QEventLoop>
33 #include <QTimer>
34
35 using namespace MUtils;
36
37 ///////////////////////////////////////////////////////////////////////////////
38 // CONSTANTS
39 ///////////////////////////////////////////////////////////////////////////////
40
41 static const char *header_id = "!Update";
42
43 static const char *mirror_url_postfix[] = 
44 {
45         "update.ver",
46         "update_beta.ver",
47         NULL
48 };
49
50 static const char *update_mirrors[] =
51 {
52         "http://muldersoft.com/",
53         "http://mulder.bplaced.net/",
54         "http://mulder.6te.net/",
55         "http://mulder.webuda.com/",
56         "http://mulder.pe.hu/",
57         "http://muldersoft.square7.ch/",
58         "http://muldersoft.co.nf/",
59         "http://muldersoft.eu.pn/",
60         "http://muldersoft.lima-city.de/",
61         "http://www.muldersoft.keepfree.de/",
62         "http://lamexp.sourceforge.net/",
63         "http://lordmulder.github.io/LameXP/",
64         "http://lord_mulder.bitbucket.org/",
65         "http://www.tricksoft.de/",
66         "http://repo.or.cz/LameXP.git/blob_plain/gh-pages:/",
67         "http://gitlab.com/lamexp/lamexp/raw/gh-pages/",
68         NULL
69 };
70
71 static const char *known_hosts[] =              //Taken form: http://www.alexa.com/topsites !!!
72 {
73         "www.163.com",
74         "www.7-zip.org",
75         "www.ac3filter.net",
76         "clbianco.altervista.org",
77         "status.aws.amazon.com",
78         "build.antergos.com",
79         "www.aol.com",
80         "www.apache.org",
81         "www.apple.com",
82         "www.adobe.com",
83         "archive.org",
84         "www.artlebedev.ru",
85         "web.audacityteam.org",
86         "status.automattic.com",
87         "www.avidemux.org",
88         "www.babylon.com",
89         "www.baidu.com",
90         "bandcamp.com",
91         "www.bbc.co.uk",
92         "www.berlios.de",
93         "www.bing.com",
94         "www.bingeandgrab.com",
95         "www.bucketheadpikes.com",
96         "www.buckethead-coop.com",
97         "www.buzzfeed.com",
98         "www.ccc.de",
99         "www.citizeninsomniac.com",
100         "www.cnet.com",
101         "cnzz.com",
102         "www.codeplex.com",
103         "www.codeproject.com",
104         "www.der-postillon.com",
105         "www.ebay.com",
106         "www.equation.com",
107         "www.farbrausch.de",
108         "fc2.com",
109         "fedoraproject.org",
110         "blog.fefe.de",
111         "www.ffmpeg.org",
112         "blog.flickr.net",
113         "free-codecs.com",
114         "git-scm.com",
115         "doc.gitlab.com",
116         "www.gmx.net",
117         "news.gnome.org",
118         "www.gnu.org",
119         "go.com",
120         "code.google.com",
121         "haali.su",
122         "www.heise.de",
123         "www.huffingtonpost.co.uk",
124         "www.iana.org",
125         "www.imdb.com",
126         "www.imgburn.com",
127         "imgur.com",
128         "www.jd.com",
129         "www.jiscdigitalmedia.ac.uk",
130         "kannmanumdieuhrzeitschonnbierchentrinken.de",
131         "mirrors.kernel.org",
132         "komisar.gin.by",
133         "lame.sourceforge.net",
134         "www.libav.org",
135         "blog.linkedin.com",
136         "www.linuxmint.com",
137         "www.livedoor.com",
138         "www.livejournal.com",
139         "longplayer.org",
140         "go.mail.ru",
141         "marknelson.us",
142         "www.mediafire.com",
143         "www.mod-technologies.com",
144         "ftp.mozilla.org",
145         "mplayerhq.hu",
146         "www.msn.com",
147         "wiki.multimedia.cx",
148         "www.nch.com.au",
149         "mirror.netcologne.de",
150         "oss.netfarm.it",
151         "blog.netflix.com",
152         "netrenderer.de",
153         "www.nytimes.com",
154         "www.opera.com",
155         "www.partha.com",
156         "pastebin.com",
157         "pastie.org",
158         "portableapps.com",
159         "www.portablefreeware.com",
160         "support.proboards.com",
161         "www.qq.com",
162         "www.qt.io",
163         "www.quakelive.com",
164         "rationalqm.us",
165         "www.seamonkey-project.org",
166         "selfhtml.org",
167         "www.sina.com.cn",
168         "www.sohu.com",
169         "help.sogou.com",
170         "sourceforge.net",
171         "www.spiegel.de",
172         "www.sputnikmusic.com",
173         "stackoverflow.com",
174         "www.t-online.de",
175         "www.tagesschau.de",
176         "tdm-gcc.tdragon.net",
177         "www.tdrsmusic.com",
178         "www.ubuntu.com",
179         "www.uol.com.br",
180         "www.videohelp.com",
181         "www.videolan.org",
182         "virtualdub.org",
183         "blog.virustotal.com",
184         "www.vkgoeswild.com",
185         "www.warr.org",
186         "www.weibo.com",
187         "status.wikimedia.org",
188         "www.winamp.com",
189         "www.winhoros.de",
190         "wpde.org",
191         "x265.org",
192         "xhmikosr.1f0.de",
193         "xiph.org",
194         "us.mail.yahoo.com",
195         "www.youtube.com",
196         "www.zedo.com",
197         "ffmpeg.zeranoe.com",
198         NULL
199 };
200
201 static const int MIN_CONNSCORE = 5;
202 static const int VERSION_INFO_EXPIRES_MONTHS = 6;
203 static char *USER_AGENT_STR = "Mozilla/5.0 (X11; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0"; /*use something innocuous*/
204
205 ////////////////////////////////////////////////////////////
206 // Helper Functions
207 ////////////////////////////////////////////////////////////
208
209 static int getMaxProgress(void)
210 {
211         int counter = MIN_CONNSCORE + 2;
212         for(int i = 0; update_mirrors[i]; i++) counter++;
213         return counter;
214 }
215
216 static QStringList buildRandomList(const char *const values[])
217 {
218         QStringList list;
219         for (int index = 0; values[index]; index++)
220         {
221                 const int pos = next_rand_u32() % (index + 1);
222                 list.insert(pos, QString::fromLatin1(values[index]));
223         }
224         return list;
225 }
226
227 ////////////////////////////////////////////////////////////
228 // Update Info Class
229 ////////////////////////////////////////////////////////////
230
231 UpdateCheckerInfo::UpdateCheckerInfo(void)
232 {
233         resetInfo();
234 }
235         
236 void UpdateCheckerInfo::resetInfo(void)
237 {
238         m_buildNo = 0;
239         m_buildDate.setDate(1900, 1, 1);
240         m_downloadSite.clear();
241         m_downloadAddress.clear();
242         m_downloadFilename.clear();
243         m_downloadFilecode.clear();
244         m_downloadChecksum.clear();
245 }
246
247 bool UpdateCheckerInfo::isComplete(void)
248 {
249         if(this->m_buildNo < 1)                return false;
250         if(this->m_buildDate.year() < 2010)    return false;
251         if(this->m_downloadSite.isEmpty())     return false;
252         if(this->m_downloadAddress.isEmpty())  return false;
253         if(this->m_downloadFilename.isEmpty()) return false;
254         if(this->m_downloadFilecode.isEmpty()) return false;
255         if(this->m_downloadChecksum.isEmpty()) return false;
256
257         return true;
258 }
259
260 ////////////////////////////////////////////////////////////
261 // Constructor & Destructor
262 ////////////////////////////////////////////////////////////
263
264 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)
265 :
266         m_updateInfo(new UpdateCheckerInfo()),
267         m_binaryWGet(binWGet),
268         m_binaryNC(binNC),
269         m_binaryGnuPG(binGnuPG),
270         m_binaryKeys(binKeys),
271         m_applicationId(applicationId),
272         m_installedBuildNo(installedBuildNo),
273         m_betaUpdates(betaUpdates),
274         m_testMode(testMode),
275         m_maxProgress(getMaxProgress())
276 {
277         m_success = false;
278         m_status = UpdateStatus_NotStartedYet;
279         m_progress = 0;
280
281         if(m_binaryWGet.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryKeys.isEmpty())
282         {
283                 MUTILS_THROW("Tools not initialized correctly!");
284         }
285 }
286
287 UpdateChecker::~UpdateChecker(void)
288 {
289 }
290
291 ////////////////////////////////////////////////////////////
292 // Protected functions
293 ////////////////////////////////////////////////////////////
294
295 void UpdateChecker::run(void)
296 {
297         qDebug("Update checker thread started!");
298         MUTILS_EXCEPTION_HANDLER(m_testMode ? testKnownHosts() : checkForUpdates());
299         qDebug("Update checker thread completed.");
300 }
301
302 void UpdateChecker::checkForUpdates(void)
303 {
304         // ----- Initialization ----- //
305
306         m_success = false;
307         m_updateInfo->resetInfo();
308         setProgress(0);
309
310         // ----- Test Internet Connection ----- //
311
312         log("Checking internet connection...", "");
313         setStatus(UpdateStatus_CheckingConnection);
314
315         const int networkStatus = OS::network_status();
316         if(networkStatus == OS::NETWORK_TYPE_NON)
317         {
318                 log("Operating system reports that the computer is currently offline !!!");
319                 setProgress(m_maxProgress);
320                 setStatus(UpdateStatus_ErrorNoConnection);
321                 return;
322         }
323         
324         setProgress(1);
325
326         // ----- Test Known Hosts Connectivity ----- //
327
328         int connectionScore = 0, connectionRetry = MIN_CONNSCORE;
329
330         QStringList hostList = buildRandomList(known_hosts);
331         while((!hostList.isEmpty()) && (connectionScore < MIN_CONNSCORE) && (connectionRetry > 0))
332         {
333                 connectionRetry--;
334                 if(tryContactHost(hostList.takeFirst()))
335                 {
336                         connectionScore += 1;
337                         connectionRetry = MIN_CONNSCORE;
338                 }
339                 setProgress(qBound(1, connectionScore + 1, MIN_CONNSCORE + 1));
340                 msleep(25);
341         }
342
343         if(connectionScore < MIN_CONNSCORE)
344         {
345                 log("", "Connectivity test has failed: Internet connection appears to be broken!");
346                 setProgress(m_maxProgress);
347                 setStatus(UpdateStatus_ErrorConnectionTestFailed);
348                 return;
349         }
350
351         // ----- Fetch Update Info From Server ----- //
352
353         log("----", "", "Checking for updates online...");
354         setStatus(UpdateStatus_FetchingUpdates);
355
356         QStringList mirrorList = buildRandomList(update_mirrors);
357         while(!mirrorList.isEmpty())
358         {
359                 QString currentMirror = mirrorList.takeFirst();
360                 setProgress(m_progress + 1);
361                 if(!m_success)
362                 {
363                         if(tryUpdateMirror(m_updateInfo.data(), currentMirror))
364                         {
365                                 m_success = true;
366                         }
367                 }
368                 else
369                 {
370                         msleep(25);
371                 }
372         }
373         
374         setProgress(m_maxProgress);
375
376         if(m_success)
377         {
378                 if(m_updateInfo->m_buildNo > m_installedBuildNo)
379                 {
380                         setStatus(UpdateStatus_CompletedUpdateAvailable);
381                 }
382                 else if(m_updateInfo->m_buildNo == m_installedBuildNo)
383                 {
384                         setStatus(UpdateStatus_CompletedNoUpdates);
385                 }
386                 else
387                 {
388                         setStatus(UpdateStatus_CompletedNewVersionOlder);
389                 }
390         }
391         else
392         {
393                 setStatus(UpdateStatus_ErrorFetchUpdateInfo);
394         }
395 }
396
397 void UpdateChecker::testKnownHosts(void)
398 {
399         QStringList hostList;
400         for(int i = 0; known_hosts[i]; i++)
401         {
402                 hostList << QString::fromLatin1(known_hosts[i]);
403         }
404
405         qDebug("\n[Known Hosts]");
406         log("Testing all known hosts...", "", "---");
407
408         int hostCount = hostList.count();
409         while(!hostList.isEmpty())
410         {
411                 QString currentHost = hostList.takeFirst();
412                 qDebug("Testing: %s", currentHost.toLatin1().constData());
413                 log("", "Testing:", currentHost, "");
414                 if (!tryContactHost(currentHost))
415                 {
416                         qWarning("\nConnectivity test FAILED on the following host:\n%s\n", currentHost.toLatin1().constData());
417                 }
418                 log("", "---");
419         }
420 }
421
422 ////////////////////////////////////////////////////////////
423 // PRIVATE FUNCTIONS
424 ////////////////////////////////////////////////////////////
425
426 void UpdateChecker::setStatus(const int status)
427 {
428         if(m_status != status)
429         {
430                 m_status = status;
431                 emit statusChanged(status);
432         }
433 }
434
435 void UpdateChecker::setProgress(const int progress)
436 {
437         if(m_progress != progress)
438         {
439                 m_progress = progress;
440                 emit progressChanged(progress);
441         }
442 }
443
444 void UpdateChecker::log(const QString &str1, const QString &str2, const QString &str3, const QString &str4)
445 {
446         if(!str1.isNull()) emit messageLogged(str1);
447         if(!str2.isNull()) emit messageLogged(str2);
448         if(!str3.isNull()) emit messageLogged(str3);
449         if(!str4.isNull()) emit messageLogged(str4);
450 }
451
452 bool UpdateChecker::tryUpdateMirror(UpdateCheckerInfo *updateInfo, const QString &url)
453 {
454         bool success = false;
455         log("", "Trying mirror:", url);
456
457         const QString randPart = next_rand_str();
458         const QString outFileVers = QString("%1/%2.ver").arg(temp_folder(), randPart);
459         const QString outFileSign = QString("%1/%2.sig").arg(temp_folder(), randPart);
460
461         if(getUpdateInfo(url, outFileVers, outFileSign))
462         {
463                 log("", "Download okay, checking signature:");
464                 if(checkSignature(outFileVers, outFileSign))
465                 {
466                         log("", "Signature okay, parsing info:");
467                         success = parseVersionInfo(outFileVers, updateInfo);
468                 }
469                 else
470                 {
471                         log("", "Bad signature, take care!");
472                 }
473         }
474         else
475         {
476                 log("", "Download has failed!");
477         }
478
479         QFile::remove(outFileVers);
480         QFile::remove(outFileSign);
481         
482         return success;
483 }
484
485 bool UpdateChecker::getUpdateInfo(const QString &url, const QString &outFileVers, const QString &outFileSign)
486 {
487         log("", "Downloading update info:");
488         if(!getFile(QString("%1%2"     ).arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileVers))
489         {
490                 return false;
491         }
492
493         log("", "Downloading signature:");
494         if(!getFile(QString("%1%2.sig2").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileSign))
495         {
496                 return false;
497         }
498
499         return true;
500 }
501
502 bool UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *updateInfo)
503 {
504         QRegExp value("^(\\w+)=(.+)$");
505         QRegExp section("^\\[(.+)\\]$");
506
507         QDate updateInfoDate;
508         updateInfo->resetInfo();
509
510         QFile data(file);
511         if(!data.open(QIODevice::ReadOnly))
512         {
513                 qWarning("Cannot open update info file for reading!");
514                 return false;
515         }
516         
517         bool inHdr = false;
518         bool inSec = false;
519         
520         while(!data.atEnd())
521         {
522                 QString line = QString::fromLatin1(data.readLine()).trimmed();
523                 if(section.indexIn(line) >= 0)
524                 {
525                         log(QString("Sec: [%1]").arg(section.cap(1)));
526                         inSec = (section.cap(1).compare(m_applicationId, Qt::CaseInsensitive) == 0);
527                         inHdr = (section.cap(1).compare(QString::fromLatin1(header_id), Qt::CaseInsensitive) == 0);
528                         continue;
529                 }
530                 if(inSec && (value.indexIn(line) >= 0))
531                 {
532                         log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
533                         if(value.cap(1).compare("BuildNo", Qt::CaseInsensitive) == 0)
534                         {
535                                 bool ok = false;
536                                 const unsigned int temp = value.cap(2).toUInt(&ok);
537                                 if(ok) updateInfo->m_buildNo = temp;
538                         }
539                         else if(value.cap(1).compare("BuildDate", Qt::CaseInsensitive) == 0)
540                         {
541                                 const QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
542                                 if(temp.isValid()) updateInfo->m_buildDate = temp;
543                         }
544                         else if(value.cap(1).compare("DownloadSite", Qt::CaseInsensitive) == 0)
545                         {
546                                 updateInfo->m_downloadSite = value.cap(2).trimmed();
547                         }
548                         else if(value.cap(1).compare("DownloadAddress", Qt::CaseInsensitive) == 0)
549                         {
550                                 updateInfo->m_downloadAddress = value.cap(2).trimmed();
551                         }
552                         else if(value.cap(1).compare("DownloadFilename", Qt::CaseInsensitive) == 0)
553                         {
554                                 updateInfo->m_downloadFilename = value.cap(2).trimmed();
555                         }
556                         else if(value.cap(1).compare("DownloadFilecode", Qt::CaseInsensitive) == 0)
557                         {
558                                 updateInfo->m_downloadFilecode = value.cap(2).trimmed();
559                         }
560                         else if(value.cap(1).compare("DownloadChecksum", Qt::CaseInsensitive) == 0)
561                         {
562                                 updateInfo->m_downloadChecksum = value.cap(2).trimmed();
563                         }
564                 }
565                 if(inHdr && (value.indexIn(line) >= 0))
566                 {
567                         log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
568                         if(value.cap(1).compare("TimestampCreated", Qt::CaseInsensitive) == 0)
569                         {
570                                 QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
571                                 if(temp.isValid()) updateInfoDate = temp;
572                         }
573                 }
574         }
575
576         if(!updateInfoDate.isValid())
577         {
578                 updateInfo->resetInfo();
579                 log("WARNING: Version info timestamp is missing!");
580                 return false;
581         }
582         
583         const QDate currentDate = OS::current_date();
584         if(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS) < currentDate)
585         {
586                 updateInfo->resetInfo();
587                 log(QString::fromLatin1("WARNING: This version info has expired at %1!").arg(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS).toString(Qt::ISODate)));
588                 return false;
589         }
590         else if(currentDate < updateInfoDate)
591         {
592                 log("Version info is from the future, take care!");
593                 qWarning("Version info is from the future, take care!");
594         }
595         
596         if(!updateInfo->isComplete())
597         {
598                 log("WARNING: Version info is incomplete!");
599                 return false;
600         }
601
602         return true;
603 }
604
605 //----------------------------------------------------------
606 // EXTERNAL TOOLS
607 //----------------------------------------------------------
608
609 bool UpdateChecker::getFile(const QString &url, const QString &outFile, const unsigned int maxRedir, bool *httpOk)
610 {
611         for (int i = 0; i < 2; i++)
612         {
613                 if (getFile(url, (i > 0), outFile, maxRedir, httpOk))
614                 {
615                         return true;
616                 }
617         }
618         return false;
619 }
620
621 bool UpdateChecker::getFile(const QString &url, const bool forceIp4, const QString &outFile, const unsigned int maxRedir, bool *httpOk)
622 {
623         QFileInfo output(outFile);
624         output.setCaching(false);
625         if (httpOk) *httpOk = false;
626
627         if (output.exists())
628         {
629                 QFile::remove(output.canonicalFilePath());
630                 if (output.exists())
631                 {
632                         return false;
633                 }
634         }
635
636         QProcess process;
637         init_process(process, output.absolutePath());
638
639         QStringList args;
640         if (forceIp4)
641         {
642                 args << "-4";
643         }
644
645         args << "--no-config" << "--no-cache" << "--no-dns-cache" << "--no-check-certificate" << "--no-hsts";
646         args << QString().sprintf("--max-redirect=%u", maxRedir) << "--timeout=15";
647         args << QString("--referer=%1://%2/").arg(QUrl(url).scheme(), QUrl(url).host()) << "-U" << USER_AGENT_STR;
648         args << "-O" << output.fileName() << url;
649
650         QEventLoop loop;
651         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
652         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
653         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
654
655         QTimer timer;
656         timer.setSingleShot(true);
657         connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
658
659         const QRegExp httpResponseOK("200 OK$");
660
661         process.start(m_binaryWGet, args);
662
663         if (!process.waitForStarted())
664         {
665                 return false;
666         }
667
668         timer.start(25000);
669
670         while (process.state() != QProcess::NotRunning)
671         {
672                 loop.exec();
673                 const bool bTimeOut = (!timer.isActive());
674                 while (process.canReadLine())
675                 {
676                         QString line = QString::fromLatin1(process.readLine()).simplified();
677                         if (line.contains(httpResponseOK))
678                         {
679                                 line.append(" [OK]");
680                                 if (httpOk) *httpOk = true;
681                         }
682                         log(line);
683                 }
684                 if (bTimeOut)
685                 {
686                         qWarning("WGet process timed out <-- killing!");
687                         process.kill();
688                         process.waitForFinished();
689                         log("!!! TIMEOUT !!!");
690                         return false;
691                 }
692         }
693
694         timer.stop();
695         timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
696
697         log(QString().sprintf("Exited with code %d", process.exitCode()));
698         return (process.exitCode() == 0) && output.exists() && output.isFile();
699 }
700
701 bool UpdateChecker::tryContactHost(const QString &hostname)
702 {
703         log(QString("Connecting to host: %1").arg(hostname));
704
705         QProcess process;
706         init_process(process, temp_folder());
707
708         QStringList args;
709         args << "-z" << hostname << QString::number(80);
710
711         QEventLoop loop;
712         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
713         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
714         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
715
716         QTimer timer;
717         timer.setSingleShot(true);
718         connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
719
720         process.start(m_binaryNC, args);
721
722         if (!process.waitForStarted())
723         {
724                 return false;
725         }
726
727         timer.start(10000);
728
729         while (process.state() != QProcess::NotRunning)
730         {
731                 loop.exec();
732                 const bool bTimeOut = (!timer.isActive());
733                 while (process.canReadLine())
734                 {
735                         QString line = QString::fromLatin1(process.readLine()).simplified();
736                         log(line);
737                 }
738                 if (bTimeOut)
739                 {
740                         qWarning("NC process timed out <-- killing!");
741                         process.kill();
742                         process.waitForFinished();
743                         log("!!! TIMEOUT !!!");
744                         return false;
745                 }
746         }
747
748         timer.stop();
749         timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
750
751         if (process.exitCode() != 0)
752         {
753                 log("Connection has failed!");
754         }
755
756         log(QString().sprintf("Exited with code %d", process.exitCode()), "");
757         return (process.exitCode() == 0);
758 }
759
760 bool UpdateChecker::checkSignature(const QString &file, const QString &signature)
761 {
762         if (QFileInfo(file).absolutePath().compare(QFileInfo(signature).absolutePath(), Qt::CaseInsensitive) != 0)
763         {
764                 qWarning("CheckSignature: File and signature should be in same folder!");
765                 return false;
766         }
767
768         QString keyRingPath(m_binaryKeys);
769         bool removeKeyring = false;
770         if (QFileInfo(file).absolutePath().compare(QFileInfo(m_binaryKeys).absolutePath(), Qt::CaseInsensitive) != 0)
771         {
772                 keyRingPath = make_temp_file(QFileInfo(file).absolutePath(), "gpg");
773                 removeKeyring = true;
774                 if (!QFile::copy(m_binaryKeys, keyRingPath))
775                 {
776                         qWarning("CheckSignature: Failed to copy the key-ring file!");
777                         return false;
778                 }
779         }
780
781         QProcess process;
782         init_process(process, QFileInfo(file).absolutePath());
783
784         QEventLoop loop;
785         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
786         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
787         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
788
789         process.start(m_binaryGnuPG, QStringList() << "--homedir" << "." << "--keyring" << QFileInfo(keyRingPath).fileName() << QFileInfo(signature).fileName() << QFileInfo(file).fileName());
790
791         if (!process.waitForStarted())
792         {
793                 if (removeKeyring)
794                 {
795                         remove_file(keyRingPath);
796                 }
797                 return false;
798         }
799
800         while (process.state() == QProcess::Running)
801         {
802                 loop.exec();
803                 while (process.canReadLine())
804                 {
805                         log(QString::fromLatin1(process.readLine()).simplified());
806                 }
807         }
808
809         if (removeKeyring)
810         {
811                 remove_file(keyRingPath);
812         }
813
814         log(QString().sprintf("Exited with code %d", process.exitCode()));
815         return (process.exitCode() == 0);
816 }
817
818 ////////////////////////////////////////////////////////////
819 // SLOTS
820 ////////////////////////////////////////////////////////////
821
822 /*NONE*/
823
824 ////////////////////////////////////////////////////////////
825 // EVENTS
826 ////////////////////////////////////////////////////////////
827
828 /*NONE*/