1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Merge remote-tracking branch 'tux3/updater'

This commit is contained in:
tux3 2015-12-10 20:52:54 +01:00
commit 685dee62d0
No known key found for this signature in database
GPG Key ID: 7E086DD661263264
20 changed files with 514 additions and 186 deletions

View File

@ -47,8 +47,14 @@ include(translations/i18n.pri)
# Build all the qm files now, to make RCC happy
system($$fromfile(translations/i18n.pri, updateallqm))
GIT_VERSION = $$system(git rev-parse HEAD 2> /dev/null || echo "built without git")
isEmpty(GIT_VERSION) {
GIT_VERSION = $$system(git rev-parse HEAD 2> /dev/null || echo "built without git")
}
DEFINES += GIT_VERSION=\"\\\"$$quote($$GIT_VERSION)\\\"\"
isEmpty(GIT_DESCRIBE) {
GIT_DESCRIBE = $$system(git describe --tags 2> /dev/null || echo "Nightly")
}
DEFINES += GIT_DESCRIBE=\"\\\"$$quote($$GIT_DESCRIBE)\\\"\"
# date works on linux/mac, but it would hangs qmake on windows
# This hack returns 0 on batch (windows), but executes "date +%s" or return 0 if it fails on bash (linux/mac)
TIMESTAMP = $$system($1 2>null||echo 0||a;rm null;date +%s||echo 0) # I'm so sorry

View File

@ -90,6 +90,8 @@ void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QSt
#endif
}
#include <windows.h>
int main(int argc, char *argv[])
{
qInstallMessageHandler(logMessageHandler); // Enable log as early as possible
@ -127,7 +129,7 @@ int main(int argc, char *argv[])
if (logFileFile->open(QIODevice::Append))
{
logFileStream->setDevice(logFileFile.get());
*logFileStream << QDateTime::currentDateTime().toString("\nyyyy-MM-dd HH:mm:ss' file logger starting\n'");
*logFileStream << QDateTime::currentDateTime().toString("\nyyyy-MM-dd HH:mm:ss' qTox file logger starting\n'");
}
else
{
@ -143,6 +145,10 @@ int main(int argc, char *argv[])
qDebug() << "built on: " << __TIME__ << __DATE__ << "(" << TIMESTAMP << ")";
qDebug() << "commit: " << GIT_VERSION << "\n";
long unsigned int bufsize=100;
char buf[100];
GetUserNameA(buf, &bufsize);
#if defined(Q_OS_MACX) && defined(QT_RELEASE)
osx::moveToAppFolder();
#endif

View File

@ -23,6 +23,7 @@
#include "src/persistence/settings.h"
#include "src/widget/widget.h"
#include "src/widget/gui.h"
#include "src/nexus.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QCoreApplication>
@ -31,6 +32,8 @@
#include <QProcess>
#include <QtConcurrent/QtConcurrent>
#include <QMessageBox>
#include <QMutexLocker>
#include <iostream>
#ifdef Q_OS_WIN
#include <windows.h>
@ -44,7 +47,7 @@ const QString AutoUpdater::platform = "win64";
const QString AutoUpdater::platform = "win32";
#endif
const QString AutoUpdater::updaterBin = "qtox-updater.exe";
const QString AutoUpdater::updateServer = "https://qtox-win.pkg.tox.chat";
const QString AutoUpdater::updateServer = "http://45.79.166.124";
unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES] =
{
@ -72,20 +75,20 @@ unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES];
const QString AutoUpdater::checkURI = AutoUpdater::updateServer+"/qtox/"+AutoUpdater::platform+"/version";
const QString AutoUpdater::flistURI = AutoUpdater::updateServer+"/qtox/"+AutoUpdater::platform+"/flist";
const QString AutoUpdater::filesURI = AutoUpdater::updateServer+"/qtox/"+AutoUpdater::platform+"/files/";
bool AutoUpdater::abortFlag{false};
std::atomic_bool AutoUpdater::abortFlag{false};
std::atomic_bool AutoUpdater::isDownloadingUpdate{false};
std::atomic<float> AutoUpdater::progressValue{0};
QString AutoUpdater::progressVersion;
QMutex AutoUpdater::progressVersionMutex;
bool AutoUpdater::isUpdateAvailable()
{
if (isDownloadingUpdate)
return false;
VersionInfo newVersion = getUpdateVersion();
if (newVersion.timestamp <= TIMESTAMP
|| newVersion.versionString.isEmpty() || newVersion.versionString == GIT_VERSION)
return false;
else
return true;
QByteArray updateFlist = getUpdateFlist();
QList<UpdateFileMeta> diff = genUpdateDiff(parseFlist(updateFlist));
return !diff.isEmpty();
}
AutoUpdater::VersionInfo AutoUpdater::getUpdateVersion()
@ -100,7 +103,11 @@ AutoUpdater::VersionInfo AutoUpdater::getUpdateVersion()
QNetworkAccessManager *manager = new QNetworkAccessManager;
QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(checkURI)));
while (!reply->isFinished())
{
if (abortFlag)
return versionInfo;
qApp->processEvents();
}
if (reply->error() != QNetworkReply::NoError)
{
@ -210,7 +217,11 @@ QByteArray AutoUpdater::getUpdateFlist()
QNetworkAccessManager *manager = new QNetworkAccessManager;
QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(flistURI)));
while (!reply->isFinished())
{
if (abortFlag)
return flist;
qApp->processEvents();
}
if (reply->error() != QNetworkReply::NoError)
{
@ -227,42 +238,41 @@ QByteArray AutoUpdater::getUpdateFlist()
return flist;
}
QByteArray AutoUpdater::getLocalFlist()
{
QByteArray flist;
QFile flistFile("flist");
if (!flistFile.open(QIODevice::ReadOnly))
{
qWarning() << "getLocalFlist: Can't open local flist";
return flist;
}
flist = flistFile.readAll();
flistFile.close();
return flist;
}
QList<AutoUpdater::UpdateFileMeta> AutoUpdater::genUpdateDiff(QList<UpdateFileMeta> updateFlist)
{
QList<UpdateFileMeta> diff;
QList<UpdateFileMeta> localFlist = parseFlist(getLocalFlist());
for (UpdateFileMeta file : updateFlist)
if (!localFlist.contains(file))
if (!isUpToDate(file))
diff += file;
return diff;
}
AutoUpdater::UpdateFile AutoUpdater::getUpdateFile(UpdateFileMeta fileMeta)
bool AutoUpdater::isUpToDate(AutoUpdater::UpdateFileMeta fileMeta)
{
QString appDir = qApp->applicationDirPath();
QFile file(appDir+QDir::separator()+fileMeta.installpath);
if (!file.open(QIODevice::ReadOnly))
return false;
// If the data we have is corrupted or old, mark it for update
QByteArray data = file.readAll();
if (crypto_sign_verify_detached(fileMeta.sig, (unsigned char*)data.data(), data.size(), key) != 0)
return false;
return true;
}
AutoUpdater::UpdateFile AutoUpdater::getUpdateFile(UpdateFileMeta fileMeta,
std::function<void(int,int)> progressCallback)
{
UpdateFile file;
file.metadata = fileMeta;
QNetworkAccessManager *manager = new QNetworkAccessManager;
QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(filesURI+fileMeta.id)));
QObject::connect(reply, &QNetworkReply::downloadProgress, progressCallback);
while (!reply->isFinished())
{
if (abortFlag)
@ -301,13 +311,16 @@ bool AutoUpdater::downloadUpdate()
QList<UpdateFileMeta> newFlist = parseFlist(newFlistData);
QList<UpdateFileMeta> diff = genUpdateDiff(newFlist);
// Progress
progressValue = 0;
if (abortFlag)
{
isDownloadingUpdate = false;
return false;
}
qDebug() << "Need to update " << diff.size() << " files";
qDebug() << "Need to update" << diff.size() << "files";
// Create an empty directory to download updates into
QString updateDirStr = Settings::getInstance().getSettingsDirPath() + "/update/";
@ -333,9 +346,17 @@ bool AutoUpdater::downloadUpdate()
newFlistFile.write(newFlistData);
newFlistFile.close();
progressValue = 1;
// Download and write each new file
for (UpdateFileMeta fileMeta : diff)
{
float initialProgress = progressValue, step = 99./diff.size();
auto stepProgressCallback = [&](int current, int total)
{
progressValue = initialProgress + step * (float)current/total;
};
if (abortFlag)
{
isDownloadingUpdate = false;
@ -359,17 +380,13 @@ bool AutoUpdater::downloadUpdate()
QDir().mkpath(fileDirStr);
// Download
UpdateFile file = getUpdateFile(fileMeta);
UpdateFile file = getUpdateFile(fileMeta, stepProgressCallback);
if (abortFlag)
{
isDownloadingUpdate = false;
return false;
}
goto fail;
if (file.data.isNull())
{
qCritical() << "downloadUpdate: Error downloading a file, aborting...";
isDownloadingUpdate = false;
return false;
goto fail;
}
// Check signature
@ -377,25 +394,32 @@ bool AutoUpdater::downloadUpdate()
file.data.size(), key) != 0)
{
qCritical() << "downloadUpdate: RECEIVED FORGED FILE, aborting...";
isDownloadingUpdate = false;
return false;
goto fail;
}
// Save
if (!fileFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
qCritical() << "downloadUpdate: Can't save new update file, aborting...";
isDownloadingUpdate = false;
return false;
goto fail;
}
fileFile.write(file.data);
fileFile.close();
progressValue = initialProgress + step;
}
qDebug() << "downloadUpdate: The update is ready, it'll be installed on the next restart";
isDownloadingUpdate = false;
progressValue = 100;
return true;
fail:
isDownloadingUpdate = false;
progressValue = 0;
setProgressVersion("");
return false;
}
bool AutoUpdater::isLocalUpdateReady()
@ -441,43 +465,36 @@ void AutoUpdater::installLocalUpdate()
{
qDebug() << "About to start the qTox updater to install a local update";
// Delete the update if we fail so we don't fail again.
// Updates only for supported platforms.
if (platform.isEmpty())
// Prepare to delete the update if we fail so we don't fail again.
auto failExit = []()
{
qCritical() << "Failed to start the qTox updater, removing the update and exiting";
QString updateDirStr = Settings::getInstance().getSettingsDirPath() + "/update/";
QDir(updateDirStr).removeRecursively();
exit(-1);
}
};
// Updates only for supported platforms.
if (platform.isEmpty())
failExit();
// Workaround QTBUG-7645
// QProcess fails silently when elevation is required instead of showing a UAC prompt on Win7/Vista
#ifdef Q_OS_WIN
HINSTANCE result = ::ShellExecuteA(0, "open", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL);
HINSTANCE result = ::ShellExecuteW(0, L"open", updaterBin.toStdWString().c_str(), 0, 0, SW_SHOWNORMAL);
if (result == (HINSTANCE)SE_ERR_ACCESSDENIED)
{
// Requesting elevation
result = ::ShellExecuteA(0, "runas", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL);
result = ::ShellExecuteW(0, L"runas", updaterBin.toStdWString().c_str(), 0, 0, SW_SHOWNORMAL);
}
if (result <= (HINSTANCE)32)
{
goto fail;
}
failExit();
#else
if (!QProcess::startDetached(updaterBin))
goto fail;
failExit();
#endif
exit(0);
// Centralized error handling
fail:
qCritical() << "Failed to start the qTox updater, removing the update and exiting";
QString updateDirStr = Settings::getInstance().getSettingsDirPath() + "/update/";
QDir(updateDirStr).removeRecursively();
exit(-1);
}
void AutoUpdater::checkUpdatesAsyncInteractive()
@ -497,16 +514,52 @@ void AutoUpdater::checkUpdatesAsyncInteractiveWorker()
QString updateDirStr = Settings::getInstance().getSettingsDirPath() + "/update/";
QDir updateDir(updateDirStr);
if ((updateDir.exists() && QFile(updateDirStr+"flist").exists())
|| GUI::askQuestion(QObject::tr("Update", "The title of a message box"),
QObject::tr("An update is available, do you want to download it now?\nIt will be installed when qTox restarts."), true, false))
if (updateDir.exists() && QFile(updateDirStr+"flist").exists())
{
setProgressVersion(getUpdateVersion().versionString);
downloadUpdate();
return;
}
VersionInfo newVersion = getUpdateVersion();
QString contentText = QObject::tr("An update is available, do you want to download it now?\n"
"It will be installed when qTox restarts.");
if (!newVersion.versionString.isEmpty())
contentText += "\n\n" + QObject::tr("Version %1, %2").arg(newVersion.versionString,
QDateTime::fromMSecsSinceEpoch(newVersion.timestamp*1000).toString());
if (GUI::askQuestion(QObject::tr("Update", "The title of a message box"),
contentText, true, false))
{
setProgressVersion(newVersion.versionString);
GUI::showUpdateDownloadProgress();
downloadUpdate();
}
}
void AutoUpdater::setProgressVersion(QString version)
{
QMutexLocker lock(&progressVersionMutex);
progressVersion = version;
}
void AutoUpdater::abortUpdates()
{
abortFlag = true;
isDownloadingUpdate = false;
}
QString AutoUpdater::getProgressVersion()
{
QMutexLocker lock(&progressVersionMutex);
return progressVersion;
}
int AutoUpdater::getProgressValue()
{
return progressValue;
}

View File

@ -23,8 +23,10 @@
#include <QString>
#include <QList>
#include <QMutex>
#include <sodium.h>
#include <atomic>
#include <functional>
/// For now we only support auto updates on Windows and OS X, although extending it is not a technical issue.
/// Linux users are expected to use their package managers or update manually through official channels.
@ -94,6 +96,9 @@ public:
/// Aborting will make some functions try to return early
/// Call before qTox exits to avoid the updater running in the background
static void abortUpdates();
/// Functions giving info on the progress of update downloads
static QString getProgressVersion();
static int getProgressValue();
protected:
/// Parses and validates a flist file. Returns an empty list on error
@ -101,17 +106,19 @@ protected:
/// Gets the update server's flist. Returns an empty array on error
/// Will try to follow qTox's proxy settings, may block and processEvents
static QByteArray getUpdateFlist();
/// Gets the local flist. Returns an empty array on error
static QByteArray getLocalFlist();
/// Generates a list of files we need to update
static QList<UpdateFileMeta> genUpdateDiff(QList<UpdateFileMeta> updateFlist);
/// Checks if we have an up to date version of this file locally installed
static bool isUpToDate(UpdateFileMeta file);
/// Tries to fetch the file from the update server. Returns a file with a null QByteArray on error.
/// Note that a file with an empty but non-null QByteArray is not an error, merely a file of size 0.
/// Will try to follow qTox's proxy settings, may block and processEvents
static UpdateFile getUpdateFile(UpdateFileMeta fileMeta);
static UpdateFile getUpdateFile(UpdateFileMeta fileMeta, std::function<void(int,int)> progressCallback);
/// Does the actual work for checkUpdatesAsyncInteractive
/// Blocking, but otherwise has the same properties than checkUpdatesAsyncInteractive
static void checkUpdatesAsyncInteractiveWorker();
/// Thread safe setter
static void setProgressVersion(QString version);
private:
AutoUpdater() = delete;
@ -125,8 +132,11 @@ private:
static const QString filesURI; ///< URI of the actual files of the latest version
static const QString updaterBin; ///< Path to the qtox-updater binary
static unsigned char key[];
static bool abortFlag; ///< If true, try to abort everything.
static std::atomic_bool abortFlag; ///< If true, try to abort everything.
static std::atomic_bool isDownloadingUpdate; ///< We'll pretend there's no new update available if we're already updating
static std::atomic<float> progressValue;
static QString progressVersion;
static QMutex progressVersionMutex; ///< No, we can't just make the QString atomic
};
#endif // AUTOUPDATE_H

View File

@ -22,6 +22,9 @@
#include "aboutform.h"
#include "src/widget/translator.h"
#include "tox/tox.h"
#include "src/net/autoupdate.h"
#include <QTimer>
#include <QDebug>
AboutForm::AboutForm() :
GenericForm(QPixmap(":/img/settings/general.png"))
@ -33,6 +36,12 @@ AboutForm::AboutForm() :
if (QString(GIT_VERSION).indexOf(" ") > -1)
bodyUI->gitVersion->setOpenExternalLinks(false);
showUpdateProgress();
progressTimer = new QTimer();
progressTimer->setInterval(500);
progressTimer->setSingleShot(false);
connect(progressTimer, &QTimer::timeout, this, &AboutForm::showUpdateProgress);
Translator::registerHandler(std::bind(&AboutForm::retranslateUi, this), this);
}
@ -40,6 +49,7 @@ AboutForm::AboutForm() :
//nightly builds from stable releases.
void AboutForm::replaceVersions()
{
bodyUI->youareusing->setText(bodyUI->youareusing->text().replace("$GIT_DESCRIBE", QString(GIT_DESCRIBE)));
bodyUI->gitVersion->setText(bodyUI->gitVersion->text().replace("$GIT_VERSION", QString(GIT_VERSION)));
bodyUI->toxCoreVersion->setText(
bodyUI->toxCoreVersion->text().replace("$TOXCOREVERSION",
@ -52,11 +62,46 @@ void AboutForm::replaceVersions()
AboutForm::~AboutForm()
{
Translator::unregister(this);
delete progressTimer;
delete bodyUI;
}
void AboutForm::showUpdateProgress()
{
QString version = AutoUpdater::getProgressVersion();
int value = AutoUpdater::getProgressValue();
if (version.isEmpty())
{
bodyUI->updateProgress->setVisible(value != 0);
bodyUI->updateText->setVisible(value != 0);
}
else
{
if (value == 100)
bodyUI->updateText->setText(tr("Restart qTox to install version %1").arg(version));
else
bodyUI->updateText->setText(tr("qTox is downloading update %1", "%1 is the version of the update").arg(version));
bodyUI->updateProgress->setValue(value);
bodyUI->updateProgress->setVisible(value != 0 && value != 100);
bodyUI->updateText->setVisible(value != 0);
}
}
void AboutForm::hideEvent(QHideEvent *)
{
progressTimer->stop();
}
void AboutForm::showEvent(QShowEvent *)
{
progressTimer->start();
}
void AboutForm::retranslateUi()
{
bodyUI->retranslateUi(this);
replaceVersions();
showUpdateProgress();
}

View File

@ -23,6 +23,7 @@
#include "genericsettings.h"
class Core;
class QTimer;
namespace Ui {
class AboutSettings;
@ -39,6 +40,9 @@ public:
protected:
private slots:
void showUpdateProgress();
virtual void hideEvent(QHideEvent*) final override;
virtual void showEvent(QShowEvent*) final override;
private:
void retranslateUi();
@ -46,6 +50,7 @@ private:
private:
Ui::AboutSettings* bodyUI;
QTimer* progressTimer;
};
#endif // ABOUTFORM_H

View File

@ -30,8 +30,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>496</width>
<height>565</height>
<width>493</width>
<height>534</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1,0,0">
@ -128,7 +128,7 @@
<enum>QFrame::NoFrame</enum>
</property>
<property name="text">
<string>You are using a qTox nightly build.</string>
<string>You are using qTox version $GIT_DESCRIBE.</string>
</property>
<property name="indent">
<number>-1</number>
@ -141,6 +141,32 @@
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QProgressBar" name="updateProgress">
<property name="value">
<number>0</number>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="textVisible">
<bool>true</bool>
</property>
<property name="format">
<string>Downloading update: %p%</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="updateText">
<property name="text">
<string>Update text</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -183,12 +209,12 @@
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Oxygen-Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif'; color:#000000;&quot;&gt;Copyright © 2014-2015 by The qTox Project&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;qTox is a Qt-based graphical interface for Tox.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif';&quot;&gt;qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif';&quot;&gt;qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;You should have received a copy of the GNU General Public License along with this program. If not, see &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/copyleft/gpl.html&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; text-decoration: underline; color:#007af4;&quot;&gt;https://www.gnu.org/copyleft/gpl.html&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif'; font-size:10pt; color:#000000;&quot;&gt;Copyright © 2014-2015 by The qTox Project&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:10pt;&quot;&gt;qTox is a Qt-based graphical interface for Tox.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif'; font-size:10pt;&quot;&gt;qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif'; font-size:10pt;&quot;&gt;qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:10pt;&quot;&gt;You should have received a copy of the GNU General Public License along with this program. If not, see &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/copyleft/gpl.html&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:10pt; text-decoration: underline; color:#007af4;&quot;&gt;https://www.gnu.org/copyleft/gpl.html&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:10pt;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="searchPaths">
<stringlist/>

View File

@ -101,6 +101,18 @@ void GUI::reloadTheme()
}
}
void GUI::showUpdateDownloadProgress()
{
if (QThread::currentThread() == qApp->thread())
{
getInstance()._showUpdateDownloadProgress();
}
else
{
QMetaObject::invokeMethod(&getInstance(), "_showUpdateDownloadProgress", Qt::BlockingQueuedConnection);
}
}
void GUI::showInfo(const QString& title, const QString& msg)
{
if (QThread::currentThread() == qApp->thread())
@ -267,6 +279,13 @@ void GUI::_showError(const QString& title, const QString& msg)
QMessageBox::critical(getMainWidget(), title, msg);
}
void GUI::_showUpdateDownloadProgress()
{
#ifndef Q_OS_ANDROID
Nexus::getDesktopGUI()->showUpdateDownloadProgress();
#endif
}
bool GUI::_askQuestion(const QString& title, const QString& msg,
bool defaultAns, bool warning,
bool yesno)

View File

@ -45,6 +45,8 @@ public:
static void setWindowTitle(const QString& title);
/// Reloads the application theme and redraw the window
static void reloadTheme();
/// Optionally switches to a view of the qTox update being downloaded
static void showUpdateDownloadProgress();
/// Show some text to the user, for example in a message box
static void showInfo(const QString& title, const QString& msg);
/// Show a warning to the user, for example in a message box
@ -93,6 +95,7 @@ private slots:
void _showInfo(const QString& title, const QString& msg);
void _showWarning(const QString& title, const QString& msg);
void _showError(const QString& title, const QString& msg);
void _showUpdateDownloadProgress();
bool _askQuestion(const QString& title, const QString& msg,
bool defaultAns = false, bool warning = true,
bool yesno = true);

View File

@ -484,6 +484,12 @@ Widget* Widget::getInstance()
return instance;
}
void Widget::showUpdateDownloadProgress()
{
settingsWidget->showAbout();
onSettingsClicked();
}
void Widget::moveEvent(QMoveEvent *event)
{
if (event->type() == QEvent::Move)

View File

@ -65,6 +65,7 @@ public:
QString getUsername();
Camera* getCamera();
static Widget* getInstance();
void showUpdateDownloadProgress(); ///< Switches to the About settings page
void addFriendDialog(Friend* frnd, ContentDialog* dialog);
void addGroupDialog(Group* group, ContentDialog* dialog);
bool newFriendMessageAlert(int friendId, bool sound=true);

View File

@ -20,10 +20,83 @@
#include "widget.h"
#include <QApplication>
#include <QDebug>
#include <QFile>
#include <QMutex>
#include <QDateTime>
#include <QDir>
#include <QStandardPaths>
#include <memory>
#include <windows.h>
static std::unique_ptr<QTextStream> logFileStream {nullptr};
static std::unique_ptr<QFile> logFileFile {nullptr};
static QMutex mutex;
void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QString& msg)
{
// Silence qWarning spam due to bug in QTextBrowser (trying to open a file for base64 images)
if (ctxt.function == QString("virtual bool QFSFileEngine::open(QIODevice::OpenMode)")
&& msg == QString("QFSFileEngine::open: No file name specified"))
return;
QString LogMsg = QString("[%1] %2:%3 : ")
.arg(QTime::currentTime().toString("HH:mm:ss.zzz")).arg(ctxt.file).arg(ctxt.line);
switch (type)
{
case QtDebugMsg:
LogMsg += "Debug";
break;
case QtWarningMsg:
LogMsg += "Warning";
break;
case QtCriticalMsg:
LogMsg += "Critical";
break;
case QtFatalMsg:
LogMsg += "Fatal";
break;
default:
break;
}
LogMsg += ": " + msg + "\n";
QTextStream out(stderr, QIODevice::WriteOnly);
out << LogMsg;
if (!logFileStream)
return;
QMutexLocker locker(&mutex);
*logFileStream << LogMsg;
logFileStream->flush();
}
int main(int argc, char *argv[])
{
qInstallMessageHandler(logMessageHandler);
QApplication a(argc, argv);
logFileStream.reset(new QTextStream);
logFileFile.reset(new QFile(QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator()
+ "AppData" + QDir::separator() + "Roaming" + QDir::separator() + "tox")+QDir::separator()+"qtox.log"));
if (logFileFile->open(QIODevice::Append))
{
logFileStream->setDevice(logFileFile.get());
*logFileStream << QDateTime::currentDateTime().toString("\nyyyy-MM-dd HH:mm:ss' Updater file logger starting\n'");
}
else
{
qWarning() << "Couldn't open log file!\n";
logFileStream.release();
}
long unsigned int bufsize=100;
char buf[100];
GetUserNameA(buf, &bufsize);
qDebug() << "Updater running as user" << buf;
Widget w;
w.show();

View File

@ -1,55 +0,0 @@
/*
Copyright © 2014 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>
*/
#include "settingsDir.h"
#include <QFile>
#include <QSettings>
#include <QDir>
#include <QStandardPaths>
const QString FILENAME = "settings.ini";
QString getSettingsDirPath()
{
if (isToxPortableEnabled())
return ".";
#ifdef Q_OS_WIN
return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)
+ QDir::separator() + "AppData" + QDir::separator() + "Roaming" + QDir::separator() + "tox");
#else
return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QDir::separator() + "tox");
#endif
}
bool isToxPortableEnabled()
{
QFile portableSettings(FILENAME);
if (portableSettings.exists())
{
QSettings ps(FILENAME, QSettings::IniFormat);
ps.beginGroup("General");
return ps.value("makeToxPortable", false).toBool();
}
else
{
return false;
}
}

View File

@ -1,30 +0,0 @@
/*
Copyright © 2014 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef SETTINGSDIR_H
#define SETTINGSDIR_H
#include <QString>
QString getSettingsDirPath();
bool isToxPortableEnabled();
#endif // SETTINGSDIR_H

View File

@ -20,13 +20,17 @@
#include "update.h"
#include "serialize.h"
#include "widget.h"
#include <QFile>
#include <QDebug>
#include <QMessageBox>
#include <QDir>
#include <QCoreApplication>
unsigned char key[crypto_sign_PUBLICKEYBYTES] =
{
0xa5, 0x80, 0xf3, 0xb7, 0xd0, 0x10, 0xc0, 0xf9, 0xd6, 0xcf, 0x48, 0x15, 0x99, 0x70, 0x92, 0x49,
0xf6, 0xe8, 0xe5, 0xe2, 0x6c, 0x73, 0x8c, 0x48, 0x25, 0xed, 0x01, 0x72, 0xf7, 0x6c, 0x17, 0x28
0x20, 0x89, 0x39, 0xaa, 0x9a, 0xe8, 0xb5, 0x21, 0x0e, 0xac, 0x02, 0xa9, 0xc4, 0x92, 0xd9, 0xa2,
0x17, 0x83, 0xbd, 0x78, 0x0a, 0xda, 0x33, 0xcd, 0xa5, 0xc6, 0x44, 0xc7, 0xfc, 0xed, 0x00, 0x13
};
QByteArray getLocalFlist()
@ -46,14 +50,35 @@ QByteArray getLocalFlist()
return flist;
}
QList<UpdateFileMeta> genUpdateDiff(QList<UpdateFileMeta> updateFlist)
bool isUpToDate(UpdateFileMeta fileMeta)
{
QString appDir = qApp->applicationDirPath();
QFile file(appDir+QDir::separator()+fileMeta.installpath);
if (!file.open(QIODevice::ReadOnly))
return false;
// If the data we have is corrupted or old, mark it for update
QByteArray data = file.readAll();
if (crypto_sign_verify_detached(fileMeta.sig, (unsigned char*)data.data(), data.size(), key) != 0)
return false;
return true;
}
QList<UpdateFileMeta> genUpdateDiff(QList<UpdateFileMeta> updateFlist, Widget* w)
{
QList<UpdateFileMeta> diff;
QList<UpdateFileMeta> localFlist = parseFlist(getLocalFlist());
float progressDiff = 45;
float progress = 5;
for (UpdateFileMeta file : updateFlist)
if (!localFlist.contains(file))
{
if (!isUpToDate(file))
diff += file;
progress += progressDiff / updateFlist.size();
w->setProgress(progress);
}
return diff;
}

View File

@ -25,6 +25,8 @@
#include <QString>
#include <sodium.h>
class Widget;
struct UpdateFileMeta
{
unsigned char sig[crypto_sign_BYTES]; ///< Signature of the file (ed25519)
@ -51,7 +53,7 @@ QByteArray getLocalFlist();
/// Parses and validates a flist file. Returns an empty list on error
QList<UpdateFileMeta> parseFlist(QByteArray flistData);
/// Generates a list of files we need to update
QList<UpdateFileMeta> genUpdateDiff(QList<UpdateFileMeta> updateFlist);
QList<UpdateFileMeta> genUpdateDiff(QList<UpdateFileMeta> updateFlist, Widget *w);
extern unsigned char key[crypto_sign_PUBLICKEYBYTES];

View File

@ -13,14 +13,14 @@ TEMPLATE = app
CONFIG += c++11
QMAKE_CXXFLAGS += -fno-exceptions
SOURCES += main.cpp\
widget.cpp \
settingsDir.cpp \
update.cpp \
serialize.cpp
HEADERS += widget.h \
settingsDir.h \
update.h \
serialize.h
@ -34,3 +34,7 @@ INCLUDEPATH += libs/include
RC_FILE = windows/updater.rc
LIBS += -L$$PWD/libs/lib/ -lsodium
win32 {
LIBS += -lshell32 -luuid
}

View File

@ -26,17 +26,28 @@
#include <QProcess>
#include <QMessageBox>
#include <QMetaObject>
#include <QDebug>
#include <QSettings>
#include "settingsDir.h"
#include "update.h"
#ifdef Q_OS_WIN
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#define _WIN32_WINNT 0x0600 // Vista for SHGetKnownFolderPath
#include <windows.h>
#include <shldisp.h>
#include <shlobj.h>
#include <exdisp.h>
const bool supported = true;
const QString QTOX_PATH = "qtox.exe";
#else
const bool supported = false;
const QString QTOX_PATH;
#endif
const QString SETTINGS_FILE = "settings.ini";
Widget::Widget(QWidget *parent) :
QWidget(parent),
@ -48,11 +59,66 @@ Widget::Widget(QWidget *parent) :
if (!supported)
fatalError(tr("The qTox updater is not supported on this platform."));
#ifdef Q_OS_WIN
// Get a primary unelevated token of the actual user
hPrimaryToken = nullptr;
HANDLE hShellProcess = nullptr, hShellProcessToken = nullptr;
const DWORD dwTokenRights = TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_ASSIGN_PRIMARY
| TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID;
DWORD dwPID = 0;
HWND hwnd = nullptr;
DWORD dwLastErr = 0;
// Enable SeIncreaseQuotaPrivilege
HANDLE hProcessToken = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hProcessToken))
goto unelevateFail;
TOKEN_PRIVILEGES tkp;
tkp.PrivilegeCount = 1;
LookupPrivilegeValueW(NULL, SE_INCREASE_QUOTA_NAME, &tkp.Privileges[0].Luid);
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hProcessToken, FALSE, &tkp, 0, NULL, NULL);
dwLastErr = GetLastError();
CloseHandle(hProcessToken);
if (ERROR_SUCCESS != dwLastErr)
goto unelevateFail;
// Get a primary copy of the desktop shell's token,
// we're assuming the shell is running as the actual user
hwnd = GetShellWindow();
if (!hwnd)
goto unelevateFail;
GetWindowThreadProcessId(hwnd, &dwPID);
if (!dwPID)
goto unelevateFail;
hShellProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPID);
if (!hShellProcess)
goto unelevateFail;
if (!OpenProcessToken(hShellProcess, TOKEN_DUPLICATE, &hShellProcessToken))
goto unelevateFail;
// Duplicate the shell's process token to get a primary token.
// Based on experimentation, this is the minimal set of rights required for CreateProcessWithTokenW (contrary to current documentation).
if (!DuplicateTokenEx(hShellProcessToken, dwTokenRights, NULL, SecurityImpersonation, TokenPrimary, &hPrimaryToken))
goto unelevateFail;
qDebug() << "Unelevated primary access token acquired";
goto unelevateCleanup;
unelevateFail:
qWarning() << "Unelevate failed, couldn't get access token";
unelevateCleanup:
CloseHandle(hShellProcessToken);
CloseHandle(hShellProcess);
#endif
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
}
Widget::~Widget()
{
#ifdef Q_OS_WIN
CloseHandle(hPrimaryToken);
#endif
delete ui;
}
@ -65,6 +131,7 @@ void Widget::setProgress(int value)
void Widget::fatalError(QString message)
{
qCritical() << "Update aborted with error:"<<message;
QMessageBox::critical(this,tr("Error"), message+'\n'+tr("qTox will restart now."));
deleteUpdate();
restoreBackups();
@ -79,7 +146,25 @@ void Widget::deleteUpdate()
void Widget::startQToxAndExit()
{
#ifdef Q_OS_WIN
// Try to restart qTox as the actual user with our unelevated token
STARTUPINFOW si;
PROCESS_INFORMATION pi;
SecureZeroMemory(&si, sizeof(si));
SecureZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
if (!CreateProcessWithTokenW(hPrimaryToken, 0, QTOX_PATH.toStdWString().c_str(), 0, 0, 0, 0, &si, &pi))
{
qWarning() << "Failed to start unelevated qTox";
QProcess::startDetached(QTOX_PATH);
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
#else
QProcess::startDetached(QTOX_PATH);
#endif
exit(0);
}
@ -95,6 +180,37 @@ void Widget::restoreBackups()
QFile(file+".bak").rename(file);
}
QString Widget::getSettingsDirPath()
{
if (isToxPortableEnabled())
return ".";
#ifdef Q_OS_WIN
wchar_t* path;
SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, hPrimaryToken, &path);
QString pathStr = QString::fromStdWString(path);
pathStr.replace("\\", "/");
return pathStr + "/tox";
#else
return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QDir::separator() + "tox");
#endif
}
bool Widget::isToxPortableEnabled()
{
QFile portableSettings(SETTINGS_FILE);
if (portableSettings.exists())
{
QSettings ps(SETTINGS_FILE, QSettings::IniFormat);
ps.beginGroup("General");
return ps.value("makeToxPortable", false).toBool();
}
else
{
return false;
}
}
void Widget::update()
{
/// 1. Find and parse the update (0-5%)
@ -104,6 +220,8 @@ void Widget::update()
if (!updateDir.exists())
fatalError(tr("No update found."));
setProgress(2);
// Check that we have a flist and that every file on the diff exists
QFile updateFlistFile(updateDirStr+"flist");
if (!updateFlistFile.open(QIODevice::ReadOnly))
@ -112,24 +230,23 @@ void Widget::update()
QByteArray updateFlistData = updateFlistFile.readAll();
updateFlistFile.close();
setProgress(1);
QList<UpdateFileMeta> updateFlist = parseFlist(updateFlistData);
setProgress(2);
QList<UpdateFileMeta> diff = genUpdateDiff(updateFlist);
setProgress(4);
setProgress(5);
/// 2. Generate a diff (5-50%)
QList<UpdateFileMeta> diff = genUpdateDiff(updateFlist, this);
for (UpdateFileMeta fileMeta : diff)
if (!QFile::exists(updateDirStr+fileMeta.installpath))
fatalError(tr("The update is incomplete."));
if (diff.size() == 0)
fatalError(tr("The diff list is empty."));
fatalError(tr("The update is empty!"));
setProgress(50);
qDebug() << "Diff generated,"<<diff.size()<<"files to update";
setProgress(5);
/// 2. Check the update (5-50%)
float checkProgressStep = 45.0/(float)diff.size();
float checkProgress = 5;
/// 2. Check the update (50-75%)
float checkProgressStep = 25.0/(float)diff.size();
float checkProgress = 50;
for (UpdateFileMeta fileMeta : diff)
{
UpdateFile file;
@ -152,11 +269,12 @@ void Widget::update()
checkProgress += checkProgressStep;
setProgress(checkProgress);
}
setProgress(50);
setProgress(75);
qDebug() << "Update files signature verified, installing";
/// 3. Install the update (50-95%)
float installProgressStep = 45.0/(float)diff.size();
float installProgress = 50;
/// 3. Install the update (75-95%)
float installProgressStep = 20.0/(float)diff.size();
float installProgress = 75;
for (UpdateFileMeta fileMeta : diff)
{
// Backup old files
@ -184,5 +302,6 @@ void Widget::update()
setProgress(100);
/// 5. Start qTox and exit
qDebug() << "Update applied, restarting qTox!";
startQToxAndExit();
}

View File

@ -23,6 +23,10 @@
#include <QWidget>
#ifdef Q_OS_WIN
#include <windows.h>
#endif
namespace Ui {
class Widget;
}
@ -39,6 +43,8 @@ public:
void deleteBackups();
void restoreBackups();
void setProgress(int value);
QString getSettingsDirPath();
bool isToxPortableEnabled();
// Noreturn
void fatalError(QString message); ///< Calls deleteUpdate and startQToxAndExit
@ -52,6 +58,10 @@ public slots:
private:
Ui::Widget *ui;
QStringList backups;
#ifdef Q_OS_WIN
HANDLE hPrimaryToken;
#endif
};
#endif // WIDGET_H

View File

@ -84,7 +84,7 @@
</rect>
</property>
<property name="text">
<string>&lt;a href=&quot;https://tox.im&quot;&gt;https://tox.im&lt;/a&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://tox.im&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;https://tox.chat&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>