Improve autoupdate code, move it from Application.

This commit is contained in:
John Preston 2018-04-26 20:14:21 +04:00
parent 65f968ec1b
commit 993cb987a6
18 changed files with 745 additions and 556 deletions

View File

@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "observer_peer.h" #include "observer_peer.h"
#include "auth_session.h" #include "auth_session.h"
#include "core/crash_reports.h" #include "core/crash_reports.h"
#include "core/update_checker.h"
#include "storage/storage_facade.h" #include "storage/storage_facade.h"
#include "storage/storage_shared_media.h" #include "storage/storage_shared_media.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
@ -1667,7 +1668,7 @@ namespace {
void restart() { void restart() {
#ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifndef TDESKTOP_DISABLE_AUTOUPDATE
bool updateReady = (Sandbox::updatingState() == Application::UpdatingReady); bool updateReady = (Core::UpdateChecker().state() == Core::UpdateChecker::State::Ready);
#else // !TDESKTOP_DISABLE_AUTOUPDATE #else // !TDESKTOP_DISABLE_AUTOUPDATE
bool updateReady = false; bool updateReady = false;
#endif // else for !TDESKTOP_DISABLE_AUTOUPDATE #endif // else for !TDESKTOP_DISABLE_AUTOUPDATE

View File

@ -11,11 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwidget.h" #include "mainwidget.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "autoupdater.h"
#include "window/notifications_manager.h" #include "window/notifications_manager.h"
#include "core/crash_reports.h" #include "core/crash_reports.h"
#include "messenger.h" #include "messenger.h"
#include "base/timer.h" #include "base/timer.h"
#include "core/update_checker.h"
#include "core/crash_report_window.h" #include "core/crash_report_window.h"
namespace { namespace {
@ -65,7 +65,11 @@ Application::Application(
int &argc, int &argc,
char **argv) char **argv)
: QApplication(argc, argv) : QApplication(argc, argv)
, _launcher(launcher) { , _launcher(launcher)
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
, _updateChecker(std::make_unique<Core::UpdateChecker>())
#endif // TDESKTOP_DISABLE_AUTOUPDATE
{
const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath()); const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath());
char h[33] = { 0 }; char h[33] = { 0 };
hashMd5Hex(d.constData(), d.size(), h); hashMd5Hex(d.constData(), d.size(), h);
@ -86,13 +90,6 @@ Application::Application(
QTimer::singleShot(0, this, SLOT(startApplication())); QTimer::singleShot(0, this, SLOT(startApplication()));
connect(this, SIGNAL(aboutToQuit()), this, SLOT(closeApplication())); connect(this, SIGNAL(aboutToQuit()), this, SLOT(closeApplication()));
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
_updateCheckTimer.create(this);
connect(_updateCheckTimer, SIGNAL(timeout()), this, SLOT(updateCheck()));
connect(this, SIGNAL(updateFailed()), this, SLOT(onUpdateFailed()));
connect(this, SIGNAL(updateReady()), this, SLOT(onUpdateReady()));
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
if (cManyInstance()) { if (cManyInstance()) {
LOG(("Many instance allowed, starting...")); LOG(("Many instance allowed, starting..."));
singleInstanceChecked(); singleInstanceChecked();
@ -180,7 +177,7 @@ void Application::socketError(QLocalSocket::LocalSocketError e) {
#endif // !Q_OS_WINRT #endif // !Q_OS_WINRT
#ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifndef TDESKTOP_DISABLE_AUTOUPDATE
if (!cNoStartUpdate() && checkReadyUpdate()) { if (!cNoStartUpdate() && Core::checkReadyUpdate()) {
cSetRestartingUpdate(true); cSetRestartingUpdate(true);
DEBUG_LOG(("Application Info: installing update instead of starting app...")); DEBUG_LOG(("Application Info: installing update instead of starting app..."));
return App::quit(); return App::quit();
@ -356,168 +353,10 @@ void Application::closeApplication() {
_localSocket.close(); _localSocket.close();
#ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifndef TDESKTOP_DISABLE_AUTOUPDATE
delete _updateReply; _updateChecker = nullptr;
_updateReply = 0;
if (_updateChecker) _updateChecker->deleteLater();
_updateChecker = 0;
if (_updateThread) {
_updateThread->quit();
}
_updateThread = 0;
#endif // !TDESKTOP_DISABLE_AUTOUPDATE #endif // !TDESKTOP_DISABLE_AUTOUPDATE
} }
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
void Application::updateCheck() {
startUpdateCheck(false);
}
void Application::updateGotCurrent() {
if (!_updateReply || _updateThread) return;
cSetLastUpdateCheck(unixtime());
QRegularExpressionMatch m = QRegularExpression(qsl("^\\s*(\\d+)\\s*:\\s*([\\x21-\\x7f]+)\\s*$")).match(QString::fromLatin1(_updateReply->readAll()));
if (m.hasMatch()) {
uint64 currentVersion = m.captured(1).toULongLong();
QString url = m.captured(2);
bool betaVersion = false;
if (url.startsWith(qstr("beta_"))) {
betaVersion = true;
url = url.mid(5) + '_' + countBetaVersionSignature(currentVersion);
}
if ((!betaVersion || cBetaVersion()) && currentVersion > (betaVersion ? cBetaVersion() : uint64(AppVersion))) {
_updateThread = new QThread();
connect(_updateThread, SIGNAL(finished()), _updateThread, SLOT(deleteLater()));
_updateChecker = new UpdateChecker(_updateThread, url);
_updateThread->start();
}
}
if (_updateReply) _updateReply->deleteLater();
_updateReply = 0;
if (!_updateThread) {
QDir updates(cWorkingDir() + "tupdates");
if (updates.exists()) {
QFileInfoList list = updates.entryInfoList(QDir::Files);
for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) {
if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) {
QFile(i->absoluteFilePath()).remove();
}
}
}
emit updateLatest();
}
startUpdateCheck(true);
Local::writeSettings();
}
void Application::updateFailedCurrent(QNetworkReply::NetworkError e) {
LOG(("App Error: could not get current version (update check): %1").arg(e));
if (_updateReply) _updateReply->deleteLater();
_updateReply = 0;
emit updateFailed();
startUpdateCheck(true);
}
void Application::onUpdateReady() {
if (_updateChecker) {
_updateChecker->deleteLater();
_updateChecker = nullptr;
}
_updateCheckTimer->stop();
cSetLastUpdateCheck(unixtime());
Local::writeSettings();
}
void Application::onUpdateFailed() {
if (_updateChecker) {
_updateChecker->deleteLater();
_updateChecker = 0;
if (_updateThread) _updateThread->quit();
_updateThread = 0;
}
cSetLastUpdateCheck(unixtime());
Local::writeSettings();
}
Application::UpdatingState Application::updatingState() {
if (!_updateThread) return Application::UpdatingNone;
if (!_updateChecker) return Application::UpdatingReady;
return Application::UpdatingDownload;
}
int32 Application::updatingSize() {
if (!_updateChecker) return 0;
return _updateChecker->size();
}
int32 Application::updatingReady() {
if (!_updateChecker) return 0;
return _updateChecker->ready();
}
void Application::stopUpdate() {
if (_updateReply) {
_updateReply->abort();
_updateReply->deleteLater();
_updateReply = 0;
}
if (_updateChecker) {
_updateChecker->deleteLater();
_updateChecker = 0;
if (_updateThread) _updateThread->quit();
_updateThread = 0;
}
}
void Application::startUpdateCheck(bool forceWait) {
if (!Sandbox::started()) return;
_updateCheckTimer->stop();
if (_updateThread || _updateReply || !cAutoUpdate() || cExeName().isEmpty()) return;
int32 constDelay = cBetaVersion() ? 600 : UpdateDelayConstPart, randDelay = cBetaVersion() ? 300 : UpdateDelayRandPart;
int32 updateInSecs = cLastUpdateCheck() + constDelay + int32(rand() % randDelay) - unixtime();
bool sendRequest = (updateInSecs <= 0 || updateInSecs > (constDelay + randDelay));
if (!sendRequest && !forceWait) {
QDir updates(cWorkingDir() + "tupdates");
if (updates.exists()) {
QFileInfoList list = updates.entryInfoList(QDir::Files);
for (QFileInfoList::iterator i = list.begin(), e = list.end(); i != e; ++i) {
if (QRegularExpression("^(tupdate|tmacupd|tmac32upd|tlinuxupd|tlinux32upd)\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption).match(i->fileName()).hasMatch()) {
sendRequest = true;
}
}
}
}
if (cManyInstance() && !cDebug()) return; // only main instance is updating
if (sendRequest) {
QUrl url(cUpdateURL());
if (cBetaVersion()) {
url.setQuery(qsl("version=%1&beta=%2").arg(AppVersion).arg(cBetaVersion()));
} else if (cAlphaVersion()) {
url.setQuery(qsl("version=%1&alpha=1").arg(AppVersion));
} else {
url.setQuery(qsl("version=%1").arg(AppVersion));
}
QString u = url.toString();
QNetworkRequest checkVersion(url);
if (_updateReply) _updateReply->deleteLater();
_updateReply = _updateManager.get(checkVersion);
connect(_updateReply, SIGNAL(finished()), this, SLOT(updateGotCurrent()));
connect(_updateReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(updateFailedCurrent(QNetworkReply::NetworkError)));
emit updateChecking();
} else {
_updateCheckTimer->start((updateInSecs + 5) * 1000);
}
}
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
inline Application *application() { inline Application *application() {
return qobject_cast<Application*>(QApplication::instance()); return qobject_cast<Application*>(QApplication::instance());
} }
@ -569,73 +408,6 @@ void adjustSingleTimers() {
base::Timer::Adjust(); base::Timer::Adjust();
} }
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
void startUpdateCheck() {
if (auto a = application()) {
return a->startUpdateCheck(false);
}
}
void stopUpdate() {
if (auto a = application()) {
return a->stopUpdate();
}
}
Application::UpdatingState updatingState() {
if (auto a = application()) {
return a->updatingState();
}
return Application::UpdatingNone;
}
int32 updatingSize() {
if (auto a = application()) {
return a->updatingSize();
}
return 0;
}
int32 updatingReady() {
if (auto a = application()) {
return a->updatingReady();
}
return 0;
}
void updateChecking() {
if (auto a = application()) {
emit a->updateChecking();
}
}
void updateLatest() {
if (auto a = application()) {
emit a->updateLatest();
}
}
void updateProgress(qint64 ready, qint64 total) {
if (auto a = application()) {
emit a->updateProgress(ready, total);
}
}
void updateFailed() {
if (auto a = application()) {
emit a->updateFailed();
}
}
void updateReady() {
if (auto a = application()) {
emit a->updateReady();
}
}
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
void connect(const char *signal, QObject *object, const char *method) { void connect(const char *signal, QObject *object, const char *method) {
if (auto a = application()) { if (auto a = application()) {
a->connect(a, signal, object, method); a->connect(a, signal, object, method);

View File

@ -7,10 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
class UpdateChecker;
namespace Core { namespace Core {
class Launcher; class Launcher;
class UpdateChecker;
} // namespace Core } // namespace Core
class Application : public QApplication { class Application : public QApplication {
@ -59,46 +58,11 @@ private:
void singleInstanceChecked(); void singleInstanceChecked();
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
// Autoupdating
public:
void startUpdateCheck(bool forceWait);
void stopUpdate();
enum UpdatingState {
UpdatingNone,
UpdatingDownload,
UpdatingReady,
};
UpdatingState updatingState();
int32 updatingSize();
int32 updatingReady();
signals:
void updateChecking();
void updateLatest();
void updateProgress(qint64 ready, qint64 total);
void updateReady();
void updateFailed();
public slots:
void updateCheck();
void updateGotCurrent();
void updateFailedCurrent(QNetworkReply::NetworkError e);
void onUpdateReady();
void onUpdateFailed();
private: private:
object_ptr<SingleTimer> _updateCheckTimer = { nullptr }; #ifndef TDESKTOP_DISABLE_AUTOUPDATE
QNetworkReply *_updateReply = nullptr; std::unique_ptr<Core::UpdateChecker> _updateChecker;
QNetworkAccessManager _updateManager;
QThread *_updateThread = nullptr;
UpdateChecker *_updateChecker = nullptr;
#endif // !TDESKTOP_DISABLE_AUTOUPDATE #endif // !TDESKTOP_DISABLE_AUTOUPDATE
}; };
namespace Sandbox { namespace Sandbox {
@ -112,23 +76,6 @@ void execExternal(const QString &cmd);
void adjustSingleTimers(); void adjustSingleTimers();
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
void startUpdateCheck();
void stopUpdate();
Application::UpdatingState updatingState();
int32 updatingSize();
int32 updatingReady();
void updateChecking();
void updateLatest();
void updateProgress(qint64 ready, qint64 total);
void updateFailed();
void updateReady();
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
void refreshGlobalProxy(); void refreshGlobalProxy();
void connect(const char *signal, QObject *object, const char *method); void connect(const char *signal, QObject *object, const char *method);

View File

@ -1,63 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
#include <QtNetwork/QLocalSocket>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QNetworkReply>
class UpdateChecker : public QObject {
Q_OBJECT
public:
UpdateChecker(QThread *thread, const QString &url);
void unpackUpdate();
int32 ready();
int32 size();
static void clearAll();
~UpdateChecker();
public slots:
void start();
void partMetaGot();
void partFinished(qint64 got, qint64 total);
void partFailed(QNetworkReply::NetworkError e);
void sendRequest();
private:
void initOutput();
void fatalFail();
QString updateUrl;
QNetworkAccessManager manager;
QNetworkReply *reply;
int32 already, full;
QFile outputFile;
QMutex mutex;
};
bool checkReadyUpdate();
#else // TDESKTOP_DISABLE_AUTOUPDATE
class UpdateChecker : public QObject {
Q_OBJECT
};
#endif // TDESKTOP_DISABLE_AUTOUPDATE
QString countBetaVersionSignature(uint64 version);

View File

@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "autoupdater.h"
#include "boxes/confirm_box.h" #include "boxes/confirm_box.h"
#include "application.h" #include "application.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
@ -18,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "platform/platform_file_utilities.h" #include "platform/platform_file_utilities.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "core/update_checker.h"
AboutBox::AboutBox(QWidget *parent) AboutBox::AboutBox(QWidget *parent)
: _version(this, lng_about_version(lt_version, QString::fromLatin1(AppVersionStr.c_str()) + (cAlphaVersion() ? " alpha" : "") + (cBetaVersion() ? qsl(" beta %1").arg(cBetaVersion()) : QString())), st::aboutVersionLink) : _version(this, lng_about_version(lt_version, QString::fromLatin1(AppVersionStr.c_str()) + (cAlphaVersion() ? " alpha" : "") + (cBetaVersion() ? qsl(" beta %1").arg(cBetaVersion()) : QString())), st::aboutVersionLink)
@ -68,7 +68,7 @@ void AboutBox::showVersionHistory() {
case dbipLinux32: url += qsl("linux32/%1.tar.xz"); break; case dbipLinux32: url += qsl("linux32/%1.tar.xz"); break;
case dbipLinux64: url += qsl("linux/%1.tar.xz"); break; case dbipLinux64: url += qsl("linux/%1.tar.xz"); break;
} }
url = url.arg(qsl("tbeta%1_%2").arg(cRealBetaVersion()).arg(countBetaVersionSignature(cRealBetaVersion()))); url = url.arg(qsl("tbeta%1_%2").arg(cRealBetaVersion()).arg(Core::countBetaVersionSignature(cRealBetaVersion())));
Application::clipboard()->setText(url); Application::clipboard()->setText(url);

View File

@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/platform_specific.h" #include "platform/platform_specific.h"
#include "application.h" #include "application.h"
#include "base/zlib_help.h" #include "base/zlib_help.h"
#include "autoupdater.h" #include "core/update_checker.h"
PreLaunchWindow *PreLaunchWindowInstance = nullptr; PreLaunchWindow *PreLaunchWindowInstance = nullptr;
@ -299,23 +299,36 @@ LastCrashedWindow::LastCrashedWindow()
_updatingSkip.setText(qsl("SKIP")); _updatingSkip.setText(qsl("SKIP"));
connect(&_updatingSkip, SIGNAL(clicked()), this, SLOT(onUpdateSkip())); connect(&_updatingSkip, SIGNAL(clicked()), this, SLOT(onUpdateSkip()));
Sandbox::connect(SIGNAL(updateChecking()), this, SLOT(onUpdateChecking())); Core::UpdateChecker checker;
Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onUpdateLatest())); using Progress = Core::UpdateChecker::Progress;
Sandbox::connect(SIGNAL(updateProgress(qint64,qint64)), this, SLOT(onUpdateDownloading(qint64,qint64))); checker.checking(
Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onUpdateFailed())); ) | rpl::start_with_next([=] { onUpdateChecking(); }, _lifetime);
Sandbox::connect(SIGNAL(updateReady()), this, SLOT(onUpdateReady())); checker.isLatest(
) | rpl::start_with_next([=] { onUpdateLatest(); }, _lifetime);
checker.progress(
) | rpl::start_with_next([=](const Progress &result) {
onUpdateDownloading(result.already, result.size);
}, _lifetime);
checker.failed(
) | rpl::start_with_next([=] { onUpdateFailed(); }, _lifetime);
checker.ready(
) | rpl::start_with_next([=] { onUpdateReady(); }, _lifetime);
switch (Sandbox::updatingState()) { switch (checker.state()) {
case Application::UpdatingDownload: case Core::UpdateChecker::State::Download:
setUpdatingState(UpdatingDownload, true); setUpdatingState(UpdatingDownload, true);
setDownloadProgress(Sandbox::updatingReady(), Sandbox::updatingSize()); setDownloadProgress(checker.already(), checker.size());
break; break;
case Application::UpdatingReady: setUpdatingState(UpdatingReady, true); break; case Core::UpdateChecker::State::Ready:
default: setUpdatingState(UpdatingCheck, true); break; setUpdatingState(UpdatingReady, true);
break;
default:
setUpdatingState(UpdatingCheck, true);
break;
} }
cSetLastUpdateCheck(0); cSetLastUpdateCheck(0);
Sandbox::startUpdateCheck(); checker.start();
#else // !TDESKTOP_DISABLE_AUTOUPDATE #else // !TDESKTOP_DISABLE_AUTOUPDATE
_updating.setText(qsl("Please check if there is a new version available.")); _updating.setText(qsl("Please check if there is a new version available."));
if (_sendingState != SendingNoReport) { if (_sendingState != SendingNoReport) {
@ -790,9 +803,10 @@ void LastCrashedWindow::onNetworkSettingsSaved(
#ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifndef TDESKTOP_DISABLE_AUTOUPDATE
if ((_updatingState == UpdatingFail && (_sendingState == SendingNoReport || _sendingState == SendingUpdateCheck)) || (_updatingState == UpdatingCheck)) { if ((_updatingState == UpdatingFail && (_sendingState == SendingNoReport || _sendingState == SendingUpdateCheck)) || (_updatingState == UpdatingCheck)) {
Sandbox::stopUpdate(); Core::UpdateChecker checker;
checker.stop();
cSetLastUpdateCheck(0); cSetLastUpdateCheck(0);
Sandbox::startUpdateCheck(); checker.start();
} else } else
#endif // !TDESKTOP_DISABLE_AUTOUPDATE #endif // !TDESKTOP_DISABLE_AUTOUPDATE
if (_sendingState == SendingFail || _sendingState == SendingProgress) { if (_sendingState == SendingFail || _sendingState == SendingProgress) {
@ -815,7 +829,7 @@ void LastCrashedWindow::setUpdatingState(UpdatingState state, bool force) {
} }
break; break;
case UpdatingReady: case UpdatingReady:
if (checkReadyUpdate()) { if (Core::checkReadyUpdate()) {
cSetRestartingUpdate(true); cSetRestartingUpdate(true);
App::quit(); App::quit();
return; return;
@ -849,7 +863,8 @@ void LastCrashedWindow::setDownloadProgress(qint64 ready, qint64 total) {
void LastCrashedWindow::onUpdateRetry() { void LastCrashedWindow::onUpdateRetry() {
cSetLastUpdateCheck(0); cSetLastUpdateCheck(0);
Sandbox::startUpdateCheck(); Core::UpdateChecker checker;
checker.start();
} }
void LastCrashedWindow::onUpdateSkip() { void LastCrashedWindow::onUpdateSkip() {
@ -857,7 +872,8 @@ void LastCrashedWindow::onUpdateSkip() {
onContinue(); onContinue();
} else { } else {
if (_updatingState == UpdatingCheck || _updatingState == UpdatingDownload) { if (_updatingState == UpdatingCheck || _updatingState == UpdatingDownload) {
Sandbox::stopUpdate(); Core::UpdateChecker checker;
checker.stop();
setUpdatingState(UpdatingFail); setUpdatingState(UpdatingFail);
} }
_sendingState = SendingNone; _sendingState = SendingNone;

View File

@ -114,6 +114,11 @@ private:
QString minidumpFileName(); QString minidumpFileName();
void updateControls(); void updateControls();
void excludeReportUsername();
QString getReportField(const QLatin1String &name, const QLatin1String &prefix);
void addReportFieldPart(const QLatin1String &name, const QLatin1String &prefix, QHttpMultiPart *multipart);
QString _host, _username, _password; QString _host, _username, _password;
quint32 _port; quint32 _port;
@ -128,8 +133,6 @@ private:
bool _reportShown, _reportSaved; bool _reportShown, _reportSaved;
void excludeReportUsername();
enum SendingState { enum SendingState {
SendingNoReport, SendingNoReport,
SendingUpdateCheck, SendingUpdateCheck,
@ -167,8 +170,7 @@ private:
void setDownloadProgress(qint64 ready, qint64 total); void setDownloadProgress(qint64 ready, qint64 total);
#endif // !TDESKTOP_DISABLE_AUTOUPDATE #endif // !TDESKTOP_DISABLE_AUTOUPDATE
QString getReportField(const QLatin1String &name, const QLatin1String &prefix); rpl::lifetime _lifetime;
void addReportFieldPart(const QLatin1String &name, const QLatin1String &prefix, QHttpMultiPart *multipart);
}; };

View File

@ -5,8 +5,12 @@ the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link: For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "core/update_checker.h"
#include "autoupdater.h" #include "application.h"
#include "platform/platform_specific.h"
#include "base/timer.h"
#include "storage/localstorage.h"
#include <openssl/rsa.h> #include <openssl/rsa.h>
#include <openssl/pem.h> #include <openssl/pem.h>
@ -19,131 +23,554 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <lzma.h> #include <lzma.h>
#endif // else of Q_OS_WIN #endif // else of Q_OS_WIN
#include "application.h" namespace Core {
#include "platform/platform_specific.h"
#ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifndef TDESKTOP_DISABLE_AUTOUPDATE
namespace {
constexpr auto kCheckTimeout = TimeMs(10000);
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
typedef DWORD VerInt; using VersionInt = DWORD;
typedef WCHAR VerChar; using VersionChar = WCHAR;
#else // Q_OS_WIN #else // Q_OS_WIN
typedef int VerInt; using VersionInt = int;
typedef wchar_t VerChar; using VersionChar = wchar_t;
#endif // Q_OS_WIN #endif // Q_OS_WIN
UpdateChecker::UpdateChecker(QThread *thread, const QString &url) : reply(0), already(0), full(0) { using ErrorSignal = void(QNetworkReply::*)(QNetworkReply::NetworkError);
updateUrl = url; const auto QNetworkReply_error = ErrorSignal(&QNetworkReply::error);
moveToThread(thread);
manager.moveToThread(thread);
connect(thread, SIGNAL(started()), this, SLOT(start())); std::weak_ptr<Updater> UpdaterInstance;
std::shared_ptr<Updater> GetUpdaterInstance() {
if (const auto result = UpdaterInstance.lock()) {
return result;
}
const auto result = std::make_shared<Updater>();
UpdaterInstance = result;
return result;
}
void ClearAll() {
psDeleteDir(cWorkingDir() + qsl("tupdates"));
}
QString FindUpdateFile() {
QDir updates(cWorkingDir() + "tupdates");
if (!updates.exists()) {
return QString();
}
const auto list = updates.entryInfoList(QDir::Files);
for (const auto &info : list) {
if (QRegularExpression(
"^("
"tupdate|"
"tmacupd|"
"tmac32upd|"
"tlinuxupd|"
"tlinux32upd"
")\\d+(_[a-z\\d]+)?$",
QRegularExpression::CaseInsensitiveOption
).match(info.fileName()).hasMatch()) {
return info.absoluteFilePath();
}
}
return QString();
}
} // namespace
class Updater : public base::has_weak_ptr {
public:
using Progress = UpdateChecker::Progress;
using State = UpdateChecker::State;
Updater();
rpl::producer<> checking() const;
rpl::producer<> isLatest() const;
rpl::producer<Progress> progress() const;
rpl::producer<> failed() const;
rpl::producer<> ready() const;
void start(bool forceWait);
void stop();
State state() const;
int already() const;
int size() const;
// Thread-safe methods.
void onProgress(Progress progress);
void onFailed();
void onReady();
~Updater();
private:
class Private;
void check();
void handleFailed();
void handleReady();
void gotResponse();
void gotFailure(QNetworkReply::NetworkError e);
void clearSentRequest();
void requestTimeout();
bool _isReady = false;
base::Timer _timer;
base::Timer _retryTimer;
rpl::event_stream<> _checking;
rpl::event_stream<> _isLatest;
rpl::event_stream<Progress> _progress;
rpl::event_stream<> _failed;
rpl::event_stream<> _ready;
std::unique_ptr<QNetworkAccessManager> _manager;
QNetworkReply *_reply = nullptr;
std::unique_ptr<QThread> _thread;
Private *_private = nullptr;
rpl::lifetime _lifetime;
};
class Updater::Private : public QObject {
public:
Private(
not_null<Updater*> parent,
not_null<QThread*> thread,
const QString &url);
void unpackUpdate();
// Thread-safe methods.
int already() const;
int size() const;
private:
void start();
void sendRequest();
void initOutput();
void gotMetaData();
void partFinished(qint64 got, qint64 total);
void partFailed(QNetworkReply::NetworkError e);
void fatalFail();
not_null<Updater*> _parent;
QString _url;
QNetworkAccessManager _manager;
std::unique_ptr<QNetworkReply> _reply;
int _already = 0;
int _size = 0;
QFile _output;
mutable QMutex _mutex;
};
Updater::Updater()
: _timer([=] { check(); })
, _retryTimer([=] { requestTimeout(); }) {
failed() | rpl::start_with_next([=] {
handleFailed();
}, _lifetime);
ready() | rpl::start_with_next([=] {
handleReady();
}, _lifetime);
}
rpl::producer<> Updater::checking() const {
return _checking.events();
}
rpl::producer<> Updater::isLatest() const {
return _isLatest.events();
}
auto Updater::progress() const
-> rpl::producer<Progress> {
return _progress.events();
}
rpl::producer<> Updater::failed() const {
return _failed.events();
}
rpl::producer<> Updater::ready() const {
return _ready.events();
}
void Updater::onProgress(Progress progress) {
crl::on_main(this, [=] {
_progress.fire_copy(progress);
});
}
void Updater::onFailed() {
crl::on_main(this, [=] {
_failed.fire({});
});
}
void Updater::onReady() {
crl::on_main(this, [=] {
_ready.fire({});
});
}
void Updater::check() {
start(false);
}
void Updater::gotResponse() {
if (!_reply || _thread) {
return;
}
cSetLastUpdateCheck(unixtime());
const auto m = QRegularExpression(qsl("^\\s*(\\d+)\\s*:\\s*([\\x21-\\x7f]+)\\s*$")).match(QString::fromLatin1(_reply->readAll()));
if (m.hasMatch()) {
uint64 currentVersion = m.captured(1).toULongLong();
QString url = m.captured(2);
bool betaVersion = false;
if (url.startsWith(qstr("beta_"))) {
betaVersion = true;
url = url.mid(5) + '_' + Core::countBetaVersionSignature(currentVersion);
}
if ((!betaVersion || cBetaVersion()) && currentVersion > (betaVersion ? cBetaVersion() : uint64(AppVersion))) {
_thread = std::make_unique<QThread>();
_private = new Private(this, _thread.get(), url);
_thread->start();
}
}
clearSentRequest();
if (!_thread) {
if (const auto update = FindUpdateFile(); !update.isEmpty()) {
QFile(update).remove();
}
_isLatest.fire({});
}
start(true);
Local::writeSettings();
}
void Updater::gotFailure(QNetworkReply::NetworkError e) {
LOG(("App Error: could not get current version (update check): %1").arg(e));
if (const auto reply = base::take(_reply)) {
reply->deleteLater();
}
_failed.fire({});
start(true);
}
void Updater::handleReady() {
_isReady = true;
stop();
cSetLastUpdateCheck(unixtime());
Local::writeSettings();
}
void Updater::handleFailed() {
stop();
cSetLastUpdateCheck(unixtime());
Local::writeSettings();
}
auto Updater::state() const -> State {
if (_isReady) {
return State::Ready;
} else if (!_thread) {
return State::None;
}
return State::Download;
}
int Updater::size() const {
return _private ? _private->size() : 0;
}
int Updater::already() const {
return _private ? _private->already() : 0;
}
void Updater::clearSentRequest() {
const auto reply = base::take(_reply);
if (!reply) {
return;
}
reply->disconnect(reply, &QNetworkReply::finished, nullptr, nullptr);
reply->disconnect(reply, QNetworkReply_error, nullptr, nullptr);
reply->abort();
reply->deleteLater();
_manager = nullptr;
}
void Updater::stop() {
clearSentRequest();
if (const auto checker = base::take(_private)) {
InvokeQueued(checker, [=] { checker->deleteLater(); });
if (const auto thread = base::take(_thread)) {
thread->quit();
thread->wait();
}
}
}
void Updater::start(bool forceWait) {
if (!Sandbox::started() || _isReady) {
return;
}
_timer.cancel();
if (_thread || _reply || !cAutoUpdate() || cExeName().isEmpty()) {
return;
}
_retryTimer.cancel();
const auto constDelay = cBetaVersion() ? 600 : UpdateDelayConstPart;
const auto randDelay = cBetaVersion() ? 300 : UpdateDelayRandPart;
const auto updateInSecs = cLastUpdateCheck()
+ constDelay
+ int(rand() % randDelay)
- unixtime();
auto sendRequest = (updateInSecs <= 0)
|| (updateInSecs > constDelay + randDelay);
if (!sendRequest && !forceWait) {
if (!FindUpdateFile().isEmpty()) {
sendRequest = true;
}
}
if (cManyInstance() && !cDebug()) {
// Only main instance is updating.
return;
}
if (sendRequest) {
clearSentRequest();
auto url = QUrl(cUpdateURL());
if (cBetaVersion()) {
url.setQuery(qsl("version=%1&beta=%2"
).arg(AppVersion
).arg(cBetaVersion()));
} else if (cAlphaVersion()) {
url.setQuery(qsl("version=%1&alpha=1").arg(AppVersion));
} else {
url.setQuery(qsl("version=%1").arg(AppVersion));
}
DEBUG_LOG(("App Info: requesting update state from '%1'"
).arg(url.toDisplayString()));
const auto request = QNetworkRequest(url);
_manager = std::make_unique<QNetworkAccessManager>();
_reply = _manager->get(request);
_reply->connect(_reply, &QNetworkReply::finished, [=] {
gotResponse();
});
_reply->connect(_reply, QNetworkReply_error, [=](auto e) {
gotFailure(e);
});
_checking.fire({});
_retryTimer.callOnce(kCheckTimeout);
} else {
_timer.callOnce((updateInSecs + 5) * TimeMs(1000));
}
}
void Updater::requestTimeout() {
if (_reply) {
stop();
_failed.fire({});
cSetLastUpdateCheck(0);
_timer.callOnce(kCheckTimeout);
}
}
Updater::~Updater() {
stop();
}
UpdateChecker::UpdateChecker()
: _updater(GetUpdaterInstance()) {
}
rpl::producer<> UpdateChecker::checking() const {
return _updater->checking();
}
rpl::producer<> UpdateChecker::isLatest() const {
return _updater->isLatest();
}
auto UpdateChecker::progress() const
-> rpl::producer<Progress> {
return _updater->progress();
}
rpl::producer<> UpdateChecker::failed() const {
return _updater->failed();
}
rpl::producer<> UpdateChecker::ready() const {
return _updater->ready();
}
void UpdateChecker::start(bool forceWait) {
_updater->start(forceWait);
}
void UpdateChecker::stop() {
_updater->stop();
}
auto UpdateChecker::state() const
-> State {
return _updater->state();
}
int UpdateChecker::already() const {
return _updater->already();
}
int UpdateChecker::size() const {
return _updater->size();
}
Updater::Private::Private(
not_null<Updater*> parent,
not_null<QThread*> thread,
const QString &url)
: _parent(parent) {
_url = url;
moveToThread(thread);
_manager.moveToThread(thread);
connect(thread, &QThread::started, this, [=] { start(); });
initOutput(); initOutput();
} }
void UpdateChecker::initOutput() { void Updater::Private::initOutput() {
QString fileName; auto fileName = QString();
QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl); const auto nameRegExp = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)"));
if (m.hasMatch()) { const auto nameMatch = nameRegExp.match(_url);
fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString());
}
if (fileName.isEmpty()) {
fileName = qsl("tupdate-%1").arg(rand_value<uint32>() % 1000000);
}
QString dirStr = cWorkingDir() + qsl("tupdates/");
fileName = dirStr + fileName;
QFileInfo file(fileName);
QDir dir(dirStr); if (nameMatch.hasMatch()) {
fileName = nameMatch.captured(1).replace(
QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")),
QString());
} else if (fileName.isEmpty()) {
fileName = qsl("tupdate-%1").arg(rand() % 1000000);
}
const auto folder = cWorkingDir() + qsl("tupdates/");
const auto finalName = folder + fileName;
QFileInfo info(finalName);
QDir dir(folder);
if (dir.exists()) { if (dir.exists()) {
QFileInfoList all = dir.entryInfoList(QDir::Files); const auto all = dir.entryInfoList(QDir::Files);
for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) { for (auto i = all.begin(), e = all.end(); i != e; ++i) {
if (i->absoluteFilePath() != file.absoluteFilePath()) { if (i->absoluteFilePath() != info.absoluteFilePath()) {
QFile::remove(i->absoluteFilePath()); QFile::remove(i->absoluteFilePath());
} }
} }
} else { } else {
dir.mkdir(dir.absolutePath()); dir.mkdir(dir.absolutePath());
} }
outputFile.setFileName(fileName); _output.setFileName(finalName);
if (file.exists()) { if (info.exists()) {
uint64 fullSize = file.size(); uint64 fullSize = info.size();
if (fullSize < INT_MAX) { if (fullSize < INT_MAX) {
int32 goodSize = (int32)fullSize; int32 goodSize = (int32)fullSize;
if (goodSize % UpdateChunk) { if (goodSize % UpdateChunk) {
goodSize = goodSize - (goodSize % UpdateChunk); goodSize = goodSize - (goodSize % UpdateChunk);
if (goodSize) { if (goodSize) {
if (outputFile.open(QIODevice::ReadOnly)) { if (_output.open(QIODevice::ReadOnly)) {
QByteArray goodData = outputFile.readAll().mid(0, goodSize); QByteArray goodData = _output.readAll().mid(0, goodSize);
outputFile.close(); _output.close();
if (outputFile.open(QIODevice::WriteOnly)) { if (_output.open(QIODevice::WriteOnly)) {
outputFile.write(goodData); _output.write(goodData);
outputFile.close(); _output.close();
QMutexLocker lock(&mutex); QMutexLocker lock(&_mutex);
already = goodSize; _already = goodSize;
} }
} }
} }
} else { } else {
QMutexLocker lock(&mutex); QMutexLocker lock(&_mutex);
already = goodSize; _already = goodSize;
} }
} }
if (!already) { if (!_already) {
QFile::remove(fileName); QFile::remove(finalName);
} }
} }
} }
void UpdateChecker::start() { void Updater::Private::start() {
sendRequest(); sendRequest();
} }
void UpdateChecker::sendRequest() { void Updater::Private::sendRequest() {
QNetworkRequest req(updateUrl); auto request = QNetworkRequest(_url);
QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-"; const auto rangeHeaderValue = "bytes="
req.setRawHeader("Range", rangeHeaderValue); + QByteArray::number(_already)
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); + "-";
if (reply) reply->deleteLater(); request.setRawHeader("Range", rangeHeaderValue);
reply = manager.get(req); request.setAttribute(
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64))); QNetworkRequest::HttpPipeliningAllowedAttribute,
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError))); true);
connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot())); _reply.reset(_manager.get(request));
connect(
_reply.get(),
&QNetworkReply::downloadProgress,
this,
&Private::partFinished);
connect(_reply.get(), QNetworkReply_error, [=](auto error) {
partFailed(error);
});
connect(_reply.get(), &QNetworkReply::metaDataChanged, [=] {
gotMetaData();
});
} }
void UpdateChecker::partMetaGot() { void Updater::Private::gotMetaData() {
typedef QList<QNetworkReply::RawHeaderPair> Pairs; const auto pairs = _reply->rawHeaderPairs();
Pairs pairs = reply->rawHeaderPairs(); for (const auto pair : pairs) {
for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) { if (QString::fromUtf8(pair.first).toLower() == "content-range") {
if (QString::fromUtf8(i->first).toLower() == "content-range") { const auto m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(pair.second));
QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second));
if (m.hasMatch()) { if (m.hasMatch()) {
{ {
QMutexLocker lock(&mutex); QMutexLocker lock(&_mutex);
full = m.captured(1).toInt(); _size = m.captured(1).toInt();
} }
_parent->onProgress({ _already, _size });
Sandbox::updateProgress(already, full);
} }
} }
} }
} }
int32 UpdateChecker::ready() { int Updater::Private::already() const {
QMutexLocker lock(&mutex); QMutexLocker lock(&_mutex);
return already; return _already;
} }
int32 UpdateChecker::size() { int Updater::Private::size() const {
QMutexLocker lock(&mutex); QMutexLocker lock(&_mutex);
return full; return _size;
} }
void UpdateChecker::partFinished(qint64 got, qint64 total) { void Updater::Private::partFinished(qint64 got, qint64 total) {
if (!reply) return; if (!_reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); QVariant statusCode = _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (statusCode.isValid()) { if (statusCode.isValid()) {
int status = statusCode.toInt(); int status = statusCode.toInt();
if (status != 200 && status != 206 && status != 416) { if (status != 200 && status != 206 && status != 416) {
@ -152,60 +579,55 @@ void UpdateChecker::partFinished(qint64 got, qint64 total) {
} }
} }
if (!already && !full) { if (!_already && !_size) {
QMutexLocker lock(&mutex); QMutexLocker lock(&_mutex);
full = total; _size = total;
} }
DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total)); DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total));
if (!outputFile.isOpen()) { if (!_output.isOpen()) {
if (!outputFile.open(QIODevice::Append)) { if (!_output.open(QIODevice::Append)) {
LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName())); LOG(("Update Error: Could not open output file '%1' for appending").arg(_output.fileName()));
return fatalFail(); return fatalFail();
} }
} }
QByteArray r = reply->readAll(); QByteArray r = _reply->readAll();
if (!r.isEmpty()) { if (!r.isEmpty()) {
outputFile.write(r); _output.write(r);
QMutexLocker lock(&mutex); QMutexLocker lock(&_mutex);
already += r.size(); _already += r.size();
} }
if (got >= total) { if (got >= total) {
reply->deleteLater(); _reply.release()->deleteLater();
reply = 0; _output.close();
outputFile.close();
unpackUpdate(); unpackUpdate();
} else { } else {
Sandbox::updateProgress(already, full); _parent->onProgress({ _already, _size });
} }
} }
void UpdateChecker::partFailed(QNetworkReply::NetworkError e) { void Updater::Private::partFailed(QNetworkReply::NetworkError e) {
if (!reply) return; if (!_reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); const auto statusCode = _reply->attribute(
reply->deleteLater(); QNetworkRequest::HttpStatusCodeAttribute);
reply = 0; _reply.release()->deleteLater();
if (statusCode.isValid()) { if (statusCode.isValid()) {
int status = statusCode.toInt(); int status = statusCode.toInt();
if (status == 416) { // Requested range not satisfiable if (status == 416) { // Requested range not satisfiable
outputFile.close(); _output.close();
unpackUpdate(); unpackUpdate();
return; return;
} }
} }
LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e)); LOG(("Update Error: failed to download part starting from %1, error %2").arg(_already).arg(e));
Sandbox::updateFailed(); _parent->onFailed();
} }
void UpdateChecker::fatalFail() { void Updater::Private::fatalFail() {
clearAll(); ClearAll();
Sandbox::updateFailed(); _parent->onFailed();
}
void UpdateChecker::clearAll() {
psDeleteDir(cWorkingDir() + qsl("tupdates"));
} }
//QString winapiErrorWrap() { //QString winapiErrorWrap() {
@ -223,9 +645,9 @@ void UpdateChecker::clearAll() {
// return QString::fromWCharArray(errMsg); // return QString::fromWCharArray(errMsg);
//} //}
void UpdateChecker::unpackUpdate() { void Updater::Private::unpackUpdate() {
QByteArray packed; QByteArray packed;
if (!outputFile.open(QIODevice::ReadOnly)) { if (!_output.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read updates file!")); LOG(("Update Error: cant read updates file!"));
return fatalFail(); return fatalFail();
} }
@ -236,13 +658,13 @@ void UpdateChecker::unpackUpdate() {
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header
#endif // Q_OS_WIN #endif // Q_OS_WIN
QByteArray compressed = outputFile.readAll(); QByteArray compressed = _output.readAll();
int32 compressedLen = compressed.size() - hSize; int32 compressedLen = compressed.size() - hSize;
if (compressedLen <= 0) { if (compressedLen <= 0) {
LOG(("Update Error: bad compressed size: %1").arg(compressed.size())); LOG(("Update Error: bad compressed size: %1").arg(compressed.size()));
return fatalFail(); return fatalFail();
} }
outputFile.close(); _output.close();
QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"); QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready");
psDeleteDir(tempDirPath); psDeleteDir(tempDirPath);
@ -429,8 +851,9 @@ void UpdateChecker::unpackUpdate() {
tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath()); tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath());
std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString(); std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString();
VerInt versionNum = VerInt(version), versionLen = VerInt(versionString.size() * sizeof(VerChar)); const auto versionNum = VersionInt(version);
VerChar versionStr[32]; const auto versionLen = VersionInt(versionString.size() * sizeof(VersionChar));
VersionChar versionStr[32];
memcpy(versionStr, versionString.c_str(), versionLen); memcpy(versionStr, versionString.c_str(), versionLen);
QFile fVersion(tempDirPath + qsl("/tdata/version")); QFile fVersion(tempDirPath + qsl("/tdata/version"));
@ -438,11 +861,11 @@ void UpdateChecker::unpackUpdate() {
LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version"))); LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version")));
return fatalFail(); return fatalFail();
} }
fVersion.write((const char*)&versionNum, sizeof(VerInt)); fVersion.write((const char*)&versionNum, sizeof(VersionInt));
if (versionNum == 0x7FFFFFFF) { // beta version if (versionNum == 0x7FFFFFFF) { // beta version
fVersion.write((const char*)&betaVersion, sizeof(quint64)); fVersion.write((const char*)&betaVersion, sizeof(quint64));
} else { } else {
fVersion.write((const char*)&versionLen, sizeof(VerInt)); fVersion.write((const char*)&versionLen, sizeof(VersionInt));
fVersion.write((const char*)&versionStr[0], versionLen); fVersion.write((const char*)&versionStr[0], versionLen);
} }
fVersion.close(); fVersion.close();
@ -460,21 +883,16 @@ void UpdateChecker::unpackUpdate() {
LOG(("Update Error: cant create ready file '%1'").arg(readyFilePath)); LOG(("Update Error: cant create ready file '%1'").arg(readyFilePath));
return fatalFail(); return fatalFail();
} }
outputFile.remove(); _output.remove();
Sandbox::updateReady(); _parent->onReady();
}
UpdateChecker::~UpdateChecker() {
delete reply;
reply = 0;
} }
bool checkReadyUpdate() { bool checkReadyUpdate() {
QString readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"), readyPath = cWorkingDir() + qsl("tupdates/temp"); QString readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"), readyPath = cWorkingDir() + qsl("tupdates/temp");
if (!QFile(readyFilePath).exists() || cExeName().isEmpty()) { if (!QFile(readyFilePath).exists() || cExeName().isEmpty()) {
if (QDir(cWorkingDir() + qsl("tupdates/ready")).exists() || QDir(cWorkingDir() + qsl("tupdates/temp")).exists()) { if (QDir(cWorkingDir() + qsl("tupdates/ready")).exists() || QDir(cWorkingDir() + qsl("tupdates/temp")).exists()) {
UpdateChecker::clearAll(); ClearAll();
} }
return false; return false;
} }
@ -485,30 +903,30 @@ bool checkReadyUpdate() {
QFile fVersion(versionPath); QFile fVersion(versionPath);
if (!fVersion.open(QIODevice::ReadOnly)) { if (!fVersion.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read version file '%1'").arg(versionPath)); LOG(("Update Error: cant read version file '%1'").arg(versionPath));
UpdateChecker::clearAll(); ClearAll();
return false; return false;
} }
VerInt versionNum; auto versionNum = VersionInt();
if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) { if (fVersion.read((char*)&versionNum, sizeof(VersionInt)) != sizeof(VersionInt)) {
LOG(("Update Error: cant read version from file '%1'").arg(versionPath)); LOG(("Update Error: cant read version from file '%1'").arg(versionPath));
UpdateChecker::clearAll(); ClearAll();
return false; return false;
} }
if (versionNum == 0x7FFFFFFF) { // beta version if (versionNum == 0x7FFFFFFF) { // beta version
quint64 betaVersion = 0; quint64 betaVersion = 0;
if (fVersion.read((char*)&betaVersion, sizeof(quint64)) != sizeof(quint64)) { if (fVersion.read((char*)&betaVersion, sizeof(quint64)) != sizeof(quint64)) {
LOG(("Update Error: cant read beta version from file '%1'").arg(versionPath)); LOG(("Update Error: cant read beta version from file '%1'").arg(versionPath));
UpdateChecker::clearAll(); ClearAll();
return false; return false;
} }
if (!cBetaVersion() || betaVersion <= cBetaVersion()) { if (!cBetaVersion() || betaVersion <= cBetaVersion()) {
LOG(("Update Error: cant install beta version %1 having beta version %2").arg(betaVersion).arg(cBetaVersion())); LOG(("Update Error: cant install beta version %1 having beta version %2").arg(betaVersion).arg(cBetaVersion()));
UpdateChecker::clearAll(); ClearAll();
return false; return false;
} }
} else if (versionNum <= AppVersion) { } else if (versionNum <= AppVersion) {
LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion)); LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion));
UpdateChecker::clearAll(); ClearAll();
return false; return false;
} }
fVersion.close(); fVersion.close();
@ -527,11 +945,11 @@ bool checkReadyUpdate() {
if (!updater.exists()) { if (!updater.exists()) {
QFileInfo current(curUpdater); QFileInfo current(curUpdater);
if (!current.exists()) { if (!current.exists()) {
UpdateChecker::clearAll(); ClearAll();
return false; return false;
} }
if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) { if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) {
UpdateChecker::clearAll(); ClearAll();
return false; return false;
} }
} }
@ -542,24 +960,24 @@ bool checkReadyUpdate() {
cSetWriteProtected(true); cSetWriteProtected(true);
return true; return true;
} else { } else {
UpdateChecker::clearAll(); ClearAll();
return false; return false;
} }
} }
if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) { if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) {
UpdateChecker::clearAll(); ClearAll();
return false; return false;
} }
#elif defined Q_OS_MAC // Q_OS_WIN #elif defined Q_OS_MAC // Q_OS_WIN
QDir().mkpath(QFileInfo(curUpdater).absolutePath()); QDir().mkpath(QFileInfo(curUpdater).absolutePath());
DEBUG_LOG(("Update Info: moving %1 to %2...").arg(updater.absoluteFilePath()).arg(curUpdater)); DEBUG_LOG(("Update Info: moving %1 to %2...").arg(updater.absoluteFilePath()).arg(curUpdater));
if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) { if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) {
UpdateChecker::clearAll(); ClearAll();
return false; return false;
} }
#elif defined Q_OS_LINUX // Q_OS_MAC #elif defined Q_OS_LINUX // Q_OS_MAC
if (!linuxMoveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) { if (!linuxMoveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) {
UpdateChecker::clearAll(); ClearAll();
return false; return false;
} }
#endif // Q_OS_LINUX #endif // Q_OS_LINUX
@ -611,3 +1029,5 @@ QString countBetaVersionSignature(uint64 version) { // duplicated in packer.cpp
signature = signature.replace('-', '8').replace('_', 'B'); signature = signature.replace('-', '8').replace('_', 'B');
return QString::fromUtf8(signature.mid(19, 32)); return QString::fromUtf8(signature.mid(19, 32));
} }
} // namespace Core

View File

@ -0,0 +1,58 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Core {
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
class Updater;
class UpdateChecker {
public:
enum class State {
None,
Download,
Ready,
};
struct Progress {
int64 already;
int64 size;
};
UpdateChecker();
rpl::producer<> checking() const;
rpl::producer<> isLatest() const;
rpl::producer<Progress> progress() const;
rpl::producer<> failed() const;
rpl::producer<> ready() const;
void start(bool forceWait = false);
void stop();
State state() const;
int already() const;
int size() const;
private:
const std::shared_ptr<Updater> _updater;
};
bool checkReadyUpdate();
#else // TDESKTOP_DISABLE_AUTOUPDATE
class UpdateChecker {
};
#endif // TDESKTOP_DISABLE_AUTOUPDATE
QString countBetaVersionSignature(uint64 version);
} // namespace Core

View File

@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "application.h" #include "application.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "autoupdater.h" #include "core/update_checker.h"
#include "auth_session.h" #include "auth_session.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "messenger.h" #include "messenger.h"
@ -119,10 +119,15 @@ DialogsWidget::DialogsWidget(QWidget *parent, not_null<Window::Controller*> cont
connect(_filter, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(onFilterCursorMoved(int,int))); connect(_filter, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(onFilterCursorMoved(int,int)));
#ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifndef TDESKTOP_DISABLE_AUTOUPDATE
Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onCheckUpdateStatus())); Core::UpdateChecker checker;
Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onCheckUpdateStatus())); rpl::merge(
Sandbox::connect(SIGNAL(updateReady()), this, SLOT(onCheckUpdateStatus())); rpl::single(rpl::empty_value()),
onCheckUpdateStatus(); checker.isLatest(),
checker.failed(),
checker.ready()
) | rpl::start_with_next([=] {
checkUpdateStatus();
}, lifetime());
#endif // !TDESKTOP_DISABLE_AUTOUPDATE #endif // !TDESKTOP_DISABLE_AUTOUPDATE
subscribe(Adaptive::Changed(), [this] { updateForwardBar(); }); subscribe(Adaptive::Changed(), [this] { updateForwardBar(); });
@ -168,13 +173,14 @@ DialogsWidget::DialogsWidget(QWidget *parent, not_null<Window::Controller*> cont
} }
#ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifndef TDESKTOP_DISABLE_AUTOUPDATE
void DialogsWidget::onCheckUpdateStatus() { void DialogsWidget::checkUpdateStatus() {
if (Sandbox::updatingState() == Application::UpdatingReady) { using Checker = Core::UpdateChecker;
if (Checker().state() == Checker::State::Ready) {
if (_updateTelegram) return; if (_updateTelegram) return;
_updateTelegram.create(this); _updateTelegram.create(this);
_updateTelegram->show(); _updateTelegram->show();
_updateTelegram->setClickedCallback([] { _updateTelegram->setClickedCallback([] {
checkReadyUpdate(); Core::checkReadyUpdate();
App::restart(); App::restart();
}); });
} else { } else {

View File

@ -120,10 +120,6 @@ public slots:
private slots: private slots:
void onDraggingScrollTimer(); void onDraggingScrollTimer();
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
void onCheckUpdateStatus();
#endif // TDESKTOP_DISABLE_AUTOUPDATE
protected: protected:
void dragEnterEvent(QDragEnterEvent *e) override; void dragEnterEvent(QDragEnterEvent *e) override;
void dragMoveEvent(QDragMoveEvent *e) override; void dragMoveEvent(QDragMoveEvent *e) override;
@ -167,6 +163,10 @@ private:
void updateControlsGeometry(); void updateControlsGeometry();
void updateForwardBar(); void updateForwardBar();
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
void checkUpdateStatus();
#endif // TDESKTOP_DISABLE_AUTOUPDATE
bool dialogsFailed(const RPCError &error, mtpRequestId req); bool dialogsFailed(const RPCError &error, mtpRequestId req);
bool searchFailed(DialogsSearchRequestType type, const RPCError &error, mtpRequestId req); bool searchFailed(DialogsSearchRequestType type, const RPCError &error, mtpRequestId req);
bool peopleFailed(const RPCError &error, mtpRequestId req); bool peopleFailed(const RPCError &error, mtpRequestId req);

View File

@ -396,7 +396,7 @@ bool CheckBetaVersionDir() {
quint64 v; quint64 v;
QByteArray k; QByteArray k;
dataStream >> v >> k; dataStream >> v >> k;
if (dataStream.status() == QDataStream::Ok) { if (dataStream.status() == QDataStream::Ok && !k.isEmpty()) {
cSetBetaVersion(qMax(v, AppVersion * 1000ULL)); cSetBetaVersion(qMax(v, AppVersion * 1000ULL));
cSetBetaPrivateKey(k); cSetBetaPrivateKey(k);
cSetRealBetaVersion(v); cSetRealBetaVersion(v);

View File

@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/wrap/fade_wrap.h" #include "ui/wrap/fade_wrap.h"
#include "ui/effects/slide_animation.h" #include "ui/effects/slide_animation.h"
#include "autoupdater.h" #include "core/update_checker.h"
#include "window/window_slide_animation.h" #include "window/window_slide_animation.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_intro.h" #include "styles/style_intro.h"
@ -42,7 +42,7 @@ constexpr str_const kDefaultCountry = "US";
} // namespace } // namespace
Widget::Widget(QWidget *parent) : TWidget(parent) Widget::Widget(QWidget *parent) : RpWidget(parent)
, _back(this, object_ptr<Ui::IconButton>(this, st::introBackButton)) , _back(this, object_ptr<Ui::IconButton>(this, st::introBackButton))
, _settings( , _settings(
this, this,
@ -82,10 +82,17 @@ Widget::Widget(QWidget *parent) : TWidget(parent)
cSetPasswordRecovered(false); cSetPasswordRecovered(false);
#ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifndef TDESKTOP_DISABLE_AUTOUPDATE
Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onCheckUpdateStatus())); Core::UpdateChecker checker;
Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onCheckUpdateStatus())); checker.isLatest() | rpl::start_with_next([=] {
Sandbox::connect(SIGNAL(updateReady()), this, SLOT(onCheckUpdateStatus())); onCheckUpdateStatus();
Sandbox::startUpdateCheck(); }, lifetime());
checker.failed() | rpl::start_with_next([=] {
onCheckUpdateStatus();
}, lifetime());
checker.ready() | rpl::start_with_next([=] {
onCheckUpdateStatus();
}, lifetime());
checker.start();
onCheckUpdateStatus(); onCheckUpdateStatus();
#endif // !TDESKTOP_DISABLE_AUTOUPDATE #endif // !TDESKTOP_DISABLE_AUTOUPDATE
} }
@ -132,7 +139,7 @@ void Widget::createLanguageLink() {
#ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifndef TDESKTOP_DISABLE_AUTOUPDATE
void Widget::onCheckUpdateStatus() { void Widget::onCheckUpdateStatus() {
if (Sandbox::updatingState() == Application::UpdatingReady) { if (Core::UpdateChecker().state() == Core::UpdateChecker::State::Ready) {
if (_update) return; if (_update) return;
_update.create( _update.create(
this, this,
@ -144,7 +151,7 @@ void Widget::onCheckUpdateStatus() {
_update->setVisible(true); _update->setVisible(true);
} }
_update->entity()->setClickedCallback([] { _update->entity()->setClickedCallback([] {
checkReadyUpdate(); Core::checkReadyUpdate();
App::restart(); App::restart();
}); });
} else { } else {

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
#include "mtproto/sender.h" #include "mtproto/sender.h"
#include "ui/rp_widget.h"
namespace Ui { namespace Ui {
class IconButton; class IconButton;
@ -22,7 +23,7 @@ class FadeWrap;
namespace Intro { namespace Intro {
class Widget : public TWidget, private MTP::Sender, private base::Subscriber { class Widget : public Ui::RpWidget, private MTP::Sender, private base::Subscriber {
Q_OBJECT Q_OBJECT
public: public:

View File

@ -74,6 +74,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "mtproto/dc_options.h" #include "mtproto/dc_options.h"
#include "core/file_utilities.h" #include "core/file_utilities.h"
#include "core/update_checker.h"
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
#include "calls/calls_top_bar.h" #include "calls/calls_top_bar.h"
#include "auth_session.h" #include "auth_session.h"
@ -323,7 +324,8 @@ MainWidget::MainWidget(
orderWidgets(); orderWidgets();
#ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifndef TDESKTOP_DISABLE_AUTOUPDATE
Sandbox::startUpdateCheck(); Core::UpdateChecker checker;
checker.start();
#endif // !TDESKTOP_DISABLE_AUTOUPDATE #endif // !TDESKTOP_DISABLE_AUTOUPDATE
} }
@ -704,7 +706,7 @@ void MainWidget::finishForwarding(not_null<History*> history) {
} }
void MainWidget::updateMutedIn(TimeMs delay) { void MainWidget::updateMutedIn(TimeMs delay) {
accumulate_max(delay, 24 * 3600 * 1000LL); accumulate_min(delay, 24 * 3600 * 1000LL);
if (!_updateMutedTimer.isActive() if (!_updateMutedTimer.isActive()
|| _updateMutedTimer.remainingTime() > delay) { || _updateMutedTimer.remainingTime() > delay) {
_updateMutedTimer.start(delay); _updateMutedTimer.start(delay);

View File

@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_file_parser.h" #include "lang/lang_file_parser.h"
#include "lang/lang_cloud_manager.h" #include "lang/lang_cloud_manager.h"
#include "messenger.h" #include "messenger.h"
#include "autoupdater.h" #include "core/update_checker.h"
namespace Settings { namespace Settings {
@ -34,21 +34,37 @@ UpdateStateRow::UpdateStateRow(QWidget *parent) : RpWidget(parent)
connect(_check, SIGNAL(clicked()), this, SLOT(onCheck())); connect(_check, SIGNAL(clicked()), this, SLOT(onCheck()));
connect(_restart, SIGNAL(clicked()), this, SIGNAL(restart())); connect(_restart, SIGNAL(clicked()), this, SIGNAL(restart()));
Sandbox::connect(SIGNAL(updateChecking()), this, SLOT(onChecking()));
Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onLatest()));
Sandbox::connect(SIGNAL(updateProgress(qint64, qint64)), this, SLOT(onDownloading(qint64, qint64)));
Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onFailed()));
Sandbox::connect(SIGNAL(updateReady()), this, SLOT(onReady()));
_versionText = lng_settings_current_version_label(lt_version, currentVersionText()); _versionText = lng_settings_current_version_label(lt_version, currentVersionText());
switch (Sandbox::updatingState()) { Core::UpdateChecker checker;
case Application::UpdatingDownload: checker.checking() | rpl::start_with_next([=] {
onChecking();
}, lifetime());
checker.isLatest() | rpl::start_with_next([=] {
onLatest();
}, lifetime());
checker.progress(
) | rpl::start_with_next([=](Core::UpdateChecker::Progress progress) {
onDownloading(progress.already, progress.size);
}, lifetime());
checker.failed() | rpl::start_with_next([=] {
onFailed();
}, lifetime());
checker.ready() | rpl::start_with_next([=] {
onReady();
}, lifetime());
switch (checker.state()) {
case Core::UpdateChecker::State::Download:
setState(State::Download, true); setState(State::Download, true);
setDownloadProgress(Sandbox::updatingReady(), Sandbox::updatingSize()); setDownloadProgress(checker.already(), checker.size());
break; break;
case Application::UpdatingReady: setState(State::Ready, true); break; case Core::UpdateChecker::State::Ready:
default: setState(State::None, true); break; setState(State::Ready, true);
break;
default:
setState(State::None, true);
break;
} }
} }
@ -89,9 +105,11 @@ void UpdateStateRow::paintEvent(QPaintEvent *e) {
void UpdateStateRow::onCheck() { void UpdateStateRow::onCheck() {
if (!cAutoUpdate()) return; if (!cAutoUpdate()) return;
Core::UpdateChecker checker;
setState(State::Check); setState(State::Check);
cSetLastUpdateCheck(0); cSetLastUpdateCheck(0);
Sandbox::startUpdateCheck(); checker.start();
} }
void UpdateStateRow::setState(State state, bool force) { void UpdateStateRow::setState(State state, bool force) {
@ -212,7 +230,7 @@ void GeneralWidget::onChangeLanguage() {
void GeneralWidget::onRestart() { void GeneralWidget::onRestart() {
#ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifndef TDESKTOP_DISABLE_AUTOUPDATE
checkReadyUpdate(); Core::checkReadyUpdate();
#endif // !TDESKTOP_DISABLE_AUTOUPDATE #endif // !TDESKTOP_DISABLE_AUTOUPDATE
App::restart(); App::restart();
} }
@ -224,10 +242,11 @@ void GeneralWidget::onUpdateAutomatically() {
_updateRow->toggle( _updateRow->toggle(
cAutoUpdate(), cAutoUpdate(),
anim::type::normal); anim::type::normal);
Core::UpdateChecker checker;
if (cAutoUpdate()) { if (cAutoUpdate()) {
Sandbox::startUpdateCheck(); checker.start();
} else { } else {
Sandbox::stopUpdate(); checker.stop();
} }
} }
#endif // !TDESKTOP_DISABLE_AUTOUPDATE #endif // !TDESKTOP_DISABLE_AUTOUPDATE

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/send_files_box.h" #include "boxes/send_files_box.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "core/crash_reports.h" #include "core/crash_reports.h"
#include "core/update_checker.h"
#include "observer_peer.h" #include "observer_peer.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "mainwindow.h" #include "mainwindow.h"
@ -1251,7 +1252,7 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
cSetAutoUpdate(v == 1); cSetAutoUpdate(v == 1);
#ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifndef TDESKTOP_DISABLE_AUTOUPDATE
if (!cAutoUpdate()) { if (!cAutoUpdate()) {
Sandbox::stopUpdate(); Core::UpdateChecker().stop();
} }
#endif // !TDESKTOP_DISABLE_AUTOUPDATE #endif // !TDESKTOP_DISABLE_AUTOUPDATE
} break; } break;

View File

@ -158,6 +158,8 @@
<(src_loc)/core/single_timer.cpp <(src_loc)/core/single_timer.cpp
<(src_loc)/core/single_timer.h <(src_loc)/core/single_timer.h
<(src_loc)/core/tl_help.h <(src_loc)/core/tl_help.h
<(src_loc)/core/update_checker.cpp
<(src_loc)/core/update_checker.h
<(src_loc)/core/utils.cpp <(src_loc)/core/utils.cpp
<(src_loc)/core/utils.h <(src_loc)/core/utils.h
<(src_loc)/core/version.h <(src_loc)/core/version.h
@ -725,8 +727,6 @@
<(src_loc)/application.h <(src_loc)/application.h
<(src_loc)/auth_session.cpp <(src_loc)/auth_session.cpp
<(src_loc)/auth_session.h <(src_loc)/auth_session.h
<(src_loc)/autoupdater.cpp
<(src_loc)/autoupdater.h
<(src_loc)/config.h <(src_loc)/config.h
<(src_loc)/countries.h <(src_loc)/countries.h
<(src_loc)/facades.cpp <(src_loc)/facades.cpp