mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-23 11:47:57 +00:00
All mtproto downloads using DownloadMtprotoTask.
This commit is contained in:
parent
4611727ab9
commit
ee94e78533
@ -55,7 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "ui/emoji_config.h"
|
#include "ui/emoji_config.h"
|
||||||
#include "support/support_helper.h"
|
#include "support/support_helper.h"
|
||||||
#include "storage/localimageloader.h"
|
#include "storage/localimageloader.h"
|
||||||
#include "storage/file_download_mtproto.h"
|
#include "storage/download_manager_mtproto.h"
|
||||||
#include "storage/file_upload.h"
|
#include "storage/file_upload.h"
|
||||||
#include "storage/storage_facade.h"
|
#include "storage/storage_facade.h"
|
||||||
#include "storage/storage_shared_media.h"
|
#include "storage/storage_shared_media.h"
|
||||||
@ -2973,12 +2973,12 @@ void ApiWrap::requestFileReference(
|
|||||||
|
|
||||||
void ApiWrap::refreshFileReference(
|
void ApiWrap::refreshFileReference(
|
||||||
Data::FileOrigin origin,
|
Data::FileOrigin origin,
|
||||||
not_null<mtpFileLoader*> loader,
|
not_null<Storage::DownloadMtprotoTask*> task,
|
||||||
int requestId,
|
int requestId,
|
||||||
const QByteArray ¤t) {
|
const QByteArray ¤t) {
|
||||||
return refreshFileReference(origin, crl::guard(loader, [=](
|
return refreshFileReference(origin, crl::guard(task, [=](
|
||||||
const UpdatedFileReferences &data) {
|
const UpdatedFileReferences &data) {
|
||||||
loader->refreshFileReferenceFrom(data, requestId, current);
|
task->refreshFileReferenceFrom(data, requestId, current);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ struct MessageGroupId;
|
|||||||
struct SendingAlbum;
|
struct SendingAlbum;
|
||||||
enum class SendMediaType;
|
enum class SendMediaType;
|
||||||
struct FileLoadTo;
|
struct FileLoadTo;
|
||||||
class mtpFileLoader;
|
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
@ -38,6 +37,7 @@ class Result;
|
|||||||
namespace Storage {
|
namespace Storage {
|
||||||
enum class SharedMediaType : signed char;
|
enum class SharedMediaType : signed char;
|
||||||
struct PreparedList;
|
struct PreparedList;
|
||||||
|
class DownloadMtprotoTask;
|
||||||
} // namespace Storage
|
} // namespace Storage
|
||||||
|
|
||||||
namespace Dialogs {
|
namespace Dialogs {
|
||||||
@ -201,7 +201,7 @@ public:
|
|||||||
FileReferencesHandler &&handler);
|
FileReferencesHandler &&handler);
|
||||||
void refreshFileReference(
|
void refreshFileReference(
|
||||||
Data::FileOrigin origin,
|
Data::FileOrigin origin,
|
||||||
not_null<mtpFileLoader*> loader,
|
not_null<Storage::DownloadMtprotoTask*> task,
|
||||||
int requestId,
|
int requestId,
|
||||||
const QByteArray ¤t);
|
const QByteArray ¤t);
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "observer_peer.h"
|
#include "observer_peer.h"
|
||||||
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "base/call_delayed.h"
|
#include "base/call_delayed.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "main/main_account.h"
|
#include "main/main_account.h"
|
||||||
|
#include "mtproto/facade.h"
|
||||||
#include "ui/widgets/checkbox.h"
|
#include "ui/widgets/checkbox.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/input_fields.h"
|
||||||
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
|
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "chat_helpers/stickers.h"
|
#include "chat_helpers/stickers.h"
|
||||||
#include "boxes/confirm_box.h"
|
#include "boxes/confirm_box.h"
|
||||||
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_peer_values.h"
|
#include "data/data_peer_values.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "storage/localstorage.h"
|
#include "storage/localstorage.h"
|
||||||
|
@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "styles/style_chat_helpers.h"
|
#include "data/data_file_origin.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/input_fields.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
#include "facades.h"
|
#include "facades.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
#include <QtWidgets/QApplication>
|
#include <QtWidgets/QApplication>
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
|
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "boxes/stickers_box.h"
|
#include "boxes/stickers_box.h"
|
||||||
#include "boxes/confirm_box.h"
|
#include "boxes/confirm_box.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
@ -341,6 +341,7 @@ QString PlatformString() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void StartCatching(not_null<Core::Launcher*> launcher) {
|
void StartCatching(not_null<Core::Launcher*> launcher) {
|
||||||
|
return; AssertIsDebug();
|
||||||
#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS
|
#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS
|
||||||
ProcessAnnotations["Binary"] = cExeName().toUtf8().constData();
|
ProcessAnnotations["Binary"] = cExeName().toUtf8().constData();
|
||||||
ProcessAnnotations["ApiId"] = QString::number(ApiId).toUtf8().constData();
|
ProcessAnnotations["ApiId"] = QString::number(ApiId).toUtf8().constData();
|
||||||
|
@ -817,7 +817,6 @@ void DocumentData::destroyLoader() const {
|
|||||||
if (cancelled()) {
|
if (cancelled()) {
|
||||||
loader->cancel();
|
loader->cancel();
|
||||||
}
|
}
|
||||||
loader->stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DocumentData::loading() const {
|
bool DocumentData::loading() const {
|
||||||
|
@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_web_page.h"
|
#include "data/data_web_page.h"
|
||||||
#include "data/data_poll.h"
|
#include "data/data_poll.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "layout.h"
|
#include "layout.h"
|
||||||
#include "storage/file_upload.h"
|
#include "storage/file_upload.h"
|
||||||
|
@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_scheduled_messages.h"
|
#include "data/data_scheduled_messages.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/history_message.h"
|
#include "history/history_message.h"
|
||||||
|
@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_media_types.h"
|
#include "data/data_media_types.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
#include "styles/style_history.h"
|
#include "styles/style_history.h"
|
||||||
|
|
||||||
|
@ -13,13 +13,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_photo.h"
|
#include "data/data_photo.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
#include "window/themes/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "window/window_peer_menu.h"
|
#include "window/window_peer_menu.h"
|
||||||
#include "storage/file_download.h"
|
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
#include "ui/inactive_press.h"
|
#include "ui/inactive_press.h"
|
||||||
@ -574,7 +574,7 @@ void ListWidget::start() {
|
|||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
ObservableViewer(
|
ObservableViewer(
|
||||||
session().downloader().taskFinished()
|
session().downloaderTaskFinished()
|
||||||
) | rpl::start_with_next([this] { update(); }, lifetime());
|
) | rpl::start_with_next([this] { update(); }, lifetime());
|
||||||
session().data().itemLayoutChanged(
|
session().data().itemLayoutChanged(
|
||||||
) | rpl::start_with_next([this](auto item) {
|
) | rpl::start_with_next([this](auto item) {
|
||||||
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_photo.h"
|
#include "data/data_photo.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "styles/style_overview.h"
|
#include "styles/style_overview.h"
|
||||||
#include "styles/style_history.h"
|
#include "styles/style_history.h"
|
||||||
#include "styles/style_chat_helpers.h"
|
#include "styles/style_chat_helpers.h"
|
||||||
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_photo.h"
|
#include "data/data_photo.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
#include "inline_bots/inline_bot_result.h"
|
#include "inline_bots/inline_bot_result.h"
|
||||||
#include "inline_bots/inline_bot_layout_internal.h"
|
#include "inline_bots/inline_bot_layout_internal.h"
|
||||||
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_photo.h"
|
#include "data/data_photo.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "inline_bots/inline_bot_layout_item.h"
|
#include "inline_bots/inline_bot_layout_item.h"
|
||||||
#include "inline_bots/inline_bot_send_data.h"
|
#include "inline_bots/inline_bot_send_data.h"
|
||||||
#include "storage/file_download.h"
|
#include "storage/file_download.h"
|
||||||
|
@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "styles/style_chat_helpers.h"
|
#include "data/data_file_origin.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
#include "facades.h"
|
#include "facades.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
#include <QtWidgets/QApplication>
|
#include <QtWidgets/QApplication>
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "main/main_account.h"
|
#include "main/main_account.h"
|
||||||
#include "chat_helpers/stickers_emoji_pack.h"
|
#include "chat_helpers/stickers_emoji_pack.h"
|
||||||
#include "storage/file_download.h"
|
#include "storage/file_download.h"
|
||||||
|
#include "storage/download_manager_mtproto.h"
|
||||||
#include "storage/file_upload.h"
|
#include "storage/file_upload.h"
|
||||||
#include "storage/localstorage.h"
|
#include "storage/localstorage.h"
|
||||||
#include "storage/storage_facade.h"
|
#include "storage/storage_facade.h"
|
||||||
@ -44,7 +45,7 @@ Session::Session(
|
|||||||
, _autoLockTimer([=] { checkAutoLock(); })
|
, _autoLockTimer([=] { checkAutoLock(); })
|
||||||
, _api(std::make_unique<ApiWrap>(this))
|
, _api(std::make_unique<ApiWrap>(this))
|
||||||
, _calls(std::make_unique<Calls::Instance>(this))
|
, _calls(std::make_unique<Calls::Instance>(this))
|
||||||
, _downloader(std::make_unique<Storage::DownloadManager>(_api.get()))
|
, _downloader(std::make_unique<Storage::DownloadManagerMtproto>(_api.get()))
|
||||||
, _uploader(std::make_unique<Storage::Uploader>(_api.get()))
|
, _uploader(std::make_unique<Storage::Uploader>(_api.get()))
|
||||||
, _storage(std::make_unique<Storage::Facade>())
|
, _storage(std::make_unique<Storage::Facade>())
|
||||||
, _notifications(std::make_unique<Window::Notifications::System>(this))
|
, _notifications(std::make_unique<Window::Notifications::System>(this))
|
||||||
|
@ -29,7 +29,7 @@ class Session;
|
|||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
class DownloadManager;
|
class DownloadManagerMtproto;
|
||||||
class Uploader;
|
class Uploader;
|
||||||
class Facade;
|
class Facade;
|
||||||
} // namespace Storage
|
} // namespace Storage
|
||||||
@ -80,7 +80,7 @@ public:
|
|||||||
}
|
}
|
||||||
bool validateSelf(const MTPUser &user);
|
bool validateSelf(const MTPUser &user);
|
||||||
|
|
||||||
[[nodiscard]] Storage::DownloadManager &downloader() {
|
[[nodiscard]] Storage::DownloadManagerMtproto &downloader() {
|
||||||
return *_downloader;
|
return *_downloader;
|
||||||
}
|
}
|
||||||
[[nodiscard]] Storage::Uploader &uploader() {
|
[[nodiscard]] Storage::Uploader &uploader() {
|
||||||
@ -145,7 +145,7 @@ private:
|
|||||||
|
|
||||||
const std::unique_ptr<ApiWrap> _api;
|
const std::unique_ptr<ApiWrap> _api;
|
||||||
const std::unique_ptr<Calls::Instance> _calls;
|
const std::unique_ptr<Calls::Instance> _calls;
|
||||||
const std::unique_ptr<Storage::DownloadManager> _downloader;
|
const std::unique_ptr<Storage::DownloadManagerMtproto> _downloader;
|
||||||
const std::unique_ptr<Storage::Uploader> _uploader;
|
const std::unique_ptr<Storage::Uploader> _uploader;
|
||||||
const std::unique_ptr<Storage::Facade> _storage;
|
const std::unique_ptr<Storage::Facade> _storage;
|
||||||
const std::unique_ptr<Window::Notifications::System> _notifications;
|
const std::unique_ptr<Window::Notifications::System> _notifications;
|
||||||
|
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_scheduled_messages.h"
|
#include "data/data_scheduled_messages.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "api/api_text_entities.h"
|
#include "api/api_text_entities.h"
|
||||||
#include "ui/special_buttons.h"
|
#include "ui/special_buttons.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
|
@ -21,27 +21,17 @@ constexpr auto kMaxConcurrentRequests = 4;
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
LoaderMtproto::LoaderMtproto(
|
LoaderMtproto::LoaderMtproto(
|
||||||
not_null<Storage::DownloadManager*> owner,
|
not_null<Storage::DownloadManagerMtproto*> owner,
|
||||||
const StorageFileLocation &location,
|
const StorageFileLocation &location,
|
||||||
int size,
|
int size,
|
||||||
Data::FileOrigin origin)
|
Data::FileOrigin origin)
|
||||||
: _owner(owner)
|
: DownloadMtprotoTask(owner, location, origin)
|
||||||
, _location(location)
|
|
||||||
, _dcId(location.dcId())
|
|
||||||
, _size(size)
|
, _size(size)
|
||||||
, _origin(origin)
|
, _api(api().instance()) {
|
||||||
, _api(_owner->api().instance()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
LoaderMtproto::~LoaderMtproto() {
|
|
||||||
for (const auto [index, amount] : _amountByDcIndex) {
|
|
||||||
changeRequestedAmount(index, -amount);
|
|
||||||
}
|
|
||||||
_owner->remove(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Storage::Cache::Key> LoaderMtproto::baseCacheKey() const {
|
std::optional<Storage::Cache::Key> LoaderMtproto::baseCacheKey() const {
|
||||||
return _location.bigFileBaseCacheKey();
|
return location().data.get<StorageFileLocation>().bigFileBaseCacheKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
int LoaderMtproto::size() const {
|
int LoaderMtproto::size() const {
|
||||||
@ -58,22 +48,19 @@ void LoaderMtproto::load(int offset) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_requests.contains(offset)) {
|
if (haveSentRequestForOffset(offset)) {
|
||||||
return;
|
return;
|
||||||
} else if (_requested.add(offset)) {
|
} else if (_requested.add(offset)) {
|
||||||
_owner->enqueue(this); // #TODO download priority
|
addToQueue(); // #TODO download priority
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoaderMtproto::stop() {
|
void LoaderMtproto::stop() {
|
||||||
crl::on_main(this, [=] {
|
crl::on_main(this, [=] {
|
||||||
ranges::for_each(
|
cancelAllRequests();
|
||||||
base::take(_requests),
|
|
||||||
_api.requestCanceller(),
|
|
||||||
&base::flat_map<int, mtpRequestId>::value_type::second);
|
|
||||||
_requested.clear();
|
_requested.clear();
|
||||||
_owner->remove(this);
|
removeFromQueue();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,9 +71,9 @@ void LoaderMtproto::cancel(int offset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LoaderMtproto::cancelForOffset(int offset) {
|
void LoaderMtproto::cancelForOffset(int offset) {
|
||||||
if (const auto requestId = _requests.take(offset)) {
|
if (haveSentRequestForOffset(offset)) {
|
||||||
_api.request(*requestId).cancel();
|
cancelRequestForOffset(offset);
|
||||||
_owner->enqueue(this);
|
addToQueue(); // #TODO download priority
|
||||||
} else {
|
} else {
|
||||||
_requested.remove(offset);
|
_requested.remove(offset);
|
||||||
}
|
}
|
||||||
@ -107,100 +94,26 @@ void LoaderMtproto::increasePriority() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoaderMtproto::changeRequestedAmount(int index, int amount) {
|
|
||||||
_owner->requestedAmountIncrement(_dcId, index, amount);
|
|
||||||
_amountByDcIndex[index] += amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
MTP::DcId LoaderMtproto::dcId() const {
|
|
||||||
return _dcId;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LoaderMtproto::readyToRequest() const {
|
bool LoaderMtproto::readyToRequest() const {
|
||||||
return !_requested.empty();
|
return !_requested.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoaderMtproto::loadPart(int dcIndex) {
|
int LoaderMtproto::takeNextRequestOffset() {
|
||||||
const auto offset = _requested.take().value_or(-1);
|
const auto offset = _requested.take();
|
||||||
if (offset < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
changeRequestedAmount(dcIndex, kPartSize);
|
Ensures(offset.has_value());
|
||||||
|
return *offset;
|
||||||
const auto usedFileReference = _location.fileReference();
|
|
||||||
const auto id = _api.request(MTPupload_GetFile(
|
|
||||||
MTP_flags(0),
|
|
||||||
_location.tl(Auth().userId()),
|
|
||||||
MTP_int(offset),
|
|
||||||
MTP_int(kPartSize)
|
|
||||||
)).done([=](const MTPupload_File &result) {
|
|
||||||
changeRequestedAmount(dcIndex, -kPartSize);
|
|
||||||
requestDone(offset, result);
|
|
||||||
}).fail([=](const RPCError &error) {
|
|
||||||
changeRequestedAmount(dcIndex, -kPartSize);
|
|
||||||
requestFailed(offset, error, usedFileReference);
|
|
||||||
}).toDC(
|
|
||||||
MTP::downloadDcId(_dcId, dcIndex)
|
|
||||||
).send();
|
|
||||||
_requests.emplace(offset, id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoaderMtproto::requestDone(int offset, const MTPupload_File &result) {
|
bool LoaderMtproto::feedPart(int offset, const QByteArray &bytes) {
|
||||||
result.match([&](const MTPDupload_file &data) {
|
_parts.fire({ offset, bytes });
|
||||||
_requests.erase(offset);
|
return true;
|
||||||
_owner->enqueue(this);
|
|
||||||
_parts.fire({ offset, data.vbytes().v });
|
|
||||||
}, [&](const MTPDupload_fileCdnRedirect &data) {
|
|
||||||
changeCdnParams(
|
|
||||||
offset,
|
|
||||||
data.vdc_id().v,
|
|
||||||
data.vfile_token().v,
|
|
||||||
data.vencryption_key().v,
|
|
||||||
data.vencryption_iv().v,
|
|
||||||
data.vfile_hashes().v);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoaderMtproto::changeCdnParams(
|
void LoaderMtproto::cancelOnFail() {
|
||||||
int offset,
|
|
||||||
MTP::DcId dcId,
|
|
||||||
const QByteArray &token,
|
|
||||||
const QByteArray &encryptionKey,
|
|
||||||
const QByteArray &encryptionIV,
|
|
||||||
const QVector<MTPFileHash> &hashes) {
|
|
||||||
// #TODO streaming later cdn
|
|
||||||
_parts.fire({ LoadedPart::kFailedOffset });
|
_parts.fire({ LoadedPart::kFailedOffset });
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoaderMtproto::requestFailed(
|
|
||||||
int offset,
|
|
||||||
const RPCError &error,
|
|
||||||
const QByteArray &usedFileReference) {
|
|
||||||
const auto &type = error.type();
|
|
||||||
const auto fail = [=] {
|
|
||||||
_parts.fire({ LoadedPart::kFailedOffset });
|
|
||||||
};
|
|
||||||
if (error.code() != 400 || !type.startsWith(qstr("FILE_REFERENCE_"))) {
|
|
||||||
return fail();
|
|
||||||
}
|
|
||||||
const auto callback = [=](const Data::UpdatedFileReferences &updated) {
|
|
||||||
_location.refreshFileReference(updated);
|
|
||||||
if (_location.fileReference() == usedFileReference) {
|
|
||||||
fail();
|
|
||||||
} else if (!_requests.take(offset)) {
|
|
||||||
// Request with such offset was already cancelled.
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
_requested.add(offset);
|
|
||||||
_owner->enqueue(this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
_owner->api().refreshFileReference(
|
|
||||||
_origin,
|
|
||||||
crl::guard(this, callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<LoadedPart> LoaderMtproto::parts() const {
|
rpl::producer<LoadedPart> LoaderMtproto::parts() const {
|
||||||
return _parts.events();
|
return _parts.events();
|
||||||
}
|
}
|
||||||
|
@ -10,22 +10,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "media/streaming/media_streaming_loader.h"
|
#include "media/streaming/media_streaming_loader.h"
|
||||||
#include "mtproto/sender.h"
|
#include "mtproto/sender.h"
|
||||||
#include "data/data_file_origin.h"
|
#include "data/data_file_origin.h"
|
||||||
#include "storage/file_download.h"
|
#include "storage/download_manager_mtproto.h"
|
||||||
|
|
||||||
namespace Media {
|
namespace Media {
|
||||||
namespace Streaming {
|
namespace Streaming {
|
||||||
|
|
||||||
class LoaderMtproto
|
class LoaderMtproto : public Loader, public Storage::DownloadMtprotoTask {
|
||||||
: public Loader
|
|
||||||
, public base::has_weak_ptr
|
|
||||||
, public Storage::Downloader {
|
|
||||||
public:
|
public:
|
||||||
LoaderMtproto(
|
LoaderMtproto(
|
||||||
not_null<Storage::DownloadManager*> owner,
|
not_null<Storage::DownloadManagerMtproto*> owner,
|
||||||
const StorageFileLocation &location,
|
const StorageFileLocation &location,
|
||||||
int size,
|
int size,
|
||||||
Data::FileOrigin origin);
|
Data::FileOrigin origin);
|
||||||
~LoaderMtproto();
|
|
||||||
|
|
||||||
[[nodiscard]] auto baseCacheKey() const
|
[[nodiscard]] auto baseCacheKey() const
|
||||||
-> std::optional<Storage::Cache::Key> override;
|
-> std::optional<Storage::Cache::Key> override;
|
||||||
@ -44,39 +40,18 @@ public:
|
|||||||
void clearAttachedDownloader() override;
|
void clearAttachedDownloader() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MTP::DcId dcId() const override;
|
|
||||||
bool readyToRequest() const override;
|
bool readyToRequest() const override;
|
||||||
void loadPart(int dcIndex) override;
|
int takeNextRequestOffset() override;
|
||||||
|
bool feedPart(int offset, const QByteArray &bytes) override;
|
||||||
|
void cancelOnFail() override;
|
||||||
|
|
||||||
void requestDone(int offset, const MTPupload_File &result);
|
|
||||||
void requestFailed(
|
|
||||||
int offset,
|
|
||||||
const RPCError &error,
|
|
||||||
const QByteArray &usedFileReference);
|
|
||||||
void changeCdnParams(
|
|
||||||
int offset,
|
|
||||||
MTP::DcId dcId,
|
|
||||||
const QByteArray &token,
|
|
||||||
const QByteArray &encryptionKey,
|
|
||||||
const QByteArray &encryptionIV,
|
|
||||||
const QVector<MTPFileHash> &hashes);
|
|
||||||
void cancelForOffset(int offset);
|
void cancelForOffset(int offset);
|
||||||
void changeRequestedAmount(int index, int amount);
|
|
||||||
|
|
||||||
const not_null<Storage::DownloadManager*> _owner;
|
|
||||||
|
|
||||||
// _location can be changed with an updated file_reference.
|
|
||||||
StorageFileLocation _location;
|
|
||||||
MTP::DcId _dcId = 0;
|
|
||||||
|
|
||||||
const int _size = 0;
|
const int _size = 0;
|
||||||
const Data::FileOrigin _origin;
|
|
||||||
|
|
||||||
MTP::Sender _api;
|
MTP::Sender _api;
|
||||||
|
|
||||||
PriorityQueue _requested;
|
PriorityQueue _requested;
|
||||||
base::flat_map<int, mtpRequestId> _requests;
|
|
||||||
base::flat_map<int, int> _amountByDcIndex;
|
|
||||||
rpl::event_stream<LoadedPart> _parts;
|
rpl::event_stream<LoadedPart> _parts;
|
||||||
|
|
||||||
Storage::StreamedFileDownloader *_downloader = nullptr;
|
Storage::StreamedFileDownloader *_downloader = nullptr;
|
||||||
|
@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "window/themes/window_theme_preview.h"
|
#include "window/themes/window_theme_preview.h"
|
||||||
#include "window/window_peer_menu.h"
|
#include "window/window_peer_menu.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_web_page.h"
|
#include "data/data_web_page.h"
|
||||||
#include "data/data_media_types.h"
|
#include "data/data_media_types.h"
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "styles/style_overview.h"
|
#include "styles/style_overview.h"
|
||||||
#include "styles/style_history.h"
|
#include "styles/style_history.h"
|
||||||
#include "core/file_utilities.h"
|
#include "core/file_utilities.h"
|
||||||
|
@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_cloud_themes.h"
|
#include "data/data_cloud_themes.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "chat_helpers/emoji_sets_manager.h"
|
#include "chat_helpers/emoji_sets_manager.h"
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
#include "base/call_delayed.h"
|
#include "base/call_delayed.h"
|
||||||
|
714
Telegram/SourceFiles/storage/download_manager_mtproto.cpp
Normal file
714
Telegram/SourceFiles/storage/download_manager_mtproto.cpp
Normal file
@ -0,0 +1,714 @@
|
|||||||
|
/*
|
||||||
|
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 "storage/download_manager_mtproto.h"
|
||||||
|
|
||||||
|
#include "mtproto/facade.h"
|
||||||
|
#include "mtproto/mtproto_auth_key.h"
|
||||||
|
#include "mtproto/mtproto_rpc_sender.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "base/openssl_help.h"
|
||||||
|
|
||||||
|
namespace Storage {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// How much time without download causes additional session kill.
|
||||||
|
constexpr auto kKillSessionTimeout = 15 * crl::time(1000);
|
||||||
|
|
||||||
|
// Max 16 file parts downloaded at the same time, 128 KB each.
|
||||||
|
constexpr auto kMaxFileQueries = 16;
|
||||||
|
|
||||||
|
constexpr auto kMaxWaitedInConnection = 512 * 1024;
|
||||||
|
|
||||||
|
// Max 8 http[s] files downloaded at the same time.
|
||||||
|
constexpr auto kMaxWebFileQueries = 8;
|
||||||
|
|
||||||
|
constexpr auto kStartSessionsCount = 1;
|
||||||
|
constexpr auto kMaxSessionsCount = 8;
|
||||||
|
constexpr auto kResetDownloadPrioritiesTimeout = crl::time(200);
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void DownloadManagerMtproto::Queue::enqueue(not_null<Task*> task) {
|
||||||
|
const auto i = ranges::find(_tasks, task);
|
||||||
|
if (i != end(_tasks)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_tasks.push_back(task);
|
||||||
|
_previousGeneration.erase(
|
||||||
|
ranges::remove(_previousGeneration, task),
|
||||||
|
end(_previousGeneration));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadManagerMtproto::Queue::remove(not_null<Task*> task) {
|
||||||
|
_tasks.erase(ranges::remove(_tasks, task), end(_tasks));
|
||||||
|
_previousGeneration.erase(
|
||||||
|
ranges::remove(_previousGeneration, task),
|
||||||
|
end(_previousGeneration));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadManagerMtproto::Queue::resetGeneration() {
|
||||||
|
if (!_previousGeneration.empty()) {
|
||||||
|
_tasks.reserve(_tasks.size() + _previousGeneration.size());
|
||||||
|
std::copy(
|
||||||
|
begin(_previousGeneration),
|
||||||
|
end(_previousGeneration),
|
||||||
|
std::back_inserter(_tasks));
|
||||||
|
_previousGeneration.clear();
|
||||||
|
}
|
||||||
|
std::swap(_tasks, _previousGeneration);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DownloadManagerMtproto::Queue::empty() const {
|
||||||
|
return _tasks.empty() && _previousGeneration.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto DownloadManagerMtproto::Queue::nextTask() const -> Task* {
|
||||||
|
auto &&all = ranges::view::concat(_tasks, _previousGeneration);
|
||||||
|
const auto i = ranges::find(all, true, &Task::readyToRequest);
|
||||||
|
return (i != all.end()) ? i->get() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadManagerMtproto::DownloadManagerMtproto(not_null<ApiWrap*> api)
|
||||||
|
: _api(api)
|
||||||
|
, _resetGenerationTimer([=] { resetGeneration(); })
|
||||||
|
, _killDownloadSessionsTimer([=] { killDownloadSessions(); }) {
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadManagerMtproto::~DownloadManagerMtproto() {
|
||||||
|
killDownloadSessions();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadManagerMtproto::enqueue(not_null<Task*> task) {
|
||||||
|
const auto dcId = task->dcId();
|
||||||
|
auto &queue = _queues[dcId];
|
||||||
|
queue.enqueue(task);
|
||||||
|
if (!_resetGenerationTimer.isActive()) {
|
||||||
|
_resetGenerationTimer.callOnce(kResetDownloadPrioritiesTimeout);
|
||||||
|
}
|
||||||
|
checkSendNext(dcId, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadManagerMtproto::remove(not_null<Task*> task) {
|
||||||
|
const auto dcId = task->dcId();
|
||||||
|
auto &queue = _queues[dcId];
|
||||||
|
queue.remove(task);
|
||||||
|
checkSendNext(dcId, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadManagerMtproto::resetGeneration() {
|
||||||
|
_resetGenerationTimer.cancel();
|
||||||
|
for (auto &[dcId, queue] : _queues) {
|
||||||
|
queue.resetGeneration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadManagerMtproto::checkSendNext() {
|
||||||
|
for (auto &[dcId, queue] : _queues) {
|
||||||
|
if (queue.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
checkSendNext(dcId, queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadManagerMtproto::checkSendNext(MTP::DcId dcId, Queue &queue) {
|
||||||
|
const auto bestIndex = [&] {
|
||||||
|
const auto i = _requestedBytesAmount.find(dcId);
|
||||||
|
if (i == end(_requestedBytesAmount)) {
|
||||||
|
_requestedBytesAmount[dcId].resize(kStartSessionsCount);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const auto j = ranges::min_element(i->second);
|
||||||
|
const auto already = *j;
|
||||||
|
return (already + kDownloadPartSize <= kMaxWaitedInConnection)
|
||||||
|
? (j - begin(i->second))
|
||||||
|
: -1;
|
||||||
|
}();
|
||||||
|
if (bestIndex >= 0) {
|
||||||
|
if (const auto task = queue.nextTask()) {
|
||||||
|
task->loadPart(bestIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadManagerMtproto::requestedAmountIncrement(
|
||||||
|
MTP::DcId dcId,
|
||||||
|
int index,
|
||||||
|
int amount) {
|
||||||
|
Expects(dcId != 0);
|
||||||
|
|
||||||
|
using namespace rpl::mappers;
|
||||||
|
|
||||||
|
auto it = _requestedBytesAmount.find(dcId);
|
||||||
|
if (it == _requestedBytesAmount.end()) {
|
||||||
|
it = _requestedBytesAmount.emplace(
|
||||||
|
dcId,
|
||||||
|
std::vector<int>(dcId ? kStartSessionsCount : 1, 0)
|
||||||
|
).first;
|
||||||
|
}
|
||||||
|
it->second[index] += amount;
|
||||||
|
if (amount > 0) {
|
||||||
|
killDownloadSessionsStop(dcId);
|
||||||
|
} else {
|
||||||
|
crl::on_main(this, [=] { checkSendNext(); });
|
||||||
|
if (ranges::find_if(it->second, _1 > 0) == end(it->second)) {
|
||||||
|
killDownloadSessionsStart(dcId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int DownloadManagerMtproto::chooseDcIndexForRequest(MTP::DcId dcId) {
|
||||||
|
const auto i = _requestedBytesAmount.find(dcId);
|
||||||
|
return (i != end(_requestedBytesAmount))
|
||||||
|
? (ranges::min_element(i->second) - begin(i->second))
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadManagerMtproto::killDownloadSessionsStart(MTP::DcId dcId) {
|
||||||
|
if (!_killDownloadSessionTimes.contains(dcId)) {
|
||||||
|
_killDownloadSessionTimes.emplace(
|
||||||
|
dcId,
|
||||||
|
crl::now() + kKillSessionTimeout);
|
||||||
|
}
|
||||||
|
if (!_killDownloadSessionsTimer.isActive()) {
|
||||||
|
_killDownloadSessionsTimer.callOnce(kKillSessionTimeout + 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadManagerMtproto::killDownloadSessionsStop(MTP::DcId dcId) {
|
||||||
|
_killDownloadSessionTimes.erase(dcId);
|
||||||
|
if (_killDownloadSessionTimes.empty()
|
||||||
|
&& _killDownloadSessionsTimer.isActive()) {
|
||||||
|
_killDownloadSessionsTimer.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadManagerMtproto::killDownloadSessions() {
|
||||||
|
const auto now = crl::now();
|
||||||
|
auto left = kKillSessionTimeout;
|
||||||
|
for (auto i = _killDownloadSessionTimes.begin(); i != _killDownloadSessionTimes.end(); ) {
|
||||||
|
if (i->second <= now) {
|
||||||
|
const auto j = _requestedBytesAmount.find(i->first);
|
||||||
|
if (j != end(_requestedBytesAmount)) {
|
||||||
|
for (auto index = 0; index != int(j->second.size()); ++index) {
|
||||||
|
MTP::stopSession(MTP::downloadDcId(i->first, index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = _killDownloadSessionTimes.erase(i);
|
||||||
|
} else {
|
||||||
|
if (i->second - now < left) {
|
||||||
|
left = i->second - now;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!_killDownloadSessionTimes.empty()) {
|
||||||
|
_killDownloadSessionsTimer.callOnce(left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadMtprotoTask::DownloadMtprotoTask(
|
||||||
|
not_null<DownloadManagerMtproto*> owner,
|
||||||
|
const StorageFileLocation &location,
|
||||||
|
Data::FileOrigin origin)
|
||||||
|
: _owner(owner)
|
||||||
|
, _dcId(location.dcId())
|
||||||
|
, _location({ location })
|
||||||
|
, _origin(origin) {
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadMtprotoTask::DownloadMtprotoTask(
|
||||||
|
not_null<DownloadManagerMtproto*> owner,
|
||||||
|
MTP::DcId dcId,
|
||||||
|
const Location &location)
|
||||||
|
: _owner(owner)
|
||||||
|
, _dcId(dcId)
|
||||||
|
, _location(location) {
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadMtprotoTask::~DownloadMtprotoTask() {
|
||||||
|
cancelAllRequests();
|
||||||
|
_owner->remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
MTP::DcId DownloadMtprotoTask::dcId() const {
|
||||||
|
return _dcId;
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::FileOrigin DownloadMtprotoTask::fileOrigin() const {
|
||||||
|
return _origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 DownloadMtprotoTask::objectId() const {
|
||||||
|
if (const auto v = base::get_if<StorageFileLocation>(&_location.data)) {
|
||||||
|
return v->objectId();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DownloadMtprotoTask::Location &DownloadMtprotoTask::location() const {
|
||||||
|
return _location;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::refreshFileReferenceFrom(
|
||||||
|
const Data::UpdatedFileReferences &updates,
|
||||||
|
int requestId,
|
||||||
|
const QByteArray ¤t) {
|
||||||
|
if (const auto v = base::get_if<StorageFileLocation>(&_location.data)) {
|
||||||
|
v->refreshFileReference(updates);
|
||||||
|
if (v->fileReference() == current) {
|
||||||
|
cancelOnFail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cancelOnFail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
makeRequest(finishSentRequest(requestId));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::loadPart(int dcIndex) {
|
||||||
|
makeRequest({ takeNextRequestOffset(), dcIndex });
|
||||||
|
}
|
||||||
|
|
||||||
|
mtpRequestId DownloadMtprotoTask::sendRequest(const RequestData &requestData) {
|
||||||
|
const auto offset = requestData.offset;
|
||||||
|
const auto limit = Storage::kDownloadPartSize;
|
||||||
|
const auto shiftedDcId = MTP::downloadDcId(
|
||||||
|
_cdnDcId ? _cdnDcId : dcId(),
|
||||||
|
requestData.dcIndex);
|
||||||
|
if (_cdnDcId) {
|
||||||
|
return api().request(MTPupload_GetCdnFile(
|
||||||
|
MTP_bytes(_cdnToken),
|
||||||
|
MTP_int(offset),
|
||||||
|
MTP_int(limit)
|
||||||
|
)).done([=](const MTPupload_CdnFile &result, mtpRequestId id) {
|
||||||
|
cdnPartLoaded(result, id);
|
||||||
|
}).fail([=](const RPCError &error, mtpRequestId id) {
|
||||||
|
cdnPartFailed(error, id);
|
||||||
|
}).toDC(shiftedDcId).send();
|
||||||
|
}
|
||||||
|
return _location.data.match([&](const WebFileLocation &location) {
|
||||||
|
return api().request(MTPupload_GetWebFile(
|
||||||
|
MTP_inputWebFileLocation(
|
||||||
|
MTP_bytes(location.url()),
|
||||||
|
MTP_long(location.accessHash())),
|
||||||
|
MTP_int(offset),
|
||||||
|
MTP_int(limit)
|
||||||
|
)).done([=](const MTPupload_WebFile &result, mtpRequestId id) {
|
||||||
|
webPartLoaded(result, id);
|
||||||
|
}).fail([=](const RPCError &error, mtpRequestId id) {
|
||||||
|
partFailed(error, id);
|
||||||
|
}).toDC(shiftedDcId).send();
|
||||||
|
}, [&](const GeoPointLocation &location) {
|
||||||
|
return api().request(MTPupload_GetWebFile(
|
||||||
|
MTP_inputWebFileGeoPointLocation(
|
||||||
|
MTP_inputGeoPoint(
|
||||||
|
MTP_double(location.lat),
|
||||||
|
MTP_double(location.lon)),
|
||||||
|
MTP_long(location.access),
|
||||||
|
MTP_int(location.width),
|
||||||
|
MTP_int(location.height),
|
||||||
|
MTP_int(location.zoom),
|
||||||
|
MTP_int(location.scale)),
|
||||||
|
MTP_int(offset),
|
||||||
|
MTP_int(limit)
|
||||||
|
)).done([=](const MTPupload_WebFile &result, mtpRequestId id) {
|
||||||
|
webPartLoaded(result, id);
|
||||||
|
}).fail([=](const RPCError &error, mtpRequestId id) {
|
||||||
|
partFailed(error, id);
|
||||||
|
}).toDC(shiftedDcId).send();
|
||||||
|
}, [&](const StorageFileLocation &location) {
|
||||||
|
const auto reference = location.fileReference();
|
||||||
|
return api().request(MTPupload_GetFile(
|
||||||
|
MTP_flags(0),
|
||||||
|
location.tl(api().session().userId()),
|
||||||
|
MTP_int(offset),
|
||||||
|
MTP_int(limit)
|
||||||
|
)).done([=](const MTPupload_File &result, mtpRequestId id) {
|
||||||
|
normalPartLoaded(result, id);
|
||||||
|
}).fail([=](const RPCError &error, mtpRequestId id) {
|
||||||
|
normalPartFailed(reference, error, id);
|
||||||
|
}).toDC(shiftedDcId).send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DownloadMtprotoTask::setWebFileSizeHook(int size) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::makeRequest(const RequestData &requestData) {
|
||||||
|
placeSentRequest(sendRequest(requestData), requestData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::requestMoreCdnFileHashes() {
|
||||||
|
if (_cdnHashesRequestId || _cdnUncheckedParts.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto requestData = _cdnUncheckedParts.cbegin()->first;
|
||||||
|
const auto shiftedDcId = MTP::downloadDcId(
|
||||||
|
dcId(),
|
||||||
|
requestData.dcIndex);
|
||||||
|
_cdnHashesRequestId = api().request(MTPupload_GetCdnFileHashes(
|
||||||
|
MTP_bytes(_cdnToken),
|
||||||
|
MTP_int(requestData.offset)
|
||||||
|
)).done([=](const MTPVector<MTPFileHash> &result, mtpRequestId id) {
|
||||||
|
getCdnFileHashesDone(result, id);
|
||||||
|
}).fail([=](const RPCError &error, mtpRequestId id) {
|
||||||
|
cdnPartFailed(error, id);
|
||||||
|
}).toDC(shiftedDcId).send();
|
||||||
|
placeSentRequest(_cdnHashesRequestId, requestData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::normalPartLoaded(
|
||||||
|
const MTPupload_File &result,
|
||||||
|
mtpRequestId requestId) {
|
||||||
|
const auto requestData = finishSentRequest(requestId);
|
||||||
|
result.match([&](const MTPDupload_fileCdnRedirect &data) {
|
||||||
|
switchToCDN(requestData, data);
|
||||||
|
}, [&](const MTPDupload_file &data) {
|
||||||
|
partLoaded(requestData.offset, data.vbytes().v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::webPartLoaded(
|
||||||
|
const MTPupload_WebFile &result,
|
||||||
|
mtpRequestId requestId) {
|
||||||
|
result.match([&](const MTPDupload_webFile &data) {
|
||||||
|
const auto requestData = finishSentRequest(requestId);
|
||||||
|
if (setWebFileSizeHook(data.vsize().v)) {
|
||||||
|
partLoaded(requestData.offset, data.vbytes().v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::cdnPartLoaded(const MTPupload_CdnFile &result, mtpRequestId requestId) {
|
||||||
|
const auto requestData = finishSentRequest(requestId);
|
||||||
|
result.match([&](const MTPDupload_cdnFileReuploadNeeded &data) {
|
||||||
|
const auto shiftedDcId = MTP::downloadDcId(
|
||||||
|
dcId(),
|
||||||
|
requestData.dcIndex);
|
||||||
|
const auto requestId = api().request(MTPupload_ReuploadCdnFile(
|
||||||
|
MTP_bytes(_cdnToken),
|
||||||
|
data.vrequest_token()
|
||||||
|
)).done([=](const MTPVector<MTPFileHash> &result, mtpRequestId id) {
|
||||||
|
reuploadDone(result, id);
|
||||||
|
}).fail([=](const RPCError &error, mtpRequestId id) {
|
||||||
|
cdnPartFailed(error, id);
|
||||||
|
}).toDC(shiftedDcId).send();
|
||||||
|
placeSentRequest(requestId, requestData);
|
||||||
|
}, [&](const MTPDupload_cdnFile &data) {
|
||||||
|
auto key = bytes::make_span(_cdnEncryptionKey);
|
||||||
|
auto iv = bytes::make_span(_cdnEncryptionIV);
|
||||||
|
Expects(key.size() == MTP::CTRState::KeySize);
|
||||||
|
Expects(iv.size() == MTP::CTRState::IvecSize);
|
||||||
|
|
||||||
|
auto state = MTP::CTRState();
|
||||||
|
auto ivec = bytes::make_span(state.ivec);
|
||||||
|
std::copy(iv.begin(), iv.end(), ivec.begin());
|
||||||
|
|
||||||
|
auto counterOffset = static_cast<uint32>(requestData.offset) >> 4;
|
||||||
|
state.ivec[15] = static_cast<uchar>(counterOffset & 0xFF);
|
||||||
|
state.ivec[14] = static_cast<uchar>((counterOffset >> 8) & 0xFF);
|
||||||
|
state.ivec[13] = static_cast<uchar>((counterOffset >> 16) & 0xFF);
|
||||||
|
state.ivec[12] = static_cast<uchar>((counterOffset >> 24) & 0xFF);
|
||||||
|
|
||||||
|
auto decryptInPlace = data.vbytes().v;
|
||||||
|
auto buffer = bytes::make_detached_span(decryptInPlace);
|
||||||
|
MTP::aesCtrEncrypt(buffer, key.data(), &state);
|
||||||
|
|
||||||
|
switch (checkCdnFileHash(requestData.offset, buffer)) {
|
||||||
|
case CheckCdnHashResult::NoHash: {
|
||||||
|
_cdnUncheckedParts.emplace(requestData, decryptInPlace);
|
||||||
|
requestMoreCdnFileHashes();
|
||||||
|
} return;
|
||||||
|
|
||||||
|
case CheckCdnHashResult::Invalid: {
|
||||||
|
LOG(("API Error: Wrong cdnFileHash for offset %1."
|
||||||
|
).arg(requestData.offset));
|
||||||
|
cancelOnFail();
|
||||||
|
} return;
|
||||||
|
|
||||||
|
case CheckCdnHashResult::Good: {
|
||||||
|
partLoaded(requestData.offset, decryptInPlace);
|
||||||
|
} return;
|
||||||
|
}
|
||||||
|
Unexpected("Result of checkCdnFileHash()");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadMtprotoTask::CheckCdnHashResult DownloadMtprotoTask::checkCdnFileHash(
|
||||||
|
int offset,
|
||||||
|
bytes::const_span buffer) {
|
||||||
|
const auto cdnFileHashIt = _cdnFileHashes.find(offset);
|
||||||
|
if (cdnFileHashIt == _cdnFileHashes.cend()) {
|
||||||
|
return CheckCdnHashResult::NoHash;
|
||||||
|
}
|
||||||
|
const auto realHash = openssl::Sha256(buffer);
|
||||||
|
const auto receivedHash = bytes::make_span(cdnFileHashIt->second.hash);
|
||||||
|
if (bytes::compare(realHash, receivedHash)) {
|
||||||
|
return CheckCdnHashResult::Invalid;
|
||||||
|
}
|
||||||
|
return CheckCdnHashResult::Good;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::reuploadDone(
|
||||||
|
const MTPVector<MTPFileHash> &result,
|
||||||
|
mtpRequestId requestId) {
|
||||||
|
const auto requestData = finishSentRequest(requestId);
|
||||||
|
addCdnHashes(result.v);
|
||||||
|
makeRequest(requestData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::getCdnFileHashesDone(
|
||||||
|
const MTPVector<MTPFileHash> &result,
|
||||||
|
mtpRequestId requestId) {
|
||||||
|
Expects(_cdnHashesRequestId == requestId);
|
||||||
|
|
||||||
|
_cdnHashesRequestId = 0;
|
||||||
|
|
||||||
|
const auto requestData = finishSentRequest(requestId);
|
||||||
|
addCdnHashes(result.v);
|
||||||
|
auto someMoreChecked = false;
|
||||||
|
for (auto i = _cdnUncheckedParts.begin(); i != _cdnUncheckedParts.cend();) {
|
||||||
|
const auto uncheckedData = i->first;
|
||||||
|
const auto uncheckedBytes = bytes::make_span(i->second);
|
||||||
|
|
||||||
|
switch (checkCdnFileHash(uncheckedData.offset, uncheckedBytes)) {
|
||||||
|
case CheckCdnHashResult::NoHash: {
|
||||||
|
++i;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case CheckCdnHashResult::Invalid: {
|
||||||
|
LOG(("API Error: Wrong cdnFileHash for offset %1."
|
||||||
|
).arg(uncheckedData.offset));
|
||||||
|
cancelOnFail();
|
||||||
|
return;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case CheckCdnHashResult::Good: {
|
||||||
|
someMoreChecked = true;
|
||||||
|
const auto goodOffset = uncheckedData.offset;
|
||||||
|
const auto goodBytes = std::move(i->second);
|
||||||
|
const auto weak = base::make_weak(this);
|
||||||
|
i = _cdnUncheckedParts.erase(i);
|
||||||
|
if (!feedPart(goodOffset, goodBytes) || !weak) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default: Unexpected("Result of checkCdnFileHash()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!someMoreChecked) {
|
||||||
|
LOG(("API Error: "
|
||||||
|
"Could not find cdnFileHash for offset %1 "
|
||||||
|
"after getCdnFileHashes request."
|
||||||
|
).arg(requestData.offset));
|
||||||
|
cancelOnFail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
requestMoreCdnFileHashes();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::placeSentRequest(
|
||||||
|
mtpRequestId requestId,
|
||||||
|
const RequestData &requestData) {
|
||||||
|
_owner->requestedAmountIncrement(
|
||||||
|
dcId(),
|
||||||
|
requestData.dcIndex,
|
||||||
|
Storage::kDownloadPartSize);
|
||||||
|
const auto [i, ok1] = _sentRequests.emplace(requestId, requestData);
|
||||||
|
const auto [j, ok2] = _requestByOffset.emplace(
|
||||||
|
requestData.offset,
|
||||||
|
requestId);
|
||||||
|
|
||||||
|
Ensures(ok1 && ok2);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto DownloadMtprotoTask::finishSentRequest(mtpRequestId requestId)
|
||||||
|
-> RequestData {
|
||||||
|
auto it = _sentRequests.find(requestId);
|
||||||
|
Assert(it != _sentRequests.cend());
|
||||||
|
|
||||||
|
const auto result = it->second;
|
||||||
|
_owner->requestedAmountIncrement(
|
||||||
|
dcId(),
|
||||||
|
result.dcIndex,
|
||||||
|
-Storage::kDownloadPartSize);
|
||||||
|
_sentRequests.erase(it);
|
||||||
|
_requestByOffset.erase(result.offset);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DownloadMtprotoTask::haveSentRequests() const {
|
||||||
|
return !_sentRequests.empty() || !_cdnUncheckedParts.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DownloadMtprotoTask::haveSentRequestForOffset(int offset) const {
|
||||||
|
return _requestByOffset.contains(offset)
|
||||||
|
|| _cdnUncheckedParts.contains({ offset, 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::cancelAllRequests() {
|
||||||
|
while (!_sentRequests.empty()) {
|
||||||
|
cancelRequest(_sentRequests.begin()->first);
|
||||||
|
}
|
||||||
|
_cdnUncheckedParts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::cancelRequestForOffset(int offset) {
|
||||||
|
const auto i = _requestByOffset.find(offset);
|
||||||
|
if (i != end(_requestByOffset)) {
|
||||||
|
cancelRequest(i->second);
|
||||||
|
}
|
||||||
|
_cdnUncheckedParts.remove({ offset, 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::cancelRequest(mtpRequestId requestId) {
|
||||||
|
api().request(requestId).cancel();
|
||||||
|
[[maybe_unused]] const auto data = finishSentRequest(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::addToQueue() {
|
||||||
|
_owner->enqueue(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::removeFromQueue() {
|
||||||
|
_owner->remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::partLoaded(
|
||||||
|
int offset,
|
||||||
|
const QByteArray &bytes) {
|
||||||
|
feedPart(offset, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DownloadMtprotoTask::normalPartFailed(
|
||||||
|
QByteArray fileReference,
|
||||||
|
const RPCError &error,
|
||||||
|
mtpRequestId requestId) {
|
||||||
|
if (MTP::isDefaultHandledError(error)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (error.code() == 400
|
||||||
|
&& error.type().startsWith(qstr("FILE_REFERENCE_"))) {
|
||||||
|
api().refreshFileReference(
|
||||||
|
_origin,
|
||||||
|
this,
|
||||||
|
requestId,
|
||||||
|
fileReference);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return partFailed(error, requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DownloadMtprotoTask::partFailed(
|
||||||
|
const RPCError &error,
|
||||||
|
mtpRequestId requestId) {
|
||||||
|
if (MTP::isDefaultHandledError(error)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cancelOnFail();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DownloadMtprotoTask::cdnPartFailed(
|
||||||
|
const RPCError &error,
|
||||||
|
mtpRequestId requestId) {
|
||||||
|
if (MTP::isDefaultHandledError(error)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestId == _cdnHashesRequestId) {
|
||||||
|
_cdnHashesRequestId = 0;
|
||||||
|
}
|
||||||
|
if (error.type() == qstr("FILE_TOKEN_INVALID")
|
||||||
|
|| error.type() == qstr("REQUEST_TOKEN_INVALID")) {
|
||||||
|
const auto requestData = finishSentRequest(requestId);
|
||||||
|
changeCDNParams(
|
||||||
|
requestData,
|
||||||
|
0,
|
||||||
|
QByteArray(),
|
||||||
|
QByteArray(),
|
||||||
|
QByteArray(),
|
||||||
|
QVector<MTPFileHash>());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return partFailed(error, requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::switchToCDN(
|
||||||
|
const RequestData &requestData,
|
||||||
|
const MTPDupload_fileCdnRedirect &redirect) {
|
||||||
|
changeCDNParams(
|
||||||
|
requestData,
|
||||||
|
redirect.vdc_id().v,
|
||||||
|
redirect.vfile_token().v,
|
||||||
|
redirect.vencryption_key().v,
|
||||||
|
redirect.vencryption_iv().v,
|
||||||
|
redirect.vfile_hashes().v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::addCdnHashes(
|
||||||
|
const QVector<MTPFileHash> &hashes) {
|
||||||
|
for (const auto &hash : hashes) {
|
||||||
|
hash.match([&](const MTPDfileHash &data) {
|
||||||
|
_cdnFileHashes.emplace(
|
||||||
|
data.voffset().v,
|
||||||
|
CdnFileHash{ data.vlimit().v, data.vhash().v });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadMtprotoTask::changeCDNParams(
|
||||||
|
const RequestData &requestData,
|
||||||
|
MTP::DcId dcId,
|
||||||
|
const QByteArray &token,
|
||||||
|
const QByteArray &encryptionKey,
|
||||||
|
const QByteArray &encryptionIV,
|
||||||
|
const QVector<MTPFileHash> &hashes) {
|
||||||
|
if (dcId != 0
|
||||||
|
&& (encryptionKey.size() != MTP::CTRState::KeySize
|
||||||
|
|| encryptionIV.size() != MTP::CTRState::IvecSize)) {
|
||||||
|
LOG(("Message Error: Wrong key (%1) / iv (%2) size in CDN params"
|
||||||
|
).arg(encryptionKey.size()
|
||||||
|
).arg(encryptionIV.size()));
|
||||||
|
cancelOnFail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto resendAllRequests = (_cdnDcId != dcId
|
||||||
|
|| _cdnToken != token
|
||||||
|
|| _cdnEncryptionKey != encryptionKey
|
||||||
|
|| _cdnEncryptionIV != encryptionIV);
|
||||||
|
_cdnDcId = dcId;
|
||||||
|
_cdnToken = token;
|
||||||
|
_cdnEncryptionKey = encryptionKey;
|
||||||
|
_cdnEncryptionIV = encryptionIV;
|
||||||
|
addCdnHashes(hashes);
|
||||||
|
|
||||||
|
if (resendAllRequests && !_sentRequests.empty()) {
|
||||||
|
auto resendRequests = std::vector<RequestData>();
|
||||||
|
resendRequests.reserve(_sentRequests.size());
|
||||||
|
while (!_sentRequests.empty()) {
|
||||||
|
const auto requestId = _sentRequests.begin()->first;
|
||||||
|
api().request(requestId).cancel();
|
||||||
|
resendRequests.push_back(finishSentRequest(requestId));
|
||||||
|
}
|
||||||
|
for (const auto &requestData : resendRequests) {
|
||||||
|
makeRequest(requestData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
makeRequest(requestData);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Storage
|
227
Telegram/SourceFiles/storage/download_manager_mtproto.h
Normal file
227
Telegram/SourceFiles/storage/download_manager_mtproto.h
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
|
#include "base/timer.h"
|
||||||
|
#include "base/weak_ptr.h"
|
||||||
|
|
||||||
|
class ApiWrap;
|
||||||
|
class RPCError;
|
||||||
|
|
||||||
|
namespace Storage {
|
||||||
|
|
||||||
|
// Different part sizes are not supported for now :(
|
||||||
|
// Because we start downloading with some part size
|
||||||
|
// and then we get a CDN-redirect where we support only
|
||||||
|
// fixed part size download for hash checking.
|
||||||
|
constexpr auto kDownloadPartSize = 128 * 1024;
|
||||||
|
|
||||||
|
class DownloadMtprotoTask;
|
||||||
|
|
||||||
|
class DownloadManagerMtproto final : public base::has_weak_ptr {
|
||||||
|
public:
|
||||||
|
using Task = DownloadMtprotoTask;
|
||||||
|
|
||||||
|
explicit DownloadManagerMtproto(not_null<ApiWrap*> api);
|
||||||
|
~DownloadManagerMtproto();
|
||||||
|
|
||||||
|
[[nodiscard]] ApiWrap &api() const {
|
||||||
|
return *_api;
|
||||||
|
}
|
||||||
|
|
||||||
|
void enqueue(not_null<Task*> task);
|
||||||
|
void remove(not_null<Task*> task);
|
||||||
|
|
||||||
|
[[nodiscard]] base::Observable<void> &taskFinished() {
|
||||||
|
return _taskFinishedObservable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void requestedAmountIncrement(MTP::DcId dcId, int index, int amount);
|
||||||
|
[[nodiscard]] int chooseDcIndexForRequest(MTP::DcId dcId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Queue final {
|
||||||
|
public:
|
||||||
|
void enqueue(not_null<Task*> task);
|
||||||
|
void remove(not_null<Task*> task);
|
||||||
|
void resetGeneration();
|
||||||
|
[[nodiscard]] bool empty() const;
|
||||||
|
[[nodiscard]] Task *nextTask() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<not_null<Task*>> _tasks;
|
||||||
|
std::vector<not_null<Task*>> _previousGeneration;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
void checkSendNext();
|
||||||
|
void checkSendNext(MTP::DcId dcId, Queue &queue);
|
||||||
|
|
||||||
|
void killDownloadSessionsStart(MTP::DcId dcId);
|
||||||
|
void killDownloadSessionsStop(MTP::DcId dcId);
|
||||||
|
void killDownloadSessions();
|
||||||
|
|
||||||
|
void resetGeneration();
|
||||||
|
|
||||||
|
const not_null<ApiWrap*> _api;
|
||||||
|
|
||||||
|
base::Observable<void> _taskFinishedObservable;
|
||||||
|
|
||||||
|
base::flat_map<MTP::DcId, std::vector<int>> _requestedBytesAmount;
|
||||||
|
base::Timer _resetGenerationTimer;
|
||||||
|
|
||||||
|
base::flat_map<MTP::DcId, crl::time> _killDownloadSessionTimes;
|
||||||
|
base::Timer _killDownloadSessionsTimer;
|
||||||
|
|
||||||
|
base::flat_map<MTP::DcId, Queue> _queues;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class DownloadMtprotoTask : public base::has_weak_ptr {
|
||||||
|
public:
|
||||||
|
struct Location {
|
||||||
|
base::variant<
|
||||||
|
StorageFileLocation,
|
||||||
|
WebFileLocation,
|
||||||
|
GeoPointLocation> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
DownloadMtprotoTask(
|
||||||
|
not_null<DownloadManagerMtproto*> owner,
|
||||||
|
const StorageFileLocation &location,
|
||||||
|
Data::FileOrigin origin);
|
||||||
|
DownloadMtprotoTask(
|
||||||
|
not_null<DownloadManagerMtproto*> owner,
|
||||||
|
MTP::DcId dcId,
|
||||||
|
const Location &location);
|
||||||
|
virtual ~DownloadMtprotoTask();
|
||||||
|
|
||||||
|
[[nodiscard]] MTP::DcId dcId() const;
|
||||||
|
[[nodiscard]] Data::FileOrigin fileOrigin() const;
|
||||||
|
[[nodiscard]] uint64 objectId() const;
|
||||||
|
[[nodiscard]] const Location &location() const;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual bool readyToRequest() const = 0;
|
||||||
|
void loadPart(int dcIndex);
|
||||||
|
|
||||||
|
void refreshFileReferenceFrom(
|
||||||
|
const Data::UpdatedFileReferences &updates,
|
||||||
|
int requestId,
|
||||||
|
const QByteArray ¤t);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
[[nodiscard]] bool haveSentRequests() const;
|
||||||
|
[[nodiscard]] bool haveSentRequestForOffset(int offset) const;
|
||||||
|
void cancelAllRequests();
|
||||||
|
void cancelRequestForOffset(int offset);
|
||||||
|
|
||||||
|
void addToQueue();
|
||||||
|
void removeFromQueue();
|
||||||
|
|
||||||
|
[[nodiscard]] ApiWrap &api() const {
|
||||||
|
return _owner->api();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct RequestData {
|
||||||
|
int offset = 0;
|
||||||
|
int dcIndex = 0;
|
||||||
|
|
||||||
|
inline bool operator<(const RequestData &other) const {
|
||||||
|
return offset < other.offset;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
struct CdnFileHash {
|
||||||
|
CdnFileHash(int limit, QByteArray hash) : limit(limit), hash(hash) {
|
||||||
|
}
|
||||||
|
int limit = 0;
|
||||||
|
QByteArray hash;
|
||||||
|
};
|
||||||
|
enum class CheckCdnHashResult {
|
||||||
|
NoHash,
|
||||||
|
Invalid,
|
||||||
|
Good,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Called only if readyToRequest() == true.
|
||||||
|
[[nodiscard]] virtual int takeNextRequestOffset() = 0;
|
||||||
|
virtual bool feedPart(int offset, const QByteArray &bytes) = 0;
|
||||||
|
virtual bool setWebFileSizeHook(int size);
|
||||||
|
virtual void cancelOnFail() = 0;
|
||||||
|
|
||||||
|
void cancelRequest(mtpRequestId requestId);
|
||||||
|
void makeRequest(const RequestData &requestData);
|
||||||
|
void normalPartLoaded(
|
||||||
|
const MTPupload_File &result,
|
||||||
|
mtpRequestId requestId);
|
||||||
|
void webPartLoaded(
|
||||||
|
const MTPupload_WebFile &result,
|
||||||
|
mtpRequestId requestId);
|
||||||
|
void cdnPartLoaded(
|
||||||
|
const MTPupload_CdnFile &result,
|
||||||
|
mtpRequestId requestId);
|
||||||
|
void reuploadDone(
|
||||||
|
const MTPVector<MTPFileHash> &result,
|
||||||
|
mtpRequestId requestId);
|
||||||
|
void requestMoreCdnFileHashes();
|
||||||
|
void getCdnFileHashesDone(
|
||||||
|
const MTPVector<MTPFileHash> &result,
|
||||||
|
mtpRequestId requestId);
|
||||||
|
|
||||||
|
void partLoaded(int offset, const QByteArray &bytes);
|
||||||
|
|
||||||
|
bool partFailed(const RPCError &error, mtpRequestId requestId);
|
||||||
|
bool normalPartFailed(
|
||||||
|
QByteArray fileReference,
|
||||||
|
const RPCError &error,
|
||||||
|
mtpRequestId requestId);
|
||||||
|
bool cdnPartFailed(const RPCError &error, mtpRequestId requestId);
|
||||||
|
|
||||||
|
[[nodiscard]] mtpRequestId sendRequest(const RequestData &requestData);
|
||||||
|
void placeSentRequest(
|
||||||
|
mtpRequestId requestId,
|
||||||
|
const RequestData &requestData);
|
||||||
|
[[nodiscard]] RequestData finishSentRequest(mtpRequestId requestId);
|
||||||
|
void switchToCDN(
|
||||||
|
const RequestData &requestData,
|
||||||
|
const MTPDupload_fileCdnRedirect &redirect);
|
||||||
|
void addCdnHashes(const QVector<MTPFileHash> &hashes);
|
||||||
|
void changeCDNParams(
|
||||||
|
const RequestData &requestData,
|
||||||
|
MTP::DcId dcId,
|
||||||
|
const QByteArray &token,
|
||||||
|
const QByteArray &encryptionKey,
|
||||||
|
const QByteArray &encryptionIV,
|
||||||
|
const QVector<MTPFileHash> &hashes);
|
||||||
|
|
||||||
|
[[nodiscard]] CheckCdnHashResult checkCdnFileHash(
|
||||||
|
int offset,
|
||||||
|
bytes::const_span buffer);
|
||||||
|
|
||||||
|
const not_null<DownloadManagerMtproto*> _owner;
|
||||||
|
const MTP::DcId _dcId = 0;
|
||||||
|
|
||||||
|
// _location can be changed with an updated file_reference.
|
||||||
|
Location _location;
|
||||||
|
const Data::FileOrigin _origin;
|
||||||
|
|
||||||
|
base::flat_map<mtpRequestId, RequestData> _sentRequests;
|
||||||
|
base::flat_map<int, mtpRequestId> _requestByOffset;
|
||||||
|
|
||||||
|
MTP::DcId _cdnDcId = 0;
|
||||||
|
QByteArray _cdnToken;
|
||||||
|
QByteArray _cdnEncryptionKey;
|
||||||
|
QByteArray _cdnEncryptionIV;
|
||||||
|
base::flat_map<int, CdnFileHash> _cdnFileHashes;
|
||||||
|
base::flat_map<RequestData, QByteArray> _cdnUncheckedParts;
|
||||||
|
mtpRequestId _cdnHashesRequestId = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Storage
|
@ -23,210 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "facades.h"
|
#include "facades.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
|
|
||||||
namespace Storage {
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// How much time without download causes additional session kill.
|
|
||||||
constexpr auto kKillSessionTimeout = 15 * crl::time(1000);
|
|
||||||
|
|
||||||
// Max 16 file parts downloaded at the same time, 128 KB each.
|
|
||||||
constexpr auto kMaxFileQueries = 16;
|
|
||||||
|
|
||||||
constexpr auto kMaxWaitedInConnection = 512 * 1024;
|
|
||||||
|
|
||||||
// Max 8 http[s] files downloaded at the same time.
|
|
||||||
constexpr auto kMaxWebFileQueries = 8;
|
|
||||||
|
|
||||||
constexpr auto kStartSessionsCount = 1;
|
|
||||||
constexpr auto kMaxSessionsCount = 8;
|
|
||||||
constexpr auto kResetDownloadPrioritiesTimeout = crl::time(200);
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void DownloadManager::Queue::enqueue(not_null<Downloader*> loader) {
|
|
||||||
const auto i = ranges::find(_loaders, loader);
|
|
||||||
if (i != end(_loaders)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_loaders.push_back(loader);
|
|
||||||
_previousGeneration.erase(
|
|
||||||
ranges::remove(_previousGeneration, loader),
|
|
||||||
end(_previousGeneration));
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::Queue::remove(not_null<Downloader*> loader) {
|
|
||||||
_loaders.erase(ranges::remove(_loaders, loader), end(_loaders));
|
|
||||||
_previousGeneration.erase(
|
|
||||||
ranges::remove(_previousGeneration, loader),
|
|
||||||
end(_previousGeneration));
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::Queue::resetGeneration() {
|
|
||||||
if (!_previousGeneration.empty()) {
|
|
||||||
_loaders.reserve(_loaders.size() + _previousGeneration.size());
|
|
||||||
std::copy(
|
|
||||||
begin(_previousGeneration),
|
|
||||||
end(_previousGeneration),
|
|
||||||
std::back_inserter(_loaders));
|
|
||||||
_previousGeneration.clear();
|
|
||||||
}
|
|
||||||
std::swap(_loaders, _previousGeneration);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DownloadManager::Queue::empty() const {
|
|
||||||
return _loaders.empty() && _previousGeneration.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
Downloader *DownloadManager::Queue::nextLoader() const {
|
|
||||||
auto &&all = ranges::view::concat(_loaders, _previousGeneration);
|
|
||||||
const auto i = ranges::find(all, true, &Downloader::readyToRequest);
|
|
||||||
return (i != all.end()) ? i->get() : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
DownloadManager::DownloadManager(not_null<ApiWrap*> api)
|
|
||||||
: _api(api)
|
|
||||||
, _resetGenerationTimer([=] { resetGeneration(); })
|
|
||||||
, _killDownloadSessionsTimer([=] { killDownloadSessions(); }) {
|
|
||||||
}
|
|
||||||
|
|
||||||
DownloadManager::~DownloadManager() {
|
|
||||||
killDownloadSessions();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::enqueue(not_null<Downloader*> loader) {
|
|
||||||
const auto dcId = loader->dcId();
|
|
||||||
(dcId ? _mtprotoLoaders[dcId] : _webLoaders).enqueue(loader);
|
|
||||||
if (!_resetGenerationTimer.isActive()) {
|
|
||||||
_resetGenerationTimer.callOnce(kResetDownloadPrioritiesTimeout);
|
|
||||||
}
|
|
||||||
checkSendNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::remove(not_null<Downloader*> loader) {
|
|
||||||
const auto dcId = loader->dcId();
|
|
||||||
(dcId ? _mtprotoLoaders[dcId] : _webLoaders).remove(loader);
|
|
||||||
crl::on_main(&_api->session(), [=] { checkSendNext(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::resetGeneration() {
|
|
||||||
_resetGenerationTimer.cancel();
|
|
||||||
for (auto &[dcId, queue] : _mtprotoLoaders) {
|
|
||||||
queue.resetGeneration();
|
|
||||||
}
|
|
||||||
_webLoaders.resetGeneration();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::checkSendNext() {
|
|
||||||
for (auto &[dcId, queue] : _mtprotoLoaders) {
|
|
||||||
if (queue.empty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto bestIndex = [&] {
|
|
||||||
const auto i = _requestedBytesAmount.find(dcId);
|
|
||||||
if (i == end(_requestedBytesAmount)) {
|
|
||||||
_requestedBytesAmount[dcId].resize(kStartSessionsCount);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const auto j = ranges::min_element(i->second);
|
|
||||||
const auto already = *j;
|
|
||||||
return (already + kDownloadPartSize <= kMaxWaitedInConnection)
|
|
||||||
? (j - begin(i->second))
|
|
||||||
: -1;
|
|
||||||
}();
|
|
||||||
if (bestIndex < 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (const auto loader = queue.nextLoader()) {
|
|
||||||
loader->loadPart(bestIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (_requestedBytesAmount[0].empty()) {
|
|
||||||
_requestedBytesAmount[0] = std::vector<int>(1, 0);
|
|
||||||
}
|
|
||||||
if (_requestedBytesAmount[0][0] < kMaxWebFileQueries) {
|
|
||||||
if (const auto loader = _webLoaders.nextLoader()) {
|
|
||||||
loader->loadPart(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::requestedAmountIncrement(
|
|
||||||
MTP::DcId dcId,
|
|
||||||
int index,
|
|
||||||
int amount) {
|
|
||||||
using namespace rpl::mappers;
|
|
||||||
|
|
||||||
auto it = _requestedBytesAmount.find(dcId);
|
|
||||||
if (it == _requestedBytesAmount.end()) {
|
|
||||||
it = _requestedBytesAmount.emplace(
|
|
||||||
dcId,
|
|
||||||
std::vector<int>(dcId ? kStartSessionsCount : 1, 0)
|
|
||||||
).first;
|
|
||||||
}
|
|
||||||
it->second[index] += amount;
|
|
||||||
if (!dcId) {
|
|
||||||
return; // webLoaders.
|
|
||||||
}
|
|
||||||
if (amount > 0) {
|
|
||||||
killDownloadSessionsStop(dcId);
|
|
||||||
} else if (ranges::find_if(it->second, _1 > 0) == end(it->second)) {
|
|
||||||
killDownloadSessionsStart(dcId);
|
|
||||||
checkSendNext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int DownloadManager::chooseDcIndexForRequest(MTP::DcId dcId) {
|
|
||||||
const auto i = _requestedBytesAmount.find(dcId);
|
|
||||||
return (i != end(_requestedBytesAmount))
|
|
||||||
? (ranges::min_element(i->second) - begin(i->second))
|
|
||||||
: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::killDownloadSessionsStart(MTP::DcId dcId) {
|
|
||||||
if (!_killDownloadSessionTimes.contains(dcId)) {
|
|
||||||
_killDownloadSessionTimes.emplace(
|
|
||||||
dcId,
|
|
||||||
crl::now() + kKillSessionTimeout);
|
|
||||||
}
|
|
||||||
if (!_killDownloadSessionsTimer.isActive()) {
|
|
||||||
_killDownloadSessionsTimer.callOnce(kKillSessionTimeout + 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::killDownloadSessionsStop(MTP::DcId dcId) {
|
|
||||||
_killDownloadSessionTimes.erase(dcId);
|
|
||||||
if (_killDownloadSessionTimes.empty()
|
|
||||||
&& _killDownloadSessionsTimer.isActive()) {
|
|
||||||
_killDownloadSessionsTimer.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DownloadManager::killDownloadSessions() {
|
|
||||||
const auto now = crl::now();
|
|
||||||
auto left = kKillSessionTimeout;
|
|
||||||
for (auto i = _killDownloadSessionTimes.begin(); i != _killDownloadSessionTimes.end(); ) {
|
|
||||||
if (i->second <= now) {
|
|
||||||
const auto j = _requestedBytesAmount.find(i->first);
|
|
||||||
if (j != end(_requestedBytesAmount)) {
|
|
||||||
for (auto index = 0; index != int(j->second.size()); ++index) {
|
|
||||||
MTP::stopSession(MTP::downloadDcId(i->first, index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i = _killDownloadSessionTimes.erase(i);
|
|
||||||
} else {
|
|
||||||
if (i->second - now < left) {
|
|
||||||
left = i->second - now;
|
|
||||||
}
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!_killDownloadSessionTimes.empty()) {
|
|
||||||
_killDownloadSessionsTimer.callOnce(left);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Storage
|
|
||||||
|
|
||||||
FileLoader::FileLoader(
|
FileLoader::FileLoader(
|
||||||
const QString &toFile,
|
const QString &toFile,
|
||||||
int32 size,
|
int32 size,
|
||||||
@ -445,7 +241,9 @@ void FileLoader::cancel() {
|
|||||||
|
|
||||||
void FileLoader::cancel(bool fail) {
|
void FileLoader::cancel(bool fail) {
|
||||||
const auto started = (currentOffset() > 0);
|
const auto started = (currentOffset() > 0);
|
||||||
cancelRequests();
|
|
||||||
|
cancelHook();
|
||||||
|
|
||||||
_cancelled = true;
|
_cancelled = true;
|
||||||
_finished = true;
|
_finished = true;
|
||||||
if (_fileIsOpen) {
|
if (_fileIsOpen) {
|
||||||
|
@ -10,12 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "base/observer.h"
|
#include "base/observer.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
#include "base/binary_guard.h"
|
#include "base/binary_guard.h"
|
||||||
#include "data/data_file_origin.h"
|
|
||||||
#include "mtproto/facade.h"
|
|
||||||
|
|
||||||
#include <QtNetwork/QNetworkReply>
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
|
||||||
class ApiWrap;
|
namespace Data {
|
||||||
|
struct FileOrigin;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
@ -26,88 +26,22 @@ namespace Cache {
|
|||||||
struct Key;
|
struct Key;
|
||||||
} // namespace Cache
|
} // namespace Cache
|
||||||
|
|
||||||
|
// 10 MB max file could be hold in memory
|
||||||
// This value is used in local cache database settings!
|
// This value is used in local cache database settings!
|
||||||
constexpr auto kMaxFileInMemory = 10 * 1024 * 1024; // 10 MB max file could be hold in memory
|
constexpr auto kMaxFileInMemory = 10 * 1024 * 1024;
|
||||||
|
|
||||||
constexpr auto kMaxVoiceInMemory = 2 * 1024 * 1024; // 2 MB audio is hold in memory and auto loaded
|
// 2 MB audio is hold in memory and auto loaded
|
||||||
constexpr auto kMaxStickerInMemory = 2 * 1024 * 1024; // 2 MB stickers hold in memory, auto loaded and displayed inline
|
constexpr auto kMaxVoiceInMemory = 2 * 1024 * 1024;
|
||||||
|
|
||||||
|
// 2 MB stickers hold in memory, auto loaded and displayed inline
|
||||||
|
constexpr auto kMaxStickerInMemory = 2 * 1024 * 1024;
|
||||||
|
|
||||||
|
// 10 MB GIF and mp4 animations held in memory while playing
|
||||||
constexpr auto kMaxWallPaperInMemory = kMaxFileInMemory;
|
constexpr auto kMaxWallPaperInMemory = kMaxFileInMemory;
|
||||||
constexpr auto kMaxAnimationInMemory = kMaxFileInMemory; // 10 MB gif and mp4 animations held in memory while playing
|
constexpr auto kMaxAnimationInMemory = kMaxFileInMemory;
|
||||||
constexpr auto kMaxWallPaperDimension = 4096; // 4096x4096 is max area.
|
|
||||||
|
|
||||||
// Different part sizes are not supported for now :(
|
// 4096x4096 is max area.
|
||||||
// Because we start downloading with some part size
|
constexpr auto kMaxWallPaperDimension = 4096;
|
||||||
// and then we get a cdn-redirect where we support only
|
|
||||||
// fixed part size download for hash checking.
|
|
||||||
constexpr auto kDownloadPartSize = 128 * 1024;
|
|
||||||
|
|
||||||
class Downloader {
|
|
||||||
public:
|
|
||||||
virtual ~Downloader() = default;
|
|
||||||
|
|
||||||
[[nodiscard]] virtual MTP::DcId dcId() const = 0;
|
|
||||||
[[nodiscard]] virtual bool readyToRequest() const = 0;
|
|
||||||
virtual void loadPart(int dcIndex) = 0;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class DownloadManager final : public base::has_weak_ptr {
|
|
||||||
public:
|
|
||||||
explicit DownloadManager(not_null<ApiWrap*> api);
|
|
||||||
~DownloadManager();
|
|
||||||
|
|
||||||
[[nodiscard]] ApiWrap &api() const {
|
|
||||||
return *_api;
|
|
||||||
}
|
|
||||||
|
|
||||||
void enqueue(not_null<Downloader*> loader);
|
|
||||||
void remove(not_null<Downloader*> loader);
|
|
||||||
|
|
||||||
[[nodiscard]] base::Observable<void> &taskFinished() {
|
|
||||||
return _taskFinishedObservable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dcId == 0 is for web requests.
|
|
||||||
void requestedAmountIncrement(MTP::DcId dcId, int index, int amount);
|
|
||||||
[[nodiscard]] int chooseDcIndexForRequest(MTP::DcId dcId);
|
|
||||||
|
|
||||||
private:
|
|
||||||
class Queue final {
|
|
||||||
public:
|
|
||||||
void enqueue(not_null<Downloader*> loader);
|
|
||||||
void remove(not_null<Downloader*> loader);
|
|
||||||
void resetGeneration();
|
|
||||||
[[nodiscard]] bool empty() const;
|
|
||||||
[[nodiscard]] Downloader *nextLoader() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<not_null<Downloader*>> _loaders;
|
|
||||||
std::vector<not_null<Downloader*>> _previousGeneration;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
void checkSendNext();
|
|
||||||
|
|
||||||
void killDownloadSessionsStart(MTP::DcId dcId);
|
|
||||||
void killDownloadSessionsStop(MTP::DcId dcId);
|
|
||||||
void killDownloadSessions();
|
|
||||||
|
|
||||||
void resetGeneration();
|
|
||||||
|
|
||||||
const not_null<ApiWrap*> _api;
|
|
||||||
|
|
||||||
base::Observable<void> _taskFinishedObservable;
|
|
||||||
|
|
||||||
base::flat_map<MTP::DcId, std::vector<int>> _requestedBytesAmount;
|
|
||||||
base::Timer _resetGenerationTimer;
|
|
||||||
|
|
||||||
base::flat_map<MTP::DcId, crl::time> _killDownloadSessionTimes;
|
|
||||||
base::Timer _killDownloadSessionsTimer;
|
|
||||||
|
|
||||||
base::flat_map<MTP::DcId, Queue> _mtprotoLoaders;
|
|
||||||
Queue _webLoaders;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Storage
|
} // namespace Storage
|
||||||
|
|
||||||
@ -132,8 +66,9 @@ public:
|
|||||||
LoadFromCloudSetting fromCloud,
|
LoadFromCloudSetting fromCloud,
|
||||||
bool autoLoading,
|
bool autoLoading,
|
||||||
uint8 cacheTag);
|
uint8 cacheTag);
|
||||||
|
virtual ~FileLoader();
|
||||||
|
|
||||||
Main::Session &session() const;
|
[[nodiscard]] Main::Session &session() const;
|
||||||
|
|
||||||
bool finished() const {
|
bool finished() const {
|
||||||
return _finished;
|
return _finished;
|
||||||
@ -153,7 +88,8 @@ public:
|
|||||||
QString fileName() const {
|
QString fileName() const {
|
||||||
return _filename;
|
return _filename;
|
||||||
}
|
}
|
||||||
virtual Data::FileOrigin fileOrigin() const;
|
// Used in MainWidget::documentLoadFailed.
|
||||||
|
[[nodiscard]] virtual Data::FileOrigin fileOrigin() const;
|
||||||
float64 currentProgress() const;
|
float64 currentProgress() const;
|
||||||
virtual int currentOffset() const;
|
virtual int currentOffset() const;
|
||||||
int fullSize() const;
|
int fullSize() const;
|
||||||
@ -171,10 +107,6 @@ public:
|
|||||||
return _autoLoading;
|
return _autoLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void stop() {
|
|
||||||
}
|
|
||||||
virtual ~FileLoader();
|
|
||||||
|
|
||||||
void localLoaded(
|
void localLoaded(
|
||||||
const StorageImageSaved &result,
|
const StorageImageSaved &result,
|
||||||
const QByteArray &imageFormat,
|
const QByteArray &imageFormat,
|
||||||
@ -198,7 +130,7 @@ protected:
|
|||||||
void loadLocal(const Storage::Cache::Key &key);
|
void loadLocal(const Storage::Cache::Key &key);
|
||||||
virtual Storage::Cache::Key cacheKey() const = 0;
|
virtual Storage::Cache::Key cacheKey() const = 0;
|
||||||
virtual std::optional<MediaKey> fileLocationKey() const = 0;
|
virtual std::optional<MediaKey> fileLocationKey() const = 0;
|
||||||
virtual void cancelRequests() = 0;
|
virtual void cancelHook() = 0;
|
||||||
virtual void startLoading() = 0;
|
virtual void startLoading() = 0;
|
||||||
|
|
||||||
void cancel(bool failed);
|
void cancel(bool failed);
|
||||||
|
@ -35,10 +35,7 @@ mtpFileLoader::mtpFileLoader(
|
|||||||
fromCloud,
|
fromCloud,
|
||||||
autoLoading,
|
autoLoading,
|
||||||
cacheTag)
|
cacheTag)
|
||||||
, _downloader(&session().downloader())
|
, DownloadMtprotoTask(&session().downloader(), location, origin) {
|
||||||
, _dcId(location.dcId())
|
|
||||||
, _location(location)
|
|
||||||
, _origin(origin) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mtpFileLoader::mtpFileLoader(
|
mtpFileLoader::mtpFileLoader(
|
||||||
@ -55,9 +52,10 @@ mtpFileLoader::mtpFileLoader(
|
|||||||
fromCloud,
|
fromCloud,
|
||||||
autoLoading,
|
autoLoading,
|
||||||
cacheTag)
|
cacheTag)
|
||||||
, _downloader(&session().downloader())
|
, DownloadMtprotoTask(
|
||||||
, _dcId(Global::WebFileDcId())
|
&session().downloader(),
|
||||||
, _location(location) {
|
Global::WebFileDcId(),
|
||||||
|
{ location }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
mtpFileLoader::mtpFileLoader(
|
mtpFileLoader::mtpFileLoader(
|
||||||
@ -74,506 +72,85 @@ mtpFileLoader::mtpFileLoader(
|
|||||||
fromCloud,
|
fromCloud,
|
||||||
autoLoading,
|
autoLoading,
|
||||||
cacheTag)
|
cacheTag)
|
||||||
, _downloader(&session().downloader())
|
, DownloadMtprotoTask(
|
||||||
, _dcId(Global::WebFileDcId())
|
&session().downloader(),
|
||||||
, _location(location) {
|
Global::WebFileDcId(),
|
||||||
}
|
{ location }) {
|
||||||
|
|
||||||
mtpFileLoader::~mtpFileLoader() {
|
|
||||||
cancelRequests();
|
|
||||||
_downloader->remove(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Data::FileOrigin mtpFileLoader::fileOrigin() const {
|
Data::FileOrigin mtpFileLoader::fileOrigin() const {
|
||||||
return _origin;
|
return DownloadMtprotoTask::fileOrigin();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64 mtpFileLoader::objId() const {
|
uint64 mtpFileLoader::objId() const {
|
||||||
if (const auto storage = base::get_if<StorageFileLocation>(&_location)) {
|
return DownloadMtprotoTask::objectId();
|
||||||
return storage->objectId();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mtpFileLoader::refreshFileReferenceFrom(
|
|
||||||
const Data::UpdatedFileReferences &updates,
|
|
||||||
int requestId,
|
|
||||||
const QByteArray ¤t) {
|
|
||||||
if (const auto storage = base::get_if<StorageFileLocation>(&_location)) {
|
|
||||||
storage->refreshFileReference(updates);
|
|
||||||
if (storage->fileReference() == current) {
|
|
||||||
cancel(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cancel(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
makeRequest(finishSentRequest(requestId));
|
|
||||||
}
|
|
||||||
|
|
||||||
MTP::DcId mtpFileLoader::dcId() const {
|
|
||||||
return _dcId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mtpFileLoader::readyToRequest() const {
|
bool mtpFileLoader::readyToRequest() const {
|
||||||
return !_finished
|
return !_finished
|
||||||
&& !_lastComplete
|
&& !_lastComplete
|
||||||
&& (_sentRequests.empty() || _size != 0)
|
&& (_size != 0 || !haveSentRequests())
|
||||||
&& (!_size || _nextRequestOffset < _size);
|
&& (!_size || _nextRequestOffset < _size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mtpFileLoader::loadPart(int dcIndex) {
|
int mtpFileLoader::takeNextRequestOffset() {
|
||||||
Expects(readyToRequest());
|
Expects(readyToRequest());
|
||||||
|
|
||||||
makeRequest({ _nextRequestOffset, dcIndex });
|
const auto result = _nextRequestOffset;
|
||||||
_nextRequestOffset += Storage::kDownloadPartSize;
|
_nextRequestOffset += Storage::kDownloadPartSize;
|
||||||
}
|
|
||||||
|
|
||||||
mtpRequestId mtpFileLoader::sendRequest(const RequestData &requestData) {
|
|
||||||
const auto offset = requestData.offset;
|
|
||||||
const auto limit = Storage::kDownloadPartSize;
|
|
||||||
const auto shiftedDcId = MTP::downloadDcId(
|
|
||||||
_cdnDcId ? _cdnDcId : dcId(),
|
|
||||||
requestData.dcIndex);
|
|
||||||
if (_cdnDcId) {
|
|
||||||
return MTP::send(
|
|
||||||
MTPupload_GetCdnFile(
|
|
||||||
MTP_bytes(_cdnToken),
|
|
||||||
MTP_int(offset),
|
|
||||||
MTP_int(limit)),
|
|
||||||
rpcDone(&mtpFileLoader::cdnPartLoaded),
|
|
||||||
rpcFail(&mtpFileLoader::cdnPartFailed),
|
|
||||||
shiftedDcId,
|
|
||||||
50);
|
|
||||||
}
|
|
||||||
return _location.match([&](const WebFileLocation &location) {
|
|
||||||
return MTP::send(
|
|
||||||
MTPupload_GetWebFile(
|
|
||||||
MTP_inputWebFileLocation(
|
|
||||||
MTP_bytes(location.url()),
|
|
||||||
MTP_long(location.accessHash())),
|
|
||||||
MTP_int(offset),
|
|
||||||
MTP_int(limit)),
|
|
||||||
rpcDone(&mtpFileLoader::webPartLoaded),
|
|
||||||
rpcFail(&mtpFileLoader::partFailed),
|
|
||||||
shiftedDcId,
|
|
||||||
50);
|
|
||||||
}, [&](const GeoPointLocation &location) {
|
|
||||||
return MTP::send(
|
|
||||||
MTPupload_GetWebFile(
|
|
||||||
MTP_inputWebFileGeoPointLocation(
|
|
||||||
MTP_inputGeoPoint(
|
|
||||||
MTP_double(location.lat),
|
|
||||||
MTP_double(location.lon)),
|
|
||||||
MTP_long(location.access),
|
|
||||||
MTP_int(location.width),
|
|
||||||
MTP_int(location.height),
|
|
||||||
MTP_int(location.zoom),
|
|
||||||
MTP_int(location.scale)),
|
|
||||||
MTP_int(offset),
|
|
||||||
MTP_int(limit)),
|
|
||||||
rpcDone(&mtpFileLoader::webPartLoaded),
|
|
||||||
rpcFail(&mtpFileLoader::partFailed),
|
|
||||||
shiftedDcId,
|
|
||||||
50);
|
|
||||||
}, [&](const StorageFileLocation &location) {
|
|
||||||
return MTP::send(
|
|
||||||
MTPupload_GetFile(
|
|
||||||
MTP_flags(0),
|
|
||||||
location.tl(session().userId()),
|
|
||||||
MTP_int(offset),
|
|
||||||
MTP_int(limit)),
|
|
||||||
rpcDone(&mtpFileLoader::normalPartLoaded),
|
|
||||||
rpcFail(
|
|
||||||
&mtpFileLoader::normalPartFailed,
|
|
||||||
location.fileReference()),
|
|
||||||
shiftedDcId,
|
|
||||||
50);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void mtpFileLoader::makeRequest(const RequestData &requestData) {
|
|
||||||
Expects(!_finished);
|
|
||||||
|
|
||||||
placeSentRequest(sendRequest(requestData), requestData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void mtpFileLoader::requestMoreCdnFileHashes() {
|
|
||||||
Expects(!_finished);
|
|
||||||
|
|
||||||
if (_cdnHashesRequestId || _cdnUncheckedParts.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto requestData = _cdnUncheckedParts.cbegin()->first;
|
|
||||||
const auto shiftedDcId = MTP::downloadDcId(
|
|
||||||
dcId(),
|
|
||||||
requestData.dcIndex);
|
|
||||||
const auto requestId = _cdnHashesRequestId = MTP::send(
|
|
||||||
MTPupload_GetCdnFileHashes(
|
|
||||||
MTP_bytes(_cdnToken),
|
|
||||||
MTP_int(requestData.offset)),
|
|
||||||
rpcDone(&mtpFileLoader::getCdnFileHashesDone),
|
|
||||||
rpcFail(&mtpFileLoader::cdnPartFailed),
|
|
||||||
shiftedDcId);
|
|
||||||
placeSentRequest(requestId, requestData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void mtpFileLoader::normalPartLoaded(
|
|
||||||
const MTPupload_File &result,
|
|
||||||
mtpRequestId requestId) {
|
|
||||||
Expects(!_finished);
|
|
||||||
|
|
||||||
const auto requestData = finishSentRequest(requestId);
|
|
||||||
result.match([&](const MTPDupload_fileCdnRedirect &data) {
|
|
||||||
switchToCDN(requestData, data);
|
|
||||||
}, [&](const MTPDupload_file &data) {
|
|
||||||
partLoaded(requestData.offset, bytes::make_span(data.vbytes().v));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void mtpFileLoader::webPartLoaded(
|
|
||||||
const MTPupload_WebFile &result,
|
|
||||||
mtpRequestId requestId) {
|
|
||||||
result.match([&](const MTPDupload_webFile &data) {
|
|
||||||
const auto requestData = finishSentRequest(requestId);
|
|
||||||
if (!_size) {
|
|
||||||
_size = data.vsize().v;
|
|
||||||
} else if (data.vsize().v != _size) {
|
|
||||||
LOG(("MTP Error: "
|
|
||||||
"Bad size provided by bot for webDocument: %1, real: %2"
|
|
||||||
).arg(_size
|
|
||||||
).arg(data.vsize().v));
|
|
||||||
cancel(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
partLoaded(requestData.offset, bytes::make_span(data.vbytes().v));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void mtpFileLoader::cdnPartLoaded(const MTPupload_CdnFile &result, mtpRequestId requestId) {
|
|
||||||
Expects(!_finished);
|
|
||||||
|
|
||||||
const auto requestData = finishSentRequest(requestId);
|
|
||||||
result.match([&](const MTPDupload_cdnFileReuploadNeeded &data) {
|
|
||||||
const auto shiftedDcId = MTP::downloadDcId(
|
|
||||||
dcId(),
|
|
||||||
requestData.dcIndex);
|
|
||||||
const auto requestId = MTP::send(
|
|
||||||
MTPupload_ReuploadCdnFile(
|
|
||||||
MTP_bytes(_cdnToken),
|
|
||||||
data.vrequest_token()),
|
|
||||||
rpcDone(&mtpFileLoader::reuploadDone),
|
|
||||||
rpcFail(&mtpFileLoader::cdnPartFailed),
|
|
||||||
shiftedDcId);
|
|
||||||
placeSentRequest(requestId, requestData);
|
|
||||||
}, [&](const MTPDupload_cdnFile &data) {
|
|
||||||
auto key = bytes::make_span(_cdnEncryptionKey);
|
|
||||||
auto iv = bytes::make_span(_cdnEncryptionIV);
|
|
||||||
Expects(key.size() == MTP::CTRState::KeySize);
|
|
||||||
Expects(iv.size() == MTP::CTRState::IvecSize);
|
|
||||||
|
|
||||||
auto state = MTP::CTRState();
|
|
||||||
auto ivec = bytes::make_span(state.ivec);
|
|
||||||
std::copy(iv.begin(), iv.end(), ivec.begin());
|
|
||||||
|
|
||||||
auto counterOffset = static_cast<uint32>(requestData.offset) >> 4;
|
|
||||||
state.ivec[15] = static_cast<uchar>(counterOffset & 0xFF);
|
|
||||||
state.ivec[14] = static_cast<uchar>((counterOffset >> 8) & 0xFF);
|
|
||||||
state.ivec[13] = static_cast<uchar>((counterOffset >> 16) & 0xFF);
|
|
||||||
state.ivec[12] = static_cast<uchar>((counterOffset >> 24) & 0xFF);
|
|
||||||
|
|
||||||
auto decryptInPlace = data.vbytes().v;
|
|
||||||
auto buffer = bytes::make_detached_span(decryptInPlace);
|
|
||||||
MTP::aesCtrEncrypt(buffer, key.data(), &state);
|
|
||||||
|
|
||||||
switch (checkCdnFileHash(requestData.offset, buffer)) {
|
|
||||||
case CheckCdnHashResult::NoHash: {
|
|
||||||
_cdnUncheckedParts.emplace(requestData, decryptInPlace);
|
|
||||||
requestMoreCdnFileHashes();
|
|
||||||
} return;
|
|
||||||
|
|
||||||
case CheckCdnHashResult::Invalid: {
|
|
||||||
LOG(("API Error: Wrong cdnFileHash for offset %1."
|
|
||||||
).arg(requestData.offset));
|
|
||||||
cancel(true);
|
|
||||||
} return;
|
|
||||||
|
|
||||||
case CheckCdnHashResult::Good: {
|
|
||||||
partLoaded(requestData.offset, buffer);
|
|
||||||
} return;
|
|
||||||
}
|
|
||||||
Unexpected("Result of checkCdnFileHash()");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
mtpFileLoader::CheckCdnHashResult mtpFileLoader::checkCdnFileHash(
|
|
||||||
int offset,
|
|
||||||
bytes::const_span buffer) {
|
|
||||||
const auto cdnFileHashIt = _cdnFileHashes.find(offset);
|
|
||||||
if (cdnFileHashIt == _cdnFileHashes.cend()) {
|
|
||||||
return CheckCdnHashResult::NoHash;
|
|
||||||
}
|
|
||||||
const auto realHash = openssl::Sha256(buffer);
|
|
||||||
const auto receivedHash = bytes::make_span(cdnFileHashIt->second.hash);
|
|
||||||
if (bytes::compare(realHash, receivedHash)) {
|
|
||||||
return CheckCdnHashResult::Invalid;
|
|
||||||
}
|
|
||||||
return CheckCdnHashResult::Good;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mtpFileLoader::reuploadDone(
|
|
||||||
const MTPVector<MTPFileHash> &result,
|
|
||||||
mtpRequestId requestId) {
|
|
||||||
const auto requestData = finishSentRequest(requestId);
|
|
||||||
addCdnHashes(result.v);
|
|
||||||
makeRequest(requestData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void mtpFileLoader::getCdnFileHashesDone(
|
|
||||||
const MTPVector<MTPFileHash> &result,
|
|
||||||
mtpRequestId requestId) {
|
|
||||||
Expects(!_finished);
|
|
||||||
Expects(_cdnHashesRequestId == requestId);
|
|
||||||
|
|
||||||
_cdnHashesRequestId = 0;
|
|
||||||
|
|
||||||
const auto requestData = finishSentRequest(requestId);
|
|
||||||
addCdnHashes(result.v);
|
|
||||||
auto someMoreChecked = false;
|
|
||||||
for (auto i = _cdnUncheckedParts.begin(); i != _cdnUncheckedParts.cend();) {
|
|
||||||
const auto uncheckedData = i->first;
|
|
||||||
const auto uncheckedBytes = bytes::make_span(i->second);
|
|
||||||
|
|
||||||
switch (checkCdnFileHash(uncheckedData.offset, uncheckedBytes)) {
|
|
||||||
case CheckCdnHashResult::NoHash: {
|
|
||||||
++i;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case CheckCdnHashResult::Invalid: {
|
|
||||||
LOG(("API Error: Wrong cdnFileHash for offset %1."
|
|
||||||
).arg(uncheckedData.offset));
|
|
||||||
cancel(true);
|
|
||||||
return;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case CheckCdnHashResult::Good: {
|
|
||||||
someMoreChecked = true;
|
|
||||||
const auto goodOffset = uncheckedData.offset;
|
|
||||||
const auto goodBytes = std::move(i->second);
|
|
||||||
const auto weak = QPointer<mtpFileLoader>(this);
|
|
||||||
i = _cdnUncheckedParts.erase(i);
|
|
||||||
if (!feedPart(goodOffset, bytes::make_span(goodBytes))
|
|
||||||
|| !weak) {
|
|
||||||
return;
|
|
||||||
} else if (_finished) {
|
|
||||||
notifyAboutProgress();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
|
|
||||||
default: Unexpected("Result of checkCdnFileHash()");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (someMoreChecked) {
|
|
||||||
const auto weak = QPointer<mtpFileLoader>(this);
|
|
||||||
notifyAboutProgress();
|
|
||||||
if (weak) {
|
|
||||||
requestMoreCdnFileHashes();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LOG(("API Error: "
|
|
||||||
"Could not find cdnFileHash for offset %1 "
|
|
||||||
"after getCdnFileHashes request."
|
|
||||||
).arg(requestData.offset));
|
|
||||||
cancel(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void mtpFileLoader::placeSentRequest(
|
|
||||||
mtpRequestId requestId,
|
|
||||||
const RequestData &requestData) {
|
|
||||||
Expects(!_finished);
|
|
||||||
|
|
||||||
_downloader->requestedAmountIncrement(
|
|
||||||
dcId(),
|
|
||||||
requestData.dcIndex,
|
|
||||||
Storage::kDownloadPartSize);
|
|
||||||
_sentRequests.emplace(requestId, requestData);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto mtpFileLoader::finishSentRequest(mtpRequestId requestId)
|
|
||||||
-> RequestData {
|
|
||||||
auto it = _sentRequests.find(requestId);
|
|
||||||
Assert(it != _sentRequests.cend());
|
|
||||||
|
|
||||||
const auto result = it->second;
|
|
||||||
_downloader->requestedAmountIncrement(
|
|
||||||
dcId(),
|
|
||||||
result.dcIndex,
|
|
||||||
-Storage::kDownloadPartSize);
|
|
||||||
_sentRequests.erase(it);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mtpFileLoader::feedPart(int offset, bytes::const_span buffer) {
|
bool mtpFileLoader::feedPart(int offset, const QByteArray &bytes) {
|
||||||
|
const auto buffer = bytes::make_span(bytes);
|
||||||
if (!writeResultPart(offset, buffer)) {
|
if (!writeResultPart(offset, buffer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (buffer.empty() || (buffer.size() % 1024)) { // bad next offset
|
if (buffer.empty() || (buffer.size() % 1024)) { // bad next offset
|
||||||
_lastComplete = true;
|
_lastComplete = true;
|
||||||
}
|
}
|
||||||
const auto finished = _sentRequests.empty()
|
const auto weak = QPointer<mtpFileLoader>(this);
|
||||||
&& _cdnUncheckedParts.empty()
|
const auto finished = !haveSentRequests()
|
||||||
&& (_lastComplete || (_size && _nextRequestOffset >= _size));
|
&& (_lastComplete || (_size && _nextRequestOffset >= _size));
|
||||||
if (finished) {
|
if (finished) {
|
||||||
_downloader->remove(this);
|
removeFromQueue();
|
||||||
if (!finalizeResult()) {
|
if (!finalizeResult()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
if (weak) {
|
||||||
}
|
|
||||||
|
|
||||||
void mtpFileLoader::partLoaded(int offset, bytes::const_span buffer) {
|
|
||||||
if (feedPart(offset, buffer)) {
|
|
||||||
notifyAboutProgress();
|
notifyAboutProgress();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool mtpFileLoader::normalPartFailed(
|
|
||||||
QByteArray fileReference,
|
|
||||||
const RPCError &error,
|
|
||||||
mtpRequestId requestId) {
|
|
||||||
if (MTP::isDefaultHandledError(error)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (error.code() == 400
|
|
||||||
&& error.type().startsWith(qstr("FILE_REFERENCE_"))) {
|
|
||||||
session().api().refreshFileReference(
|
|
||||||
_origin,
|
|
||||||
this,
|
|
||||||
requestId,
|
|
||||||
fileReference);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return partFailed(error, requestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool mtpFileLoader::partFailed(
|
|
||||||
const RPCError &error,
|
|
||||||
mtpRequestId requestId) {
|
|
||||||
if (MTP::isDefaultHandledError(error)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
cancel(true);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mtpFileLoader::cdnPartFailed(
|
void mtpFileLoader::cancelOnFail() {
|
||||||
const RPCError &error,
|
cancel(true);
|
||||||
mtpRequestId requestId) {
|
}
|
||||||
if (MTP::isDefaultHandledError(error)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestId == _cdnHashesRequestId) {
|
bool mtpFileLoader::setWebFileSizeHook(int size) {
|
||||||
_cdnHashesRequestId = 0;
|
if (!_size || _size == size) {
|
||||||
}
|
_size = size;
|
||||||
if (error.type() == qstr("FILE_TOKEN_INVALID")
|
|
||||||
|| error.type() == qstr("REQUEST_TOKEN_INVALID")) {
|
|
||||||
const auto requestData = finishSentRequest(requestId);
|
|
||||||
changeCDNParams(
|
|
||||||
requestData,
|
|
||||||
0,
|
|
||||||
QByteArray(),
|
|
||||||
QByteArray(),
|
|
||||||
QByteArray(),
|
|
||||||
QVector<MTPFileHash>());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return partFailed(error, requestId);
|
LOG(("MTP Error: "
|
||||||
|
"Bad size provided by bot for webDocument: %1, real: %2"
|
||||||
|
).arg(_size
|
||||||
|
).arg(size));
|
||||||
|
cancel(true);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mtpFileLoader::startLoading() {
|
void mtpFileLoader::startLoading() {
|
||||||
_downloader->enqueue(this);
|
addToQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void mtpFileLoader::cancelRequests() {
|
void mtpFileLoader::cancelHook() {
|
||||||
while (!_sentRequests.empty()) {
|
cancelAllRequests();
|
||||||
auto requestId = _sentRequests.begin()->first;
|
|
||||||
MTP::cancel(requestId);
|
|
||||||
[[maybe_unused]] const auto data = finishSentRequest(requestId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void mtpFileLoader::switchToCDN(
|
|
||||||
const RequestData &requestData,
|
|
||||||
const MTPDupload_fileCdnRedirect &redirect) {
|
|
||||||
changeCDNParams(
|
|
||||||
requestData,
|
|
||||||
redirect.vdc_id().v,
|
|
||||||
redirect.vfile_token().v,
|
|
||||||
redirect.vencryption_key().v,
|
|
||||||
redirect.vencryption_iv().v,
|
|
||||||
redirect.vfile_hashes().v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void mtpFileLoader::addCdnHashes(const QVector<MTPFileHash> &hashes) {
|
|
||||||
for (const auto &hash : hashes) {
|
|
||||||
hash.match([&](const MTPDfileHash &data) {
|
|
||||||
_cdnFileHashes.emplace(
|
|
||||||
data.voffset().v,
|
|
||||||
CdnFileHash{ data.vlimit().v, data.vhash().v });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void mtpFileLoader::changeCDNParams(
|
|
||||||
const RequestData &requestData,
|
|
||||||
MTP::DcId dcId,
|
|
||||||
const QByteArray &token,
|
|
||||||
const QByteArray &encryptionKey,
|
|
||||||
const QByteArray &encryptionIV,
|
|
||||||
const QVector<MTPFileHash> &hashes) {
|
|
||||||
if (dcId != 0
|
|
||||||
&& (encryptionKey.size() != MTP::CTRState::KeySize
|
|
||||||
|| encryptionIV.size() != MTP::CTRState::IvecSize)) {
|
|
||||||
LOG(("Message Error: Wrong key (%1) / iv (%2) size in CDN params").arg(encryptionKey.size()).arg(encryptionIV.size()));
|
|
||||||
cancel(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto resendAllRequests = (_cdnDcId != dcId
|
|
||||||
|| _cdnToken != token
|
|
||||||
|| _cdnEncryptionKey != encryptionKey
|
|
||||||
|| _cdnEncryptionIV != encryptionIV);
|
|
||||||
_cdnDcId = dcId;
|
|
||||||
_cdnToken = token;
|
|
||||||
_cdnEncryptionKey = encryptionKey;
|
|
||||||
_cdnEncryptionIV = encryptionIV;
|
|
||||||
addCdnHashes(hashes);
|
|
||||||
|
|
||||||
if (resendAllRequests && !_sentRequests.empty()) {
|
|
||||||
auto resendRequests = std::vector<RequestData>();
|
|
||||||
resendRequests.reserve(_sentRequests.size());
|
|
||||||
while (!_sentRequests.empty()) {
|
|
||||||
auto requestId = _sentRequests.begin()->first;
|
|
||||||
MTP::cancel(requestId);
|
|
||||||
resendRequests.push_back(finishSentRequest(requestId));
|
|
||||||
}
|
|
||||||
for (const auto &requestData : resendRequests) {
|
|
||||||
makeRequest(requestData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
makeRequest(requestData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage::Cache::Key mtpFileLoader::cacheKey() const {
|
Storage::Cache::Key mtpFileLoader::cacheKey() const {
|
||||||
return _location.match([&](const WebFileLocation &location) {
|
return location().data.match([&](const WebFileLocation &location) {
|
||||||
return Data::WebDocumentCacheKey(location);
|
return Data::WebDocumentCacheKey(location);
|
||||||
}, [&](const GeoPointLocation &location) {
|
}, [&](const GeoPointLocation &location) {
|
||||||
return Data::GeoPointCacheKey(location);
|
return Data::GeoPointCacheKey(location);
|
||||||
|
@ -8,13 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "storage/file_download.h"
|
#include "storage/file_download.h"
|
||||||
|
#include "storage/download_manager_mtproto.h"
|
||||||
|
|
||||||
class StorageImageLocation;
|
|
||||||
class WebFileLocation;
|
|
||||||
class mtpFileLoader final
|
class mtpFileLoader final
|
||||||
: public FileLoader
|
: public FileLoader
|
||||||
, public RPCSender
|
, private Storage::DownloadMtprotoTask {
|
||||||
, public Storage::Downloader {
|
|
||||||
public:
|
public:
|
||||||
mtpFileLoader(
|
mtpFileLoader(
|
||||||
const StorageFileLocation &location,
|
const StorageFileLocation &location,
|
||||||
@ -40,101 +38,21 @@ public:
|
|||||||
uint8 cacheTag);
|
uint8 cacheTag);
|
||||||
|
|
||||||
Data::FileOrigin fileOrigin() const override;
|
Data::FileOrigin fileOrigin() const override;
|
||||||
|
|
||||||
uint64 objId() const override;
|
uint64 objId() const override;
|
||||||
|
|
||||||
void stop() override {
|
|
||||||
rpcInvalidate();
|
|
||||||
}
|
|
||||||
void refreshFileReferenceFrom(
|
|
||||||
const Data::UpdatedFileReferences &updates,
|
|
||||||
int requestId,
|
|
||||||
const QByteArray ¤t);
|
|
||||||
|
|
||||||
~mtpFileLoader();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct RequestData {
|
|
||||||
int offset = 0;
|
|
||||||
int dcIndex = 0;
|
|
||||||
|
|
||||||
inline bool operator<(const RequestData &other) const {
|
|
||||||
return offset < other.offset;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
struct CdnFileHash {
|
|
||||||
CdnFileHash(int limit, QByteArray hash) : limit(limit), hash(hash) {
|
|
||||||
}
|
|
||||||
int limit = 0;
|
|
||||||
QByteArray hash;
|
|
||||||
};
|
|
||||||
Storage::Cache::Key cacheKey() const override;
|
Storage::Cache::Key cacheKey() const override;
|
||||||
std::optional<MediaKey> fileLocationKey() const override;
|
std::optional<MediaKey> fileLocationKey() const override;
|
||||||
void startLoading() override;
|
void startLoading() override;
|
||||||
void cancelRequests() override;
|
void cancelHook() override;
|
||||||
|
|
||||||
void makeRequest(const RequestData &requestData);
|
|
||||||
|
|
||||||
MTP::DcId dcId() const override;
|
|
||||||
bool readyToRequest() const override;
|
bool readyToRequest() const override;
|
||||||
void loadPart(int dcIndex) override;
|
int takeNextRequestOffset() override;
|
||||||
void normalPartLoaded(const MTPupload_File &result, mtpRequestId requestId);
|
bool feedPart(int offset, const QByteArray &bytes) override;
|
||||||
void webPartLoaded(const MTPupload_WebFile &result, mtpRequestId requestId);
|
void cancelOnFail() override;
|
||||||
void cdnPartLoaded(const MTPupload_CdnFile &result, mtpRequestId requestId);
|
bool setWebFileSizeHook(int size) override;
|
||||||
void reuploadDone(const MTPVector<MTPFileHash> &result, mtpRequestId requestId);
|
|
||||||
void requestMoreCdnFileHashes();
|
|
||||||
void getCdnFileHashesDone(const MTPVector<MTPFileHash> &result, mtpRequestId requestId);
|
|
||||||
|
|
||||||
void partLoaded(int offset, bytes::const_span buffer);
|
|
||||||
bool feedPart(int offset, bytes::const_span buffer);
|
|
||||||
|
|
||||||
bool partFailed(const RPCError &error, mtpRequestId requestId);
|
|
||||||
bool normalPartFailed(QByteArray fileReference, const RPCError &error, mtpRequestId requestId);
|
|
||||||
bool cdnPartFailed(const RPCError &error, mtpRequestId requestId);
|
|
||||||
|
|
||||||
mtpRequestId sendRequest(const RequestData &requestData);
|
|
||||||
void placeSentRequest(
|
|
||||||
mtpRequestId requestId,
|
|
||||||
const RequestData &requestData);
|
|
||||||
[[nodiscard]] RequestData finishSentRequest(mtpRequestId requestId);
|
|
||||||
void switchToCDN(
|
|
||||||
const RequestData &requestData,
|
|
||||||
const MTPDupload_fileCdnRedirect &redirect);
|
|
||||||
void addCdnHashes(const QVector<MTPFileHash> &hashes);
|
|
||||||
void changeCDNParams(
|
|
||||||
const RequestData &requestData,
|
|
||||||
MTP::DcId dcId,
|
|
||||||
const QByteArray &token,
|
|
||||||
const QByteArray &encryptionKey,
|
|
||||||
const QByteArray &encryptionIV,
|
|
||||||
const QVector<MTPFileHash> &hashes);
|
|
||||||
|
|
||||||
enum class CheckCdnHashResult {
|
|
||||||
NoHash,
|
|
||||||
Invalid,
|
|
||||||
Good,
|
|
||||||
};
|
|
||||||
CheckCdnHashResult checkCdnFileHash(int offset, bytes::const_span buffer);
|
|
||||||
|
|
||||||
const not_null<Storage::DownloadManager*> _downloader;
|
|
||||||
const MTP::DcId _dcId = 0;
|
|
||||||
std::map<mtpRequestId, RequestData> _sentRequests;
|
|
||||||
|
|
||||||
bool _lastComplete = false;
|
bool _lastComplete = false;
|
||||||
int32 _nextRequestOffset = 0;
|
int32 _nextRequestOffset = 0;
|
||||||
|
|
||||||
base::variant<
|
|
||||||
StorageFileLocation,
|
|
||||||
WebFileLocation,
|
|
||||||
GeoPointLocation> _location;
|
|
||||||
Data::FileOrigin _origin;
|
|
||||||
|
|
||||||
MTP::DcId _cdnDcId = 0;
|
|
||||||
QByteArray _cdnToken;
|
|
||||||
QByteArray _cdnEncryptionKey;
|
|
||||||
QByteArray _cdnEncryptionIV;
|
|
||||||
base::flat_map<int, CdnFileHash> _cdnFileHashes;
|
|
||||||
base::flat_map<RequestData, QByteArray> _cdnUncheckedParts;
|
|
||||||
mtpRequestId _cdnHashesRequestId = 0;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -455,7 +455,7 @@ webFileLoader::webFileLoader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
webFileLoader::~webFileLoader() {
|
webFileLoader::~webFileLoader() {
|
||||||
cancelRequests();
|
cancelRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString webFileLoader::url() const {
|
QString webFileLoader::url() const {
|
||||||
@ -493,7 +493,7 @@ void webFileLoader::loadProgress(qint64 ready, qint64 total) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void webFileLoader::loadFinished(const QByteArray &data) {
|
void webFileLoader::loadFinished(const QByteArray &data) {
|
||||||
cancelRequests();
|
cancelRequest();
|
||||||
if (writeResultPart(0, bytes::make_span(data))) {
|
if (writeResultPart(0, bytes::make_span(data))) {
|
||||||
if (finalizeResult()) {
|
if (finalizeResult()) {
|
||||||
notifyAboutProgress();
|
notifyAboutProgress();
|
||||||
@ -513,11 +513,11 @@ std::optional<MediaKey> webFileLoader::fileLocationKey() const {
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void webFileLoader::stop() {
|
void webFileLoader::cancelHook() {
|
||||||
cancelRequests();
|
cancelRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
void webFileLoader::cancelRequests() {
|
void webFileLoader::cancelRequest() {
|
||||||
if (!_manager) {
|
if (!_manager) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -24,10 +24,10 @@ public:
|
|||||||
[[nodiscard]] QString url() const;
|
[[nodiscard]] QString url() const;
|
||||||
|
|
||||||
int currentOffset() const override;
|
int currentOffset() const override;
|
||||||
void stop() override;
|
|
||||||
void cancelRequests() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void cancelRequest();
|
||||||
|
void cancelHook() override;
|
||||||
void startLoading() override;
|
void startLoading() override;
|
||||||
Storage::Cache::Key cacheKey() const override;
|
Storage::Cache::Key cacheKey() const override;
|
||||||
std::optional<MediaKey> fileLocationKey() const override;
|
std::optional<MediaKey> fileLocationKey() const override;
|
||||||
|
@ -63,7 +63,7 @@ StreamedFileDownloader::StreamedFileDownloader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
StreamedFileDownloader::~StreamedFileDownloader() {
|
StreamedFileDownloader::~StreamedFileDownloader() {
|
||||||
stop();
|
cancelHook();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64 StreamedFileDownloader::objId() const {
|
uint64 StreamedFileDownloader::objId() const {
|
||||||
@ -74,10 +74,6 @@ Data::FileOrigin StreamedFileDownloader::fileOrigin() const {
|
|||||||
return _origin;
|
return _origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamedFileDownloader::stop() {
|
|
||||||
cancelRequests();
|
|
||||||
}
|
|
||||||
|
|
||||||
void StreamedFileDownloader::requestParts() {
|
void StreamedFileDownloader::requestParts() {
|
||||||
while (!_finished
|
while (!_finished
|
||||||
&& _nextPartIndex < _partsCount
|
&& _nextPartIndex < _partsCount
|
||||||
@ -121,7 +117,7 @@ std::optional<MediaKey> StreamedFileDownloader::fileLocationKey() const {
|
|||||||
return _fileLocationKey;
|
return _fileLocationKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamedFileDownloader::cancelRequests() {
|
void StreamedFileDownloader::cancelHook() {
|
||||||
_partsRequested = 0;
|
_partsRequested = 0;
|
||||||
_nextPartIndex = 0;
|
_nextPartIndex = 0;
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
|
|
||||||
#include "storage/file_download.h"
|
#include "storage/file_download.h"
|
||||||
#include "storage/cache/storage_cache_types.h"
|
#include "storage/cache/storage_cache_types.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
|
|
||||||
namespace Media {
|
namespace Media {
|
||||||
namespace Streaming {
|
namespace Streaming {
|
||||||
@ -41,7 +42,6 @@ public:
|
|||||||
|
|
||||||
uint64 objId() const override;
|
uint64 objId() const override;
|
||||||
Data::FileOrigin fileOrigin() const override;
|
Data::FileOrigin fileOrigin() const override;
|
||||||
void stop() override;
|
|
||||||
|
|
||||||
QByteArray readLoadedPart(int offset);
|
QByteArray readLoadedPart(int offset);
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ private:
|
|||||||
void startLoading() override;
|
void startLoading() override;
|
||||||
Cache::Key cacheKey() const override;
|
Cache::Key cacheKey() const override;
|
||||||
std::optional<MediaKey> fileLocationKey() const override;
|
std::optional<MediaKey> fileLocationKey() const override;
|
||||||
void cancelRequests() override;
|
void cancelHook() override;
|
||||||
void requestParts();
|
void requestParts();
|
||||||
void requestPart();
|
void requestPart();
|
||||||
|
|
||||||
|
@ -306,7 +306,6 @@ void RemoteSource::destroyLoader() {
|
|||||||
if (cancelled()) {
|
if (cancelled()) {
|
||||||
loader->cancel();
|
loader->cancel();
|
||||||
}
|
}
|
||||||
loader->stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RemoteSource::loadLocal() {
|
void RemoteSource::loadLocal() {
|
||||||
|
@ -65,7 +65,7 @@ std::unique_ptr<Manager> Create(System *system) {
|
|||||||
Manager::Manager(System *system)
|
Manager::Manager(System *system)
|
||||||
: Notifications::Manager(system)
|
: Notifications::Manager(system)
|
||||||
, _inputCheckTimer([=] { checkLastInput(); }) {
|
, _inputCheckTimer([=] { checkLastInput(); }) {
|
||||||
subscribe(system->session().downloader().taskFinished(), [this] {
|
subscribe(system->session().downloaderTaskFinished(), [this] {
|
||||||
for (const auto ¬ification : _notifications) {
|
for (const auto ¬ification : _notifications) {
|
||||||
notification->updatePeerPhoto();
|
notification->updatePeerPhoto();
|
||||||
}
|
}
|
||||||
|
@ -683,6 +683,8 @@
|
|||||||
<(src_loc)/settings/settings_privacy_controllers.h
|
<(src_loc)/settings/settings_privacy_controllers.h
|
||||||
<(src_loc)/settings/settings_privacy_security.cpp
|
<(src_loc)/settings/settings_privacy_security.cpp
|
||||||
<(src_loc)/settings/settings_privacy_security.h
|
<(src_loc)/settings/settings_privacy_security.h
|
||||||
|
<(src_loc)/storage/download_manager_mtproto.cpp
|
||||||
|
<(src_loc)/storage/download_manager_mtproto.h
|
||||||
<(src_loc)/storage/file_download.cpp
|
<(src_loc)/storage/file_download.cpp
|
||||||
<(src_loc)/storage/file_download.h
|
<(src_loc)/storage/file_download.h
|
||||||
<(src_loc)/storage/file_download_mtproto.cpp
|
<(src_loc)/storage/file_download_mtproto.cpp
|
||||||
|
Loading…
Reference in New Issue
Block a user