Updated list of update mirrors.
[MUtilities.git] / src / UpdateChecker.cpp
blob81d9fd06a101ae144e6c8e8c5beee68cbd05f04c
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>
35 using namespace MUtils;
37 ///////////////////////////////////////////////////////////////////////////////
38 // CONSTANTS
39 ///////////////////////////////////////////////////////////////////////////////
41 static const char *header_id = "!Update";
43 static const char *mirror_url_postfix[] =
45 "update.ver",
46 "update_beta.ver",
47 NULL
50 static const char *update_mirrors[] =
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://muldersoft.bitbucket.io/", //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
71 static const char *known_hosts[] = //Taken form: http://www.alexa.com/topsites !!!
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
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*/
205 ////////////////////////////////////////////////////////////
206 // Helper Functions
207 ////////////////////////////////////////////////////////////
209 static int getMaxProgress(void)
211 int counter = MIN_CONNSCORE + 2;
212 for(int i = 0; update_mirrors[i]; i++) counter++;
213 return counter;
216 static QStringList buildRandomList(const char *const values[])
218 QStringList list;
219 for (int index = 0; values[index]; index++)
221 const int pos = next_rand_u32() % (index + 1);
222 list.insert(pos, QString::fromLatin1(values[index]));
224 return list;
227 ////////////////////////////////////////////////////////////
228 // Update Info Class
229 ////////////////////////////////////////////////////////////
231 UpdateCheckerInfo::UpdateCheckerInfo(void)
233 resetInfo();
236 void UpdateCheckerInfo::resetInfo(void)
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();
247 bool UpdateCheckerInfo::isComplete(void)
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;
257 return true;
260 ////////////////////////////////////////////////////////////
261 // Constructor & Destructor
262 ////////////////////////////////////////////////////////////
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)
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())
277 m_success = false;
278 m_status = UpdateStatus_NotStartedYet;
279 m_progress = 0;
281 if(m_binaryWGet.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryKeys.isEmpty())
283 MUTILS_THROW("Tools not initialized correctly!");
287 UpdateChecker::~UpdateChecker(void)
291 ////////////////////////////////////////////////////////////
292 // Protected functions
293 ////////////////////////////////////////////////////////////
295 void UpdateChecker::run(void)
297 qDebug("Update checker thread started!");
298 MUTILS_EXCEPTION_HANDLER(m_testMode ? testKnownHosts() : checkForUpdates());
299 qDebug("Update checker thread completed.");
302 void UpdateChecker::checkForUpdates(void)
304 // ----- Initialization ----- //
306 m_success = false;
307 m_updateInfo->resetInfo();
308 setProgress(0);
310 // ----- Test Internet Connection ----- //
312 log("Checking internet connection...", "");
313 setStatus(UpdateStatus_CheckingConnection);
315 const int networkStatus = OS::network_status();
316 if(networkStatus == OS::NETWORK_TYPE_NON)
318 log("Operating system reports that the computer is currently offline !!!");
319 setProgress(m_maxProgress);
320 setStatus(UpdateStatus_ErrorNoConnection);
321 return;
324 setProgress(1);
326 // ----- Test Known Hosts Connectivity ----- //
328 int connectionScore = 0, connectionRetry = MIN_CONNSCORE;
330 QStringList hostList = buildRandomList(known_hosts);
331 while((!hostList.isEmpty()) && (connectionScore < MIN_CONNSCORE) && (connectionRetry > 0))
333 connectionRetry--;
334 if(tryContactHost(hostList.takeFirst()))
336 connectionScore += 1;
337 connectionRetry = MIN_CONNSCORE;
339 setProgress(qBound(1, connectionScore + 1, MIN_CONNSCORE + 1));
340 msleep(25);
343 if(connectionScore < MIN_CONNSCORE)
345 log("", "Connectivity test has failed: Internet connection appears to be broken!");
346 setProgress(m_maxProgress);
347 setStatus(UpdateStatus_ErrorConnectionTestFailed);
348 return;
351 // ----- Fetch Update Info From Server ----- //
353 log("----", "", "Checking for updates online...");
354 setStatus(UpdateStatus_FetchingUpdates);
356 QStringList mirrorList = buildRandomList(update_mirrors);
357 while(!mirrorList.isEmpty())
359 QString currentMirror = mirrorList.takeFirst();
360 setProgress(m_progress + 1);
361 if(!m_success)
363 if(tryUpdateMirror(m_updateInfo.data(), currentMirror))
365 m_success = true;
368 else
370 msleep(25);
374 setProgress(m_maxProgress);
376 if(m_success)
378 if(m_updateInfo->m_buildNo > m_installedBuildNo)
380 setStatus(UpdateStatus_CompletedUpdateAvailable);
382 else if(m_updateInfo->m_buildNo == m_installedBuildNo)
384 setStatus(UpdateStatus_CompletedNoUpdates);
386 else
388 setStatus(UpdateStatus_CompletedNewVersionOlder);
391 else
393 setStatus(UpdateStatus_ErrorFetchUpdateInfo);
397 void UpdateChecker::testKnownHosts(void)
399 QStringList hostList;
400 for(int i = 0; known_hosts[i]; i++)
402 hostList << QString::fromLatin1(known_hosts[i]);
405 qDebug("\n[Known Hosts]");
406 log("Testing all known hosts...", "", "---");
408 int hostCount = hostList.count();
409 while(!hostList.isEmpty())
411 QString currentHost = hostList.takeFirst();
412 qDebug("Testing: %s", currentHost.toLatin1().constData());
413 log("", "Testing:", currentHost, "");
414 if (!tryContactHost(currentHost))
416 qWarning("\nConnectivity test FAILED on the following host:\n%s\n", currentHost.toLatin1().constData());
418 log("", "---");
422 ////////////////////////////////////////////////////////////
423 // PRIVATE FUNCTIONS
424 ////////////////////////////////////////////////////////////
426 void UpdateChecker::setStatus(const int status)
428 if(m_status != status)
430 m_status = status;
431 emit statusChanged(status);
435 void UpdateChecker::setProgress(const int progress)
437 if(m_progress != progress)
439 m_progress = progress;
440 emit progressChanged(progress);
444 void UpdateChecker::log(const QString &str1, const QString &str2, const QString &str3, const QString &str4)
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);
452 bool UpdateChecker::tryUpdateMirror(UpdateCheckerInfo *updateInfo, const QString &url)
454 bool success = false;
455 log("", "Trying mirror:", url);
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);
461 if(getUpdateInfo(url, outFileVers, outFileSign))
463 log("", "Download okay, checking signature:");
464 if(checkSignature(outFileVers, outFileSign))
466 log("", "Signature okay, parsing info:");
467 success = parseVersionInfo(outFileVers, updateInfo);
469 else
471 log("", "Bad signature, take care!");
474 else
476 log("", "Download has failed!");
479 QFile::remove(outFileVers);
480 QFile::remove(outFileSign);
482 return success;
485 bool UpdateChecker::getUpdateInfo(const QString &url, const QString &outFileVers, const QString &outFileSign)
487 log("", "Downloading update info:");
488 if(!getFile(QString("%1%2" ).arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileVers))
490 return false;
493 log("", "Downloading signature:");
494 if(!getFile(QString("%1%2.sig2").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileSign))
496 return false;
499 return true;
502 bool UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *updateInfo)
504 QRegExp value("^(\\w+)=(.+)$");
505 QRegExp section("^\\[(.+)\\]$");
507 QDate updateInfoDate;
508 updateInfo->resetInfo();
510 QFile data(file);
511 if(!data.open(QIODevice::ReadOnly))
513 qWarning("Cannot open update info file for reading!");
514 return false;
517 bool inHdr = false;
518 bool inSec = false;
520 while(!data.atEnd())
522 QString line = QString::fromLatin1(data.readLine()).trimmed();
523 if(section.indexIn(line) >= 0)
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;
530 if(inSec && (value.indexIn(line) >= 0))
532 log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
533 if(value.cap(1).compare("BuildNo", Qt::CaseInsensitive) == 0)
535 bool ok = false;
536 const unsigned int temp = value.cap(2).toUInt(&ok);
537 if(ok) updateInfo->m_buildNo = temp;
539 else if(value.cap(1).compare("BuildDate", Qt::CaseInsensitive) == 0)
541 const QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
542 if(temp.isValid()) updateInfo->m_buildDate = temp;
544 else if(value.cap(1).compare("DownloadSite", Qt::CaseInsensitive) == 0)
546 updateInfo->m_downloadSite = value.cap(2).trimmed();
548 else if(value.cap(1).compare("DownloadAddress", Qt::CaseInsensitive) == 0)
550 updateInfo->m_downloadAddress = value.cap(2).trimmed();
552 else if(value.cap(1).compare("DownloadFilename", Qt::CaseInsensitive) == 0)
554 updateInfo->m_downloadFilename = value.cap(2).trimmed();
556 else if(value.cap(1).compare("DownloadFilecode", Qt::CaseInsensitive) == 0)
558 updateInfo->m_downloadFilecode = value.cap(2).trimmed();
560 else if(value.cap(1).compare("DownloadChecksum", Qt::CaseInsensitive) == 0)
562 updateInfo->m_downloadChecksum = value.cap(2).trimmed();
565 if(inHdr && (value.indexIn(line) >= 0))
567 log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
568 if(value.cap(1).compare("TimestampCreated", Qt::CaseInsensitive) == 0)
570 QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
571 if(temp.isValid()) updateInfoDate = temp;
576 if(!updateInfoDate.isValid())
578 updateInfo->resetInfo();
579 log("WARNING: Version info timestamp is missing!");
580 return false;
583 const QDate currentDate = OS::current_date();
584 if(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS) < currentDate)
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;
590 else if(currentDate < updateInfoDate)
592 log("Version info is from the future, take care!");
593 qWarning("Version info is from the future, take care!");
596 if(!updateInfo->isComplete())
598 log("WARNING: Version info is incomplete!");
599 return false;
602 return true;
605 //----------------------------------------------------------
606 // EXTERNAL TOOLS
607 //----------------------------------------------------------
609 bool UpdateChecker::getFile(const QString &url, const QString &outFile, const unsigned int maxRedir, bool *httpOk)
611 for (int i = 0; i < 2; i++)
613 if (getFile(url, (i > 0), outFile, maxRedir, httpOk))
615 return true;
618 return false;
621 bool UpdateChecker::getFile(const QString &url, const bool forceIp4, const QString &outFile, const unsigned int maxRedir, bool *httpOk)
623 QFileInfo output(outFile);
624 output.setCaching(false);
625 if (httpOk) *httpOk = false;
627 if (output.exists())
629 QFile::remove(output.canonicalFilePath());
630 if (output.exists())
632 return false;
636 QProcess process;
637 init_process(process, output.absolutePath());
639 QStringList args;
640 if (forceIp4)
642 args << "-4";
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;
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()));
655 QTimer timer;
656 timer.setSingleShot(true);
657 connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
659 const QRegExp httpResponseOK("200 OK$");
661 process.start(m_binaryWGet, args);
663 if (!process.waitForStarted())
665 return false;
668 timer.start(25000);
670 while (process.state() != QProcess::NotRunning)
672 loop.exec();
673 const bool bTimeOut = (!timer.isActive());
674 while (process.canReadLine())
676 QString line = QString::fromLatin1(process.readLine()).simplified();
677 if (line.contains(httpResponseOK))
679 line.append(" [OK]");
680 if (httpOk) *httpOk = true;
682 log(line);
684 if (bTimeOut)
686 qWarning("WGet process timed out <-- killing!");
687 process.kill();
688 process.waitForFinished();
689 log("!!! TIMEOUT !!!");
690 return false;
694 timer.stop();
695 timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
697 log(QString().sprintf("Exited with code %d", process.exitCode()));
698 return (process.exitCode() == 0) && output.exists() && output.isFile();
701 bool UpdateChecker::tryContactHost(const QString &hostname)
703 log(QString("Connecting to host: %1").arg(hostname));
705 QProcess process;
706 init_process(process, temp_folder());
708 QStringList args;
709 args << "-z" << hostname << QString::number(80);
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()));
716 QTimer timer;
717 timer.setSingleShot(true);
718 connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
720 process.start(m_binaryNC, args);
722 if (!process.waitForStarted())
724 return false;
727 timer.start(10000);
729 while (process.state() != QProcess::NotRunning)
731 loop.exec();
732 const bool bTimeOut = (!timer.isActive());
733 while (process.canReadLine())
735 QString line = QString::fromLatin1(process.readLine()).simplified();
736 log(line);
738 if (bTimeOut)
740 qWarning("NC process timed out <-- killing!");
741 process.kill();
742 process.waitForFinished();
743 log("!!! TIMEOUT !!!");
744 return false;
748 timer.stop();
749 timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
751 if (process.exitCode() != 0)
753 log("Connection has failed!");
756 log(QString().sprintf("Exited with code %d", process.exitCode()), "");
757 return (process.exitCode() == 0);
760 bool UpdateChecker::checkSignature(const QString &file, const QString &signature)
762 if (QFileInfo(file).absolutePath().compare(QFileInfo(signature).absolutePath(), Qt::CaseInsensitive) != 0)
764 qWarning("CheckSignature: File and signature should be in same folder!");
765 return false;
768 QString keyRingPath(m_binaryKeys);
769 bool removeKeyring = false;
770 if (QFileInfo(file).absolutePath().compare(QFileInfo(m_binaryKeys).absolutePath(), Qt::CaseInsensitive) != 0)
772 keyRingPath = make_temp_file(QFileInfo(file).absolutePath(), "gpg");
773 removeKeyring = true;
774 if (!QFile::copy(m_binaryKeys, keyRingPath))
776 qWarning("CheckSignature: Failed to copy the key-ring file!");
777 return false;
781 QProcess process;
782 init_process(process, QFileInfo(file).absolutePath());
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()));
789 process.start(m_binaryGnuPG, QStringList() << "--homedir" << "." << "--keyring" << QFileInfo(keyRingPath).fileName() << QFileInfo(signature).fileName() << QFileInfo(file).fileName());
791 if (!process.waitForStarted())
793 if (removeKeyring)
795 remove_file(keyRingPath);
797 return false;
800 while (process.state() == QProcess::Running)
802 loop.exec();
803 while (process.canReadLine())
805 log(QString::fromLatin1(process.readLine()).simplified());
809 if (removeKeyring)
811 remove_file(keyRingPath);
814 log(QString().sprintf("Exited with code %d", process.exitCode()));
815 return (process.exitCode() == 0);
818 ////////////////////////////////////////////////////////////
819 // SLOTS
820 ////////////////////////////////////////////////////////////
822 /*NONE*/
824 ////////////////////////////////////////////////////////////
825 // EVENTS
826 ////////////////////////////////////////////////////////////
828 /*NONE*/