From 0134b928dca89c4b895647d4ddca83eebdc1439a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 9 Feb 2026 12:22:06 +0300 Subject: [PATCH] Added controller for calendar search. --- Telegram/CMakeLists.txt | 2 + .../SourceFiles/data/data_search_calendar.cpp | 194 ++++++++++++++++++ .../SourceFiles/data/data_search_calendar.h | 90 ++++++++ 3 files changed, 286 insertions(+) create mode 100644 Telegram/SourceFiles/data/data_search_calendar.cpp create mode 100644 Telegram/SourceFiles/data/data_search_calendar.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index ca6cec7c8a..dad09e835a 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -119,6 +119,8 @@ PRIVATE api/api_chat_participants.h api/api_cloud_password.cpp api/api_cloud_password.h + data/data_search_calendar.h + data/data_search_calendar.cpp api/api_common.cpp api/api_common.h api/api_confirm_phone.cpp diff --git a/Telegram/SourceFiles/data/data_search_calendar.cpp b/Telegram/SourceFiles/data/data_search_calendar.cpp new file mode 100644 index 0000000000..d9f74ac352 --- /dev/null +++ b/Telegram/SourceFiles/data/data_search_calendar.cpp @@ -0,0 +1,194 @@ +/* +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 "data/data_search_calendar.h" + +#include "apiwrap.h" +#include "base/unixtime.h" +#include "data/data_document.h" +#include "data/data_media_types.h" +#include "data/data_peer.h" +#include "data/data_search_controller.h" // PrepareSearchFilter +#include "data/data_session.h" +#include "history/history_item.h" +#include "history/history.h" +#include "main/main_session.h" +#include "ui/dynamic_thumbnails.h" + +namespace Api { + +SearchCalendarController::SearchCalendarController( + not_null session, + Storage::SharedMediaType type) +: _session(session) +, _type(type) { +} + +void SearchCalendarController::monthThumbnails( + PeerId peerId, + TimeId date, + Fn)> onFinish) { + const auto parsed = base::unixtime::parse(date).date(); + const auto key = MonthKey{ + .peerId = peerId, + .year = parsed.year(), + .month = parsed.month(), + }; + + if (const auto it = _months.find(key); it != _months.end()) { + if (!it->second.cache.empty()) { + onFinish(it->second.cache); + return; + } + } + + _months[key].callbacks.push_back(std::move(onFinish)); + + if (!_months[key].requestId) { + performMonthRequest(key); + } +} + +void SearchCalendarController::performMonthRequest(const MonthKey &key) { + const auto peer = _session->data().peer(key.peerId); + const auto filter = PrepareSearchFilter(_type); + + const auto parsed = QDate(key.year, key.month, 1); + const auto endDate = base::unixtime::serialize(QDateTime( + parsed.addMonths(1).addDays(-1), + QTime(23, 59, 59))); + + auto &state = _months[key].state; + + _months[key].requestId = _session->api().request( + MTPmessages_GetSearchResultsCalendar( + MTP_flags(0), + peer->input(), + MTPInputPeer(), + filter, + MTP_int(state.offsetId), + MTP_int(state.offsetDate ? state.offsetDate : endDate) + )).done([=](const MTPmessages_SearchResultsCalendar &result) { + _months[key].requestId = 0; + const auto &data = result.data(); + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + _session->data().processMessages( + data.vmessages(), + NewMessageType::Existing); + + auto messageIds = std::vector(); + messageIds.reserve(data.vmessages().v.size()); + for (const auto &message : data.vmessages().v) { + messageIds.push_back( + FullMsgId(key.peerId, IdFromMessage(message))); + } + + auto &monthState = _months[key].state; + const auto prevOffsetId = monthState.offsetId; + const auto prevOffsetDate = monthState.offsetDate; + monthState.offsetId = data.vmin_msg_id().v; + monthState.offsetDate = data.vmin_date().v; + + const auto noMoreData = (prevOffsetId == monthState.offsetId + && prevOffsetDate == monthState.offsetDate + && prevOffsetId != 0); + + processMonthMessages( + key, + messageIds, + data.vmin_date().v, + data.vmin_msg_id().v, + noMoreData); + }).fail([=] { + auto &data = _months[key]; + data.requestId = 0; + data.cache = {}; + for (const auto &callback : data.callbacks) { + callback({}); + } + data.callbacks.clear(); + }).send(); +} + +void SearchCalendarController::processMonthMessages( + const MonthKey &key, + const std::vector &messages, + TimeId minDate, + MsgId minMsgId, + bool noMoreData) { + auto result = std::vector(); + auto seenDays = base::flat_set(); + const auto peer = _session->data().peer(key.peerId); + const auto history = peer->owner().history(peer); + + const auto targetMonth = QDate(key.year, key.month, 1); + const auto targetStart = base::unixtime::serialize( + QDateTime(targetMonth, QTime())); + const auto targetEnd = base::unixtime::serialize(QDateTime( + targetMonth.addMonths(1).addDays(-1), + QTime(23, 59, 59))); + + for (const auto &fullId : messages) { + const auto item = _session->data().message(fullId); + if (!item) { + continue; + } + + const auto date = item->date(); + if (date < targetStart || date > targetEnd) { + continue; + } + + const auto parsed = base::unixtime::parse(date).date(); + const auto dayStart = base::unixtime::serialize( + QDateTime(parsed, QTime())); + + if (seenDays.contains(dayStart)) { + continue; + } + + const auto media = item->media(); + if (!media) { + continue; + } + + auto image = std::shared_ptr(); + + if (const auto photo = media->photo()) { + image = Ui::MakePhotoThumbnail(photo, item->fullId()); + } else if (const auto document = media->document()) { + if (document->isVideoFile()) { + image = Ui::MakeDocumentThumbnail(document, item->fullId()); + } + } + + if (image) { + seenDays.insert(dayStart); + result.push_back(DayThumbnail{ + .date = dayStart, + .image = std::move(image), + }); + } + } + + if (result.empty() + && minDate < targetStart + && !_months[key].requestId + && !noMoreData) { + performMonthRequest(key); + } else { + auto &data = _months[key]; + data.cache = result; + for (const auto &callback : data.callbacks) { + callback(result); + } + data.callbacks.clear(); + } +} + +} // namespace Api \ No newline at end of file diff --git a/Telegram/SourceFiles/data/data_search_calendar.h b/Telegram/SourceFiles/data/data_search_calendar.h new file mode 100644 index 0000000000..88304a2885 --- /dev/null +++ b/Telegram/SourceFiles/data/data_search_calendar.h @@ -0,0 +1,90 @@ +/* +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 + +#include "storage/storage_shared_media.h" + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { +class DynamicImage; +} // namespace Ui + +namespace Api { + +struct CalendarPeriod { + TimeId date = 0; + MsgId minMsgId = 0; + MsgId maxMsgId = 0; + int count = 0; +}; + +struct CalendarResult { + std::vector periods; + int count = 0; + TimeId minDate = 0; + MsgId minMsgId = 0; +}; + +struct DayThumbnail { + TimeId date = 0; + std::shared_ptr image; +}; + +class SearchCalendarController final { +public: + SearchCalendarController( + not_null session, + Storage::SharedMediaType type); + + void monthThumbnails( + PeerId peerId, + TimeId date, + Fn)> onFinish); + +private: + struct MonthKey { + PeerId peerId = 0; + int year = 0; + int month = 0; + + friend inline auto operator<=>( + const MonthKey &, + const MonthKey &) = default; + }; + + struct MonthState { + MsgId offsetId = 0; + TimeId offsetDate = 0; + }; + + struct MonthData { + std::vector cache; + std::vector)>> callbacks; + mtpRequestId requestId = 0; + MonthState state; + }; + + void performMonthRequest(const MonthKey &key); + void processMonthMessages( + const MonthKey &key, + const std::vector &messages, + TimeId minDate, + MsgId minMsgId, + bool noMoreData); + + const not_null _session; + const Storage::SharedMediaType _type; + + base::flat_map _months; + +}; + +} // namespace Api