From 993cb987a6ef6d75625a086969105c9729bd2ee6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 26 Apr 2018 20:14:21 +0400 Subject: [PATCH] Improve autoupdate code, move it from Application. --- Telegram/SourceFiles/app.cpp | 3 +- Telegram/SourceFiles/application.cpp | 244 +------ Telegram/SourceFiles/application.h | 61 +- Telegram/SourceFiles/autoupdater.h | 63 -- Telegram/SourceFiles/boxes/about_box.cpp | 4 +- .../SourceFiles/core/crash_report_window.cpp | 52 +- .../SourceFiles/core/crash_report_window.h | 10 +- .../update_checker.cpp} | 682 ++++++++++++++---- Telegram/SourceFiles/core/update_checker.h | 58 ++ .../SourceFiles/dialogs/dialogs_widget.cpp | 22 +- Telegram/SourceFiles/dialogs/dialogs_widget.h | 8 +- Telegram/SourceFiles/facades.cpp | 2 +- Telegram/SourceFiles/intro/introwidget.cpp | 23 +- Telegram/SourceFiles/intro/introwidget.h | 3 +- Telegram/SourceFiles/mainwidget.cpp | 6 +- .../settings/settings_general_widget.cpp | 53 +- Telegram/SourceFiles/storage/localstorage.cpp | 3 +- Telegram/gyp/telegram_sources.txt | 4 +- 18 files changed, 745 insertions(+), 556 deletions(-) delete mode 100644 Telegram/SourceFiles/autoupdater.h rename Telegram/SourceFiles/{autoupdater.cpp => core/update_checker.cpp} (55%) create mode 100644 Telegram/SourceFiles/core/update_checker.h diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 8fdebe7aa4..f93551069b 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "observer_peer.h" #include "auth_session.h" #include "core/crash_reports.h" +#include "core/update_checker.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "window/themes/window_theme.h" @@ -1667,7 +1668,7 @@ namespace { void restart() { #ifndef TDESKTOP_DISABLE_AUTOUPDATE - bool updateReady = (Sandbox::updatingState() == Application::UpdatingReady); + bool updateReady = (Core::UpdateChecker().state() == Core::UpdateChecker::State::Ready); #else // !TDESKTOP_DISABLE_AUTOUPDATE bool updateReady = false; #endif // else for !TDESKTOP_DISABLE_AUTOUPDATE diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 29db39b388..438911a8f6 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -11,11 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwidget.h" #include "mainwindow.h" #include "storage/localstorage.h" -#include "autoupdater.h" #include "window/notifications_manager.h" #include "core/crash_reports.h" #include "messenger.h" #include "base/timer.h" +#include "core/update_checker.h" #include "core/crash_report_window.h" namespace { @@ -65,7 +65,11 @@ Application::Application( int &argc, char **argv) : QApplication(argc, argv) -, _launcher(launcher) { +, _launcher(launcher) +#ifndef TDESKTOP_DISABLE_AUTOUPDATE +, _updateChecker(std::make_unique()) +#endif // TDESKTOP_DISABLE_AUTOUPDATE +{ const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath()); char h[33] = { 0 }; hashMd5Hex(d.constData(), d.size(), h); @@ -86,13 +90,6 @@ Application::Application( QTimer::singleShot(0, this, SLOT(startApplication())); 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()) { LOG(("Many instance allowed, starting...")); singleInstanceChecked(); @@ -180,7 +177,7 @@ void Application::socketError(QLocalSocket::LocalSocketError e) { #endif // !Q_OS_WINRT #ifndef TDESKTOP_DISABLE_AUTOUPDATE - if (!cNoStartUpdate() && checkReadyUpdate()) { + if (!cNoStartUpdate() && Core::checkReadyUpdate()) { cSetRestartingUpdate(true); DEBUG_LOG(("Application Info: installing update instead of starting app...")); return App::quit(); @@ -356,168 +353,10 @@ void Application::closeApplication() { _localSocket.close(); #ifndef TDESKTOP_DISABLE_AUTOUPDATE - delete _updateReply; - _updateReply = 0; - if (_updateChecker) _updateChecker->deleteLater(); - _updateChecker = 0; - if (_updateThread) { - _updateThread->quit(); - } - _updateThread = 0; + _updateChecker = nullptr; #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() { return qobject_cast(QApplication::instance()); } @@ -569,73 +408,6 @@ void adjustSingleTimers() { 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) { if (auto a = application()) { a->connect(a, signal, object, method); diff --git a/Telegram/SourceFiles/application.h b/Telegram/SourceFiles/application.h index 116d121f50..97d4304846 100644 --- a/Telegram/SourceFiles/application.h +++ b/Telegram/SourceFiles/application.h @@ -7,10 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -class UpdateChecker; - namespace Core { class Launcher; +class UpdateChecker; } // namespace Core class Application : public QApplication { @@ -59,46 +58,11 @@ private: 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: - object_ptr _updateCheckTimer = { nullptr }; - QNetworkReply *_updateReply = nullptr; - QNetworkAccessManager _updateManager; - QThread *_updateThread = nullptr; - UpdateChecker *_updateChecker = nullptr; - +#ifndef TDESKTOP_DISABLE_AUTOUPDATE + std::unique_ptr _updateChecker; #endif // !TDESKTOP_DISABLE_AUTOUPDATE + }; namespace Sandbox { @@ -112,23 +76,6 @@ void execExternal(const QString &cmd); 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 connect(const char *signal, QObject *object, const char *method); diff --git a/Telegram/SourceFiles/autoupdater.h b/Telegram/SourceFiles/autoupdater.h deleted file mode 100644 index 9839428b9d..0000000000 --- a/Telegram/SourceFiles/autoupdater.h +++ /dev/null @@ -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 -#include -#include - -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); diff --git a/Telegram/SourceFiles/boxes/about_box.cpp b/Telegram/SourceFiles/boxes/about_box.cpp index c2f70830b7..ef3831f004 100644 --- a/Telegram/SourceFiles/boxes/about_box.cpp +++ b/Telegram/SourceFiles/boxes/about_box.cpp @@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "mainwidget.h" #include "mainwindow.h" -#include "autoupdater.h" #include "boxes/confirm_box.h" #include "application.h" #include "ui/widgets/buttons.h" @@ -18,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_boxes.h" #include "platform/platform_file_utilities.h" #include "core/click_handler_types.h" +#include "core/update_checker.h" 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) @@ -68,7 +68,7 @@ void AboutBox::showVersionHistory() { case dbipLinux32: url += qsl("linux32/%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); diff --git a/Telegram/SourceFiles/core/crash_report_window.cpp b/Telegram/SourceFiles/core/crash_report_window.cpp index 2e123bab67..62365d06c0 100644 --- a/Telegram/SourceFiles/core/crash_report_window.cpp +++ b/Telegram/SourceFiles/core/crash_report_window.cpp @@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_specific.h" #include "application.h" #include "base/zlib_help.h" -#include "autoupdater.h" +#include "core/update_checker.h" PreLaunchWindow *PreLaunchWindowInstance = nullptr; @@ -299,23 +299,36 @@ LastCrashedWindow::LastCrashedWindow() _updatingSkip.setText(qsl("SKIP")); connect(&_updatingSkip, SIGNAL(clicked()), this, SLOT(onUpdateSkip())); - Sandbox::connect(SIGNAL(updateChecking()), this, SLOT(onUpdateChecking())); - Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onUpdateLatest())); - Sandbox::connect(SIGNAL(updateProgress(qint64,qint64)), this, SLOT(onUpdateDownloading(qint64,qint64))); - Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onUpdateFailed())); - Sandbox::connect(SIGNAL(updateReady()), this, SLOT(onUpdateReady())); + Core::UpdateChecker checker; + using Progress = Core::UpdateChecker::Progress; + checker.checking( + ) | rpl::start_with_next([=] { onUpdateChecking(); }, _lifetime); + 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()) { - case Application::UpdatingDownload: + switch (checker.state()) { + case Core::UpdateChecker::State::Download: setUpdatingState(UpdatingDownload, true); - setDownloadProgress(Sandbox::updatingReady(), Sandbox::updatingSize()); - break; - case Application::UpdatingReady: setUpdatingState(UpdatingReady, true); break; - default: setUpdatingState(UpdatingCheck, true); break; + setDownloadProgress(checker.already(), checker.size()); + break; + case Core::UpdateChecker::State::Ready: + setUpdatingState(UpdatingReady, true); + break; + default: + setUpdatingState(UpdatingCheck, true); + break; } cSetLastUpdateCheck(0); - Sandbox::startUpdateCheck(); + checker.start(); #else // !TDESKTOP_DISABLE_AUTOUPDATE _updating.setText(qsl("Please check if there is a new version available.")); if (_sendingState != SendingNoReport) { @@ -790,9 +803,10 @@ void LastCrashedWindow::onNetworkSettingsSaved( #ifndef TDESKTOP_DISABLE_AUTOUPDATE if ((_updatingState == UpdatingFail && (_sendingState == SendingNoReport || _sendingState == SendingUpdateCheck)) || (_updatingState == UpdatingCheck)) { - Sandbox::stopUpdate(); + Core::UpdateChecker checker; + checker.stop(); cSetLastUpdateCheck(0); - Sandbox::startUpdateCheck(); + checker.start(); } else #endif // !TDESKTOP_DISABLE_AUTOUPDATE if (_sendingState == SendingFail || _sendingState == SendingProgress) { @@ -815,7 +829,7 @@ void LastCrashedWindow::setUpdatingState(UpdatingState state, bool force) { } break; case UpdatingReady: - if (checkReadyUpdate()) { + if (Core::checkReadyUpdate()) { cSetRestartingUpdate(true); App::quit(); return; @@ -849,7 +863,8 @@ void LastCrashedWindow::setDownloadProgress(qint64 ready, qint64 total) { void LastCrashedWindow::onUpdateRetry() { cSetLastUpdateCheck(0); - Sandbox::startUpdateCheck(); + Core::UpdateChecker checker; + checker.start(); } void LastCrashedWindow::onUpdateSkip() { @@ -857,7 +872,8 @@ void LastCrashedWindow::onUpdateSkip() { onContinue(); } else { if (_updatingState == UpdatingCheck || _updatingState == UpdatingDownload) { - Sandbox::stopUpdate(); + Core::UpdateChecker checker; + checker.stop(); setUpdatingState(UpdatingFail); } _sendingState = SendingNone; diff --git a/Telegram/SourceFiles/core/crash_report_window.h b/Telegram/SourceFiles/core/crash_report_window.h index 7006a87b24..cde1f62808 100644 --- a/Telegram/SourceFiles/core/crash_report_window.h +++ b/Telegram/SourceFiles/core/crash_report_window.h @@ -114,6 +114,11 @@ private: QString minidumpFileName(); 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; quint32 _port; @@ -128,8 +133,6 @@ private: bool _reportShown, _reportSaved; - void excludeReportUsername(); - enum SendingState { SendingNoReport, SendingUpdateCheck, @@ -167,8 +170,7 @@ private: void setDownloadProgress(qint64 ready, qint64 total); #endif // !TDESKTOP_DISABLE_AUTOUPDATE - QString getReportField(const QLatin1String &name, const QLatin1String &prefix); - void addReportFieldPart(const QLatin1String &name, const QLatin1String &prefix, QHttpMultiPart *multipart); + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/autoupdater.cpp b/Telegram/SourceFiles/core/update_checker.cpp similarity index 55% rename from Telegram/SourceFiles/autoupdater.cpp rename to Telegram/SourceFiles/core/update_checker.cpp index dbbfb4dad1..499930530c 100644 --- a/Telegram/SourceFiles/autoupdater.cpp +++ b/Telegram/SourceFiles/core/update_checker.cpp @@ -5,8 +5,12 @@ 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 */ +#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 #include @@ -19,131 +23,554 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #endif // else of Q_OS_WIN -#include "application.h" -#include "platform/platform_specific.h" +namespace Core { #ifndef TDESKTOP_DISABLE_AUTOUPDATE +namespace { + +constexpr auto kCheckTimeout = TimeMs(10000); + #ifdef Q_OS_WIN -typedef DWORD VerInt; -typedef WCHAR VerChar; +using VersionInt = DWORD; +using VersionChar = WCHAR; #else // Q_OS_WIN -typedef int VerInt; -typedef wchar_t VerChar; +using VersionInt = int; +using VersionChar = wchar_t; #endif // Q_OS_WIN -UpdateChecker::UpdateChecker(QThread *thread, const QString &url) : reply(0), already(0), full(0) { - updateUrl = url; - moveToThread(thread); - manager.moveToThread(thread); +using ErrorSignal = void(QNetworkReply::*)(QNetworkReply::NetworkError); +const auto QNetworkReply_error = ErrorSignal(&QNetworkReply::error); - connect(thread, SIGNAL(started()), this, SLOT(start())); +std::weak_ptr UpdaterInstance; + +std::shared_ptr GetUpdaterInstance() { + if (const auto result = UpdaterInstance.lock()) { + return result; + } + const auto result = std::make_shared(); + 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() 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; + rpl::event_stream<> _failed; + rpl::event_stream<> _ready; + std::unique_ptr _manager; + QNetworkReply *_reply = nullptr; + std::unique_ptr _thread; + Private *_private = nullptr; + + rpl::lifetime _lifetime; + +}; + +class Updater::Private : public QObject { +public: + Private( + not_null parent, + not_null 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 _parent; + QString _url; + QNetworkAccessManager _manager; + std::unique_ptr _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 { + 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(); + _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(); + _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 { + 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 parent, + not_null thread, + const QString &url) +: _parent(parent) { + _url = url; + moveToThread(thread); + _manager.moveToThread(thread); + + connect(thread, &QThread::started, this, [=] { start(); }); initOutput(); } -void UpdateChecker::initOutput() { - QString fileName; - QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl); - if (m.hasMatch()) { - fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString()); - } - if (fileName.isEmpty()) { - fileName = qsl("tupdate-%1").arg(rand_value() % 1000000); - } - QString dirStr = cWorkingDir() + qsl("tupdates/"); - fileName = dirStr + fileName; - QFileInfo file(fileName); +void Updater::Private::initOutput() { + auto fileName = QString(); + const auto nameRegExp = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")); + const auto nameMatch = nameRegExp.match(_url); - 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()) { - QFileInfoList all = dir.entryInfoList(QDir::Files); - for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) { - if (i->absoluteFilePath() != file.absoluteFilePath()) { + const auto all = dir.entryInfoList(QDir::Files); + for (auto i = all.begin(), e = all.end(); i != e; ++i) { + if (i->absoluteFilePath() != info.absoluteFilePath()) { QFile::remove(i->absoluteFilePath()); } } } else { dir.mkdir(dir.absolutePath()); } - outputFile.setFileName(fileName); - if (file.exists()) { - uint64 fullSize = file.size(); + _output.setFileName(finalName); + if (info.exists()) { + uint64 fullSize = info.size(); if (fullSize < INT_MAX) { int32 goodSize = (int32)fullSize; if (goodSize % UpdateChunk) { goodSize = goodSize - (goodSize % UpdateChunk); if (goodSize) { - if (outputFile.open(QIODevice::ReadOnly)) { - QByteArray goodData = outputFile.readAll().mid(0, goodSize); - outputFile.close(); - if (outputFile.open(QIODevice::WriteOnly)) { - outputFile.write(goodData); - outputFile.close(); + if (_output.open(QIODevice::ReadOnly)) { + QByteArray goodData = _output.readAll().mid(0, goodSize); + _output.close(); + if (_output.open(QIODevice::WriteOnly)) { + _output.write(goodData); + _output.close(); - QMutexLocker lock(&mutex); - already = goodSize; + QMutexLocker lock(&_mutex); + _already = goodSize; } } } } else { - QMutexLocker lock(&mutex); - already = goodSize; + QMutexLocker lock(&_mutex); + _already = goodSize; } } - if (!already) { - QFile::remove(fileName); + if (!_already) { + QFile::remove(finalName); } } } -void UpdateChecker::start() { +void Updater::Private::start() { sendRequest(); } -void UpdateChecker::sendRequest() { - QNetworkRequest req(updateUrl); - QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-"; - req.setRawHeader("Range", rangeHeaderValue); - req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); - if (reply) reply->deleteLater(); - reply = manager.get(req); - connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64))); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError))); - connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot())); +void Updater::Private::sendRequest() { + auto request = QNetworkRequest(_url); + const auto rangeHeaderValue = "bytes=" + + QByteArray::number(_already) + + "-"; + request.setRawHeader("Range", rangeHeaderValue); + request.setAttribute( + QNetworkRequest::HttpPipeliningAllowedAttribute, + true); + _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() { - typedef QList Pairs; - Pairs pairs = reply->rawHeaderPairs(); - for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) { - if (QString::fromUtf8(i->first).toLower() == "content-range") { - QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second)); +void Updater::Private::gotMetaData() { + const auto pairs = _reply->rawHeaderPairs(); + for (const auto pair : pairs) { + if (QString::fromUtf8(pair.first).toLower() == "content-range") { + const auto m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(pair.second)); if (m.hasMatch()) { { - QMutexLocker lock(&mutex); - full = m.captured(1).toInt(); + QMutexLocker lock(&_mutex); + _size = m.captured(1).toInt(); } - - Sandbox::updateProgress(already, full); + _parent->onProgress({ _already, _size }); } } } } -int32 UpdateChecker::ready() { - QMutexLocker lock(&mutex); - return already; +int Updater::Private::already() const { + QMutexLocker lock(&_mutex); + return _already; } -int32 UpdateChecker::size() { - QMutexLocker lock(&mutex); - return full; +int Updater::Private::size() const { + QMutexLocker lock(&_mutex); + return _size; } -void UpdateChecker::partFinished(qint64 got, qint64 total) { - if (!reply) return; +void Updater::Private::partFinished(qint64 got, qint64 total) { + if (!_reply) return; - QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + QVariant statusCode = _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); if (statusCode.isValid()) { int status = statusCode.toInt(); if (status != 200 && status != 206 && status != 416) { @@ -152,60 +579,55 @@ void UpdateChecker::partFinished(qint64 got, qint64 total) { } } - if (!already && !full) { - QMutexLocker lock(&mutex); - full = total; + if (!_already && !_size) { + QMutexLocker lock(&_mutex); + _size = total; } DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total)); - if (!outputFile.isOpen()) { - if (!outputFile.open(QIODevice::Append)) { - LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName())); + if (!_output.isOpen()) { + if (!_output.open(QIODevice::Append)) { + LOG(("Update Error: Could not open output file '%1' for appending").arg(_output.fileName())); return fatalFail(); } } - QByteArray r = reply->readAll(); + QByteArray r = _reply->readAll(); if (!r.isEmpty()) { - outputFile.write(r); + _output.write(r); - QMutexLocker lock(&mutex); - already += r.size(); + QMutexLocker lock(&_mutex); + _already += r.size(); } if (got >= total) { - reply->deleteLater(); - reply = 0; - outputFile.close(); + _reply.release()->deleteLater(); + _output.close(); unpackUpdate(); } else { - Sandbox::updateProgress(already, full); + _parent->onProgress({ _already, _size }); } } -void UpdateChecker::partFailed(QNetworkReply::NetworkError e) { - if (!reply) return; +void Updater::Private::partFailed(QNetworkReply::NetworkError e) { + if (!_reply) return; - QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - reply->deleteLater(); - reply = 0; + const auto statusCode = _reply->attribute( + QNetworkRequest::HttpStatusCodeAttribute); + _reply.release()->deleteLater(); if (statusCode.isValid()) { int status = statusCode.toInt(); if (status == 416) { // Requested range not satisfiable - outputFile.close(); + _output.close(); unpackUpdate(); return; } } - LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e)); - Sandbox::updateFailed(); + LOG(("Update Error: failed to download part starting from %1, error %2").arg(_already).arg(e)); + _parent->onFailed(); } -void UpdateChecker::fatalFail() { - clearAll(); - Sandbox::updateFailed(); -} - -void UpdateChecker::clearAll() { - psDeleteDir(cWorkingDir() + qsl("tupdates")); +void Updater::Private::fatalFail() { + ClearAll(); + _parent->onFailed(); } //QString winapiErrorWrap() { @@ -223,9 +645,9 @@ void UpdateChecker::clearAll() { // return QString::fromWCharArray(errMsg); //} -void UpdateChecker::unpackUpdate() { +void Updater::Private::unpackUpdate() { QByteArray packed; - if (!outputFile.open(QIODevice::ReadOnly)) { + if (!_output.open(QIODevice::ReadOnly)) { LOG(("Update Error: cant read updates file!")); 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 #endif // Q_OS_WIN - QByteArray compressed = outputFile.readAll(); + QByteArray compressed = _output.readAll(); int32 compressedLen = compressed.size() - hSize; if (compressedLen <= 0) { LOG(("Update Error: bad compressed size: %1").arg(compressed.size())); return fatalFail(); } - outputFile.close(); + _output.close(); QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"); psDeleteDir(tempDirPath); @@ -429,8 +851,9 @@ void UpdateChecker::unpackUpdate() { 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(); - VerInt versionNum = VerInt(version), versionLen = VerInt(versionString.size() * sizeof(VerChar)); - VerChar versionStr[32]; + const auto versionNum = VersionInt(version); + const auto versionLen = VersionInt(versionString.size() * sizeof(VersionChar)); + VersionChar versionStr[32]; memcpy(versionStr, versionString.c_str(), versionLen); 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"))); return fatalFail(); } - fVersion.write((const char*)&versionNum, sizeof(VerInt)); + fVersion.write((const char*)&versionNum, sizeof(VersionInt)); if (versionNum == 0x7FFFFFFF) { // beta version fVersion.write((const char*)&betaVersion, sizeof(quint64)); } else { - fVersion.write((const char*)&versionLen, sizeof(VerInt)); + fVersion.write((const char*)&versionLen, sizeof(VersionInt)); fVersion.write((const char*)&versionStr[0], versionLen); } fVersion.close(); @@ -460,21 +883,16 @@ void UpdateChecker::unpackUpdate() { LOG(("Update Error: cant create ready file '%1'").arg(readyFilePath)); return fatalFail(); } - outputFile.remove(); + _output.remove(); - Sandbox::updateReady(); -} - -UpdateChecker::~UpdateChecker() { - delete reply; - reply = 0; + _parent->onReady(); } bool checkReadyUpdate() { QString readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"), readyPath = cWorkingDir() + qsl("tupdates/temp"); if (!QFile(readyFilePath).exists() || cExeName().isEmpty()) { if (QDir(cWorkingDir() + qsl("tupdates/ready")).exists() || QDir(cWorkingDir() + qsl("tupdates/temp")).exists()) { - UpdateChecker::clearAll(); + ClearAll(); } return false; } @@ -485,30 +903,30 @@ bool checkReadyUpdate() { QFile fVersion(versionPath); if (!fVersion.open(QIODevice::ReadOnly)) { LOG(("Update Error: cant read version file '%1'").arg(versionPath)); - UpdateChecker::clearAll(); + ClearAll(); return false; } - VerInt versionNum; - if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) { + auto versionNum = VersionInt(); + if (fVersion.read((char*)&versionNum, sizeof(VersionInt)) != sizeof(VersionInt)) { LOG(("Update Error: cant read version from file '%1'").arg(versionPath)); - UpdateChecker::clearAll(); + ClearAll(); return false; } if (versionNum == 0x7FFFFFFF) { // beta version quint64 betaVersion = 0; if (fVersion.read((char*)&betaVersion, sizeof(quint64)) != sizeof(quint64)) { LOG(("Update Error: cant read beta version from file '%1'").arg(versionPath)); - UpdateChecker::clearAll(); + ClearAll(); return false; } if (!cBetaVersion() || betaVersion <= cBetaVersion()) { LOG(("Update Error: cant install beta version %1 having beta version %2").arg(betaVersion).arg(cBetaVersion())); - UpdateChecker::clearAll(); + ClearAll(); return false; } } else if (versionNum <= AppVersion) { LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion)); - UpdateChecker::clearAll(); + ClearAll(); return false; } fVersion.close(); @@ -527,11 +945,11 @@ bool checkReadyUpdate() { if (!updater.exists()) { QFileInfo current(curUpdater); if (!current.exists()) { - UpdateChecker::clearAll(); + ClearAll(); return false; } if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) { - UpdateChecker::clearAll(); + ClearAll(); return false; } } @@ -542,24 +960,24 @@ bool checkReadyUpdate() { cSetWriteProtected(true); return true; } else { - UpdateChecker::clearAll(); + ClearAll(); return false; } } if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) { - UpdateChecker::clearAll(); + ClearAll(); return false; } #elif defined Q_OS_MAC // Q_OS_WIN QDir().mkpath(QFileInfo(curUpdater).absolutePath()); DEBUG_LOG(("Update Info: moving %1 to %2...").arg(updater.absoluteFilePath()).arg(curUpdater)); if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) { - UpdateChecker::clearAll(); + ClearAll(); return false; } #elif defined Q_OS_LINUX // Q_OS_MAC if (!linuxMoveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) { - UpdateChecker::clearAll(); + ClearAll(); return false; } #endif // Q_OS_LINUX @@ -611,3 +1029,5 @@ QString countBetaVersionSignature(uint64 version) { // duplicated in packer.cpp signature = signature.replace('-', '8').replace('_', 'B'); return QString::fromUtf8(signature.mid(19, 32)); } + +} // namespace Core diff --git a/Telegram/SourceFiles/core/update_checker.h b/Telegram/SourceFiles/core/update_checker.h new file mode 100644 index 0000000000..042d84443a --- /dev/null +++ b/Telegram/SourceFiles/core/update_checker.h @@ -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() 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; + +}; + +bool checkReadyUpdate(); + +#else // TDESKTOP_DISABLE_AUTOUPDATE +class UpdateChecker { +}; + +#endif // TDESKTOP_DISABLE_AUTOUPDATE + +QString countBetaVersionSignature(uint64 version); + +} // namespace Core diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 7403f3a744..c0c4b74c04 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "application.h" #include "mainwindow.h" #include "mainwidget.h" -#include "autoupdater.h" +#include "core/update_checker.h" #include "auth_session.h" #include "apiwrap.h" #include "messenger.h" @@ -119,10 +119,15 @@ DialogsWidget::DialogsWidget(QWidget *parent, not_null cont connect(_filter, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(onFilterCursorMoved(int,int))); #ifndef TDESKTOP_DISABLE_AUTOUPDATE - Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onCheckUpdateStatus())); - Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onCheckUpdateStatus())); - Sandbox::connect(SIGNAL(updateReady()), this, SLOT(onCheckUpdateStatus())); - onCheckUpdateStatus(); + Core::UpdateChecker checker; + rpl::merge( + rpl::single(rpl::empty_value()), + checker.isLatest(), + checker.failed(), + checker.ready() + ) | rpl::start_with_next([=] { + checkUpdateStatus(); + }, lifetime()); #endif // !TDESKTOP_DISABLE_AUTOUPDATE subscribe(Adaptive::Changed(), [this] { updateForwardBar(); }); @@ -168,13 +173,14 @@ DialogsWidget::DialogsWidget(QWidget *parent, not_null cont } #ifndef TDESKTOP_DISABLE_AUTOUPDATE -void DialogsWidget::onCheckUpdateStatus() { - if (Sandbox::updatingState() == Application::UpdatingReady) { +void DialogsWidget::checkUpdateStatus() { + using Checker = Core::UpdateChecker; + if (Checker().state() == Checker::State::Ready) { if (_updateTelegram) return; _updateTelegram.create(this); _updateTelegram->show(); _updateTelegram->setClickedCallback([] { - checkReadyUpdate(); + Core::checkReadyUpdate(); App::restart(); }); } else { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 66d63aa0d7..715751bf00 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -120,10 +120,6 @@ public slots: private slots: void onDraggingScrollTimer(); -#ifndef TDESKTOP_DISABLE_AUTOUPDATE - void onCheckUpdateStatus(); -#endif // TDESKTOP_DISABLE_AUTOUPDATE - protected: void dragEnterEvent(QDragEnterEvent *e) override; void dragMoveEvent(QDragMoveEvent *e) override; @@ -167,6 +163,10 @@ private: void updateControlsGeometry(); void updateForwardBar(); +#ifndef TDESKTOP_DISABLE_AUTOUPDATE + void checkUpdateStatus(); +#endif // TDESKTOP_DISABLE_AUTOUPDATE + bool dialogsFailed(const RPCError &error, mtpRequestId req); bool searchFailed(DialogsSearchRequestType type, const RPCError &error, mtpRequestId req); bool peopleFailed(const RPCError &error, mtpRequestId req); diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 2f8aee24ec..64edea19c8 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -396,7 +396,7 @@ bool CheckBetaVersionDir() { quint64 v; QByteArray k; dataStream >> v >> k; - if (dataStream.status() == QDataStream::Ok) { + if (dataStream.status() == QDataStream::Ok && !k.isEmpty()) { cSetBetaVersion(qMax(v, AppVersion * 1000ULL)); cSetBetaPrivateKey(k); cSetRealBetaVersion(v); diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index e1c4da6523..54413872e8 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/wrap/fade_wrap.h" #include "ui/effects/slide_animation.h" -#include "autoupdater.h" +#include "core/update_checker.h" #include "window/window_slide_animation.h" #include "styles/style_boxes.h" #include "styles/style_intro.h" @@ -42,7 +42,7 @@ constexpr str_const kDefaultCountry = "US"; } // namespace -Widget::Widget(QWidget *parent) : TWidget(parent) +Widget::Widget(QWidget *parent) : RpWidget(parent) , _back(this, object_ptr(this, st::introBackButton)) , _settings( this, @@ -82,10 +82,17 @@ Widget::Widget(QWidget *parent) : TWidget(parent) cSetPasswordRecovered(false); #ifndef TDESKTOP_DISABLE_AUTOUPDATE - Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onCheckUpdateStatus())); - Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onCheckUpdateStatus())); - Sandbox::connect(SIGNAL(updateReady()), this, SLOT(onCheckUpdateStatus())); - Sandbox::startUpdateCheck(); + Core::UpdateChecker checker; + checker.isLatest() | rpl::start_with_next([=] { + onCheckUpdateStatus(); + }, lifetime()); + checker.failed() | rpl::start_with_next([=] { + onCheckUpdateStatus(); + }, lifetime()); + checker.ready() | rpl::start_with_next([=] { + onCheckUpdateStatus(); + }, lifetime()); + checker.start(); onCheckUpdateStatus(); #endif // !TDESKTOP_DISABLE_AUTOUPDATE } @@ -132,7 +139,7 @@ void Widget::createLanguageLink() { #ifndef TDESKTOP_DISABLE_AUTOUPDATE void Widget::onCheckUpdateStatus() { - if (Sandbox::updatingState() == Application::UpdatingReady) { + if (Core::UpdateChecker().state() == Core::UpdateChecker::State::Ready) { if (_update) return; _update.create( this, @@ -144,7 +151,7 @@ void Widget::onCheckUpdateStatus() { _update->setVisible(true); } _update->entity()->setClickedCallback([] { - checkReadyUpdate(); + Core::checkReadyUpdate(); App::restart(); }); } else { diff --git a/Telegram/SourceFiles/intro/introwidget.h b/Telegram/SourceFiles/intro/introwidget.h index 1aed671dae..b1d8ba35c3 100644 --- a/Telegram/SourceFiles/intro/introwidget.h +++ b/Telegram/SourceFiles/intro/introwidget.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "mtproto/sender.h" +#include "ui/rp_widget.h" namespace Ui { class IconButton; @@ -22,7 +23,7 @@ class FadeWrap; 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 public: diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index a726d73cb2..23e8149d87 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -74,6 +74,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/themes/window_theme.h" #include "mtproto/dc_options.h" #include "core/file_utilities.h" +#include "core/update_checker.h" #include "calls/calls_instance.h" #include "calls/calls_top_bar.h" #include "auth_session.h" @@ -323,7 +324,8 @@ MainWidget::MainWidget( orderWidgets(); #ifndef TDESKTOP_DISABLE_AUTOUPDATE - Sandbox::startUpdateCheck(); + Core::UpdateChecker checker; + checker.start(); #endif // !TDESKTOP_DISABLE_AUTOUPDATE } @@ -704,7 +706,7 @@ void MainWidget::finishForwarding(not_null history) { } void MainWidget::updateMutedIn(TimeMs delay) { - accumulate_max(delay, 24 * 3600 * 1000LL); + accumulate_min(delay, 24 * 3600 * 1000LL); if (!_updateMutedTimer.isActive() || _updateMutedTimer.remainingTime() > delay) { _updateMutedTimer.start(delay); diff --git a/Telegram/SourceFiles/settings/settings_general_widget.cpp b/Telegram/SourceFiles/settings/settings_general_widget.cpp index ccd1651b22..31df77273b 100644 --- a/Telegram/SourceFiles/settings/settings_general_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_general_widget.cpp @@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_file_parser.h" #include "lang/lang_cloud_manager.h" #include "messenger.h" -#include "autoupdater.h" +#include "core/update_checker.h" namespace Settings { @@ -34,21 +34,37 @@ UpdateStateRow::UpdateStateRow(QWidget *parent) : RpWidget(parent) connect(_check, SIGNAL(clicked()), this, SLOT(onCheck())); 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()); - switch (Sandbox::updatingState()) { - case Application::UpdatingDownload: + Core::UpdateChecker checker; + 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); - setDownloadProgress(Sandbox::updatingReady(), Sandbox::updatingSize()); - break; - case Application::UpdatingReady: setState(State::Ready, true); break; - default: setState(State::None, true); break; + setDownloadProgress(checker.already(), checker.size()); + break; + case Core::UpdateChecker::State::Ready: + setState(State::Ready, true); + break; + default: + setState(State::None, true); + break; } } @@ -89,9 +105,11 @@ void UpdateStateRow::paintEvent(QPaintEvent *e) { void UpdateStateRow::onCheck() { if (!cAutoUpdate()) return; + Core::UpdateChecker checker; + setState(State::Check); cSetLastUpdateCheck(0); - Sandbox::startUpdateCheck(); + checker.start(); } void UpdateStateRow::setState(State state, bool force) { @@ -212,7 +230,7 @@ void GeneralWidget::onChangeLanguage() { void GeneralWidget::onRestart() { #ifndef TDESKTOP_DISABLE_AUTOUPDATE - checkReadyUpdate(); + Core::checkReadyUpdate(); #endif // !TDESKTOP_DISABLE_AUTOUPDATE App::restart(); } @@ -224,10 +242,11 @@ void GeneralWidget::onUpdateAutomatically() { _updateRow->toggle( cAutoUpdate(), anim::type::normal); + Core::UpdateChecker checker; if (cAutoUpdate()) { - Sandbox::startUpdateCheck(); + checker.start(); } else { - Sandbox::stopUpdate(); + checker.stop(); } } #endif // !TDESKTOP_DISABLE_AUTOUPDATE diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 266d948c07..decb5bcc16 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/send_files_box.h" #include "window/themes/window_theme.h" #include "core/crash_reports.h" +#include "core/update_checker.h" #include "observer_peer.h" #include "mainwidget.h" #include "mainwindow.h" @@ -1251,7 +1252,7 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting cSetAutoUpdate(v == 1); #ifndef TDESKTOP_DISABLE_AUTOUPDATE if (!cAutoUpdate()) { - Sandbox::stopUpdate(); + Core::UpdateChecker().stop(); } #endif // !TDESKTOP_DISABLE_AUTOUPDATE } break; diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 7716c85510..53aa5562f9 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -158,6 +158,8 @@ <(src_loc)/core/single_timer.cpp <(src_loc)/core/single_timer.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.h <(src_loc)/core/version.h @@ -725,8 +727,6 @@ <(src_loc)/application.h <(src_loc)/auth_session.cpp <(src_loc)/auth_session.h -<(src_loc)/autoupdater.cpp -<(src_loc)/autoupdater.h <(src_loc)/config.h <(src_loc)/countries.h <(src_loc)/facades.cpp