From 280d79fecc206bff64f2bad0dd8b7d7cff20c1a3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 21 Feb 2022 18:40:20 +0300 Subject: [PATCH] Add observing Data::DownloadManager. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/core/application.cpp | 2 + Telegram/SourceFiles/core/application.h | 5 + .../data/data_download_manager.cpp | 272 ++++++++++++++++++ .../SourceFiles/data/data_download_manager.h | 95 ++++++ .../data/data_file_click_handler.cpp | 15 +- Telegram/SourceFiles/main/main_session.cpp | 3 + 7 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 Telegram/SourceFiles/data/data_download_manager.cpp create mode 100644 Telegram/SourceFiles/data/data_download_manager.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 199f5ed4e4..a64fe1222c 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -424,6 +424,8 @@ PRIVATE data/data_document_media.h data/data_document_resolver.cpp data/data_document_resolver.h + data/data_download_manager.cpp + data/data_download_manager.h data/data_drafts.cpp data/data_drafts.h data/data_folder.cpp diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 1d66fb63d8..ceb62c824c 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_session.h" #include "data/data_user.h" +#include "data/data_download_manager.h" #include "base/timer.h" #include "base/event_filter.h" #include "base/concurrent_timer.h" @@ -139,6 +140,7 @@ Application::Application(not_null launcher) , _audio(std::make_unique()) , _fallbackProductionConfig( std::make_unique(MTP::Environment::Production)) +, _downloadManager(std::make_unique()) , _domain(std::make_unique(cDataFile())) , _exportManager(std::make_unique()) , _calls(std::make_unique()) diff --git a/Telegram/SourceFiles/core/application.h b/Telegram/SourceFiles/core/application.h index c11b3150a2..2f73ab5187 100644 --- a/Telegram/SourceFiles/core/application.h +++ b/Telegram/SourceFiles/core/application.h @@ -84,6 +84,7 @@ class CloudManager; namespace Data { struct CloudTheme; +class DownloadManager; } // namespace Data namespace Stickers { @@ -143,6 +144,9 @@ public: return *_notifications; } + [[nodiscard]] Data::DownloadManager &downloadManager() const { + return *_downloadManager; + } // Windows interface. bool hasActiveWindow(not_null session) const; @@ -354,6 +358,7 @@ private: // Mutable because is created in run() after OpenSSL is inited. std::unique_ptr _notifications; + const std::unique_ptr _downloadManager; const std::unique_ptr _domain; const std::unique_ptr _exportManager; const std::unique_ptr _calls; diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp new file mode 100644 index 0000000000..6dcec29162 --- /dev/null +++ b/Telegram/SourceFiles/data/data_download_manager.cpp @@ -0,0 +1,272 @@ +/* +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_changes.h" +#include "data/data_user.h" +#include "data/data_channel.h" +#include "main/main_session.h" +#include "main/main_account.h" +#include "history/history.h" +#include "history/history_item.h" + +namespace Data { +namespace { + +constexpr auto ByItem = [](const auto &entry) { + if constexpr (std::is_same_v) { + return entry.object.item; + } else { + const auto resolved = entry.object.get(); + return resolved ? resolved->item : nullptr; + } +}; + +[[nodiscard]] uint64 PeerAccessHash(not_null peer) { + if (const auto user = peer->asUser()) { + return user->accessHash(); + } else if (const auto channel = peer->asChannel()) { + return channel->access; + } + return 0; +} + +} // namespace + +DownloadManager::DownloadManager() = default; + +DownloadManager::~DownloadManager() = default; + +void DownloadManager::trackSession(not_null session) { + auto &data = _sessions.emplace(session, SessionData()).first->second; + + session->data().itemRepaintRequest( + ) | rpl::filter([=](not_null item) { + return _downloading.contains(item); + }) | rpl::start_with_next([=](not_null item) { + check(item); + }, data.lifetime); + + session->data().itemLayoutChanged( + ) | rpl::filter([=](not_null item) { + return _downloading.contains(item); + }) | rpl::start_with_next([=](not_null item) { + check(item); + }, data.lifetime); + + session->data().itemViewRefreshRequest( + ) | rpl::start_with_next([=](not_null 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::addLoading(DownloadObject object) { + Expects(object.item != nullptr); + Expects(object.document || object.photo); + + 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 + ? object.document->size + : object.photo->imageByteSize(PhotoSize::Large); + + data.downloading.push_back({ .object = object, .total = size }); + _downloading.emplace(item); + _loadingTotal += size; + + check(item); +} + +void DownloadManager::check(not_null item) { + auto &data = sessionData(item); + const auto i = ranges::find(data.downloading, item, ByItem); + Assert(i != end(data.downloading)); + auto &entry = *i; + + const auto media = item->media(); + const auto photo = media ? media->photo() : nullptr; + const auto document = media ? media->document() : nullptr; + if (entry.object.photo != photo || entry.object.document != document) { + if (const auto document = entry.object.document) { + document->cancel(); + } else if (const auto photo = entry.object.photo) { + photo->cancel(); + } + remove(data, i); + return; + } + // Load with progress only documents for now. + Assert(document != nullptr); + + const auto path = document->filepath(true); + if (!path.isEmpty()) { + addLoaded(entry.object, path, entry.started); + } else if (!document->loading()) { + remove(data, i); + } else { + const auto total = document->size; + const auto ready = document->loadOffset(); + if (total == entry.total && ready == entry.ready) { + return; + } + _loadingTotal += (total - entry.total); + _loadingReady += (ready - entry.ready); + entry.total = total; + entry.ready = ready; + } +} + +void DownloadManager::addLoaded( + DownloadObject object, + const QString &path, + DownloadDate started) { + Expects(object.item != nullptr); + Expects(object.document || object.photo); + + 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, + .itemId = item->fullId(), + .peerAccessHash = PeerAccessHash(item->history()->peer), + .object = std::make_unique(object), + }); + _downloaded.emplace(item); + + const auto i = ranges::find(data.downloading, item, ByItem); + if (i != end(data.downloading)) { + auto &entry = *i; + const auto size = entry.total; + remove(data, i); + if (_downloading.empty()) { + Assert(_loadingTotal == 0 && _loadingReady == 0); + _loadedTotal = 0; + } else { + _loadedTotal += size; + } + } +} + +void DownloadManager::remove( + SessionData &data, + std::vector::iterator i) { + _loadingTotal -= i->total; + _loadingReady -= i->ready; + _downloading.remove(i->object.item); + data.downloading.erase(i); +} + +void DownloadManager::changed(not_null item) { + if (_downloading.contains(item)) { + check(item); + } else if (_downloaded.contains(item)) { + auto &data = sessionData(item); + const auto i = ranges::find(data.downloaded, item, 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) { + *i->object = DownloadObject(); + + _downloaded.remove(item); + } + } +} + +void DownloadManager::removed(not_null item) { + if (_downloading.contains(item)) { + auto &data = sessionData(item); + const auto i = ranges::find(data.downloading, item, ByItem); + Assert(i != end(data.downloading)); + auto &entry = *i; + + // 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; + if (const auto document = entry.object.document) { + document->cancel(); + } else if (const auto photo = entry.object.photo) { + photo->cancel(); + } + remove(data, i); + } else if (_downloaded.contains(item)) { + auto &data = sessionData(item); + const auto i = ranges::find(data.downloaded, item, ByItem); + Assert(i != end(data.downloaded)); + *i->object = DownloadObject(); + + _downloaded.remove(item); + } +} + +DownloadManager::SessionData &DownloadManager::sessionData( + not_null session) { + const auto i = _sessions.find(session); + Assert(i != end(_sessions)); + return i->second; +} + +DownloadManager::SessionData &DownloadManager::sessionData( + not_null item) { + return sessionData(&item->history()->session()); +} + +void DownloadManager::untrack(not_null 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()) { + if (const auto item = resolved->item) { + _downloaded.remove(item); + } + } + } + while (!i->second.downloading.empty()) { + remove(i->second, i->second.downloading.end() - 1); + } + _sessions.erase(i); +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_download_manager.h b/Telegram/SourceFiles/data/data_download_manager.h new file mode 100644 index 0000000000..2c6a4cfadf --- /dev/null +++ b/Telegram/SourceFiles/data/data_download_manager.h @@ -0,0 +1,95 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Main { +class Session; +} // namespace Main + +namespace Data { + +enum class DownloadType { + Document, + Photo, +}; + +// unixtime * 1000. +using DownloadDate = int64; + +struct DownloadId { + uint64 objectId = 0; + DownloadType type = DownloadType::Document; +}; + +struct DownloadObject { + HistoryItem *item = nullptr; + DocumentData *document = nullptr; + PhotoData *photo = nullptr; +}; + +struct DownloadedId { + DownloadId download; + DownloadDate started = 0; + QString path; + FullMsgId itemId; + uint64 peerAccessHash = 0; + + std::unique_ptr object; + +}; + +struct DownloadingId { + DownloadObject object; + DownloadDate started = 0; + int ready = 0; + int total = 0; +}; + +class DownloadManager final { +public: + DownloadManager(); + ~DownloadManager(); + + void trackSession(not_null session); + + void addLoading(DownloadObject object); + void addLoaded( + DownloadObject object, + const QString &path, + DownloadDate started); + +private: + struct SessionData { + std::vector downloaded; + std::vector downloading; + rpl::lifetime lifetime; + }; + + void check(not_null item); + void changed(not_null item); + void removed(not_null item); + void untrack(not_null session); + void remove( + SessionData &data, + std::vector::iterator i); + + [[nodiscard]] SessionData &sessionData(not_null session); + [[nodiscard]] SessionData &sessionData( + not_null item); + + base::flat_map, SessionData> _sessions; + base::flat_set> _downloading; + base::flat_set> _downloaded; + + int64 _loadedTotal = 0; + int64 _loadingReady = 0; + int64 _loadingTotal = 0; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_file_click_handler.cpp b/Telegram/SourceFiles/data/data_file_click_handler.cpp index 704130f6ce..d7e1b5be82 100644 --- a/Telegram/SourceFiles/data/data_file_click_handler.cpp +++ b/Telegram/SourceFiles/data/data_file_click_handler.cpp @@ -9,7 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/click_handler_types.h" #include "core/file_utilities.h" +#include "core/application.h" #include "data/data_document.h" +#include "data/data_session.h" +#include "data/data_download_manager.h" #include "data/data_photo.h" FileClickHandler::FileClickHandler(FullMsgId context) @@ -98,7 +101,17 @@ void DocumentSaveClickHandler::Save( } void DocumentSaveClickHandler::onClickImpl() const { - Save(context(), document()); + const auto document = this->document(); + const auto itemId = context(); + Save(itemId, document); + if (document->loading()) { + if (const auto item = document->owner().message(itemId)) { + Core::App().downloadManager().addLoading({ + .item = item, + .document = document, + }); + } + } } DocumentCancelClickHandler::DocumentCancelClickHandler( diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index 084137b820..3c136d4487 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_changes.h" #include "data/data_user.h" +#include "data/data_download_manager.h" #include "data/stickers/data_stickers.h" #include "window/window_session_controller.h" #include "window/window_controller.h" @@ -158,6 +159,8 @@ Session::Session( _api->requestNotifySettings(MTP_inputNotifyUsers()); _api->requestNotifySettings(MTP_inputNotifyChats()); _api->requestNotifySettings(MTP_inputNotifyBroadcasts()); + + Core::App().downloadManager().trackSession(this); } void Session::setTmpPassword(const QByteArray &password, TimeId validUntil) {