OSDN Git Service

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