| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209 |
- /*
- 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_download_manager.h"
- #include "data/data_session.h"
- #include "data/data_photo.h"
- #include "data/data_document.h"
- #include "data/data_document_media.h"
- #include "data/data_web_page.h"
- #include "data/data_changes.h"
- #include "data/data_user.h"
- #include "data/data_channel.h"
- #include "data/data_file_origin.h"
- #include "base/unixtime.h"
- #include "base/random.h"
- #include "main/main_session.h"
- #include "main/main_account.h"
- #include "lang/lang_keys.h"
- #include "storage/storage_account.h"
- #include "history/history.h"
- #include "history/history_item.h"
- #include "history/history_item_helpers.h"
- #include "core/application.h"
- #include "core/mime_type.h"
- #include "ui/controls/download_bar.h"
- #include "ui/text/format_song_document_name.h"
- #include "ui/layers/generic_box.h"
- #include "ui/ui_utility.h"
- #include "storage/serialize_common.h"
- #include "window/window_controller.h"
- #include "window/window_session_controller.h"
- #include "apiwrap.h"
- #include "styles/style_layers.h"
- namespace Data {
- namespace {
- constexpr auto kClearLoadingTimeout = 5 * crl::time(1000);
- constexpr auto kMaxFileSize = 4000 * int64(1024 * 1024);
- constexpr auto kMaxResolvePerAttempt = 100;
- constexpr auto ByItem = [](const auto &entry) {
- if constexpr (std::is_same_v<decltype(entry), const DownloadingId&>) {
- return entry.object.item;
- } else {
- const auto resolved = entry.object.get();
- return resolved ? resolved->item.get() : nullptr;
- }
- };
- constexpr auto ByDocument = [](const auto &entry) {
- return entry.object.document;
- };
- [[nodiscard]] uint64 PeerAccessHash(not_null<PeerData*> peer) {
- if (const auto user = peer->asUser()) {
- return user->accessHash();
- } else if (const auto channel = peer->asChannel()) {
- return channel->access;
- }
- return 0;
- }
- [[nodiscard]] bool ItemContainsMedia(const DownloadObject &object) {
- if (const auto photo = object.photo) {
- if (const auto media = object.item->media()) {
- if (const auto page = media->webpage()) {
- if (page->photo == photo) {
- return true;
- }
- for (const auto &item : page->collage.items) {
- if (const auto v = std::get_if<PhotoData*>(&item)) {
- if ((*v) == photo) {
- return true;
- }
- }
- }
- } else {
- return (media->photo() == photo);
- }
- }
- } else if (const auto document = object.document) {
- if (const auto media = object.item->media()) {
- if (const auto page = media->webpage()) {
- if (page->document == document) {
- return true;
- }
- for (const auto &item : page->collage.items) {
- if (const auto v = std::get_if<DocumentData*>(&item)) {
- if ((*v) == document) {
- return true;
- }
- }
- }
- } else {
- return (media->document() == document);
- }
- }
- }
- return false;
- }
- struct DocumentDescriptor {
- uint64 sessionUniqueId = 0;
- DocumentId documentId = 0;
- FullMsgId itemId;
- };
- } // namespace
- struct DownloadManager::DeleteFilesDescriptor {
- base::flat_set<not_null<Main::Session*>> sessions;
- base::flat_map<QString, DocumentDescriptor> files;
- };
- DownloadManager::DownloadManager()
- : _clearLoadingTimer([=] { clearLoading(); }) {
- }
- DownloadManager::~DownloadManager() = default;
- bool DownloadManager::empty() const {
- for (const auto &[session, data] : _sessions) {
- if (!data.downloading.empty() || !data.downloaded.empty()) {
- return false;
- }
- }
- return true;
- }
- void DownloadManager::trackSession(not_null<Main::Session*> session) {
- auto &data = _sessions.emplace(session, SessionData()).first->second;
- data.downloaded = deserialize(session);
- data.resolveNeeded = data.downloaded.size();
- session->data().documentLoadProgress(
- ) | rpl::filter([=](not_null<DocumentData*> document) {
- return _loadingDocuments.contains(document);
- }) | rpl::start_with_next([=](not_null<DocumentData*> document) {
- check(document);
- }, data.lifetime);
- session->data().itemLayoutChanged(
- ) | rpl::filter([=](not_null<const HistoryItem*> item) {
- return _loading.contains(item);
- }) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
- check(item);
- }, data.lifetime);
- session->data().itemViewRefreshRequest(
- ) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
- changed(item);
- }, data.lifetime);
- session->changes().messageUpdates(
- MessageUpdate::Flag::Destroyed
- ) | rpl::start_with_next([=](const MessageUpdate &update) {
- removed(update.item);
- }, data.lifetime);
- session->account().sessionChanges(
- ) | rpl::filter(
- rpl::mappers::_1 != session
- ) | rpl::take(1) | rpl::start_with_next([=] {
- untrack(session);
- }, data.lifetime);
- }
- void DownloadManager::itemVisibilitiesUpdated(
- not_null<Main::Session*> session) {
- const auto i = _sessions.find(session);
- if (i == end(_sessions)
- || i->second.downloading.empty()
- || !i->second.downloading.front().hiddenByView) {
- return;
- }
- for (const auto &id : i->second.downloading) {
- if (!id.done
- && !session->data().queryItemVisibility(id.object.item)) {
- for (auto &id : i->second.downloading) {
- id.hiddenByView = false;
- }
- _loadingListChanges.fire({});
- return;
- }
- }
- }
- int64 DownloadManager::computeNextStartDate() {
- const auto now = base::unixtime::now();
- if (_lastStartedBase != now) {
- _lastStartedBase = now;
- _lastStartedAdded = 0;
- } else {
- ++_lastStartedAdded;
- }
- return int64(_lastStartedBase) * 1000 + _lastStartedAdded;
- }
- void DownloadManager::addLoading(DownloadObject object) {
- Expects(object.item != nullptr);
- Expects(object.document != nullptr);
- const auto item = object.item;
- auto &data = sessionData(item);
- const auto already = ranges::find(data.downloading, item, ByItem);
- if (already != end(data.downloading)) {
- const auto document = already->object.document;
- const auto photo = already->object.photo;
- if (document == object.document && photo == object.photo) {
- check(item);
- return;
- }
- remove(data, already);
- }
- const auto size = object.document->size;
- const auto path = object.document->loadingFilePath();
- if (path.isEmpty()) {
- return;
- }
- const auto shownExists = !data.downloading.empty()
- && !data.downloading.front().hiddenByView;
- data.downloading.push_back({
- .object = object,
- .started = computeNextStartDate(),
- .path = path,
- .total = size,
- .hiddenByView = (!shownExists
- && item->history()->owner().queryItemVisibility(item)),
- });
- _loading.emplace(item);
- _loadingDocuments.emplace(object.document);
- _loadingProgress = DownloadProgress{
- .ready = _loadingProgress.current().ready,
- .total = _loadingProgress.current().total + size,
- };
- _loadingListChanges.fire({});
- _clearLoadingTimer.cancel();
- check(item);
- }
- void DownloadManager::check(not_null<const HistoryItem*> item) {
- auto &data = sessionData(item);
- const auto i = ranges::find(data.downloading, item, ByItem);
- Assert(i != end(data.downloading));
- check(data, i);
- }
- void DownloadManager::check(not_null<DocumentData*> document) {
- auto &data = sessionData(document);
- const auto i = ranges::find(
- data.downloading,
- document.get(),
- ByDocument);
- Assert(i != end(data.downloading));
- check(data, i);
- }
- void DownloadManager::check(
- SessionData &data,
- std::vector<DownloadingId>::iterator i) {
- auto &entry = *i;
- if (!ItemContainsMedia(entry.object)) {
- cancel(data, i);
- return;
- }
- const auto document = entry.object.document;
- // Load with progress only documents for now.
- Assert(document != nullptr);
- const auto path = document->filepath(true);
- if (!path.isEmpty()) {
- if (_loading.contains(entry.object.item)) {
- addLoaded(entry.object, path, entry.started);
- }
- } else if (!document->loading()) {
- remove(data, i);
- } else {
- const auto totalChange = document->size - entry.total;
- const auto readyChange = document->loadOffset() - entry.ready;
- if (!readyChange && !totalChange) {
- return;
- }
- entry.ready += readyChange;
- entry.total += totalChange;
- _loadingProgress = DownloadProgress{
- .ready = _loadingProgress.current().ready + readyChange,
- .total = _loadingProgress.current().total + totalChange,
- };
- }
- }
- void DownloadManager::addLoaded(
- DownloadObject object,
- const QString &path,
- DownloadDate started) {
- Expects(object.item != nullptr);
- Expects(object.document || object.photo);
- const auto size = QFileInfo(path).size();
- if (size <= 0 || size > kMaxFileSize) {
- return;
- }
- const auto item = object.item;
- auto &data = sessionData(item);
- const auto id = object.document
- ? DownloadId{ object.document->id, DownloadType::Document }
- : DownloadId{ object.photo->id, DownloadType::Photo };
- data.downloaded.push_back({
- .download = id,
- .started = started,
- .path = path,
- .size = size,
- .itemId = item->fullId(),
- .peerAccessHash = PeerAccessHash(item->history()->peer),
- .object = std::make_unique<DownloadObject>(object),
- });
- _loaded.emplace(item);
- _loadedAdded.fire(&data.downloaded.back());
- writePostponed(&item->history()->session());
- const auto i = ranges::find(data.downloading, item, ByItem);
- if (i != end(data.downloading)) {
- auto &entry = *i;
- const auto document = entry.object.document;
- if (document) {
- _loadingDocuments.remove(document);
- }
- const auto j = _loading.find(entry.object.item);
- if (j == end(_loading)) {
- return;
- }
- const auto totalChange = document->size - entry.total;
- const auto readyChange = document->size - entry.ready;
- entry.ready += readyChange;
- entry.total += totalChange;
- entry.done = true;
- _loading.erase(j);
- _loadingDone.emplace(entry.object.item);
- _loadingProgress = DownloadProgress{
- .ready = _loadingProgress.current().ready + readyChange,
- .total = _loadingProgress.current().total + totalChange,
- };
- _loadingListChanges.fire({});
- if (_loading.empty()) {
- _clearLoadingTimer.callOnce(kClearLoadingTimeout);
- }
- }
- }
- void DownloadManager::clearIfFinished() {
- if (_clearLoadingTimer.isActive()) {
- _clearLoadingTimer.cancel();
- clearLoading();
- }
- }
- void DownloadManager::deleteFiles(const std::vector<GlobalMsgId> &ids) {
- auto descriptor = DeleteFilesDescriptor();
- for (const auto &id : ids) {
- if (const auto item = MessageByGlobalId(id)) {
- const auto session = &item->history()->session();
- const auto i = _sessions.find(session);
- if (i == end(_sessions)) {
- continue;
- }
- auto &data = i->second;
- const auto j = ranges::find(
- data.downloading,
- not_null{ item },
- ByItem);
- if (j != end(data.downloading)) {
- cancel(data, j);
- }
- const auto k = ranges::find(data.downloaded, item, ByItem);
- if (k != end(data.downloaded)) {
- const auto document = k->object->document;
- descriptor.files.emplace(k->path, DocumentDescriptor{
- .sessionUniqueId = id.sessionUniqueId,
- .documentId = document ? document->id : DocumentId(),
- .itemId = id.itemId,
- });
- _loaded.remove(item);
- _generated.remove(item);
- if (document) {
- _generatedDocuments.remove(document);
- }
- data.downloaded.erase(k);
- _loadedRemoved.fire_copy(item);
- descriptor.sessions.emplace(session);
- }
- }
- }
- finishFilesDelete(std::move(descriptor));
- }
- void DownloadManager::deleteAll() {
- auto descriptor = DeleteFilesDescriptor();
- for (auto &[session, data] : _sessions) {
- if (!data.downloaded.empty()) {
- descriptor.sessions.emplace(session);
- } else if (data.downloading.empty()) {
- continue;
- }
- const auto sessionUniqueId = session->uniqueId();
- while (!data.downloading.empty()) {
- cancel(data, data.downloading.end() - 1);
- }
- for (auto &id : base::take(data.downloaded)) {
- const auto object = id.object.get();
- const auto document = object ? object->document : nullptr;
- descriptor.files.emplace(id.path, DocumentDescriptor{
- .sessionUniqueId = sessionUniqueId,
- .documentId = document ? document->id : DocumentId(),
- .itemId = id.itemId,
- });
- if (document) {
- _generatedDocuments.remove(document);
- }
- if (const auto item = object ? object->item.get() : nullptr) {
- _loaded.remove(item);
- _generated.remove(item);
- _loadedRemoved.fire_copy(item);
- }
- }
- }
- for (const auto &session : descriptor.sessions) {
- writePostponed(session);
- }
- finishFilesDelete(std::move(descriptor));
- }
- void DownloadManager::finishFilesDelete(DeleteFilesDescriptor &&descriptor) {
- for (const auto &session : descriptor.sessions) {
- writePostponed(session);
- }
- crl::async([files = std::move(descriptor.files)]{
- for (const auto &file : files) {
- QFile(file.first).remove();
- crl::on_main([descriptor = file.second] {
- if (const auto session = SessionByUniqueId(
- descriptor.sessionUniqueId)) {
- if (const auto id = descriptor.documentId) {
- [[maybe_unused]] const auto location
- = session->data().document(id)->location(true);
- }
- const auto itemId = descriptor.itemId;
- if (const auto item = session->data().message(itemId)) {
- session->data().requestItemRepaint(item);
- }
- }
- });
- }
- });
- }
- bool DownloadManager::loadedHasNonCloudFile() const {
- for (const auto &[session, data] : _sessions) {
- for (const auto &id : data.downloaded) {
- if (const auto object = id.object.get()) {
- if (!object->item->isHistoryEntry()) {
- return true;
- }
- }
- }
- }
- return false;
- }
- auto DownloadManager::loadingList() const
- -> ranges::any_view<const DownloadingId*, ranges::category::input> {
- return ranges::views::all(
- _sessions
- ) | ranges::views::transform([=](const auto &pair) {
- return ranges::views::all(
- pair.second.downloading
- ) | ranges::views::transform([](const DownloadingId &id) {
- return &id;
- });
- }) | ranges::views::join;
- }
- DownloadProgress DownloadManager::loadingProgress() const {
- return _loadingProgress.current();
- }
- rpl::producer<> DownloadManager::loadingListChanges() const {
- return _loadingListChanges.events();
- }
- auto DownloadManager::loadingProgressValue() const
- -> rpl::producer<DownloadProgress> {
- return _loadingProgress.value();
- }
- bool DownloadManager::loadingInProgress(Main::Session *onlyInSession) const {
- return lookupLoadingItem(onlyInSession) != nullptr;
- }
- HistoryItem *DownloadManager::lookupLoadingItem(
- Main::Session *onlyInSession) const {
- constexpr auto find = [](const SessionData &data) {
- constexpr auto proj = &DownloadingId::done;
- const auto i = ranges::find(data.downloading, false, proj);
- return (i != end(data.downloading)) ? i->object.item.get() : nullptr;
- };
- if (onlyInSession) {
- const auto i = _sessions.find(onlyInSession);
- return (i != end(_sessions)) ? find(i->second) : nullptr;
- } else {
- for (const auto &[session, data] : _sessions) {
- if (const auto result = find(data)) {
- return result;
- }
- }
- }
- return nullptr;
- }
- void DownloadManager::loadingStopWithConfirmation(
- Fn<void()> callback,
- Main::Session *onlyInSession) {
- const auto item = lookupLoadingItem(onlyInSession);
- if (!item) {
- return;
- }
- const auto window = Core::App().windowFor(
- not_null(&item->history()->session().account()));
- if (!window) {
- return;
- }
- const auto weak = base::make_weak(&item->history()->session());
- const auto id = item->fullId();
- auto box = Box([=](not_null<Ui::GenericBox*> box) {
- box->addRow(
- object_ptr<Ui::FlatLabel>(
- box.get(),
- tr::lng_download_sure_stop(),
- st::boxLabel),
- st::boxPadding + QMargins(0, 0, 0, st::boxPadding.bottom()));
- box->setStyle(st::defaultBox);
- box->addButton(tr::lng_selected_upload_stop(), [=] {
- box->closeBox();
- if (!onlyInSession || weak.get()) {
- loadingStop(onlyInSession);
- }
- if (callback) {
- callback();
- }
- }, st::attentionBoxButton);
- box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
- box->addLeftButton(tr::lng_upload_show_file(), [=] {
- box->closeBox();
- if (const auto strong = weak.get()) {
- if (const auto item = strong->data().message(id)) {
- if (const auto window = strong->tryResolveWindow()) {
- window->showMessage(item);
- }
- }
- }
- });
- });
- window->show(std::move(box));
- window->activate();
- }
- void DownloadManager::loadingStop(Main::Session *onlyInSession) {
- const auto stopInSession = [&](SessionData &data) {
- while (!data.downloading.empty()) {
- cancel(data, data.downloading.end() - 1);
- }
- };
- if (onlyInSession) {
- const auto i = _sessions.find(onlyInSession);
- if (i != end(_sessions)) {
- stopInSession(i->second);
- }
- } else {
- for (auto &[session, data] : _sessions) {
- stopInSession(data);
- }
- }
- }
- void DownloadManager::clearLoading() {
- Expects(_loading.empty());
- for (auto &[session, data] : _sessions) {
- while (!data.downloading.empty()) {
- remove(data, data.downloading.end() - 1);
- }
- }
- }
- auto DownloadManager::loadedList()
- -> ranges::any_view<const DownloadedId*, ranges::category::input> {
- for (auto &[session, data] : _sessions) {
- resolve(session, data);
- }
- return ranges::views::all(
- _sessions
- ) | ranges::views::transform([=](const auto &pair) {
- return ranges::views::all(
- pair.second.downloaded
- ) | ranges::views::filter([](const DownloadedId &id) {
- return (id.object != nullptr);
- }) | ranges::views::transform([](const DownloadedId &id) {
- return &id;
- });
- }) | ranges::views::join;
- }
- rpl::producer<> DownloadManager::loadedResolveDone() const {
- using namespace rpl::mappers;
- return _loadedResolveDone.value() | rpl::filter(_1) | rpl::to_empty;
- }
- void DownloadManager::resolve(
- not_null<Main::Session*> session,
- SessionData &data) {
- const auto guard = gsl::finally([&] {
- checkFullResolveDone();
- });
- if (data.resolveSentTotal >= data.resolveNeeded
- || data.resolveSentTotal >= kMaxResolvePerAttempt) {
- return;
- }
- struct Prepared {
- uint64 peerAccessHash = 0;
- QVector<MTPInputMessage> ids;
- };
- auto &owner = session->data();
- auto prepared = base::flat_map<PeerId, Prepared>();
- auto last = begin(data.downloaded);
- auto from = last + (data.resolveNeeded - data.resolveSentTotal);
- for (auto i = from; i != last;) {
- auto &id = *--i;
- const auto msgId = id.itemId.msg;
- const auto info = QFileInfo(id.path);
- if (!info.exists() || info.size() != id.size) {
- // Mark as deleted.
- id.path = QString();
- } else if (!owner.message(id.itemId) && IsServerMsgId(msgId)) {
- const auto groupByPeer = peerIsChannel(id.itemId.peer)
- ? id.itemId.peer
- : session->userPeerId();
- auto &perPeer = prepared[groupByPeer];
- if (peerIsChannel(id.itemId.peer) && !perPeer.peerAccessHash) {
- perPeer.peerAccessHash = id.peerAccessHash;
- }
- perPeer.ids.push_back(MTP_inputMessageID(MTP_int(msgId.bare)));
- }
- if (++data.resolveSentTotal >= kMaxResolvePerAttempt) {
- break;
- }
- }
- const auto check = [=] {
- auto &data = sessionData(session);
- if (!data.resolveSentRequests) {
- resolveRequestsFinished(session, data);
- }
- };
- const auto requestFinished = [=] {
- --sessionData(session).resolveSentRequests;
- check();
- };
- for (auto &[peer, perPeer] : prepared) {
- if (const auto channelId = peerToChannel(peer)) {
- session->api().request(MTPchannels_GetMessages(
- MTP_inputChannel(
- MTP_long(channelId.bare),
- MTP_long(perPeer.peerAccessHash)),
- MTP_vector<MTPInputMessage>(perPeer.ids)
- )).done([=](const MTPmessages_Messages &result) {
- session->data().processExistingMessages(
- session->data().channelLoaded(channelId),
- result);
- requestFinished();
- }).fail(requestFinished).send();
- } else {
- session->api().request(MTPmessages_GetMessages(
- MTP_vector<MTPInputMessage>(perPeer.ids)
- )).done([=](const MTPmessages_Messages &result) {
- session->data().processExistingMessages(nullptr, result);
- requestFinished();
- }).fail(requestFinished).send();
- }
- }
- data.resolveSentRequests += prepared.size();
- check();
- }
- void DownloadManager::resolveRequestsFinished(
- not_null<Main::Session*> session,
- SessionData &data) {
- auto &owner = session->data();
- for (; data.resolveSentTotal > 0; --data.resolveSentTotal) {
- const auto i = begin(data.downloaded) + (--data.resolveNeeded);
- if (i->path.isEmpty()) {
- data.downloaded.erase(i);
- continue;
- }
- const auto item = owner.message(i->itemId);
- const auto media = item ? item->media() : nullptr;
- const auto document = media ? media->document() : nullptr;
- const auto photo = media ? media->photo() : nullptr;
- if (i->download.type == DownloadType::Document
- && (!document || document->id != i->download.objectId)) {
- generateEntry(session, *i);
- } else if (i->download.type == DownloadType::Photo
- && (!photo || photo->id != i->download.objectId)) {
- generateEntry(session, *i);
- } else {
- i->object = std::make_unique<DownloadObject>(DownloadObject{
- .item = item,
- .document = document,
- .photo = photo,
- });
- _loaded.emplace(item);
- }
- _loadedAdded.fire(&*i);
- }
- crl::on_main(session, [=] {
- resolve(session, sessionData(session));
- });
- }
- void DownloadManager::checkFullResolveDone() {
- if (_loadedResolveDone.current()) {
- return;
- }
- for (const auto &[session, data] : _sessions) {
- if (data.resolveSentTotal < data.resolveNeeded
- || data.resolveSentRequests > 0) {
- return;
- }
- }
- _loadedResolveDone = true;
- }
- void DownloadManager::generateEntry(
- not_null<Main::Session*> session,
- DownloadedId &id) {
- Expects(!id.object);
- const auto info = QFileInfo(id.path);
- const auto document = session->data().document(
- base::RandomValue<DocumentId>(),
- 0, // accessHash
- QByteArray(), // fileReference
- TimeId(id.started / 1000),
- QVector<MTPDocumentAttribute>(
- 1,
- MTP_documentAttributeFilename(
- MTP_string(info.fileName()))),
- Core::MimeTypeForFile(info).name(),
- InlineImageLocation(), // inlineThumbnail
- ImageWithLocation(), // thumbnail
- ImageWithLocation(), // videoThumbnail
- false, // isPremiumSticker
- 0, // dc
- id.size);
- document->setLocation(Core::FileLocation(info));
- _generatedDocuments.emplace(document);
- id.object = std::make_unique<DownloadObject>(DownloadObject{
- .item = generateFakeItem(document),
- .document = document,
- });
- _loaded.emplace(id.object->item);
- }
- auto DownloadManager::loadedAdded() const
- -> rpl::producer<not_null<const DownloadedId*>> {
- return _loadedAdded.events();
- }
- auto DownloadManager::loadedRemoved() const
- -> rpl::producer<not_null<const HistoryItem*>> {
- return _loadedRemoved.events();
- }
- void DownloadManager::remove(
- SessionData &data,
- std::vector<DownloadingId>::iterator i) {
- const auto now = DownloadProgress{
- .ready = _loadingProgress.current().ready - i->ready,
- .total = _loadingProgress.current().total - i->total,
- };
- _loading.remove(i->object.item);
- _loadingDone.remove(i->object.item);
- if (const auto document = i->object.document) {
- _loadingDocuments.remove(document);
- }
- data.downloading.erase(i);
- _loadingListChanges.fire({});
- _loadingProgress = now;
- if (_loading.empty() && !_loadingDone.empty()) {
- _clearLoadingTimer.callOnce(kClearLoadingTimeout);
- }
- }
- void DownloadManager::cancel(
- SessionData &data,
- std::vector<DownloadingId>::iterator i) {
- const auto object = i->object;
- const auto item = object.item;
- remove(data, i);
- if (!item->isAdminLogEntry()) {
- if (const auto document = object.document) {
- document->cancel();
- } else if (const auto photo = object.photo) {
- photo->cancel();
- }
- }
- }
- void DownloadManager::changed(not_null<const HistoryItem*> item) {
- if (_loaded.contains(item)) {
- auto &data = sessionData(item);
- const auto i = ranges::find(data.downloaded, item.get(), ByItem);
- Assert(i != end(data.downloaded));
- const auto media = item->media();
- const auto photo = media ? media->photo() : nullptr;
- const auto document = media ? media->document() : nullptr;
- if (i->object->photo != photo || i->object->document != document) {
- detach(*i);
- }
- }
- if (_loading.contains(item) || _loadingDone.contains(item)) {
- check(item);
- }
- }
- void DownloadManager::removed(not_null<const HistoryItem*> item) {
- if (_loaded.contains(item)) {
- auto &data = sessionData(item);
- const auto i = ranges::find(data.downloaded, item.get(), ByItem);
- Assert(i != end(data.downloaded));
- detach(*i);
- }
- if (_loading.contains(item) || _loadingDone.contains(item)) {
- auto &data = sessionData(item);
- const auto i = ranges::find(data.downloading, item, ByItem);
- Assert(i != end(data.downloading));
- // We don't want to download files without messages.
- // For example, there is no way to refresh a file reference for them.
- //entry.object.item = nullptr;
- cancel(data, i);
- }
- }
- not_null<HistoryItem*> DownloadManager::regenerateItem(
- const DownloadObject &previous) {
- return generateItem(previous.item, previous.document, previous.photo);
- }
- not_null<HistoryItem*> DownloadManager::generateFakeItem(
- not_null<DocumentData*> document) {
- return generateItem(nullptr, document, nullptr);
- }
- not_null<HistoryItem*> DownloadManager::generateItem(
- HistoryItem *previousItem,
- DocumentData *document,
- PhotoData *photo) {
- Expects(document || photo);
- const auto session = document
- ? &document->session()
- : &photo->session();
- const auto history = previousItem
- ? previousItem->history()
- : session->data().history(session->user());
- ;
- const auto caption = TextWithEntities();
- const auto make = [&](const auto media) {
- return history->makeMessage({
- .id = history->nextNonHistoryEntryId(),
- .flags = MessageFlag::FakeHistoryItem,
- .from = (previousItem
- ? previousItem->from()->id
- : session->userPeerId()),
- .date = base::unixtime::now(),
- }, media, caption);
- };
- const auto result = document ? make(document) : make(photo);
- _generated.emplace(result);
- return result;
- }
- void DownloadManager::detach(DownloadedId &id) {
- Expects(id.object != nullptr);
- Expects(_loaded.contains(id.object->item));
- Expects(!_generated.contains(id.object->item));
- // Maybe generate new document?
- const auto was = id.object->item;
- const auto now = regenerateItem(*id.object);
- _loaded.remove(was);
- _loaded.emplace(now);
- id.object->item = now;
- _loadedRemoved.fire_copy(was);
- _loadedAdded.fire_copy(&id);
- }
- DownloadManager::SessionData &DownloadManager::sessionData(
- not_null<Main::Session*> session) {
- const auto i = _sessions.find(session);
- Assert(i != end(_sessions));
- return i->second;
- }
- const DownloadManager::SessionData &DownloadManager::sessionData(
- not_null<Main::Session*> session) const {
- const auto i = _sessions.find(session);
- Assert(i != end(_sessions));
- return i->second;
- }
- DownloadManager::SessionData &DownloadManager::sessionData(
- not_null<const HistoryItem*> item) {
- return sessionData(&item->history()->session());
- }
- DownloadManager::SessionData &DownloadManager::sessionData(
- not_null<DocumentData*> document) {
- return sessionData(&document->session());
- }
- void DownloadManager::writePostponed(not_null<Main::Session*> session) {
- session->account().local().updateDownloads(serializator(session));
- }
- Fn<std::optional<QByteArray>()> DownloadManager::serializator(
- not_null<Main::Session*> session) const {
- return [this, weak = base::make_weak(session)]()
- -> std::optional<QByteArray> {
- const auto strong = weak.get();
- if (!strong) {
- return std::nullopt;
- } else if (!_sessions.contains(strong)) {
- return QByteArray();
- }
- auto result = QByteArray();
- const auto &data = sessionData(strong);
- const auto count = data.downloaded.size();
- const auto constant = sizeof(quint64) // download.objectId
- + sizeof(qint32) // download.type
- + sizeof(qint64) // started
- + sizeof(quint32) // size
- + sizeof(quint64) // itemId.peer
- + sizeof(qint64) // itemId.msg
- + sizeof(quint64); // peerAccessHash
- auto size = sizeof(qint32) // count
- + count * constant;
- for (const auto &id : data.downloaded) {
- size += Serialize::stringSize(id.path);
- }
- result.reserve(size);
- auto stream = QDataStream(&result, QIODevice::WriteOnly);
- stream.setVersion(QDataStream::Qt_5_1);
- stream << qint32(count);
- for (const auto &id : data.downloaded) {
- stream
- << quint64(id.download.objectId)
- << qint32(id.download.type)
- << qint64(id.started)
- // FileSize: Right now any file size fits 32 bit.
- << quint32(id.size)
- << quint64(id.itemId.peer.value)
- << qint64(id.itemId.msg.bare)
- << quint64(id.peerAccessHash)
- << id.path;
- }
- stream.device()->close();
- return result;
- };
- }
- std::vector<DownloadedId> DownloadManager::deserialize(
- not_null<Main::Session*> session) const {
- const auto serialized = session->account().local().downloadsSerialized();
- if (serialized.isEmpty()) {
- return {};
- }
- QDataStream stream(serialized);
- stream.setVersion(QDataStream::Qt_5_1);
- auto count = qint32();
- stream >> count;
- if (stream.status() != QDataStream::Ok || count <= 0 || count > 99'999) {
- return {};
- }
- auto result = std::vector<DownloadedId>();
- result.reserve(count);
- for (auto i = 0; i != count; ++i) {
- auto downloadObjectId = quint64();
- auto uncheckedDownloadType = qint32();
- auto started = qint64();
- // FileSize: Right now any file size fits 32 bit.
- auto size = quint32();
- auto itemIdPeer = quint64();
- auto itemIdMsg = qint64();
- auto peerAccessHash = quint64();
- auto path = QString();
- stream
- >> downloadObjectId
- >> uncheckedDownloadType
- >> started
- >> size
- >> itemIdPeer
- >> itemIdMsg
- >> peerAccessHash
- >> path;
- const auto downloadType = DownloadType(uncheckedDownloadType);
- if (stream.status() != QDataStream::Ok
- || path.isEmpty()
- || size <= 0
- || size > kMaxFileSize
- || (downloadType != DownloadType::Document
- && downloadType != DownloadType::Photo)) {
- return {};
- }
- result.push_back({
- .download = {
- .objectId = downloadObjectId,
- .type = downloadType,
- },
- .started = started,
- .path = path,
- .size = int64(size),
- .itemId = { PeerId(itemIdPeer), MsgId(itemIdMsg) },
- .peerAccessHash = peerAccessHash,
- });
- }
- return result;
- }
- void DownloadManager::untrack(not_null<Main::Session*> session) {
- const auto i = _sessions.find(session);
- Assert(i != end(_sessions));
- for (const auto &entry : i->second.downloaded) {
- if (const auto resolved = entry.object.get()) {
- const auto item = resolved->item;
- _loaded.remove(item);
- _generated.remove(item);
- if (const auto document = resolved->document) {
- _generatedDocuments.remove(document);
- }
- }
- }
- while (!i->second.downloading.empty()) {
- remove(i->second, i->second.downloading.end() - 1);
- }
- _sessions.erase(i);
- }
- rpl::producer<Ui::DownloadBarProgress> MakeDownloadBarProgress() {
- return Core::App().downloadManager().loadingProgressValue(
- ) | rpl::map([=](const DownloadProgress &progress) {
- return Ui::DownloadBarProgress{
- .ready = progress.ready,
- .total = progress.total,
- };
- });
- }
- rpl::producer<Ui::DownloadBarContent> MakeDownloadBarContent() {
- return [](auto consumer) {
- auto lifetime = rpl::lifetime();
- struct State {
- DocumentData *document = nullptr;
- std::shared_ptr<Data::DocumentMedia> media;
- rpl::lifetime downloadTaskLifetime;
- QImage thumbnail;
- base::has_weak_ptr guard;
- bool scheduled = false;
- Fn<void()> push;
- };
- const auto state = lifetime.make_state<State>();
- auto &manager = Core::App().downloadManager();
- const auto resolveThumbnailRecursive = [=](auto &&self) -> bool {
- if (state->document && !state->document->hasThumbnail()) {
- state->media = nullptr;
- }
- if (!state->media) {
- state->downloadTaskLifetime.destroy();
- if (!state->thumbnail.isNull()) {
- return false;
- }
- state->thumbnail = QImage();
- return true;
- }
- if (const auto image = state->media->thumbnail()) {
- state->thumbnail = image->original();
- state->downloadTaskLifetime.destroy();
- state->media = nullptr;
- return true;
- } else if (const auto embed = state->media->thumbnailInline()) {
- if (!state->thumbnail.isNull()) {
- return false;
- }
- state->thumbnail = Images::Prepare(embed->original(), 0, {
- .options = Images::Option::Blur,
- });
- } else if (!state->downloadTaskLifetime) {
- state->document->session().downloaderTaskFinished(
- ) | rpl::filter([=] {
- return self(self);
- }) | rpl::start_with_next(
- state->push,
- state->downloadTaskLifetime);
- }
- return !state->thumbnail.isNull();
- };
- const auto resolveThumbnail = [=] {
- return resolveThumbnailRecursive(resolveThumbnailRecursive);
- };
- const auto notify = [=, &manager] {
- auto content = Ui::DownloadBarContent();
- auto single = (const Data::DownloadObject*) nullptr;
- for (const auto id : manager.loadingList()) {
- if (id->hiddenByView) {
- break;
- }
- if (!single) {
- single = &id->object;
- }
- ++content.count;
- if (id->done) {
- ++content.done;
- }
- }
- if (content.count == 1) {
- const auto document = single->document;
- const auto thumbnailed = (single->item
- && document->hasThumbnail())
- ? document
- : nullptr;
- if (state->document != thumbnailed) {
- state->document = thumbnailed;
- state->media = thumbnailed
- ? thumbnailed->createMediaView()
- : nullptr;
- if (const auto raw = state->media.get()) {
- raw->thumbnailWanted(single->item->fullId());
- }
- state->thumbnail = QImage();
- resolveThumbnail();
- }
- content.singleName = Ui::Text::FormatDownloadsName(
- document);
- content.singleThumbnail = state->thumbnail;
- }
- consumer.put_next(std::move(content));
- };
- state->push = [=] {
- if (state->scheduled) {
- return;
- }
- state->scheduled = true;
- Ui::PostponeCall(&state->guard, [=] {
- state->scheduled = false;
- notify();
- });
- };
- manager.loadingListChanges(
- ) | rpl::filter([=] {
- return !state->scheduled;
- }) | rpl::start_with_next(state->push, lifetime);
- notify();
- return lifetime;
- };
- }
- } // namespace Data
|