From e2bc4cef5b444e31c19b9cd9b0154c74d067dbd7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 14 Aug 2026 11:16:58 +0400 Subject: [PATCH] Clear directly-deleted local photos from cache. --- Telegram/SourceFiles/apiwrap.cpp | 12 +- .../data/business/data_shortcut_messages.cpp | 58 +++-- .../data/components/ephemeral_messages.cpp | 26 +- .../data/components/ephemeral_messages.h | 2 + .../data/components/scheduled_messages.cpp | 26 +- .../data/components/welcome_messages.cpp | 21 +- Telegram/SourceFiles/data/data_photo.cpp | 15 ++ Telegram/SourceFiles/data/data_photo.h | 1 + Telegram/SourceFiles/data/data_session.cpp | 238 +++++++++++++++++- Telegram/SourceFiles/data/data_session.h | 32 ++- Telegram/SourceFiles/history/history.cpp | 2 + Telegram/SourceFiles/history/history_item.cpp | 29 ++- Telegram/SourceFiles/history/history_item.h | 1 + .../window/window_session_controller.cpp | 2 +- 14 files changed, 366 insertions(+), 99 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 2ae476e36f..ae1cd40172 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -1488,11 +1488,14 @@ void ApiWrap::deleteAllFromParticipant( const auto ids = history ? history->collectMessagesFromParticipantToDelete(from) : std::vector(); + auto items = std::vector>(); + items.reserve(ids.size()); for (const auto &msgId : ids) { if (const auto item = _session->data().message(channel->id, msgId)) { - item->destroy(); + items.push_back(item); } } + _session->data().destroyMessagesWithCacheCleanup(items); _session->data().sendHistoryChangeNotifications(); @@ -2211,7 +2214,7 @@ void ApiWrap::deleteHistory( return; } else if (!last->isRegular()) { // Destroy client-side message locally. - last->destroy(); + history->owner().destroyMessageWithCacheCleanup(last); } else { break; } @@ -5408,11 +5411,14 @@ void ApiWrap::sendMultiPaidMedia( ), [=](const MTPUpdates &result, const MTP::Response &response) { if (const auto album = _sendingAlbums.take(groupId)) { const auto copy = (*album)->items; + auto items = std::vector>(); + items.reserve(copy.size()); for (const auto &part : copy) { if (const auto item = history->owner().message(part.msgId)) { - item->destroy(); + items.push_back(item); } } + history->owner().destroyMessagesWithCacheCleanup(items); } if (done) done(true); }, [=](const MTP::Error &error, const MTP::Response &response) { diff --git a/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp b/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp index 859cb0ce3f..7cea773a7a 100644 --- a/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp +++ b/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp @@ -200,9 +200,10 @@ void ShortcutMessages::mergeMessagesFromTo( destroy.emplace(item.get()); } } - for (const auto &item : destroy) { - item->destroy(); - } + _session->data().destroyMessagesWithCacheCleanup( + std::vector>( + begin(destroy), + end(destroy))); _data.remove(fromId); cancelRequest(fromId); @@ -345,21 +346,20 @@ void ShortcutMessages::apply( if (!shortcutId) { return; } - auto i = _data.find(shortcutId); + const auto i = _data.find(shortcutId); if (i == end(_data)) { return; } + auto items = std::vector>(); + items.reserve(update.vmessages().v.size()); for (const auto &id : update.vmessages().v) { const auto &list = i->second; const auto j = list.itemById.find(id.v); if (j != end(list.itemById)) { - j->second->destroy(); - i = _data.find(shortcutId); - if (i == end(_data)) { - break; - } + items.push_back(j->second); } } + _session->data().destroyMessagesWithCacheCleanup(items); _updates.fire_copy(shortcutId); updateCount(shortcutId); @@ -372,10 +372,14 @@ void ShortcutMessages::apply(const MTPDupdateDeleteQuickReply &update) { if (!shortcutId) { return; } - auto i = _data.find(shortcutId); - while (i != end(_data) && !i->second.itemById.empty()) { - i->second.itemById.back().second->destroy(); - i = _data.find(shortcutId); + const auto i = _data.find(shortcutId); + if (i != end(_data)) { + auto items = std::vector>(); + items.reserve(i->second.itemById.size()); + for (const auto &entry : i->second.itemById) { + items.push_back(entry.second); + } + _session->data().destroyMessagesWithCacheCleanup(items); } _updates.fire_copy(shortcutId); if (_data.contains(shortcutId)) { @@ -395,7 +399,7 @@ void ShortcutMessages::apply( auto &list = i->second; const auto j = list.itemById.find(id); if (j != end(list.itemById) || !IsServerMsgId(id)) { - local->destroy(); + _session->data().destroyMessageWithCacheCleanup(local); } else { Assert(!list.itemById.contains(local->id)); local->setRealId(localMessageId(id)); @@ -418,7 +422,7 @@ void ShortcutMessages::removeSending(not_null item) { Expects(item->isSending() || item->hasFailed()); Expects(item->isBusinessShortcut()); - item->destroy(); + _session->data().destroyMessageWithCacheCleanup(item); } rpl::producer<> ShortcutMessages::updates(BusinessShortcutId shortcutId) { @@ -568,15 +572,16 @@ void ShortcutMessages::editShortcut( } void ShortcutMessages::removeShortcut(BusinessShortcutId shortcutId) { - auto i = _data.find(shortcutId); - while (i != end(_data)) { - if (i->second.items.empty()) { - _data.erase(i); - } else { - i->second.items.front()->destroy(); + const auto i = _data.find(shortcutId); + if (i != end(_data)) { + auto items = std::vector>(); + items.reserve(i->second.items.size()); + for (const auto &item : i->second.items) { + items.push_back(item.get()); } - i = _data.find(shortcutId); + _session->data().destroyMessagesWithCacheCleanup(items); } + _data.remove(shortcutId); _shortcuts.list.remove(shortcutId); _shortcutIdChanges.fire({ shortcutId, 0 }); @@ -716,11 +721,10 @@ void ShortcutMessages::updated( BusinessShortcutId shortcutId, const base::flat_set> &added, const base::flat_set> &clear) { - if (!clear.empty()) { - for (const auto &item : clear) { - item->destroy(); - } - } + _session->data().destroyMessagesWithCacheCleanup( + std::vector>( + begin(clear), + end(clear))); const auto i = _data.find(shortcutId); if (i != end(_data)) { sort(i->second); diff --git a/Telegram/SourceFiles/data/components/ephemeral_messages.cpp b/Telegram/SourceFiles/data/components/ephemeral_messages.cpp index f799833f1b..9832497230 100644 --- a/Telegram/SourceFiles/data/components/ephemeral_messages.cpp +++ b/Telegram/SourceFiles/data/components/ephemeral_messages.cpp @@ -299,6 +299,7 @@ void EphemeralMessages::apply( if (!history) { return; } + auto items = std::vector>(); for (const auto &id : update.vids().v) { if (_session->welcomeMessages().applyDelete(history->peer, id.v)) { continue; @@ -307,10 +308,11 @@ void EphemeralMessages::apply( if (anchored(item)) { revertAnchored(item); } else { - item->destroy(); + items.push_back(item); } } } + _session->data().destroyMessagesWithCacheCleanup(items); } HistoryItem *EphemeralMessages::applyNew(const MTPDephemeralMessage &data) { @@ -544,6 +546,12 @@ bool EphemeralMessages::anchored(not_null item) const { return !_anchored.empty() && _anchored.contains(item->fullId()); } +const Media *EphemeralMessages::anchoredMedia( + not_null item) const { + const auto i = _anchored.find(item->fullId()); + return (i != end(_anchored)) ? i->second.media.get() : nullptr; +} + UserData *EphemeralMessages::replyReceiver( not_null item) const { if (const auto entry = findByItem(item)) { @@ -805,7 +813,7 @@ bool EphemeralMessages::sendMedia( } reportDroppedReply(); } - item->destroy(); + _session->data().destroyMessageWithCacheCleanup(item); return true; } @@ -827,7 +835,7 @@ bool EphemeralMessages::sendRich( } else if (action.options.scheduled || action.options.shortcutId) { LOG(("API Error: " "Dropping a scheduled ephemeral rich message send.")); - item->destroy(); + _session->data().destroyMessageWithCacheCleanup(item); return true; } const auto session = _session; @@ -905,7 +913,7 @@ bool EphemeralMessages::sendRich( } reportDroppedReply(); } - item->destroy(); + _session->data().destroyMessageWithCacheCleanup(item); return true; } @@ -955,7 +963,7 @@ void EphemeralMessages::request( const auto destroyLocal = [=] { if (destroyOnResult) { if (const auto local = session->data().message(destroyOnResult)) { - local->destroy(); + session->data().destroyMessageWithCacheCleanup(local); } } }; @@ -1010,7 +1018,7 @@ void EphemeralMessages::request( if (destroyOnResult) { const auto local = session->data().message(destroyOnResult); if (local && !findByItem(local)) { - local->destroy(); + session->data().destroyMessageWithCacheCleanup(local); } } }).fail([=](const MTP::Error &error) { @@ -1055,7 +1063,7 @@ void EphemeralMessages::deleteMessage(not_null item) { if (anchored(item)) { revertAnchored(item); } else if (item->isEphemeral()) { - item->destroy(); + _session->data().destroyMessageWithCacheCleanup(item); } } @@ -1113,9 +1121,7 @@ void EphemeralMessages::pruneOld() { } } } - for (const auto &item : old) { - item->destroy(); - } + _session->data().destroyMessagesWithCacheCleanup(old); } } // namespace Data diff --git a/Telegram/SourceFiles/data/components/ephemeral_messages.h b/Telegram/SourceFiles/data/components/ephemeral_messages.h index b8e0150882..a3ed39358b 100644 --- a/Telegram/SourceFiles/data/components/ephemeral_messages.h +++ b/Telegram/SourceFiles/data/components/ephemeral_messages.h @@ -90,6 +90,8 @@ public: PeerId botId, MsgId topicRootId); [[nodiscard]] bool anchored(not_null item) const; + [[nodiscard]] const Media *anchoredMedia( + not_null item) const; void revertAnchored(not_null item); void deleteMessage(not_null item); diff --git a/Telegram/SourceFiles/data/components/scheduled_messages.cpp b/Telegram/SourceFiles/data/components/scheduled_messages.cpp index eb0684e189..52c7158ab0 100644 --- a/Telegram/SourceFiles/data/components/scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/components/scheduled_messages.cpp @@ -300,7 +300,7 @@ void ScheduledMessages::sendNowSimpleMessage( _session->data().requestItemTextRefresh(sent); } - local->destroy(); + _session->data().destroyMessageWithCacheCleanup(local); } void ScheduledMessages::apply(const MTPDupdateNewScheduledMessage &update) { @@ -352,7 +352,7 @@ void ScheduledMessages::checkEntitiesAndUpdate(const MTPDmessage &data) { existing->updateForwardedInfo(data.vfwd_from()); _session->data().requestItemTextRefresh(existing); - existing->destroy(); + _session->data().destroyMessageWithCacheCleanup(existing); } } @@ -372,6 +372,8 @@ void ScheduledMessages::apply( } const auto sent = update.vsent_messages(); const auto &ids = update.vmessages().v; + auto items = std::vector>(); + items.reserve(ids.size()); for (auto k = 0, count = int(ids.size()); k != count; ++k) { const auto id = ids[k].v; const auto &list = i->second; @@ -384,13 +386,10 @@ void ScheduledMessages::apply( .sentId = sentId.v, }); } - j->second->destroy(); - i = _data.find(history); - if (i == end(_data)) { - break; - } + items.push_back(j->second); } } + _session->data().destroyMessagesWithCacheCleanup(items); _updates.fire_copy(history); } @@ -403,7 +402,7 @@ void ScheduledMessages::apply( auto &list = i->second; const auto j = list.itemById.find(id); if (j != end(list.itemById) || !IsServerMsgId(id)) { - local->destroy(); + _session->data().destroyMessageWithCacheCleanup(local); } else { Assert(!list.itemById.contains(local->id)); local->setRealId(localMessageId(id)); @@ -426,7 +425,7 @@ void ScheduledMessages::removeSending(not_null item) { Expects(item->isSending() || item->hasFailed()); Expects(item->isScheduled()); - item->destroy(); + _session->data().destroyMessageWithCacheCleanup(item); } rpl::producer<> ScheduledMessages::updates(not_null history) { @@ -610,11 +609,10 @@ void ScheduledMessages::updated( not_null history, const base::flat_set> &added, const base::flat_set> &clear) { - if (!clear.empty()) { - for (const auto &item : clear) { - item->destroy(); - } - } + _session->data().destroyMessagesWithCacheCleanup( + std::vector>( + begin(clear), + end(clear))); const auto i = _data.find(history); if (i != end(_data)) { sort(i->second); diff --git a/Telegram/SourceFiles/data/components/welcome_messages.cpp b/Telegram/SourceFiles/data/components/welcome_messages.cpp index 37fcdaf01f..86583fd406 100644 --- a/Telegram/SourceFiles/data/components/welcome_messages.cpp +++ b/Telegram/SourceFiles/data/components/welcome_messages.cpp @@ -75,7 +75,7 @@ WelcomeMessages::WelcomeMessages(not_null session) ) | rpl::on_next([=](FullMsgId id) { if (const auto item = _session->data().message(id)) { if (owns(item)) { - item->destroy(); + _session->data().destroyMessageWithCacheCleanup(item); } } }, _lifetime); @@ -167,7 +167,7 @@ void WelcomeMessages::removeSending(not_null item) { Expects(owns(item)); Expects(item->isSending() || item->hasFailed()); - item->destroy(); + _session->data().destroyMessageWithCacheCleanup(item); } void WelcomeMessages::applyNew(const MTPDephemeralMessage &data) { @@ -195,7 +195,7 @@ void WelcomeMessages::applyNew(const MTPDephemeralMessage &data) { applyEdition(existing, data); existing->updateDate(data.vdate().v); sort(list); - local->destroy(); + _session->data().destroyMessageWithCacheCleanup(local); } else { applyEdition(local, data); local->updateDate(data.vdate().v); @@ -233,7 +233,7 @@ bool WelcomeMessages::applyDelete( not_null peer, int32 ephemeralId) { if (const auto item = lookupItem(peer, ephemeralId)) { - item->destroy(); + _session->data().destroyMessageWithCacheCleanup(item); return true; } return false; @@ -297,7 +297,7 @@ void WelcomeMessages::sendMedia( const auto destroyLocal = [=] { if (const auto local = session->data().message(localId)) { if (owns(local)) { - local->destroy(); + session->data().destroyMessageWithCacheCleanup(local); } } }; @@ -495,7 +495,7 @@ void WelcomeMessages::deleteTemplate(not_null item) { LOG(("API Error: delete welcome template - %1" ).arg(error.type())); }).send(); - item->destroy(); + _session->data().destroyMessageWithCacheCleanup(item); } void WelcomeMessages::deleteAll(not_null history) { @@ -514,9 +514,7 @@ void WelcomeMessages::deleteAll(not_null history) { for (const auto &owned : i->second.items) { items.push_back(owned.get()); } - for (const auto &item : items) { - item->destroy(); - } + _session->data().destroyMessagesWithCacheCleanup(items); } rpl::producer<> WelcomeMessages::updates(not_null history) { @@ -700,9 +698,12 @@ void WelcomeMessages::updated( const base::flat_set> &added, const base::flat_set> &clear) { if (!clear.empty()) { + auto items = std::vector>(); + items.reserve(clear.size()); for (const auto &item : clear) { - item->destroy(); + items.push_back(item); } + _session->data().destroyMessagesWithCacheCleanup(items); } const auto i = _data.find(history); if (i != end(_data)) { diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp index c354ca551b..adc742e498 100644 --- a/Telegram/SourceFiles/data/data_photo.cpp +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -205,6 +205,21 @@ void PhotoData::cancel() { } } +void PhotoData::clearLocalCache() { + auto keys = base::flat_set(); + for (auto &file : _images) { + if (const auto key = file.location.file().cacheKey()) { + keys.emplace(key); + } + if (file.loader) { + file.loader->cancel(); + } + } + for (const auto &key : keys) { + _owner->cache().remove(key); + } +} + float64 PhotoData::progress() const { if (uploading()) { if (uploadingData->size > 0) { diff --git a/Telegram/SourceFiles/data/data_photo.h b/Telegram/SourceFiles/data/data_photo.h index f33078082a..1e7c5c7ace 100644 --- a/Telegram/SourceFiles/data/data_photo.h +++ b/Telegram/SourceFiles/data/data_photo.h @@ -58,6 +58,7 @@ public: [[nodiscard]] bool loading() const; [[nodiscard]] bool displayLoading() const; void cancel(); + void clearLocalCache(); [[nodiscard]] float64 progress() const; [[nodiscard]] int32 loadOffset() const; [[nodiscard]] bool uploading() const; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index dd9c442d7a..8cb819f05f 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -224,6 +224,87 @@ void CheckForSwitchInlineButton(not_null item) { double(std::numeric_limits::max()))); } +template +void EnumerateWebPagePhotos( + const WebPageData *page, + const Callback &callback) { + if (!page) { + return; + } + if (page->photo) { + callback(page->photo); + } + for (const auto &entry : page->collage.items) { + if (const auto photo = std::get_if(&entry)) { + if (*photo) { + callback(*photo); + } + } + } +} + +[[nodiscard]] base::flat_set CollectWebPagePhotos( + const WebPageData *page) { + auto result = base::flat_set(); + EnumerateWebPagePhotos(page, [&](PhotoData *photo) { + result.emplace(photo); + }); + return result; +} + +[[nodiscard]] bool WebPagePhotoBelongsToExternalOwner( + const WebPageData *page) { + switch (page->type) { + case WebPageType::Group: + case WebPageType::GroupWithRequest: + case WebPageType::GroupBoost: + case WebPageType::Channel: + case WebPageType::ChannelWithRequest: + case WebPageType::ChannelBoost: + case WebPageType::User: + case WebPageType::Bot: + case WebPageType::Profile: + case WebPageType::BotApp: + case WebPageType::Story: + case WebPageType::StoryAlbum: + case WebPageType::NewBot: + case WebPageType::VoiceChat: + case WebPageType::Livestream: + case WebPageType::ConferenceCall: return true; + default: return false; + } +} + +template +void EnumerateStaticMediaPhotos( + const Media *media, + const Callback &callback) { + if (!media) { + return; + } + const auto page = media->webpage(); + if (page) { + if (!WebPagePhotoBelongsToExternalOwner(page)) { + EnumerateWebPagePhotos(page, [&](PhotoData *photo) { + if (!photo->hasVideo()) { + callback(photo); + } + }); + } + } else { + const auto photo = media->photo(); + if (photo && !photo->hasVideo()) { + callback(photo); + } + } + const auto invoice = media->invoice(); + if (invoice) { + for (const auto &extended : invoice->extendedMedia) { + EnumerateStaticMediaPhotos(extended.get(), callback); + } + } +} + } // namespace Session::Session(not_null session) @@ -247,6 +328,7 @@ Session::Session(not_null session) , _ttlCheckTimer([=] { checkTTLs(); }) , _mediaDestroyCheckTimer([=] { checkMediaDestroys(); }) , _formattedDateTimer([=] { checkFormattedDateUpdates(); }) +, _clearPhotoCacheDelayed([=] { clearScheduledPhotoCache(); }) , _pollsClosingTimer([=] { checkPollsClosings(); }) , _watchForOfflineTimer([=] { checkLocalUsersWentOffline(); }) , _groups(this) @@ -444,6 +526,7 @@ void Session::clear() { _sendActionManager->clear(); + clearScheduledPhotoCache(); _histories->unloadAll(); _shortcutMessages = nullptr; _session->scheduledMessages().clear(); @@ -1866,8 +1949,8 @@ void Session::documentLoadSettingsChanged() { void Session::notifyPhotoLayoutChanged(not_null photo) { if (const auto i = _photoItems.find(photo); i != end(_photoItems)) { - for (const auto &item : i->second) { - notifyItemLayoutChange(item); + for (const auto &entry : i->second) { + notifyItemLayoutChange(entry.first); } } } @@ -1875,8 +1958,8 @@ void Session::notifyPhotoLayoutChanged(not_null photo) { void Session::requestPhotoViewRepaint(not_null photo) { const auto i = _photoItems.find(photo); if (i != end(_photoItems)) { - for (const auto &item : i->second) { - requestItemRepaint(item); + for (const auto &entry : i->second) { + requestItemRepaint(entry.first); } } } @@ -2319,9 +2402,81 @@ rpl::producer<> Session::sessionDataAboutToBeCleared() const { void Session::notifyItemsAboutToBeDestroyed( const std::vector> &items) { + schedulePhotoCacheClear(items); _itemsAboutToBeDestroyed.fire_copy(items); } +void Session::schedulePhotoCacheClear( + const std::vector> &items) { + const auto schedule = [&](PhotoData *photo) { + if (photo) { + _photosScheduledForCacheClear.emplace(photo); + } + }; + const auto scheduleMedia = [&](const Media *media) { + if (media + && !media->sharedMediaTypes().test( + Storage::SharedMediaType::ChatPhoto)) { + EnumerateStaticMediaPhotos(media, schedule); + } + }; + for (const auto &item : items) { + scheduleMedia(item->media()); + scheduleMedia(item->savedMedia()); + scheduleMedia(_session->ephemeralMessages().anchoredMedia(item)); + } + if (!_photosScheduledForCacheClear.empty()) { + _clearPhotoCacheDelayed.call(); + } +} + +void Session::destroyMessagesWithCacheCleanup( + const std::vector> &items) { + if (items.empty()) { + return; + } + auto ids = std::vector(); + ids.reserve(items.size()); + for (const auto item : items) { + ids.push_back(item->fullId()); + } + notifyItemsAboutToBeDestroyed(items); + for (auto i = size_t(0); i != items.size(); ++i) { + if (message(ids[i]) == items[i]) { + items[i]->destroy(); + } + } +} + +void Session::destroyMessageWithCacheCleanup( + not_null item) { + destroyMessagesWithCacheCleanup({ item }); +} + +void Session::scheduleItemPhotoCacheClear( + not_null item) { + schedulePhotoCacheClear({ item }); +} + +bool Session::photoHasItemReferences( + not_null photo) const { + return _photoItems.contains(photo); +} + +void Session::clearPhotoCache(not_null photo) { + if (!photo->hasVideo()) { + photo->clearLocalCache(); + } +} + +void Session::clearScheduledPhotoCache() { + for (const auto photo : base::take(_photosScheduledForCacheClear)) { + if (!photoHasItemReferences(photo)) { + clearPhotoCache(photo); + } + } +} + auto Session::itemsAboutToBeDestroyed() const -> rpl::producer>> { return _itemsAboutToBeDestroyed.events(); @@ -4405,10 +4560,19 @@ void Session::webpageApplyFields( stories().resolve(storyId, [=] { if (const auto maybe = stories().lookup(storyId)) { const auto story = *maybe; + const auto updatePhotoItems + = _webpageItems.contains(page) + && (page->photo != story->photo()); + const auto previous = updatePhotoItems + ? CollectWebPagePhotos(page) + : base::flat_set(); page->document = story->document(); page->photo = story->photo(); page->description = story->caption(); page->type = WebPageType::Story; + if (updatePhotoItems) { + updateWebPagePhotoItems(page, previous); + } notifyWebPageUpdateDelayed(page); } }); @@ -4488,6 +4652,11 @@ void Session::webpageApplyFields( bool photoIsVideoCover, TimeId pendingTill) { const auto requestPending = (!page->pendingTill && pendingTill > 0); + const auto updatePhotoItems = _webpageItems.contains(page) + && (page->photo != photo || page->collage.items != collage.items); + const auto previous = updatePhotoItems + ? CollectWebPagePhotos(page) + : base::flat_set(); const auto changed = page->applyChanges( type, url, @@ -4509,6 +4678,9 @@ void Session::webpageApplyFields( hasLargeMedia, photoIsVideoCover, pendingTill); + if (changed && updatePhotoItems) { + updateWebPagePhotoItems(page, previous); + } if (requestPending) { _session->api().requestWebPageDelayed(page); } @@ -4914,7 +5086,7 @@ not_null Session::location(const LocationPoint &point) { void Session::registerPhotoItem( not_null photo, not_null item) { - _photoItems[photo].insert(item); + ++_photoItems[photo][item]; } void Session::unregisterPhotoItem( @@ -4923,8 +5095,14 @@ void Session::unregisterPhotoItem( const auto i = _photoItems.find(photo); if (i != _photoItems.end()) { auto &items = i->second; - if (items.remove(item) && items.empty()) { - _photoItems.erase(i); + const auto j = items.find(item); + if (j != items.end()) { + if (!--j->second) { + items.erase(j); + } + if (items.empty()) { + _photoItems.erase(i); + } } } } @@ -4971,7 +5149,37 @@ void Session::unregisterWebPageView( void Session::registerWebPageItem( not_null page, not_null item) { - _webpageItems[page].insert(item); + auto &count = _webpageItems[page][item]; + if (!count) { + for (const auto photo : CollectWebPagePhotos(page)) { + registerPhotoItem(photo, item); + } + } + ++count; +} + +void Session::updateWebPagePhotoItems( + not_null page, + const base::flat_set &previous) { + const auto i = _webpageItems.find(page); + if (i == end(_webpageItems)) { + return; + } + const auto current = CollectWebPagePhotos(page); + for (const auto photo : previous) { + if (!current.contains(photo)) { + for (const auto &entry : i->second) { + unregisterPhotoItem(photo, entry.first); + } + } + } + for (const auto photo : current) { + if (!previous.contains(photo)) { + for (const auto &entry : i->second) { + registerPhotoItem(photo, entry.first); + } + } + } } void Session::unregisterWebPageItem( @@ -4980,8 +5188,15 @@ void Session::unregisterWebPageItem( const auto i = _webpageItems.find(page); if (i != _webpageItems.end()) { auto &items = i->second; - if (items.remove(item) && items.empty()) { - _webpageItems.erase(i); + const auto j = items.find(item); + if (j != items.end() && !--j->second) { + items.erase(j); + for (const auto photo : CollectWebPagePhotos(page)) { + unregisterPhotoItem(photo, item); + } + if (items.empty()) { + _webpageItems.erase(i); + } } } } @@ -5192,7 +5407,8 @@ void Session::checkPlayingAnimations() { HistoryItem *Session::findWebPageItem(not_null page) const { const auto i = _webpageItems.find(page); if (i != _webpageItems.end()) { - for (const auto &item : i->second) { + for (const auto &entry : i->second) { + const auto item = entry.first; if (item->isRegular()) { return item; } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 831caa60bd..fccd185f02 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -7,13 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "storage/storage_databases.h" -#include "dialogs/dialogs_main_list.h" -#include "data/data_groups.h" -#include "data/data_cloud_file.h" -#include "data/data_star_gift.h" -#include "history/history_location_manager.h" +#include "base/invoke_queued.h" #include "base/timer.h" +#include "data/data_cloud_file.h" +#include "data/data_groups.h" +#include "data/data_star_gift.h" +#include "dialogs/dialogs_main_list.h" +#include "history/history_location_manager.h" +#include "storage/storage_databases.h" class Image; class HistoryItem; @@ -435,6 +436,11 @@ public: [[nodiscard]] rpl::producer<> sessionDataAboutToBeCleared() const; void notifyItemsAboutToBeDestroyed( const std::vector> &items); + void destroyMessagesWithCacheCleanup( + const std::vector> &items); + void destroyMessageWithCacheCleanup(not_null item); + void scheduleItemPhotoCacheClear(not_null item); + void clearPhotoCache(not_null photo); [[nodiscard]] auto itemsAboutToBeDestroyed() const -> rpl::producer>>; void notifyViewAboutToBeRemoved( @@ -1158,6 +1164,14 @@ private: const MTPMessageMedia &media, TimeId date, bool invertMedia); + [[nodiscard]] bool photoHasItemReferences( + not_null photo) const; + void schedulePhotoCacheClear( + const std::vector> &items); + void updateWebPagePhotoItems( + not_null page, + const base::flat_set &previous); + void clearScheduledPhotoCache(); void setWallpapers(const QVector &data, uint64 hash); void highlightProcessDone(uint64 processId); @@ -1261,7 +1275,9 @@ private: std::unique_ptr> _photos; std::unordered_map< not_null, - base::flat_set>> _photoItems; + base::flat_map, int>> _photoItems; + SingleQueuedInvokation _clearPhotoCacheDelayed; + std::unordered_set _photosScheduledForCacheClear; std::unordered_map< DocumentId, std::unique_ptr> _documents; @@ -1273,7 +1289,7 @@ private: std::unique_ptr> _webpages; std::unordered_map< not_null, - base::flat_set>> _webpageItems; + base::flat_map, int>> _webpageItems; std::unordered_map< not_null, base::flat_set>> _webpageViews; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 139d21f137..61f071faf9 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -4389,6 +4389,7 @@ void History::clear(ClearType type, bool markEmpty) { setLastServerMessage(nullptr); } else if (_lastMessage && *_lastMessage) { if ((*_lastMessage)->isRegular()) { + owner().scheduleItemPhotoCacheClear(*_lastMessage); (*_lastMessage)->applyEditionToHistoryCleared(); } else { _lastMessage = std::nullopt; @@ -4430,6 +4431,7 @@ void History::clearUpTill(MsgId availableMinId) { if (!item->isRegular()) { continue; } else if (itemId == availableMinId) { + owner().scheduleItemPhotoCacheClear(item.get()); item->applyEditionToHistoryCleared(); } else if (itemId < availableMinId) { remove.push_back(item.get()); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 4d58210789..37e10518d4 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -2041,6 +2041,11 @@ bool HistoryItem::isEditingMedia() const { return Has(); } +const Data::Media *HistoryItem::savedMedia() const { + const auto data = Get(); + return data ? data->media.get() : nullptr; +} + PaidPostType HistoryItem::paidType() const { return (_flags & MessageFlag::StarsPaidSuggested) ? PaidPostType::Stars @@ -2514,7 +2519,13 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) { const auto wasGrouped = history()->owner().groups().isGrouped(this); setReplyMarkup({}, true); removeFromSharedMediaIndex(); - refreshMedia(nullptr); + const auto hadMedia = (_media != nullptr); + _media = nullptr; + if (hadMedia) { + if (const auto views = Get()) { + refreshRepliesText(views); + } + } setTextValue({}); changeViewsCount(-1); setForwardsCount(-1); @@ -2825,20 +2836,8 @@ void HistoryItem::clearMediaAsExpired() { updateServiceText({ std::move(text) }); _flags |= MessageFlag::ReactionsAllowed; } else if (const auto photo = media->photo()) { - photo->cancel(); - const auto sizes = { - Data::PhotoSize::Small, - Data::PhotoSize::Thumbnail, - Data::PhotoSize::Large, - }; - for (const auto size : sizes) { - const auto key = photo->location(size).file().cacheKey(); - if (key.valid()) { - owner.cache().remove(key); - } - } - applyEditionToHistoryCleared(); + photo->clearLocalCache(); updateServiceText({ tr::lng_ttl_photo_expired(tr::now, tr::marked) }); @@ -4164,7 +4163,7 @@ void HistoryItem::applyTTL(TimeId destroyAt) { const auto session = &_history->session(); crl::on_main(session, [session, id = fullId()]{ if (const auto item = session->data().message(id)) { - item->destroy(); + session->data().destroyMessageWithCacheCleanup(item); } }); } else { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index fbaec399a4..60eb350e31 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -554,6 +554,7 @@ public: [[nodiscard]] Data::Media *media() const { return _media.get(); } + [[nodiscard]] const Data::Media *savedMedia() const; [[nodiscard]] std::shared_ptr richPage() const; [[nodiscard]] auto translatedRichPage() const -> std::shared_ptr; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 57b213c5a4..985815edc0 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -3177,7 +3177,7 @@ void SessionController::cancelUploadLayer(not_null item) { if (const auto item = data.message(itemId)) { if (!item->isEditingMedia()) { const auto history = item->history(); - item->destroy(); + data.destroyMessageWithCacheCleanup(item); history->requestChatListMessage(); } else { item->returnSavedMedia();