From f48ae29f22d63c082a24ae95954ac1d1f60301f2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 26 Dec 2018 21:05:06 +0400 Subject: [PATCH] Load emoji sets from the cloud. --- Telegram/SourceFiles/base/zlib_help.h | 44 +- .../chat_helpers/emoji_sets_manager.cpp | 174 ++++- Telegram/SourceFiles/core/update_checker.cpp | 607 +----------------- Telegram/SourceFiles/core/update_checker.h | 7 +- .../mtproto/dedicated_file_loader.cpp | 461 +++++++++++++ .../mtproto/dedicated_file_loader.h | 195 ++++++ Telegram/SourceFiles/ui/emoji_config.cpp | 24 +- Telegram/SourceFiles/ui/emoji_config.h | 5 + Telegram/gyp/telegram_sources.txt | 2 + 9 files changed, 893 insertions(+), 626 deletions(-) create mode 100644 Telegram/SourceFiles/mtproto/dedicated_file_loader.cpp create mode 100644 Telegram/SourceFiles/mtproto/dedicated_file_loader.h diff --git a/Telegram/SourceFiles/base/zlib_help.h b/Telegram/SourceFiles/base/zlib_help.h index dfaffc621..66f6615f5 100644 --- a/Telegram/SourceFiles/base/zlib_help.h +++ b/Telegram/SourceFiles/base/zlib_help.h @@ -172,15 +172,28 @@ public: return error(); } + int goToFirstFile() { + if (error() == UNZ_OK) { + _error = _handle ? unzGoToFirstFile(_handle) : -1; + } + return error(); + } + + int goToNextFile() { + if (error() == UNZ_OK) { + _error = _handle ? unzGoToNextFile(_handle) : -1; + } + return error(); + } + int getCurrentFileInfo( - unz_file_info *pfile_info, - char *szFileName, - uLong fileNameBufferSize, - void *extraField, - uLong extraFieldBufferSize, - char *szComment, - uLong commentBufferSize - ) { + unz_file_info *pfile_info, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize) { if (error() == UNZ_OK) { _error = _handle ? unzGetCurrentFileInfo( _handle, @@ -196,6 +209,21 @@ public: return error(); } + QString getCurrentFileName() { + unz_file_info info = { 0 }; + constexpr auto kMaxName = 128; + char name[kMaxName + 1] = { 0 }; + const auto result = getCurrentFileInfo( + &info, + name, + kMaxName, + nullptr, + 0, + nullptr, + 0); + return (result == UNZ_OK) ? QString::fromUtf8(name) : QString(); + } + int openCurrentFile() { if (error() == UNZ_OK) { _error = _handle ? unzOpenCurrentFile(_handle) : -1; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp b/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp index 286c6f74a..18dafdd7b 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/emoji_sets_manager.h" +#include "mtproto/dedicated_file_loader.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/fade_wrap.h" #include "ui/widgets/buttons.h" @@ -14,7 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/radial_animation.h" #include "ui/emoji_config.h" #include "lang/lang_keys.h" +#include "base/zlib_help.h" #include "layout.h" +#include "messenger.h" +#include "mainwidget.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" @@ -48,18 +52,7 @@ struct Active { return true; } }; -struct Loading { - int offset = 0; - int size = 0; - - inline bool operator<(const Loading &other) const { - return (offset < other.offset) - || (offset == other.offset && size < other.size); - } - inline bool operator==(const Loading &other) const { - return (offset == other.offset) && (size == other.size); - } -}; +using Loading = MTP::DedicatedLoader::Progress; struct Failed { inline bool operator<(const Failed &other) const { return false; @@ -77,17 +70,28 @@ using SetState = base::variant< class Loader : public QObject { public: - explicit Loader(int id); + Loader(QObject *parent, int id); int id() const; rpl::producer state() const; private: + void setImplementation(std::unique_ptr loader); + void unpack(const QString &path); + void finalize(const QString &path); + bool goodName(const QString &name) const; + bool writeCurrentFile(zlib::FileToRead &zip, const QString name) const; + void destroy(); + void fail(); + int _id = 0; int _size = 0; rpl::variable _state; + MTP::WeakInstance _mtproto; + std::unique_ptr _implementation; + }; class Inner : public Ui::RpWidget { @@ -111,6 +115,7 @@ private: void setupCheck(); void setupLabels(const Set &set); void setupHandler(); + void load(); int _id = 0; rpl::variable _state; @@ -126,6 +131,13 @@ int GetDownloadSize(int id) { return ranges::find(sets, id, &Set::id)->size; } +MTP::DedicatedLoader::Location GetDownloadLocation(int id) { + constexpr auto kUsername = "tdhbcfiles"; + const auto sets = Sets(); + const auto i = ranges::find(sets, id, &Set::id); + return MTP::DedicatedLoader::Location{ kUsername, i->postId }; +} + SetState ComputeState(int id) { if (id == CurrentSetId()) { return Active(); @@ -145,16 +157,37 @@ QString StateDescription(const SetState &state) { }, [](const Loading &data) { return lng_emoji_set_loading( lt_progress, - formatDownloadText(data.offset, data.size)); + formatDownloadText(data.already, data.size)); }, [](const Failed &data) { return lang(lng_attach_failed); }); } -Loader::Loader(int id) -: _id(id) +QByteArray ReadFinalFile(const QString &path) { + constexpr auto kMaxZipSize = 10 * 1024 * 1024; + auto file = QFile(path); + if (file.size() > kMaxZipSize || !file.open(QIODevice::ReadOnly)) { + return QByteArray(); + } + return file.readAll(); +} + +Loader::Loader(QObject *parent, int id) +: QObject(parent) +, _id(id) , _size(GetDownloadSize(_id)) -, _state(Loading{ 0, _size }) { +, _state(Loading{ 0, _size }) +, _mtproto(Messenger::Instance().mtp()) { + const auto ready = [=](std::unique_ptr loader) { + if (loader) { + setImplementation(std::move(loader)); + } else { + fail(); + } + }; + const auto location = GetDownloadLocation(id); + const auto folder = internal::SetDataPath(id); + MTP::StartDedicatedLoader(&_mtproto, location, folder, ready); } int Loader::id() const { @@ -165,6 +198,95 @@ rpl::producer Loader::state() const { return _state.value(); } +void Loader::setImplementation( + std::unique_ptr loader) { + _implementation = std::move(loader); + auto convert = [](auto value) { + return SetState(value); + }; + _state = _implementation->progress( + ) | rpl::map([](const Loading &state) { + return SetState(state); + }); + _implementation->failed( + ) | rpl::start_with_next([=] { + fail(); + }, _implementation->lifetime()); + + _implementation->ready( + ) | rpl::start_with_next([=](const QString &filepath) { + unpack(filepath); + }, _implementation->lifetime()); + + QDir(internal::SetDataPath(_id)).removeRecursively(); + _implementation->start(); +} + +void Loader::unpack(const QString &path) { + const auto bytes = ReadFinalFile(path); + if (bytes.isEmpty()) { + return fail(); + } + + auto zip = zlib::FileToRead(bytes); + if (zip.goToFirstFile() != UNZ_OK) { + return fail(); + } + do { + const auto name = zip.getCurrentFileName(); + if (goodName(name) && !writeCurrentFile(zip, name)) { + return fail(); + } + + const auto jump = zip.goToNextFile(); + if (jump == UNZ_END_OF_LIST_OF_FILE) { + break; + } else if (jump != UNZ_OK) { + return fail(); + } + } while (true); + + finalize(path); +} + +bool Loader::goodName(const QString &name) const { + return (name == qstr("config.json")) + || (name.startsWith(qstr("emoji_")) + && name.endsWith(qstr(".webp"))); +} + +void Loader::finalize(const QString &path) { + QFile(path).remove(); + if (!SwitchToSet(_id)) { + fail(); + } else { + destroy(); + } +} + +void Loader::fail() { + _state = Failed(); +} + +void Loader::destroy() { + GlobalLoaderValues.fire(nullptr); + delete this; +} + +bool Loader::writeCurrentFile( + zlib::FileToRead &zip, + const QString name) const { + constexpr auto kMaxSize = 10 * 1024 * 1024; + const auto content = zip.readCurrentFileContent(kMaxSize); + if (content.isEmpty() || zip.error() != UNZ_OK) { + return false; + } + const auto folder = internal::SetDataPath(_id) + '/'; + auto file = QFile(folder + name); + return file.open(QIODevice::WriteOnly) + && (file.write(content) == content.size()); +} + Inner::Inner(QWidget *parent) : RpWidget(parent) { setupContent(); } @@ -233,9 +355,14 @@ void Row::setupHandler() { const auto &state = _state.current(); return state.is() || state.is(); }) | rpl::start_with_next([=] { - App::CallDelayed(st::defaultRippleAnimation.hideDuration, this, [=] { + if (_state.current().is()) { + load(); + return; + } + const auto delay = st::defaultRippleAnimation.hideDuration; + App::CallDelayed(delay, this, [=] { if (!SwitchToSet(_id)) { - // load + load(); } else { delete GlobalLoader; } @@ -243,7 +370,7 @@ void Row::setupHandler() { }, lifetime()); _state.value( - ) | rpl::map([](const SetState &state) { + ) | rpl::map([=](const SetState &state) { return state.is() || state.is(); }) | rpl::start_with_next([=](bool active) { setDisabled(!active); @@ -251,6 +378,11 @@ void Row::setupHandler() { }, lifetime()); } +void Row::load() { + GlobalLoader = Ui::CreateChild(App::main(), _id); + GlobalLoaderValues.fire(GlobalLoader.data()); +} + void Row::setupCheck() { using namespace rpl::mappers; @@ -286,10 +418,12 @@ void Row::setupLabels(const Set &set) { set.name, Ui::FlatLabel::InitType::Simple, st::localStorageRowTitle); + name->setAttribute(Qt::WA_TransparentForMouseEvents); const auto status = Ui::CreateChild( this, _state.value() | rpl::map(StateDescription), st::localStorageRowSize); + status->setAttribute(Qt::WA_TransparentForMouseEvents); sizeValue( ) | rpl::start_with_next([=](QSize size) { diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp index 0c71c39e2..34b6f5ae1 100644 --- a/Telegram/SourceFiles/core/update_checker.cpp +++ b/Telegram/SourceFiles/core/update_checker.cpp @@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/bytes.h" #include "storage/localstorage.h" #include "messenger.h" -#include "mtproto/session.h" #include "mainwindow.h" #include "core/click_handler_types.h" #include "info/info_memento.h" @@ -39,8 +38,6 @@ namespace { constexpr auto kUpdaterTimeout = 10 * TimeMs(1000); constexpr auto kMaxResponseSize = 1024 * 1024; -constexpr auto kMaxUpdateSize = 256 * 1024 * 1024; -constexpr auto kChunkSize = 128 * 1024; #ifdef TDESKTOP_DISABLE_AUTOUPDATE bool UpdaterIsDisabled = true; @@ -64,52 +61,7 @@ using VersionInt = int; using VersionChar = wchar_t; #endif // Q_OS_WIN -class Loader : public base::has_weak_ptr { -public: - Loader(const QString &filename, int chunkSize); - - void start(); - - int alreadySize() const; - int totalSize() const; - - rpl::producer progress() const; - rpl::producer ready() const; - rpl::producer<> failed() const; - - rpl::lifetime &lifetime(); - - virtual ~Loader() = default; - -protected: - bool startOutput(); - void threadSafeFailed(); - - // Single threaded. - void writeChunk(bytes::const_span data, int totalSize); - -private: - virtual void startLoading() = 0; - - bool validateOutput(); - void threadSafeProgress(Progress progress); - void threadSafeReady(); - - QString _filename; - QString _filepath; - int _chunkSize = 0; - - QFile _output; - int _alreadySize = 0; - int _totalSize = 0; - mutable QMutex _sizesMutex; - rpl::event_stream _progress; - rpl::event_stream _ready; - rpl::event_stream<> _failed; - - rpl::lifetime _lifetime; - -}; +using Loader = MTP::AbstractDedicatedLoader; class Checker : public base::has_weak_ptr { public: @@ -212,31 +164,6 @@ private: }; -class MtpWeak : private QObject, private base::Subscriber { -public: - MtpWeak(QPointer instance); - - template - void send( - const T &request, - Fn done, - Fn fail, - MTP::ShiftedDcId dcId = 0); - - bool valid() const; - QPointer instance() const; - - ~MtpWeak(); - -private: - void die(); - bool removeRequest(mtpRequestId requestId); - - QPointer _instance; - std::map> _requests; - -}; - class MtpChecker : public Checker { public: MtpChecker(QPointer instance, bool testing); @@ -244,23 +171,11 @@ public: void start() override; private: - struct FileLocation { - QString username; - int32 postId = 0; - }; - struct ParsedFile { - QString name; - int32 size = 0; - MTP::DcId dcId = 0; - MTPInputFileLocation location; - }; + using FileLocation = MTP::DedicatedLoader::Location; using Checker::fail; Fn failHandler(); - void resolveChannel( - const QString &username, - Fn callback); void gotMessage(const MTPmessages_Messages &result); std::optional parseMessage( const MTPmessages_Messages &result) const; @@ -268,42 +183,8 @@ private: FileLocation validateLatestLocation( uint64 availableVersion, const FileLocation &location) const; - void gotFile(const MTPmessages_Messages &result); - std::optional parseFile( - const MTPmessages_Messages &result) const; - MtpWeak _mtp; - -}; - -class MtpLoader : public Loader { -public: - MtpLoader( - QPointer instance, - const QString &name, - int32 size, - MTP::DcId dcId, - const MTPInputFileLocation &location); - -private: - struct Request { - int offset = 0; - QByteArray bytes; - }; - void startLoading() override; - void sendRequest(); - void gotPart(int offset, const MTPupload_File &result); - Fn failHandler(); - - static constexpr auto kRequestsCount = 2; - static constexpr auto kNextRequestDelay = TimeMs(20); - - std::deque _requests; - int32 _size = 0; - int _offset = 0; - MTP::DcId _dcId = 0; - MTPInputFileLocation _location; - MtpWeak _mtp; + MTP::WeakInstance _mtp; }; @@ -316,12 +197,16 @@ std::shared_ptr GetUpdaterInstance() { return result; } +QString UpdatesFolder() { + return cWorkingDir() + qsl("tupdates"); +} + void ClearAll() { - psDeleteDir(cWorkingDir() + qsl("tupdates")); + psDeleteDir(UpdatesFolder()); } QString FindUpdateFile() { - QDir updates(cWorkingDir() + "tupdates"); + QDir updates(UpdatesFolder()); if (!updates.exists()) { return QString(); } @@ -594,24 +479,6 @@ bool UnpackUpdate(const QString &filepath) { return true; } -std::optional ExtractChannel( - const MTPcontacts_ResolvedPeer &result) { - const auto &data = result.c_contacts_resolvedPeer(); - if (const auto peer = peerFromMTP(data.vpeer)) { - for (const auto &chat : data.vchats.v) { - if (chat.type() == mtpc_channel) { - const auto &channel = chat.c_channel(); - if (peer == peerFromChannel(channel.vid)) { - return MTP_inputChannel( - channel.vid, - channel.vaccess_hash); - } - } - } - } - return std::nullopt; -} - template bool ParseCommonMap( const QByteArray &json, @@ -714,156 +581,6 @@ bool ParseCommonMap( return true; } -std::optional GetMessagesElement( - const MTPmessages_Messages &list) { - const auto get = [](auto &&data) -> std::optional { - return data.vmessages.v.isEmpty() - ? std::nullopt - : base::make_optional(data.vmessages.v[0]); - }; - switch (list.type()) { - case mtpc_messages_messages: - return get(list.c_messages_messages()); - case mtpc_messages_messagesSlice: - return get(list.c_messages_messagesSlice()); - case mtpc_messages_channelMessages: - return get(list.c_messages_channelMessages()); - case mtpc_messages_messagesNotModified: - return std::nullopt; - default: Unexpected("Type of messages.Messages (GetMessagesElement)"); - } -} - -Loader::Loader(const QString &filename, int chunkSize) -: _filename(filename) -, _chunkSize(chunkSize) { -} - -void Loader::start() { - if (!validateOutput() - || (!_output.isOpen() && !_output.open(QIODevice::Append))) { - QFile(_filepath).remove(); - threadSafeFailed(); - return; - } - - LOG(("Update Info: Starting loading '%1' from %2 offset." - ).arg(_filename - ).arg(alreadySize())); - startLoading(); -} - -int Loader::alreadySize() const { - QMutexLocker lock(&_sizesMutex); - return _alreadySize; -} - -int Loader::totalSize() const { - QMutexLocker lock(&_sizesMutex); - return _totalSize; -} - -rpl::producer Loader::ready() const { - return _ready.events(); -} - -rpl::producer Loader::progress() const { - return _progress.events(); -} - -rpl::producer<> Loader::failed() const { - return _failed.events(); -} - -bool Loader::validateOutput() { - if (_filename.isEmpty()) { - return false; - } - const auto folder = cWorkingDir() + qsl("tupdates/"); - _filepath = folder + _filename; - - QFileInfo info(_filepath); - QDir dir(folder); - if (dir.exists()) { - 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()); - } - _output.setFileName(_filepath); - - if (!info.exists()) { - return true; - } - const auto fullSize = info.size(); - if (fullSize < _chunkSize || fullSize > kMaxUpdateSize) { - return _output.remove(); - } - const auto goodSize = int((fullSize % _chunkSize) - ? (fullSize - (fullSize % _chunkSize)) - : fullSize); - if (_output.resize(goodSize)) { - _alreadySize = goodSize; - return true; - } - return false; -} - -void Loader::threadSafeProgress(Progress progress) { - crl::on_main(this, [=] { - _progress.fire_copy(progress); - }); -} - -void Loader::threadSafeReady() { - crl::on_main(this, [=] { - _ready.fire_copy(_filepath); - }); -} - -void Loader::threadSafeFailed() { - crl::on_main(this, [=] { - _failed.fire({}); - }); -} - -void Loader::writeChunk(bytes::const_span data, int totalSize) { - const auto size = data.size(); - if (size > 0) { - const auto written = _output.write(QByteArray::fromRawData( - reinterpret_cast(data.data()), - size)); - if (written != size) { - threadSafeFailed(); - return; - } - } - - const auto progress = [&] { - QMutexLocker lock(&_sizesMutex); - if (!_totalSize) { - _totalSize = totalSize; - } - _alreadySize += size; - return Progress { _alreadySize, _totalSize }; - }(); - - if (progress.size > 0 && progress.already >= progress.size) { - _output.close(); - threadSafeReady(); - } else { - threadSafeProgress(progress); - } -} - -rpl::lifetime &Loader::lifetime() { - return _lifetime; -} - Checker::Checker(bool testing) : _testing(testing) { } @@ -1037,7 +754,7 @@ HttpChecker::~HttpChecker() { } HttpLoader::HttpLoader(const QString &url) -: Loader(ExtractFilename(url), kChunkSize) +: Loader(UpdatesFolder() + '/' + ExtractFilename(url), kChunkSize) , _url(url) { } @@ -1159,96 +876,6 @@ void HttpLoaderActor::partFailed(QNetworkReply::NetworkError e) { _parent->threadSafeFailed(); } -MtpWeak::MtpWeak(QPointer instance) -: _instance(instance) { - if (!valid()) { - return; - } - - connect(_instance, &QObject::destroyed, this, [=] { - _instance = nullptr; - die(); - }); - subscribe(Messenger::Instance().authSessionChanged(), [=] { - if (!AuthSession::Exists()) { - die(); - } - }); -} - -bool MtpWeak::valid() const { - return (_instance != nullptr) && AuthSession::Exists(); -} - -QPointer MtpWeak::instance() const { - return _instance; -} - -void MtpWeak::die() { - const auto instance = _instance.data(); - for (const auto &[requestId, fail] : base::take(_requests)) { - if (instance) { - instance->cancel(requestId); - } - fail(MTP::internal::rpcClientError("UNAVAILABLE")); - } -} - -template -void MtpWeak::send( - const T &request, - Fn done, - Fn fail, - MTP::ShiftedDcId dcId) { - using Response = typename T::ResponseType; - if (!valid()) { - InvokeQueued(this, [=] { - fail(MTP::internal::rpcClientError("UNAVAILABLE")); - }); - return; - } - const auto onDone = crl::guard((QObject*)this, [=]( - const Response &result, - mtpRequestId requestId) { - if (removeRequest(requestId)) { - done(result); - } - }); - const auto onFail = crl::guard((QObject*)this, [=]( - const RPCError &error, - mtpRequestId requestId) { - if (MTP::isDefaultHandledError(error)) { - return false; - } - if (removeRequest(requestId)) { - fail(error); - } - return true; - }); - const auto requestId = _instance->send( - request, - rpcDone(onDone), - rpcFail(onFail), - dcId); - _requests.emplace(requestId, fail); -} - -bool MtpWeak::removeRequest(mtpRequestId requestId) { - if (const auto i = _requests.find(requestId); i != end(_requests)) { - _requests.erase(i); - return true; - } - return false; -} - -MtpWeak::~MtpWeak() { - if (const auto instance = _instance.data()) { - for (const auto &[requestId, fail] : base::take(_requests)) { - instance->cancel(requestId); - } - } -} - MtpChecker::MtpChecker(QPointer instance, bool testing) : Checker(testing) , _mtp(instance) { @@ -1260,8 +887,8 @@ void MtpChecker::start() { crl::on_main(this, [=] { fail(); }); return; } - constexpr auto kFeedUsername = "tdhbcfeed"; - resolveChannel(kFeedUsername, [=](const MTPInputChannel &channel) { + constexpr auto kFeed = "tdhbcfeed"; + MTP::ResolveChannel(&_mtp, kFeed, [=](const MTPInputChannel &channel) { _mtp.send( MTPmessages_GetHistory( MTP_inputPeerChannel( @@ -1276,53 +903,7 @@ void MtpChecker::start() { MTP_int(0)), // hash [=](const MTPmessages_Messages &result) { gotMessage(result); }, failHandler()); - }); -} - -void MtpChecker::resolveChannel( - const QString &username, - Fn callback) { - const auto failed = [&] { - LOG(("Update Error: MTP channel '%1' resolve failed." - ).arg(username)); - fail(); - }; - if (!AuthSession::Exists()) { - failed(); - return; - } - - struct ResolveResult { - base::weak_ptr auth; - MTPInputChannel channel; - }; - static std::map ResolveCache; - - const auto i = ResolveCache.find(username); - if (i != end(ResolveCache)) { - if (i->second.auth.get() == &Auth()) { - callback(i->second.channel); - return; - } - ResolveCache.erase(i); - } - - const auto doneHandler = [=](const MTPcontacts_ResolvedPeer &result) { - Expects(result.type() == mtpc_contacts_resolvedPeer); - - if (const auto channel = ExtractChannel(result)) { - ResolveCache.emplace( - username, - ResolveResult { base::make_weak(&Auth()), *channel }); - callback(*channel); - } else { - failed(); - } - }; - _mtp.send( - MTPcontacts_ResolveUsername(MTP_string(username)), - doneHandler, - failHandler()); + }, [=] { fail(); }); } void MtpChecker::gotMessage(const MTPmessages_Messages &result) { @@ -1334,21 +915,19 @@ void MtpChecker::gotMessage(const MTPmessages_Messages &result) { done(nullptr); return; } - resolveChannel(location->username, [=](const MTPInputChannel &channel) { - _mtp.send( - MTPchannels_GetMessages( - channel, - MTP_vector( - 1, - MTP_inputMessageID(MTP_int(location->postId)))), - [=](const MTPmessages_Messages &result) { gotFile(result); }, - failHandler()); - }); + const auto ready = [=](std::unique_ptr loader) { + if (loader) { + done(std::move(loader)); + } else { + fail(); + } + }; + MTP::StartDedicatedLoader(&_mtp, *location, UpdatesFolder(), ready); } auto MtpChecker::parseMessage(const MTPmessages_Messages &result) const -> std::optional { - const auto message = GetMessagesElement(result); + const auto message = MTP::GetMessagesElement(result); if (!message || message->type() != mtpc_message) { LOG(("Update Error: MTP feed message not found.")); return std::nullopt; @@ -1413,64 +992,6 @@ auto MtpChecker::validateLatestLocation( return (availableVersion <= myVersion) ? FileLocation() : location; } -void MtpChecker::gotFile(const MTPmessages_Messages &result) { - if (const auto file = parseFile(result)) { - done(std::make_shared( - _mtp.instance(), - file->name, - file->size, - file->dcId, - file->location)); - } else { - fail(); - } -} - -auto MtpChecker::parseFile(const MTPmessages_Messages &result) const --> std::optional { - const auto message = GetMessagesElement(result); - if (!message || message->type() != mtpc_message) { - LOG(("Update Error: MTP file message not found.")); - return std::nullopt; - } - const auto &data = message->c_message(); - if (!data.has_media() - || data.vmedia.type() != mtpc_messageMediaDocument) { - LOG(("Update Error: MTP file media not found.")); - return std::nullopt; - } - const auto &document = data.vmedia.c_messageMediaDocument(); - if (!document.has_document() - || document.vdocument.type() != mtpc_document) { - LOG(("Update Error: MTP file not found.")); - return std::nullopt; - } - const auto &fields = document.vdocument.c_document(); - const auto name = [&] { - for (const auto &attribute : fields.vattributes.v) { - if (attribute.type() == mtpc_documentAttributeFilename) { - const auto &data = attribute.c_documentAttributeFilename(); - return qs(data.vfile_name); - } - } - return QString(); - }(); - if (name.isEmpty()) { - LOG(("Update Error: MTP file name not found.")); - return std::nullopt; - } - const auto size = fields.vsize.v; - if (size <= 0) { - LOG(("Update Error: MTP file size is invalid.")); - return std::nullopt; - } - const auto location = MTP_inputDocumentFileLocation( - fields.vid, - fields.vaccess_hash, - fields.vfile_reference); - return ParsedFile { name, size, fields.vdc_id.v, location }; -} - Fn MtpChecker::failHandler() { return [=](const RPCError &error) { LOG(("Update Error: MTP check failed with '%1'" @@ -1479,88 +1000,6 @@ Fn MtpChecker::failHandler() { }; } -MtpLoader::MtpLoader( - QPointer instance, - const QString &name, - int32 size, - MTP::DcId dcId, - const MTPInputFileLocation &location) -: Loader(name, kChunkSize) -, _size(size) -, _dcId(dcId) -, _location(location) -, _mtp(instance) { - Expects(_size > 0); -} - -void MtpLoader::startLoading() { - if (!_mtp.valid()) { - LOG(("Update Error: MTP is unavailable.")); - threadSafeFailed(); - return; - } - - LOG(("Update Info: Loading using MTP from '%1'.").arg(_dcId)); - _offset = alreadySize(); - writeChunk({}, _size); - sendRequest(); -} - -void MtpLoader::sendRequest() { - if (_requests.size() >= kRequestsCount || _offset >= _size) { - return; - } - const auto offset = _offset; - _requests.push_back({ offset }); - _mtp.send( - MTPupload_GetFile(_location, MTP_int(offset), MTP_int(kChunkSize)), - [=](const MTPupload_File &result) { gotPart(offset, result); }, - failHandler(), - MTP::updaterDcId(_dcId)); - _offset += kChunkSize; - - if (_requests.size() < kRequestsCount) { - App::CallDelayed(kNextRequestDelay, this, [=] { sendRequest(); }); - } -} - -void MtpLoader::gotPart(int offset, const MTPupload_File &result) { - Expects(!_requests.empty()); - - if (result.type() == mtpc_upload_fileCdnRedirect) { - LOG(("Update Error: MTP does not support cdn right now.")); - threadSafeFailed(); - return; - } - const auto &data = result.c_upload_file(); - if (data.vbytes.v.isEmpty()) { - LOG(("Update Error: MTP empty part received.")); - threadSafeFailed(); - return; - } - - const auto i = ranges::find( - _requests, - offset, - [](const Request &request) { return request.offset; }); - Assert(i != end(_requests)); - - i->bytes = data.vbytes.v; - while (!_requests.empty() && !_requests.front().bytes.isEmpty()) { - writeChunk(bytes::make_span(_requests.front().bytes), _size); - _requests.pop_front(); - } - sendRequest(); -} - -Fn MtpLoader::failHandler() { - return [=](const RPCError &error) { - LOG(("Update Error: MTP load failed with '%1'" - ).arg(QString::number(error.code()) + ':' + error.type())); - threadSafeFailed(); - }; -} - } // namespace bool UpdaterDisabled() { @@ -1847,7 +1286,6 @@ void Updater::test() { void Updater::setMtproto(const QPointer &mtproto) { _mtproto = mtproto; - } void Updater::handleTimeout() { @@ -1892,6 +1330,7 @@ bool Updater::tryLoaders() { }, loader->lifetime()); _retryTimer.callOnce(kUpdaterTimeout); + loader->wipeFolder(); loader->start(); } else { _isLatest.fire({}); diff --git a/Telegram/SourceFiles/core/update_checker.h b/Telegram/SourceFiles/core/update_checker.h index 0dd882311..4d137c881 100644 --- a/Telegram/SourceFiles/core/update_checker.h +++ b/Telegram/SourceFiles/core/update_checker.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "mtproto/dedicated_file_loader.h" + namespace MTP { class Instance; } // namespace MTP @@ -25,10 +27,7 @@ public: Download, Ready, }; - struct Progress { - int64 already; - int64 size; - }; + using Progress = MTP::AbstractDedicatedLoader::Progress; UpdateChecker(); diff --git a/Telegram/SourceFiles/mtproto/dedicated_file_loader.cpp b/Telegram/SourceFiles/mtproto/dedicated_file_loader.cpp new file mode 100644 index 000000000..752698250 --- /dev/null +++ b/Telegram/SourceFiles/mtproto/dedicated_file_loader.cpp @@ -0,0 +1,461 @@ +/* +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 +*/ +#include "mtproto/dedicated_file_loader.h" + +#include "mtproto/session.h" +#include "auth_session.h" +#include "messenger.h" + +namespace MTP { +namespace { + +std::optional ExtractChannel( + const MTPcontacts_ResolvedPeer &result) { + const auto &data = result.c_contacts_resolvedPeer(); + if (const auto peer = peerFromMTP(data.vpeer)) { + for (const auto &chat : data.vchats.v) { + if (chat.type() == mtpc_channel) { + const auto &channel = chat.c_channel(); + if (peer == peerFromChannel(channel.vid)) { + return MTP_inputChannel( + channel.vid, + channel.vaccess_hash); + } + } + } + } + return std::nullopt; +} + +std::optional ParseFile( + const MTPmessages_Messages &result) { + const auto message = GetMessagesElement(result); + if (!message || message->type() != mtpc_message) { + LOG(("Update Error: MTP file message not found.")); + return std::nullopt; + } + const auto &data = message->c_message(); + if (!data.has_media() + || data.vmedia.type() != mtpc_messageMediaDocument) { + LOG(("Update Error: MTP file media not found.")); + return std::nullopt; + } + const auto &document = data.vmedia.c_messageMediaDocument(); + if (!document.has_document() + || document.vdocument.type() != mtpc_document) { + LOG(("Update Error: MTP file not found.")); + return std::nullopt; + } + const auto &fields = document.vdocument.c_document(); + const auto name = [&] { + for (const auto &attribute : fields.vattributes.v) { + if (attribute.type() == mtpc_documentAttributeFilename) { + const auto &data = attribute.c_documentAttributeFilename(); + return qs(data.vfile_name); + } + } + return QString(); + }(); + if (name.isEmpty()) { + LOG(("Update Error: MTP file name not found.")); + return std::nullopt; + } + const auto size = fields.vsize.v; + if (size <= 0) { + LOG(("Update Error: MTP file size is invalid.")); + return std::nullopt; + } + const auto location = MTP_inputDocumentFileLocation( + fields.vid, + fields.vaccess_hash, + fields.vfile_reference); + return DedicatedLoader::File{ name, size, fields.vdc_id.v, location }; +} + +} // namespace + +WeakInstance::WeakInstance(QPointer instance) +: _instance(instance) { + if (!valid()) { + return; + } + + connect(_instance, &QObject::destroyed, this, [=] { + _instance = nullptr; + die(); + }); + subscribe(Messenger::Instance().authSessionChanged(), [=] { + if (!AuthSession::Exists()) { + die(); + } + }); +} + +bool WeakInstance::valid() const { + return (_instance != nullptr) && AuthSession::Exists(); +} + +QPointer WeakInstance::instance() const { + return _instance; +} + +void WeakInstance::die() { + const auto instance = _instance.data(); + for (const auto &[requestId, fail] : base::take(_requests)) { + if (instance) { + instance->cancel(requestId); + } + fail(MTP::internal::rpcClientError("UNAVAILABLE")); + } +} + +bool WeakInstance::removeRequest(mtpRequestId requestId) { + if (const auto i = _requests.find(requestId); i != end(_requests)) { + _requests.erase(i); + return true; + } + return false; +} + +void WeakInstance::reportUnavailable( + Fn callback) { + InvokeQueued(this, [=] { + callback(MTP::internal::rpcClientError("UNAVAILABLE")); + }); +} + +WeakInstance::~WeakInstance() { + if (const auto instance = _instance.data()) { + for (const auto &[requestId, fail] : base::take(_requests)) { + instance->cancel(requestId); + } + } +} + +AbstractDedicatedLoader::AbstractDedicatedLoader( + const QString &filepath, + int chunkSize) +: _filepath(filepath) +, _chunkSize(chunkSize) { +} + +void AbstractDedicatedLoader::start() { + if (!validateOutput() + || (!_output.isOpen() && !_output.open(QIODevice::Append))) { + QFile(_filepath).remove(); + threadSafeFailed(); + return; + } + + LOG(("Update Info: Starting loading '%1' from %2 offset." + ).arg(_filepath + ).arg(alreadySize())); + startLoading(); +} + +int AbstractDedicatedLoader::alreadySize() const { + QMutexLocker lock(&_sizesMutex); + return _alreadySize; +} + +int AbstractDedicatedLoader::totalSize() const { + QMutexLocker lock(&_sizesMutex); + return _totalSize; +} + +rpl::producer AbstractDedicatedLoader::ready() const { + return _ready.events(); +} + +auto AbstractDedicatedLoader::progress() const -> rpl::producer { + return _progress.events(); +} + +rpl::producer<> AbstractDedicatedLoader::failed() const { + return _failed.events(); +} + +void AbstractDedicatedLoader::wipeFolder() { + QFileInfo info(_filepath); + const auto dir = info.dir(); + 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()); + } + } +} + +bool AbstractDedicatedLoader::validateOutput() { + if (_filepath.isEmpty()) { + return false; + } + + QFileInfo info(_filepath); + const auto dir = info.dir(); + if (!dir.exists()) { + dir.mkdir(dir.absolutePath()); + } + _output.setFileName(_filepath); + + if (!info.exists()) { + return true; + } + const auto fullSize = info.size(); + if (fullSize < _chunkSize || fullSize > kMaxFileSize) { + return _output.remove(); + } + const auto goodSize = int((fullSize % _chunkSize) + ? (fullSize - (fullSize % _chunkSize)) + : fullSize); + if (_output.resize(goodSize)) { + _alreadySize = goodSize; + return true; + } + return false; +} + +void AbstractDedicatedLoader::threadSafeProgress(Progress progress) { + crl::on_main(this, [=] { + _progress.fire_copy(progress); + }); +} + +void AbstractDedicatedLoader::threadSafeReady() { + crl::on_main(this, [=] { + _ready.fire_copy(_filepath); + }); +} + +void AbstractDedicatedLoader::threadSafeFailed() { + crl::on_main(this, [=] { + _failed.fire({}); + }); +} + +void AbstractDedicatedLoader::writeChunk(bytes::const_span data, int totalSize) { + const auto size = data.size(); + if (size > 0) { + const auto written = _output.write(QByteArray::fromRawData( + reinterpret_cast(data.data()), + size)); + if (written != size) { + threadSafeFailed(); + return; + } + } + + const auto progress = [&] { + QMutexLocker lock(&_sizesMutex); + if (!_totalSize) { + _totalSize = totalSize; + } + _alreadySize += size; + return Progress { _alreadySize, _totalSize }; + }(); + + if (progress.size > 0 && progress.already >= progress.size) { + _output.close(); + threadSafeReady(); + } else { + threadSafeProgress(progress); + } +} + +rpl::lifetime &AbstractDedicatedLoader::lifetime() { + return _lifetime; +} + +DedicatedLoader::DedicatedLoader( + QPointer instance, + const QString &folder, + const File &file) +: AbstractDedicatedLoader(folder + '/' + file.name, kChunkSize) +, _size(file.size) +, _dcId(file.dcId) +, _location(file.location) +, _mtp(instance) { + Expects(_size > 0); +} + +void DedicatedLoader::startLoading() { + if (!_mtp.valid()) { + LOG(("Update Error: MTP is unavailable.")); + threadSafeFailed(); + return; + } + + LOG(("Update Info: Loading using MTP from '%1'.").arg(_dcId)); + _offset = alreadySize(); + writeChunk({}, _size); + sendRequest(); +} + +void DedicatedLoader::sendRequest() { + if (_requests.size() >= kRequestsCount || _offset >= _size) { + return; + } + const auto offset = _offset; + _requests.push_back({ offset }); + _mtp.send( + MTPupload_GetFile(_location, MTP_int(offset), MTP_int(kChunkSize)), + [=](const MTPupload_File &result) { gotPart(offset, result); }, + failHandler(), + MTP::updaterDcId(_dcId)); + _offset += kChunkSize; + + if (_requests.size() < kRequestsCount) { + App::CallDelayed(kNextRequestDelay, this, [=] { sendRequest(); }); + } +} + +void DedicatedLoader::gotPart(int offset, const MTPupload_File &result) { + Expects(!_requests.empty()); + + if (result.type() == mtpc_upload_fileCdnRedirect) { + LOG(("Update Error: MTP does not support cdn right now.")); + threadSafeFailed(); + return; + } + const auto &data = result.c_upload_file(); + if (data.vbytes.v.isEmpty()) { + LOG(("Update Error: MTP empty part received.")); + threadSafeFailed(); + return; + } + + const auto i = ranges::find( + _requests, + offset, + [](const Request &request) { return request.offset; }); + Assert(i != end(_requests)); + + i->bytes = data.vbytes.v; + while (!_requests.empty() && !_requests.front().bytes.isEmpty()) { + writeChunk(bytes::make_span(_requests.front().bytes), _size); + _requests.pop_front(); + } + sendRequest(); +} + +Fn DedicatedLoader::failHandler() { + return [=](const RPCError &error) { + LOG(("Update Error: MTP load failed with '%1'" + ).arg(QString::number(error.code()) + ':' + error.type())); + threadSafeFailed(); + }; +} + +void ResolveChannel( + not_null mtp, + const QString &username, + Fn done, + Fn fail) { + const auto failed = [&] { + LOG(("Dedicated MTP Error: Channel '%1' resolve failed." + ).arg(username)); + fail(); + }; + if (!AuthSession::Exists()) { + failed(); + return; + } + + struct ResolveResult { + base::weak_ptr auth; + MTPInputChannel channel; + }; + static std::map ResolveCache; + + const auto i = ResolveCache.find(username); + if (i != end(ResolveCache)) { + if (i->second.auth.get() == &Auth()) { + done(i->second.channel); + return; + } + ResolveCache.erase(i); + } + + const auto doneHandler = [=](const MTPcontacts_ResolvedPeer &result) { + Expects(result.type() == mtpc_contacts_resolvedPeer); + + if (const auto channel = ExtractChannel(result)) { + ResolveCache.emplace( + username, + ResolveResult { base::make_weak(&Auth()), *channel }); + done(*channel); + } else { + failed(); + } + }; + const auto failHandler = [=](const RPCError &error) { + LOG(("Dedicated MTP Error: Resolve failed with '%1'" + ).arg(QString::number(error.code()) + ':' + error.type())); + fail(); + }; + mtp->send( + MTPcontacts_ResolveUsername(MTP_string(username)), + doneHandler, + failHandler); +} + +std::optional GetMessagesElement( + const MTPmessages_Messages &list) { + const auto get = [](auto &&data) -> std::optional { + return data.vmessages.v.isEmpty() + ? std::nullopt + : base::make_optional(data.vmessages.v[0]); + }; + switch (list.type()) { + case mtpc_messages_messages: + return get(list.c_messages_messages()); + case mtpc_messages_messagesSlice: + return get(list.c_messages_messagesSlice()); + case mtpc_messages_channelMessages: + return get(list.c_messages_channelMessages()); + case mtpc_messages_messagesNotModified: + return std::nullopt; + default: Unexpected("Type of messages.Messages (GetMessagesElement)"); + } +} + +void StartDedicatedLoader( + not_null mtp, + const DedicatedLoader::Location &location, + const QString &folder, + Fn)> ready) { + const auto doneHandler = [=](const MTPmessages_Messages &result) { + const auto file = ParseFile(result); + ready(file + ? std::make_unique( + mtp->instance(), + folder, + *file) + : nullptr); + }; + const auto failHandler = [=](const RPCError &error) { + LOG(("Update Error: MTP check failed with '%1'" + ).arg(QString::number(error.code()) + ':' + error.type())); + ready(nullptr); + }; + + const auto [username, postId] = location; + ResolveChannel(mtp, username, [=, postId = postId]( + const MTPInputChannel &channel) { + mtp->send( + MTPchannels_GetMessages( + channel, + MTP_vector( + 1, + MTP_inputMessageID(MTP_int(postId)))), + doneHandler, + failHandler); + }, [=] { ready(nullptr); }); +} + +} // namespace MTP diff --git a/Telegram/SourceFiles/mtproto/dedicated_file_loader.h b/Telegram/SourceFiles/mtproto/dedicated_file_loader.h new file mode 100644 index 000000000..1855aa5aa --- /dev/null +++ b/Telegram/SourceFiles/mtproto/dedicated_file_loader.h @@ -0,0 +1,195 @@ +/* +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 MTP { + +class Instance; + +class WeakInstance : private QObject, private base::Subscriber { +public: + WeakInstance(QPointer instance); + + template + void send( + const T &request, + Fn done, + Fn fail, + ShiftedDcId dcId = 0); + + bool valid() const; + QPointer instance() const; + + ~WeakInstance(); + +private: + void die(); + bool removeRequest(mtpRequestId requestId); + void reportUnavailable(Fn callback); + + QPointer _instance; + std::map> _requests; + +}; + +class AbstractDedicatedLoader : public base::has_weak_ptr { +public: + AbstractDedicatedLoader(const QString &filepath, int chunkSize); + + static constexpr auto kChunkSize = 128 * 1024; + static constexpr auto kMaxFileSize = 256 * 1024 * 1024; + + struct Progress { + int64 already; + int64 size; + + inline bool operator<(const Progress &other) const { + return (already < other.already) + || (already == other.already && size < other.size); + } + inline bool operator==(const Progress &other) const { + return (already == other.already) && (size == other.size); + } + }; + + void start(); + void wipeFolder(); + void wipeOutput(); + + int alreadySize() const; + int totalSize() const; + + rpl::producer progress() const; + rpl::producer ready() const; + rpl::producer<> failed() const; + + rpl::lifetime &lifetime(); + + virtual ~AbstractDedicatedLoader() = default; + +protected: + void threadSafeFailed(); + + // Single threaded. + void writeChunk(bytes::const_span data, int totalSize); + +private: + virtual void startLoading() = 0; + + bool validateOutput(); + void threadSafeProgress(Progress progress); + void threadSafeReady(); + + QString _filepath; + int _chunkSize = 0; + + QFile _output; + int _alreadySize = 0; + int _totalSize = 0; + mutable QMutex _sizesMutex; + rpl::event_stream _progress; + rpl::event_stream _ready; + rpl::event_stream<> _failed; + + rpl::lifetime _lifetime; + +}; + +class DedicatedLoader : public AbstractDedicatedLoader { +public: + struct Location { + QString username; + int32 postId = 0; + }; + struct File { + QString name; + int32 size = 0; + DcId dcId = 0; + MTPInputFileLocation location; + }; + + DedicatedLoader( + QPointer instance, + const QString &folder, + const File &file); + +private: + struct Request { + int offset = 0; + QByteArray bytes; + }; + void startLoading() override; + void sendRequest(); + void gotPart(int offset, const MTPupload_File &result); + Fn failHandler(); + + static constexpr auto kRequestsCount = 2; + static constexpr auto kNextRequestDelay = TimeMs(20); + + std::deque _requests; + int32 _size = 0; + int _offset = 0; + DcId _dcId = 0; + MTPInputFileLocation _location; + WeakInstance _mtp; + +}; + +void ResolveChannel( + not_null mtp, + const QString &username, + Fn done, + Fn fail); + +std::optional GetMessagesElement( + const MTPmessages_Messages &list); + +void StartDedicatedLoader( + not_null mtp, + const DedicatedLoader::Location &location, + const QString &folder, + Fn)> ready); + +template +void WeakInstance::send( + const T &request, + Fn done, + Fn fail, + MTP::ShiftedDcId dcId) { + using Response = typename T::ResponseType; + if (!valid()) { + reportUnavailable(fail); + return; + } + const auto onDone = crl::guard((QObject*)this, [=]( + const Response &result, + mtpRequestId requestId) { + if (removeRequest(requestId)) { + done(result); + } + }); + const auto onFail = crl::guard((QObject*)this, [=]( + const RPCError &error, + mtpRequestId requestId) { + if (MTP::isDefaultHandledError(error)) { + return false; + } + if (removeRequest(requestId)) { + fail(error); + } + return true; + }); + const auto requestId = _instance->send( + request, + rpcDone(onDone), + rpcFail(onFail), + dcId); + _requests.emplace(requestId, fail); +} + +} // namespace MTP diff --git a/Telegram/SourceFiles/ui/emoji_config.cpp b/Telegram/SourceFiles/ui/emoji_config.cpp index b1c23cba6..14d18bbdf 100644 --- a/Telegram/SourceFiles/ui/emoji_config.cpp +++ b/Telegram/SourceFiles/ui/emoji_config.cpp @@ -124,12 +124,6 @@ bool IsValidSetId(int id) { return (id == 0) || (id > 0 && id < kMaxId); } -[[nodiscard]] QString SetDataPath(int id) { - Expects(IsValidSetId(id) && id != 0); - - return CacheFileFolder() + "/set" + QString::number(id); -} - uint32 ComputeVersion(int id) { Expects(IsValidSetId(id)); @@ -166,7 +160,7 @@ void ClearCurrentSetId() { if (!id) { return; } - QDir(SetDataPath(id)).removeRecursively(); + QDir(internal::SetDataPath(id)).removeRecursively(); SwitchToSet(0); } @@ -275,7 +269,7 @@ std::vector LoadSprites(int id) { auto result = std::vector(); const auto folder = (id != 0) - ? SetDataPath(id) + '/' + ? internal::SetDataPath(id) + '/' : qsl(":/gui/emoji/"); const auto base = folder + "emoji_"; return ranges::view::ints( @@ -295,7 +289,7 @@ bool ValidateConfig(int id) { return true; } constexpr auto kSizeLimit = 65536; - auto config = QFile(SetDataPath(id) + "/config.json"); + auto config = QFile(internal::SetDataPath(id) + "/config.json"); if (!config.open(QIODevice::ReadOnly) || config.size() > kSizeLimit) { return false; } @@ -473,6 +467,16 @@ void ClearUniversalChecked() { } // namespace +namespace internal { + +QString SetDataPath(int id) { + Expects(IsValidSetId(id) && id != 0); + + return CacheFileFolder() + "/set" + QString::number(id); +} + +} // namespace internal + void Init() { internal::Init(); @@ -558,7 +562,7 @@ bool SetIsReady(int id) { if (!id) { return true; } - const auto folder = SetDataPath(id) + '/'; + const auto folder = internal::SetDataPath(id) + '/'; auto names = ranges::view::ints( 0, SpritesCount + 1 diff --git a/Telegram/SourceFiles/ui/emoji_config.h b/Telegram/SourceFiles/ui/emoji_config.h index 91bc4f820..bbca91eb6 100644 --- a/Telegram/SourceFiles/ui/emoji_config.h +++ b/Telegram/SourceFiles/ui/emoji_config.h @@ -12,6 +12,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { namespace Emoji { +namespace internal { + +[[nodiscard]] QString SetDataPath(int id); + +} // namespace internal constexpr auto kRecentLimit = 42; diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 85082fcc6..9e6bc94d6 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -463,6 +463,8 @@ <(src_loc)/mtproto/dcenter.h <(src_loc)/mtproto/dc_options.cpp <(src_loc)/mtproto/dc_options.h +<(src_loc)/mtproto/dedicated_file_loader.cpp +<(src_loc)/mtproto/dedicated_file_loader.h <(src_loc)/mtproto/facade.cpp <(src_loc)/mtproto/facade.h <(src_loc)/mtproto/mtp_instance.cpp