Actually implement the auto-updater.

This commit is contained in:
LoRd_MuldeR 2010-11-28 22:18:07 +01:00
parent 533055e177
commit b59bb2399a
8 changed files with 582 additions and 36 deletions

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>622</width>
<width>642</width>
<height>364</height>
</rect>
</property>
@ -143,27 +143,20 @@
<number>0</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="margin">
<number>18</number>
<property name="leftMargin">
<number>32</number>
</property>
<property name="topMargin">
<number>42</number>
</property>
<property name="rightMargin">
<number>32</number>
</property>
<property name="bottomMargin">
<number>32</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="statusLabel">
<property name="font">
@ -200,6 +193,163 @@
</property>
</spacer>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Latest version available:</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="labelVersionLatest">
<property name="text">
<string>(Unknown)</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Currently installed version:</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="labelVersionInstalled">
<property name="text">
<string>(Unknown)</string>
</property>
</widget>
</item>
<item row="0" column="3">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="3">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>15</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>15</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="infoLabel">
<property name="text">
<string>$(INFORMATION)</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="retryButton">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Retry</string>
</property>
<property name="icon">
<iconset resource="../res/Icons.qrc">
<normaloff>:/icons/arrow_refresh.png</normaloff>:/icons/arrow_refresh.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
@ -331,6 +481,9 @@
<include location="../res/Images.qrc"/>
<include location="../res/Images.qrc"/>
<include location="../res/Images.qrc"/>
<include location="../res/Images.qrc"/>
<include location="../res/Images.qrc"/>
<include location="../res/Images.qrc"/>
</resources>
<connections>
<connection>

View File

@ -9,6 +9,7 @@
<file>icons/application_xp_terminal.png</file>
<file>icons/arrow_down.png</file>
<file>icons/arrow_up.png</file>
<file>icons/arrow_refresh.png</file>
<file>icons/bin.png</file>
<file>icons/bomb.png</file>
<file>icons/calendar.png</file>

View File

@ -5,6 +5,7 @@
<file>tools/faad.exe</file>
<file>tools/flac.exe</file>
<file>tools/gpgv.exe</file>
<file>tools/gpgv.gpg</file>
<file>tools/lame.exe</file>
<file>tools/MAC.exe</file>
<file>tools/mediainfo_icl11.exe</file>

BIN
res/tools/gpgv.gpg Normal file

Binary file not shown.

View File

@ -25,7 +25,7 @@
#define VER_LAMEXP_MAJOR 4
#define VER_LAMEXP_MINOR_HI 0
#define VER_LAMEXP_MINOR_LO 0
#define VER_LAMEXP_BUILD 89
#define VER_LAMEXP_BUILD 95
#define VER_LAMEXP_SUFFIX TechPreview
/*

View File

@ -21,49 +21,424 @@
#include "Dialog_Update.h"
#include "Global.h"
#include "Resource.h"
#include <QClipboard>
#include <QFileDialog>
#include <QTimer>
#include <QProcess>
#include <QUuid>
#include <QDate>
#include <QRegExp>
#include <QDesktopServices>
#include <QUrl>
#include <QCloseEvent>
#include <Windows.h>
///////////////////////////////////////////////////////////////////////////////
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)
{
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:<br><a href=\"%1\">%1</a>").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);
}

View File

@ -25,6 +25,8 @@
#include <QDialog>
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;
};

View File

@ -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"},