diff --git a/example/uwp/app/MainPage.xaml.cs b/example/uwp/app/MainPage.xaml.cs index b12116cd3..fa4454595 100644 --- a/example/uwp/app/MainPage.xaml.cs +++ b/example/uwp/app/MainPage.xaml.cs @@ -118,7 +118,7 @@ namespace TdApp { var args = command.Split(" ".ToCharArray(), 2); AcceptCommand(command); - _client.Send(new TdApi.DownloadFile(Int32.Parse(args[1]), 1, 0, 0), _handler); + _client.Send(new TdApi.DownloadFile(Int32.Parse(args[1]), 1, 0, 0, false), _handler); } else if (command.StartsWith("bench")) { diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 96c01aad3..2f465473a 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -3099,12 +3099,14 @@ resetAllNotificationSettings = Ok; setPinnedChats chat_ids:vector = Ok; -//@description Asynchronously downloads a file from the cloud. updateFile will be used to notify about the download progress and successful completion of the download. Returns file state just after the download has been started +//@description Downloads a file from the cloud. Download progress and completion of the download will be notified through updateFile updates //@file_id Identifier of the file to download //@priority Priority of the download (1-32). The higher the priority, the earlier the file will be downloaded. If the priorities of two files are equal, then the last one for which downloadFile was called will be downloaded first //@offset File will be downloaded starting from that offset in bytes first. Supposed to be used for streaming //@limit Download will be automatically cancelled when at least limit bytes are downloaded starting from the specified offset; use 0 to download without limit -downloadFile file_id:int32 priority:int32 offset:int32 limit:int32 = File; +//@synchronous If false, this request returns file state just after the download has been started. If true, this request returns file state only after +//-the download has succeeded, has failed, has been cancelled or a new downloadFile request with different offset/limit parameters was sent +downloadFile file_id:int32 priority:int32 offset:int32 limit:int32 synchronous:Bool = File; //@description Returns file downloaded prefix size from a given offset @file_id Identifier of the file @offset Offset from which downloaded prefix size should be calculated getFileDownloadedPrefixSize file_id:int32 offset:int32 = Count; diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index 36b44511f..a95a73927 100644 Binary files a/td/generate/scheme/td_api.tlo and b/td/generate/scheme/td_api.tlo differ diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 126e126a9..416871cca 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -3915,9 +3915,11 @@ class Td::DownloadFileCallback : public FileManager::DownloadCallback { } void on_download_ok(FileId file_id) override { + send_closure(G()->td(), &Td::on_file_download_finished, file_id); } void on_download_error(FileId file_id, Status error) override { + send_closure(G()->td(), &Td::on_file_download_finished, file_id); } }; @@ -5734,18 +5736,67 @@ void Td::on_request(uint64 id, const td_api::downloadFile &request) { if (!(1 <= priority && priority <= 32)) { return send_error_raw(id, 5, "Download priority must be in [1;32] range"); } - if (request.offset_ < 0) { + auto offset = request.offset_; + if (offset < 0) { return send_error_raw(id, 5, "Download offset must be non-negative"); } - file_manager_->download(FileId(request.file_id_, 0), download_file_callback_, priority, request.offset_, - request.limit_); + auto limit = request.limit_; + if (limit < 0) { + return send_error_raw(id, 5, "Download limit must be non-negative"); + } - auto file = file_manager_->get_file_object(FileId(request.file_id_, 0), false); - if (file->id_ == 0) { + FileId file_id(request.file_id_, 0); + auto file_view = file_manager_->get_file_view(file_id); + if (file_view.empty()) { return send_error_raw(id, 400, "Invalid file id"); } - send_closure(actor_id(this), &Td::send_result, id, std::move(file)); + auto info_it = pending_file_downloads_.find(file_id); + DownloadInfo *info = info_it == pending_file_downloads_.end() ? nullptr : &info_it->second; + if (info != nullptr && (offset != info->offset || limit != info->limit)) { + // we can't have two pending requests with different offset and limit, so cancel all previous requests + for (auto request_id : info->request_ids) { + send_closure(actor_id(this), &Td::send_error, request_id, + Status::Error(200, "Cancelled by another downloadFile request")); + } + info->request_ids.clear(); + } + if (request.synchronous_) { + if (info == nullptr) { + info = &pending_file_downloads_[file_id]; + } + info->offset = offset; + info->limit = limit; + info->request_ids.push_back(id); + } + file_manager_->download(file_id, download_file_callback_, priority, offset, limit); + if (!request.synchronous_) { + send_closure(actor_id(this), &Td::send_result, id, file_manager_->get_file_object(file_id, false)); + } +} + +void Td::on_file_download_finished(FileId file_id) { + auto it = pending_file_downloads_.find(file_id); + if (it == pending_file_downloads_.end()) { + return; + } + for (auto id : it->second.request_ids) { + // there was send_closure to call this function + auto file_object = file_manager_->get_file_object(file_id, false); + CHECK(file_object != nullptr); + auto download_offset = file_object->local_->download_offset_; + auto downloaded_size = file_object->local_->downloaded_prefix_size_; + auto file_size = file_object->size_; + if (file_object->local_->is_downloading_completed_ || + (download_offset <= it->second.offset && download_offset + downloaded_size >= it->second.offset && + ((file_size != 0 && download_offset + downloaded_size == file_size) || + download_offset + downloaded_size - it->second.offset >= it->second.limit))) { + send_result(id, std::move(file_object)); + } else { + send_error_impl(id, td_api::make_object(400, "File download has failed or was cancelled")); + } + } + pending_file_downloads_.erase(it); } void Td::on_request(uint64 id, const td_api::cancelDownloadFile &request) { diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 04da9d297..1b43037a1 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -6,6 +6,7 @@ // #pragma once +#include "td/telegram/files/FileId.h" #include "td/telegram/net/MtprotoHeader.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/StateManager.h" @@ -275,6 +276,13 @@ class Td final : public NetQueryCallback { TermsOfService pending_terms_of_service_; + struct DownloadInfo { + int32 offset = -1; + int32 limit = -1; + vector request_ids; + }; + std::unordered_map pending_file_downloads_; + vector>> pending_preauthentication_requests_; template @@ -303,6 +311,8 @@ class Td final : public NetQueryCallback { void clear_requests(); + void on_file_download_finished(FileId file_id); + static bool is_internal_config_option(Slice name); void on_config_option_updated(const string &name); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 5b16b016a..855db7783 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -2371,7 +2371,7 @@ class CliClient final : public Actor { send_request(td_api::make_object( as_location(latitude, longitude), to_integer(zoom), to_integer(width), to_integer(height), to_integer(scale), as_chat_id(chat_id))); - } else if (op == "df" || op == "DownloadFile" || op == "dff") { + } else if (op == "df" || op == "DownloadFile" || op == "dff" || op == "dfs") { string file_id; string priority; string offset; @@ -2386,8 +2386,8 @@ class CliClient final : public Actor { int32 max_file_id = as_file_id(file_id); int32 min_file_id = (op == "dff" ? 1 : max_file_id); for (int32 i = min_file_id; i <= max_file_id; i++) { - send_request(td_api::make_object(i, to_integer(priority), - to_integer(offset), to_integer(limit))); + send_request(td_api::make_object( + i, to_integer(priority), to_integer(offset), to_integer(limit), op == "dfs")); } } else if (op == "cdf") { send_request(td_api::make_object(as_file_id(args), false)); diff --git a/td/telegram/files/FileManager.cpp b/td/telegram/files/FileManager.cpp index 5883d50a3..4784455e7 100644 --- a/td/telegram/files/FileManager.cpp +++ b/td/telegram/files/FileManager.cpp @@ -1900,6 +1900,13 @@ void FileManager::download(FileId file_id, std::shared_ptr cal node->set_download_limit(limit); auto *file_info = get_file_id_info(file_id); CHECK(new_priority == 0 || callback); + if (file_info->download_callback_ != nullptr && file_info->download_callback_.get() != callback.get()) { + // the callback will be destroyed soon and lost forever + // this would be an error and should never happen, unless we cancel previous download query + // in that case we send an error to the callback + CHECK(new_priority == 0); + file_info->download_callback_->on_download_error(file_id, Status::Error(200, "Cancelled")); + } file_info->download_priority_ = narrow_cast(new_priority); file_info->download_callback_ = std::move(callback); // TODO: send current progress? diff --git a/test/tdclient.cpp b/test/tdclient.cpp index 3173b3f95..ffb2d299e 100644 --- a/test/tdclient.cpp +++ b/test/tdclient.cpp @@ -615,7 +615,7 @@ class CheckTestC : public Task { if (text.substr(0, tag_.size()) == tag_) { file_id_to_check_ = messageDocument->document_->document_->id_; LOG(ERROR) << "GOT FILE " << to_string(messageDocument->document_->document_); - this->send_query(make_tl_object(file_id_to_check_, 1, 0, 0), + this->send_query(make_tl_object(file_id_to_check_, 1, 0, 0, false), [](auto res) { check_td_error(res); }); } }