OSDN Git Service

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