OSDN Git Service

Merge branch 'master' of github.com:lordmulder/LameXP
[lamexp/LameXP.git] / src / Dialog_Update.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2012 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // This program 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
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "Dialog_Update.h"
23
24 #include "Global.h"
25 #include "Resource.h"
26 #include "Dialog_LogView.h"
27 #include "Model_Settings.h"
28 #include "WinSevenTaskbar.h"
29
30 #include <QClipboard>
31 #include <QFileDialog>
32 #include <QTimer>
33 #include <QProcess>
34 #include <QDate>
35 #include <QRegExp>
36 #include <QDesktopServices>
37 #include <QUrl>
38 #include <QCloseEvent>
39 #include <QMovie>
40 #include <QtConcurrentRun>
41
42 #include <time.h>
43 #include <MMSystem.h>
44 #include <WinInet.h>
45
46 ///////////////////////////////////////////////////////////////////////////////
47
48 static const char *header_id = "!Update";
49 static const char *section_id = "LameXP";
50
51 static const char *mirror_url_postfix[] = 
52 {
53         "update.ver",
54         "update_beta.ver",
55         NULL
56 };
57
58 static const char *update_mirrors_prim[] =
59 {
60         "http://mulder.brhack.net/",
61         "http://mulder.bplaced.net/",
62         "http://lamexp.sourceforge.net/",
63         "http://free.pages.at/borschdfresser/",
64         "http://lordmulder.github.com/LameXP/",
65         "http://www.tricksoft.de/",
66         NULL
67 };
68
69 static const char *update_mirrors_back[] =
70 {
71         "http://mplayer.savedonthe.net/",
72         "http://mulder.dummwiedeutsch.de/",
73         "http://mplayer.somestuff.org/",
74         NULL
75 };
76
77 static const char *known_hosts[] =              //Taken form: http://www.alexa.com/topsites
78 {
79         "http://www.163.com/",
80         "http://www.360buy.com/",
81         "http://www.amazon.com/",
82         "http://www.aol.com/",
83         "http://www.apache.org/",
84         "http://www.apple.com/",
85         "http://www.adobe.com/",
86         "http://www.avidemux.org/",
87         "http://www.babylon.com/",
88         "http://www.baidu.com/",
89         "http://www.bbc.co.uk/",
90         "http://www.bing.com/",
91         "http://www.cnet.com/",
92         "http://cnzz.com/",
93         "http://www.ebay.com/",
94         "http://www.equation.com/",
95         "http://fc2.com/",
96         "http://www.ffmpeg.org/",
97         "http://www.flickr.com/",
98         "http://www.gitorious.org/",
99         "http://www.gnome.org/",
100         "http://www.gnu.org/",
101         "http://go.com/",
102         "http://code.google.com/",
103         "http://www.heise.de/",
104         "http://www.huffingtonpost.co.uk/",
105         "http://www.iana.org/",
106         "http://www.imdb.com/",
107         "http://www.imgburn.com/",
108         "http://imgur.com/",
109         "http://www.kernel.org/",
110         "http://www.libav.org/",
111         "http://www.linkedin.com/",
112         "http://www.livedoor.com/",
113         "http://www.livejournal.com/",
114         "http://mail.ru/",
115         "http://www.mediafire.com/",
116         "http://www.mozilla.org/",
117         "http://mplayerhq.hu/",
118         "http://www.msn.com/?st=1",
119         "http://oss.netfarm.it/",
120         "http://www.nytimes.com/",
121         "http://www.opera.com/",
122         "http://www.quakelive.com/",
123         "http://www.seamonkey-project.org/",
124         "http://www.sina.com.cn/",
125         "http://www.sohu.com/",
126         "http://www.soso.com/",
127         "http://sourceforge.net/",
128         "http://www.spiegel.de/",
129         "http://tdm-gcc.tdragon.net/",
130         "http://www.tdrsmusic.com/",
131         "http://www.ubuntu.com/",
132         "http://twitter.com/",
133         "http://www.uol.com.br/",
134         "http://www.videohelp.com/",
135         "http://www.videolan.org/",
136         "http://www.weibo.com/",
137         "http://www.wikipedia.org/",
138         "http://wordpress.com/",
139         "http://us.yahoo.com/",
140         "http://www.yandex.ru/",
141         "http://www.youtube.com/",
142         "http://www.zedo.com/",
143         NULL
144 };
145
146 static const int MIN_CONNSCORE = 8;
147 static const int VERSION_INFO_EXPIRES_MONTHS = 6;
148 static char *USER_AGENT_STR = "Mozilla/5.0 (X11; Linux i686; rv:7.0.1) Gecko/20111106 IceCat/7.0.1";
149
150 static BOOL getInternetConnectedState(void)
151 {
152         DWORD lpdwFlags = NULL;
153         BOOL result = InternetGetConnectedState(&lpdwFlags, NULL);
154         return result;
155 }
156
157 static BOOL CALLBACK focusUpdaterWindow(HWND hwnd, LPARAM lParam)
158 {
159         DWORD processId = *reinterpret_cast<WORD*>(lParam);
160         DWORD windowProcessId = NULL;
161         GetWindowThreadProcessId(hwnd, &windowProcessId);
162         if(windowProcessId == processId)
163         {
164                 SwitchToThisWindow(hwnd, TRUE);
165                 SetForegroundWindow(hwnd);
166                 return FALSE;
167         }
168
169         return TRUE;
170 }
171
172 ///////////////////////////////////////////////////////////////////////////////
173
174 class UpdateInfo
175 {
176 public:
177         UpdateInfo(void) { resetInfo(); }
178         
179         void resetInfo(void)
180         {
181                 m_buildNo = 0;
182                 m_buildDate.setDate(1900, 1, 1);
183                 m_downloadSite.clear();
184                 m_downloadAddress.clear();
185                 m_downloadFilename.clear();
186                 m_downloadFilecode.clear();
187         }
188
189         unsigned int m_buildNo;
190         QDate m_buildDate;
191         QString m_downloadSite;
192         QString m_downloadAddress;
193         QString m_downloadFilename;
194         QString m_downloadFilecode;
195 };
196
197 ///////////////////////////////////////////////////////////////////////////////
198
199 UpdateDialog::UpdateDialog(SettingsModel *settings, QWidget *parent)
200 :
201         QDialog(parent),
202         m_binaryWGet(lamexp_lookup_tool("wget.exe")),
203         m_binaryGnuPG(lamexp_lookup_tool("gpgv.exe")),
204         m_binaryUpdater(lamexp_lookup_tool("wupdate.exe")),
205         m_binaryKeys(lamexp_lookup_tool("gpgv.gpg")),
206         m_updateInfo(NULL),
207         m_settings(settings),
208         m_logFile(new QStringList()),
209         m_betaUpdates(settings ? (settings->autoUpdateCheckBeta() || lamexp_version_demo()) : lamexp_version_demo()),
210         m_success(false),
211         m_firstShow(true),
212         m_updateReadyToInstall(false),
213         m_updaterProcess(NULL)
214 {
215         if(m_binaryWGet.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryUpdater.isEmpty() || m_binaryKeys.isEmpty())
216         {
217                 throw "Tools not initialized correctly!";
218         }
219         
220         //Init the dialog, from the .ui file
221         setupUi(this);
222         setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint);
223
224         //Disable "X" button
225         HMENU hMenu = GetSystemMenu((HWND) winId(), FALSE);
226         EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
227
228         //Init animation
229         m_animator = new QMovie(":/images/Loading3.gif");
230         labelAnimationCenter->setMovie(m_animator);
231         m_animator->start();
232
233         //Indicate beta updates
234         if(m_betaUpdates)
235         {
236                 setWindowTitle(windowTitle().append(" [Beta]"));
237         }
238         
239         //Enable button
240         connect(retryButton, SIGNAL(clicked()), this, SLOT(checkForUpdates()));
241         connect(installButton, SIGNAL(clicked()), this, SLOT(applyUpdate()));
242         connect(infoLabel, SIGNAL(linkActivated(QString)), this, SLOT(linkActivated(QString)));
243         connect(logButton, SIGNAL(clicked()), this, SLOT(logButtonClicked()));
244
245         //Enable progress bar
246         connect(progressBar, SIGNAL(valueChanged(int)), this, SLOT(progressBarValueChanged(int)));
247 }
248
249 UpdateDialog::~UpdateDialog(void)
250 {
251         if(m_animator) m_animator->stop();
252         
253         LAMEXP_DELETE(m_updateInfo);
254         LAMEXP_DELETE(m_logFile);
255         LAMEXP_DELETE(m_animator);
256
257         WinSevenTaskbar::setTaskbarState(this->parentWidget(), WinSevenTaskbar::WinSevenTaskbarNoState);
258         WinSevenTaskbar::setOverlayIcon(this->parentWidget(), NULL);
259 }
260
261 void UpdateDialog::showEvent(QShowEvent *event)
262 {
263         QDialog::showEvent(event);
264         
265         if(m_firstShow)
266         {
267                 labelVersionInstalled->setText(QString("%1 %2 (%3)").arg(tr("Build"), QString::number(lamexp_version_build()), lamexp_version_date().toString(Qt::ISODate)));
268                 labelVersionLatest->setText(QString("(%1)").arg(tr("Unknown")));
269
270                 installButton->setEnabled(false);
271                 closeButton->setEnabled(false);
272                 retryButton->setEnabled(false);
273                 logButton->setEnabled(false);
274                 retryButton->hide();
275                 logButton->hide();
276                 infoLabel->hide();
277                 hintLabel->hide();
278                 hintIcon->hide();
279                 frameAnimation->hide();
280         
281                 int counter = MIN_CONNSCORE + 2;
282                 for(int i = 0; update_mirrors_prim[i]; i++) counter++;
283                 for(int i = 0; update_mirrors_back[i]; i++) counter++;
284
285                 progressBar->setMaximum(counter);
286                 progressBar->setValue(0);
287
288                 m_updaterProcess = NULL;
289
290                 QTimer::singleShot(0, this, SLOT(updateInit()));
291                 m_firstShow = false;
292         }
293 }
294
295 void UpdateDialog::closeEvent(QCloseEvent *event)
296 {
297         if(!closeButton->isEnabled())
298         {
299                 event->ignore();
300         }
301         else
302         {
303                 WinSevenTaskbar::setTaskbarState(this->parentWidget(), WinSevenTaskbar::WinSevenTaskbarNoState);
304                 WinSevenTaskbar::setOverlayIcon(this->parentWidget(), NULL);
305         }
306 }
307
308 void UpdateDialog::keyPressEvent(QKeyEvent *e)
309 {
310         if(e->key() == Qt::Key_F11)
311         {
312                 if(closeButton->isEnabled()) logButtonClicked();
313         }
314         else if((e->key() == Qt::Key_F12) && e->modifiers().testFlag(Qt::ControlModifier))
315         {
316                 if(closeButton->isEnabled())
317                 {
318                         testKnownWebSites();
319                         logButtonClicked();
320                 }
321         }
322         else
323         {
324                 QDialog::keyPressEvent(e);
325         }
326 }
327
328 bool UpdateDialog::event(QEvent *e)
329 {
330         if((e->type() == QEvent::ActivationChange) && (m_updaterProcess != NULL))
331         {
332                 EnumWindows(focusUpdaterWindow, reinterpret_cast<LPARAM>(&m_updaterProcess));
333         }
334         return QDialog::event(e);
335 }
336
337 bool UpdateDialog::winEvent(MSG *message, long *result)
338 {
339         return WinSevenTaskbar::handleWinEvent(message, result);
340 }
341
342 void UpdateDialog::updateInit(void)
343 {
344         setMinimumSize(size());
345         setMaximumHeight(height());
346         QApplication::processEvents();
347         checkForUpdates();
348 }
349
350 void UpdateDialog::checkForUpdates(void)
351 {
352         bool success = false;
353         int connectionScore = 0;
354
355         // ----- Initialization ----- //
356
357         m_updateInfo = new UpdateInfo;
358
359         progressBar->setValue(0);
360         WinSevenTaskbar::setTaskbarState(this->parentWidget(), WinSevenTaskbar::WinSevenTaskbarNormalState);
361         WinSevenTaskbar::setOverlayIcon(this->parentWidget(), &QIcon(":/icons/transmit_blue.png"));
362         installButton->setEnabled(false);
363         closeButton->setEnabled(false);
364         retryButton->setEnabled(false);
365         logButton->setEnabled(false);
366         if(infoLabel->isVisible()) infoLabel->hide();
367         if(hintLabel->isVisible()) hintLabel->hide();
368         if(hintIcon->isVisible()) hintIcon->hide();
369         frameAnimation->show();
370
371         QApplication::processEvents();
372         QApplication::setOverrideCursor(Qt::WaitCursor);
373
374         // ----- Test Internet Connection ----- //
375
376         statusLabel->setText(tr("Testing your internet connection, please wait..."));
377
378         m_logFile->clear();
379         m_logFile->append("Checking internet connection...");
380         
381         QFuture<BOOL> connectedState = QtConcurrent::run(getInternetConnectedState);
382         while(!connectedState.isFinished())
383         {
384                 QApplication::processEvents(QEventLoop::WaitForMoreEvents);
385         }
386
387         if(!connectedState.result())
388         {
389                 m_logFile->append(QStringList() << "" << "Operating system reports that the computer is currently offline !!!");
390                 if(!retryButton->isVisible()) retryButton->show();
391                 if(!logButton->isVisible()) logButton->show();
392                 closeButton->setEnabled(true);
393                 retryButton->setEnabled(true);
394                 logButton->setEnabled(true);
395                 if(frameAnimation->isVisible()) frameAnimation->hide();
396                 statusLabel->setText(tr("It appears that the computer currently is offline!"));
397                 progressBar->setValue(progressBar->maximum());
398                 hintIcon->setPixmap(QIcon(":/icons/network_error.png").pixmap(16,16));
399                 hintLabel->setText(tr("Please make sure your computer is connected to the internet and try again."));
400                 hintIcon->show();
401                 hintLabel->show();
402                 LAMEXP_DELETE(m_updateInfo);
403                 if(m_settings->soundsEnabled()) PlaySound(MAKEINTRESOURCE(IDR_WAVE_ERROR), GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC);
404                 QApplication::restoreOverrideCursor();
405                 progressBar->setValue(progressBar->maximum());
406                 WinSevenTaskbar::setTaskbarState(this->parentWidget(), WinSevenTaskbar::WinSevenTaskbarErrorState);
407                 WinSevenTaskbar::setOverlayIcon(this->parentWidget(), &QIcon(":/icons/exclamation.png"));
408                 return;
409         }
410         
411         progressBar->setValue(1);
412         QApplication::processEvents();
413
414         // ----- Test Known Hosts Connectivity ----- //
415
416         QStringList hostList;
417         for(int i = 0; known_hosts[i]; i++)
418         {
419                 hostList << QString::fromLatin1(known_hosts[i]);
420         }
421
422         qsrand(time(NULL));
423         while(!hostList.isEmpty())
424         {
425                 QString currentHost = hostList.takeAt(qrand() % hostList.count());
426                 if(connectionScore < MIN_CONNSCORE)
427                 {
428                         m_logFile->append(QStringList() << "" << "Testing host:" << currentHost << "");
429                         QString outFile = QString("%1/%2.htm").arg(lamexp_temp_folder2(), lamexp_rand_str());
430                         bool httpOk = false;
431                         if(getFile(currentHost, outFile, 0, &httpOk))
432                         {
433                                 connectionScore++;
434                                 progressBar->setValue(qBound(1, connectionScore + 1, MIN_CONNSCORE + 1));
435                                 QApplication::processEvents();
436                                 Sleep(125);
437                         }
438                         if(httpOk)
439                         {
440                                 connectionScore++;
441                                 progressBar->setValue(qBound(1, connectionScore + 1, MIN_CONNSCORE + 1));
442                                 QApplication::processEvents();
443                                 Sleep(125);
444                         }
445                         QFile::remove(outFile);
446                 }
447         }
448
449         if(connectionScore < MIN_CONNSCORE)
450         {
451                 if(!retryButton->isVisible()) retryButton->show();
452                 if(!logButton->isVisible()) logButton->show();
453                 closeButton->setEnabled(true);
454                 retryButton->setEnabled(true);
455                 logButton->setEnabled(true);
456                 if(frameAnimation->isVisible()) frameAnimation->hide();
457                 statusLabel->setText(tr("Network connectivity test has failed!"));
458                 progressBar->setValue(progressBar->maximum());
459                 hintIcon->setPixmap(QIcon(":/icons/network_error.png").pixmap(16,16));
460                 hintLabel->setText(tr("Please make sure your internet connection is working properly and try again."));
461                 hintIcon->show();
462                 hintLabel->show();
463                 LAMEXP_DELETE(m_updateInfo);
464                 if(m_settings->soundsEnabled()) PlaySound(MAKEINTRESOURCE(IDR_WAVE_ERROR), GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC);
465                 QApplication::restoreOverrideCursor();
466                 progressBar->setValue(progressBar->maximum());
467                 WinSevenTaskbar::setTaskbarState(this->parentWidget(), WinSevenTaskbar::WinSevenTaskbarErrorState);
468                 WinSevenTaskbar::setOverlayIcon(this->parentWidget(), &QIcon(":/icons/exclamation.png"));
469                 return;
470         }
471
472         // ----- Build Mirror List ----- //
473
474         statusLabel->setText(tr("Checking for new updates online, please wait..."));
475         m_logFile->append(QStringList() << "" << "----" << "" << "Checking for updates online...");
476         
477         QStringList mirrorList;
478         for(int index = 0; update_mirrors_prim[index]; index++)
479         {
480                 mirrorList << QString::fromLatin1(update_mirrors_prim[index]);
481         }
482
483         qsrand(time(NULL));
484         for(int i = 0; i < 4375; i++)
485         {
486                 mirrorList.swap(i % mirrorList.count(), qrand() % mirrorList.count());
487         }
488
489         for(int index = 0; update_mirrors_back[index]; index++)
490         {
491                 mirrorList << QString::fromLatin1(update_mirrors_back[index]);
492         }
493         
494         // ----- Fetch Update Info From Server ----- //
495
496         while(!mirrorList.isEmpty())
497         {
498                 QString currentMirror = mirrorList.takeFirst();
499                 progressBar->setValue(progressBar->value() + 1);
500                 if(!success)
501                 {
502                         if(tryUpdateMirror(m_updateInfo, currentMirror))
503                         {
504                                 success = true;
505                         }
506                 }
507                 else
508                 {
509                         QApplication::processEvents();
510                         Sleep(125);
511                 }
512         }
513         
514         QApplication::restoreOverrideCursor();
515         progressBar->setValue(progressBar->maximum());
516         
517         if(!success)
518         {
519                 if(!retryButton->isVisible()) retryButton->show();
520                 if(!logButton->isVisible()) logButton->show();
521                 closeButton->setEnabled(true);
522                 retryButton->setEnabled(true);
523                 logButton->setEnabled(true);
524                 if(frameAnimation->isVisible()) frameAnimation->hide();
525                 statusLabel->setText(tr("Failed to fetch update information from server!"));
526                 progressBar->setValue(progressBar->maximum());
527                 WinSevenTaskbar::setTaskbarState(this->parentWidget(), WinSevenTaskbar::WinSevenTaskbarErrorState);
528                 WinSevenTaskbar::setOverlayIcon(this->parentWidget(), &QIcon(":/icons/exclamation.png"));
529                 hintIcon->setPixmap(QIcon(":/icons/server_error.png").pixmap(16,16));
530                 hintLabel->setText(tr("Sorry, the update server might be busy at this time. Plase try again later."));
531                 hintIcon->show();
532                 hintLabel->show();
533                 LAMEXP_DELETE(m_updateInfo);
534                 if(m_settings->soundsEnabled()) PlaySound(MAKEINTRESOURCE(IDR_WAVE_ERROR), GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC);
535                 return;
536         }
537
538         // ----- Download New Program Version ----- //
539         
540         labelVersionLatest->setText(QString("%1 %2 (%3)").arg(tr("Build"), QString::number(m_updateInfo->m_buildNo), m_updateInfo->m_buildDate.toString(Qt::ISODate)));
541         infoLabel->show();
542         infoLabel->setText(QString("%1<br><a href=\"%2\">%2</a>").arg(tr("More information available at:"), m_updateInfo->m_downloadSite));
543         QApplication::processEvents();
544         
545         if(m_updateInfo->m_buildNo > lamexp_version_build())
546         {
547                 installButton->setEnabled(true);
548                 statusLabel->setText(tr("A new version of LameXP is available!"));
549                 hintIcon->setPixmap(QIcon(":/icons/shield_exclamation.png").pixmap(16,16));
550                 hintLabel->setText(tr("We highly recommend all users to install this update as soon as possible."));
551                 if(frameAnimation->isVisible()) frameAnimation->hide();
552                 hintIcon->show();
553                 hintLabel->show();
554                 WinSevenTaskbar::setOverlayIcon(this->parentWidget(), &QIcon(":/icons/shield_exclamation.png"));
555                 MessageBeep(MB_ICONINFORMATION);
556         }
557         else if(m_updateInfo->m_buildNo == lamexp_version_build())
558         {
559                 statusLabel->setText(tr("No new updates available at this time."));
560                 hintIcon->setPixmap(QIcon(":/icons/shield_green.png").pixmap(16,16));
561                 hintLabel->setText(tr("Your version of LameXP is still up-to-date. Please check for updates regularly!"));
562                 if(frameAnimation->isVisible()) frameAnimation->hide();
563                 hintIcon->show();
564                 hintLabel->show();
565                 WinSevenTaskbar::setOverlayIcon(this->parentWidget(), &QIcon(":/icons/shield_green.png"));
566                 MessageBeep(MB_ICONINFORMATION);
567         }
568         else
569         {
570                 statusLabel->setText(tr("Your version appears to be newer than the latest release."));
571                 hintIcon->setPixmap(QIcon(":/icons/shield_error.png").pixmap(16,16));
572                 hintLabel->setText(tr("This usually indicates your are currently using a pre-release version of LameXP."));
573                 if(frameAnimation->isVisible()) frameAnimation->hide();
574                 hintIcon->show();
575                 hintLabel->show();
576                 WinSevenTaskbar::setOverlayIcon(this->parentWidget(), &QIcon(":/icons/shield_error.png"));
577                 MessageBeep(MB_ICONEXCLAMATION);
578         }
579
580         closeButton->setEnabled(true);
581         if(retryButton->isVisible()) retryButton->hide();
582         if(logButton->isVisible()) logButton->hide();
583         if(frameAnimation->isVisible()) frameAnimation->hide();
584
585         m_success = true;
586 }
587
588 bool UpdateDialog::tryUpdateMirror(UpdateInfo *updateInfo, const QString &url)
589 {
590         bool success = false;
591         m_logFile->append(QStringList() << "" << "Trying mirror:" << url);
592
593         QString randPart = lamexp_rand_str();
594         QString outFileVersionInfo = QString("%1/%2.ver").arg(lamexp_temp_folder2(), randPart);
595         QString outFileSignature = QString("%1/%2.sig").arg(lamexp_temp_folder2(), randPart);
596
597         m_logFile->append(QStringList() << "" << "Downloading update info:");
598         bool ok1 = getFile(QString("%1%2").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileVersionInfo);
599
600         m_logFile->append(QStringList() << "" << "Downloading signature:");
601         bool ok2 = getFile(QString("%1%2.sig").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileSignature);
602
603         if(ok1 && ok2)
604         {
605                 m_logFile->append(QStringList() << "" << "Download okay, checking signature:");
606                 if(checkSignature(outFileVersionInfo, outFileSignature))
607                 {
608                         m_logFile->append(QStringList() << "" << "Signature okay, parsing info:");
609                         success = parseVersionInfo(outFileVersionInfo, updateInfo);
610                 }
611                 else
612                 {
613                         m_logFile->append(QStringList() << "" << "Bad signature, take care!");
614                 }
615         }
616         else
617         {
618                 m_logFile->append(QStringList() << "" << "Download has failed!");
619         }
620
621         QFile::remove(outFileVersionInfo);
622         QFile::remove(outFileSignature);
623         
624         return success;
625 }
626
627 bool UpdateDialog::getFile(const QString &url, const QString &outFile, unsigned int maxRedir, bool *httpOk)
628 {
629         QFileInfo output(outFile);
630         output.setCaching(false);
631         if(httpOk) *httpOk = false;
632
633         if(output.exists())
634         {
635                 QFile::remove(output.canonicalFilePath());
636                 if(output.exists())
637                 {
638                         return false;
639                 }
640         }
641
642         QProcess process;
643         process.setProcessChannelMode(QProcess::MergedChannels);
644         process.setReadChannel(QProcess::StandardOutput);
645         process.setWorkingDirectory(output.absolutePath());
646
647         QStringList args;
648         args << "--no-cache" << "--no-dns-cache" << QString().sprintf("--max-redirect=%u", maxRedir);
649         args << QString("--referer=%1://%2/").arg(QUrl(url).scheme(), QUrl(url).host()) << "-U" << USER_AGENT_STR;
650         args << "-O" << output.fileName() << url;
651
652         QEventLoop loop;
653         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
654         connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit()));
655         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
656
657         QTimer timer;
658         timer.setSingleShot(true);
659         timer.setInterval(25000);
660         connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
661
662         const QRegExp httpResponseOK("200 OK$");
663         
664         process.start(m_binaryWGet, args);
665         
666         if(!process.waitForStarted())
667         {
668                 return false;
669         }
670
671         timer.start();
672
673         while(process.state() == QProcess::Running)
674         {
675                 loop.exec();
676                 bool bTimeOut = (!timer.isActive());
677                 while(process.canReadLine())
678                 {
679                         QString line = QString::fromLatin1(process.readLine()).simplified();
680                         if(line.contains(httpResponseOK))
681                         {
682                                 line.append(" [OK]");
683                                 if(httpOk) *httpOk = true;
684                         }
685                         m_logFile->append(line);
686                 }
687                 if(bTimeOut)
688                 {
689                         qWarning("WGet process timed out <-- killing!");
690                         process.kill();
691                         process.waitForFinished();
692                         m_logFile->append("!!! TIMEOUT !!!");
693                         return false;
694                 }
695         }
696         
697         timer.stop();
698         timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
699
700         m_logFile->append(QString().sprintf("Exited with code %d", process.exitCode()));
701         return (process.exitCode() == 0) && output.exists() && output.isFile();
702 }
703
704 bool UpdateDialog::checkSignature(const QString &file, const QString &signature)
705 {
706         if(QFileInfo(file).absolutePath().compare(QFileInfo(signature).absolutePath(), Qt::CaseInsensitive) != 0)
707         {
708                 qWarning("CheckSignature: File and signature should be in same folder!");
709                 return false;
710         }
711         if(QFileInfo(file).absolutePath().compare(QFileInfo(m_binaryKeys).absolutePath(), Qt::CaseInsensitive) != 0)
712         {
713                 qWarning("CheckSignature: File and keyring should be in same folder!");
714                 return false;
715         }
716
717         QProcess process;
718         process.setProcessChannelMode(QProcess::MergedChannels);
719         process.setReadChannel(QProcess::StandardOutput);
720         process.setWorkingDirectory(QFileInfo(file).absolutePath());
721
722         QEventLoop loop;
723         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
724         connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit()));
725         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
726         
727         process.start(m_binaryGnuPG, QStringList() << "--homedir" << "." << "--keyring" << QFileInfo(m_binaryKeys).fileName() << QFileInfo(signature).fileName() << QFileInfo(file).fileName());
728
729         if(!process.waitForStarted())
730         {
731                 return false;
732         }
733
734         while(process.state() == QProcess::Running)
735         {
736                 loop.exec();
737                 while(process.canReadLine())
738                 {
739                         m_logFile->append(QString::fromLatin1(process.readLine()).simplified());
740                 }
741         }
742         
743         m_logFile->append(QString().sprintf("Exited with code %d", process.exitCode()));
744         return (process.exitCode() == 0);
745 }
746
747 bool UpdateDialog::parseVersionInfo(const QString &file, UpdateInfo *updateInfo)
748 {
749         QRegExp value("^(\\w+)=(.+)$");
750         QRegExp section("^\\[(.+)\\]$");
751
752         QDate updateInfoDate;
753         updateInfo->resetInfo();
754
755         QFile data(file);
756         if(!data.open(QIODevice::ReadOnly))
757         {
758                 qWarning("Cannot open update info file for reading!");
759                 return false;
760         }
761         
762         bool inHeader = false;
763         bool inSection = false;
764         
765         while(!data.atEnd())
766         {
767                 QString line = QString::fromLatin1(data.readLine()).trimmed();
768                 if(section.indexIn(line) >= 0)
769                 {
770                         m_logFile->append(QString("Sec: [%1]").arg(section.cap(1)));
771                         inSection = (section.cap(1).compare(section_id, Qt::CaseInsensitive) == 0);
772                         inHeader = (section.cap(1).compare(header_id, Qt::CaseInsensitive) == 0);
773                         continue;
774                 }
775                 if(inSection && (value.indexIn(line) >= 0))
776                 {
777                         m_logFile->append(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
778                         if(value.cap(1).compare("BuildNo", Qt::CaseInsensitive) == 0)
779                         {
780                                 bool ok = false;
781                                 unsigned int temp = value.cap(2).toUInt(&ok);
782                                 if(ok) updateInfo->m_buildNo = temp;
783                         }
784                         else if(value.cap(1).compare("BuildDate", Qt::CaseInsensitive) == 0)
785                         {
786                                 QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
787                                 if(temp.isValid()) updateInfo->m_buildDate = temp;
788                         }
789                         else if(value.cap(1).compare("DownloadSite", Qt::CaseInsensitive) == 0)
790                         {
791                                 updateInfo->m_downloadSite = value.cap(2).trimmed();
792                         }
793                         else if(value.cap(1).compare("DownloadAddress", Qt::CaseInsensitive) == 0)
794                         {
795                                 updateInfo->m_downloadAddress = value.cap(2).trimmed();
796                         }
797                         else if(value.cap(1).compare("DownloadFilename", Qt::CaseInsensitive) == 0)
798                         {
799                                 updateInfo->m_downloadFilename = value.cap(2).trimmed();
800                         }
801                         else if(value.cap(1).compare("DownloadFilecode", Qt::CaseInsensitive) == 0)
802                         {
803                                 updateInfo->m_downloadFilecode = value.cap(2).trimmed();
804                         }
805                 }
806                 if(inHeader && (value.indexIn(line) >= 0))
807                 {
808                         m_logFile->append(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
809                         if(value.cap(1).compare("TimestampCreated", Qt::CaseInsensitive) == 0)
810                         {
811                                 QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
812                                 if(temp.isValid()) updateInfoDate = temp;
813                         }
814                 }
815         }
816         
817         if(!updateInfoDate.isValid())
818         {
819                 updateInfo->resetInfo();
820                 m_logFile->append("WARNING: Version info timestamp is missing!");
821                 return false;
822         }
823         else if(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS) < QDate::currentDate())
824         {
825                 updateInfo->resetInfo();
826                 m_logFile->append(QString::fromLatin1("WARNING: This version info has expired at %1!").arg(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS).toString(Qt::ISODate)));
827                 return false;
828         }
829         else if(QDate::currentDate() < updateInfoDate)
830         {
831                 m_logFile->append("Version info is from the future, take care!");
832                 qWarning("Version info is from the future, take care!");
833         }
834
835         bool complete = true;
836
837         if(!(updateInfo->m_buildNo > 0)) complete = false;
838         if(!(updateInfo->m_buildDate.year() >= 2010)) complete = false;
839         if(updateInfo->m_downloadSite.isEmpty()) complete = false;
840         if(updateInfo->m_downloadAddress.isEmpty()) complete = false;
841         if(updateInfo->m_downloadFilename.isEmpty()) complete = false;
842         if(updateInfo->m_downloadFilecode.isEmpty()) complete = false;
843         
844         if(!complete)
845         {
846                 m_logFile->append("WARNING: Version info is incomplete!");
847         }
848
849         return complete;
850 }
851
852 void UpdateDialog::linkActivated(const QString &link)
853 {
854         QDesktopServices::openUrl(QUrl(link));
855 }
856
857 void UpdateDialog::applyUpdate(void)
858 {
859         installButton->setEnabled(false);
860         closeButton->setEnabled(false);
861         retryButton->setEnabled(false);
862
863         if(m_updateInfo)
864         {
865                 statusLabel->setText(tr("Update is being downloaded, please be patient..."));
866                 frameAnimation->show();
867                 if(hintLabel->isVisible()) hintLabel->hide();
868                 if(hintIcon->isVisible()) hintIcon->hide();
869                 int oldMax = progressBar->maximum();
870                 int oldMin = progressBar->minimum();
871                 progressBar->setRange(0, 0);
872                 QApplication::processEvents();
873                 
874                 QProcess process;
875                 QStringList args;
876                 QEventLoop loop;
877
878                 connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
879                 connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit()));
880
881                 args << QString("/Location=%1").arg(m_updateInfo->m_downloadAddress);
882                 args << QString("/Filename=%1").arg(m_updateInfo->m_downloadFilename);
883                 args << QString("/TicketID=%1").arg(m_updateInfo->m_downloadFilecode);
884                 args << QString("/ToFolder=%1").arg(QDir::toNativeSeparators(QDir(QApplication::applicationDirPath()).canonicalPath())); 
885                 args << QString("/ToExFile=%1.exe").arg(QFileInfo(QFileInfo(QApplication::applicationFilePath()).canonicalFilePath()).completeBaseName());
886                 args << QString("/AppTitle=LameXP (Build #%1)").arg(QString::number(m_updateInfo->m_buildNo));
887
888                 QApplication::setOverrideCursor(Qt::WaitCursor);
889                 WinSevenTaskbar::setTaskbarState(this->parentWidget(), WinSevenTaskbar::WinSevenTaskbarIndeterminateState);
890                 WinSevenTaskbar::setOverlayIcon(this->parentWidget(), &QIcon(":/icons/transmit_blue.png"));
891
892                 process.start(m_binaryUpdater, args);
893                 bool updateStarted = process.waitForStarted();
894                 if(updateStarted)
895                 {
896                         m_updaterProcess = process.pid()->dwProcessId;
897                         loop.exec();
898                 }
899                 m_updaterProcess = NULL;
900                 QApplication::restoreOverrideCursor();
901
902                 hintLabel->show();
903                 hintIcon->show();
904                 progressBar->setRange(oldMin, oldMax);
905                 progressBar->setValue(oldMax);
906                 frameAnimation->hide();
907
908                 if(updateStarted && (process.exitCode() == 0))
909                 {
910                         statusLabel->setText(tr("Update ready to install. Applicaion will quit..."));
911                         m_updateReadyToInstall = true;
912                         WinSevenTaskbar::setTaskbarState(this->parentWidget(), WinSevenTaskbar::WinSevenTaskbarNoState);
913                         WinSevenTaskbar::setOverlayIcon(this->parentWidget(), NULL);
914                         accept();
915                 }
916                 else
917                 {
918                         statusLabel->setText(tr("Update failed. Please try again or download manually!"));
919                         WinSevenTaskbar::setTaskbarState(this->parentWidget(), WinSevenTaskbar::WinSevenTaskbarErrorState);
920                         WinSevenTaskbar::setOverlayIcon(this->parentWidget(), &QIcon(":/icons/exclamation.png"));
921                         WinSevenTaskbar::setTaskbarProgress(this->parentWidget(), 100, 100);
922                 }
923         }
924
925         installButton->setEnabled(true);
926         closeButton->setEnabled(true);
927 }
928
929 void UpdateDialog::logButtonClicked(void)
930 {
931         LogViewDialog *logView = new LogViewDialog(this);
932         logView->exec(*m_logFile);
933         LAMEXP_DELETE(logView);
934 }
935
936 void UpdateDialog::progressBarValueChanged(int value)
937 {
938         WinSevenTaskbar::setTaskbarProgress(this->parentWidget(), value, progressBar->maximum());
939 }
940
941 void UpdateDialog::testKnownWebSites(void)
942 {
943         int connectionScore = 0;
944
945         // ----- Initialization ----- //
946
947         progressBar->setValue(0);
948         WinSevenTaskbar::setTaskbarState(this->parentWidget(), WinSevenTaskbar::WinSevenTaskbarNormalState);
949         WinSevenTaskbar::setOverlayIcon(this->parentWidget(), &QIcon(":/icons/transmit_blue.png"));
950         installButton->setEnabled(false);
951         closeButton->setEnabled(false);
952         retryButton->setEnabled(false);
953         logButton->setEnabled(false);
954         if(infoLabel->isVisible()) infoLabel->hide();
955         if(hintLabel->isVisible()) hintLabel->hide();
956         if(hintIcon->isVisible()) hintIcon->hide();
957         frameAnimation->show();
958
959         QApplication::processEvents();
960         QApplication::setOverrideCursor(Qt::WaitCursor);
961
962         // ----- Test Internet Connection ----- //
963
964         statusLabel->setText("Testing all known hosts, this may take a few minutes...");
965
966         m_logFile->clear();
967         m_logFile->append("Checking internet connection...");
968         
969         QFuture<BOOL> connectedState = QtConcurrent::run(getInternetConnectedState);
970         while(!connectedState.isFinished())
971         {
972                 QApplication::processEvents(QEventLoop::WaitForMoreEvents);
973         }
974
975         if(!connectedState.result())
976         {
977                 m_logFile->append(QStringList() << "" << "Operating system reports that the computer is currently offline !!!");
978                 if(!retryButton->isVisible()) retryButton->show();
979                 if(!logButton->isVisible()) logButton->show();
980                 closeButton->setEnabled(true);
981                 retryButton->setEnabled(true);
982                 logButton->setEnabled(true);
983                 if(frameAnimation->isVisible()) frameAnimation->hide();
984                 statusLabel->setText(tr("It appears that the computer currently is offline!"));
985                 progressBar->setValue(progressBar->maximum());
986                 hintIcon->setPixmap(QIcon(":/icons/network_error.png").pixmap(16,16));
987                 hintLabel->setText(tr("Please make sure your computer is connected to the internet and try again."));
988                 hintIcon->show();
989                 hintLabel->show();
990                 LAMEXP_DELETE(m_updateInfo);
991                 if(m_settings->soundsEnabled()) PlaySound(MAKEINTRESOURCE(IDR_WAVE_ERROR), GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC);
992                 QApplication::restoreOverrideCursor();
993                 progressBar->setValue(progressBar->maximum());
994                 WinSevenTaskbar::setTaskbarState(this->parentWidget(), WinSevenTaskbar::WinSevenTaskbarErrorState);
995                 WinSevenTaskbar::setOverlayIcon(this->parentWidget(), &QIcon(":/icons/exclamation.png"));
996                 return;
997         }
998         
999         // ----- Test Known Hosts Connectivity ----- //
1000
1001         QStringList hostList;
1002         for(int i = 0; known_hosts[i]; i++)
1003         {
1004                 hostList << QString::fromLatin1(known_hosts[i]);
1005         }
1006
1007         qDebug("[Known Hosts]");
1008
1009         int hostCount = hostList.count();
1010         while(!hostList.isEmpty())
1011         {
1012                 QString currentHost = hostList.takeFirst();
1013                 progressBar->setValue(qRound((static_cast<double>(progressBar->maximum() - 1) / static_cast<double>(hostCount)) * static_cast<double>(connectionScore)) + 1);
1014                 qDebug("Testing: %s", currentHost.toLatin1().constData());
1015                 m_logFile->append(QStringList() << "" << "Testing host:" << currentHost << "");
1016                 QString outFile = QString("%1/%2.htm").arg(lamexp_temp_folder2(), lamexp_rand_str());
1017                 bool httpOk = false;
1018                 if(getFile(currentHost, outFile, 0, &httpOk))
1019                 {
1020                         connectionScore++;
1021                 }
1022                 else
1023                 {
1024                         if(httpOk)
1025                         {
1026                                 qWarning("\nConnectivity test was SLOW on the following site:\n%s\n", currentHost.toLatin1().constData());
1027                                 connectionScore++;
1028                         }
1029                         else
1030                         {
1031                                 qWarning("\nConnectivity test FAILED on the following site:\n%s\n", currentHost.toLatin1().constData());
1032                         }
1033                 }
1034                 QFile::remove(outFile);
1035         }
1036
1037         if(connectionScore < hostCount)
1038         {
1039                 if(!retryButton->isVisible()) retryButton->show();
1040                 if(!logButton->isVisible()) logButton->show();
1041                 closeButton->setEnabled(true);
1042                 retryButton->setEnabled(true);
1043                 logButton->setEnabled(true);
1044                 if(frameAnimation->isVisible()) frameAnimation->hide();
1045                 statusLabel->setText("At least one host could not be reached!");
1046                 progressBar->setValue(progressBar->maximum());
1047                 hintIcon->setPixmap(QIcon(":/icons/network_error.png").pixmap(16,16));
1048                 hintLabel->setText("Please make sure your internet connection is working properly and try again.");
1049                 hintIcon->show();
1050                 hintLabel->show();
1051                 LAMEXP_DELETE(m_updateInfo);
1052                 if(m_settings->soundsEnabled()) PlaySound(MAKEINTRESOURCE(IDR_WAVE_ERROR), GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC);
1053                 QApplication::restoreOverrideCursor();
1054                 progressBar->setValue(progressBar->maximum());
1055                 WinSevenTaskbar::setTaskbarState(this->parentWidget(), WinSevenTaskbar::WinSevenTaskbarErrorState);
1056                 WinSevenTaskbar::setOverlayIcon(this->parentWidget(), &QIcon(":/icons/exclamation.png"));
1057                 return;
1058         }
1059
1060         // ----- Done ----- //
1061         
1062         QApplication::restoreOverrideCursor();
1063         progressBar->setValue(progressBar->maximum());
1064
1065         statusLabel->setText("Test completed.");
1066         hintIcon->setPixmap(QIcon(":/icons/shield_green.png").pixmap(16,16));
1067         hintLabel->setText("Congratulations, the test has completed.");
1068         if(frameAnimation->isVisible()) frameAnimation->hide();
1069         hintIcon->show();
1070         hintLabel->show();
1071         WinSevenTaskbar::setOverlayIcon(this->parentWidget(), &QIcon(":/icons/shield_green.png"));
1072         MessageBeep(MB_ICONINFORMATION);
1073
1074         closeButton->setEnabled(true);
1075         if(retryButton->isVisible()) retryButton->hide();
1076         if(logButton->isVisible()) logButton->hide();
1077         if(frameAnimation->isVisible()) frameAnimation->hide();
1078 }