diff --git a/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp b/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp index 18dafdd7b7..d007ef9884 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp @@ -75,14 +75,12 @@ public: int id() const; rpl::producer state() const; + void destroy(); 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; @@ -118,14 +116,20 @@ private: void load(); int _id = 0; + bool _switching = false; rpl::variable _state; std::unique_ptr _loading; }; -QPointer GlobalLoader; +base::unique_qptr GlobalLoader; rpl::event_stream GlobalLoaderValues; +void SetGlobalLoader(base::unique_qptr loader) { + GlobalLoader = std::move(loader); + GlobalLoaderValues.fire(GlobalLoader.get()); +} + int GetDownloadSize(int id) { const auto sets = Sets(); return ranges::find(sets, id, &Set::id)->size; @@ -172,6 +176,50 @@ QByteArray ReadFinalFile(const QString &path) { return file.readAll(); } +bool ExtractZipFile(zlib::FileToRead &zip, const QString path) { + constexpr auto kMaxSize = 10 * 1024 * 1024; + const auto content = zip.readCurrentFileContent(kMaxSize); + if (content.isEmpty() || zip.error() != UNZ_OK) { + return false; + } + auto file = QFile(path); + return file.open(QIODevice::WriteOnly) + && (file.write(content) == content.size()); +} + +bool GoodSetPartName(const QString &name) { + return (name == qstr("config.json")) + || (name.startsWith(qstr("emoji_")) + && name.endsWith(qstr(".webp"))); +} + +bool UnpackSet(const QString &path, const QString &folder) { + const auto bytes = ReadFinalFile(path); + if (bytes.isEmpty()) { + return false; + } + + auto zip = zlib::FileToRead(bytes); + if (zip.goToFirstFile() != UNZ_OK) { + return false; + } + do { + const auto name = zip.getCurrentFileName(); + const auto path = folder + '/' + name; + if (GoodSetPartName(name) && !ExtractZipFile(zip, path)) { + return false; + } + + const auto jump = zip.goToNextFile(); + if (jump == UNZ_END_OF_LIST_OF_FILE) { + break; + } else if (jump != UNZ_OK) { + return false; + } + } while (true); + return true; +} + Loader::Loader(QObject *parent, int id) : QObject(parent) , _id(id) @@ -223,45 +271,27 @@ void Loader::setImplementation( } 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 folder = internal::SetDataPath(_id); + const auto weak = make_weak(this); + crl::async([=] { + if (UnpackSet(path, folder)) { + QFile(path).remove(); + SwitchToSet(_id, crl::guard(weak, [=](bool success) { + if (success) { + destroy(); + } else { + fail(); + } + })); + } else { + crl::on_main(weak, [=] { + 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() { @@ -269,22 +299,9 @@ void Loader::fail() { } void Loader::destroy() { - GlobalLoaderValues.fire(nullptr); - delete this; -} + Expects(GlobalLoader == 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()); + SetGlobalLoader(nullptr); } Inner::Inner(QWidget *parent) : RpWidget(parent) { @@ -327,7 +344,7 @@ void Row::paintEvent(QPaintEvent *e) { void Row::setupContent(const Set &set) { _state = GlobalLoaderValues.events_starting_with( - GlobalLoader.data() + GlobalLoader.get() ) | rpl::map([=](Loader *loader) { return (loader && loader->id() == _id) ? loader->state() @@ -353,20 +370,21 @@ void Row::setupHandler() { clicks( ) | rpl::filter([=] { const auto &state = _state.current(); - return state.is() || state.is(); + return !_switching && (state.is() || state.is()); }) | rpl::start_with_next([=] { if (_state.current().is()) { load(); return; } - const auto delay = st::defaultRippleAnimation.hideDuration; - App::CallDelayed(delay, this, [=] { - if (!SwitchToSet(_id)) { + _switching = true; + SwitchToSet(_id, crl::guard(this, [=](bool success) { + _switching = false; + if (!success) { load(); - } else { - delete GlobalLoader; + } else if (GlobalLoader && GlobalLoader->id() == _id) { + GlobalLoader->destroy(); } - }); + })); }, lifetime()); _state.value( @@ -379,8 +397,7 @@ void Row::setupHandler() { } void Row::load() { - GlobalLoader = Ui::CreateChild(App::main(), _id); - GlobalLoaderValues.fire(GlobalLoader.data()); + SetGlobalLoader(base::make_unique_q(App::main(), _id)); } void Row::setupCheck() { diff --git a/Telegram/SourceFiles/ui/emoji_config.cpp b/Telegram/SourceFiles/ui/emoji_config.cpp index 14d18bbdf5..781d93a845 100644 --- a/Telegram/SourceFiles/ui/emoji_config.cpp +++ b/Telegram/SourceFiles/ui/emoji_config.cpp @@ -153,7 +153,22 @@ int ReadCurrentSetId() { : 0; } -void ClearCurrentSetId() { +void SwitchToSetPrepared(int id, std::shared_ptr images) { + auto setting = QFile(CurrentSettingPath()); + if (!id) { + setting.remove(); + } else if (setting.open(QIODevice::WriteOnly)) { + auto stream = QDataStream(&setting); + stream.setVersion(QDataStream::Qt_5_1); + stream << qint32(id); + } + Universal = std::move(images); + MainEmojiMap.clear(); + OtherEmojiMap.clear(); + Updates.fire({}); +} + +void ClearCurrentSetIdSync() { Expects(Universal != nullptr); const auto id = Universal->id(); @@ -161,7 +176,11 @@ void ClearCurrentSetId() { return; } QDir(internal::SetDataPath(id)).removeRecursively(); - SwitchToSet(0); + + const auto newId = 0; + auto universal = std::make_shared(newId); + universal->ensureLoaded(); + SwitchToSetPrepared(newId, std::move(universal)); } void SaveToFile(int id, const QImage &image, int size, int index) { @@ -531,29 +550,26 @@ int CurrentSetId() { return Universal->id(); } -bool SwitchToSet(int id) { +void SwitchToSet(int id, Fn callback) { Expects(IsValidSetId(id)); if (Universal && Universal->id() == id) { - return true; + callback(true); + return; } - auto universal = std::make_shared(id); - if (!universal->ensureLoaded()) { - return false; - } - auto setting = QFile(CurrentSettingPath()); - if (!id) { - setting.remove(); - } else if (setting.open(QIODevice::WriteOnly)) { - auto stream = QDataStream(&setting); - stream.setVersion(QDataStream::Qt_5_1); - stream << qint32(id); - } - Universal = std::move(universal); - MainEmojiMap.clear(); - OtherEmojiMap.clear(); - Updates.fire({}); - return true; + crl::async([=] { + auto universal = std::make_shared(id); + if (!universal->ensureLoaded()) { + crl::on_main([=] { + callback(false); + }); + } else { + crl::on_main([=, universal = std::move(universal)]() mutable { + SwitchToSetPrepared(id, std::move(universal)); + callback(true); + }); + } + }); } bool SetIsReady(int id) { @@ -903,7 +919,7 @@ void Instance::checkUniversalImages() { _sprites.clear(); } if (!Universal->ensureLoaded() && Universal->id() != 0) { - ClearCurrentSetId(); + ClearCurrentSetIdSync(); } } diff --git a/Telegram/SourceFiles/ui/emoji_config.h b/Telegram/SourceFiles/ui/emoji_config.h index bbca91eb6e..a6f3fbbe30 100644 --- a/Telegram/SourceFiles/ui/emoji_config.h +++ b/Telegram/SourceFiles/ui/emoji_config.h @@ -32,9 +32,11 @@ struct Set { QString name; }; +// Thread safe, callback is called on main thread. +void SwitchToSet(int id, Fn callback); + std::vector Sets(); int CurrentSetId(); -bool SwitchToSet(int id); bool SetIsReady(int id); rpl::producer<> Updated();