From b59bb2399a83c7dce56f5015d8a4df7e4bc28aa9 Mon Sep 17 00:00:00 2001 From: lordmulder Date: Sun, 28 Nov 2010 22:18:07 +0100 Subject: [PATCH] Actually implement the auto-updater. --- gui/UpdateDialog.ui | 191 ++++++++++++++-- res/Icons.qrc | 1 + res/Tools.qrc | 1 + res/tools/gpgv.gpg | Bin 0 -> 1695 bytes src/Config.h | 2 +- src/Dialog_Update.cpp | 401 ++++++++++++++++++++++++++++++++-- src/Dialog_Update.h | 21 +- src/Thread_Initialization.cpp | 1 + 8 files changed, 582 insertions(+), 36 deletions(-) create mode 100644 res/tools/gpgv.gpg diff --git a/gui/UpdateDialog.ui b/gui/UpdateDialog.ui index 35b4af31..a0937d3f 100644 --- a/gui/UpdateDialog.ui +++ b/gui/UpdateDialog.ui @@ -6,7 +6,7 @@ 0 0 - 622 + 642 364 @@ -143,27 +143,20 @@ 0 - - 18 + + 32 + + + 42 + + + 32 + + + 32 - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 40 - - - - @@ -200,6 +193,163 @@ + + + + + + Latest version available: + + + + + + + (Unknown) + + + + + + + Currently installed version: + + + + + + + (Unknown) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 15 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 15 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + $(INFORMATION) + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 100 + 0 + + + + Retry + + + + :/icons/arrow_refresh.png:/icons/arrow_refresh.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -331,6 +481,9 @@ + + + diff --git a/res/Icons.qrc b/res/Icons.qrc index 0f8a5553..ab39e215 100644 --- a/res/Icons.qrc +++ b/res/Icons.qrc @@ -9,6 +9,7 @@ icons/application_xp_terminal.png icons/arrow_down.png icons/arrow_up.png + icons/arrow_refresh.png icons/bin.png icons/bomb.png icons/calendar.png diff --git a/res/Tools.qrc b/res/Tools.qrc index 14875932..0154dfe6 100644 --- a/res/Tools.qrc +++ b/res/Tools.qrc @@ -5,6 +5,7 @@ tools/faad.exe tools/flac.exe tools/gpgv.exe + tools/gpgv.gpg tools/lame.exe tools/MAC.exe tools/mediainfo_icl11.exe diff --git a/res/tools/gpgv.gpg b/res/tools/gpgv.gpg new file mode 100644 index 0000000000000000000000000000000000000000..8ac5f57745e42fca95a34481145fc2b492a16f5d GIT binary patch literal 1695 zcmV;Q24MM_0ipy-bSyX#1OS_68&w^)__b+C2rN+D;r&ew^HGY(*+I?sh9vO9MiA{q z#wZ}NWdL|&W*n+V+l4|0^BNuJ{N49=Ztb!iC;Y|*p7%&uM_NlQ`K`vIbxE7QVp4I- zg3fahFiFkN)7JwpU3PpyO0#~Q?Bm!+px2YWVObn3o!u;)ktYmu6)OOss4FM;E^Ea% zhu>=r%Gn(-$fJ-w1N|D!W+1Enl)Mm8lF^oc8FElwDJw#t?FUIH3G@=9o2NOOnbCiRie#+gBcIhT~n}Lol zc~wp;9rRV{n??r*^A|B1%`KMF&i9hulZ2TW^3x}X*LM`l%W4DuF^z45K3oCrQWQDE z@?EcKmFStD%CRt*f^1BDLt1|vv*hn@}1*sQBjrz|B zseRs{okAn(0acY&WXMAKS|iWGw6uu_S(tmXFW^#6mjaZ{@F%~;yNrN1U#k@t&Ox)L z^nDba7||Z&HUwT#v@=X^Qe;e4ber<^h~y*nWjb= z*6h3kLbC`Bmm12lkQ>;6Lh+9|j$Y5Y$5(_eJ*!v`<;UCUV)GUV5oX?$5KY}D=7G4R zuKG{1)_?Cu$_J*KNUM2XqH(!(dtT*`7=z*i>^5`R1)#f%7I-<-m)E+TnB60*hKt%;8Oi~Q#+PYT_2R#L7$b8+KM6Y6k7%C4eEpK zi{c?-!E8K2wg=S*y9QwvWwgV+HY)py1H4NUn!-zgw7k^WmfpM*l4fjLnl1MeY5*B) zTwG{M9L&4C^N=a@p>h3<(E}kgG?C|t@w)B*1#ex81}3wF~M9chgHzTX66A$k2-P*x1!p0d@N~Jmu;RElJFe#05 z63~HZTkg_vgxdI|83~DghPv-R-S|+@hyC@LX?QATFp*7?_+S*ptqD`)=IvtsTo+RZ_t$ zku-K=4&h__sXS-M+hw!O_&Qo#8e=OmIM>B_$_h*7T3u8<1TZZ`GLjE$myv+yfRe*; zvy(XT@q9D?bgN;jITru}1rPle{?53z!Xvv<1{2GWw^ztyK$Y;%2PNfNw9yqOBg3sP z)*$UKspoNqhkB}!byEYu8(1({r@Nf^MW7tRYa+ec>4aTL;L>y$m{kc{PS0G9`PCg& z6<8~6Ts|z#v&&D`L@8m~r53;Hx+dbe=-Mbh9qfPsIJ)7ei5R@9-ySyWx)lF(EkdZ$ z-)1mOl>l;GLANHf!9`P&xA{k*Rfs6D`em5AdBTt^Ti?9Rg`I0)9XeZ{2>>#WX%}UBrTQ|56cNG z55){B+gf!o+y!n9{6<0$1EA~I+~;Y7vyrlpnimM->=DiX$7TtW$ZsG$;7970{|r}H zG}U85i`0O=#*P@=977fW3)Ukx-O9jc+86@d z*i7a!j@EZET{KGHCIleVOix z(52TjE{>wt*{dl_`N${vB*S)Li-~P3!?rKzEK&KDyYDEL9fqb&b~J}NxKsa4oopZo zkt-XTn!3TfVkch=cc&Tk^Ue pl!KR6XO`aJdC?VZ0Gta;leP6w^;<#ljMjRvPS #include #include +#include +#include +#include +#include +#include +#include +#include #include +/////////////////////////////////////////////////////////////////////////////// + +static const char *section_id = "LameXP"; + +static const char *mirror_url_postfix = "update_beta.ver"; + +static const char *mirrors[] = +{ + "http://mulder.dummwiedeutsch.de/", + "http://mulder.brhack.net/", + "http://free.pages.at/borschdfresser/", + "http://mplayer.savedonthe.net/", + "http://www.tricksoft.de/", + NULL +}; + +/////////////////////////////////////////////////////////////////////////////// + +class UpdateInfo +{ +public: + UpdateInfo(void) + : + m_buildNo(0), + m_buildDate(1900, 1, 1), + m_downloadSite(""), + m_downloadAddress(""), + m_downloadFilename(""), + m_downloadFilecode("") + { + } + + unsigned int m_buildNo; + QDate m_buildDate; + QString m_downloadSite; + QString m_downloadAddress; + QString m_downloadFilename; + QString m_downloadFilecode; +}; + +/////////////////////////////////////////////////////////////////////////////// + UpdateDialog::UpdateDialog(QWidget *parent) : - QDialog(parent) + QDialog(parent), + m_binaryWGet(lamexp_lookup_tool("wget.exe")), + m_binaryGnuPG(lamexp_lookup_tool("gpgv.exe")), + m_binaryUpdater(lamexp_lookup_tool("wupdate.exe")), + m_binaryKeys(lamexp_lookup_tool("gpgv.gpg")), + m_updateInfo(NULL) { + if(m_binaryWGet.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryUpdater.isEmpty() || m_binaryKeys.isEmpty()) + { + throw "Tools not initialized correctly!"; + } + //Init the dialog, from the .ui file setupUi(this); setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint); - setMinimumSize(size()); - setMaximumHeight(height()); //Disable "X" button HMENU hMenu = GetSystemMenu((HWND) winId(), FALSE); EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED); - //Init flags - m_clipboardUsed = false; + //Enable button + connect(retryButton, SIGNAL(clicked()), this, SLOT(checkForUpdates())); + connect(installButton, SIGNAL(clicked()), this, SLOT(applyUpdate())); + connect(infoLabel, SIGNAL(linkActivated(QString)), this, SLOT(linkActivated(QString))); } UpdateDialog::~UpdateDialog(void) { - if(m_clipboardUsed) - { - QApplication::clipboard()->clear(); - } + LAMEXP_DELETE(m_updateInfo); } void UpdateDialog::showEvent(QShowEvent *event) { + QDialog::showEvent(event); + statusLabel->setText("Checking for new updates online, please wait..."); - QTimer::singleShot(8000, this, SLOT(updateCompleted())); + labelVersionInstalled->setText(QString("Build %1 (%2)").arg(QString::number(lamexp_version_build()), lamexp_version_date().toString(Qt::ISODate))); + labelVersionLatest->setText("(Unknown)"); + + QTimer::singleShot(0, this, SLOT(updateInit())); installButton->setEnabled(false); closeButton->setEnabled(false); + retryButton->setEnabled(false); + retryButton->hide(); + infoLabel->hide(); + + for(int i = 0; mirrors[i]; i++) + { + progressBar->setMaximum(i+2); + } + + progressBar->setValue(0); } -void UpdateDialog::updateCompleted(void) +void UpdateDialog::closeEvent(QCloseEvent *event) { - statusLabel->setText("No new updates avialbale. Your version of LameXP is up-to-date."); + if(!closeButton->isEnabled()) event->ignore(); +} + +void UpdateDialog::updateInit(void) +{ + setMinimumSize(size()); + setMaximumHeight(height()); + + checkForUpdates(); +} + +void UpdateDialog::checkForUpdates(void) +{ + bool success = false; + m_updateInfo = new UpdateInfo; + + progressBar->setValue(0); + installButton->setEnabled(false); + closeButton->setEnabled(false); + retryButton->setEnabled(false); + if(infoLabel->isVisible()) infoLabel->hide(); + + QApplication::processEvents(); + QApplication::setOverrideCursor(Qt::WaitCursor); + + for(int i = 0; mirrors[i]; i++) + { + progressBar->setValue(i+1); + if(tryUpdateMirror(m_updateInfo, mirrors[i])) + { + success = true; + break; + } + } + + QApplication::restoreOverrideCursor(); + + if(!success) + { + if(!retryButton->isVisible()) retryButton->show(); + closeButton->setEnabled(true); + retryButton->setEnabled(true); + statusLabel->setText("Failed to fetch update information. Check internet connection!"); + progressBar->setValue(progressBar->maximum()); + LAMEXP_DELETE(m_updateInfo); + PlaySound(MAKEINTRESOURCE(IDR_WAVE_ERROR), GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC); + return; + } + + labelVersionLatest->setText(QString("Build %1 (%2)").arg(QString::number(m_updateInfo->m_buildNo), m_updateInfo->m_buildDate.toString(Qt::ISODate))); + infoLabel->show(); + infoLabel->setText(QString("More information available at:
%1").arg(m_updateInfo->m_downloadSite)); + QApplication::processEvents(); + + if(m_updateInfo->m_buildNo > lamexp_version_build()) + { + installButton->setEnabled(true); + statusLabel->setText("A new version of LameXP is available. Update highly recommended!"); + MessageBeep(MB_ICONINFORMATION); + } + else if(m_updateInfo->m_buildNo == lamexp_version_build()) + { + statusLabel->setText("No new updates avialbale. Your version of LameXP is up-to-date."); + MessageBeep(MB_ICONINFORMATION); + } + else + { + statusLabel->setText("Your version appears to be newer than the latest release."); + MessageBeep(MB_ICONEXCLAMATION); + } + + closeButton->setEnabled(true); + if(retryButton->isVisible()) retryButton->hide(); + progressBar->setValue(progressBar->maximum()); +} + +bool UpdateDialog::tryUpdateMirror(UpdateInfo *updateInfo, const QString &url) +{ + bool success = false; + + QUuid uuid = QUuid::createUuid(); + QString outFileVersionInfo = QString("%1/%2.ver").arg(QDir::tempPath(), uuid.toString()); + QString outFileSignature = QString("%1/%2.sig").arg(QDir::tempPath(), uuid.toString()); + + qDebug("\nDownloading update info:"); + bool ok1 = getFile(QString("%1%2").arg(url,mirror_url_postfix), outFileVersionInfo); + + qDebug("\nDownloading signature file:"); + bool ok2 = getFile(QString("%1%2.sig").arg(url,mirror_url_postfix), outFileSignature); + + if(ok1 && ok2) + { + qDebug("\nDownload okay, checking signature:"); + if(checkSignature(outFileVersionInfo, outFileSignature)) + { + qDebug("\nSignature okay, parsing info:"); + success = parseVersionInfo(outFileVersionInfo, updateInfo); + } + else + { + qDebug("\nBad signature, take care!"); + } + } + else + { + qDebug("\nDownload has failed!"); + } + + QFile::remove(outFileVersionInfo); + QFile::remove(outFileSignature); + + return success; +} + +bool UpdateDialog::getFile(const QString &url, const QString &outFile) +{ + QFileInfo output(outFile); + output.setCaching(false); + + if(output.exists()) + { + QFile::remove(output.canonicalFilePath()); + if(output.exists()) + { + return false; + } + } + + QProcess process; + process.setProcessChannelMode(QProcess::MergedChannels); + process.setReadChannel(QProcess::StandardOutput); + + QEventLoop loop; + connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit())); + connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit())); + connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit())); + + process.start(m_binaryWGet, QStringList() << "-O" << output.absoluteFilePath() << url); + + if(!process.waitForStarted()) + { + return false; + } + + while(process.state() == QProcess::Running) + { + loop.exec(); + while(process.canReadLine()) + { + qDebug("WGet: %s", QString::fromLatin1(process.readLine()).simplified().toLatin1().constData()); + } + } + + qDebug("WGet: Exited with code %d", process.exitCode()); + return (process.exitCode() == 0) && output.exists() && output.isFile(); +} + +bool UpdateDialog::checkSignature(const QString &file, const QString &signature) +{ + QProcess process; + process.setProcessChannelMode(QProcess::MergedChannels); + process.setReadChannel(QProcess::StandardOutput); + + QEventLoop loop; + connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit())); + connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit())); + connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit())); + + process.start(m_binaryGnuPG, QStringList() << "--homedir" << lamexp_temp_folder() << "--keyring" << QDir::toNativeSeparators(m_binaryKeys) << QDir::toNativeSeparators(signature) << QDir::toNativeSeparators(file)); + + if(!process.waitForStarted()) + { + return false; + } + + while(process.state() == QProcess::Running) + { + loop.exec(); + while(process.canReadLine()) + { + qDebug("GnuPG: %s", QString::fromLatin1(process.readLine()).simplified().toLatin1().constData()); + } + } + + qDebug("GnuPG: Exited with code %d", process.exitCode()); + return (process.exitCode() == 0); +} + +bool UpdateDialog::parseVersionInfo(const QString &file, UpdateInfo *updateInfo) +{ + + QRegExp value("^(\\w+)=(.+)$"); + QRegExp section("^\\[(.+)\\]$"); + + QFile data(file); + if(!data.open(QIODevice::ReadOnly)) + { + qWarning("Cannot open update info file for reading!"); + return false; + } + + bool inSection = false; + + while(!data.atEnd()) + { + QString line = QString::fromLatin1(data.readLine()).trimmed(); + if(section.indexIn(line) >= 0) + { + qDebug("Section: '%s'", section.cap(1).toLatin1().constData()); + inSection = (section.cap(1).compare(section_id, Qt::CaseInsensitive) == 0); + continue; + } + if(inSection && value.indexIn(line) >= 0) + { + qDebug("Value: '%s' ==> '%s'", value.cap(1).toLatin1().constData(), value.cap(2).toLatin1().constData()); + if(value.cap(1).compare("BuildNo", Qt::CaseInsensitive) == 0) + { + bool ok = false; + unsigned int temp = value.cap(2).toUInt(&ok); + if(ok) updateInfo->m_buildNo = temp; + } + else if(value.cap(1).compare("BuildDate", Qt::CaseInsensitive) == 0) + { + QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate); + if(temp.isValid()) updateInfo->m_buildDate = temp; + } + else if(value.cap(1).compare("DownloadSite", Qt::CaseInsensitive) == 0) + { + updateInfo->m_downloadSite = value.cap(2).trimmed(); + } + else if(value.cap(1).compare("DownloadAddress", Qt::CaseInsensitive) == 0) + { + updateInfo->m_downloadAddress = value.cap(2).trimmed(); + } + else if(value.cap(1).compare("DownloadFilename", Qt::CaseInsensitive) == 0) + { + updateInfo->m_downloadFilename = value.cap(2).trimmed(); + } + else if(value.cap(1).compare("DownloadFilecode", Qt::CaseInsensitive) == 0) + { + updateInfo->m_downloadFilecode = value.cap(2).trimmed(); + } + } + } + + bool complete = true; + + if(!(updateInfo->m_buildNo > 0)) complete = false; + if(!(updateInfo->m_buildDate.year() >= 2010)) complete = false; + if(updateInfo->m_downloadSite.isEmpty()) complete = false; + if(updateInfo->m_downloadAddress.isEmpty()) complete = false; + if(updateInfo->m_downloadFilename.isEmpty()) complete = false; + if(updateInfo->m_downloadFilecode.isEmpty()) complete = false; + + return complete; +} + +void UpdateDialog::linkActivated(const QString &link) +{ + QDesktopServices::openUrl(QUrl(link)); +} + +void UpdateDialog::applyUpdate(void) +{ + installButton->setEnabled(false); + closeButton->setEnabled(false); + retryButton->setEnabled(false); + + if(m_updateInfo) + { + statusLabel->setText("Update is being downloaded, please be patient..."); + QApplication::processEvents(); + + QProcess process; + QStringList args; + QEventLoop loop; + + connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit())); + connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit())); + + args << QString("/Location=%1").arg(m_updateInfo->m_downloadAddress); + args << QString("/Filename=%1").arg(m_updateInfo->m_downloadFilename); + args << QString("/TicketID=%1").arg(m_updateInfo->m_downloadFilecode); + args << QString("/ToFolder=%1").arg(QDir::toNativeSeparators(QApplication::applicationDirPath())); + args << QString("/AppTitle=LameXP (Build #%1)").arg(QString::number(m_updateInfo->m_buildNo)); + + QApplication::setOverrideCursor(Qt::WaitCursor); + process.start(m_binaryUpdater, args); + loop.exec(); + QApplication::restoreOverrideCursor(); + + if(process.exitCode() == 0) + { + statusLabel->setText("Update ready to install. Applicaion will quit..."); + QApplication::quit(); + } + else + { + statusLabel->setText("Update failed. Please try again or download manually!"); + } + } + + installButton->setEnabled(true); closeButton->setEnabled(true); } - diff --git a/src/Dialog_Update.h b/src/Dialog_Update.h index 5958594a..7b200d80 100644 --- a/src/Dialog_Update.h +++ b/src/Dialog_Update.h @@ -25,6 +25,8 @@ #include +class UpdateInfo; + class UpdateDialog : public QDialog, private Ui::UpdateDialog { Q_OBJECT @@ -34,12 +36,25 @@ public: ~UpdateDialog(void); private slots: - void updateCompleted(void); + void updateInit(void); + void checkForUpdates(void); + void linkActivated(const QString &link); + void applyUpdate(void); protected: void showEvent(QShowEvent *event); - + void closeEvent(QCloseEvent *event); private: - bool m_clipboardUsed; + bool tryUpdateMirror(UpdateInfo *updateInfo, const QString &url); + bool getFile(const QString &url, const QString &outFile); + bool checkSignature(const QString &file, const QString &signature); + bool parseVersionInfo(const QString &file, UpdateInfo *updateInfo); + + UpdateInfo *m_updateInfo; + + const QString m_binaryWGet; + const QString m_binaryGnuPG; + const QString m_binaryUpdater; + const QString m_binaryKeys; }; diff --git a/src/Thread_Initialization.cpp b/src/Thread_Initialization.cpp index edfd306c..1d6bfe0f 100644 --- a/src/Thread_Initialization.cpp +++ b/src/Thread_Initialization.cpp @@ -44,6 +44,7 @@ static const struct lamexp_tool_t g_lamexp_tools[] = {"09e5a07555a24b8c9d6af880b81eb8ed75be16fd", "faad.exe"}, {"070bf98f78e572a97e4703ef5720c682567a6a56", "flac.exe"}, {"cf379081035ae6bfb6f7bc22f13bfb7ac6302ac5", "gpgv.exe"}, + {"d837bf6ee4dab557d8b02d46c75a24e58980fffa", "gpgv.gpg"}, {"143fc001a2f6c56fe1b9e6f8a2eb2b53b9e1e504", "lame.exe"}, {"775b260b3f64101beaeb317b74746f9bccdab842", "MAC.exe"}, {"e8719fbfd7b690b3e518489f7aae3915305711c2", "mediainfo_icl11.exe"},