Added saved music to data export.

This commit is contained in:
23rd
2025-11-20 13:55:25 +03:00
parent f78a9b4220
commit f41fdcdb98
20 changed files with 612 additions and 4 deletions
@@ -440,6 +440,9 @@ div.toast_shown {
.section.stories {
background-image: url(../images/section_stories.png);
}
.section.music {
background-image: url(../images/section_music.png);
}
.section.web {
background-image: url(../images/section_web.png);
}
@@ -481,6 +484,16 @@ div.toast_shown {
.media_video .fill {
background-image: url(../images/media_video.png)
}
.audio_icon {
width: 48px;
height: 48px;
border-radius: 50%;
background-color: #4f9cd9;
background-image: url(../images/media_music.png);
background-repeat: no-repeat;
background-position: 12px 12px;
background-size: 24px 24px;
}
@media only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) {
.section.calls {
@@ -504,6 +517,9 @@ div.toast_shown {
.section.stories {
background-image: url(../images/section_stories@2x.png);
}
.section.music {
background-image: url(../images/section_music@2x.png);
}
.section.web {
background-image: url(../images/section_web@2x.png);
}
@@ -545,6 +561,9 @@ div.toast_shown {
.media_video .fill {
background-image: url(../images/media_video@2x.png)
}
.audio_icon {
background-image: url(../images/media_music@2x.png);
}
}
.spoiler {
Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

+2
View File
@@ -6304,6 +6304,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_export_option_contacts_about" = "If you allow access, contacts are continuously synced with Telegram. You can adjust this in Settings > Privacy & Security on mobile devices.";
"lng_export_option_stories" = "Story archive";
"lng_export_option_stories_about" = "All stories you posted from Telegram mobile apps.";
"lng_export_option_profile_music" = "Music on Profiles";
"lng_export_option_profile_music_about" = "All tracks you saved to your playlist.";
"lng_export_option_sessions" = "Active sessions";
"lng_export_option_sessions_about" = "We may store this to display your connected devices in Settings > Privacy & Security > Show all sessions.";
"lng_export_header_other" = "Other";
@@ -31,6 +31,8 @@
<file alias="images/section_contacts@2x.png">../../export_html/images/section_contacts@2x.png</file>
<file alias="images/section_frequent.png">../../export_html/images/section_frequent.png</file>
<file alias="images/section_frequent@2x.png">../../export_html/images/section_frequent@2x.png</file>
<file alias="images/section_music.png">../../export_html/images/section_music.png</file>
<file alias="images/section_music@2x.png">../../export_html/images/section_music@2x.png</file>
<file alias="images/section_other.png">../../export_html/images/section_other.png</file>
<file alias="images/section_other@2x.png">../../export_html/images/section_other@2x.png</file>
<file alias="images/section_photos.png">../../export_html/images/section_photos.png</file>
@@ -89,6 +89,10 @@ struct StoriesInfo {
int count = 0;
};
struct ProfileMusicInfo {
int count = 0;
};
struct FileLocation {
int dcId = 0;
MTPInputFileLocation data;
@@ -929,6 +933,11 @@ StoriesSlice ParseStoriesSlice(
const MTPVector<MTPStoryItem> &data,
int baseIndex);
struct ProfileMusicSlice {
std::vector<Message> list;
int skipped = 0;
};
Message ParseMessage(
ParseMediaContext &context,
const MTPMessage &data,
@@ -32,6 +32,7 @@ constexpr auto kFileMaxSize = 4000 * int64(1024 * 1024);
constexpr auto kLocationCacheSize = 100'000;
constexpr auto kMaxEmojiPerRequest = 100;
constexpr auto kStoriesSliceLimit = 100;
constexpr auto kProfileMusicSliceLimit = 100;
struct LocationKey {
uint64 type;
@@ -112,6 +113,7 @@ struct ApiWrap::StartProcess {
enum class Step {
UserpicsCount,
StoriesCount,
ProfileMusicCount,
SplitRanges,
DialogsCount,
LeftChannelsCount,
@@ -155,6 +157,19 @@ struct ApiWrap::StoriesProcess {
int fileIndex = 0;
};
struct ApiWrap::ProfileMusicProcess {
FnMut<bool(Data::ProfileMusicInfo&&)> start;
Fn<bool(DownloadProgress)> fileProgress;
Fn<bool(Data::ProfileMusicSlice&&)> handleSlice;
FnMut<void()> finish;
int processed = 0;
std::optional<Data::ProfileMusicSlice> slice;
int offsetId = 0;
bool lastSlice = false;
int fileIndex = 0;
};
struct ApiWrap::OtherDataProcess {
Data::File file;
FnMut<void(Data::File&&)> done;
@@ -438,6 +453,9 @@ void ApiWrap::startExport(
if (_settings->types & Settings::Type::Stories) {
_startProcess->steps.push_back(Step::StoriesCount);
}
if (_settings->types & Settings::Type::ProfileMusic) {
_startProcess->steps.push_back(Step::ProfileMusicCount);
}
if (_settings->types & Settings::Type::AnyChatsMask) {
_startProcess->steps.push_back(Step::SplitRanges);
_startProcess->steps.push_back(Step::DialogsCount);
@@ -468,6 +486,8 @@ void ApiWrap::sendNextStartRequest() {
return requestUserpicsCount();
case Step::StoriesCount:
return requestStoriesCount();
case Step::ProfileMusicCount:
return requestProfileMusicCount();
case Step::SplitRanges:
return requestSplitRanges();
case Step::DialogsCount:
@@ -518,6 +538,34 @@ void ApiWrap::requestStoriesCount() {
}).send();
}
void ApiWrap::requestProfileMusicCount() {
Expects(_startProcess != nullptr);
mainRequest(MTPusers_GetSavedMusic(
_user,
MTP_int(0), // offset
MTP_int(0), // limit
MTP_long(0) // hash
)).done([=](const MTPusers_SavedMusic &result) {
Expects(_settings != nullptr);
Expects(_startProcess != nullptr);
const auto count = result.match(
[](const MTPDusers_savedMusic &data) {
return data.vcount().v;
}, [](const MTPDusers_savedMusicNotModified &data) {
return -1;
});
if (count < 0) {
error("Unexpected messagesNotModified received.");
return;
}
_startProcess->info.profileMusicCount = count;
sendNextStartRequest();
}).send();
}
void ApiWrap::requestSplitRanges() {
Expects(_startProcess != nullptr);
@@ -1062,6 +1110,215 @@ void ApiWrap::finishStories() {
base::take(_storiesProcess)->finish();
}
void ApiWrap::requestProfileMusic(
FnMut<bool(Data::ProfileMusicInfo&&)> start,
Fn<bool(DownloadProgress)> progress,
Fn<bool(Data::ProfileMusicSlice&&)> slice,
FnMut<void()> finish) {
Expects(_profileMusicProcess == nullptr);
_profileMusicProcess = std::make_unique<ProfileMusicProcess>();
_profileMusicProcess->start = std::move(start);
_profileMusicProcess->fileProgress = std::move(progress);
_profileMusicProcess->handleSlice = std::move(slice);
_profileMusicProcess->finish = std::move(finish);
mainRequest(MTPusers_GetSavedMusic(
_user,
MTP_int(0), // offset
MTP_int(kProfileMusicSliceLimit), // limit
MTP_long(0) // hash
)).done([=](const MTPusers_SavedMusic &result) mutable {
Expects(_profileMusicProcess != nullptr);
auto startInfo = result.match(
[](const MTPDusers_savedMusic &data) {
return Data::ProfileMusicInfo{ data.vcount().v };
}, [](const MTPDusers_savedMusicNotModified &data) {
return Data::ProfileMusicInfo{ 0 };
});
if (!_profileMusicProcess->start(std::move(startInfo))) {
return;
}
handleProfileMusicSlice(result);
}).send();
}
void ApiWrap::handleProfileMusicSlice(const MTPusers_SavedMusic &result) {
Expects(_profileMusicProcess != nullptr);
Expects(_selfId.has_value());
auto context = Data::ParseMediaContext();
context.selfPeerId = peerFromUser(*_selfId);
auto slice = result.match([&](const MTPDusers_savedMusic &data) {
if (data.vdocuments().v.size() < kProfileMusicSliceLimit) {
_profileMusicProcess->lastSlice = true;
}
auto result = Data::MessagesSlice();
for (const auto &doc : data.vdocuments().v) {
auto message = Data::Message();
message.id = ++_profileMusicProcess->processed;
message.date = 0;
message.media.content = Data::ParseDocument(
context,
doc,
"profile_music/",
0);
result.list.push_back(std::move(message));
}
return result;
}, [&](const MTPDusers_savedMusicNotModified &) {
_profileMusicProcess->lastSlice = true;
return Data::MessagesSlice();
});
auto profileSlice = Data::ProfileMusicSlice();
profileSlice.list.reserve(slice.list.size());
for (auto &message : slice.list) {
if (v::is<Data::Document>(message.media.content)) {
const auto &doc = v::get<Data::Document>(message.media.content);
if (doc.isAudioFile) {
profileSlice.list.push_back(std::move(message));
}
}
}
loadProfileMusicFiles(std::move(profileSlice));
}
void ApiWrap::loadProfileMusicFiles(Data::ProfileMusicSlice &&slice) {
Expects(_profileMusicProcess != nullptr);
Expects(!_profileMusicProcess->slice.has_value());
if (slice.list.empty()) {
_profileMusicProcess->lastSlice = true;
}
_profileMusicProcess->slice = std::move(slice);
_profileMusicProcess->fileIndex = 0;
loadNextProfileMusic();
}
void ApiWrap::loadNextProfileMusic() {
Expects(_profileMusicProcess != nullptr);
Expects(_profileMusicProcess->slice.has_value());
for (auto &list = _profileMusicProcess->slice->list
; _profileMusicProcess->fileIndex < list.size()
; ++_profileMusicProcess->fileIndex) {
auto &message = list[_profileMusicProcess->fileIndex];
const auto origin = Data::FileOrigin{ .messageId = message.id };
const auto ready = processFileLoad(
message.file(),
origin,
[=](FileProgress value) { return loadProfileMusicProgress(value); },
[=](const QString &path) { loadProfileMusicDone(path); },
&message);
if (!ready) {
return;
}
const auto thumbProgress = [=](FileProgress value) {
return loadProfileMusicThumbProgress(value);
};
const auto thumbReady = processFileLoad(
message.thumb().file,
origin,
thumbProgress,
[=](const QString &path) { loadProfileMusicThumbDone(path); },
&message);
if (!thumbReady) {
return;
}
}
finishProfileMusicSlice();
}
void ApiWrap::finishProfileMusicSlice() {
Expects(_profileMusicProcess != nullptr);
Expects(_profileMusicProcess->slice.has_value());
auto slice = *base::take(_profileMusicProcess->slice);
if (!slice.list.empty()) {
_profileMusicProcess->processed += slice.list.size();
_profileMusicProcess->offsetId = slice.list.back().id;
if (!_profileMusicProcess->handleSlice(std::move(slice))) {
return;
}
}
if (_profileMusicProcess->lastSlice) {
finishProfileMusic();
return;
}
mainRequest(MTPusers_GetSavedMusic(
_user,
MTP_int(_profileMusicProcess->offsetId),
MTP_int(kProfileMusicSliceLimit),
MTP_long(0)
)).done([=](const MTPusers_SavedMusic &result) {
handleProfileMusicSlice(result);
}).send();
}
bool ApiWrap::loadProfileMusicProgress(FileProgress progress) {
Expects(_fileProcess != nullptr);
Expects(_profileMusicProcess != nullptr);
Expects(_profileMusicProcess->slice.has_value());
Expects((_profileMusicProcess->fileIndex >= 0)
&& (_profileMusicProcess->fileIndex
< _profileMusicProcess->slice->list.size()));
return _profileMusicProcess->fileProgress(DownloadProgress{
_fileProcess->randomId,
_fileProcess->relativePath,
_profileMusicProcess->fileIndex,
progress.ready,
progress.total });
}
void ApiWrap::loadProfileMusicDone(const QString &relativePath) {
Expects(_profileMusicProcess != nullptr);
Expects(_profileMusicProcess->slice.has_value());
Expects((_profileMusicProcess->fileIndex >= 0)
&& (_profileMusicProcess->fileIndex
< _profileMusicProcess->slice->list.size()));
const auto index = _profileMusicProcess->fileIndex;
auto &file = _profileMusicProcess->slice->list[index].file();
file.relativePath = relativePath;
if (relativePath.isEmpty()) {
file.skipReason = Data::File::SkipReason::Unavailable;
}
loadNextProfileMusic();
}
bool ApiWrap::loadProfileMusicThumbProgress(FileProgress progress) {
return loadProfileMusicProgress(progress);
}
void ApiWrap::loadProfileMusicThumbDone(const QString &relativePath) {
Expects(_profileMusicProcess != nullptr);
Expects(_profileMusicProcess->slice.has_value());
Expects((_profileMusicProcess->fileIndex >= 0)
&& (_profileMusicProcess->fileIndex
< _profileMusicProcess->slice->list.size()));
const auto index = _profileMusicProcess->fileIndex;
auto &file = _profileMusicProcess->slice->list[index].thumb().file;
file.relativePath = relativePath;
if (relativePath.isEmpty()) {
file.skipReason = Data::File::SkipReason::Unavailable;
}
loadNextProfileMusic();
}
void ApiWrap::finishProfileMusic() {
Expects(_profileMusicProcess != nullptr);
base::take(_profileMusicProcess)->finish();
}
void ApiWrap::requestContacts(FnMut<void(Data::ContactsList&&)> done) {
Expects(_contactsProcess == nullptr);
@@ -21,6 +21,8 @@ struct UserpicsInfo;
struct UserpicsSlice;
struct StoriesInfo;
struct StoriesSlice;
struct ProfileMusicInfo;
struct ProfileMusicSlice;
struct ContactsList;
struct SessionsList;
struct DialogsInfo;
@@ -50,6 +52,7 @@ public:
struct StartInfo {
int userpicsCount = 0;
int storiesCount = 0;
int profileMusicCount = 0;
int dialogsCount = 0;
};
void startExport(
@@ -86,6 +89,12 @@ public:
Fn<bool(Data::StoriesSlice&&)> slice,
FnMut<void()> finish);
void requestProfileMusic(
FnMut<bool(Data::ProfileMusicInfo&&)> start,
Fn<bool(DownloadProgress)> progress,
Fn<bool(Data::ProfileMusicSlice&&)> slice,
FnMut<void()> finish);
void requestContacts(FnMut<void(Data::ContactsList&&)> done);
void requestSessions(FnMut<void(Data::SessionsList&&)> done);
@@ -109,6 +118,7 @@ private:
struct ContactsProcess;
struct UserpicsProcess;
struct StoriesProcess;
struct ProfileMusicProcess;
struct OtherDataProcess;
struct FileProcess;
struct FileProgress;
@@ -121,6 +131,7 @@ private:
void sendNextStartRequest();
void requestUserpicsCount();
void requestStoriesCount();
void requestProfileMusicCount();
void requestSplitRanges();
void requestDialogsCount();
void requestLeftChannelsCount();
@@ -146,6 +157,16 @@ private:
void finishStoriesSlice();
void finishStories();
void handleProfileMusicSlice(const MTPusers_SavedMusic &result);
void loadProfileMusicFiles(Data::ProfileMusicSlice &&slice);
void loadNextProfileMusic();
bool loadProfileMusicProgress(FileProgress value);
void loadProfileMusicDone(const QString &relativePath);
bool loadProfileMusicThumbProgress(FileProgress value);
void loadProfileMusicThumbDone(const QString &relativePath);
void finishProfileMusicSlice();
void finishProfileMusic();
void otherDataDone(const QString &relativePath);
bool useOnlyLastSplit() const;
@@ -258,6 +279,7 @@ private:
std::unique_ptr<ContactsProcess> _contactsProcess;
std::unique_ptr<UserpicsProcess> _userpicsProcess;
std::unique_ptr<StoriesProcess> _storiesProcess;
std::unique_ptr<ProfileMusicProcess> _profileMusicProcess;
std::unique_ptr<OtherDataProcess> _otherDataProcess;
std::unique_ptr<FileProcess> _fileProcess;
std::unique_ptr<LeftChannelsProcess> _leftChannelsProcess;
@@ -76,6 +76,7 @@ private:
void exportPersonalInfo();
void exportUserpics();
void exportStories();
void exportProfileMusic();
void exportContacts();
void exportSessions();
void exportOtherData();
@@ -91,6 +92,7 @@ private:
ProcessingState statePersonalInfo() const;
ProcessingState stateUserpics(const DownloadProgress &progress) const;
ProcessingState stateStories(const DownloadProgress &progress) const;
ProcessingState stateProfileMusic(const DownloadProgress &progress) const;
ProcessingState stateContacts() const;
ProcessingState stateSessions() const;
ProcessingState stateOtherData() const;
@@ -119,6 +121,9 @@ private:
int _storiesWritten = 0;
int _storiesCount = 0;
int _profileMusicWritten = 0;
int _profileMusicCount = 0;
// rpl::variable<State> fails to compile in MSVC :(
State _state;
rpl::event_stream<State> _stateChanges;
@@ -281,6 +286,9 @@ void ControllerObject::fillExportSteps() {
if (_settings.types & Type::Stories) {
_steps.push_back(Step::Stories);
}
if (_settings.types & Type::ProfileMusic) {
_steps.push_back(Step::ProfileMusic);
}
if (_settings.types & Type::Contacts) {
_steps.push_back(Step::Contacts);
}
@@ -317,6 +325,9 @@ void ControllerObject::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
if (_settings.types & Settings::Type::Stories) {
push(Step::Stories, 1);
}
if (_settings.types & Settings::Type::ProfileMusic) {
push(Step::ProfileMusic, 1);
}
if (_settings.types & Settings::Type::Contacts) {
push(Step::Contacts, 1);
}
@@ -356,6 +367,7 @@ void ControllerObject::exportNext() {
case Step::PersonalInfo: return exportPersonalInfo();
case Step::Userpics: return exportUserpics();
case Step::Stories: return exportStories();
case Step::ProfileMusic: return exportProfileMusic();
case Step::Contacts: return exportContacts();
case Step::Sessions: return exportSessions();
case Step::OtherData: return exportOtherData();
@@ -454,6 +466,32 @@ void ControllerObject::exportStories() {
});
}
void ControllerObject::exportProfileMusic() {
_api.requestProfileMusic([=](Data::ProfileMusicInfo &&start) {
if (ioCatchError(_writer->writeProfileMusicStart(start))) {
return false;
}
_profileMusicWritten = 0;
_profileMusicCount = start.count;
return true;
}, [=](DownloadProgress progress) {
setState(stateProfileMusic(progress));
return true;
}, [=](Data::ProfileMusicSlice &&slice) {
if (ioCatchError(_writer->writeProfileMusicSlice(slice))) {
return false;
}
_profileMusicWritten += slice.list.size();
setState(stateProfileMusic(DownloadProgress()));
return true;
}, [=] {
if (ioCatchError(_writer->writeProfileMusicEnd())) {
return;
}
exportNext();
});
}
void ControllerObject::exportContacts() {
setState(stateContacts());
_api.requestContacts([=](Data::ContactsList &&result) {
@@ -596,6 +634,21 @@ ProcessingState ControllerObject::stateStories(
});
}
ProcessingState ControllerObject::stateProfileMusic(
const DownloadProgress &progress) const {
return prepareState(Step::ProfileMusic, [&](ProcessingState &result) {
result.entityIndex = _profileMusicWritten + progress.itemIndex;
result.entityCount = std::max(_profileMusicCount, result.entityIndex);
result.bytesRandomId = progress.randomId;
if (!progress.path.isEmpty()) {
const auto last = progress.path.lastIndexOf('/');
result.bytesName = progress.path.mid(last + 1);
}
result.bytesLoaded = progress.ready;
result.bytesCount = progress.total;
});
}
ProcessingState ControllerObject::stateContacts() const {
return prepareState(Step::Contacts);
}
@@ -39,6 +39,7 @@ struct ProcessingState {
PersonalInfo,
Userpics,
Stories,
ProfileMusic,
Contacts,
Sessions,
OtherData,
@@ -58,6 +58,7 @@ struct Settings {
PrivateChannels = 0x200,
PublicChannels = 0x400,
Stories = 0x800,
ProfileMusic = 0x1000,
GroupsMask = PrivateGroups | PublicGroups,
ChannelsMask = PrivateChannels | PublicChannels,
@@ -68,6 +69,7 @@ struct Settings {
| Userpics
| Contacts
| Stories
| ProfileMusic
| Sessions),
AllMask = NonChatsMask | OtherData | AnyChatsMask,
};
@@ -97,6 +99,7 @@ struct Settings {
| Type::Userpics
| Type::Contacts
| Type::Stories
| Type::ProfileMusic
| Type::PersonalChats
| Type::PrivateGroups;
}
@@ -16,6 +16,8 @@ struct UserpicsInfo;
struct UserpicsSlice;
struct StoriesInfo;
struct StoriesSlice;
struct ProfileMusicInfo;
struct ProfileMusicSlice;
struct ContactsList;
struct SessionsList;
struct DialogsInfo;
@@ -64,6 +66,12 @@ public:
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;
@@ -44,9 +44,10 @@ constexpr auto kContactsPriority = 2;
constexpr auto kFrequentContactsPriority = 3;
constexpr auto kUserpicsPriority = 4;
constexpr auto kStoriesPriority = 5;
constexpr auto kSessionsPriority = 6;
constexpr auto kWebSessionsPriority = 7;
constexpr auto kOtherPriority = 8;
constexpr auto kProfileMusicPriority = 6;
constexpr auto kSessionsPriority = 7;
constexpr auto kWebSessionsPriority = 8;
constexpr auto kOtherPriority = 9;
const auto kLineBreak = QByteArrayLiteral("<br>");
@@ -538,6 +539,12 @@ public:
const std::vector<Data::TextPart> &caption,
const QString &internalLinksDomain,
const QString &link = QString());
[[nodiscard]] QByteArray pushAudioEntry(
const QByteArray &name,
const QByteArray &info,
const QByteArrayList &details,
const QByteArray &duration,
const QString &link = QString());
[[nodiscard]] QByteArray pushSessionListEntry(
int apiId,
const QByteArray &name,
@@ -877,6 +884,54 @@ QByteArray HtmlWriter::Wrap::pushStoriesListEntry(
return result;
}
QByteArray HtmlWriter::Wrap::pushAudioEntry(
const QByteArray &name,
const QByteArray &info,
const QByteArrayList &details,
const QByteArray &duration,
const QString &link) {
auto result = pushDiv("entry clearfix");
if (!link.isEmpty()) {
result.append(pushTag("a", {
{ "class", "pull_left userpic_wrap" },
{ "href", relativePath(link).toUtf8() + "#allow_back" },
}));
} else {
result.append(pushDiv("pull_left userpic_wrap"));
}
result.append(pushDiv("userpic audio_icon"));
result.append(popTag());
result.append(popTag());
result.append(pushDiv("body"));
if (!duration.isEmpty()) {
result.append(pushDiv("pull_right info details"));
result.append(SerializeString(duration));
result.append(popTag());
}
if (!info.isEmpty()) {
if (!link.isEmpty()) {
result.append(pushTag("a", {
{ "class", "block_link expanded" },
{ "href", relativePath(link).toUtf8() + "#allow_back" },
}));
}
result.append(pushDiv("name bold"));
result.append(SerializeString(info));
result.append(popTag());
if (!link.isEmpty()) {
result.append(popTag());
}
}
for (const auto &detail : details) {
result.append(pushDiv("details_entry details"));
result.append(SerializeString(detail));
result.append(popTag());
}
result.append(popTag());
result.append(popTag());
return result;
}
QByteArray HtmlWriter::Wrap::pushSessionListEntry(
int apiId,
const QByteArray &name,
@@ -2706,6 +2761,7 @@ Result HtmlWriter::start(
"images/section_chats.png",
"images/section_contacts.png",
"images/section_frequent.png",
"images/section_music.png",
"images/section_other.png",
"images/section_photos.png",
"images/section_sessions.png",
@@ -3001,6 +3057,93 @@ Result HtmlWriter::writeStoriesEnd() {
return Result::Success();
}
Result HtmlWriter::writeProfileMusicStart(const Data::ProfileMusicInfo &data) {
Expects(_summary != nullptr);
Expects(_profileMusic == nullptr);
_profileMusicCount = data.count;
if (!_profileMusicCount) {
return Result::Success();
}
_profileMusic = fileWithRelativePath(profileMusicFilePath());
auto block = _profileMusic->pushHeader(
"Profile Music",
mainFileRelativePath());
block.append(_profileMusic->pushDiv("page_body list_page"));
block.append(_profileMusic->pushDiv("entry_list"));
if (const auto result = _profileMusic->writeBlock(block); !result) {
return result;
}
return Result::Success();
}
Result HtmlWriter::writeProfileMusicSlice(const Data::ProfileMusicSlice &data) {
Expects(_profileMusic != nullptr);
_profileMusicCount -= data.skipped;
if (data.list.empty()) {
return Result::Success();
}
auto block = QByteArray();
for (const auto &message : data.list) {
if (!v::is<Data::Document>(message.media.content)) {
continue;
}
const auto &doc = v::get<Data::Document>(message.media.content);
if (!doc.isAudioFile) {
continue;
}
using SkipReason = Data::File::SkipReason;
const auto &file = doc.file;
Assert(!file.relativePath.isEmpty()
|| file.skipReason != SkipReason::None);
auto status = QByteArrayList();
status.append([&]() -> Data::Utf8String {
switch (file.skipReason) {
case SkipReason::Unavailable:
return "(File unavailable, please try again later)";
case SkipReason::FileSize:
return "(File exceeds maximum size. "
"Change data exporting settings to download.)";
case SkipReason::FileType:
return "(File not included. "
"Change data exporting settings to download.)";
case SkipReason::None: return Data::FormatFileSize(file.size);
}
Unexpected("Skip reason while writing profile music path.");
}());
const auto &path = file.relativePath;
const auto title = !doc.songTitle.isEmpty()
? doc.songTitle
: !doc.name.isEmpty()
? doc.name
: Data::Utf8String("Unknown Track");
const auto performer = !doc.songPerformer.isEmpty()
? doc.songPerformer
: Data::Utf8String("Unknown Artist");
const auto info = performer + " - " + title;
const auto duration = doc.duration > 0
? Data::FormatDuration(doc.duration)
: QByteArray();
block.append(_profileMusic->pushAudioEntry(
(path.isEmpty() ? QString("File unavailable") : path).toUtf8(),
info,
status,
duration,
path));
}
return _profileMusic->writeBlock(block);
}
Result HtmlWriter::writeProfileMusicEnd() {
pushProfileMusicSection();
if (_profileMusic) {
return base::take(_profileMusic)->close();
}
return Result::Success();
}
QString HtmlWriter::storiesFilePath() const {
return "lists/stories.html";
}
@@ -3014,6 +3157,19 @@ void HtmlWriter::pushStoriesSection() {
storiesFilePath());
}
QString HtmlWriter::profileMusicFilePath() const {
return "lists/profile_music.html";
}
void HtmlWriter::pushProfileMusicSection() {
pushSection(
kProfileMusicPriority,
"Profile Music",
"music",
_profileMusicCount,
profileMusicFilePath());
}
Result HtmlWriter::writeContactsList(const Data::ContactsList &data) {
Expects(_summary != nullptr);
@@ -64,6 +64,10 @@ public:
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;
@@ -131,9 +135,11 @@ private:
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,
@@ -159,6 +165,9 @@ private:
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;
@@ -73,6 +73,24 @@ Result HtmlAndJsonWriter::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);
@@ -36,6 +36,10 @@ public:
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;
@@ -1318,6 +1318,34 @@ Result JsonWriter::writeStoriesEnd() {
return _output->writeBlock(popNesting());
}
Result JsonWriter::writeProfileMusicStart(const Data::ProfileMusicInfo &data) {
Expects(_output != nullptr);
auto block = prepareObjectItemStart("profile_music");
return _output->writeBlock(block + pushNesting(Context::kArray));
}
Result JsonWriter::writeProfileMusicSlice(const Data::ProfileMusicSlice &data) {
Expects(_output != nullptr);
if (data.list.empty()) {
return Result::Success();
}
auto block = QByteArray();
for (const auto &message : data.list) {
block.append(prepareArrayItemStart());
block.append(SerializeMessage(_context, message, {}, QString()));
}
return _output->writeBlock(block);
}
Result JsonWriter::writeProfileMusicEnd() {
Expects(_output != nullptr);
return _output->writeBlock(popNesting());
}
Result JsonWriter::writeContactsList(const Data::ContactsList &data) {
Expects(_output != nullptr);
@@ -48,6 +48,10 @@ public:
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;
@@ -96,6 +96,13 @@ Content ContentFromState(
state.bytesName,
state.bytesRandomId);
break;
case Step::ProfileMusic:
pushMain(tr::lng_export_option_profile_music(tr::now));
pushBytes(
"music" + QString::number(state.entityIndex),
state.bytesName,
state.bytesRandomId);
break;
case Step::Sessions:
pushMain(tr::lng_export_option_sessions(tr::now));
break;
@@ -183,6 +183,11 @@ void SettingsWidget::setupFullExportOptions(
tr::lng_export_option_stories(tr::now),
Type::Stories,
tr::lng_export_option_stories_about(tr::now));
addOptionWithAbout(
container,
tr::lng_export_option_profile_music(tr::now),
Type::ProfileMusic,
tr::lng_export_option_profile_music_about(tr::now));
addHeader(container, tr::lng_export_header_chats(tr::now));
addOption(
container,
@@ -233,7 +238,8 @@ void SettingsWidget::setupMediaOptions(
| Type::PrivateGroups
| Type::PrivateChannels
| Type::PublicGroups
| Type::PublicChannels)) != 0, anim::type::normal);
| Type::PublicChannels
| Type::ProfileMusic)) != 0, anim::type::normal);
}, mediaWrap->lifetime());
widthValue(