mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Added saved music to data export.
This commit is contained in:
@@ -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 |
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user