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:
417
Telegram/SourceFiles/data/data_saved_music.cpp
Normal file
417
Telegram/SourceFiles/data/data_saved_music.cpp
Normal file
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_saved_music.h"
|
||||
|
||||
#include "api/api_hash.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/ui_utility.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kPerPage = 50;
|
||||
constexpr auto kReloadIdsEvery = 30 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] not_null<DocumentData*> ItemDocument(
|
||||
not_null<HistoryItem*> item) {
|
||||
return item->media()->document();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SavedMusic::SavedMusic(not_null<Session*> owner)
|
||||
: _owner(owner) {
|
||||
}
|
||||
|
||||
SavedMusic::~SavedMusic() {
|
||||
Expects(_entries.empty());
|
||||
}
|
||||
|
||||
void SavedMusic::clear() {
|
||||
base::take(_entries);
|
||||
}
|
||||
|
||||
bool SavedMusic::Supported(PeerId peerId) {
|
||||
return peerId && peerIsUser(peerId);
|
||||
}
|
||||
|
||||
not_null<HistoryItem*> SavedMusic::musicIdToMsg(
|
||||
PeerId peerId,
|
||||
Entry &entry,
|
||||
not_null<DocumentData*> id) {
|
||||
const auto i = entry.musicIdToMsg.find(id);
|
||||
if (i != end(entry.musicIdToMsg)) {
|
||||
return i->second.get();
|
||||
} else if (!entry.history) {
|
||||
entry.history = _owner->history(peerId);
|
||||
}
|
||||
return entry.musicIdToMsg.emplace(id, entry.history->makeMessage({
|
||||
.id = entry.history->nextNonHistoryEntryId(),
|
||||
.flags = (MessageFlag::FakeHistoryItem
|
||||
| MessageFlag::HasFromId
|
||||
| MessageFlag::SavedMusicItem),
|
||||
.from = entry.history->peer->id,
|
||||
.date = base::unixtime::now(),
|
||||
}, id, TextWithEntities())).first->second.get();
|
||||
}
|
||||
|
||||
void SavedMusic::loadIds() {
|
||||
if (_loadIdsRequest
|
||||
|| (_lastReceived
|
||||
&& (crl::now() - _lastReceived < kReloadIdsEvery))) {
|
||||
return;
|
||||
}
|
||||
_loadIdsRequest = _owner->session().api().request(
|
||||
MTPaccount_GetSavedMusicIds(MTP_long(Api::CountHash(_myIds)))
|
||||
).done([=](const MTPaccount_SavedMusicIds &result) {
|
||||
_loadIdsRequest = 0;
|
||||
_lastReceived = crl::now();
|
||||
result.match([&](const MTPDaccount_savedMusicIds &data) {
|
||||
_myIds = data.vids().v
|
||||
| ranges::views::transform(&MTPlong::v)
|
||||
| ranges::to_vector;
|
||||
}, [](const MTPDaccount_savedMusicIdsNotModified &) {
|
||||
});
|
||||
}).fail([=] {
|
||||
_loadIdsRequest = 0;
|
||||
_lastReceived = crl::now();
|
||||
}).send();
|
||||
}
|
||||
|
||||
bool SavedMusic::has(not_null<DocumentData*> document) const {
|
||||
return ranges::contains(_myIds, document->id);
|
||||
}
|
||||
|
||||
void SavedMusic::save(
|
||||
not_null<DocumentData*> document,
|
||||
Data::FileOrigin origin) {
|
||||
const auto peerId = _owner->session().userPeerId();
|
||||
auto &entry = _entries[peerId];
|
||||
if (entry.list.empty() && !entry.loaded) {
|
||||
loadMore(peerId);
|
||||
}
|
||||
if (has(document)) {
|
||||
return;
|
||||
}
|
||||
const auto item = musicIdToMsg(peerId, entry, document);
|
||||
entry.list.insert(begin(entry.list), item);
|
||||
if (entry.total >= 0) {
|
||||
++entry.total;
|
||||
}
|
||||
_myIds.insert(begin(_myIds), document->id);
|
||||
|
||||
const auto send = [=](auto resend) -> void {
|
||||
const auto usedFileReference = document->fileReference();
|
||||
_owner->session().api().request(MTPaccount_SaveMusic(
|
||||
MTP_flags(0),
|
||||
document->mtpInput(),
|
||||
MTPInputDocument()
|
||||
)).fail([=](const MTP::Error &error) {
|
||||
if (error.code() == 400
|
||||
&& error.type().startsWith(u"FILE_REFERENCE_"_q)) {
|
||||
document->session().api().refreshFileReference(origin, [=](
|
||||
const auto &) {
|
||||
if (document->fileReference() != usedFileReference) {
|
||||
resend(resend);
|
||||
}
|
||||
});
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
send(send);
|
||||
|
||||
_changed.fire_copy(peerId);
|
||||
}
|
||||
|
||||
void SavedMusic::remove(not_null<DocumentData*> document) {
|
||||
const auto peerId = _owner->session().userPeerId();
|
||||
auto &entry = _entries[peerId];
|
||||
const auto i = ranges::find(entry.list, document, ItemDocument);
|
||||
if (i != end(entry.list)) {
|
||||
entry.musicIdFromMsgId.remove((*i)->id);
|
||||
entry.list.erase(i);
|
||||
if (entry.total > 0) {
|
||||
entry.total = std::max(entry.total - 1, 0);
|
||||
}
|
||||
}
|
||||
entry.musicIdToMsg.remove(document);
|
||||
_myIds.erase(ranges::remove(_myIds, document->id), end(_myIds));
|
||||
_owner->session().api().request(MTPaccount_SaveMusic(
|
||||
MTP_flags(MTPaccount_SaveMusic::Flag::f_unsave),
|
||||
document->mtpInput(),
|
||||
MTPInputDocument()
|
||||
)).send();
|
||||
_changed.fire_copy(peerId);
|
||||
}
|
||||
|
||||
void SavedMusic::reorder(int oldPosition, int newPosition) {
|
||||
const auto peerId = _owner->session().userPeerId();
|
||||
auto &entry = _entries[peerId];
|
||||
if (oldPosition < 0 || newPosition < 0
|
||||
|| oldPosition >= entry.list.size()
|
||||
|| newPosition >= entry.list.size()
|
||||
|| oldPosition == newPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto item = entry.list[oldPosition];
|
||||
const auto document = ItemDocument(item);
|
||||
|
||||
base::reorder(entry.list, oldPosition, newPosition);
|
||||
|
||||
const auto afterDocument = (newPosition > 0)
|
||||
? ItemDocument(entry.list[newPosition - 1]).get()
|
||||
: nullptr;
|
||||
|
||||
_owner->session().api().request(MTPaccount_SaveMusic(
|
||||
MTP_flags(afterDocument
|
||||
? MTPaccount_SaveMusic::Flag::f_after_id
|
||||
: MTPaccount_SaveMusic::Flags(0)),
|
||||
document->mtpInput(),
|
||||
afterDocument ? afterDocument->mtpInput() : MTPInputDocument()
|
||||
)).done([=] {
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
}).send();
|
||||
|
||||
_changed.fire_copy(peerId);
|
||||
}
|
||||
|
||||
void SavedMusic::apply(not_null<UserData*> user, const MTPDocument *last) {
|
||||
const auto peerId = user->id;
|
||||
auto &entry = _entries[peerId];
|
||||
if (!last) {
|
||||
if (const auto requestId = base::take(entry.requestId)) {
|
||||
_owner->session().api().request(requestId).cancel();
|
||||
}
|
||||
entry = Entry{ .total = 0, .loaded = true };
|
||||
_changed.fire_copy(peerId);
|
||||
return;
|
||||
}
|
||||
const auto document = _owner->processDocument(*last);
|
||||
const auto i = ranges::find(entry.list, document, ItemDocument);
|
||||
if (i != end(entry.list)) {
|
||||
if (i == begin(entry.list)) {
|
||||
return;
|
||||
}
|
||||
ranges::rotate(begin(entry.list), i, i + 1);
|
||||
_changed.fire_copy(peerId);
|
||||
loadMore(peerId, true);
|
||||
return;
|
||||
}
|
||||
entry.list.insert(
|
||||
begin(entry.list),
|
||||
musicIdToMsg(peerId, entry, document));
|
||||
_changed.fire_copy(peerId);
|
||||
if (entry.loaded) {
|
||||
loadMore(peerId, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool SavedMusic::countKnown(PeerId peerId) const {
|
||||
if (!Supported(peerId)) {
|
||||
return true;
|
||||
}
|
||||
const auto entry = lookupEntry(peerId);
|
||||
return entry && entry->total >= 0;
|
||||
}
|
||||
|
||||
int SavedMusic::count(PeerId peerId) const {
|
||||
if (!Supported(peerId)) {
|
||||
return 0;
|
||||
}
|
||||
const auto entry = lookupEntry(peerId);
|
||||
return entry ? std::max(entry->total, 0) : 0;
|
||||
}
|
||||
|
||||
const std::vector<not_null<HistoryItem*>> &SavedMusic::list(
|
||||
PeerId peerId) const {
|
||||
static const auto empty = std::vector<not_null<HistoryItem*>>();
|
||||
if (!Supported(peerId)) {
|
||||
return empty;
|
||||
}
|
||||
|
||||
const auto entry = lookupEntry(peerId);
|
||||
return entry ? entry->list : empty;
|
||||
}
|
||||
|
||||
void SavedMusic::loadMore(PeerId peerId) {
|
||||
loadMore(peerId, false);
|
||||
}
|
||||
|
||||
void SavedMusic::loadMore(PeerId peerId, bool reload) {
|
||||
if (!Supported(peerId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &entry = _entries[peerId];
|
||||
if (!entry.reloading && reload) {
|
||||
_owner->session().api().request(
|
||||
base::take(entry.requestId)).cancel();
|
||||
}
|
||||
if ((!reload && entry.loaded) || entry.requestId) {
|
||||
return;
|
||||
}
|
||||
const auto user = _owner->peer(peerId)->asUser();
|
||||
Assert(user != nullptr);
|
||||
|
||||
entry.reloading = reload;
|
||||
entry.requestId = _owner->session().api().request(MTPusers_GetSavedMusic(
|
||||
user->inputUser(),
|
||||
MTP_int(reload ? 0 : entry.list.size()),
|
||||
MTP_int(kPerPage),
|
||||
MTP_long(reload ? firstPageHash(entry) : 0)
|
||||
)).done([=](const MTPusers_SavedMusic &result) {
|
||||
auto &entry = _entries[peerId];
|
||||
entry.requestId = 0;
|
||||
const auto reloaded = base::take(entry.reloading);
|
||||
result.match([&](const MTPDusers_savedMusicNotModified &) {
|
||||
}, [&](const MTPDusers_savedMusic &data) {
|
||||
const auto list = data.vdocuments().v;
|
||||
const auto count = int(list.size());
|
||||
entry.total = std::max(count, data.vcount().v);
|
||||
if (reloaded) {
|
||||
entry.list.clear();
|
||||
}
|
||||
for (const auto &item : list) {
|
||||
const auto document = _owner->processDocument(item);
|
||||
if (!ranges::contains(entry.list, document, ItemDocument)) {
|
||||
entry.list.push_back(
|
||||
musicIdToMsg(peerId, entry, document));
|
||||
}
|
||||
}
|
||||
entry.loaded = list.empty()
|
||||
|| (entry.total == entry.list.size());
|
||||
});
|
||||
_changed.fire_copy(peerId);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
auto &entry = _entries[peerId];
|
||||
entry.requestId = 0;
|
||||
entry.total = int(entry.list.size());
|
||||
entry.loaded = true;
|
||||
_changed.fire_copy(peerId);
|
||||
}).send();
|
||||
}
|
||||
|
||||
uint64 SavedMusic::firstPageHash(const Entry &entry) const {
|
||||
return Api::CountHash(entry.list
|
||||
| ranges::views::transform(ItemDocument)
|
||||
| ranges::views::transform(&DocumentData::id)
|
||||
| ranges::views::take(kPerPage));
|
||||
}
|
||||
|
||||
rpl::producer<PeerId> SavedMusic::changed() const {
|
||||
return _changed.events();
|
||||
}
|
||||
|
||||
SavedMusic::Entry *SavedMusic::lookupEntry(PeerId peerId) {
|
||||
if (!Supported(peerId)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto it = _entries.find(peerId);
|
||||
if (it == end(_entries)) {
|
||||
return nullptr;
|
||||
}
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
const SavedMusic::Entry *SavedMusic::lookupEntry(PeerId peerId) const {
|
||||
return const_cast<SavedMusic*>(this)->lookupEntry(peerId);
|
||||
}
|
||||
|
||||
rpl::producer<SavedMusicSlice> SavedMusicList(
|
||||
not_null<PeerData*> peer,
|
||||
HistoryItem *aroundId,
|
||||
int limit) {
|
||||
if (!peer->isUser()) {
|
||||
return rpl::single(SavedMusicSlice({}, 0, 0, 0));
|
||||
}
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
struct State {
|
||||
SavedMusicSlice slice;
|
||||
base::has_weak_ptr guard;
|
||||
bool scheduled = false;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>();
|
||||
|
||||
const auto push = [=] {
|
||||
state->scheduled = false;
|
||||
|
||||
const auto peerId = peer->id;
|
||||
const auto savedMusic = &peer->owner().savedMusic();
|
||||
if (!savedMusic->countKnown(peerId)) {
|
||||
return;
|
||||
}
|
||||
const auto &loaded = savedMusic->list(peerId);
|
||||
const auto count = savedMusic->count(peerId);
|
||||
auto i = aroundId
|
||||
? ranges::find(loaded, not_null(aroundId))
|
||||
: begin(loaded);
|
||||
if (i == end(loaded)) {
|
||||
i = begin(loaded);
|
||||
}
|
||||
const auto hasAfter = int(i - begin(loaded));
|
||||
const auto hasBefore = int(end(loaded) - i);
|
||||
if (hasBefore < limit) {
|
||||
savedMusic->loadMore(peerId);
|
||||
}
|
||||
const auto takeAfter = std::min(hasAfter, limit);
|
||||
const auto takeBefore = std::min(hasBefore, limit);
|
||||
auto ids = std::vector<not_null<HistoryItem*>>();
|
||||
ids.reserve(takeAfter + takeBefore);
|
||||
for (auto j = i - takeAfter; j != i + takeBefore; ++j) {
|
||||
ids.push_back(*j);
|
||||
}
|
||||
const auto added = int(ids.size());
|
||||
state->slice = SavedMusicSlice(
|
||||
std::move(ids),
|
||||
count,
|
||||
count - (hasAfter - takeAfter) - added,
|
||||
hasAfter - takeAfter);
|
||||
consumer.put_next_copy(state->slice);
|
||||
};
|
||||
const auto schedule = [=] {
|
||||
if (state->scheduled) {
|
||||
return;
|
||||
}
|
||||
state->scheduled = true;
|
||||
Ui::PostponeCall(&state->guard, [=] {
|
||||
if (state->scheduled) {
|
||||
push();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const auto peerId = peer->id;
|
||||
const auto savedMusic = &peer->owner().savedMusic();
|
||||
savedMusic->changed(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 == peerId
|
||||
) | rpl::on_next(schedule, lifetime);
|
||||
|
||||
if (!savedMusic->countKnown(peerId)) {
|
||||
savedMusic->loadMore(peerId);
|
||||
}
|
||||
|
||||
push();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
Reference in New Issue
Block a user