init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
499
Telegram/SourceFiles/export/output/export_output_abstract.cpp
Normal file
499
Telegram/SourceFiles/export/output/export_output_abstract.cpp
Normal file
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
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 "export/output/export_output_abstract.h"
|
||||
|
||||
#include "export/output/export_output_html_and_json.h"
|
||||
#include "export/output/export_output_html.h"
|
||||
#include "export/output/export_output_json.h"
|
||||
#include "export/output/export_output_stats.h"
|
||||
#include "export/output/export_output_result.h"
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QDate>
|
||||
|
||||
namespace Export {
|
||||
namespace Output {
|
||||
|
||||
QString NormalizePath(const Settings &settings) {
|
||||
QDir folder(settings.path);
|
||||
const auto path = folder.absolutePath();
|
||||
auto result = path.endsWith('/') ? path : (path + '/');
|
||||
if (!folder.exists() && !settings.forceSubPath) {
|
||||
return result;
|
||||
}
|
||||
const auto mode = QDir::AllEntries | QDir::NoDotAndDotDot;
|
||||
const auto list = folder.entryInfoList(mode);
|
||||
if (list.isEmpty() && !settings.forceSubPath) {
|
||||
return result;
|
||||
}
|
||||
const auto date = QDate::currentDate();
|
||||
const auto base = QString(settings.onlySinglePeer()
|
||||
? "ChatExport_%1"
|
||||
: "DataExport_%1"
|
||||
).arg(date.toString(Qt::ISODate));
|
||||
const auto add = [&](int i) {
|
||||
return base + (i ? " (" + QString::number(i) + ')' : QString());
|
||||
};
|
||||
auto index = 0;
|
||||
while (QDir(result + add(index)).exists()) {
|
||||
++index;
|
||||
}
|
||||
result += add(index) + '/';
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<AbstractWriter> CreateWriter(Format format) {
|
||||
switch (format) {
|
||||
case Format::Html: return std::make_unique<HtmlWriter>();
|
||||
case Format::Json: return std::make_unique<JsonWriter>();
|
||||
case Format::HtmlAndJson: return std::make_unique<HtmlAndJsonWriter>();
|
||||
}
|
||||
Unexpected("Format in Export::Output::CreateWriter.");
|
||||
}
|
||||
|
||||
Stats AbstractWriter::produceTestExample(
|
||||
const QString &path,
|
||||
const Environment &environment) {
|
||||
auto result = Stats();
|
||||
const auto folder = QDir(path).absolutePath();
|
||||
auto settings = Settings();
|
||||
settings.format = format();
|
||||
settings.path = (folder.endsWith('/') ? folder : (folder + '/'))
|
||||
+ "ExportExample/";
|
||||
settings.types = Settings::Type::AllMask;
|
||||
settings.fullChats = Settings::Type::AllMask
|
||||
& ~(Settings::Type::PublicChannels | Settings::Type::PublicGroups);
|
||||
settings.media.types = MediaSettings::Type::AllMask;
|
||||
settings.media.sizeLimit = 1024 * 1024;
|
||||
|
||||
const auto check = [](Result result) {
|
||||
Assert(result.isSuccess());
|
||||
};
|
||||
|
||||
check(start(settings, environment, &result));
|
||||
|
||||
const auto counter = [&] {
|
||||
static auto GlobalCounter = 0;
|
||||
return ++GlobalCounter;
|
||||
};
|
||||
const auto date = [&] {
|
||||
return time(nullptr) - 86400 + counter();
|
||||
};
|
||||
const auto prevdate = [&] {
|
||||
return date() - 86400;
|
||||
};
|
||||
|
||||
auto personal = Data::PersonalInfo();
|
||||
personal.bio = "Nice text about me.";
|
||||
personal.user.info.firstName = "John";
|
||||
personal.user.info.lastName = "Preston";
|
||||
personal.user.info.phoneNumber = "447400000000";
|
||||
personal.user.info.date = date();
|
||||
personal.user.username = "preston";
|
||||
personal.user.info.userId = counter();
|
||||
personal.user.isBot = false;
|
||||
personal.user.isSelf = true;
|
||||
check(writePersonal(personal));
|
||||
|
||||
const auto generatePhoto = [&] {
|
||||
static auto index = 0;
|
||||
auto result = Data::Photo();
|
||||
result.date = date();
|
||||
result.id = counter();
|
||||
result.image.width = 512;
|
||||
result.image.height = 512;
|
||||
result.image.file.relativePath = "files/photo_"
|
||||
+ QString::number(++index)
|
||||
+ ".jpg";
|
||||
return result;
|
||||
};
|
||||
|
||||
auto userpics = Data::UserpicsInfo();
|
||||
userpics.count = 3;
|
||||
auto userpicsSlice1 = Data::UserpicsSlice();
|
||||
userpicsSlice1.list.push_back(generatePhoto());
|
||||
userpicsSlice1.list.push_back(generatePhoto());
|
||||
auto userpicsSlice2 = Data::UserpicsSlice();
|
||||
userpicsSlice2.list.push_back(generatePhoto());
|
||||
check(writeUserpicsStart(userpics));
|
||||
check(writeUserpicsSlice(userpicsSlice1));
|
||||
check(writeUserpicsSlice(userpicsSlice2));
|
||||
check(writeUserpicsEnd());
|
||||
|
||||
auto contacts = Data::ContactsList();
|
||||
auto topUser = Data::TopPeer();
|
||||
auto user = personal.user;
|
||||
auto peerUser = Data::Peer{ user };
|
||||
topUser.peer = peerUser;
|
||||
topUser.rating = 0.5;
|
||||
auto topChat = Data::TopPeer();
|
||||
auto chat = Data::Chat();
|
||||
chat.bareId = counter();
|
||||
chat.title = "Group chat";
|
||||
auto peerChat = Data::Peer{ chat };
|
||||
topChat.peer = peerChat;
|
||||
topChat.rating = 0.25;
|
||||
auto topBot = Data::TopPeer();
|
||||
auto bot = Data::User();
|
||||
bot.info.date = date();
|
||||
bot.isBot = true;
|
||||
bot.info.firstName = "Bot";
|
||||
bot.info.lastName = "Father";
|
||||
bot.info.userId = counter();
|
||||
bot.username = "botfather";
|
||||
auto peerBot = Data::Peer{ bot };
|
||||
topBot.peer = peerBot;
|
||||
topBot.rating = 0.125;
|
||||
|
||||
auto peers = std::map<PeerId, Data::Peer>();
|
||||
peers.emplace(peerUser.id(), peerUser);
|
||||
peers.emplace(peerBot.id(), peerBot);
|
||||
peers.emplace(peerChat.id(), peerChat);
|
||||
|
||||
contacts.correspondents.push_back(topUser);
|
||||
contacts.correspondents.push_back(topChat);
|
||||
contacts.inlineBots.push_back(topBot);
|
||||
contacts.inlineBots.push_back(topBot);
|
||||
contacts.phoneCalls.push_back(topUser);
|
||||
contacts.list.push_back(user.info);
|
||||
contacts.list.push_back(bot.info);
|
||||
|
||||
check(writeContactsList(contacts));
|
||||
|
||||
auto sessions = Data::SessionsList();
|
||||
auto session = Data::Session();
|
||||
session.applicationName = "Telegram Desktop";
|
||||
session.applicationVersion = "1.3.8";
|
||||
session.country = "GB";
|
||||
session.created = date();
|
||||
session.deviceModel = "PC";
|
||||
session.ip = "127.0.0.1";
|
||||
session.lastActive = date();
|
||||
session.platform = "Windows";
|
||||
session.region = "London";
|
||||
session.systemVersion = "10";
|
||||
sessions.list.push_back(session);
|
||||
sessions.list.push_back(session);
|
||||
auto webSession = Data::WebSession();
|
||||
webSession.botUsername = "botfather";
|
||||
webSession.browser = "Google Chrome";
|
||||
webSession.created = date();
|
||||
webSession.domain = "telegram.org";
|
||||
webSession.ip = "127.0.0.1";
|
||||
webSession.lastActive = date();
|
||||
webSession.platform = "Windows";
|
||||
webSession.region = "London, GB";
|
||||
sessions.webList.push_back(webSession);
|
||||
sessions.webList.push_back(webSession);
|
||||
check(writeSessionsList(sessions));
|
||||
|
||||
auto sampleMessage = [&] {
|
||||
auto message = Data::Message();
|
||||
message.id = counter();
|
||||
message.date = prevdate();
|
||||
message.edited = date();
|
||||
static auto count = 0;
|
||||
if (++count % 3 == 0) {
|
||||
message.forwardedFromId = peerFromUser(user.info.userId);
|
||||
message.forwardedDate = date();
|
||||
} else if (count % 3 == 2) {
|
||||
message.forwardedFromName = "Test hidden forward";
|
||||
message.forwardedDate = date();
|
||||
}
|
||||
message.fromId = user.info.userId;
|
||||
message.replyToMsgId = counter();
|
||||
message.viaBotId = bot.info.userId;
|
||||
message.text.push_back(Data::TextPart{
|
||||
Data::TextPart::Type::Text,
|
||||
("Text message " + QString::number(counter())).toUtf8()
|
||||
});
|
||||
return message;
|
||||
};
|
||||
auto sliceBot1 = Data::MessagesSlice();
|
||||
sliceBot1.peers = peers;
|
||||
sliceBot1.list.push_back(sampleMessage());
|
||||
sliceBot1.list.push_back([&] {
|
||||
auto message = sampleMessage();
|
||||
message.media.content = generatePhoto();
|
||||
message.media.ttl = counter();
|
||||
return message;
|
||||
}());
|
||||
sliceBot1.list.push_back([&] {
|
||||
auto message = sampleMessage();
|
||||
auto document = Data::Document();
|
||||
document.date = prevdate();
|
||||
document.duration = counter();
|
||||
auto photo = generatePhoto();
|
||||
document.file = photo.image.file;
|
||||
document.width = photo.image.width;
|
||||
document.height = photo.image.height;
|
||||
document.id = counter();
|
||||
message.media.content = document;
|
||||
return message;
|
||||
}());
|
||||
sliceBot1.list.push_back([&] {
|
||||
auto message = sampleMessage();
|
||||
auto contact = Data::SharedContact();
|
||||
contact.info = user.info;
|
||||
message.media.content = contact;
|
||||
return message;
|
||||
}());
|
||||
auto sliceBot2 = Data::MessagesSlice();
|
||||
sliceBot2.peers = peers;
|
||||
sliceBot2.list.push_back([&] {
|
||||
auto message = sampleMessage();
|
||||
auto point = Data::GeoPoint();
|
||||
point.latitude = 1.5;
|
||||
point.longitude = 2.8;
|
||||
point.valid = true;
|
||||
message.media.content = point;
|
||||
message.media.ttl = counter();
|
||||
return message;
|
||||
}());
|
||||
sliceBot2.list.push_back([&] {
|
||||
auto message = sampleMessage();
|
||||
message.replyToMsgId = sliceBot1.list.back().id;
|
||||
auto venue = Data::Venue();
|
||||
venue.point.latitude = 1.5;
|
||||
venue.point.longitude = 2.8;
|
||||
venue.point.valid = true;
|
||||
venue.address = "Test address";
|
||||
venue.title = "Test venue";
|
||||
message.media.content = venue;
|
||||
return message;
|
||||
}());
|
||||
sliceBot2.list.push_back([&] {
|
||||
auto message = sampleMessage();
|
||||
auto game = Data::Game();
|
||||
game.botId = bot.info.userId;
|
||||
game.title = "Test game";
|
||||
game.description = "Test game description";
|
||||
game.id = counter();
|
||||
game.shortName = "testgame";
|
||||
message.media.content = game;
|
||||
return message;
|
||||
}());
|
||||
sliceBot2.list.push_back([&] {
|
||||
auto message = sampleMessage();
|
||||
auto invoice = Data::Invoice();
|
||||
invoice.amount = counter();
|
||||
invoice.currency = "GBP";
|
||||
invoice.title = "Huge invoice.";
|
||||
invoice.description = "So money.";
|
||||
invoice.receiptMsgId = sliceBot2.list.front().id;
|
||||
message.media.content = invoice;
|
||||
return message;
|
||||
}());
|
||||
auto serviceMessage = [&] {
|
||||
auto message = Data::Message();
|
||||
message.id = counter();
|
||||
message.date = prevdate();
|
||||
message.fromId = user.info.userId;
|
||||
return message;
|
||||
};
|
||||
auto sliceChat1 = Data::MessagesSlice();
|
||||
sliceChat1.peers = peers;
|
||||
sliceChat1.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionChatCreate();
|
||||
action.title = "Test chat";
|
||||
action.userIds.push_back(user.info.userId);
|
||||
action.userIds.push_back(bot.info.userId);
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat1.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionChatEditTitle();
|
||||
action.title = "New title";
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat1.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionChatEditPhoto();
|
||||
action.photo = generatePhoto();
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat1.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionChatDeletePhoto();
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat1.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionChatAddUser();
|
||||
action.userIds.push_back(user.info.userId);
|
||||
action.userIds.push_back(bot.info.userId);
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat1.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionChatDeleteUser();
|
||||
action.userId = bot.info.userId;
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat1.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionChatJoinedByLink();
|
||||
action.inviterId = bot.info.userId;
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat1.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionChannelCreate();
|
||||
action.title = "Channel name";
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat1.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionChatMigrateTo();
|
||||
action.channelId = ChannelId(chat.bareId);
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat1.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionChannelMigrateFrom();
|
||||
action.chatId = ChatId(chat.bareId);
|
||||
action.title = "Supergroup now";
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
auto sliceChat2 = Data::MessagesSlice();
|
||||
sliceChat2.peers = peers;
|
||||
sliceChat2.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionPinMessage();
|
||||
message.replyToMsgId = sliceChat1.list.back().id;
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat2.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionHistoryClear();
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat2.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionGameScore();
|
||||
action.score = counter();
|
||||
action.gameId = counter();
|
||||
message.replyToMsgId = sliceChat2.list.back().id;
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat2.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionPaymentSent();
|
||||
action.amount = counter();
|
||||
action.currency = "GBP";
|
||||
message.replyToMsgId = sliceChat2.list.front().id;
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat2.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionPhoneCall();
|
||||
action.duration = counter();
|
||||
action.state = Data::ActionPhoneCall::State::Busy;
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat2.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionScreenshotTaken();
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat2.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionCustomAction();
|
||||
action.message = "Custom chat action.";
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat2.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionBotAllowed();
|
||||
action.domain = "telegram.org";
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat2.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionSecureValuesSent();
|
||||
using Type = Data::ActionSecureValuesSent::Type;
|
||||
action.types.push_back(Type::BankStatement);
|
||||
action.types.push_back(Type::Phone);
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
sliceChat2.list.push_back([&] {
|
||||
auto message = serviceMessage();
|
||||
auto action = Data::ActionContactSignUp();
|
||||
message.action.content = action;
|
||||
return message;
|
||||
}());
|
||||
auto dialogs = Data::DialogsInfo();
|
||||
auto dialogBot = Data::DialogInfo();
|
||||
dialogBot.messagesCountPerSplit.push_back(sliceBot1.list.size());
|
||||
dialogBot.messagesCountPerSplit.push_back(sliceBot2.list.size());
|
||||
dialogBot.type = Data::DialogInfo::Type::Bot;
|
||||
dialogBot.name = peerBot.name();
|
||||
dialogBot.onlyMyMessages = false;
|
||||
dialogBot.peerId = peerBot.id();
|
||||
dialogBot.relativePath = "chats/chat_"
|
||||
+ QString::number(counter())
|
||||
+ '/';
|
||||
dialogBot.splits.push_back(0);
|
||||
dialogBot.splits.push_back(1);
|
||||
dialogBot.topMessageDate = sliceBot2.list.back().date;
|
||||
dialogBot.topMessageId = sliceBot2.list.back().id;
|
||||
auto dialogChat = Data::DialogInfo();
|
||||
dialogChat.messagesCountPerSplit.push_back(sliceChat1.list.size());
|
||||
dialogChat.messagesCountPerSplit.push_back(sliceChat2.list.size());
|
||||
dialogChat.type = Data::DialogInfo::Type::PrivateGroup;
|
||||
dialogChat.name = peerChat.name();
|
||||
dialogChat.onlyMyMessages = true;
|
||||
dialogChat.peerId = peerChat.id();
|
||||
dialogChat.relativePath = "chats/chat_"
|
||||
+ QString::number(counter())
|
||||
+ '/';
|
||||
dialogChat.splits.push_back(0);
|
||||
dialogChat.splits.push_back(1);
|
||||
dialogChat.topMessageDate = sliceChat2.list.back().date;
|
||||
dialogChat.topMessageId = sliceChat2.list.back().id;
|
||||
dialogs.chats.push_back(dialogBot);
|
||||
dialogs.chats.push_back(dialogChat);
|
||||
|
||||
check(writeDialogsStart(dialogs));
|
||||
check(writeDialogStart(dialogBot));
|
||||
check(writeDialogSlice(sliceBot1));
|
||||
check(writeDialogSlice(sliceBot2));
|
||||
check(writeDialogEnd());
|
||||
check(writeDialogStart(dialogChat));
|
||||
check(writeDialogSlice(sliceChat1));
|
||||
check(writeDialogSlice(sliceChat2));
|
||||
check(writeDialogEnd());
|
||||
check(writeDialogsEnd());
|
||||
|
||||
check(finish());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Output
|
||||
} // namespace Export
|
||||
108
Telegram/SourceFiles/export/output/export_output_abstract.h
Normal file
108
Telegram/SourceFiles/export/output/export_output_abstract.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
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 <QtCore/QString>
|
||||
|
||||
namespace Export {
|
||||
namespace Data {
|
||||
struct PersonalInfo;
|
||||
struct UserpicsInfo;
|
||||
struct UserpicsSlice;
|
||||
struct StoriesInfo;
|
||||
struct StoriesSlice;
|
||||
struct ProfileMusicInfo;
|
||||
struct ProfileMusicSlice;
|
||||
struct ContactsList;
|
||||
struct SessionsList;
|
||||
struct DialogsInfo;
|
||||
struct DialogInfo;
|
||||
struct MessagesSlice;
|
||||
struct File;
|
||||
} // namespace Data
|
||||
|
||||
struct Settings;
|
||||
struct Environment;
|
||||
|
||||
namespace Output {
|
||||
|
||||
QString NormalizePath(const Settings &settings);
|
||||
|
||||
struct Result;
|
||||
class Stats;
|
||||
|
||||
enum class Format {
|
||||
Html,
|
||||
Json,
|
||||
HtmlAndJson,
|
||||
};
|
||||
|
||||
class AbstractWriter {
|
||||
public:
|
||||
[[nodiscard]] virtual Format format() = 0;
|
||||
|
||||
[[nodiscard]] virtual Result start(
|
||||
const Settings &settings,
|
||||
const Environment &environment,
|
||||
Stats *stats) = 0;
|
||||
|
||||
[[nodiscard]] virtual Result writePersonal(
|
||||
const Data::PersonalInfo &data) = 0;
|
||||
|
||||
[[nodiscard]] virtual Result writeUserpicsStart(
|
||||
const Data::UserpicsInfo &data) = 0;
|
||||
[[nodiscard]] virtual Result writeUserpicsSlice(
|
||||
const Data::UserpicsSlice &data) = 0;
|
||||
[[nodiscard]] virtual Result writeUserpicsEnd() = 0;
|
||||
|
||||
[[nodiscard]] virtual Result writeStoriesStart(
|
||||
const Data::StoriesInfo &data) = 0;
|
||||
[[nodiscard]] virtual Result writeStoriesSlice(
|
||||
const Data::StoriesSlice &data) = 0;
|
||||
[[nodiscard]] virtual Result writeStoriesEnd() = 0;
|
||||
|
||||
[[nodiscard]] virtual Result writeProfileMusicStart(
|
||||
const Data::ProfileMusicInfo &data) = 0;
|
||||
[[nodiscard]] virtual Result writeProfileMusicSlice(
|
||||
const Data::ProfileMusicSlice &data) = 0;
|
||||
[[nodiscard]] virtual Result writeProfileMusicEnd() = 0;
|
||||
|
||||
[[nodiscard]] virtual Result writeContactsList(
|
||||
const Data::ContactsList &data) = 0;
|
||||
|
||||
[[nodiscard]] virtual Result writeSessionsList(
|
||||
const Data::SessionsList &data) = 0;
|
||||
|
||||
[[nodiscard]] virtual Result writeOtherData(
|
||||
const Data::File &data) = 0;
|
||||
|
||||
[[nodiscard]] virtual Result writeDialogsStart(
|
||||
const Data::DialogsInfo &data) = 0;
|
||||
[[nodiscard]] virtual Result writeDialogStart(
|
||||
const Data::DialogInfo &data) = 0;
|
||||
[[nodiscard]] virtual Result writeDialogSlice(
|
||||
const Data::MessagesSlice &data) = 0;
|
||||
[[nodiscard]] virtual Result writeDialogEnd() = 0;
|
||||
[[nodiscard]] virtual Result writeDialogsEnd() = 0;
|
||||
|
||||
[[nodiscard]] virtual Result finish() = 0;
|
||||
|
||||
[[nodiscard]] virtual QString mainFilePath() = 0;
|
||||
|
||||
virtual ~AbstractWriter() = default;
|
||||
|
||||
Stats produceTestExample(
|
||||
const QString &path,
|
||||
const Environment &environment);
|
||||
|
||||
};
|
||||
|
||||
std::unique_ptr<AbstractWriter> CreateWriter(Format format);
|
||||
|
||||
} // namespace Output
|
||||
} // namespace Export
|
||||
140
Telegram/SourceFiles/export/output/export_output_file.cpp
Normal file
140
Telegram/SourceFiles/export/output/export_output_file.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
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 "export/output/export_output_file.h"
|
||||
|
||||
#include "export/output/export_output_result.h"
|
||||
#include "export/output/export_output_stats.h"
|
||||
#include "base/qt/qt_string_view.h"
|
||||
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QDir>
|
||||
|
||||
#include <gsl/util>
|
||||
|
||||
namespace Export {
|
||||
namespace Output {
|
||||
|
||||
File::File(const QString &path, Stats *stats) : _path(path), _stats(stats) {
|
||||
}
|
||||
|
||||
int64 File::size() const {
|
||||
return _offset;
|
||||
}
|
||||
|
||||
bool File::empty() const {
|
||||
return !_offset;
|
||||
}
|
||||
|
||||
Result File::writeBlock(const QByteArray &block) {
|
||||
const auto result = writeBlockAttempt(block);
|
||||
if (!result) {
|
||||
_file.reset();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Result File::writeBlockAttempt(const QByteArray &block) {
|
||||
if (_stats && !_inStats) {
|
||||
_inStats = true;
|
||||
_stats->incrementFiles();
|
||||
}
|
||||
if (const auto result = reopen(); !result) {
|
||||
return result;
|
||||
}
|
||||
const auto size = block.size();
|
||||
if (!size) {
|
||||
return Result::Success();
|
||||
}
|
||||
if (_file->write(block) == size && _file->flush()) {
|
||||
_offset += size;
|
||||
if (_stats) {
|
||||
_stats->incrementBytes(size);
|
||||
}
|
||||
return Result::Success();
|
||||
}
|
||||
return error();
|
||||
}
|
||||
|
||||
Result File::reopen() {
|
||||
if (_file && _file->isOpen()) {
|
||||
return Result::Success();
|
||||
}
|
||||
_file.emplace(_path);
|
||||
if (_file->exists()) {
|
||||
if (_file->size() < _offset) {
|
||||
return fatalError();
|
||||
} else if (!_file->resize(_offset)) {
|
||||
return error();
|
||||
}
|
||||
} else if (_offset > 0) {
|
||||
return fatalError();
|
||||
}
|
||||
if (_file->open(QIODevice::Append)) {
|
||||
return Result::Success();
|
||||
}
|
||||
const auto info = QFileInfo(_path);
|
||||
const auto dir = info.absoluteDir();
|
||||
return (!dir.exists()
|
||||
&& dir.mkpath(dir.absolutePath())
|
||||
&& _file->open(QIODevice::Append))
|
||||
? Result::Success()
|
||||
: error();
|
||||
}
|
||||
|
||||
Result File::error() const {
|
||||
return Result(Result::Type::Error, _path);
|
||||
}
|
||||
|
||||
Result File::fatalError() const {
|
||||
return Result(Result::Type::FatalError, _path);
|
||||
}
|
||||
|
||||
QString File::PrepareRelativePath(
|
||||
const QString &folder,
|
||||
const QString &suggested) {
|
||||
if (!QFile::exists(folder + suggested)) {
|
||||
return suggested;
|
||||
}
|
||||
|
||||
// Not lastIndexOf('.') so that "file.tar.xz" won't be messed up.
|
||||
const auto position = suggested.indexOf('.');
|
||||
const auto base = suggested.mid(0, position);
|
||||
const auto extension = (position >= 0)
|
||||
? base::StringViewMid(suggested, position)
|
||||
: QStringView();
|
||||
const auto relativePart = [&](int attempt) {
|
||||
auto result = base + QString(" (%1)").arg(attempt);
|
||||
result.append(extension);
|
||||
return result;
|
||||
};
|
||||
auto attempt = 0;
|
||||
while (true) {
|
||||
const auto relativePath = relativePart(++attempt);
|
||||
if (!QFile::exists(folder + relativePath)) {
|
||||
return relativePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result File::Copy(
|
||||
const QString &source,
|
||||
const QString &path,
|
||||
Stats *stats) {
|
||||
QFile f(source);
|
||||
if (!f.exists() || !f.open(QIODevice::ReadOnly)) {
|
||||
return Result(Result::Type::FatalError, source);
|
||||
}
|
||||
const auto bytes = f.readAll();
|
||||
if (bytes.size() != f.size()) {
|
||||
return Result(Result::Type::FatalError, source);
|
||||
}
|
||||
return File(path, stats).writeBlock(bytes);
|
||||
}
|
||||
|
||||
} // namespace Output
|
||||
} // namespace File
|
||||
57
Telegram/SourceFiles/export/output/export_output_file.h
Normal file
57
Telegram/SourceFiles/export/output/export_output_file.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
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 "base/optional.h"
|
||||
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QByteArray>
|
||||
|
||||
namespace Export {
|
||||
namespace Output {
|
||||
|
||||
struct Result;
|
||||
class Stats;
|
||||
|
||||
class File {
|
||||
public:
|
||||
File(const QString &path, Stats *stats);
|
||||
|
||||
[[nodiscard]] int64 size() const;
|
||||
[[nodiscard]] bool empty() const;
|
||||
|
||||
[[nodiscard]] Result writeBlock(const QByteArray &block);
|
||||
|
||||
[[nodiscard]] static QString PrepareRelativePath(
|
||||
const QString &folder,
|
||||
const QString &suggested);
|
||||
|
||||
[[nodiscard]] static Result Copy(
|
||||
const QString &source,
|
||||
const QString &path,
|
||||
Stats *stats);
|
||||
|
||||
private:
|
||||
[[nodiscard]] Result reopen();
|
||||
[[nodiscard]] Result writeBlockAttempt(const QByteArray &block);
|
||||
|
||||
[[nodiscard]] Result error() const;
|
||||
[[nodiscard]] Result fatalError() const;
|
||||
|
||||
QString _path;
|
||||
int64 _offset = 0;
|
||||
std::optional<QFile> _file;
|
||||
|
||||
Stats *_stats = nullptr;
|
||||
bool _inStats = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Output
|
||||
} // namespace File
|
||||
3848
Telegram/SourceFiles/export/output/export_output_html.cpp
Normal file
3848
Telegram/SourceFiles/export/output/export_output_html.cpp
Normal file
File diff suppressed because it is too large
Load Diff
186
Telegram/SourceFiles/export/output/export_output_html.h
Normal file
186
Telegram/SourceFiles/export/output/export_output_html.h
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
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 "export/output/export_output_abstract.h"
|
||||
#include "export/output/export_output_file.h"
|
||||
#include "export/export_settings.h"
|
||||
#include "export/data/export_data_types.h"
|
||||
|
||||
namespace Export {
|
||||
namespace Output {
|
||||
namespace details {
|
||||
|
||||
class HtmlContext {
|
||||
public:
|
||||
[[nodiscard]] QByteArray pushTag(
|
||||
const QByteArray &tag,
|
||||
std::map<QByteArray, QByteArray> &&attributes = {});
|
||||
[[nodiscard]] QByteArray popTag();
|
||||
[[nodiscard]] QByteArray indent() const;
|
||||
[[nodiscard]] bool empty() const;
|
||||
|
||||
private:
|
||||
struct Tag {
|
||||
QByteArray name;
|
||||
bool block = true;
|
||||
};
|
||||
std::vector<Tag> _tags;
|
||||
|
||||
};
|
||||
|
||||
struct UserpicData;
|
||||
struct StoryData;
|
||||
class PeersMap;
|
||||
struct MediaData;
|
||||
|
||||
} // namespace details
|
||||
|
||||
class HtmlWriter : public AbstractWriter {
|
||||
public:
|
||||
HtmlWriter();
|
||||
|
||||
Format format() override {
|
||||
return Format::Html;
|
||||
}
|
||||
|
||||
Result start(
|
||||
const Settings &settings,
|
||||
const Environment &environment,
|
||||
Stats *stats) override;
|
||||
|
||||
Result writePersonal(const Data::PersonalInfo &data) override;
|
||||
|
||||
Result writeUserpicsStart(const Data::UserpicsInfo &data) override;
|
||||
Result writeUserpicsSlice(const Data::UserpicsSlice &data) override;
|
||||
Result writeUserpicsEnd() override;
|
||||
|
||||
Result writeStoriesStart(const Data::StoriesInfo &data) override;
|
||||
Result writeStoriesSlice(const Data::StoriesSlice &data) override;
|
||||
Result writeStoriesEnd() override;
|
||||
|
||||
Result writeProfileMusicStart(const Data::ProfileMusicInfo &data) override;
|
||||
Result writeProfileMusicSlice(const Data::ProfileMusicSlice &data) override;
|
||||
Result writeProfileMusicEnd() override;
|
||||
|
||||
Result writeContactsList(const Data::ContactsList &data) override;
|
||||
|
||||
Result writeSessionsList(const Data::SessionsList &data) override;
|
||||
|
||||
Result writeOtherData(const Data::File &data) override;
|
||||
|
||||
Result writeDialogsStart(const Data::DialogsInfo &data) override;
|
||||
Result writeDialogStart(const Data::DialogInfo &data) override;
|
||||
Result writeDialogSlice(const Data::MessagesSlice &data) override;
|
||||
Result writeDialogEnd() override;
|
||||
Result writeDialogsEnd() override;
|
||||
|
||||
Result finish() override;
|
||||
|
||||
QString mainFilePath() override;
|
||||
|
||||
~HtmlWriter();
|
||||
|
||||
private:
|
||||
using Context = details::HtmlContext;
|
||||
using UserpicData = details::UserpicData;
|
||||
using MediaData = details::MediaData;
|
||||
class Wrap;
|
||||
struct MessageInfo;
|
||||
enum class DialogsMode {
|
||||
None,
|
||||
Chats,
|
||||
Left,
|
||||
};
|
||||
|
||||
[[nodiscard]] Result copyFile(
|
||||
const QString &source,
|
||||
const QString &relativePath) const;
|
||||
|
||||
[[nodiscard]] QString mainFileRelativePath() const;
|
||||
[[nodiscard]] QString pathWithRelativePath(const QString &path) const;
|
||||
[[nodiscard]] std::unique_ptr<Wrap> fileWithRelativePath(
|
||||
const QString &path) const;
|
||||
[[nodiscard]] QString messagesFile(int index) const;
|
||||
|
||||
[[nodiscard]] Result writeSavedContacts(const Data::ContactsList &data);
|
||||
[[nodiscard]] Result writeFrequentContacts(const Data::ContactsList &data);
|
||||
|
||||
[[nodiscard]] Result writeSessions(const Data::SessionsList &data);
|
||||
[[nodiscard]] Result writeWebSessions(const Data::SessionsList &data);
|
||||
|
||||
[[nodiscard]] Result validateDialogsMode(bool isLeftChannel);
|
||||
[[nodiscard]] Result writeDialogOpening(int index);
|
||||
[[nodiscard]] Result switchToNextChatFile(int index);
|
||||
[[nodiscard]] Result writeEmptySinglePeer();
|
||||
|
||||
void pushSection(
|
||||
int priority,
|
||||
const QByteArray &label,
|
||||
const QByteArray &type,
|
||||
int count,
|
||||
const QString &path);
|
||||
[[nodiscard]] Result writeSections();
|
||||
|
||||
[[nodiscard]] Result writeDefaultPersonal(
|
||||
const Data::PersonalInfo &data);
|
||||
[[nodiscard]] Result writeDelayedPersonal(const QString &userpicPath);
|
||||
[[nodiscard]] Result writePreparedPersonal(
|
||||
const Data::PersonalInfo &data,
|
||||
const QString &userpicPath);
|
||||
void pushUserpicsSection();
|
||||
void pushStoriesSection();
|
||||
void pushProfileMusicSection();
|
||||
|
||||
[[nodiscard]] QString userpicsFilePath() const;
|
||||
[[nodiscard]] QString storiesFilePath() const;
|
||||
[[nodiscard]] QString profileMusicFilePath() const;
|
||||
|
||||
[[nodiscard]] QByteArray wrapMessageLink(
|
||||
int messageId,
|
||||
QByteArray text);
|
||||
|
||||
Settings _settings;
|
||||
Environment _environment;
|
||||
Stats *_stats = nullptr;
|
||||
|
||||
struct SavedSection;
|
||||
std::vector<SavedSection> _savedSections;
|
||||
|
||||
std::unique_ptr<Wrap> _summary;
|
||||
bool _summaryNeedDivider = false;
|
||||
bool _haveSections = false;
|
||||
|
||||
uint8 _selfColorIndex = 0;
|
||||
std::unique_ptr<Data::PersonalInfo> _delayedPersonalInfo;
|
||||
|
||||
int _userpicsCount = 0;
|
||||
std::unique_ptr<Wrap> _userpics;
|
||||
|
||||
int _storiesCount = 0;
|
||||
std::unique_ptr<Wrap> _stories;
|
||||
|
||||
int _profileMusicCount = 0;
|
||||
std::unique_ptr<Wrap> _profileMusic;
|
||||
|
||||
QString _dialogsRelativePath;
|
||||
Data::DialogInfo _dialog;
|
||||
DialogsMode _dialogsMode = DialogsMode::None;
|
||||
|
||||
int _messagesCount = 0;
|
||||
std::unique_ptr<MessageInfo> _lastMessageInfo;
|
||||
int _dateMessageId = 0;
|
||||
std::unique_ptr<Wrap> _chats;
|
||||
std::unique_ptr<Wrap> _chat;
|
||||
std::vector<int> _lastMessageIdsPerFile;
|
||||
bool _chatFileEmpty = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Output
|
||||
} // namespace Export
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
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 "export/output/export_output_html_and_json.h"
|
||||
|
||||
#include "export/output/export_output_html.h"
|
||||
#include "export/output/export_output_json.h"
|
||||
#include "export/output/export_output_result.h"
|
||||
|
||||
namespace Export::Output {
|
||||
|
||||
HtmlAndJsonWriter::HtmlAndJsonWriter() {
|
||||
_writers.push_back(CreateWriter(Format::Html));
|
||||
_writers.push_back(CreateWriter(Format::Json));
|
||||
}
|
||||
|
||||
Format HtmlAndJsonWriter::format() {
|
||||
return Format::HtmlAndJson;
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::start(
|
||||
const Settings &settings,
|
||||
const Environment &environment,
|
||||
Stats *stats) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->start(settings, environment, stats);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writePersonal(const Data::PersonalInfo &data) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writePersonal(data);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeUserpicsStart(const Data::UserpicsInfo &data) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeUserpicsStart(data);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeUserpicsSlice(const Data::UserpicsSlice &d) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeUserpicsSlice(d);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeUserpicsEnd() {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeUserpicsEnd();
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeStoriesStart(const Data::StoriesInfo &data) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeStoriesStart(data);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeStoriesSlice(const Data::StoriesSlice &data) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeStoriesSlice(data);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeStoriesEnd() {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeStoriesEnd();
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeProfileMusicStart(const Data::ProfileMusicInfo &data) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeProfileMusicStart(data);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeProfileMusicSlice(const Data::ProfileMusicSlice &data) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeProfileMusicSlice(data);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeProfileMusicEnd() {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeProfileMusicEnd();
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeContactsList(const Data::ContactsList &data) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeContactsList(data);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeSessionsList(const Data::SessionsList &data) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeSessionsList(data);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeOtherData(const Data::File &data) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeOtherData(data);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeDialogsStart(const Data::DialogsInfo &data) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeDialogsStart(data);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeDialogStart(const Data::DialogInfo &data) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeDialogStart(data);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeDialogSlice(const Data::MessagesSlice &data) {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeDialogSlice(data);
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeDialogEnd() {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeDialogEnd();
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::writeDialogsEnd() {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->writeDialogsEnd();
|
||||
});
|
||||
}
|
||||
|
||||
Result HtmlAndJsonWriter::finish() {
|
||||
return invoke([&](WriterPtr w) {
|
||||
return w->finish();
|
||||
});
|
||||
}
|
||||
|
||||
QString HtmlAndJsonWriter::mainFilePath() {
|
||||
return _writers.front()->mainFilePath();
|
||||
}
|
||||
|
||||
HtmlAndJsonWriter::~HtmlAndJsonWriter() = default;
|
||||
|
||||
Result HtmlAndJsonWriter::invoke(Fn<Result(WriterPtr)> method) const {
|
||||
auto result = Result(Result::Type::Success, QString());
|
||||
for (const auto &writer : _writers) {
|
||||
const auto current = method(writer);
|
||||
if (!current) {
|
||||
result = current;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Export::Output
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
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 "export/output/export_output_abstract.h"
|
||||
|
||||
namespace Export::Output {
|
||||
|
||||
class HtmlWriter;
|
||||
class JsonWriter;
|
||||
struct Result;
|
||||
|
||||
class HtmlAndJsonWriter final : public AbstractWriter {
|
||||
public:
|
||||
HtmlAndJsonWriter();
|
||||
|
||||
Format format() override;
|
||||
|
||||
Result start(
|
||||
const Settings &settings,
|
||||
const Environment &environment,
|
||||
Stats *stats) override;
|
||||
|
||||
Result writePersonal(const Data::PersonalInfo &data) override;
|
||||
|
||||
Result writeUserpicsStart(const Data::UserpicsInfo &data) override;
|
||||
Result writeUserpicsSlice(const Data::UserpicsSlice &data) override;
|
||||
Result writeUserpicsEnd() override;
|
||||
|
||||
Result writeStoriesStart(const Data::StoriesInfo &data) override;
|
||||
Result writeStoriesSlice(const Data::StoriesSlice &data) override;
|
||||
Result writeStoriesEnd() override;
|
||||
|
||||
Result writeProfileMusicStart(const Data::ProfileMusicInfo &data) override;
|
||||
Result writeProfileMusicSlice(const Data::ProfileMusicSlice &data) override;
|
||||
Result writeProfileMusicEnd() override;
|
||||
|
||||
Result writeContactsList(const Data::ContactsList &data) override;
|
||||
|
||||
Result writeSessionsList(const Data::SessionsList &data) override;
|
||||
|
||||
Result writeOtherData(const Data::File &data) override;
|
||||
|
||||
Result writeDialogsStart(const Data::DialogsInfo &data) override;
|
||||
Result writeDialogStart(const Data::DialogInfo &data) override;
|
||||
Result writeDialogSlice(const Data::MessagesSlice &data) override;
|
||||
Result writeDialogEnd() override;
|
||||
Result writeDialogsEnd() override;
|
||||
|
||||
Result finish() override;
|
||||
|
||||
QString mainFilePath() override;
|
||||
|
||||
~HtmlAndJsonWriter();
|
||||
|
||||
private:
|
||||
using WriterPtr = const std::unique_ptr<AbstractWriter> &;
|
||||
Result invoke(Fn<Result(WriterPtr)> method) const;
|
||||
|
||||
std::vector<std::unique_ptr<AbstractWriter>> _writers;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Export::Output
|
||||
1746
Telegram/SourceFiles/export/output/export_output_json.cpp
Normal file
1746
Telegram/SourceFiles/export/output/export_output_json.cpp
Normal file
File diff suppressed because it is too large
Load Diff
114
Telegram/SourceFiles/export/output/export_output_json.h
Normal file
114
Telegram/SourceFiles/export/output/export_output_json.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
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 "export/output/export_output_abstract.h"
|
||||
#include "export/output/export_output_file.h"
|
||||
#include "export/export_settings.h"
|
||||
#include "export/data/export_data_types.h"
|
||||
|
||||
namespace Export {
|
||||
namespace Output {
|
||||
namespace details {
|
||||
|
||||
struct JsonContext {
|
||||
using Type = bool;
|
||||
static constexpr auto kObject = Type(true);
|
||||
static constexpr auto kArray = Type(false);
|
||||
|
||||
// Always fun to use std::vector<bool>.
|
||||
std::vector<Type> nesting;
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
|
||||
class JsonWriter : public AbstractWriter {
|
||||
public:
|
||||
Format format() override {
|
||||
return Format::Json;
|
||||
}
|
||||
|
||||
Result start(
|
||||
const Settings &settings,
|
||||
const Environment &environment,
|
||||
Stats *stats) override;
|
||||
|
||||
Result writePersonal(const Data::PersonalInfo &data) override;
|
||||
|
||||
Result writeUserpicsStart(const Data::UserpicsInfo &data) override;
|
||||
Result writeUserpicsSlice(const Data::UserpicsSlice &data) override;
|
||||
Result writeUserpicsEnd() override;
|
||||
|
||||
Result writeStoriesStart(const Data::StoriesInfo &data) override;
|
||||
Result writeStoriesSlice(const Data::StoriesSlice &data) override;
|
||||
Result writeStoriesEnd() override;
|
||||
|
||||
Result writeProfileMusicStart(const Data::ProfileMusicInfo &data) override;
|
||||
Result writeProfileMusicSlice(const Data::ProfileMusicSlice &data) override;
|
||||
Result writeProfileMusicEnd() override;
|
||||
|
||||
Result writeContactsList(const Data::ContactsList &data) override;
|
||||
|
||||
Result writeSessionsList(const Data::SessionsList &data) override;
|
||||
|
||||
Result writeOtherData(const Data::File &data) override;
|
||||
|
||||
Result writeDialogsStart(const Data::DialogsInfo &data) override;
|
||||
Result writeDialogStart(const Data::DialogInfo &data) override;
|
||||
Result writeDialogSlice(const Data::MessagesSlice &data) override;
|
||||
Result writeDialogEnd() override;
|
||||
Result writeDialogsEnd() override;
|
||||
|
||||
Result finish() override;
|
||||
|
||||
QString mainFilePath() override;
|
||||
|
||||
private:
|
||||
using Context = details::JsonContext;
|
||||
enum class DialogsMode {
|
||||
None,
|
||||
Chats,
|
||||
Left,
|
||||
};
|
||||
|
||||
[[nodiscard]] QByteArray pushNesting(Context::Type type);
|
||||
[[nodiscard]] QByteArray prepareObjectItemStart(const QByteArray &key);
|
||||
[[nodiscard]] QByteArray prepareArrayItemStart();
|
||||
[[nodiscard]] QByteArray popNesting();
|
||||
|
||||
[[nodiscard]] QString mainFileRelativePath() const;
|
||||
[[nodiscard]] QString pathWithRelativePath(const QString &path) const;
|
||||
[[nodiscard]] std::unique_ptr<File> fileWithRelativePath(
|
||||
const QString &path) const;
|
||||
|
||||
[[nodiscard]] Result writeSavedContacts(const Data::ContactsList &data);
|
||||
[[nodiscard]] Result writeFrequentContacts(const Data::ContactsList &data);
|
||||
|
||||
[[nodiscard]] Result writeSessions(const Data::SessionsList &data);
|
||||
[[nodiscard]] Result writeWebSessions(const Data::SessionsList &data);
|
||||
|
||||
[[nodiscard]] Result validateDialogsMode(bool isLeftChannel);
|
||||
[[nodiscard]] Result writeChatsStart(
|
||||
const QByteArray &listName,
|
||||
const QByteArray &about);
|
||||
[[nodiscard]] Result writeChatsEnd();
|
||||
|
||||
Settings _settings;
|
||||
Environment _environment;
|
||||
Stats *_stats = nullptr;
|
||||
|
||||
Context _context;
|
||||
bool _currentNestingHadItem = false;
|
||||
DialogsMode _dialogsMode = DialogsMode::None;
|
||||
|
||||
std::unique_ptr<File> _output;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Output
|
||||
} // namespace Export
|
||||
48
Telegram/SourceFiles/export/output/export_output_result.h
Normal file
48
Telegram/SourceFiles/export/output/export_output_result.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
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 <QtCore/QString>
|
||||
|
||||
namespace Export {
|
||||
namespace Output {
|
||||
|
||||
struct Result {
|
||||
enum class Type : char {
|
||||
Success,
|
||||
Error,
|
||||
FatalError
|
||||
};
|
||||
|
||||
Result(Type type, QString path) : path(path), type(type) {
|
||||
}
|
||||
|
||||
static Result Success() {
|
||||
return Result(Type::Success, QString());
|
||||
}
|
||||
|
||||
bool isSuccess() const {
|
||||
return type == Type::Success;
|
||||
}
|
||||
bool isError() const {
|
||||
return (type == Type::Error) || (type == Type::FatalError);
|
||||
}
|
||||
bool isFatalError() const {
|
||||
return (type == Type::FatalError);
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return isSuccess();
|
||||
}
|
||||
|
||||
QString path;
|
||||
Type type;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Output
|
||||
} // namespace Export
|
||||
35
Telegram/SourceFiles/export/output/export_output_stats.cpp
Normal file
35
Telegram/SourceFiles/export/output/export_output_stats.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
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 "export/output/export_output_stats.h"
|
||||
|
||||
namespace Export {
|
||||
namespace Output {
|
||||
|
||||
Stats::Stats(const Stats &other)
|
||||
: _files(other._files.load())
|
||||
, _bytes(other._bytes.load()) {
|
||||
}
|
||||
|
||||
void Stats::incrementFiles() {
|
||||
++_files;
|
||||
}
|
||||
|
||||
void Stats::incrementBytes(int count) {
|
||||
_bytes += count;
|
||||
}
|
||||
|
||||
int Stats::filesCount() const {
|
||||
return _files;
|
||||
}
|
||||
|
||||
int64 Stats::bytesCount() const {
|
||||
return _bytes;
|
||||
}
|
||||
|
||||
} // namespace Output
|
||||
} // namespace Export
|
||||
33
Telegram/SourceFiles/export/output/export_output_stats.h
Normal file
33
Telegram/SourceFiles/export/output/export_output_stats.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 <atomic>
|
||||
|
||||
namespace Export {
|
||||
namespace Output {
|
||||
|
||||
class Stats {
|
||||
public:
|
||||
Stats() = default;
|
||||
Stats(const Stats &other);
|
||||
|
||||
void incrementFiles();
|
||||
void incrementBytes(int count);
|
||||
|
||||
int filesCount() const;
|
||||
int64 bytesCount() const;
|
||||
|
||||
private:
|
||||
std::atomic<int> _files;
|
||||
std::atomic<int64> _bytes;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Output
|
||||
} // namespace Export
|
||||
Reference in New Issue
Block a user