mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Added initial ability to export topic history.
This commit is contained in:
@@ -225,25 +225,41 @@ struct ApiWrap::DialogsProcess : ChatsProcess {
|
||||
MTPInputPeer offsetPeer = MTP_inputPeerEmpty();
|
||||
};
|
||||
|
||||
struct ApiWrap::ChatProcess {
|
||||
Data::DialogInfo info;
|
||||
|
||||
FnMut<bool(const Data::DialogInfo &)> start;
|
||||
struct ApiWrap::AbstractMessagesProcess {
|
||||
Fn<bool(DownloadProgress)> fileProgress;
|
||||
Fn<bool(Data::MessagesSlice&&)> handleSlice;
|
||||
FnMut<void()> done;
|
||||
|
||||
FnMut<void(MTPmessages_Messages&&)> requestDone;
|
||||
|
||||
int localSplitIndex = 0;
|
||||
int32 largestIdPlusOne = 1;
|
||||
|
||||
Data::ParseMediaContext context;
|
||||
std::optional<Data::MessagesSlice> slice;
|
||||
bool lastSlice = false;
|
||||
int fileIndex = 0;
|
||||
};
|
||||
|
||||
struct ApiWrap::ChatProcess : AbstractMessagesProcess {
|
||||
Data::DialogInfo info;
|
||||
|
||||
FnMut<bool(const Data::DialogInfo &)> start;
|
||||
|
||||
int localSplitIndex = 0;
|
||||
int32 largestIdPlusOne = 1;
|
||||
};
|
||||
|
||||
struct ApiWrap::TopicProcess : AbstractMessagesProcess {
|
||||
PeerId peerId = 0;
|
||||
MTPInputPeer inputPeer;
|
||||
int32 topicRootId = 0;
|
||||
QString relativePath;
|
||||
|
||||
FnMut<bool(int count)> start;
|
||||
|
||||
int32 offsetId = 0;
|
||||
int totalCount = 0;
|
||||
int processedCount = 0;
|
||||
};
|
||||
|
||||
|
||||
template <typename Request>
|
||||
class ApiWrap::RequestBuilder {
|
||||
@@ -2080,13 +2096,18 @@ std::optional<QByteArray> ApiWrap::getCustomEmoji(QByteArray &data) {
|
||||
}
|
||||
auto &file = i->second.file;
|
||||
const auto fileProgress = [=](FileProgress value) {
|
||||
return loadMessageEmojiProgress(value);
|
||||
if (_chatProcess) {
|
||||
return loadMessageEmojiProgress(value);
|
||||
} else if (_topicProcess) {
|
||||
return loadTopicEmojiProgress(value);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const auto ready = processFileLoad(
|
||||
file,
|
||||
{ .customEmojiId = id },
|
||||
fileProgress,
|
||||
[=](const QString &path) { loadMessageEmojiDone(id, path); });
|
||||
[=](const QString &path) { loadCustomEmojiDone(id, path); });
|
||||
if (!ready) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -2262,6 +2283,36 @@ void ApiWrap::loadMessageEmojiDone(uint64 id, const QString &relativePath) {
|
||||
loadNextMessageFile();
|
||||
}
|
||||
|
||||
bool ApiWrap::loadTopicEmojiProgress(FileProgress progress) {
|
||||
Expects(_fileProcess != nullptr);
|
||||
Expects(_topicProcess != nullptr);
|
||||
Expects(_topicProcess->slice.has_value());
|
||||
Expects((_topicProcess->fileIndex >= 0)
|
||||
&& (_topicProcess->fileIndex < _topicProcess->slice->list.size()));
|
||||
|
||||
return _topicProcess->fileProgress(DownloadProgress{
|
||||
.randomId = _fileProcess->randomId,
|
||||
.path = _fileProcess->relativePath,
|
||||
.itemIndex = _topicProcess->fileIndex,
|
||||
.ready = progress.ready,
|
||||
.total = progress.total });
|
||||
}
|
||||
|
||||
void ApiWrap::loadCustomEmojiDone(uint64 id, const QString &relativePath) {
|
||||
const auto i = _resolvedCustomEmoji.find(id);
|
||||
if (i != end(_resolvedCustomEmoji)) {
|
||||
i->second.file.relativePath = relativePath;
|
||||
if (relativePath.isEmpty()) {
|
||||
i->second.file.skipReason = Data::File::SkipReason::Unavailable;
|
||||
}
|
||||
}
|
||||
if (_chatProcess) {
|
||||
loadNextMessageFile();
|
||||
} else if (_topicProcess) {
|
||||
loadNextTopicMessageFile();
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::finishMessages() {
|
||||
Expects(_chatProcess != nullptr);
|
||||
Expects(!_chatProcess->slice.has_value());
|
||||
@@ -2270,6 +2321,316 @@ void ApiWrap::finishMessages() {
|
||||
process->done();
|
||||
}
|
||||
|
||||
void ApiWrap::requestTopicMessages(
|
||||
PeerId peerId,
|
||||
MTPInputPeer inputPeer,
|
||||
int32 topicRootId,
|
||||
FnMut<bool(int count)> start,
|
||||
Fn<bool(DownloadProgress)> progress,
|
||||
Fn<bool(Data::MessagesSlice&&)> slice,
|
||||
FnMut<void()> done) {
|
||||
Expects(_topicProcess == nullptr);
|
||||
Expects(_selfId.has_value());
|
||||
|
||||
_topicProcess = std::make_unique<TopicProcess>();
|
||||
_topicProcess->context.selfPeerId = peerFromUser(*_selfId);
|
||||
_topicProcess->peerId = peerId;
|
||||
_topicProcess->inputPeer = inputPeer;
|
||||
_topicProcess->topicRootId = topicRootId;
|
||||
_topicProcess->relativePath = "chats/chat_"
|
||||
+ QString::number(peerId.value)
|
||||
+ "/topic_"
|
||||
+ QString::number(topicRootId)
|
||||
+ "/";
|
||||
_topicProcess->start = std::move(start);
|
||||
_topicProcess->fileProgress = std::move(progress);
|
||||
_topicProcess->handleSlice = std::move(slice);
|
||||
_topicProcess->done = std::move(done);
|
||||
|
||||
mainRequest(MTPchannels_GetMessages(
|
||||
MTP_inputChannel(
|
||||
inputPeer.c_inputPeerChannel().vchannel_id(),
|
||||
inputPeer.c_inputPeerChannel().vaccess_hash()),
|
||||
MTP_vector<MTPInputMessage>(
|
||||
1,
|
||||
MTP_inputMessageID(MTP_int(topicRootId)))
|
||||
)).done([=](const MTPmessages_Messages &rootResult) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
|
||||
auto rootSlice = rootResult.match([&](
|
||||
const MTPDmessages_messagesNotModified &) {
|
||||
return Data::MessagesSlice();
|
||||
}, [&](const auto &data) {
|
||||
return Data::ParseMessagesSlice(
|
||||
_topicProcess->context,
|
||||
data.vmessages(),
|
||||
data.vusers(),
|
||||
data.vchats(),
|
||||
_topicProcess->relativePath);
|
||||
});
|
||||
|
||||
auto rootSlicePtr = std::make_shared<Data::MessagesSlice>(
|
||||
std::move(rootSlice));
|
||||
|
||||
requestTopicReplies(
|
||||
0,
|
||||
0,
|
||||
kMessagesSliceLimit,
|
||||
[=](const MTPmessages_Messages &result) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
|
||||
const auto count = result.match(
|
||||
[](const MTPDmessages_messages &data) {
|
||||
return int(data.vmessages().v.size());
|
||||
}, [](const MTPDmessages_messagesSlice &data) {
|
||||
return data.vcount().v;
|
||||
}, [](const MTPDmessages_channelMessages &data) {
|
||||
return data.vcount().v;
|
||||
}, [](const MTPDmessages_messagesNotModified &data) {
|
||||
return -1;
|
||||
});
|
||||
if (count < 0) {
|
||||
error("Unexpected messagesNotModified received.");
|
||||
return;
|
||||
}
|
||||
_topicProcess->totalCount = count;
|
||||
if (!_topicProcess->start(count)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rootSlicePtr->list.empty()) {
|
||||
collectMessagesCustomEmoji(*rootSlicePtr);
|
||||
_topicProcess->slice = std::move(*rootSlicePtr);
|
||||
_topicProcess->fileIndex = 0;
|
||||
resolveTopicCustomEmoji();
|
||||
return;
|
||||
}
|
||||
|
||||
requestTopicMessagesSlice();
|
||||
});
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::requestTopicMessagesSlice() {
|
||||
Expects(_topicProcess != nullptr);
|
||||
|
||||
const auto offsetId = (_topicProcess->offsetId == 0)
|
||||
? 1
|
||||
: (_topicProcess->offsetId + 1);
|
||||
requestTopicReplies(
|
||||
offsetId,
|
||||
-kMessagesSliceLimit,
|
||||
kMessagesSliceLimit,
|
||||
[=](const MTPmessages_Messages &result) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
|
||||
result.match([&](const MTPDmessages_messagesNotModified &data) {
|
||||
error("Unexpected messagesNotModified received.");
|
||||
}, [&](const auto &data) {
|
||||
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
|
||||
_topicProcess->lastSlice = true;
|
||||
}
|
||||
auto slice = Data::ParseMessagesSlice(
|
||||
_topicProcess->context,
|
||||
data.vmessages(),
|
||||
data.vusers(),
|
||||
data.vchats(),
|
||||
_topicProcess->relativePath);
|
||||
if (slice.list.empty()) {
|
||||
_topicProcess->lastSlice = true;
|
||||
}
|
||||
loadTopicMessagesFiles(std::move(slice));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void ApiWrap::requestTopicReplies(
|
||||
int offsetId,
|
||||
int addOffset,
|
||||
int limit,
|
||||
FnMut<void(MTPmessages_Messages&&)> done) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
|
||||
_topicProcess->requestDone = std::move(done);
|
||||
const auto doneHandler = [=](MTPmessages_Messages &&result) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
base::take(_topicProcess->requestDone)(std::move(result));
|
||||
};
|
||||
|
||||
mainRequest(MTPmessages_GetReplies(
|
||||
_topicProcess->inputPeer,
|
||||
MTP_int(_topicProcess->topicRootId),
|
||||
MTP_int(offsetId),
|
||||
MTP_int(0),
|
||||
MTP_int(addOffset),
|
||||
MTP_int(limit),
|
||||
MTP_int(0),
|
||||
MTP_int(0),
|
||||
MTP_long(0)
|
||||
)).done(doneHandler).send();
|
||||
}
|
||||
|
||||
void ApiWrap::loadTopicMessagesFiles(Data::MessagesSlice &&slice) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
Expects(!_topicProcess->slice.has_value());
|
||||
|
||||
collectMessagesCustomEmoji(slice);
|
||||
|
||||
if (slice.list.empty()) {
|
||||
_topicProcess->lastSlice = true;
|
||||
}
|
||||
_topicProcess->slice = std::move(slice);
|
||||
_topicProcess->fileIndex = 0;
|
||||
|
||||
resolveTopicCustomEmoji();
|
||||
}
|
||||
|
||||
void ApiWrap::resolveTopicCustomEmoji() {
|
||||
if (_unresolvedCustomEmoji.empty()) {
|
||||
loadNextTopicMessageFile();
|
||||
return;
|
||||
}
|
||||
const auto count = std::min(
|
||||
int(_unresolvedCustomEmoji.size()),
|
||||
kMaxEmojiPerRequest);
|
||||
auto v = QVector<MTPlong>();
|
||||
v.reserve(count);
|
||||
const auto till = end(_unresolvedCustomEmoji);
|
||||
const auto from = end(_unresolvedCustomEmoji) - count;
|
||||
for (auto i = from; i != till; ++i) {
|
||||
v.push_back(MTP_long(*i));
|
||||
}
|
||||
_unresolvedCustomEmoji.erase(from, till);
|
||||
const auto finalize = [=] {
|
||||
for (const auto &id : v) {
|
||||
if (_resolvedCustomEmoji.contains(id.v)) {
|
||||
continue;
|
||||
}
|
||||
_resolvedCustomEmoji.emplace(
|
||||
id.v,
|
||||
Data::Document{
|
||||
.file = {
|
||||
.skipReason = Data::File::SkipReason::Unavailable,
|
||||
},
|
||||
});
|
||||
}
|
||||
resolveTopicCustomEmoji();
|
||||
};
|
||||
mainRequest(MTPmessages_GetCustomEmojiDocuments(
|
||||
MTP_vector<MTPlong>(v)
|
||||
)).fail([=](const MTP::Error &error) {
|
||||
LOG(("Export Error: Failed to get documents for emoji."));
|
||||
finalize();
|
||||
return true;
|
||||
}).done([=](const MTPVector<MTPDocument> &result) {
|
||||
for (const auto &entry : result.v) {
|
||||
auto document = Data::ParseDocument(
|
||||
_topicProcess->context,
|
||||
entry,
|
||||
_topicProcess->relativePath,
|
||||
TimeId());
|
||||
_resolvedCustomEmoji.emplace(document.id, std::move(document));
|
||||
}
|
||||
finalize();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::loadNextTopicMessageFile() {
|
||||
Expects(_topicProcess != nullptr);
|
||||
Expects(_topicProcess->slice.has_value());
|
||||
|
||||
const auto makeProgress = [=](FileProgress progress) {
|
||||
return _topicProcess->fileProgress(DownloadProgress{
|
||||
.randomId = _fileProcess->randomId,
|
||||
.path = _fileProcess->relativePath,
|
||||
.itemIndex = _topicProcess->fileIndex,
|
||||
.ready = progress.ready,
|
||||
.total = progress.total,
|
||||
});
|
||||
};
|
||||
for (auto &list = _topicProcess->slice->list
|
||||
; _topicProcess->fileIndex < list.size()
|
||||
; ++_topicProcess->fileIndex) {
|
||||
auto &message = list[_topicProcess->fileIndex];
|
||||
if (!messageCustomEmojiReady(message)) {
|
||||
return;
|
||||
}
|
||||
const auto origin = Data::FileOrigin{
|
||||
.peer = _topicProcess->inputPeer,
|
||||
.messageId = message.id
|
||||
};
|
||||
const auto ready = processFileLoad(
|
||||
message.file(),
|
||||
origin,
|
||||
makeProgress,
|
||||
[=, &message](const QString &path) {
|
||||
loadTopicMessageFileOrThumbDone(message.file(), path);
|
||||
},
|
||||
&message);
|
||||
if (!ready) {
|
||||
return;
|
||||
}
|
||||
const auto thumbReady = processFileLoad(
|
||||
message.thumb().file,
|
||||
origin,
|
||||
makeProgress,
|
||||
[=, &message](const QString &path) {
|
||||
loadTopicMessageFileOrThumbDone(message.thumb().file, path);
|
||||
},
|
||||
&message);
|
||||
if (!thumbReady) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
finishTopicMessagesSlice();
|
||||
}
|
||||
|
||||
void ApiWrap::finishTopicMessagesSlice() {
|
||||
Expects(_topicProcess != nullptr);
|
||||
Expects(_topicProcess->slice.has_value());
|
||||
|
||||
auto slice = *base::take(_topicProcess->slice);
|
||||
if (!slice.list.empty()) {
|
||||
_topicProcess->offsetId = slice.list.back().id;
|
||||
_topicProcess->processedCount += slice.list.size();
|
||||
if (!_topicProcess->handleSlice(std::move(slice))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto reachedTotal = _topicProcess->totalCount > 0
|
||||
&& _topicProcess->processedCount >= _topicProcess->totalCount;
|
||||
|
||||
if (!_topicProcess->lastSlice && !reachedTotal) {
|
||||
requestTopicMessagesSlice();
|
||||
} else {
|
||||
finishTopicMessages();
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::loadTopicMessageFileOrThumbDone(
|
||||
Data::File &file,
|
||||
const QString &relativePath) {
|
||||
Expects(_topicProcess != nullptr);
|
||||
Expects(_topicProcess->slice.has_value());
|
||||
Expects((_topicProcess->fileIndex >= 0)
|
||||
&& (_topicProcess->fileIndex < _topicProcess->slice->list.size()));
|
||||
|
||||
file.relativePath = relativePath;
|
||||
if (relativePath.isEmpty()) {
|
||||
file.skipReason = Data::File::SkipReason::Unavailable;
|
||||
}
|
||||
loadNextTopicMessageFile();
|
||||
}
|
||||
|
||||
void ApiWrap::finishTopicMessages() {
|
||||
Expects(_topicProcess != nullptr);
|
||||
Expects(!_topicProcess->slice.has_value());
|
||||
|
||||
const auto process = base::take(_topicProcess);
|
||||
process->done();
|
||||
}
|
||||
|
||||
bool ApiWrap::processFileLoad(
|
||||
Data::File &file,
|
||||
const Data::FileOrigin &origin,
|
||||
|
||||
Reference in New Issue
Block a user