OSDN Git Service

Actually implement the auto-updater.
[lamexp/LameXP.git] / src / Dialog_Update.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2010 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
27 #include <QClipboard>
28 #include <QFileDialog>
29 #include <QTimer>
30 #include <QProcess>
31 #include <QUuid>
32 #include <QDate>
33 #include <QRegExp>
34 #include <QDesktopServices>
35 #include <QUrl>
36 #include <QCloseEvent>
37
38 #include <Windows.h>
39
40 ///////////////////////////////////////////////////////////////////////////////
41
42 static const char *section_id = "LameXP";
43
44 static const char *mirror_url_postfix = "update_beta.ver";
45
46 static const char *mirrors[] =
47 {
48         "http://mulder.dummwiedeutsch.de/",
49         "http://mulder.brhack.net/",
50         "http://free.pages.at/borschdfresser/",
51         "http://mplayer.savedonthe.net/",
52         "http://www.tricksoft.de/",
53         NULL
54 };
55
56 ///////////////////////////////////////////////////////////////////////////////
57
58 class UpdateInfo
59 {
60 public:
61         UpdateInfo(void)
62         :
63                 m_buildNo(0),
64                 m_buildDate(1900, 1, 1),
65                 m_downloadSite(""),
66                 m_downloadAddress(""),
67                 m_downloadFilename(""),
68                 m_downloadFilecode("")
69         {
70         }
71         
72         unsigned int m_buildNo;
73         QDate m_buildDate;
74         QString m_downloadSite;
75         QString m_downloadAddress;
76         QString m_downloadFilename;
77         QString m_downloadFilecode;
78 };
79
80 ///////////////////////////////////////////////////////////////////////////////
81
82 UpdateDialog::UpdateDialog(QWidget *parent)
83 :
84         QDialog(parent),
85         m_binaryWGet(lamexp_lookup_tool("wget.exe")),
86         m_binaryGnuPG(lamexp_lookup_tool("gpgv.exe")),
87         m_binaryUpdater(lamexp_lookup_tool("wupdate.exe")),
88         m_binaryKeys(lamexp_lookup_tool("gpgv.gpg")),
89         m_updateInfo(NULL)
90 {
91         if(m_binaryWGet.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryUpdater.isEmpty() || m_binaryKeys.isEmpty())
92         {
93                 throw "Tools not initialized correctly!";
94         }
95         
96         //Init the dialog, from the .ui file
97         setupUi(this);
98         setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint);
99
100         //Disable "X" button
101         HMENU hMenu = GetSystemMenu((HWND) winId(), FALSE);
102         EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
103
104         //Enable button
105         connect(retryButton, SIGNAL(clicked()), this, SLOT(checkForUpdates()));
106         connect(installButton, SIGNAL(clicked()), this, SLOT(applyUpdate()));
107         connect(infoLabel, SIGNAL(linkActivated(QString)), this, SLOT(linkActivated(QString)));
108 }
109
110 UpdateDialog::~UpdateDialog(void)
111 {
112         LAMEXP_DELETE(m_updateInfo);
113 }
114
115 void UpdateDialog::showEvent(QShowEvent *event)
116 {
117         QDialog::showEvent(event);
118         
119         statusLabel->setText("Checking for new updates online, please wait...");
120         labelVersionInstalled->setText(QString("Build %1 (%2)").arg(QString::number(lamexp_version_build()), lamexp_version_date().toString(Qt::ISODate)));
121         labelVersionLatest->setText("(Unknown)");
122
123         QTimer::singleShot(0, this, SLOT(updateInit()));
124         installButton->setEnabled(false);
125         closeButton->setEnabled(false);
126         retryButton->setEnabled(false);
127         retryButton->hide();
128         infoLabel->hide();
129         
130         for(int i = 0; mirrors[i]; i++)
131         {
132                 progressBar->setMaximum(i+2);
133         }
134
135         progressBar->setValue(0);
136 }
137
138 void UpdateDialog::closeEvent(QCloseEvent *event)
139 {
140         if(!closeButton->isEnabled()) event->ignore();
141 }
142
143 void UpdateDialog::updateInit(void)
144 {
145         setMinimumSize(size());
146         setMaximumHeight(height());
147
148         checkForUpdates();
149 }
150
151 void UpdateDialog::checkForUpdates(void)
152 {
153         bool success = false;
154         m_updateInfo = new UpdateInfo;
155
156         progressBar->setValue(0);
157         installButton->setEnabled(false);
158         closeButton->setEnabled(false);
159         retryButton->setEnabled(false);
160         if(infoLabel->isVisible()) infoLabel->hide();
161
162         QApplication::processEvents();
163         QApplication::setOverrideCursor(Qt::WaitCursor);
164
165         for(int i = 0; mirrors[i]; i++)
166         {
167                 progressBar->setValue(i+1);
168                 if(tryUpdateMirror(m_updateInfo, mirrors[i]))
169                 {
170                         success = true;
171                         break;
172                 }
173         }
174         
175         QApplication::restoreOverrideCursor();
176
177         if(!success)
178         {
179                 if(!retryButton->isVisible()) retryButton->show();
180                 closeButton->setEnabled(true);
181                 retryButton->setEnabled(true);
182                 statusLabel->setText("Failed to fetch update information. Check internet connection!");
183                 progressBar->setValue(progressBar->maximum());
184                 LAMEXP_DELETE(m_updateInfo);
185                 PlaySound(MAKEINTRESOURCE(IDR_WAVE_ERROR), GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC);
186                 return;
187         }
188         
189         labelVersionLatest->setText(QString("Build %1 (%2)").arg(QString::number(m_updateInfo->m_buildNo), m_updateInfo->m_buildDate.toString(Qt::ISODate)));
190         infoLabel->show();
191         infoLabel->setText(QString("More information available at:<br><a href=\"%1\">%1</a>").arg(m_updateInfo->m_downloadSite));
192         QApplication::processEvents();
193         
194         if(m_updateInfo->m_buildNo > lamexp_version_build())
195         {
196                 installButton->setEnabled(true);
197                 statusLabel->setText("A new version of LameXP is available. Update highly recommended!");
198                 MessageBeep(MB_ICONINFORMATION);
199         }
200         else if(m_updateInfo->m_buildNo == lamexp_version_build())
201         {
202                 statusLabel->setText("No new updates avialbale. Your version of LameXP is up-to-date.");
203                 MessageBeep(MB_ICONINFORMATION);
204         }
205         else
206         {
207                 statusLabel->setText("Your version appears to be newer than the latest release.");
208                 MessageBeep(MB_ICONEXCLAMATION);
209         }
210
211         closeButton->setEnabled(true);
212         if(retryButton->isVisible()) retryButton->hide();
213         progressBar->setValue(progressBar->maximum());
214 }
215
216 bool UpdateDialog::tryUpdateMirror(UpdateInfo *updateInfo, const QString &url)
217 {
218         bool success = false;
219         
220         QUuid uuid = QUuid::createUuid();
221         QString outFileVersionInfo = QString("%1/%2.ver").arg(QDir::tempPath(), uuid.toString());
222         QString outFileSignature = QString("%1/%2.sig").arg(QDir::tempPath(), uuid.toString());
223
224         qDebug("\nDownloading update info:");
225         bool ok1 = getFile(QString("%1%2").arg(url,mirror_url_postfix), outFileVersionInfo);
226
227         qDebug("\nDownloading signature file:");
228         bool ok2 = getFile(QString("%1%2.sig").arg(url,mirror_url_postfix), outFileSignature);
229
230         if(ok1 && ok2)
231         {
232                 qDebug("\nDownload okay, checking signature:");
233                 if(checkSignature(outFileVersionInfo, outFileSignature))
234                 {
235                         qDebug("\nSignature okay, parsing info:");
236                         success = parseVersionInfo(outFileVersionInfo, updateInfo);
237                 }
238                 else
239                 {
240                         qDebug("\nBad signature, take care!");
241                 }
242         }
243         else
244         {
245                 qDebug("\nDownload has failed!");
246         }
247
248         QFile::remove(outFileVersionInfo);
249         QFile::remove(outFileSignature);
250         
251         return success;
252 }
253
254 bool UpdateDialog::getFile(const QString &url, const QString &outFile)
255 {
256         QFileInfo output(outFile);
257         output.setCaching(false);
258
259         if(output.exists())
260         {
261                 QFile::remove(output.canonicalFilePath());
262                 if(output.exists())
263                 {
264                         return false;
265                 }
266         }
267
268         QProcess process;
269         process.setProcessChannelMode(QProcess::MergedChannels);
270         process.setReadChannel(QProcess::StandardOutput);
271
272         QEventLoop loop;
273         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
274         connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit()));
275         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
276
277         process.start(m_binaryWGet, QStringList() << "-O" << output.absoluteFilePath() << url);
278         
279         if(!process.waitForStarted())
280         {
281                 return false;
282         }
283
284         while(process.state() == QProcess::Running)
285         {
286                 loop.exec();
287                 while(process.canReadLine())
288                 {
289                         qDebug("WGet: %s", QString::fromLatin1(process.readLine()).simplified().toLatin1().constData());
290                 }
291         }
292         
293         qDebug("WGet: Exited with code %d", process.exitCode());
294         return (process.exitCode() == 0) && output.exists() && output.isFile();
295 }
296
297 bool UpdateDialog::checkSignature(const QString &file, const QString &signature)
298 {
299         QProcess process;
300         process.setProcessChannelMode(QProcess::MergedChannels);
301         process.setReadChannel(QProcess::StandardOutput);
302
303         QEventLoop loop;
304         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
305         connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit()));
306         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
307         
308         process.start(m_binaryGnuPG, QStringList() << "--homedir" << lamexp_temp_folder() << "--keyring" << QDir::toNativeSeparators(m_binaryKeys) << QDir::toNativeSeparators(signature) << QDir::toNativeSeparators(file));
309
310         if(!process.waitForStarted())
311         {
312                 return false;
313         }
314
315         while(process.state() == QProcess::Running)
316         {
317                 loop.exec();
318                 while(process.canReadLine())
319                 {
320                         qDebug("GnuPG: %s", QString::fromLatin1(process.readLine()).simplified().toLatin1().constData());
321                 }
322         }
323         
324         qDebug("GnuPG: Exited with code %d", process.exitCode());
325         return (process.exitCode() == 0);
326 }
327
328 bool UpdateDialog::parseVersionInfo(const QString &file, UpdateInfo *updateInfo)
329 {
330         
331         QRegExp value("^(\\w+)=(.+)$");
332         QRegExp section("^\\[(.+)\\]$");
333
334         QFile data(file);
335         if(!data.open(QIODevice::ReadOnly))
336         {
337                 qWarning("Cannot open update info file for reading!");
338                 return false;
339         }
340         
341         bool inSection = false;
342         
343         while(!data.atEnd())
344         {
345                 QString line = QString::fromLatin1(data.readLine()).trimmed();
346                 if(section.indexIn(line) >= 0)
347                 {
348                         qDebug("Section: '%s'", section.cap(1).toLatin1().constData());
349                         inSection = (section.cap(1).compare(section_id, Qt::CaseInsensitive) == 0);
350                         continue;
351                 }
352                 if(inSection && value.indexIn(line) >= 0)
353                 {
354                         qDebug("Value: '%s' ==> '%s'", value.cap(1).toLatin1().constData(), value.cap(2).toLatin1().constData());
355                         if(value.cap(1).compare("BuildNo", Qt::CaseInsensitive) == 0)
356                         {
357                                 bool ok = false;
358                                 unsigned int temp = value.cap(2).toUInt(&ok);
359                                 if(ok) updateInfo->m_buildNo = temp;
360                         }
361                         else if(value.cap(1).compare("BuildDate", Qt::CaseInsensitive) == 0)
362                         {
363                                 QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
364                                 if(temp.isValid()) updateInfo->m_buildDate = temp;
365                         }
366                         else if(value.cap(1).compare("DownloadSite", Qt::CaseInsensitive) == 0)
367                         {
368                                 updateInfo->m_downloadSite = value.cap(2).trimmed();
369                         }
370                         else if(value.cap(1).compare("DownloadAddress", Qt::CaseInsensitive) == 0)
371                         {
372                                 updateInfo->m_downloadAddress = value.cap(2).trimmed();
373                         }
374                         else if(value.cap(1).compare("DownloadFilename", Qt::CaseInsensitive) == 0)
375                         {
376                                 updateInfo->m_downloadFilename = value.cap(2).trimmed();
377                         }
378                         else if(value.cap(1).compare("DownloadFilecode", Qt::CaseInsensitive) == 0)
379                         {
380                                 updateInfo->m_downloadFilecode = value.cap(2).trimmed();
381                         }
382                 }
383         }
384         
385         bool complete = true;
386
387         if(!(updateInfo->m_buildNo > 0)) complete = false;
388         if(!(updateInfo->m_buildDate.year() >= 2010)) complete = false;
389         if(updateInfo->m_downloadSite.isEmpty()) complete = false;
390         if(updateInfo->m_downloadAddress.isEmpty()) complete = false;
391         if(updateInfo->m_downloadFilename.isEmpty()) complete = false;
392         if(updateInfo->m_downloadFilecode.isEmpty()) complete = false;
393
394         return complete;
395 }
396
397 void UpdateDialog::linkActivated(const QString &link)
398 {
399         QDesktopServices::openUrl(QUrl(link));
400 }
401
402 void UpdateDialog::applyUpdate(void)
403 {
404         installButton->setEnabled(false);
405         closeButton->setEnabled(false);
406         retryButton->setEnabled(false);
407
408         if(m_updateInfo)
409         {
410                 statusLabel->setText("Update is being downloaded, please be patient...");
411                 QApplication::processEvents();
412                 
413                 QProcess process;
414                 QStringList args;
415                 QEventLoop loop;
416
417                 connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
418                 connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit()));
419
420                 args << QString("/Location=%1").arg(m_updateInfo->m_downloadAddress);
421                 args << QString("/Filename=%1").arg(m_updateInfo->m_downloadFilename);
422                 args << QString("/TicketID=%1").arg(m_updateInfo->m_downloadFilecode);
423                 args << QString("/ToFolder=%1").arg(QDir::toNativeSeparators(QApplication::applicationDirPath()));
424                 args << QString("/AppTitle=LameXP (Build #%1)").arg(QString::number(m_updateInfo->m_buildNo));
425
426                 QApplication::setOverrideCursor(Qt::WaitCursor);
427                 process.start(m_binaryUpdater, args);
428                 loop.exec();
429                 QApplication::restoreOverrideCursor();
430                 
431                 if(process.exitCode() == 0)
432                 {
433                         statusLabel->setText("Update ready to install. Applicaion will quit...");
434                         QApplication::quit();
435                 }
436                 else
437                 {
438                         statusLabel->setText("Update failed. Please try again or download manually!");
439                 }
440         }
441
442         installButton->setEnabled(true);
443         closeButton->setEnabled(true);
444 }