summaryrefslogtreecommitdiff
path: root/libs/tdlib/td/td/telegram/files
diff options
context:
space:
mode:
Diffstat (limited to 'libs/tdlib/td/td/telegram/files')
-rw-r--r--libs/tdlib/td/td/telegram/files/FileDb.cpp290
-rw-r--r--libs/tdlib/td/td/telegram/files/FileDb.h75
-rw-r--r--libs/tdlib/td/td/telegram/files/FileDownloader.cpp461
-rw-r--r--libs/tdlib/td/td/telegram/files/FileDownloader.h100
-rw-r--r--libs/tdlib/td/td/telegram/files/FileFromBytes.cpp50
-rw-r--r--libs/tdlib/td/td/telegram/files/FileFromBytes.h54
-rw-r--r--libs/tdlib/td/td/telegram/files/FileGcParameters.cpp32
-rw-r--r--libs/tdlib/td/td/telegram/files/FileGcParameters.h34
-rw-r--r--libs/tdlib/td/td/telegram/files/FileGcWorker.cpp174
-rw-r--r--libs/tdlib/td/td/telegram/files/FileGcWorker.h28
-rw-r--r--libs/tdlib/td/td/telegram/files/FileGenerateManager.cpp285
-rw-r--r--libs/tdlib/td/td/telegram/files/FileGenerateManager.h70
-rw-r--r--libs/tdlib/td/td/telegram/files/FileHashUploader.cpp142
-rw-r--r--libs/tdlib/td/td/telegram/files/FileHashUploader.h80
-rw-r--r--libs/tdlib/td/td/telegram/files/FileId.h67
-rw-r--r--libs/tdlib/td/td/telegram/files/FileId.hpp26
-rw-r--r--libs/tdlib/td/td/telegram/files/FileLoadManager.cpp273
-rw-r--r--libs/tdlib/td/td/telegram/files/FileLoadManager.h167
-rw-r--r--libs/tdlib/td/td/telegram/files/FileLoader.cpp292
-rw-r--r--libs/tdlib/td/td/telegram/files/FileLoader.h129
-rw-r--r--libs/tdlib/td/td/telegram/files/FileLoaderActor.h28
-rw-r--r--libs/tdlib/td/td/telegram/files/FileLoaderUtils.cpp168
-rw-r--r--libs/tdlib/td/td/telegram/files/FileLoaderUtils.h25
-rw-r--r--libs/tdlib/td/td/telegram/files/FileLocation.h1199
-rw-r--r--libs/tdlib/td/td/telegram/files/FileManager.cpp2356
-rw-r--r--libs/tdlib/td/td/telegram/files/FileManager.h475
-rw-r--r--libs/tdlib/td/td/telegram/files/FileManager.hpp222
-rw-r--r--libs/tdlib/td/td/telegram/files/FileStats.cpp221
-rw-r--r--libs/tdlib/td/td/telegram/files/FileStats.h88
-rw-r--r--libs/tdlib/td/td/telegram/files/FileStatsWorker.cpp184
-rw-r--r--libs/tdlib/td/td/telegram/files/FileStatsWorker.h26
-rw-r--r--libs/tdlib/td/td/telegram/files/FileUploader.cpp292
-rw-r--r--libs/tdlib/td/td/telegram/files/FileUploader.h76
-rw-r--r--libs/tdlib/td/td/telegram/files/PartsManager.cpp333
-rw-r--r--libs/tdlib/td/td/telegram/files/PartsManager.h87
-rw-r--r--libs/tdlib/td/td/telegram/files/ResourceManager.cpp179
-rw-r--r--libs/tdlib/td/td/telegram/files/ResourceManager.h66
-rw-r--r--libs/tdlib/td/td/telegram/files/ResourceState.h104
38 files changed, 8958 insertions, 0 deletions
diff --git a/libs/tdlib/td/td/telegram/files/FileDb.cpp b/libs/tdlib/td/td/telegram/files/FileDb.cpp
new file mode 100644
index 0000000000..96795dcea1
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileDb.cpp
@@ -0,0 +1,290 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileDb.h"
+
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/Version.h"
+
+#include "td/actor/actor.h"
+
+#include "td/db/SqliteKeyValueSafe.h"
+
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/Slice.h"
+#include "td/utils/StackAllocator.h"
+#include "td/utils/Status.h"
+#include "td/utils/tl_helpers.h"
+#include "td/utils/tl_parsers.h"
+#include "td/utils/tl_storers.h"
+
+namespace td {
+
+Status drop_file_db(SqliteDb &db, int32 version) {
+ LOG(WARNING) << "Drop file_db " << tag("version", version) << tag("current_db_version", current_db_version());
+ TRY_STATUS(SqliteKeyValue::drop(db, "files"));
+ return Status::OK();
+}
+
+Status fix_file_remote_location_key_bug(SqliteDb &db);
+Status init_file_db(SqliteDb &db, int32 version) {
+ LOG(INFO) << "Init file db " << tag("version", version);
+
+ // Check if database exists
+ TRY_RESULT(has_table, db.has_table("files"));
+
+ if (!has_table) {
+ version = 0;
+ } else if (version < static_cast<int32>(DbVersion::DialogDbCreated)) {
+ TRY_STATUS(drop_file_db(db, version));
+ version = 0;
+ } else if (version < static_cast<int>(DbVersion::FixFileRemoteLocationKeyBug)) {
+ TRY_STATUS(fix_file_remote_location_key_bug(db));
+ }
+
+ if (version == 0) {
+ TRY_STATUS(SqliteKeyValue::init(db, "files"));
+ }
+ return Status::OK();
+}
+
+class FileDb : public FileDbInterface {
+ public:
+ class FileDbActor : public Actor {
+ public:
+ using Id = FileDbInterface::Id;
+ FileDbActor(Id current_pmc_id, std::shared_ptr<SqliteKeyValueSafe> file_kv_safe)
+ : current_pmc_id_(current_pmc_id), file_kv_safe_(std::move(file_kv_safe)) {
+ }
+
+ void close(Promise<> promise) {
+ file_kv_safe_.reset();
+ LOG(INFO) << "FileDb is closed";
+ promise.set_value(Unit());
+ stop();
+ }
+
+ void load_file_data(const string &key, Promise<FileData> promise) {
+ promise.set_result(load_file_data_impl(file_pmc(), key));
+ }
+
+ void clear_file_data(Id id, const string &remote_key, const string &local_key, const string &generate_key) {
+ auto &pmc = file_pmc();
+ pmc.begin_transaction().ensure();
+ SCOPE_EXIT {
+ pmc.commit_transaction().ensure();
+ };
+
+ if (id > current_pmc_id_) {
+ pmc.set("file_id", to_string(id));
+ current_pmc_id_ = id;
+ }
+
+ pmc.erase("file" + to_string(id));
+ LOG(DEBUG) << "ERASE " << format::as_hex_dump<4>(Slice(PSLICE() << "file" << to_string(id)));
+
+ if (!remote_key.empty()) {
+ pmc.erase(remote_key);
+ LOG(DEBUG) << "ERASE remote " << format::as_hex_dump<4>(Slice(remote_key));
+ }
+ if (!local_key.empty()) {
+ pmc.erase(local_key);
+ LOG(DEBUG) << "ERASE local " << format::as_hex_dump<4>(Slice(local_key));
+ }
+ if (!generate_key.empty()) {
+ pmc.erase(generate_key);
+ }
+ }
+ void store_file_data(Id id, const string &file_data, const string &remote_key, const string &local_key,
+ const string &generate_key) {
+ auto &pmc = file_pmc();
+ pmc.begin_transaction().ensure();
+ SCOPE_EXIT {
+ pmc.commit_transaction().ensure();
+ };
+
+ if (id > current_pmc_id_) {
+ pmc.set("file_id", to_string(id));
+ current_pmc_id_ = id;
+ }
+
+ pmc.set("file" + to_string(id), file_data);
+
+ if (!remote_key.empty()) {
+ pmc.set(remote_key, to_string(id));
+ }
+ if (!local_key.empty()) {
+ pmc.set(local_key, to_string(id));
+ }
+ if (!generate_key.empty()) {
+ pmc.set(generate_key, to_string(id));
+ }
+ }
+ void store_file_data_ref(Id id, Id new_id) {
+ auto &pmc = file_pmc();
+ pmc.begin_transaction().ensure();
+ SCOPE_EXIT {
+ pmc.commit_transaction().ensure();
+ };
+
+ if (id > current_pmc_id_) {
+ pmc.set("file_id", to_string(id));
+ current_pmc_id_ = id;
+ }
+
+ pmc.set("file" + to_string(id), "@@" + to_string(new_id));
+ }
+
+ private:
+ Id current_pmc_id_;
+ std::shared_ptr<SqliteKeyValueSafe> file_kv_safe_;
+
+ SqliteKeyValue &file_pmc() {
+ return file_kv_safe_->get();
+ }
+ };
+
+ explicit FileDb(std::shared_ptr<SqliteKeyValueSafe> kv_safe, int scheduler_id = -1) {
+ file_kv_safe_ = std::move(kv_safe);
+ CHECK(file_kv_safe_);
+ current_pmc_id_ = to_integer<int32>(file_kv_safe_->get().get("file_id"));
+ file_db_actor_ =
+ create_actor_on_scheduler<FileDbActor>("FileDbActor", scheduler_id, current_pmc_id_, file_kv_safe_);
+ }
+
+ Id create_pmc_id() override {
+ return ++current_pmc_id_;
+ }
+
+ void close(Promise<> promise) override {
+ send_closure(std::move(file_db_actor_), &FileDbActor::close, std::move(promise));
+ }
+
+ void get_file_data_impl(string key, Promise<FileData> promise) override {
+ send_closure(file_db_actor_, &FileDbActor::load_file_data, std::move(key), std::move(promise));
+ }
+
+ Result<FileData> get_file_data_sync_impl(string key) override {
+ return load_file_data_impl(file_kv_safe_->get(), key);
+ }
+
+ void clear_file_data(Id id, const FileData &file_data) override {
+ string remote_key;
+ if (file_data.remote_.type() == RemoteFileLocation::Type::Full) {
+ remote_key = as_key(file_data.remote_.full());
+ }
+ string local_key;
+ if (file_data.local_.type() == LocalFileLocation::Type::Full) {
+ local_key = as_key(file_data.local_.full());
+ }
+ string generate_key;
+ if (file_data.generate_ != nullptr) {
+ generate_key = as_key(*file_data.generate_);
+ }
+ send_closure(file_db_actor_, &FileDbActor::clear_file_data, id, remote_key, local_key, generate_key);
+ }
+ void set_file_data(Id id, const FileData &file_data, bool new_remote, bool new_local, bool new_generate) override {
+ string remote_key;
+ if (file_data.remote_.type() == RemoteFileLocation::Type::Full && new_remote) {
+ remote_key = as_key(file_data.remote_.full());
+ }
+ string local_key;
+ if (file_data.local_.type() == LocalFileLocation::Type::Full && new_local) {
+ local_key = as_key(file_data.local_.full());
+ }
+ string generate_key;
+ if (file_data.generate_ != nullptr && new_generate) {
+ generate_key = as_key(*file_data.generate_);
+ }
+ LOG(DEBUG) << "SAVE " << id << " -> " << file_data << " "
+ << tag("remote", format::as_hex_dump<4>(Slice(remote_key)))
+ << tag("local", format::as_hex_dump<4>(Slice(local_key)));
+ send_closure(file_db_actor_, &FileDbActor::store_file_data, id, serialize(file_data), remote_key, local_key,
+ generate_key);
+ }
+
+ void set_file_data_ref(Id id, Id new_id) override {
+ send_closure(file_db_actor_, &FileDbActor::store_file_data_ref, id, new_id);
+ }
+ SqliteKeyValue &pmc() override {
+ return file_kv_safe_->get();
+ }
+
+ private:
+ ActorOwn<FileDbActor> file_db_actor_;
+ Id current_pmc_id_;
+ std::shared_ptr<SqliteKeyValueSafe> file_kv_safe_;
+
+ static Result<FileData> load_file_data_impl(SqliteKeyValue &pmc, const string &key) {
+ //LOG(DEBUG) << "Load by key " << format::as_hex_dump<4>(Slice(key));
+ TRY_RESULT(id, get_id(pmc, key));
+
+ string data_str;
+ int attempt_count = 0;
+ while (true) {
+ if (attempt_count > 5) {
+ LOG(FATAL) << "cycle in files db?";
+ }
+ attempt_count++;
+
+ data_str = pmc.get(PSTRING() << "file" << id);
+ auto data_slice = Slice(data_str);
+
+ if (data_slice.substr(0, 2) == "@@") {
+ id = to_integer<Id>(data_slice.substr(2));
+ } else {
+ break;
+ }
+ }
+ //LOG(DEBUG) << "By id " << id << " found data " << format::as_hex_dump<4>(Slice(data_str));
+
+ FileData data;
+ auto status = unserialize(data, data_str);
+ if (status.is_error()) {
+ return std::move(status);
+ }
+ return std::move(data);
+ }
+
+ static Result<Id> get_id(SqliteKeyValue &pmc, const string &key) TD_WARN_UNUSED_RESULT {
+ auto id_str = pmc.get(key);
+ //LOG(DEBUG) << "Found id " << id_str << " by key " << format::as_hex_dump<4>(Slice(key));
+ if (id_str.empty()) {
+ return Status::Error("There is no such a key in db");
+ }
+ return to_integer<Id>(id_str);
+ }
+};
+
+std::shared_ptr<FileDbInterface> create_file_db(std::shared_ptr<SqliteConnectionSafe> connection, int scheduler_id) {
+ auto kv = std::make_shared<SqliteKeyValueSafe>("files", std::move(connection));
+ return std::make_shared<FileDb>(std::move(kv), scheduler_id);
+}
+
+Status fix_file_remote_location_key_bug(SqliteDb &db) {
+ static const int32 OLD_KEY_MAGIC = 0x64378433;
+ SqliteKeyValue kv;
+ kv.init_with_connection(db.clone(), "files").ensure();
+ auto ptr = StackAllocator::alloc(4);
+ MutableSlice prefix = ptr.as_slice();
+ TlStorerUnsafe(prefix.begin()).store_int(OLD_KEY_MAGIC);
+ kv.get_by_prefix(prefix, [&](Slice key, Slice value) {
+ CHECK(TlParser(key).fetch_int() == OLD_KEY_MAGIC);
+ auto remote_str = PSTRING() << key.substr(4, 4) << Slice("\0\0\0\0") << key.substr(8);
+ FullRemoteFileLocation remote;
+ if (unserialize(remote, remote_str).is_ok()) {
+ kv.set(as_key(remote), value);
+ }
+ LOG(DEBUG) << "ERASE " << format::as_hex_dump<4>(Slice(key));
+ kv.erase(key);
+ });
+ return Status::OK();
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileDb.h b/libs/tdlib/td/td/telegram/files/FileDb.h
new file mode 100644
index 0000000000..d53fb32091
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileDb.h
@@ -0,0 +1,75 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/files/FileLocation.h"
+
+#include "td/actor/PromiseFuture.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/Status.h"
+
+#include <memory>
+
+namespace td {
+class SqliteDb;
+class SqliteConnectionSafe;
+class SqliteKeyValue;
+} // namespace td
+
+namespace td {
+Status drop_file_db(SqliteDb &db, int32 version) TD_WARN_UNUSED_RESULT;
+Status init_file_db(SqliteDb &db, int32 version) TD_WARN_UNUSED_RESULT;
+
+class FileDbInterface;
+std::shared_ptr<FileDbInterface> create_file_db(std::shared_ptr<SqliteConnectionSafe> connection,
+ int32 scheduler_id = -1) TD_WARN_UNUSED_RESULT;
+
+using FileDbId = uint64;
+
+class FileDbInterface {
+ public:
+ using Id = FileDbId;
+ FileDbInterface() = default;
+ FileDbInterface(const FileDbInterface &) = delete;
+ FileDbInterface &operator=(const FileDbInterface &) = delete;
+ virtual ~FileDbInterface() = default;
+
+ // non thread safe
+ virtual Id create_pmc_id() = 0;
+
+ // thread safe
+ virtual void close(Promise<> promise) = 0;
+ template <class LocationT>
+ void get_file_data(const LocationT &location, Promise<FileData> promise) {
+ get_file_data(as_key(location), std::move(promise));
+ }
+
+ template <class LocationT>
+ Result<FileData> get_file_data_sync(const LocationT &location) {
+ auto res = get_file_data_sync_impl(as_key(location));
+ if (res.is_ok()) {
+ LOG(DEBUG) << "GET " << location << " " << res.ok();
+ } else {
+ LOG(DEBUG) << "GET " << location << " " << res.error();
+ }
+ return res;
+ }
+
+ virtual void clear_file_data(Id id, const FileData &file_data) = 0;
+ virtual void set_file_data(Id id, const FileData &file_data, bool new_remote, bool new_local, bool new_generate) = 0;
+ virtual void set_file_data_ref(Id id, Id new_id) = 0;
+
+ // For FileStatsWorker. TODO: remove it
+ virtual SqliteKeyValue &pmc() = 0;
+
+ private:
+ virtual void get_file_data_impl(string key, Promise<FileData> promise) = 0;
+ virtual Result<FileData> get_file_data_sync_impl(string key) = 0;
+};
+;
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileDownloader.cpp b/libs/tdlib/td/td/telegram/files/FileDownloader.cpp
new file mode 100644
index 0000000000..29180dd701
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileDownloader.cpp
@@ -0,0 +1,461 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileDownloader.h"
+
+#include "td/telegram/telegram_api.h"
+
+#include "td/telegram/files/FileLoaderUtils.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/UniqueId.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/Slice.h"
+
+#include <tuple>
+
+namespace td {
+
+FileDownloader::FileDownloader(const FullRemoteFileLocation &remote, const LocalFileLocation &local, int64 size,
+ string name, const FileEncryptionKey &encryption_key, bool is_small, bool search_file,
+ std::unique_ptr<Callback> callback)
+ : remote_(remote)
+ , local_(local)
+ , size_(size)
+ , name_(std::move(name))
+ , encryption_key_(encryption_key)
+ , callback_(std::move(callback))
+ , is_small_(is_small)
+ , search_file_(search_file) {
+ if (!encryption_key.empty()) {
+ set_ordered_flag(true);
+ }
+}
+
+Result<FileLoader::FileInfo> FileDownloader::init() {
+ SCOPE_EXIT {
+ try_release_fd();
+ };
+ if (local_.type() == LocalFileLocation::Type::Full) {
+ return Status::Error("File is already downloaded");
+ }
+ int ready_part_count = 0;
+ int32 part_size = 0;
+ if (local_.type() == LocalFileLocation::Type::Partial) {
+ const auto &partial = local_.partial();
+ path_ = partial.path_;
+ auto result_fd = FileFd::open(path_, FileFd::Write | FileFd::Read);
+ // TODO: check timestamps..
+ if (result_fd.is_ok()) {
+ if (!encryption_key_.empty()) {
+ CHECK(partial.iv_.size() == 32) << partial.iv_.size();
+ encryption_key_.mutable_iv() = as<UInt256>(partial.iv_.data());
+ next_part_ = partial.ready_part_count_;
+ }
+ fd_ = result_fd.move_as_ok();
+ part_size = partial.part_size_;
+ ready_part_count = partial.ready_part_count_;
+ }
+ }
+ if (search_file_ && fd_.empty() && size_ > 0 && size_ < 1000 * (1 << 20) && encryption_key_.empty() &&
+ !remote_.is_web()) {
+ [&] {
+ TRY_RESULT(path, search_file(get_files_dir(remote_.file_type_), name_, size_));
+ TRY_RESULT(fd, FileFd::open(path, FileFd::Read));
+ LOG(INFO) << "Check hash of local file " << path;
+ path_ = std::move(path);
+ fd_ = std::move(fd);
+ need_check_ = true;
+ only_check_ = true;
+ part_size = 32 * (1 << 10);
+ ready_part_count = narrow_cast<int>((size_ + part_size - 1) / part_size);
+ return Status::OK();
+ }();
+ }
+
+ std::vector<int> parts(ready_part_count);
+ for (int i = 0; i < ready_part_count; i++) {
+ parts[i] = i;
+ }
+
+ FileInfo res;
+ res.size = size_;
+ res.is_size_final = true;
+ res.part_size = part_size;
+ res.ready_parts = std::move(parts);
+ res.use_part_count_limit = false;
+ res.only_check = only_check_;
+ res.need_delay = !is_small_ && (remote_.file_type_ == FileType::VideoNote ||
+ remote_.file_type_ == FileType::VoiceNote || remote_.file_type_ == FileType::Audio ||
+ remote_.file_type_ == FileType::Video || remote_.file_type_ == FileType::Animation ||
+ (remote_.file_type_ == FileType::Encrypted && size_ > (1 << 20)));
+ return res;
+}
+Status FileDownloader::on_ok(int64 size) {
+ auto dir = get_files_dir(remote_.file_type_);
+
+ std::string path;
+ if (only_check_) {
+ path = path_;
+ } else {
+ TRY_RESULT(perm_path, create_from_temp(path_, dir, name_));
+ path = std::move(perm_path);
+ }
+ fd_.close();
+ callback_->on_ok(FullLocalFileLocation(remote_.file_type_, std::move(path), 0), size);
+ return Status::OK();
+}
+void FileDownloader::on_error(Status status) {
+ fd_.close();
+ callback_->on_error(std::move(status));
+}
+
+Result<bool> FileDownloader::should_restart_part(Part part, NetQueryPtr &net_query) {
+ // Check if we should use CDN or reupload file to CDN
+
+ if (net_query->is_error()) {
+ if (net_query->error().message() == "FILE_TOKEN_INVALID") {
+ use_cdn_ = false;
+ return true;
+ }
+ if (net_query->error().message() == "REQUEST_TOKEN_INVALID") {
+ return true;
+ }
+ return false;
+ }
+
+ switch (narrow_cast<QueryType>(UniqueId::extract_key(net_query->id()))) {
+ case QueryType::Default: {
+ if (net_query->ok_tl_constructor() == telegram_api::upload_fileCdnRedirect::ID) {
+ LOG(DEBUG) << part.id << " got REDIRECT";
+ TRY_RESULT(file_base, fetch_result<telegram_api::upload_getFile>(net_query->ok()));
+ CHECK(file_base->get_id() == telegram_api::upload_fileCdnRedirect::ID);
+ auto file = move_tl_object_as<telegram_api::upload_fileCdnRedirect>(file_base);
+
+ auto new_cdn_file_token = file->file_token_.as_slice();
+ if (cdn_file_token_ == new_cdn_file_token) {
+ return true;
+ }
+
+ use_cdn_ = true;
+ need_check_ = true;
+ cdn_file_token_generation_++;
+ cdn_file_token_ = new_cdn_file_token.str();
+ cdn_dc_id_ = DcId::external(file->dc_id_);
+ cdn_encryption_key_ = file->encryption_key_.as_slice().str();
+ cdn_encryption_iv_ = file->encryption_iv_.as_slice().str();
+ add_hash_info(file->file_hashes_);
+ if (cdn_encryption_iv_.size() != 16 || cdn_encryption_key_.size() != 32) {
+ return Status::Error("Wrong ctr key or iv size");
+ }
+
+ return true;
+ }
+ return false;
+ }
+ case QueryType::ReuploadCDN: {
+ TRY_RESULT(file_hashes, fetch_result<telegram_api::upload_reuploadCdnFile>(net_query->ok()));
+ add_hash_info(file_hashes);
+ LOG(DEBUG) << part.id << " got REUPLOAD_OK";
+ return true;
+ }
+ case QueryType::CDN: {
+ if (net_query->ok_tl_constructor() == telegram_api::upload_cdnFileReuploadNeeded::ID) {
+ LOG(DEBUG) << part.id << " got REUPLOAD";
+ TRY_RESULT(file_base, fetch_result<telegram_api::upload_getCdnFile>(net_query->ok()));
+ CHECK(file_base->get_id() == telegram_api::upload_cdnFileReuploadNeeded::ID);
+ auto file = move_tl_object_as<telegram_api::upload_cdnFileReuploadNeeded>(file_base);
+ cdn_part_reupload_token_[part.id] = file->request_token_.as_slice().str();
+ return true;
+ }
+ auto it = cdn_part_file_token_generation_.find(part.id);
+ CHECK(it != cdn_part_file_token_generation_.end());
+ if (it->second != cdn_file_token_generation_) {
+ LOG(DEBUG) << part.id << " got part with old file_token";
+ return true;
+ }
+ return false;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ return false;
+}
+Result<std::pair<NetQueryPtr, bool>> FileDownloader::start_part(Part part, int32 part_count) {
+ if (!encryption_key_.empty()) {
+ part.size = (part.size + 15) & ~15; // fix for last part
+ }
+ // auto size = part.size;
+ //// sometimes we can ask more than server has, just to check size
+ // if (size < get_part_size()) {
+ // size = min(size + 16, get_part_size());
+ // LOG(INFO) << "Ask " << size << " instead of " << part.size;
+ //}
+ auto size = get_part_size();
+ CHECK(part.size <= size);
+
+ callback_->on_start_download();
+
+ NetQueryPtr net_query;
+ if (!use_cdn_) {
+ net_query = G()->net_query_creator().create(
+ UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::Default)),
+ remote_.is_web()
+ ? create_storer(telegram_api::upload_getWebFile(remote_.as_input_web_file_location(),
+ static_cast<int32>(part.offset), static_cast<int32>(size)))
+ : create_storer(telegram_api::upload_getFile(remote_.as_input_file_location(),
+ static_cast<int32>(part.offset), static_cast<int32>(size))),
+ remote_.get_dc_id(), is_small_ ? NetQuery::Type::DownloadSmall : NetQuery::Type::Download);
+ } else {
+ if (remote_.is_web()) {
+ return Status::Error("Can't download web file from CDN");
+ }
+ auto it = cdn_part_reupload_token_.find(part.id);
+ if (it == cdn_part_reupload_token_.end()) {
+ auto query = telegram_api::upload_getCdnFile(BufferSlice(cdn_file_token_), static_cast<int32>(part.offset),
+ static_cast<int32>(size));
+ cdn_part_file_token_generation_[part.id] = cdn_file_token_generation_;
+ LOG(DEBUG) << part.id << " " << to_string(query);
+ net_query = G()->net_query_creator().create(
+ UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::CDN)), create_storer(query), cdn_dc_id_,
+ is_small_ ? NetQuery::Type::DownloadSmall : NetQuery::Type::Download, NetQuery::AuthFlag::Off);
+ } else {
+ auto query = telegram_api::upload_reuploadCdnFile(BufferSlice(cdn_file_token_), BufferSlice(it->second));
+ LOG(DEBUG) << part.id << " " << to_string(query);
+ net_query = G()->net_query_creator().create(
+ UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::ReuploadCDN)), create_storer(query),
+ remote_.get_dc_id(), is_small_ ? NetQuery::Type::DownloadSmall : NetQuery::Type::Download,
+ NetQuery::AuthFlag::On);
+ cdn_part_reupload_token_.erase(it);
+ }
+ }
+ net_query->file_type_ = narrow_cast<int32>(remote_.file_type_);
+ return std::make_pair(std::move(net_query), false);
+}
+
+Result<size_t> FileDownloader::process_part(Part part, NetQueryPtr net_query) {
+ if (net_query->is_error()) {
+ return std::move(net_query->error());
+ }
+
+ BufferSlice bytes;
+ bool need_cdn_decrypt = false;
+ auto query_type = narrow_cast<QueryType>(UniqueId::extract_key(net_query->id()));
+ switch (query_type) {
+ case QueryType::Default: {
+ if (remote_.is_web()) {
+ TRY_RESULT(file, fetch_result<telegram_api::upload_getWebFile>(net_query->ok()));
+ bytes = std::move(file->bytes_);
+ } else {
+ TRY_RESULT(file_base, fetch_result<telegram_api::upload_getFile>(net_query->ok()));
+ CHECK(file_base->get_id() == telegram_api::upload_file::ID);
+ auto file = move_tl_object_as<telegram_api::upload_file>(file_base);
+ LOG(DEBUG) << part.id << " upload_getFile result";
+ bytes = std::move(file->bytes_);
+ }
+ break;
+ }
+ case QueryType::CDN: {
+ TRY_RESULT(file_base, fetch_result<telegram_api::upload_getCdnFile>(net_query->ok()));
+ CHECK(file_base->get_id() == telegram_api::upload_cdnFile::ID);
+ auto file = move_tl_object_as<telegram_api::upload_cdnFile>(file_base);
+ LOG(DEBUG) << part.id << " upload_getCdnFile result";
+ bytes = std::move(file->bytes_);
+ need_cdn_decrypt = true;
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ auto padded_size = part.size;
+ if (!encryption_key_.empty()) {
+ padded_size = (part.size + 15) & ~15;
+ }
+ LOG(INFO) << "Got " << bytes.size() << " padded_size=" << padded_size;
+ if (bytes.size() > padded_size) {
+ return Status::Error("Part size is more than requested");
+ }
+ if (bytes.empty()) {
+ return 0;
+ }
+
+ // Encryption
+ if (need_cdn_decrypt) {
+ auto iv = as<UInt128>(cdn_encryption_iv_.c_str());
+ CHECK(part.offset % 16 == 0);
+ auto offset = narrow_cast<uint32>(part.offset / 16);
+ offset =
+ ((offset & 0xff) << 24) | ((offset & 0xff00) << 8) | ((offset & 0xff0000) >> 8) | ((offset & 0xff000000) >> 24);
+ as<uint32>(iv.raw + 12) = offset;
+ auto key = as<UInt256>(cdn_encryption_key_.c_str());
+
+ AesCtrState ctr_state;
+ ctr_state.init(key, iv);
+ ctr_state.decrypt(bytes.as_slice(), bytes.as_slice());
+ }
+ if (!encryption_key_.empty()) {
+ CHECK(next_part_ == part.id) << tag("expected part.id", next_part_) << "!=" << tag("part.id", part.id);
+ CHECK(!next_part_stop_);
+ next_part_++;
+ if (part.size % 16 != 0) {
+ next_part_stop_ = true;
+ }
+ aes_ige_decrypt(encryption_key_.key(), &encryption_key_.mutable_iv(), bytes.as_slice(), bytes.as_slice());
+ }
+
+ auto slice = bytes.as_slice().truncate(part.size);
+ TRY_STATUS(acquire_fd());
+ TRY_RESULT(written, fd_.pwrite(slice, part.offset));
+ // may write less than part.size, when size of downloadable file is unknown
+ if (written != slice.size()) {
+ return Status::Error("Failed to save file part to the file");
+ }
+ return written;
+}
+void FileDownloader::on_progress(int32 part_count, int32 part_size, int32 ready_part_count, bool is_ready,
+ int64 ready_size) {
+ if (is_ready) {
+ // do not send partial location. will lead to wrong local_size
+ return;
+ }
+ if (ready_size == 0 || path_.empty()) {
+ return;
+ }
+ if (encryption_key_.empty()) {
+ callback_->on_partial_download(PartialLocalFileLocation{remote_.file_type_, path_, part_size, ready_part_count, ""},
+ ready_size);
+ } else {
+ UInt256 iv;
+ if (ready_part_count == next_part_) {
+ iv = encryption_key_.mutable_iv();
+ } else {
+ LOG(FATAL) << tag("ready_part_count", ready_part_count) << tag("next_part", next_part_);
+ }
+ callback_->on_partial_download(PartialLocalFileLocation{remote_.file_type_, path_, part_size, ready_part_count,
+ Slice(iv.raw, sizeof(iv)).str()},
+ ready_size);
+ }
+}
+
+FileLoader::Callback *FileDownloader::get_callback() {
+ return static_cast<FileLoader::Callback *>(callback_.get());
+}
+
+Status FileDownloader::process_check_query(NetQueryPtr net_query) {
+ has_hash_query_ = false;
+ TRY_RESULT(file_hashes, fetch_result<telegram_api::upload_getCdnFileHashes>(std::move(net_query)));
+ add_hash_info(file_hashes);
+ return Status::OK();
+}
+Result<FileLoader::CheckInfo> FileDownloader::check_loop(int64 checked_prefix_size, int64 ready_prefix_size,
+ bool is_ready) {
+ if (!need_check_) {
+ return CheckInfo{};
+ }
+ SCOPE_EXIT {
+ try_release_fd();
+ };
+ CheckInfo info;
+ while (checked_prefix_size < ready_prefix_size) {
+ //LOG(ERROR) << "NEED TO CHECK: " << checked_prefix_size << "->" << ready_prefix_size - checked_prefix_size;
+ HashInfo search_info;
+ search_info.offset = checked_prefix_size;
+ auto it = hash_info_.upper_bound(search_info);
+ if (it != hash_info_.begin()) {
+ it--;
+ }
+ if (it != hash_info_.end() && it->offset <= checked_prefix_size &&
+ it->offset + narrow_cast<int64>(it->size) > checked_prefix_size) {
+ int64 begin_offset = it->offset;
+ int64 end_offset = it->offset + narrow_cast<int64>(it->size);
+ if (ready_prefix_size < end_offset) {
+ if (!is_ready) {
+ break;
+ }
+ end_offset = ready_prefix_size;
+ }
+ size_t size = narrow_cast<size_t>(end_offset - begin_offset);
+ auto slice = BufferSlice(size);
+ TRY_STATUS(acquire_fd());
+ TRY_RESULT(read_size, fd_.pread(slice.as_slice(), begin_offset));
+ if (size != read_size) {
+ return Status::Error("Failed to read file to check hash");
+ }
+ string hash(32, ' ');
+ sha256(slice.as_slice(), hash);
+
+ if (hash != it->hash) {
+ if (only_check_) {
+ return Status::Error("FILE_DOWNLOAD_RESTART");
+ }
+ return Status::Error("Hash mismatch");
+ }
+
+ checked_prefix_size = end_offset;
+ info.changed = true;
+ continue;
+ }
+ if (!has_hash_query_) {
+ has_hash_query_ = true;
+ auto query =
+ telegram_api::upload_getFileHashes(remote_.as_input_file_location(), narrow_cast<int32>(checked_prefix_size));
+ auto net_query = G()->net_query_creator().create(
+ create_storer(query), remote_.get_dc_id(),
+ is_small_ ? NetQuery::Type::DownloadSmall : NetQuery::Type::Download, NetQuery::AuthFlag::On);
+ info.queries.push_back(std::move(net_query));
+ break;
+ }
+ // Should fail?
+ break;
+ }
+ info.need_check = need_check_;
+ info.checked_prefix_size = checked_prefix_size;
+ return std::move(info);
+}
+void FileDownloader::add_hash_info(const std::vector<telegram_api::object_ptr<telegram_api::fileHash>> &hashes) {
+ for (auto &hash : hashes) {
+ //LOG(ERROR) << "ADD HASH " << hash->offset_ << "->" << hash->limit_;
+ HashInfo hash_info;
+ hash_info.size = hash->limit_;
+ hash_info.offset = hash->offset_;
+ hash_info.hash = hash->hash_.as_slice().str();
+ hash_info_.insert(std::move(hash_info));
+ }
+}
+
+void FileDownloader::keep_fd_flag(bool keep_fd) {
+ keep_fd_ = keep_fd;
+ try_release_fd();
+}
+
+void FileDownloader::try_release_fd() {
+ if (!keep_fd_ && !fd_.empty()) {
+ fd_.close();
+ }
+}
+
+Status FileDownloader::acquire_fd() {
+ if (fd_.empty()) {
+ if (path_.empty()) {
+ TRY_RESULT(file_path, open_temp_file(remote_.file_type_));
+ std::tie(fd_, path_) = std::move(file_path);
+ } else {
+ TRY_RESULT(fd, FileFd::open(path_, (only_check_ ? 0 : FileFd::Write) | FileFd::Read));
+ fd_ = std::move(fd);
+ }
+ }
+ return Status::OK();
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileDownloader.h b/libs/tdlib/td/td/telegram/files/FileDownloader.h
new file mode 100644
index 0000000000..6a45ca567b
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileDownloader.h
@@ -0,0 +1,100 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/telegram_api.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/telegram/files/FileLoader.h"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/net/NetQuery.h"
+
+#include "td/utils/common.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/Status.h"
+
+#include <map>
+#include <set>
+#include <utility>
+
+namespace td {
+class FileDownloader : public FileLoader {
+ public:
+ class Callback : public FileLoader::Callback {
+ public:
+ virtual void on_start_download() = 0;
+ virtual void on_partial_download(const PartialLocalFileLocation &partial_local, int64 ready_size) = 0;
+ virtual void on_ok(const FullLocalFileLocation &full_local, int64 size) = 0;
+ virtual void on_error(Status status) = 0;
+ };
+
+ FileDownloader(const FullRemoteFileLocation &remote, const LocalFileLocation &local, int64 size, string name,
+ const FileEncryptionKey &encryption_key, bool is_small, bool search_file,
+ std::unique_ptr<Callback> callback);
+
+ // Should just implement all parent pure virtual methods.
+ // Must not call any of them...
+ private:
+ enum class QueryType : uint8 { Default = 1, CDN, ReuploadCDN };
+ ResourceState resource_state_;
+ FullRemoteFileLocation remote_;
+ LocalFileLocation local_;
+ int64 size_;
+ string name_;
+ FileEncryptionKey encryption_key_;
+ std::unique_ptr<Callback> callback_;
+ bool only_check_{false};
+
+ string path_;
+ FileFd fd_;
+
+ int32 next_part_ = 0;
+ bool next_part_stop_ = false;
+ bool is_small_;
+ bool search_file_{false};
+
+ bool use_cdn_ = false;
+ DcId cdn_dc_id_;
+ string cdn_encryption_key_;
+ string cdn_encryption_iv_;
+ string cdn_file_token_;
+ int32 cdn_file_token_generation_{0};
+ std::map<int32, string> cdn_part_reupload_token_;
+ std::map<int32, int32> cdn_part_file_token_generation_;
+
+ bool need_check_{false};
+ struct HashInfo {
+ int64 offset;
+ size_t size;
+ string hash;
+ bool operator<(const HashInfo &other) const {
+ return offset < other.offset;
+ }
+ };
+ std::set<HashInfo> hash_info_;
+ bool has_hash_query_ = false;
+
+ Result<FileInfo> init() override TD_WARN_UNUSED_RESULT;
+ Status on_ok(int64 size) override TD_WARN_UNUSED_RESULT;
+ void on_error(Status status) override;
+ Result<bool> should_restart_part(Part part, NetQueryPtr &net_query) override TD_WARN_UNUSED_RESULT;
+ Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int32 part_count) override TD_WARN_UNUSED_RESULT;
+ Result<size_t> process_part(Part part, NetQueryPtr net_query) override TD_WARN_UNUSED_RESULT;
+ void on_progress(int32 part_count, int32 part_size, int32 ready_part_count, bool is_ready, int64 ready_size) override;
+ FileLoader::Callback *get_callback() override;
+ Status process_check_query(NetQueryPtr net_query) override;
+ Result<CheckInfo> check_loop(int64 checked_prefix_size, int64 ready_prefix_size, bool is_ready) override;
+ void add_hash_info(const std::vector<telegram_api::object_ptr<telegram_api::fileHash>> &hashes);
+
+ bool keep_fd_ = false;
+ void keep_fd_flag(bool keep_fd) override;
+ void try_release_fd();
+ Status acquire_fd() TD_WARN_UNUSED_RESULT;
+};
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileFromBytes.cpp b/libs/tdlib/td/td/telegram/files/FileFromBytes.cpp
new file mode 100644
index 0000000000..939803d8a8
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileFromBytes.cpp
@@ -0,0 +1,50 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileFromBytes.h"
+
+#include "td/telegram/files/FileLoaderUtils.h"
+#include "td/telegram/Global.h"
+
+#include "td/utils/common.h"
+#include "td/utils/misc.h"
+
+#include <tuple>
+
+namespace td {
+
+FileFromBytes::FileFromBytes(FileType type, BufferSlice bytes, string name, std::unique_ptr<Callback> callback)
+ : type_(type), bytes_(std::move(bytes)), name_(std::move(name)), callback_(std::move(callback)) {
+}
+
+void FileFromBytes::wakeup() {
+ auto r_fd_path = open_temp_file(type_);
+ if (r_fd_path.is_error()) {
+ return callback_->on_error(r_fd_path.move_as_error());
+ }
+ FileFd fd;
+ string path;
+ std::tie(fd, path) = r_fd_path.move_as_ok();
+
+ auto r_size = fd.write(bytes_.as_slice());
+ if (r_size.is_error()) {
+ return callback_->on_error(r_size.move_as_error());
+ }
+ fd.close();
+ auto size = r_size.ok();
+ if (size != bytes_.size()) {
+ return callback_->on_error(Status::Error("Failed to write bytes to the file"));
+ }
+
+ auto dir = get_files_dir(type_);
+ auto r_perm_path = create_from_temp(path, dir, name_);
+ if (r_perm_path.is_error()) {
+ return callback_->on_error(r_perm_path.move_as_error());
+ }
+ callback_->on_ok(FullLocalFileLocation(type_, r_perm_path.move_as_ok(), 0), narrow_cast<int64>(bytes_.size()));
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileFromBytes.h b/libs/tdlib/td/td/telegram/files/FileFromBytes.h
new file mode 100644
index 0000000000..23f4b2ff4f
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileFromBytes.h
@@ -0,0 +1,54 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/actor/actor.h"
+
+#include "td/telegram/files/FileLoader.h"
+#include "td/telegram/files/FileLocation.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/Status.h"
+
+namespace td {
+class FileFromBytes : public FileLoaderActor {
+ public:
+ class Callback {
+ public:
+ Callback() = default;
+ Callback(const Callback &) = delete;
+ Callback &operator=(const Callback &) = delete;
+ virtual ~Callback() = default;
+ virtual void on_ok(const FullLocalFileLocation &full_local, int64 size) = 0;
+ virtual void on_error(Status status) = 0;
+ };
+
+ FileFromBytes(FileType type, BufferSlice bytes, string name, std::unique_ptr<Callback> callback);
+
+ // Should just implement all parent pure virtual methods.
+ // Must not call any of them...
+ private:
+ FileType type_;
+ BufferSlice bytes_;
+ string name_;
+
+ std::unique_ptr<Callback> callback_;
+
+ FileFd fd_;
+ string path_;
+
+ void wakeup() override;
+ void set_resource_manager(ActorShared<ResourceManager>) override {
+ }
+ void update_priority(int8 priority) override {
+ }
+ void update_resources(const ResourceState &other) override {
+ }
+};
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileGcParameters.cpp b/libs/tdlib/td/td/telegram/files/FileGcParameters.cpp
new file mode 100644
index 0000000000..7b949d73a2
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileGcParameters.cpp
@@ -0,0 +1,32 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileGcParameters.h"
+
+#include "td/telegram/ConfigShared.h"
+#include "td/telegram/Global.h"
+
+namespace td {
+FileGcParameters::FileGcParameters(int64 size, int32 ttl, int32 count, int32 immunity_delay,
+ vector<FileType> file_types, vector<DialogId> owner_dialog_ids,
+ vector<DialogId> exclude_owner_dialog_ids, int32 dialog_limit)
+ : file_types(std::move(file_types))
+ , owner_dialog_ids(std::move(owner_dialog_ids))
+ , exclude_owner_dialog_ids(std::move(exclude_owner_dialog_ids))
+ , dialog_limit(dialog_limit) {
+ auto &config = G()->shared_config();
+ this->max_files_size =
+ size >= 0 ? size : static_cast<int64>(config.get_option_integer("storage_max_files_size", 100 << 10)) << 10;
+
+ this->max_time_from_last_access =
+ ttl >= 0 ? ttl : config.get_option_integer("storage_max_time_from_last_access", 60 * 60 * 23);
+
+ this->max_file_count = count >= 0 ? count : config.get_option_integer("storage_max_file_count", 40000);
+
+ this->immunity_delay =
+ immunity_delay >= 0 ? immunity_delay : config.get_option_integer("storage_immunity_delay", 60 * 60);
+}
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileGcParameters.h b/libs/tdlib/td/td/telegram/files/FileGcParameters.h
new file mode 100644
index 0000000000..a0c3c35524
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileGcParameters.h
@@ -0,0 +1,34 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileLocation.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+
+struct FileGcParameters {
+ FileGcParameters() : FileGcParameters(-1, -1, -1, -1, {}, {}, {}, 0) {
+ }
+ FileGcParameters(int64 size, int32 ttl, int32 count, int32 immunity_delay, vector<FileType> file_types,
+ vector<DialogId> owner_dialog_ids, vector<DialogId> exclude_owner_dialog_ids, int32 dialog_limit);
+
+ int64 max_files_size;
+ uint32 max_time_from_last_access;
+ uint32 max_file_count;
+ uint32 immunity_delay;
+
+ vector<FileType> file_types;
+ vector<DialogId> owner_dialog_ids;
+ vector<DialogId> exclude_owner_dialog_ids;
+
+ int32 dialog_limit;
+};
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileGcWorker.cpp b/libs/tdlib/td/td/telegram/files/FileGcWorker.cpp
new file mode 100644
index 0000000000..35dce38e9c
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileGcWorker.cpp
@@ -0,0 +1,174 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileGcWorker.h"
+
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/Global.h"
+
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/Clocks.h"
+#include "td/utils/port/path.h"
+#include "td/utils/Time.h"
+
+#include <algorithm>
+#include <array>
+
+namespace td {
+void FileGcWorker::do_remove_file(const FullFileInfo &info) {
+ // LOG(WARNING) << "Gc remove file: " << tag("path", file) << tag("mtime", stat.mtime_nsec_ / 1000000000)
+ // << tag("atime", stat.atime_nsec_ / 1000000000);
+ // TODO: remove file from db too.
+ auto status = unlink(info.path);
+ LOG_IF(WARNING, status.is_error()) << "Failed to unlink file during files gc: " << status;
+ send_closure(G()->file_manager(), &FileManager::on_file_unlink,
+ FullLocalFileLocation(info.file_type, info.path, info.mtime_nsec));
+}
+
+void FileGcWorker::run_gc(const FileGcParameters &parameters, std::vector<FullFileInfo> files,
+ Promise<FileStats> promise) {
+ auto begin_time = Time::now();
+ LOG(INFO) << "Start files gc";
+ // quite stupid implementations
+ // needs a lot of memory
+ // may write something more clever, but i will need at least 2 passes over the files
+ // TODO update atime for all files in android (?)
+
+ std::array<bool, file_type_size> immune_types{{false}};
+
+ if (G()->parameters().use_file_db) {
+ // immune by default
+ immune_types[narrow_cast<size_t>(FileType::Sticker)] = true;
+ immune_types[narrow_cast<size_t>(FileType::ProfilePhoto)] = true;
+ immune_types[narrow_cast<size_t>(FileType::Thumbnail)] = true;
+ immune_types[narrow_cast<size_t>(FileType::Wallpaper)] = true;
+ }
+
+ if (!parameters.file_types.empty()) {
+ std::fill(immune_types.begin(), immune_types.end(), true);
+ for (auto file_type : parameters.file_types) {
+ immune_types[narrow_cast<size_t>(file_type)] = false;
+ }
+ }
+
+ if (G()->parameters().use_file_db) {
+ immune_types[narrow_cast<size_t>(FileType::EncryptedThumbnail)] = true;
+ }
+
+ auto file_cnt = files.size();
+ int32 type_immunity_ignored_cnt = 0;
+ int32 time_immunity_ignored_cnt = 0;
+ int32 exclude_owner_dialog_id_ignored_cnt = 0;
+ int32 owner_dialog_id_ignored_cnt = 0;
+ int32 remove_by_atime_cnt = 0;
+ int32 remove_by_count_cnt = 0;
+ int32 remove_by_size_cnt = 0;
+ int64 total_removed_size = 0;
+ int64 total_size = 0;
+ for (auto &info : files) {
+ if (info.atime_nsec < info.mtime_nsec) {
+ info.atime_nsec = info.mtime_nsec;
+ }
+ total_size += info.size;
+ }
+
+ FileStats new_stats;
+ new_stats.split_by_owner_dialog_id = parameters.dialog_limit != 0;
+
+ // Remove all files with atime > now - max_time_from_last_access
+ double now = Clocks::system();
+ files.erase(
+ std::remove_if(
+ files.begin(), files.end(),
+ [&](const FullFileInfo &info) {
+ if (immune_types[narrow_cast<size_t>(info.file_type)]) {
+ type_immunity_ignored_cnt++;
+ new_stats.add(FullFileInfo(info));
+ return true;
+ }
+ if (std::find(parameters.exclude_owner_dialog_ids.begin(), parameters.exclude_owner_dialog_ids.end(),
+ info.owner_dialog_id) != parameters.exclude_owner_dialog_ids.end()) {
+ exclude_owner_dialog_id_ignored_cnt++;
+ new_stats.add(FullFileInfo(info));
+ return true;
+ }
+ if (!parameters.owner_dialog_ids.empty() &&
+ std::find(parameters.owner_dialog_ids.begin(), parameters.owner_dialog_ids.end(),
+ info.owner_dialog_id) == parameters.owner_dialog_ids.end()) {
+ owner_dialog_id_ignored_cnt++;
+ new_stats.add(FullFileInfo(info));
+ return true;
+ }
+ if (static_cast<double>(info.mtime_nsec / 1000000000) > now - parameters.immunity_delay) {
+ // new files are immune to gc.
+ time_immunity_ignored_cnt++;
+ new_stats.add(FullFileInfo(info));
+ return true;
+ }
+
+ if (static_cast<double>(info.atime_nsec / 1000000000) < now - parameters.max_time_from_last_access) {
+ do_remove_file(info);
+ total_removed_size += info.size;
+ remove_by_atime_cnt++;
+ return true;
+ }
+ return false;
+ }),
+ files.end());
+
+ // sort by max(atime, mtime)
+ std::sort(files.begin(), files.end(), [](const auto &a, const auto &b) { return a.atime_nsec < b.atime_nsec; });
+
+ // 1. Total memory must be less than max_memory
+ // 2. Total file count must be less than MAX_FILE_COUNT
+ size_t remove_count = 0;
+ if (files.size() > parameters.max_file_count) {
+ remove_count = files.size() - parameters.max_file_count;
+ }
+ int64 remove_size = -parameters.max_files_size;
+ for (auto &file : files) {
+ remove_size += file.size;
+ }
+
+ size_t pos = 0;
+ while (pos < files.size() && (remove_count > 0 || remove_size > 0)) {
+ if (remove_count > 0) {
+ remove_by_count_cnt++;
+ } else {
+ remove_by_size_cnt++;
+ }
+
+ if (remove_count > 0) {
+ remove_count--;
+ }
+ remove_size -= files[pos].size;
+
+ total_removed_size += files[pos].size;
+ do_remove_file(files[pos]);
+ pos++;
+ }
+
+ while (pos < files.size()) {
+ new_stats.add(std::move(files[pos]));
+ pos++;
+ }
+
+ auto end_time = Time::now();
+
+ LOG(INFO) << "Finish files gc: " << tag("time", end_time - begin_time) << tag("total", file_cnt)
+ << tag("removed", remove_by_atime_cnt + remove_by_count_cnt + remove_by_size_cnt)
+ << tag("total_size", format::as_size(total_size))
+ << tag("total_removed_size", format::as_size(total_removed_size)) << tag("by_atime", remove_by_atime_cnt)
+ << tag("by_count", remove_by_count_cnt) << tag("by_size", remove_by_size_cnt)
+ << tag("type_immunity", type_immunity_ignored_cnt) << tag("time_immunity", time_immunity_ignored_cnt)
+ << tag("owner_dialog_id_immunity", owner_dialog_id_ignored_cnt)
+ << tag("exclude_owner_dialog_id_immunity", exclude_owner_dialog_id_ignored_cnt);
+
+ promise.set_value(std::move(new_stats));
+}
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileGcWorker.h b/libs/tdlib/td/td/telegram/files/FileGcWorker.h
new file mode 100644
index 0000000000..7e83d9fa98
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileGcWorker.h
@@ -0,0 +1,28 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/actor/actor.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/telegram/files/FileGcParameters.h"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileStats.h"
+
+namespace td {
+class FileGcWorker : public Actor {
+ public:
+ explicit FileGcWorker(ActorShared<> parent) : parent_(std::move(parent)) {
+ }
+ void run_gc(const FileGcParameters &parameters, std::vector<FullFileInfo> files, Promise<FileStats> promise);
+
+ private:
+ ActorShared<> parent_;
+ void do_remove_file(const FullFileInfo &info);
+};
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileGenerateManager.cpp b/libs/tdlib/td/td/telegram/files/FileGenerateManager.cpp
new file mode 100644
index 0000000000..22472a248f
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileGenerateManager.cpp
@@ -0,0 +1,285 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileGenerateManager.h"
+
+#include "td/telegram/td_api.h"
+
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileLoaderUtils.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/Td.h"
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/path.h"
+#include "td/utils/Slice.h"
+
+#include <utility>
+
+namespace td {
+
+class FileGenerateActor : public Actor {
+ public:
+ FileGenerateActor() = default;
+ FileGenerateActor(const FileGenerateActor &) = delete;
+ FileGenerateActor &operator=(const FileGenerateActor &) = delete;
+ FileGenerateActor(FileGenerateActor &&) = delete;
+ FileGenerateActor &operator=(FileGenerateActor &&) = delete;
+ ~FileGenerateActor() override = default;
+ virtual void file_generate_progress(int32 expected_size, int32 local_prefix_size, Promise<> promise) = 0;
+ virtual void file_generate_finish(Status status, Promise<> promise) = 0;
+};
+
+class FileDownloadGenerateActor : public FileGenerateActor {
+ public:
+ FileDownloadGenerateActor(FileType file_type, FileId file_id, std::unique_ptr<FileGenerateCallback> callback,
+ ActorShared<> parent)
+ : file_type_(file_type), file_id_(file_id), callback_(std::move(callback)), parent_(std::move(parent)) {
+ }
+ void file_generate_progress(int32 expected_size, int32 local_prefix_size, Promise<> promise) override {
+ UNREACHABLE();
+ }
+ void file_generate_finish(Status status, Promise<> promise) override {
+ UNREACHABLE();
+ }
+
+ private:
+ FileType file_type_;
+ FileId file_id_;
+ std::unique_ptr<FileGenerateCallback> callback_;
+ ActorShared<> parent_;
+
+ void start_up() override {
+ LOG(INFO) << "DOWNLOAD " << file_id_;
+ class Callback : public FileManager::DownloadCallback {
+ public:
+ explicit Callback(ActorId<FileDownloadGenerateActor> parent) : parent_(std::move(parent)) {
+ }
+
+ // TODO: upload during download
+
+ void on_download_ok(FileId file_id) override {
+ send_closure(parent_, &FileDownloadGenerateActor::on_download_ok);
+ }
+ void on_download_error(FileId file_id, Status error) override {
+ send_closure(parent_, &FileDownloadGenerateActor::on_download_error, std::move(error));
+ }
+
+ private:
+ ActorId<FileDownloadGenerateActor> parent_;
+ };
+
+ send_closure(G()->file_manager(), &FileManager::download, file_id_, std::make_unique<Callback>(actor_id(this)), 1);
+ }
+ void hangup() override {
+ send_closure(G()->file_manager(), &FileManager::download, file_id_, nullptr, 0);
+ stop();
+ }
+
+ void on_download_ok() {
+ send_lambda(G()->file_manager(), [file_type = file_type_, file_id = file_id_, callback = std::move(callback_)] {
+ auto file_view = G()->td().get_actor_unsafe()->file_manager_->get_file_view(file_id);
+ if (file_view.has_local_location()) {
+ auto location = file_view.local_location();
+ location.file_type_ = file_type;
+ callback->on_ok(location);
+ } else {
+ LOG(ERROR) << "Expected to have local location";
+ callback->on_error(Status::Error("Unknown"));
+ }
+ });
+ stop();
+ }
+ void on_download_error(Status error) {
+ callback_->on_error(std::move(error));
+ stop();
+ }
+};
+
+class FileExternalGenerateActor : public FileGenerateActor {
+ public:
+ FileExternalGenerateActor(uint64 query_id, const FullGenerateFileLocation &generate_location,
+ const LocalFileLocation &local_location, string name,
+ std::unique_ptr<FileGenerateCallback> callback, ActorShared<> parent)
+ : query_id_(query_id)
+ , generate_location_(generate_location)
+ , local_(local_location)
+ , name_(std::move(name))
+ , callback_(std::move(callback))
+ , parent_(std::move(parent)) {
+ }
+
+ void file_generate_progress(int32 expected_size, int32 local_prefix_size, Promise<> promise) override {
+ check_status(do_file_generate_progress(expected_size, local_prefix_size), std::move(promise));
+ }
+ void file_generate_finish(Status status, Promise<> promise) override {
+ check_status(do_file_generate_finish(std::move(status)), std::move(promise));
+ }
+
+ private:
+ uint64 query_id_;
+ FullGenerateFileLocation generate_location_;
+ LocalFileLocation local_;
+ string name_;
+ string path_;
+ std::unique_ptr<FileGenerateCallback> callback_;
+ ActorShared<> parent_;
+
+ void start_up() override {
+ if (local_.type() == LocalFileLocation::Type::Full) {
+ callback_->on_ok(local_.full());
+ callback_.reset();
+ return stop();
+ }
+
+ if (local_.type() == LocalFileLocation::Type::Partial) {
+ const auto &partial = local_.partial();
+ path_ = partial.path_;
+ LOG(INFO) << "Unlink partially generated file at " << path_;
+ unlink(path_).ignore();
+ } else {
+ auto r_file_path = open_temp_file(generate_location_.file_type_);
+ if (r_file_path.is_error()) {
+ return check_status(r_file_path.move_as_error());
+ }
+ auto file_path = r_file_path.move_as_ok();
+ file_path.first.close();
+ path_ = file_path.second;
+ }
+ send_closure(
+ G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateFileGenerationStart>(
+ static_cast<int64>(query_id_), generate_location_.original_path_, path_, generate_location_.conversion_));
+ }
+ void hangup() override {
+ check_status(Status::Error(1, "Cancelled"));
+ }
+
+ Status do_file_generate_progress(int32 expected_size, int32 local_prefix_size) {
+ if (local_prefix_size < 0) {
+ return Status::Error(1, "Invalid local prefix size");
+ }
+ callback_->on_partial_generate(
+ PartialLocalFileLocation{generate_location_.file_type_, path_, 1, local_prefix_size, ""}, expected_size);
+ return Status::OK();
+ }
+
+ Status do_file_generate_finish(Status status) {
+ TRY_STATUS(std::move(status));
+
+ auto dir = get_files_dir(generate_location_.file_type_);
+
+ TRY_RESULT(perm_path, create_from_temp(path_, dir, name_));
+ callback_->on_ok(FullLocalFileLocation(generate_location_.file_type_, std::move(perm_path), 0));
+ callback_.reset();
+ stop();
+ return Status::OK();
+ }
+
+ void check_status(Status status, Promise<> promise = Promise<>()) {
+ if (promise) {
+ if (status.is_ok() || status.code() == 1) {
+ promise.set_value(Unit());
+ } else {
+ promise.set_error(Status::Error(400, status.message()));
+ }
+ }
+
+ if (status.is_error()) {
+ LOG(INFO) << "Unlink partially generated file at " << path_ << " because of " << status;
+ unlink(path_).ignore();
+ callback_->on_error(std::move(status));
+ callback_.reset();
+ stop();
+ }
+ }
+
+ void tear_down() override {
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateFileGenerationStop>(static_cast<int64>(query_id_)));
+ }
+};
+
+FileGenerateManager::Query::~Query() = default;
+FileGenerateManager::Query::Query(Query &&other) = default;
+FileGenerateManager::Query &FileGenerateManager::Query::operator=(Query &&other) = default;
+
+void FileGenerateManager::generate_file(uint64 query_id, const FullGenerateFileLocation &generate_location,
+ const LocalFileLocation &local_location, string name,
+ std::unique_ptr<FileGenerateCallback> callback) {
+ CHECK(query_id != 0);
+ auto it_flag = query_id_to_query_.insert(std::make_pair(query_id, Query{}));
+ CHECK(it_flag.second) << "Query id must be unique";
+ auto parent = actor_shared(this, query_id);
+
+ Slice file_id_query = "#file_id#";
+ Slice conversion = generate_location.conversion_;
+
+ auto &query = it_flag.first->second;
+ if (conversion.copy().truncate(file_id_query.size()) == file_id_query) {
+ auto file_id = FileId(to_integer<int32>(conversion.substr(file_id_query.size())), 0);
+ query.worker_ = create_actor<FileDownloadGenerateActor>("FileDownloadGenerateActor", generate_location.file_type_,
+ file_id, std::move(callback), std::move(parent));
+ } else {
+ query.worker_ = create_actor<FileExternalGenerateActor>("FileExternalGenerationActor", query_id, generate_location,
+ local_location, std::move(name), std::move(callback),
+ std::move(parent));
+ }
+}
+
+void FileGenerateManager::cancel(uint64 query_id) {
+ auto it = query_id_to_query_.find(query_id);
+ if (it == query_id_to_query_.end()) {
+ return;
+ }
+ it->second.worker_.reset();
+}
+
+void FileGenerateManager::external_file_generate_progress(uint64 query_id, int32 expected_size, int32 local_prefix_size,
+ Promise<> promise) {
+ auto it = query_id_to_query_.find(query_id);
+ if (it == query_id_to_query_.end()) {
+ return promise.set_error(Status::Error(400, "Unknown generation_id"));
+ }
+ send_closure(it->second.worker_, &FileGenerateActor::file_generate_progress, expected_size, local_prefix_size,
+ std::move(promise));
+}
+
+void FileGenerateManager::external_file_generate_finish(uint64 query_id, Status status, Promise<> promise) {
+ auto it = query_id_to_query_.find(query_id);
+ if (it == query_id_to_query_.end()) {
+ return promise.set_error(Status::Error(400, "Unknown generation_id"));
+ }
+ send_closure(it->second.worker_, &FileGenerateActor::file_generate_finish, std::move(status), std::move(promise));
+}
+
+void FileGenerateManager::do_cancel(uint64 query_id) {
+ query_id_to_query_.erase(query_id);
+}
+
+void FileGenerateManager::hangup_shared() {
+ do_cancel(get_link_token());
+ loop();
+}
+
+void FileGenerateManager::hangup() {
+ close_flag_ = true;
+ for (auto &it : query_id_to_query_) {
+ it.second.worker_.reset();
+ }
+ loop();
+}
+
+void FileGenerateManager::loop() {
+ if (close_flag_ && query_id_to_query_.empty()) {
+ stop();
+ }
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileGenerateManager.h b/libs/tdlib/td/td/telegram/files/FileGenerateManager.h
new file mode 100644
index 0000000000..4acd495d72
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileGenerateManager.h
@@ -0,0 +1,70 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/actor/actor.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/telegram/files/FileLocation.h"
+
+#include "td/utils/Status.h"
+
+#include <map>
+
+namespace td {
+class FileGenerateActor;
+
+class FileGenerateCallback {
+ public:
+ FileGenerateCallback() = default;
+ FileGenerateCallback(const FileGenerateCallback &) = delete;
+ FileGenerateCallback &operator=(const FileGenerateCallback &) = delete;
+ virtual ~FileGenerateCallback() = default;
+
+ virtual void on_partial_generate(const PartialLocalFileLocation &partial_local, int32 expected_size) = 0;
+ virtual void on_ok(const FullLocalFileLocation &local) = 0;
+ virtual void on_error(Status error) = 0;
+};
+
+class FileGenerateManager : public Actor {
+ public:
+ explicit FileGenerateManager(ActorShared<> parent) : parent_(std::move(parent)) {
+ }
+
+ void generate_file(uint64 query_id, const FullGenerateFileLocation &generate_location,
+ const LocalFileLocation &local_location, string name,
+ std::unique_ptr<FileGenerateCallback> callback);
+ void cancel(uint64 query_id);
+
+ // external updates about file generation state
+ void external_file_generate_progress(uint64 query_id, int32 expected_size, int32 local_prefix_size,
+ Promise<> promise);
+ void external_file_generate_finish(uint64 query_id, Status status, Promise<> promise);
+
+ private:
+ struct Query {
+ Query() = default;
+ Query(const Query &other) = delete;
+ Query &operator=(const Query &other) = delete;
+ Query(Query &&other);
+ Query &operator=(Query &&other);
+ ~Query();
+
+ ActorOwn<FileGenerateActor> worker_;
+ };
+
+ ActorShared<> parent_;
+ std::map<uint64, Query> query_id_to_query_;
+ bool close_flag_ = false;
+
+ void hangup() override;
+ void hangup_shared() override;
+ void loop() override;
+ void do_cancel(uint64 query_id);
+};
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileHashUploader.cpp b/libs/tdlib/td/td/telegram/files/FileHashUploader.cpp
new file mode 100644
index 0000000000..2408e16df4
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileHashUploader.cpp
@@ -0,0 +1,142 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileHashUploader.h"
+
+#include "td/telegram/telegram_api.h"
+
+#include "td/telegram/Global.h"
+#include "td/telegram/net/NetQueryDispatcher.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/crypto.h"
+#include "td/utils/logging.h"
+#include "td/utils/MimeType.h"
+#include "td/utils/misc.h"
+#include "td/utils/PathView.h"
+#include "td/utils/port/Fd.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/Status.h"
+
+namespace td {
+void FileHashUploader::start_up() {
+ auto status = init();
+ if (status.is_error()) {
+ callback_->on_error(std::move(status));
+ stop_flag_ = true;
+ return;
+ }
+}
+Status FileHashUploader::init() {
+ TRY_RESULT(fd, FileFd::open(local_.path_, FileFd::Read));
+ if (fd.get_size() != size_) {
+ return Status::Error("size mismatch");
+ }
+ fd_ = BufferedFd<FileFd>(std::move(fd));
+ sha256_init(&sha256_state_);
+
+ resource_state_.set_unit_size(1024);
+ resource_state_.update_estimated_limit(size_);
+ return Status::OK();
+}
+void FileHashUploader::loop() {
+ if (stop_flag_) {
+ return;
+ }
+
+ auto status = loop_impl();
+ if (status.is_error()) {
+ callback_->on_error(std::move(status));
+ stop_flag_ = true;
+ return;
+ }
+}
+
+Status FileHashUploader::loop_impl() {
+ if (state_ == CalcSha) {
+ TRY_STATUS(loop_sha());
+ }
+ if (state_ == NetRequest) {
+ // messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document;
+ auto hash = BufferSlice(32);
+ sha256_final(&sha256_state_, hash.as_slice());
+ auto mime_type = MimeType::from_extension(PathView(local_.path_).extension(), "image/gif");
+ auto query =
+ telegram_api::messages_getDocumentByHash(std::move(hash), static_cast<int32>(size_), std::move(mime_type));
+ LOG(INFO) << "Send getDocumentByHash request: " << to_string(query);
+ auto ptr = G()->net_query_creator().create(create_storer(query));
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(ptr), actor_shared(this));
+ state_ = WaitNetResult;
+ }
+ return Status::OK();
+}
+
+Status FileHashUploader::loop_sha() {
+ auto limit = resource_state_.unused();
+ if (limit == 0) {
+ return Status::OK();
+ }
+ if (limit > size_left_) {
+ limit = size_left_;
+ }
+ resource_state_.start_use(limit);
+
+ fd_.update_flags(Fd::Flag::Read);
+ TRY_RESULT(read_size, fd_.flush_read(static_cast<size_t>(limit)));
+ if (read_size != static_cast<size_t>(limit)) {
+ return Status::Error("unexpected end of file");
+ }
+ while (true) {
+ auto ready = fd_.input_buffer().prepare_read();
+ if (ready.empty()) {
+ break;
+ }
+ sha256_update(ready, &sha256_state_);
+ fd_.input_buffer().confirm_read(ready.size());
+ }
+ resource_state_.stop_use(limit);
+
+ size_left_ -= narrow_cast<int64>(read_size);
+ CHECK(size_left_ >= 0);
+ if (size_left_ == 0) {
+ state_ = NetRequest;
+ return Status::OK();
+ }
+ return Status::OK();
+}
+
+void FileHashUploader::on_result(NetQueryPtr net_query) {
+ auto status = on_result_impl(std::move(net_query));
+ if (status.is_error()) {
+ callback_->on_error(std::move(status));
+ stop_flag_ = true;
+ return;
+ }
+}
+
+Status FileHashUploader::on_result_impl(NetQueryPtr net_query) {
+ if (net_query->is_error()) {
+ return net_query->move_as_error();
+ }
+ TRY_RESULT(res, fetch_result<telegram_api::messages_getDocumentByHash>(net_query->ok()));
+
+ switch (res->get_id()) {
+ case telegram_api::documentEmpty::ID:
+ return Status::Error("Document is not found by hash");
+ case telegram_api::document::ID: {
+ auto document = move_tl_object_as<telegram_api::document>(res);
+ callback_->on_ok(FullRemoteFileLocation(FileType::Document, document->id_, document->access_hash_,
+ DcId::internal(document->dc_id_)));
+
+ stop_flag_ = true;
+ return Status::OK();
+ }
+ default:
+ UNREACHABLE();
+ return Status::Error("UNREACHABLE");
+ }
+}
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileHashUploader.h b/libs/tdlib/td/td/telegram/files/FileHashUploader.h
new file mode 100644
index 0000000000..d836c521b7
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileHashUploader.h
@@ -0,0 +1,80 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/files/FileLoaderActor.h"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/ResourceManager.h"
+
+#include "td/utils/BufferedFd.h"
+#include "td/utils/crypto.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class FileHashUploader : public FileLoaderActor {
+ public:
+ class Callback {
+ public:
+ Callback() = default;
+ Callback(const Callback &) = delete;
+ Callback &operator=(const Callback &) = delete;
+ virtual ~Callback() = default;
+ virtual void on_ok(const FullRemoteFileLocation &locatioin) = 0;
+ virtual void on_error(Status status) = 0;
+ };
+
+ FileHashUploader(const FullLocalFileLocation &local, int64 size, std::unique_ptr<Callback> callback)
+ : local_(local), size_(size), size_left_(size), callback_(std::move(callback)) {
+ }
+
+ void set_resource_manager(ActorShared<ResourceManager> resource_manager) override {
+ resource_manager_ = std::move(resource_manager);
+ send_closure(resource_manager_, &ResourceManager::update_resources, resource_state_);
+ }
+
+ void update_priority(int8 priority) override {
+ send_closure(resource_manager_, &ResourceManager::update_priority, priority);
+ }
+ void update_resources(const ResourceState &other) override {
+ if (stop_flag_) {
+ return;
+ }
+ resource_state_.update_slave(other);
+ loop();
+ }
+
+ private:
+ ResourceState resource_state_;
+ BufferedFd<FileFd> fd_;
+
+ FullLocalFileLocation local_;
+ int64 size_;
+ int64 size_left_;
+ unique_ptr<Callback> callback_;
+
+ ActorShared<ResourceManager> resource_manager_;
+
+ enum { CalcSha, NetRequest, WaitNetResult } state_ = CalcSha;
+ bool stop_flag_ = false;
+ Sha256State sha256_state_;
+
+ void start_up() override;
+ Status init();
+
+ void loop() override;
+
+ Status loop_impl();
+
+ Status loop_sha();
+
+ void on_result(NetQueryPtr net_query) override;
+
+ Status on_result_impl(NetQueryPtr net_query);
+};
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileId.h b/libs/tdlib/td/td/telegram/files/FileId.h
new file mode 100644
index 0000000000..ef68681ba0
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileId.h
@@ -0,0 +1,67 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+#include <functional>
+#include <type_traits>
+
+namespace td {
+
+class FileId {
+ int32 id = 0;
+ int32 remote_id = 0;
+
+ public:
+ FileId() = default;
+
+ FileId(int32 file_id, int32 remote_id) : id(file_id), remote_id(remote_id) {
+ }
+ template <class T1, class T2, typename = std::enable_if_t<std::is_convertible<T1, int32>::value>,
+ typename = std::enable_if_t<std::is_convertible<T2, int32>::value>>
+ FileId(T1 file_id, T2 remote_id) = delete;
+
+ bool empty() const {
+ return id <= 0;
+ }
+ bool is_valid() const {
+ return id > 0;
+ }
+
+ int32 get() const {
+ return id;
+ }
+
+ int32 get_remote() const {
+ return remote_id;
+ }
+
+ bool operator<(const FileId &other) const {
+ return id < other.id;
+ }
+
+ bool operator==(const FileId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const FileId &other) const {
+ return id != other.id;
+ }
+};
+
+struct FileIdHash {
+ std::size_t operator()(FileId file_id) const {
+ return std::hash<int32>()(file_id.get());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, FileId file_id) {
+ return string_builder << file_id.get() << "(" << file_id.get_remote() << ")";
+}
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileId.hpp b/libs/tdlib/td/td/telegram/files/FileId.hpp
new file mode 100644
index 0000000000..b4b5dbf429
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileId.hpp
@@ -0,0 +1,26 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/files/FileId.h"
+
+#include "td/telegram/files/FileManager.hpp"
+#include "td/telegram/Td.h"
+
+namespace td {
+
+template <class StorerT>
+void store(const FileId &file_id, StorerT &storer) {
+ storer.context()->td().get_actor_unsafe()->file_manager_->store_file(file_id, storer);
+}
+
+template <class ParserT>
+void parse(FileId &file_id, ParserT &parser) {
+ file_id = parser.context()->td().get_actor_unsafe()->file_manager_->parse_file(parser);
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileLoadManager.cpp b/libs/tdlib/td/td/telegram/files/FileLoadManager.cpp
new file mode 100644
index 0000000000..85ef83595d
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileLoadManager.cpp
@@ -0,0 +1,273 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileLoadManager.h"
+
+#include "td/telegram/Global.h"
+
+#include "td/utils/common.h"
+#include "td/utils/filesystem.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+
+namespace td {
+FileLoadManager::FileLoadManager(ActorShared<Callback> callback, ActorShared<> parent)
+ : callback_(std::move(callback)), parent_(std::move(parent)) {
+}
+
+void FileLoadManager::start_up() {
+ upload_resource_manager_ =
+ create_actor<ResourceManager>("UploadResourceManager", !G()->parameters().use_file_db /*tdlib_engine*/
+ ? ResourceManager::Mode::Greedy
+ : ResourceManager::Mode::Baseline);
+}
+
+ActorOwn<ResourceManager> &FileLoadManager::get_download_resource_manager(bool is_small, DcId dc_id) {
+ auto &actor = is_small ? download_small_resource_manager_map_[dc_id] : download_resource_manager_map_[dc_id];
+ if (actor.empty()) {
+ actor = create_actor<ResourceManager>(
+ PSLICE() << "DownloadResourceManager " << tag("is_small", is_small) << tag("dc_id", dc_id),
+ ResourceManager::Mode::Baseline);
+ }
+ return actor;
+}
+
+void FileLoadManager::download(QueryId id, const FullRemoteFileLocation &remote_location,
+ const LocalFileLocation &local, int64 size, string name,
+ const FileEncryptionKey &encryption_key, bool search_file, int8 priority) {
+ if (stop_flag_) {
+ return;
+ }
+ CHECK(query_id_to_node_id_.find(id) == query_id_to_node_id_.end());
+ NodeId node_id = nodes_container_.create(Node());
+ Node *node = nodes_container_.get(node_id);
+ CHECK(node);
+ node->query_id_ = id;
+ auto callback = make_unique<FileDownloaderCallback>(actor_shared(this, node_id));
+ bool is_small = size < 20 * 1024;
+ node->loader_ = create_actor<FileDownloader>("Downloader", remote_location, local, size, std::move(name),
+ encryption_key, is_small, search_file, std::move(callback));
+ auto &resource_manager = get_download_resource_manager(is_small, remote_location.get_dc_id());
+ send_closure(resource_manager, &ResourceManager::register_worker,
+ ActorShared<FileLoaderActor>(node->loader_.get(), static_cast<uint64>(-1)), priority);
+ query_id_to_node_id_[id] = node_id;
+}
+
+void FileLoadManager::upload(QueryId id, const LocalFileLocation &local_location,
+ const RemoteFileLocation &remote_location, int64 size,
+ const FileEncryptionKey &encryption_key, int8 priority, vector<int> bad_parts) {
+ if (stop_flag_) {
+ return;
+ }
+ CHECK(query_id_to_node_id_.find(id) == query_id_to_node_id_.end());
+ NodeId node_id = nodes_container_.create(Node());
+ Node *node = nodes_container_.get(node_id);
+ CHECK(node);
+ node->query_id_ = id;
+ auto callback = make_unique<FileUploaderCallback>(actor_shared(this, node_id));
+ node->loader_ = create_actor<FileUploader>("Uploader", local_location, remote_location, size, encryption_key,
+ std::move(bad_parts), std::move(callback));
+ send_closure(upload_resource_manager_, &ResourceManager::register_worker,
+ ActorShared<FileLoaderActor>(node->loader_.get(), static_cast<uint64>(-1)), priority);
+ query_id_to_node_id_[id] = node_id;
+}
+
+void FileLoadManager::upload_by_hash(QueryId id, const FullLocalFileLocation &local_location, int64 size,
+ int8 priority) {
+ if (stop_flag_) {
+ return;
+ }
+ CHECK(query_id_to_node_id_.find(id) == query_id_to_node_id_.end());
+ NodeId node_id = nodes_container_.create(Node());
+ Node *node = nodes_container_.get(node_id);
+ CHECK(node);
+ node->query_id_ = id;
+ auto callback = make_unique<FileHashUploaderCallback>(actor_shared(this, node_id));
+ node->loader_ = create_actor<FileHashUploader>("HashUploader", local_location, size, std::move(callback));
+ send_closure(upload_resource_manager_, &ResourceManager::register_worker,
+ ActorShared<FileLoaderActor>(node->loader_.get(), static_cast<uint64>(-1)), priority);
+ query_id_to_node_id_[id] = node_id;
+}
+
+void FileLoadManager::update_priority(QueryId id, int8 priority) {
+ if (stop_flag_) {
+ return;
+ }
+ auto it = query_id_to_node_id_.find(id);
+ if (it == query_id_to_node_id_.end()) {
+ return;
+ }
+ auto node = nodes_container_.get(it->second);
+ if (node == nullptr) {
+ return;
+ }
+ send_closure(node->loader_, &FileLoaderActor::update_priority, priority);
+}
+
+void FileLoadManager::from_bytes(QueryId id, FileType type, BufferSlice bytes, string name) {
+ if (stop_flag_) {
+ return;
+ }
+ CHECK(query_id_to_node_id_.find(id) == query_id_to_node_id_.end());
+ NodeId node_id = nodes_container_.create(Node());
+ Node *node = nodes_container_.get(node_id);
+ CHECK(node);
+ node->query_id_ = id;
+ auto callback = make_unique<FileFromBytesCallback>(actor_shared(this, node_id));
+ node->loader_ =
+ create_actor<FileFromBytes>("FromBytes", type, std::move(bytes), std::move(name), std::move(callback));
+ query_id_to_node_id_[id] = node_id;
+}
+
+void FileLoadManager::get_content(const FullLocalFileLocation &local_location, Promise<BufferSlice> promise) {
+ // TODO: send query to other thread
+ promise.set_result(read_file(local_location.path_));
+}
+
+// void upload_reload_parts(QueryId id, vector<int32> parts);
+// void upload_restart(QueryId id);
+void FileLoadManager::cancel(QueryId id) {
+ if (stop_flag_) {
+ return;
+ }
+ auto it = query_id_to_node_id_.find(id);
+ if (it == query_id_to_node_id_.end()) {
+ return;
+ }
+ on_error_impl(it->second, Status::Error(1, "Cancelled"));
+}
+void FileLoadManager::update_local_file_location(QueryId id, const LocalFileLocation &local) {
+ if (stop_flag_) {
+ return;
+ }
+ auto it = query_id_to_node_id_.find(id);
+ if (it == query_id_to_node_id_.end()) {
+ return;
+ }
+ auto node = nodes_container_.get(it->second);
+ if (node == nullptr) {
+ return;
+ }
+ send_closure(node->loader_, &FileLoaderActor::update_local_file_location, local);
+}
+void FileLoadManager::close() {
+ nodes_container_.for_each([](auto id, auto &node) { node.loader_.reset(); });
+ stop_flag_ = true;
+ loop();
+}
+
+void FileLoadManager::on_start_download() {
+ auto node_id = get_link_token();
+ auto node = nodes_container_.get(node_id);
+ if (node == nullptr) {
+ return;
+ }
+ if (!stop_flag_) {
+ send_closure(callback_, &Callback::on_start_download, node->query_id_);
+ }
+}
+
+void FileLoadManager::on_partial_download(const PartialLocalFileLocation &partial_local, int64 ready_size) {
+ auto node_id = get_link_token();
+ auto node = nodes_container_.get(node_id);
+ if (node == nullptr) {
+ return;
+ }
+ if (!stop_flag_) {
+ send_closure(callback_, &Callback::on_partial_download, node->query_id_, partial_local, ready_size);
+ }
+}
+
+void FileLoadManager::on_partial_upload(const PartialRemoteFileLocation &partial_remote, int64 ready_size) {
+ auto node_id = get_link_token();
+ auto node = nodes_container_.get(node_id);
+ if (node == nullptr) {
+ return;
+ }
+ if (!stop_flag_) {
+ send_closure(callback_, &Callback::on_partial_upload, node->query_id_, partial_remote, ready_size);
+ }
+}
+
+void FileLoadManager::on_ok_download(const FullLocalFileLocation &local, int64 size) {
+ auto node_id = get_link_token();
+ auto node = nodes_container_.get(node_id);
+ if (node == nullptr) {
+ return;
+ }
+ if (!stop_flag_) {
+ send_closure(callback_, &Callback::on_download_ok, node->query_id_, local, size);
+ }
+ close_node(node_id);
+ loop();
+}
+
+void FileLoadManager::on_ok_upload(FileType file_type, const PartialRemoteFileLocation &remote, int64 size) {
+ auto node_id = get_link_token();
+ auto node = nodes_container_.get(node_id);
+ if (node == nullptr) {
+ return;
+ }
+ if (!stop_flag_) {
+ send_closure(callback_, &Callback::on_upload_ok, node->query_id_, file_type, remote, size);
+ }
+ close_node(node_id);
+ loop();
+}
+
+void FileLoadManager::on_ok_upload_full(const FullRemoteFileLocation &remote) {
+ auto node_id = get_link_token();
+ auto node = nodes_container_.get(node_id);
+ if (node == nullptr) {
+ return;
+ }
+ if (!stop_flag_) {
+ send_closure(callback_, &Callback::on_upload_full_ok, node->query_id_, remote);
+ }
+ close_node(node_id);
+ loop();
+}
+
+void FileLoadManager::on_error(Status status) {
+ auto node_id = get_link_token();
+ on_error_impl(node_id, std::move(status));
+}
+
+void FileLoadManager::on_error_impl(NodeId node_id, Status status) {
+ auto node = nodes_container_.get(node_id);
+ if (node == nullptr) {
+ status.ignore();
+ return;
+ }
+ if (!stop_flag_) {
+ send_closure(callback_, &Callback::on_error, node->query_id_, std::move(status));
+ }
+ close_node(node_id);
+ loop();
+}
+
+void FileLoadManager::hangup_shared() {
+ auto node_id = get_link_token();
+ on_error_impl(node_id, Status::Error(1, "Cancelled"));
+}
+
+void FileLoadManager::loop() {
+ if (stop_flag_) {
+ if (nodes_container_.empty()) {
+ stop();
+ }
+ return;
+ }
+}
+
+void FileLoadManager::close_node(NodeId node_id) {
+ auto node = nodes_container_.get(node_id);
+ CHECK(node);
+ query_id_to_node_id_.erase(node->query_id_);
+ nodes_container_.erase(node_id);
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileLoadManager.h b/libs/tdlib/td/td/telegram/files/FileLoadManager.h
new file mode 100644
index 0000000000..d61df5d08e
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileLoadManager.h
@@ -0,0 +1,167 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/actor/actor.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/telegram/files/FileDownloader.h"
+#include "td/telegram/files/FileFromBytes.h"
+#include "td/telegram/files/FileHashUploader.h"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileUploader.h"
+#include "td/telegram/files/ResourceManager.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/Container.h"
+#include "td/utils/Status.h"
+
+#include <map>
+
+namespace td {
+
+class FileLoadManager final : public Actor {
+ public:
+ using QueryId = uint64;
+ class Callback : public Actor {
+ public:
+ Callback() = default;
+ Callback(const Callback &) = delete;
+ Callback &operator=(const Callback &) = delete;
+ ~Callback() override = default;
+ virtual void on_start_download(QueryId id) = 0;
+ virtual void on_partial_download(QueryId id, const PartialLocalFileLocation &partial_local, int64 ready_size) = 0;
+ virtual void on_partial_upload(QueryId id, const PartialRemoteFileLocation &partial_remote, int64 ready_size) = 0;
+ virtual void on_upload_ok(QueryId id, FileType file_type, const PartialRemoteFileLocation &remtoe, int64 size) = 0;
+ virtual void on_upload_full_ok(QueryId id, const FullRemoteFileLocation &remote) = 0;
+ virtual void on_download_ok(QueryId id, const FullLocalFileLocation &local, int64 size) = 0;
+ virtual void on_error(QueryId id, Status status) = 0;
+ };
+
+ explicit FileLoadManager(ActorShared<Callback> callback, ActorShared<> parent);
+ void download(QueryId id, const FullRemoteFileLocation &remote_location, const LocalFileLocation &local, int64 size,
+ string name, const FileEncryptionKey &encryption_key, bool search_file, int8 priority);
+ void upload(QueryId id, const LocalFileLocation &local_location, const RemoteFileLocation &remote_location,
+ int64 size, const FileEncryptionKey &encryption_key, int8 priority, vector<int> bad_parts);
+ void upload_by_hash(QueryId id, const FullLocalFileLocation &local_location, int64 size, int8 priority);
+ void update_priority(QueryId id, int8 priority);
+ void from_bytes(QueryId id, FileType type, BufferSlice bytes, string name);
+ void cancel(QueryId id);
+ void update_local_file_location(QueryId id, const LocalFileLocation &local);
+ void get_content(const FullLocalFileLocation &local_location, Promise<BufferSlice> promise);
+
+ // just stops actor and all queries. no callbacks will be called
+ void close();
+
+ private:
+ struct Node {
+ QueryId query_id_;
+ ActorOwn<FileLoaderActor> loader_;
+ ResourceState resource_state_;
+ };
+ using NodeId = uint64;
+
+ std::map<DcId, ActorOwn<ResourceManager>> download_resource_manager_map_;
+ std::map<DcId, ActorOwn<ResourceManager>> download_small_resource_manager_map_;
+ ActorOwn<ResourceManager> upload_resource_manager_;
+
+ Container<Node> nodes_container_;
+ ActorShared<Callback> callback_;
+ ActorShared<> parent_;
+ std::map<QueryId, NodeId> query_id_to_node_id_;
+ bool stop_flag_ = false;
+
+ void start_up() override;
+ void loop() override;
+ void hangup_shared() override;
+
+ void close_node(NodeId node_id);
+ ActorOwn<ResourceManager> &get_download_resource_manager(bool is_small, DcId dc_id);
+
+ void on_start_download();
+ void on_partial_download(const PartialLocalFileLocation &partial_local, int64 ready_size);
+ void on_partial_upload(const PartialRemoteFileLocation &partial_remote, int64 ready_size);
+ void on_ok_download(const FullLocalFileLocation &local, int64 size);
+ void on_ok_upload(FileType file_type, const PartialRemoteFileLocation &remote, int64 size);
+ void on_ok_upload_full(const FullRemoteFileLocation &remote);
+ void on_error(Status status);
+ void on_error_impl(NodeId node_id, Status status);
+
+ class FileDownloaderCallback : public FileDownloader::Callback {
+ public:
+ explicit FileDownloaderCallback(ActorShared<FileLoadManager> actor_id) : actor_id_(std::move(actor_id)) {
+ }
+
+ private:
+ ActorShared<FileLoadManager> actor_id_;
+
+ void on_start_download() override {
+ send_closure(actor_id_, &FileLoadManager::on_start_download);
+ }
+ void on_partial_download(const PartialLocalFileLocation &partial_local, int64 ready_size) override {
+ send_closure(actor_id_, &FileLoadManager::on_partial_download, partial_local, ready_size);
+ }
+ void on_ok(const FullLocalFileLocation &full_local, int64 size) override {
+ send_closure(std::move(actor_id_), &FileLoadManager::on_ok_download, full_local, size);
+ }
+ void on_error(Status status) override {
+ send_closure(std::move(actor_id_), &FileLoadManager::on_error, std::move(status));
+ }
+ };
+
+ class FileUploaderCallback : public FileUploader::Callback {
+ public:
+ explicit FileUploaderCallback(ActorShared<FileLoadManager> actor_id) : actor_id_(std::move(actor_id)) {
+ }
+
+ private:
+ ActorShared<FileLoadManager> actor_id_;
+
+ void on_partial_upload(const PartialRemoteFileLocation &partial_remote, int64 ready_size) override {
+ send_closure(actor_id_, &FileLoadManager::on_partial_upload, partial_remote, ready_size);
+ }
+ void on_ok(FileType file_type, const PartialRemoteFileLocation &partial_remote, int64 size) override {
+ send_closure(std::move(actor_id_), &FileLoadManager::on_ok_upload, file_type, partial_remote, size);
+ }
+ void on_error(Status status) override {
+ send_closure(std::move(actor_id_), &FileLoadManager::on_error, std::move(status));
+ }
+ };
+ class FileHashUploaderCallback : public FileHashUploader::Callback {
+ public:
+ explicit FileHashUploaderCallback(ActorShared<FileLoadManager> actor_id) : actor_id_(std::move(actor_id)) {
+ }
+
+ private:
+ ActorShared<FileLoadManager> actor_id_;
+
+ void on_ok(const FullRemoteFileLocation &remote) override {
+ send_closure(std::move(actor_id_), &FileLoadManager::on_ok_upload_full, remote);
+ }
+ void on_error(Status status) override {
+ send_closure(std::move(actor_id_), &FileLoadManager::on_error, std::move(status));
+ }
+ };
+
+ class FileFromBytesCallback : public FileFromBytes::Callback {
+ public:
+ explicit FileFromBytesCallback(ActorShared<FileLoadManager> actor_id) : actor_id_(std::move(actor_id)) {
+ }
+
+ private:
+ ActorShared<FileLoadManager> actor_id_;
+
+ void on_ok(const FullLocalFileLocation &full_local, int64 size) override {
+ send_closure(std::move(actor_id_), &FileLoadManager::on_ok_download, full_local, size);
+ }
+ void on_error(Status status) override {
+ send_closure(std::move(actor_id_), &FileLoadManager::on_error, std::move(status));
+ }
+ };
+};
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileLoader.cpp b/libs/tdlib/td/td/telegram/files/FileLoader.cpp
new file mode 100644
index 0000000000..8431aa8553
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileLoader.cpp
@@ -0,0 +1,292 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileLoader.h"
+
+#include "td/telegram/files/ResourceManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/UniqueId.h"
+
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/ScopeGuard.h"
+
+#include <tuple>
+
+namespace td {
+void FileLoader::set_resource_manager(ActorShared<ResourceManager> resource_manager) {
+ resource_manager_ = std::move(resource_manager);
+ send_closure(resource_manager_, &ResourceManager::update_resources, resource_state_);
+}
+void FileLoader::update_priority(int8 priority) {
+ send_closure(resource_manager_, &ResourceManager::update_priority, priority);
+}
+void FileLoader::update_resources(const ResourceState &other) {
+ resource_state_.update_slave(other);
+ VLOG(files) << "update resources " << resource_state_;
+ loop();
+}
+void FileLoader::set_ordered_flag(bool flag) {
+ ordered_flag_ = flag;
+}
+size_t FileLoader::get_part_size() const {
+ return parts_manager_.get_part_size();
+}
+void FileLoader::hangup() {
+ // if (!stop_flag_) {
+ // stop_flag_ = true;
+ // on_error(Status::Error("Cancelled"));
+ //}
+ stop();
+}
+
+void FileLoader::update_local_file_location(const LocalFileLocation &local) {
+ auto r_prefix_info = on_update_local_location(local);
+ if (r_prefix_info.is_error()) {
+ on_error(r_prefix_info.move_as_error());
+ stop_flag_ = true;
+ return;
+ }
+ auto prefix_info = r_prefix_info.move_as_ok();
+ auto status = parts_manager_.set_known_prefix(narrow_cast<size_t>(prefix_info.size), prefix_info.is_ready);
+ if (status.is_error()) {
+ on_error(std::move(status));
+ stop_flag_ = true;
+ return;
+ }
+ loop();
+}
+
+void FileLoader::start_up() {
+ auto r_file_info = init();
+ if (r_file_info.is_error()) {
+ on_error(r_file_info.move_as_error());
+ stop_flag_ = true;
+ return;
+ }
+ auto file_info = r_file_info.ok();
+ auto size = file_info.size;
+ auto expected_size = max(size, file_info.expected_size);
+ bool is_size_final = file_info.is_size_final;
+ auto part_size = file_info.part_size;
+ auto &ready_parts = file_info.ready_parts;
+ auto use_part_count_limit = file_info.use_part_count_limit;
+ auto status = parts_manager_.init(size, expected_size, is_size_final, part_size, ready_parts, use_part_count_limit);
+ if (file_info.only_check) {
+ parts_manager_.set_checked_prefix_size(0);
+ }
+ if (status.is_error()) {
+ on_error(std::move(status));
+ stop_flag_ = true;
+ return;
+ }
+ if (ordered_flag_) {
+ ordered_parts_ = OrderedEventsProcessor<std::pair<Part, NetQueryPtr>>(parts_manager_.get_ready_prefix_count());
+ }
+ if (file_info.need_delay) {
+ delay_dispatcher_ = create_actor<DelayDispatcher>("DelayDispatcher", 0.003);
+ next_delay_ = 0.05;
+ }
+ resource_state_.set_unit_size(parts_manager_.get_part_size());
+ update_estimated_limit();
+ on_progress_impl(narrow_cast<size_t>(parts_manager_.get_ready_size()));
+ yield();
+}
+
+void FileLoader::loop() {
+ if (stop_flag_) {
+ return;
+ }
+ auto status = do_loop();
+ if (status.is_error()) {
+ if (status.code() == 1) {
+ return;
+ }
+ on_error(std::move(status));
+ stop_flag_ = true;
+ return;
+ }
+}
+Status FileLoader::do_loop() {
+ TRY_RESULT(check_info,
+ check_loop(parts_manager_.get_checked_prefix_size(), parts_manager_.get_unchecked_ready_prefix_size(),
+ parts_manager_.unchecked_ready()));
+ if (check_info.changed) {
+ on_progress_impl(narrow_cast<size_t>(parts_manager_.get_ready_size()));
+ }
+ for (auto &query : check_info.queries) {
+ G()->net_query_dispatcher().dispatch_with_callback(
+ std::move(query), actor_shared(this, UniqueId::next(UniqueId::Type::Default, CommonQueryKey)));
+ }
+ if (check_info.need_check) {
+ parts_manager_.set_need_check();
+ parts_manager_.set_checked_prefix_size(check_info.checked_prefix_size);
+ }
+
+ if (parts_manager_.ready()) {
+ TRY_STATUS(parts_manager_.finish());
+ TRY_STATUS(on_ok(parts_manager_.get_size()));
+ LOG(INFO) << "Bad download order rate: "
+ << (debug_total_parts_ == 0 ? 0.0 : 100.0 * debug_bad_part_order_ / debug_total_parts_) << "% "
+ << debug_bad_part_order_ << "/" << debug_total_parts_ << " " << format::as_array(debug_bad_parts_);
+ stop_flag_ = true;
+ return Status::OK();
+ }
+
+ TRY_STATUS(before_start_parts());
+ SCOPE_EXIT {
+ after_start_parts();
+ };
+ while (true) {
+ if (blocking_id_ != 0) {
+ break;
+ }
+ if (resource_state_.unused() < static_cast<int64>(parts_manager_.get_part_size())) {
+ VLOG(files) << "Got only " << resource_state_.unused() << " resource";
+ break;
+ }
+ TRY_RESULT(part, parts_manager_.start_part());
+ if (part.size == 0) {
+ break;
+ }
+ VLOG(files) << "Start part " << tag("id", part.id) << tag("size", part.size);
+ resource_state_.start_use(static_cast<int64>(part.size));
+
+ TRY_RESULT(query_flag, start_part(part, parts_manager_.get_part_count()));
+ NetQueryPtr query;
+ bool is_blocking;
+ std::tie(query, is_blocking) = std::move(query_flag);
+ uint64 id = UniqueId::next();
+ if (is_blocking) {
+ CHECK(blocking_id_ == 0);
+ blocking_id_ = id;
+ }
+ part_map_[id] = std::make_pair(part, query->cancel_slot_.get_signal_new());
+ // part_map_[id] = std::make_pair(part, query.get_weak());
+
+ auto callback = actor_shared(this, id);
+ if (delay_dispatcher_.empty()) {
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), std::move(callback));
+ } else {
+ send_closure(delay_dispatcher_, &DelayDispatcher::send_with_callback_and_delay, std::move(query),
+ std::move(callback), next_delay_);
+ next_delay_ = std::max(next_delay_ * 0.8, 0.003);
+ }
+ }
+ return Status::OK();
+}
+
+void FileLoader::tear_down() {
+ for (auto &it : part_map_) {
+ it.second.second.reset(); // cancel_query(it.second.second);
+ }
+}
+void FileLoader::update_estimated_limit() {
+ if (stop_flag_) {
+ return;
+ }
+ auto estimated_exta = parts_manager_.get_expected_size() - parts_manager_.get_ready_size();
+ resource_state_.update_estimated_limit(estimated_exta);
+ VLOG(files) << "update estimated limit " << estimated_exta;
+ if (!resource_manager_.empty()) {
+ keep_fd_flag(narrow_cast<uint64>(resource_state_.active_limit()) >= parts_manager_.get_part_size());
+ send_closure(resource_manager_, &ResourceManager::update_resources, resource_state_);
+ }
+}
+
+void FileLoader::on_result(NetQueryPtr query) {
+ if (stop_flag_) {
+ return;
+ }
+ auto id = get_link_token();
+ if (id == blocking_id_) {
+ blocking_id_ = 0;
+ }
+ if (UniqueId::extract_key(id) == CommonQueryKey) {
+ on_common_query(std::move(query));
+ return loop();
+ }
+ auto it = part_map_.find(id);
+ if (it == part_map_.end()) {
+ LOG(WARNING) << "Got result for unknown part";
+ return;
+ }
+
+ Part part = it->second.first;
+ it->second.second.release();
+ CHECK(query->is_ready());
+
+ bool next = false;
+ auto status = [&] {
+ TRY_RESULT(should_restart, should_restart_part(part, query));
+ if (should_restart) {
+ VLOG(files) << "Restart part " << tag("id", part.id) << tag("size", part.size);
+ resource_state_.stop_use(static_cast<int64>(part.size));
+ parts_manager_.on_part_failed(part.id);
+ } else {
+ next = true;
+ }
+ return Status::OK();
+ }();
+ if (status.is_error()) {
+ on_error(std::move(status));
+ stop_flag_ = true;
+ return;
+ }
+
+ if (next) {
+ if (ordered_flag_) {
+ auto seq_no = part.id;
+ ordered_parts_.add(seq_no, std::make_pair(part, std::move(query)),
+ [this](auto seq_no, auto &&p) { this->on_part_query(p.first, std::move(p.second)); });
+ } else {
+ on_part_query(part, std::move(query));
+ }
+ }
+ update_estimated_limit();
+ loop();
+}
+
+void FileLoader::on_part_query(Part part, NetQueryPtr query) {
+ auto status = try_on_part_query(part, std::move(query));
+ if (status.is_error()) {
+ on_error(std::move(status));
+ stop_flag_ = true;
+ }
+}
+
+void FileLoader::on_common_query(NetQueryPtr query) {
+ auto status = process_check_query(std::move(query));
+ if (status.is_error()) {
+ on_error(std::move(status));
+ stop_flag_ = true;
+ }
+}
+
+Status FileLoader::try_on_part_query(Part part, NetQueryPtr query) {
+ TRY_RESULT(size, process_part(part, std::move(query)));
+ VLOG(files) << "Ok part " << tag("id", part.id) << tag("size", part.size);
+ resource_state_.stop_use(static_cast<int64>(part.size));
+ auto old_ready_prefix_count = parts_manager_.get_ready_prefix_count();
+ TRY_STATUS(parts_manager_.on_part_ok(part.id, part.size, size));
+ auto new_ready_prefix_count = parts_manager_.get_ready_prefix_count();
+ debug_total_parts_++;
+ if (old_ready_prefix_count == new_ready_prefix_count) {
+ debug_bad_parts_.push_back(part.id);
+ debug_bad_part_order_++;
+ }
+ on_progress_impl(size);
+ return Status::OK();
+}
+
+void FileLoader::on_progress_impl(size_t size) {
+ on_progress(parts_manager_.get_part_count(), static_cast<int32>(parts_manager_.get_part_size()),
+ parts_manager_.get_ready_prefix_count(), parts_manager_.ready(), parts_manager_.get_ready_size());
+}
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileLoader.h b/libs/tdlib/td/td/telegram/files/FileLoader.h
new file mode 100644
index 0000000000..a97a5199e9
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileLoader.h
@@ -0,0 +1,129 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/actor/actor.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/telegram/files/FileLoaderActor.h"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/PartsManager.h"
+#include "td/telegram/files/ResourceManager.h"
+#include "td/telegram/files/ResourceState.h"
+#include "td/telegram/net/NetQuery.h"
+
+#include "td/telegram/DelayDispatcher.h"
+
+#include "td/utils/OrderedEventsProcessor.h"
+#include "td/utils/Status.h"
+
+#include <map>
+#include <utility>
+
+namespace td {
+class FileLoader : public FileLoaderActor {
+ public:
+ class Callback {
+ public:
+ Callback() = default;
+ Callback(const Callback &) = delete;
+ Callback &operator=(const Callback &) = delete;
+ virtual ~Callback() = default;
+ };
+ void set_resource_manager(ActorShared<ResourceManager> resource_manager) override;
+ void update_priority(int8 priority) override;
+ void update_resources(const ResourceState &other) override;
+
+ void update_local_file_location(const LocalFileLocation &local) override;
+
+ protected:
+ void set_ordered_flag(bool flag);
+ size_t get_part_size() const;
+
+ struct PrefixInfo {
+ int64 size = -1;
+ bool is_ready = false;
+ };
+ struct FileInfo {
+ int64 size;
+ int64 expected_size = 0;
+ bool is_size_final;
+ int32 part_size;
+ std::vector<int> ready_parts;
+ bool use_part_count_limit = true;
+ bool only_check = false;
+ bool need_delay = false;
+ };
+ virtual Result<FileInfo> init() TD_WARN_UNUSED_RESULT = 0;
+ virtual Status on_ok(int64 size) TD_WARN_UNUSED_RESULT = 0;
+ virtual void on_error(Status status) = 0;
+ virtual Status before_start_parts() {
+ return Status::OK();
+ }
+ virtual Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int part_count) TD_WARN_UNUSED_RESULT = 0;
+ virtual void after_start_parts() {
+ }
+ virtual Result<size_t> process_part(Part part, NetQueryPtr net_query) TD_WARN_UNUSED_RESULT = 0;
+ virtual void on_progress(int32 part_count, int32 part_size, int32 ready_part_count, bool is_ready,
+ int64 ready_size) = 0;
+ virtual Callback *get_callback() = 0;
+ virtual Result<PrefixInfo> on_update_local_location(const LocalFileLocation &location) TD_WARN_UNUSED_RESULT {
+ return Status::Error("unsupported");
+ }
+ virtual Result<bool> should_restart_part(Part part, NetQueryPtr &net_query) TD_WARN_UNUSED_RESULT {
+ return false;
+ }
+
+ virtual Status process_check_query(NetQueryPtr net_query) {
+ return Status::Error("unsupported");
+ }
+ struct CheckInfo {
+ bool need_check{false};
+ bool changed{false};
+ int64 checked_prefix_size{0};
+ std::vector<NetQueryPtr> queries;
+ };
+ virtual Result<CheckInfo> check_loop(int64 checked_prefix_size, int64 ready_prefix_size, bool is_ready) {
+ return CheckInfo{};
+ }
+
+ virtual void keep_fd_flag(bool keep_fd) {
+ }
+
+ private:
+ enum { CommonQueryKey = 2 };
+ bool stop_flag_ = false;
+ ActorShared<ResourceManager> resource_manager_;
+ ResourceState resource_state_;
+ PartsManager parts_manager_;
+ uint64 blocking_id_{0};
+ std::map<uint64, std::pair<Part, ActorShared<>>> part_map_;
+ // std::map<uint64, std::pair<Part, NetQueryRef>> part_map_;
+ bool ordered_flag_ = false;
+ OrderedEventsProcessor<std::pair<Part, NetQueryPtr>> ordered_parts_;
+ ActorOwn<DelayDispatcher> delay_dispatcher_;
+ double next_delay_ = 0;
+
+ uint32 debug_total_parts_ = 0;
+ uint32 debug_bad_part_order_ = 0;
+ std::vector<int32> debug_bad_parts_;
+
+ void start_up() override;
+ void loop() override;
+ Status do_loop();
+ void hangup() override;
+ void tear_down() override;
+
+ void update_estimated_limit();
+ void on_progress_impl(size_t size);
+
+ void on_result(NetQueryPtr query) override;
+ void on_part_query(Part part, NetQueryPtr query);
+ void on_common_query(NetQueryPtr query);
+ Status try_on_part_query(Part part, NetQueryPtr query);
+};
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileLoaderActor.h b/libs/tdlib/td/td/telegram/files/FileLoaderActor.h
new file mode 100644
index 0000000000..d802f589ca
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileLoaderActor.h
@@ -0,0 +1,28 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/files/ResourceState.h"
+#include "td/telegram/net/NetQuery.h"
+
+namespace td {
+
+class LocalFileLocation;
+class ResourceManager;
+
+class FileLoaderActor : public NetQueryCallback {
+ public:
+ virtual void set_resource_manager(ActorShared<ResourceManager>) = 0;
+ virtual void update_priority(int8 priority) = 0;
+ virtual void update_resources(const ResourceState &other) = 0;
+
+ // TODO: existence of this function is a dirty hack. Refactoring is highly appreciated
+ virtual void update_local_file_location(const LocalFileLocation &local) {
+ }
+};
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileLoaderUtils.cpp b/libs/tdlib/td/td/telegram/files/FileLoaderUtils.cpp
new file mode 100644
index 0000000000..1e89cb72ee
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileLoaderUtils.cpp
@@ -0,0 +1,168 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileLoaderUtils.h"
+
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/Global.h"
+
+#include "td/utils/common.h"
+#include "td/utils/filesystem.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/PathView.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/port/path.h"
+#include "td/utils/Random.h"
+#include "td/utils/StringBuilder.h"
+
+#include <tuple>
+
+namespace td {
+
+namespace {
+Result<std::pair<FileFd, string>> try_create_new_file(Result<CSlice> result_name) {
+ TRY_RESULT(name, std::move(result_name));
+ TRY_RESULT(fd, FileFd::open(name, FileFd::Read | FileFd::Write | FileFd::CreateNew, 0640));
+ return std::make_pair(std::move(fd), name.str());
+}
+Result<std::pair<FileFd, string>> try_open_file(Result<CSlice> result_name) {
+ TRY_RESULT(name, std::move(result_name));
+ TRY_RESULT(fd, FileFd::open(name, FileFd::Read, 0640));
+ return std::make_pair(std::move(fd), name.str());
+}
+
+struct RandSuff {
+ int len;
+};
+StringBuilder &operator<<(StringBuilder &sb, const RandSuff &) {
+ for (int i = 0; i < 6; i++) {
+ sb << format::hex_digit(Random::fast(0, 15));
+ }
+ return sb;
+}
+struct Ext {
+ Slice ext;
+};
+StringBuilder &operator<<(StringBuilder &sb, const Ext &ext) {
+ if (ext.ext.empty()) {
+ return sb;
+ }
+ return sb << "." << ext.ext;
+}
+} // namespace
+
+Result<std::pair<FileFd, string>> open_temp_file(const FileType &file_type) {
+ auto pmc = G()->td_db()->get_binlog_pmc();
+ // TODO: CAS?
+ int32 file_id = to_integer<int32>(pmc->get("tmp_file_id"));
+ pmc->set("tmp_file_id", to_string(file_id + 1));
+
+ auto temp_dir = get_files_temp_dir(file_type);
+ auto res = try_create_new_file(PSLICE_SAFE() << temp_dir << file_id);
+ if (res.is_error()) {
+ res = try_create_new_file(PSLICE_SAFE() << temp_dir << file_id << "_" << RandSuff{6});
+ }
+ return res;
+}
+
+template <class F>
+bool for_suggested_file_name(CSlice name, bool use_pmc, bool use_random, F &&callback) {
+ auto try_callback = [&](Result<CSlice> r_path) {
+ if (r_path.is_error()) {
+ return true;
+ }
+ return callback(r_path.move_as_ok());
+ };
+ auto cleaned_name = clean_filename(name);
+ PathView path_view(cleaned_name);
+ auto stem = path_view.file_stem();
+ auto ext = path_view.extension();
+ bool active = true;
+ if (!stem.empty() && !G()->parameters().ignore_file_names) {
+ active = try_callback(PSLICE_SAFE() << stem << Ext{ext});
+ for (int i = 0; active && i < 10; i++) {
+ active = try_callback(PSLICE_SAFE() << stem << "_(" << i << ")" << Ext{ext});
+ }
+ for (int i = 2; active && i < 12 && use_random; i++) {
+ active = try_callback(PSLICE_SAFE() << stem << "_(" << RandSuff{i} << ")" << Ext{ext});
+ }
+ } else if (use_pmc) {
+ auto pmc = G()->td_db()->get_binlog_pmc();
+ int32 file_id = to_integer<int32>(pmc->get("perm_file_id"));
+ pmc->set("perm_file_id", to_string(file_id + 1));
+ active = try_callback(PSLICE_SAFE() << "file_" << file_id << Ext{ext});
+ if (active) {
+ active = try_callback(PSLICE_SAFE() << "file_" << file_id << "_" << RandSuff{6} << Ext{ext});
+ }
+ }
+ return active;
+}
+
+Result<string> create_from_temp(CSlice temp_path, CSlice dir, CSlice name) {
+ LOG(INFO) << "Create file in directory " << dir << " with suggested name " << name << " from temporary file "
+ << temp_path;
+ Result<std::pair<FileFd, string>> res = Status::Error(500, "Can't find suitable file name");
+ for_suggested_file_name(name, true, true, [&](CSlice suggested_name) {
+ res = try_create_new_file(PSLICE_SAFE() << dir << suggested_name);
+ return res.is_error();
+ });
+ TRY_RESULT(tmp, std::move(res));
+ tmp.first.close();
+ auto perm_path = std::move(tmp.second);
+ TRY_STATUS(rename(temp_path, perm_path));
+ return perm_path;
+}
+
+Result<string> search_file(CSlice dir, CSlice name, int64 expected_size) {
+ Result<std::string> res = Status::Error(500, "Can't find suitable file name");
+ for_suggested_file_name(name, false, false, [&](CSlice suggested_name) {
+ auto r_pair = try_open_file(PSLICE_SAFE() << dir << suggested_name);
+ if (r_pair.is_error()) {
+ return false;
+ }
+ FileFd fd;
+ std::string path;
+ std::tie(fd, path) = r_pair.move_as_ok();
+ if (fd.stat().size_ != expected_size) {
+ return true;
+ }
+ fd.close();
+ res = std::move(path);
+ return false;
+ });
+ return res;
+}
+
+const char *file_type_name[file_type_size] = {"thumbnails", "profile_photos", "photos", "voice",
+ "videos", "documents", "secret", "temp",
+ "stickers", "music", "animations", "secret_thumbnails",
+ "wallpapers", "video_notes"};
+
+string get_file_base_dir(const FileDirType &file_dir_type) {
+ switch (file_dir_type) {
+ case FileDirType::Secure:
+ return G()->get_dir().str();
+ case FileDirType::Common:
+ return G()->get_files_dir().str();
+ default:
+ UNREACHABLE();
+ return "";
+ }
+}
+
+string get_files_base_dir(const FileType &file_type) {
+ return get_file_base_dir(get_file_dir_type(file_type));
+}
+string get_files_temp_dir(const FileType &file_type) {
+ return get_files_base_dir(file_type) + "temp" + TD_DIR_SLASH;
+}
+string get_files_dir(const FileType &file_type) {
+ return get_files_base_dir(file_type) + file_type_name[static_cast<int32>(file_type)] + TD_DIR_SLASH;
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileLoaderUtils.h b/libs/tdlib/td/td/telegram/files/FileLoaderUtils.h
new file mode 100644
index 0000000000..7eaf1e12e8
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileLoaderUtils.h
@@ -0,0 +1,25 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+#include <utility>
+
+namespace td {
+enum class FileType : int8;
+
+Result<std::pair<FileFd, string>> open_temp_file(const FileType &file_type) TD_WARN_UNUSED_RESULT;
+Result<string> create_from_temp(CSlice temp_path, CSlice dir, CSlice name) TD_WARN_UNUSED_RESULT;
+Result<string> search_file(CSlice dir, CSlice name, int64 expected_size) TD_WARN_UNUSED_RESULT;
+string get_files_base_dir(const FileType &file_type);
+string get_files_temp_dir(const FileType &file_type);
+string get_files_dir(const FileType &file_type);
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileLocation.h b/libs/tdlib/td/td/telegram/files/FileLocation.h
new file mode 100644
index 0000000000..7733b65d2a
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileLocation.h
@@ -0,0 +1,1199 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/net/DcId.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
+#include "td/utils/format.h"
+#include "td/utils/int_types.h"
+#include "td/utils/logging.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+#include "td/utils/tl_storers.h"
+#include "td/utils/Variant.h"
+
+#include <cstring>
+#include <tuple>
+
+namespace td {
+
+enum class FileType : int8 {
+ Thumbnail,
+ ProfilePhoto,
+ Photo,
+ VoiceNote,
+ Video,
+ Document,
+ Encrypted,
+ Temp,
+ Sticker,
+ Audio,
+ Animation,
+ EncryptedThumbnail,
+ Wallpaper,
+ VideoNote,
+ Size,
+ None
+};
+
+inline FileType from_td_api(const td_api::FileType &file_type) {
+ switch (file_type.get_id()) {
+ case td_api::fileTypeThumbnail::ID:
+ return FileType::Thumbnail;
+ case td_api::fileTypeProfilePhoto::ID:
+ return FileType::ProfilePhoto;
+ case td_api::fileTypePhoto::ID:
+ return FileType::Photo;
+ case td_api::fileTypeVoiceNote::ID:
+ return FileType::VoiceNote;
+ case td_api::fileTypeVideo::ID:
+ return FileType::Video;
+ case td_api::fileTypeDocument::ID:
+ return FileType::Document;
+ case td_api::fileTypeSecret::ID:
+ return FileType::Encrypted;
+ case td_api::fileTypeUnknown::ID:
+ return FileType::Temp;
+ case td_api::fileTypeSticker::ID:
+ return FileType::Sticker;
+ case td_api::fileTypeAudio::ID:
+ return FileType::Audio;
+ case td_api::fileTypeAnimation::ID:
+ return FileType::Animation;
+ case td_api::fileTypeSecretThumbnail::ID:
+ return FileType::EncryptedThumbnail;
+ case td_api::fileTypeWallpaper::ID:
+ return FileType::Wallpaper;
+ case td_api::fileTypeVideoNote::ID:
+ return FileType::VideoNote;
+ case td_api::fileTypeNone::ID:
+ return FileType::None;
+ default:
+ UNREACHABLE();
+ return FileType::None;
+ }
+}
+
+inline tl_object_ptr<td_api::FileType> as_td_api(FileType file_type) {
+ switch (file_type) {
+ case FileType::Thumbnail:
+ return make_tl_object<td_api::fileTypeThumbnail>();
+ case FileType::ProfilePhoto:
+ return make_tl_object<td_api::fileTypeProfilePhoto>();
+ case FileType::Photo:
+ return make_tl_object<td_api::fileTypePhoto>();
+ case FileType::VoiceNote:
+ return make_tl_object<td_api::fileTypeVoiceNote>();
+ case FileType::Video:
+ return make_tl_object<td_api::fileTypeVideo>();
+ case FileType::Document:
+ return make_tl_object<td_api::fileTypeDocument>();
+ case FileType::Encrypted:
+ return make_tl_object<td_api::fileTypeSecret>();
+ case FileType::Temp:
+ return make_tl_object<td_api::fileTypeUnknown>();
+ case FileType::Sticker:
+ return make_tl_object<td_api::fileTypeSticker>();
+ case FileType::Audio:
+ return make_tl_object<td_api::fileTypeAudio>();
+ case FileType::Animation:
+ return make_tl_object<td_api::fileTypeAnimation>();
+ case FileType::EncryptedThumbnail:
+ return make_tl_object<td_api::fileTypeSecretThumbnail>();
+ case FileType::Wallpaper:
+ return make_tl_object<td_api::fileTypeWallpaper>();
+ case FileType::VideoNote:
+ return make_tl_object<td_api::fileTypeVideoNote>();
+ case FileType::None:
+ return make_tl_object<td_api::fileTypeNone>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+enum class FileDirType : int8 { Secure, Common };
+inline FileDirType get_file_dir_type(FileType file_type) {
+ switch (file_type) {
+ case FileType::Thumbnail:
+ case FileType::ProfilePhoto:
+ case FileType::Encrypted:
+ case FileType::Sticker:
+ case FileType::Temp:
+ case FileType::Wallpaper:
+ case FileType::EncryptedThumbnail:
+ return FileDirType::Secure;
+ default:
+ return FileDirType::Common;
+ }
+}
+
+constexpr int32 file_type_size = static_cast<int32>(FileType::Size);
+extern const char *file_type_name[file_type_size];
+
+struct FileEncryptionKey {
+ FileEncryptionKey() = default;
+ FileEncryptionKey(Slice key, Slice iv) : key_iv_(key.size() + iv.size(), '\0') {
+ if (key.size() != 32 || iv.size() != 32) {
+ LOG(ERROR) << "Wrong key/iv sizes: " << key.size() << " " << iv.size();
+ return;
+ }
+ CHECK(key_iv_.size() == 64);
+ std::memcpy(&key_iv_[0], key.data(), key.size());
+ std::memcpy(&key_iv_[key.size()], iv.data(), iv.size());
+ }
+ static FileEncryptionKey create() {
+ FileEncryptionKey res;
+ res.key_iv_.resize(64);
+ Random::secure_bytes(res.key_iv_);
+ return res;
+ }
+
+ const UInt256 &key() const {
+ CHECK(key_iv_.size() == 64);
+ return *reinterpret_cast<const UInt256 *>(key_iv_.data());
+ }
+ Slice key_slice() const {
+ CHECK(key_iv_.size() == 64);
+ return Slice(key_iv_.data(), 32);
+ }
+
+ UInt256 &mutable_iv() {
+ CHECK(key_iv_.size() == 64);
+ return *reinterpret_cast<UInt256 *>(&key_iv_[0] + 32);
+ }
+ Slice iv_slice() const {
+ CHECK(key_iv_.size() == 64);
+ return Slice(key_iv_.data() + 32, 32);
+ }
+
+ int32 calc_fingerprint() const {
+ char buf[16];
+ md5(key_iv_, {buf, sizeof(buf)});
+ return as<int32>(buf) ^ as<int32>(buf + 4);
+ }
+
+ bool empty() const {
+ return key_iv_.empty();
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(key_iv_, storer);
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(key_iv_, parser);
+ }
+
+ string key_iv_; // TODO wrong alignment is possible
+};
+
+inline bool operator==(const FileEncryptionKey &lhs, const FileEncryptionKey &rhs) {
+ return lhs.key_iv_ == rhs.key_iv_;
+}
+
+inline bool operator!=(const FileEncryptionKey &lhs, const FileEncryptionKey &rhs) {
+ return !(lhs == rhs);
+}
+
+struct EmptyRemoteFileLocation {
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ }
+};
+
+inline bool operator==(const EmptyRemoteFileLocation &lhs, const EmptyRemoteFileLocation &rhs) {
+ return true;
+}
+
+inline bool operator!=(const EmptyRemoteFileLocation &lhs, const EmptyRemoteFileLocation &rhs) {
+ return !(lhs == rhs);
+}
+
+struct PartialRemoteFileLocation {
+ int64 file_id_;
+ int32 part_count_;
+ int32 part_size_;
+ int32 ready_part_count_;
+ int32 is_big_;
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(file_id_, storer);
+ store(part_count_, storer);
+ store(part_size_, storer);
+ store(ready_part_count_, storer);
+ store(is_big_, storer);
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ parse(file_id_, parser);
+ parse(part_count_, parser);
+ parse(part_size_, parser);
+ parse(ready_part_count_, parser);
+ parse(is_big_, parser);
+ }
+};
+
+inline bool operator==(const PartialRemoteFileLocation &lhs, const PartialRemoteFileLocation &rhs) {
+ return lhs.file_id_ == rhs.file_id_ && lhs.part_count_ == rhs.part_count_ && lhs.part_size_ == rhs.part_size_ &&
+ lhs.ready_part_count_ == rhs.ready_part_count_ && lhs.is_big_ == rhs.is_big_;
+}
+
+inline bool operator!=(const PartialRemoteFileLocation &lhs, const PartialRemoteFileLocation &rhs) {
+ return !(lhs == rhs);
+}
+
+struct PhotoRemoteFileLocation {
+ int64 id_;
+ int64 access_hash_;
+ int64 volume_id_;
+ int64 secret_;
+ int32 local_id_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(id_, storer);
+ store(access_hash_, storer);
+ store(volume_id_, storer);
+ store(secret_, storer);
+ store(local_id_, storer);
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ parse(id_, parser);
+ parse(access_hash_, parser);
+ parse(volume_id_, parser);
+ parse(secret_, parser);
+ parse(local_id_, parser);
+ }
+ struct AsKey {
+ const PhotoRemoteFileLocation &key;
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(key.id_, storer);
+ store(key.volume_id_, storer);
+ store(key.local_id_, storer);
+ }
+ };
+ AsKey as_key() const {
+ return AsKey{*this};
+ }
+
+ bool operator<(const PhotoRemoteFileLocation &other) const {
+ return std::tie(id_, volume_id_, local_id_) < std::tie(other.id_, other.volume_id_, other.local_id_);
+ }
+ bool operator==(const PhotoRemoteFileLocation &other) const {
+ return std::tie(id_, volume_id_, local_id_) == std::tie(other.id_, other.volume_id_, other.local_id_);
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, const PhotoRemoteFileLocation &location) {
+ return string_builder << "[id = " << location.id_ << ", access_hash = " << location.access_hash_
+ << ", volume_id = " << location.volume_id_ << ", secret = " << location.secret_
+ << ", local_id = " << location.local_id_ << "]";
+}
+
+struct WebRemoteFileLocation {
+ string url_;
+ int64 access_hash_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(url_, storer);
+ store(access_hash_, storer);
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ parse(url_, parser);
+ parse(access_hash_, parser);
+ }
+ struct AsKey {
+ const WebRemoteFileLocation &key;
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(key.url_, storer);
+ }
+ };
+ AsKey as_key() const {
+ return AsKey{*this};
+ }
+ bool operator<(const WebRemoteFileLocation &other) const {
+ return url_ < other.url_;
+ }
+ bool operator==(const WebRemoteFileLocation &other) const {
+ return url_ == other.url_;
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, const WebRemoteFileLocation &location) {
+ return string_builder << "[url = " << location.url_ << ", access_hash = " << location.access_hash_ << "]";
+}
+
+struct CommonRemoteFileLocation {
+ int64 id_;
+ int64 access_hash_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(id_, storer);
+ store(access_hash_, storer);
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ parse(id_, parser);
+ parse(access_hash_, parser);
+ }
+ struct AsKey {
+ const CommonRemoteFileLocation &key;
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(key.id_, storer);
+ }
+ };
+ AsKey as_key() const {
+ return AsKey{*this};
+ }
+ bool operator<(const CommonRemoteFileLocation &other) const {
+ return std::tie(id_) < std::tie(other.id_);
+ }
+ bool operator==(const CommonRemoteFileLocation &other) const {
+ return std::tie(id_) == std::tie(other.id_);
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, const CommonRemoteFileLocation &location) {
+ return string_builder << "[id = " << location.id_ << ", access_hash = " << location.access_hash_ << "]";
+}
+
+class FullRemoteFileLocation {
+ public:
+ FileType file_type_{FileType::None};
+
+ private:
+ static constexpr int32 WEB_LOCATION_FLAG = 1 << 24;
+ bool web_location_flag_{false};
+ DcId dc_id_;
+ enum class LocationType { Web, Photo, Common, None };
+ Variant<WebRemoteFileLocation, PhotoRemoteFileLocation, CommonRemoteFileLocation> variant_;
+
+ LocationType location_type() const {
+ if (is_web()) {
+ return LocationType::Web;
+ }
+ switch (file_type_) {
+ case FileType::Photo:
+ case FileType::ProfilePhoto:
+ case FileType::Thumbnail:
+ case FileType::EncryptedThumbnail:
+ case FileType::Wallpaper:
+ return LocationType::Photo;
+ case FileType::Video:
+ case FileType::VoiceNote:
+ case FileType::Document:
+ case FileType::Sticker:
+ case FileType::Audio:
+ case FileType::Animation:
+ case FileType::Encrypted:
+ case FileType::VideoNote:
+ return LocationType::Common;
+ case FileType::None:
+ case FileType::Size:
+ default:
+ UNREACHABLE();
+ case FileType::Temp:
+ return LocationType::None;
+ }
+ }
+
+ WebRemoteFileLocation &web() {
+ return variant_.get<WebRemoteFileLocation>();
+ }
+ PhotoRemoteFileLocation &photo() {
+ return variant_.get<PhotoRemoteFileLocation>();
+ }
+ CommonRemoteFileLocation &common() {
+ return variant_.get<CommonRemoteFileLocation>();
+ }
+ const WebRemoteFileLocation &web() const {
+ return variant_.get<WebRemoteFileLocation>();
+ }
+ const PhotoRemoteFileLocation &photo() const {
+ return variant_.get<PhotoRemoteFileLocation>();
+ }
+ const CommonRemoteFileLocation &common() const {
+ return variant_.get<CommonRemoteFileLocation>();
+ }
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder,
+ const FullRemoteFileLocation &full_remote_file_location);
+
+ int32 full_type() const {
+ auto type = static_cast<int32>(file_type_);
+ if (is_web()) {
+ type |= WEB_LOCATION_FLAG;
+ }
+ return type;
+ }
+
+ public:
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(full_type(), storer);
+ store(dc_id_.get_value(), storer);
+ variant_.visit([&](auto &&value) {
+ using td::store;
+ store(value, storer);
+ });
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ int32 raw_type;
+ parse(raw_type, parser);
+ web_location_flag_ = (raw_type & WEB_LOCATION_FLAG) != 0;
+ raw_type &= ~WEB_LOCATION_FLAG;
+ if (raw_type < 0 || raw_type >= static_cast<int32>(FileType::Size)) {
+ return parser.set_error("Invalid FileType in FullRemoteFileLocation");
+ }
+ file_type_ = static_cast<FileType>(raw_type);
+ int32 dc_id_value;
+ parse(dc_id_value, parser);
+ dc_id_ = DcId::from_value(dc_id_value);
+
+ switch (location_type()) {
+ case LocationType::Web: {
+ variant_ = WebRemoteFileLocation();
+ return web().parse(parser);
+ }
+ case LocationType::Photo: {
+ variant_ = PhotoRemoteFileLocation();
+ return photo().parse(parser);
+ }
+ case LocationType::Common: {
+ variant_ = CommonRemoteFileLocation();
+ return common().parse(parser);
+ }
+ case LocationType::None: {
+ break;
+ }
+ }
+ parser.set_error("Invalid FileType in FullRemoteFileLocation");
+ }
+
+ struct AsKey {
+ const FullRemoteFileLocation &key;
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(key.full_type(), storer);
+ key.variant_.visit([&](auto &&value) {
+ using td::store;
+ store(value.as_key(), storer);
+ });
+ }
+ };
+ AsKey as_key() const {
+ return AsKey{*this};
+ }
+
+ DcId get_dc_id() const {
+ return dc_id_;
+ }
+ int64 get_access_hash() const {
+ switch (location_type()) {
+ case LocationType::Photo:
+ return photo().access_hash_;
+ case LocationType::Common:
+ return common().access_hash_;
+ case LocationType::Web:
+ return web().access_hash_;
+ case LocationType::None:
+ default:
+ UNREACHABLE();
+ return 0;
+ }
+ }
+ int64 get_id() const {
+ switch (location_type()) {
+ case LocationType::Photo:
+ return photo().id_;
+ case LocationType::Common:
+ return common().id_;
+ case LocationType::Web:
+ case LocationType::None:
+ default:
+ UNREACHABLE();
+ return 0;
+ }
+ }
+ string get_url() const {
+ if (is_web()) {
+ return web().url_;
+ }
+
+ return string();
+ }
+
+ bool is_web() const {
+ return web_location_flag_;
+ }
+ bool is_photo() const {
+ return location_type() == LocationType::Photo;
+ }
+ bool is_common() const {
+ return location_type() == LocationType::Common;
+ }
+ bool is_encrypted() const {
+ return file_type_ == FileType::Encrypted;
+ }
+
+ tl_object_ptr<telegram_api::inputWebFileLocation> as_input_web_file_location() const {
+ CHECK(is_web());
+ return make_tl_object<telegram_api::inputWebFileLocation>(web().url_, web().access_hash_);
+ }
+ tl_object_ptr<telegram_api::InputFileLocation> as_input_file_location() const {
+ switch (location_type()) {
+ case LocationType::Photo:
+ return make_tl_object<telegram_api::inputFileLocation>(photo().volume_id_, photo().local_id_, photo().secret_);
+ case LocationType::Common:
+ if (is_encrypted()) {
+ return make_tl_object<telegram_api::inputEncryptedFileLocation>(common().id_, common().access_hash_);
+ } else {
+ return make_tl_object<telegram_api::inputDocumentFileLocation>(common().id_, common().access_hash_, 0);
+ }
+ case LocationType::Web:
+ case LocationType::None:
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+ }
+
+ tl_object_ptr<telegram_api::InputDocument> as_input_document() const {
+ CHECK(is_common());
+ LOG_IF(ERROR, is_encrypted()) << "Can't call as_input_document on an encrypted file";
+ return make_tl_object<telegram_api::inputDocument>(common().id_, common().access_hash_);
+ }
+
+ tl_object_ptr<telegram_api::InputPhoto> as_input_photo() const {
+ CHECK(is_photo());
+ return make_tl_object<telegram_api::inputPhoto>(photo().id_, photo().access_hash_);
+ }
+
+ tl_object_ptr<telegram_api::InputEncryptedFile> as_input_encrypted_file() const {
+ CHECK(is_encrypted()) << "Can't call as_input_encrypted_file on a non-encrypted file";
+ return make_tl_object<telegram_api::inputEncryptedFile>(common().id_, common().access_hash_);
+ }
+
+ // TODO: this constructor is just for immediate unserialize
+ FullRemoteFileLocation() = default;
+ FullRemoteFileLocation(FileType file_type, int64 id, int64 access_hash, int32 local_id, int64 volume_id, int64 secret,
+ DcId dc_id)
+ : file_type_(file_type)
+ , dc_id_(dc_id)
+ , variant_(PhotoRemoteFileLocation{id, access_hash, volume_id, secret, local_id}) {
+ CHECK(is_photo());
+ }
+ FullRemoteFileLocation(FileType file_type, int64 id, int64 access_hash, DcId dc_id)
+ : file_type_(file_type), dc_id_(dc_id), variant_(CommonRemoteFileLocation{id, access_hash}) {
+ CHECK(is_common());
+ }
+ FullRemoteFileLocation(FileType file_type, string url, int64 access_hash, DcId dc_id)
+ : file_type_(file_type)
+ , web_location_flag_{true}
+ , dc_id_(dc_id)
+ , variant_(WebRemoteFileLocation{std::move(url), access_hash}) {
+ CHECK(is_web());
+ CHECK(!web().url_.empty());
+ }
+
+ bool operator<(const FullRemoteFileLocation &other) const {
+ if (full_type() != other.full_type()) {
+ return full_type() < other.full_type();
+ }
+ if (dc_id_ != other.dc_id_) {
+ return dc_id_ < other.dc_id_;
+ }
+ switch (location_type()) {
+ case LocationType::Photo:
+ return photo() < other.photo();
+ case LocationType::Common:
+ return common() < other.common();
+ case LocationType::Web:
+ return web() < other.web();
+ case LocationType::None:
+ default:
+ UNREACHABLE();
+ return false;
+ }
+ }
+ bool operator==(const FullRemoteFileLocation &other) const {
+ if (full_type() != other.full_type()) {
+ return false;
+ }
+ if (dc_id_ != other.dc_id_) {
+ return false;
+ }
+ switch (location_type()) {
+ case LocationType::Photo:
+ return photo() == other.photo();
+ case LocationType::Common:
+ return common() == other.common();
+ case LocationType::Web:
+ return web() == other.web();
+ case LocationType::None:
+ default:
+ UNREACHABLE();
+ return false;
+ }
+ }
+
+ static const int32 KEY_MAGIC = 0x64374632;
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder,
+ const FullRemoteFileLocation &full_remote_file_location) {
+ string_builder << "[" << file_type_name[static_cast<int32>(full_remote_file_location.file_type_)] << ", "
+ << full_remote_file_location.get_dc_id() << ", location = ";
+
+ if (full_remote_file_location.is_web()) {
+ string_builder << full_remote_file_location.web();
+ } else if (full_remote_file_location.is_photo()) {
+ string_builder << full_remote_file_location.photo();
+ } else if (full_remote_file_location.is_common()) {
+ string_builder << full_remote_file_location.common();
+ }
+
+ return string_builder << "]";
+}
+
+class RemoteFileLocation {
+ public:
+ enum class Type : int32 { Empty, Partial, Full };
+
+ Type type() const {
+ return static_cast<Type>(variant_.get_offset());
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ storer.store_int(variant_.get_offset());
+ bool ok{false};
+ variant_.visit([&](auto &&value) {
+ using td::store;
+ store(value, storer);
+ ok = true;
+ });
+ CHECK(ok);
+ }
+ PartialRemoteFileLocation &partial() {
+ return variant_.get<1>();
+ }
+ FullRemoteFileLocation &full() {
+ return variant_.get<2>();
+ }
+ const PartialRemoteFileLocation &partial() const {
+ return variant_.get<1>();
+ }
+ const FullRemoteFileLocation &full() const {
+ return variant_.get<2>();
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ auto type = static_cast<Type>(parser.fetch_int());
+ switch (type) {
+ case Type::Empty: {
+ variant_ = EmptyRemoteFileLocation();
+ return;
+ }
+ case Type::Partial: {
+ variant_ = PartialRemoteFileLocation();
+ return partial().parse(parser);
+ }
+ case Type::Full: {
+ variant_ = FullRemoteFileLocation();
+ return full().parse(parser);
+ }
+ }
+ parser.set_error("Invalid type in RemoteFileLocation");
+ }
+
+ RemoteFileLocation() : variant_{EmptyRemoteFileLocation{}} {
+ }
+ explicit RemoteFileLocation(const FullRemoteFileLocation &full) : variant_(full) {
+ }
+ explicit RemoteFileLocation(const PartialRemoteFileLocation &partial) : variant_(partial) {
+ }
+ RemoteFileLocation(FileType file_type, int64 id, int64 access_hash, int32 local_id, int64 volume_id, int64 secret,
+ DcId dc_id)
+ : variant_(FullRemoteFileLocation{file_type, id, access_hash, local_id, volume_id, secret, dc_id}) {
+ }
+ RemoteFileLocation(FileType file_type, int64 id, int64 access_hash, DcId dc_id)
+ : variant_(FullRemoteFileLocation{file_type, id, access_hash, dc_id}) {
+ }
+
+ private:
+ Variant<EmptyRemoteFileLocation, PartialRemoteFileLocation, FullRemoteFileLocation> variant_;
+
+ friend bool operator==(const RemoteFileLocation &lhs, const RemoteFileLocation &rhs);
+};
+
+inline bool operator==(const RemoteFileLocation &lhs, const RemoteFileLocation &rhs) {
+ return lhs.variant_ == rhs.variant_;
+}
+
+inline bool operator!=(const RemoteFileLocation &lhs, const RemoteFileLocation &rhs) {
+ return !(lhs == rhs);
+}
+
+struct EmptyLocalFileLocation {
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ }
+};
+
+inline bool operator==(const EmptyLocalFileLocation &lhs, const EmptyLocalFileLocation &rhs) {
+ return true;
+}
+
+inline bool operator!=(const EmptyLocalFileLocation &lhs, const EmptyLocalFileLocation &rhs) {
+ return !(lhs == rhs);
+}
+
+struct PartialLocalFileLocation {
+ FileType file_type_;
+ string path_;
+ int32 part_size_;
+ int32 ready_part_count_;
+ string iv_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(file_type_, storer);
+ store(path_, storer);
+ store(part_size_, storer);
+ store(ready_part_count_, storer);
+ store(iv_, storer);
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ parse(file_type_, parser);
+ if (file_type_ < FileType::Thumbnail || file_type_ >= FileType::Size) {
+ return parser.set_error("Invalid type in PartialLocalFileLocation");
+ }
+ parse(path_, parser);
+ parse(part_size_, parser);
+ parse(ready_part_count_, parser);
+ parse(iv_, parser);
+ }
+};
+
+inline bool operator==(const PartialLocalFileLocation &lhs, const PartialLocalFileLocation &rhs) {
+ return lhs.file_type_ == rhs.file_type_ && lhs.path_ == rhs.path_ && lhs.part_size_ == rhs.part_size_ &&
+ lhs.ready_part_count_ == rhs.ready_part_count_ && lhs.iv_ == rhs.iv_;
+}
+
+inline bool operator!=(const PartialLocalFileLocation &lhs, const PartialLocalFileLocation &rhs) {
+ return !(lhs == rhs);
+}
+
+struct FullLocalFileLocation {
+ FileType file_type_;
+ string path_;
+ uint64 mtime_nsec_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(file_type_, storer);
+ store(mtime_nsec_, storer);
+ store(path_, storer);
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ parse(file_type_, parser);
+ if (file_type_ < FileType::Thumbnail || file_type_ >= FileType::Size) {
+ return parser.set_error("Invalid type in FullLocalFileLocation");
+ }
+ parse(mtime_nsec_, parser);
+ parse(path_, parser);
+ }
+ const FullLocalFileLocation &as_key() const {
+ return *this;
+ }
+
+ // TODO: remove this constructor
+ FullLocalFileLocation() : file_type_(FileType::Photo) {
+ }
+ FullLocalFileLocation(FileType file_type, string path, uint64 mtime_nsec)
+ : file_type_(file_type), path_(std::move(path)), mtime_nsec_(mtime_nsec) {
+ }
+
+ static const int32 KEY_MAGIC = 0x84373817;
+};
+
+inline bool operator<(const FullLocalFileLocation &lhs, const FullLocalFileLocation &rhs) {
+ return std::tie(lhs.file_type_, lhs.mtime_nsec_, lhs.path_) < std::tie(rhs.file_type_, rhs.mtime_nsec_, rhs.path_);
+}
+
+inline bool operator==(const FullLocalFileLocation &lhs, const FullLocalFileLocation &rhs) {
+ return std::tie(lhs.file_type_, lhs.mtime_nsec_, lhs.path_) == std::tie(rhs.file_type_, rhs.mtime_nsec_, rhs.path_);
+}
+
+inline bool operator!=(const FullLocalFileLocation &lhs, const FullLocalFileLocation &rhs) {
+ return !(lhs == rhs);
+}
+
+inline StringBuilder &operator<<(StringBuilder &sb, const FullLocalFileLocation &location) {
+ return sb << tag("path", location.path_);
+}
+
+class LocalFileLocation {
+ public:
+ enum class Type : int32 { Empty, Partial, Full };
+
+ Type type() const {
+ return static_cast<Type>(variant_.get_offset());
+ }
+
+ PartialLocalFileLocation &partial() {
+ return variant_.get<1>();
+ }
+ FullLocalFileLocation &full() {
+ return variant_.get<2>();
+ }
+ const PartialLocalFileLocation &partial() const {
+ return variant_.get<1>();
+ }
+ const FullLocalFileLocation &full() const {
+ return variant_.get<2>();
+ }
+
+ CSlice file_name() const {
+ switch (type()) {
+ case Type::Partial:
+ return partial().path_;
+ case Type::Full:
+ return full().path_;
+ case Type::Empty:
+ default:
+ return CSlice();
+ }
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(variant_.get_offset(), storer);
+ variant_.visit([&](auto &&value) {
+ using td::store;
+ store(value, storer);
+ });
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ auto type = static_cast<Type>(parser.fetch_int());
+ switch (type) {
+ case Type::Empty:
+ variant_ = EmptyLocalFileLocation();
+ return;
+ case Type::Partial:
+ variant_ = PartialLocalFileLocation();
+ return parse(partial(), parser);
+ case Type::Full:
+ variant_ = FullLocalFileLocation();
+ return parse(full(), parser);
+ }
+ return parser.set_error("Invalid type in LocalFileLocation");
+ }
+
+ LocalFileLocation() : variant_{EmptyLocalFileLocation()} {
+ }
+ explicit LocalFileLocation(const PartialLocalFileLocation &partial) : variant_(partial) {
+ }
+ explicit LocalFileLocation(const FullLocalFileLocation &full) : variant_(full) {
+ }
+ LocalFileLocation(FileType file_type, string path, uint64 mtime_nsec)
+ : variant_(FullLocalFileLocation{file_type, std::move(path), mtime_nsec}) {
+ }
+
+ private:
+ Variant<EmptyLocalFileLocation, PartialLocalFileLocation, FullLocalFileLocation> variant_;
+
+ friend bool operator==(const LocalFileLocation &lhs, const LocalFileLocation &rhs);
+};
+
+inline bool operator==(const LocalFileLocation &lhs, const LocalFileLocation &rhs) {
+ return lhs.variant_ == rhs.variant_;
+}
+
+inline bool operator!=(const LocalFileLocation &lhs, const LocalFileLocation &rhs) {
+ return !(lhs == rhs);
+}
+
+struct FullGenerateFileLocation {
+ FileType file_type_{FileType::None};
+ string original_path_;
+ string conversion_;
+ static const int32 KEY_MAGIC = 0x8b60a1c8;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(file_type_, storer);
+ store(original_path_, storer);
+ store(conversion_, storer);
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ parse(file_type_, parser);
+ parse(original_path_, parser);
+ parse(conversion_, parser);
+ }
+
+ const FullGenerateFileLocation &as_key() const {
+ return *this;
+ }
+ FullGenerateFileLocation() = default;
+ FullGenerateFileLocation(FileType file_type, string original_path, string conversion)
+ : file_type_(file_type), original_path_(std::move(original_path)), conversion_(std::move(conversion)) {
+ }
+};
+
+inline bool operator<(const FullGenerateFileLocation &lhs, const FullGenerateFileLocation &rhs) {
+ return std::tie(lhs.file_type_, lhs.original_path_, lhs.conversion_) <
+ std::tie(rhs.file_type_, rhs.original_path_, rhs.conversion_);
+}
+
+inline bool operator==(const FullGenerateFileLocation &lhs, const FullGenerateFileLocation &rhs) {
+ return std::tie(lhs.file_type_, lhs.original_path_, lhs.conversion_) ==
+ std::tie(rhs.file_type_, rhs.original_path_, rhs.conversion_);
+}
+
+inline bool operator!=(const FullGenerateFileLocation &lhs, const FullGenerateFileLocation &rhs) {
+ return !(lhs == rhs);
+}
+
+inline StringBuilder &operator<<(StringBuilder &string_builder,
+ const FullGenerateFileLocation &full_generated_file_location) {
+ return string_builder << "["
+ << tag("file_type", file_type_name[static_cast<int32>(full_generated_file_location.file_type_)])
+ << tag("original_path", full_generated_file_location.original_path_)
+ << tag("conversion", full_generated_file_location.conversion_) << "]";
+}
+
+class GenerateFileLocation {
+ public:
+ enum class Type : int32 { Empty, Full };
+
+ Type type() const {
+ return type_;
+ }
+
+ FullGenerateFileLocation &full() {
+ CHECK(type_ == Type::Full);
+ return full_;
+ }
+ const FullGenerateFileLocation &full() const {
+ CHECK(type_ == Type::Full);
+ return full_;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(type_, storer);
+ switch (type_) {
+ case Type::Empty:
+ return;
+ case Type::Full:
+ return td::store(full_, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(type_, parser);
+ switch (type_) {
+ case Type::Empty:
+ return;
+ case Type::Full:
+ return td::parse(full_, parser);
+ }
+ return parser.set_error("Invalid type in GenerateFileLocation");
+ }
+
+ GenerateFileLocation() : type_(Type::Empty) {
+ }
+
+ explicit GenerateFileLocation(const FullGenerateFileLocation &full) : type_(Type::Full), full_(full) {
+ }
+
+ GenerateFileLocation(FileType file_type, string original_path, string conversion)
+ : type_(Type::Full), full_{file_type, std::move(original_path), std::move(conversion)} {
+ }
+
+ private:
+ Type type_;
+ FullGenerateFileLocation full_;
+};
+
+inline bool operator==(const GenerateFileLocation &lhs, const GenerateFileLocation &rhs) {
+ if (lhs.type() != rhs.type()) {
+ return false;
+ }
+ switch (lhs.type()) {
+ case GenerateFileLocation::Type::Empty:
+ return true;
+ case GenerateFileLocation::Type::Full:
+ return lhs.full() == rhs.full();
+ }
+ UNREACHABLE();
+ return false;
+}
+
+inline bool operator!=(const GenerateFileLocation &lhs, const GenerateFileLocation &rhs) {
+ return !(lhs == rhs);
+}
+
+class FileData {
+ public:
+ DialogId owner_dialog_id_;
+ uint64 pmc_id_ = 0;
+ RemoteFileLocation remote_;
+ LocalFileLocation local_;
+ unique_ptr<FullGenerateFileLocation> generate_;
+ int64 size_ = 0;
+ int64 expected_size_ = 0;
+ string remote_name_;
+ string url_;
+ FileEncryptionKey encryption_key_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ bool has_owner_dialog_id = owner_dialog_id_.is_valid();
+ bool has_expected_size = size_ == 0 && expected_size_ != 0;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_owner_dialog_id);
+ STORE_FLAG(has_expected_size);
+ END_STORE_FLAGS();
+
+ if (has_owner_dialog_id) {
+ store(owner_dialog_id_, storer);
+ }
+ store(pmc_id_, storer);
+ store(remote_, storer);
+ store(local_, storer);
+ auto generate = generate_ == nullptr ? GenerateFileLocation() : GenerateFileLocation(*generate_);
+ store(generate, storer);
+ if (has_expected_size) {
+ store(expected_size_, storer);
+ } else {
+ store(size_, storer);
+ }
+ store(remote_name_, storer);
+ store(url_, storer);
+ store(encryption_key_, storer);
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ bool has_owner_dialog_id;
+ bool has_expected_size;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_owner_dialog_id);
+ PARSE_FLAG(has_expected_size);
+ END_PARSE_FLAGS();
+
+ if (has_owner_dialog_id) {
+ parse(owner_dialog_id_, parser);
+ }
+ parse(pmc_id_, parser);
+ parse(remote_, parser);
+ parse(local_, parser);
+ GenerateFileLocation generate;
+ parse(generate, parser);
+ if (generate.type() == GenerateFileLocation::Type::Full) {
+ generate_ = std::make_unique<FullGenerateFileLocation>(generate.full());
+ } else {
+ generate_ = nullptr;
+ }
+ if (has_expected_size) {
+ parse(expected_size_, parser);
+ } else {
+ parse(size_, parser);
+ }
+ parse(remote_name_, parser);
+ parse(url_, parser);
+ parse(encryption_key_, parser);
+ }
+};
+inline StringBuilder &operator<<(StringBuilder &sb, const FileData &file_data) {
+ sb << "[" << tag("remote_name", file_data.remote_name_) << " " << file_data.owner_dialog_id_ << " "
+ << tag("size", file_data.size_) << tag("expected_size", file_data.expected_size_);
+ if (!file_data.url_.empty()) {
+ sb << tag("url", file_data.url_);
+ }
+ if (file_data.local_.type() == LocalFileLocation::Type::Full) {
+ sb << " local " << file_data.local_.full();
+ }
+ if (file_data.generate_ != nullptr) {
+ sb << " generate " << *file_data.generate_;
+ }
+ if (file_data.remote_.type() == RemoteFileLocation::Type::Full) {
+ sb << " remote " << file_data.remote_.full();
+ }
+ return sb << "]";
+}
+
+template <class T>
+string as_key(const T &object) {
+ TlStorerCalcLength calc_length;
+ calc_length.store_int(0);
+ object.as_key().store(calc_length);
+
+ BufferSlice key_buffer{calc_length.get_length()};
+ auto key = key_buffer.as_slice();
+ TlStorerUnsafe storer(key.begin());
+ storer.store_int(T::KEY_MAGIC);
+ object.as_key().store(storer);
+ return key.str();
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileManager.cpp b/libs/tdlib/td/td/telegram/files/FileManager.cpp
new file mode 100644
index 0000000000..dc5e2d1caf
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileManager.cpp
@@ -0,0 +1,2356 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileManager.h"
+
+#include "td/telegram/telegram_api.h"
+
+#include "td/telegram/files/FileLoaderUtils.h"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileUploader.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/Td.h"
+
+#include "td/utils/base64.h"
+#include "td/utils/format.h"
+#include "td/utils/HttpUrl.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/PathView.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/port/path.h"
+#include "td/utils/port/Stat.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/tl_helpers.h"
+
+#include <algorithm>
+#include <limits>
+#include <tuple>
+#include <utility>
+
+namespace td {
+
+static int VERBOSITY_NAME(update_file) = VERBOSITY_NAME(INFO);
+
+FileNode *FileNodePtr::operator->() const {
+ return get();
+}
+
+FileNode &FileNodePtr::operator*() const {
+ return *get();
+}
+
+FileNode *FileNodePtr::get() const {
+ auto res = get_unsafe();
+ CHECK(res);
+ return res;
+}
+
+FullRemoteFileLocation *FileNodePtr::get_remote() const {
+ return file_manager_->get_remote(file_id_.get_remote());
+}
+
+FileNode *FileNodePtr::get_unsafe() const {
+ CHECK(file_manager_ != nullptr);
+ return file_manager_->get_file_node_raw(file_id_);
+}
+
+FileNodePtr::operator bool() const {
+ return file_manager_ != nullptr && get_unsafe() != nullptr;
+}
+
+void FileNode::set_local_location(const LocalFileLocation &local, int64 ready_size) {
+ if (local_ready_size_ != ready_size) {
+ local_ready_size_ = ready_size;
+ VLOG(update_file) << "File " << main_file_id_ << " has changed local ready size";
+ on_info_changed();
+ }
+ if (local_ != local) {
+ VLOG(update_file) << "File " << main_file_id_ << " has changed local location";
+ local_ = local;
+ on_changed();
+ }
+}
+
+void FileNode::set_remote_location(const RemoteFileLocation &remote, FileLocationSource source, int64 ready_size) {
+ if (remote_ready_size_ != ready_size) {
+ remote_ready_size_ = ready_size;
+ VLOG(update_file) << "File " << main_file_id_ << " has changed remote ready size";
+ on_info_changed();
+ }
+ if (remote_ == remote) {
+ if (remote_.type() == RemoteFileLocation::Type::Full) {
+ if (remote_.full().get_access_hash() == remote.full().get_access_hash()) {
+ return;
+ }
+ } else {
+ return;
+ }
+ }
+
+ VLOG(update_file) << "File " << main_file_id_ << " has changed remote location";
+ remote_ = remote;
+ remote_source_ = source;
+ on_changed();
+}
+
+void FileNode::set_generate_location(unique_ptr<FullGenerateFileLocation> &&generate) {
+ bool is_changed = generate_ == nullptr ? generate != nullptr : generate == nullptr || *generate_ != *generate;
+ if (is_changed) {
+ generate_ = std::move(generate);
+ on_pmc_changed();
+ }
+}
+
+void FileNode::set_size(int64 size) {
+ if (size_ != size) {
+ VLOG(update_file) << "File " << main_file_id_ << " has changed size to " << size;
+ size_ = size;
+ on_changed();
+ }
+}
+
+void FileNode::set_expected_size(int64 expected_size) {
+ if (expected_size_ != expected_size) {
+ VLOG(update_file) << "File " << main_file_id_ << " has changed expected size to " << expected_size;
+ expected_size_ = expected_size;
+ on_changed();
+ }
+}
+
+void FileNode::set_remote_name(string remote_name) {
+ if (remote_name_ != remote_name) {
+ remote_name_ = std::move(remote_name);
+ on_pmc_changed();
+ }
+}
+
+void FileNode::set_url(string url) {
+ if (url_ != url) {
+ VLOG(update_file) << "File " << main_file_id_ << " has changed URL to " << url;
+ url_ = std::move(url);
+ on_changed();
+ }
+}
+
+void FileNode::set_owner_dialog_id(DialogId owner_id) {
+ if (owner_dialog_id_ != owner_id) {
+ owner_dialog_id_ = owner_id;
+ on_pmc_changed();
+ }
+}
+
+void FileNode::set_encryption_key(FileEncryptionKey key) {
+ if (encryption_key_ != key) {
+ encryption_key_ = std::move(key);
+ on_pmc_changed();
+ }
+}
+
+void FileNode::set_download_priority(int8 priority) {
+ if ((download_priority_ == 0) != (priority == 0)) {
+ VLOG(update_file) << "File " << main_file_id_ << " has changed download priority to " << priority;
+ on_info_changed();
+ }
+ download_priority_ = priority;
+}
+
+void FileNode::set_upload_priority(int8 priority) {
+ if ((upload_priority_ == 0) != (priority == 0)) {
+ VLOG(update_file) << "File " << main_file_id_ << " has changed upload priority to " << priority;
+ on_info_changed();
+ }
+ upload_priority_ = priority;
+}
+
+void FileNode::set_generate_priority(int8 download_priority, int8 upload_priority) {
+ if ((download_priority_ == 0) != (download_priority == 0) || (upload_priority_ == 0) != (upload_priority == 0)) {
+ VLOG(update_file) << "File " << main_file_id_ << " has changed generate priority to " << download_priority << "/"
+ << upload_priority;
+ on_info_changed();
+ }
+ generate_priority_ = max(download_priority, upload_priority);
+ generate_download_priority_ = download_priority;
+ generate_upload_priority_ = upload_priority;
+}
+
+void FileNode::on_changed() {
+ on_pmc_changed();
+ on_info_changed();
+}
+void FileNode::on_info_changed() {
+ info_changed_flag_ = true;
+}
+void FileNode::on_pmc_changed() {
+ pmc_changed_flag_ = true;
+}
+
+bool FileNode::need_info_flush() const {
+ return info_changed_flag_;
+}
+
+bool FileNode::need_pmc_flush() const {
+ if (!pmc_changed_flag_) {
+ return false;
+ }
+
+ // already in pmc
+ if (pmc_id_ != 0) {
+ return true;
+ }
+
+ // We must save encryption key
+ if (!encryption_key_.empty()) {
+ // && remote_.type() != RemoteFileLocation::Type::Empty
+ return true;
+ }
+
+ bool has_generate_location = generate_ != nullptr;
+ // Do not save "#file_id#" conversion.
+ if (has_generate_location && begins_with(generate_->conversion_, "#file_id#")) {
+ has_generate_location = false;
+ }
+
+ if (remote_.type() == RemoteFileLocation::Type::Full &&
+ (has_generate_location || local_.type() != LocalFileLocation::Type::Empty)) {
+ return true;
+ }
+ if (local_.type() == LocalFileLocation::Type::Full &&
+ (has_generate_location || remote_.type() != RemoteFileLocation::Type::Empty)) {
+ return true;
+ }
+
+ // TODO: Generate location with constant conversion
+
+ return false;
+}
+
+void FileNode::on_pmc_flushed() {
+ pmc_changed_flag_ = false;
+}
+void FileNode::on_info_flushed() {
+ info_changed_flag_ = false;
+}
+
+string FileNode::suggested_name() const {
+ if (!remote_name_.empty()) {
+ return remote_name_;
+ }
+ if (!url_.empty()) {
+ auto file_name = get_url_file_name(url_);
+ if (!file_name.empty()) {
+ return file_name;
+ }
+ }
+ if (generate_ != nullptr) {
+ if (!generate_->original_path_.empty()) {
+ return generate_->original_path_;
+ }
+ }
+ return local_.file_name().str();
+}
+
+/*** FileView ***/
+bool FileView::has_local_location() const {
+ return node_->local_.type() == LocalFileLocation::Type::Full;
+}
+const FullLocalFileLocation &FileView::local_location() const {
+ CHECK(has_local_location());
+ return node_->local_.full();
+}
+bool FileView::has_remote_location() const {
+ return node_->remote_.type() == RemoteFileLocation::Type::Full;
+}
+const FullRemoteFileLocation &FileView::remote_location() const {
+ CHECK(has_remote_location());
+ auto *remote = node_.get_remote();
+ if (remote) {
+ return *remote;
+ }
+ return node_->remote_.full();
+}
+bool FileView::has_generate_location() const {
+ return node_->generate_ != nullptr;
+}
+const FullGenerateFileLocation &FileView::generate_location() const {
+ CHECK(has_generate_location());
+ return *node_->generate_;
+}
+
+int64 FileView::size() const {
+ return node_->size_;
+}
+
+int64 FileView::expected_size() const {
+ if (node_->size_ != 0) {
+ return node_->size_;
+ }
+ return node_->expected_size_;
+}
+
+bool FileView::is_downloading() const {
+ return node_->download_priority_ != 0 || node_->generate_download_priority_ != 0;
+}
+
+int64 FileView::local_size() const {
+ switch (node_->local_.type()) {
+ case LocalFileLocation::Type::Full:
+ return node_->size_;
+ case LocalFileLocation::Type::Partial:
+ return node_->local_.partial().part_size_ * node_->local_.partial().ready_part_count_;
+ default:
+ return 0;
+ }
+}
+int64 FileView::local_total_size() const {
+ switch (node_->local_.type()) {
+ case LocalFileLocation::Type::Empty:
+ return 0;
+ case LocalFileLocation::Type::Full:
+ return node_->size_;
+ case LocalFileLocation::Type::Partial:
+ return max(static_cast<int64>(node_->local_.partial().part_size_) * node_->local_.partial().ready_part_count_,
+ node_->local_ready_size_);
+ default:
+ UNREACHABLE();
+ return 0;
+ }
+}
+
+bool FileView::is_uploading() const {
+ return node_->upload_priority_ != 0 || node_->generate_upload_priority_ != 0;
+}
+
+int64 FileView::remote_size() const {
+ switch (node_->remote_.type()) {
+ case RemoteFileLocation::Type::Full:
+ return node_->size_;
+ case RemoteFileLocation::Type::Partial: {
+ auto res =
+ max(static_cast<int64>(node_->remote_.partial().part_size_) * node_->remote_.partial().ready_part_count_,
+ node_->remote_ready_size_);
+ if (size() != 0 && size() < res) {
+ res = size();
+ }
+ return res;
+ }
+ default:
+ return 0;
+ }
+}
+
+string FileView::path() const {
+ switch (node_->local_.type()) {
+ case LocalFileLocation::Type::Full:
+ return node_->local_.full().path_;
+ case LocalFileLocation::Type::Partial:
+ return node_->local_.partial().path_;
+ default:
+ return "";
+ }
+}
+
+bool FileView::has_url() const {
+ return !node_->url_.empty();
+}
+
+const string &FileView::url() const {
+ return node_->url_;
+}
+
+const string &FileView::remote_name() const {
+ return node_->remote_name_;
+}
+
+string FileView::suggested_name() const {
+ return node_->suggested_name();
+}
+
+DialogId FileView::owner_dialog_id() const {
+ return node_->owner_dialog_id_;
+}
+
+bool FileView::get_by_hash() const {
+ return node_->get_by_hash_;
+}
+
+FileView::FileView(ConstFileNodePtr node) : node_(node) {
+}
+
+bool FileView::empty() const {
+ return !node_;
+}
+
+bool FileView::can_download_from_server() const {
+ if (!has_remote_location()) {
+ return false;
+ }
+ if (remote_location().file_type_ == FileType::Encrypted && encryption_key().empty()) {
+ return false;
+ }
+ if (remote_location().get_dc_id().is_empty()) {
+ return false;
+ }
+ return true;
+}
+bool FileView::can_generate() const {
+ return has_generate_location();
+}
+
+bool FileView::can_delete() const {
+ if (has_local_location()) {
+ return begins_with(local_location().path_, get_files_dir(get_type()));
+ }
+ return node_->local_.type() == LocalFileLocation::Type::Partial;
+}
+
+/*** FileManager ***/
+namespace {
+void prepare_path_for_pmc(FileType file_type, string &path) {
+ path = PathView::relative(path, get_files_base_dir(file_type)).str();
+}
+} // namespace
+
+FileManager::FileManager(std::unique_ptr<Context> context) : context_(std::move(context)) {
+ if (G()->parameters().use_file_db) {
+ file_db_ = G()->td_db()->get_file_db_shared();
+ }
+
+ parent_ = context_->create_reference();
+ next_file_id();
+ next_file_node_id();
+
+ std::vector<string> dirs;
+ auto create_dir = [&](CSlice path) {
+ dirs.push_back(path.str());
+ auto status = mkdir(path, 0750);
+ if (status.is_error()) {
+ auto r_stat = stat(path);
+ if (r_stat.is_ok() && r_stat.ok().is_dir_) {
+ LOG(ERROR) << "mkdir " << tag("path", path) << " failed " << status << ", but directory exists";
+ } else {
+ LOG(ERROR) << "mkdir " << tag("path", path) << " failed " << status;
+ }
+ }
+#if TD_ANDROID
+ FileFd::open(dirs.back() + ".nomedia", FileFd::Create | FileFd::Read).ignore();
+#endif
+ };
+ for (int i = 0; i < file_type_size; i++) {
+ auto path = get_files_dir(FileType(i));
+ create_dir(path);
+ }
+
+ // Create both temp dirs.
+ create_dir(get_files_temp_dir(FileType::Encrypted));
+ create_dir(get_files_temp_dir(FileType::Video));
+
+ G()->td_db()->with_db_path([this](CSlice path) { this->bad_paths_.insert(path.str()); });
+}
+
+void FileManager::init_actor() {
+ file_load_manager_ = create_actor_on_scheduler<FileLoadManager>("FileLoadManager", G()->get_slow_net_scheduler_id(),
+ actor_shared(this), context_->create_reference());
+ file_generate_manager_ = create_actor_on_scheduler<FileGenerateManager>(
+ "FileGenerateManager", G()->get_slow_net_scheduler_id(), context_->create_reference());
+}
+FileManager::~FileManager() {
+ // NB: As FileLoadManager callback is just "this" pointer, this event must be processed immediately.
+ send_closure(std::move(file_load_manager_), &FileLoadManager::close);
+}
+
+string FileManager::fix_file_extension(Slice file_name, Slice file_type, Slice file_extension) {
+ return (file_name.empty() ? file_type : file_name).str() + "." + file_extension.str();
+}
+
+string FileManager::get_file_name(FileType file_type, Slice path) {
+ PathView path_view(path);
+ auto file_name = path_view.file_name();
+ auto extension = path_view.extension();
+ switch (file_type) {
+ case FileType::Thumbnail:
+ if (extension != "jpg" && extension != "jpeg" && extension != "webp") {
+ return fix_file_extension(file_name, "thumbnail", "jpg");
+ }
+ break;
+ case FileType::ProfilePhoto:
+ case FileType::Photo:
+ if (extension != "jpg" && extension != "jpeg" && extension != "gif" && extension != "png" && extension != "tif" &&
+ extension != "bmp") {
+ return fix_file_extension(file_name, "photo", "jpg");
+ }
+ break;
+ case FileType::VoiceNote:
+ if (extension != "ogg" && extension != "oga" && extension != "mp3" && extension != "mpeg3" &&
+ extension != "m4a") {
+ return fix_file_extension(file_name, "voice", "oga");
+ }
+ break;
+ case FileType::Video:
+ case FileType::VideoNote:
+ if (extension != "mov" && extension != "3gp" && extension != "mpeg4" && extension != "mp4") {
+ return fix_file_extension(file_name, "video", "mp4");
+ }
+ break;
+ case FileType::Audio:
+ if (extension != "ogg" && extension != "oga" && extension != "mp3" && extension != "mpeg3" &&
+ extension != "m4a") {
+ return fix_file_extension(file_name, "audio", "mp3");
+ }
+ break;
+ case FileType::Document:
+ case FileType::Sticker:
+ case FileType::Animation:
+ case FileType::Encrypted:
+ case FileType::Temp:
+ case FileType::EncryptedThumbnail:
+ case FileType::Wallpaper:
+ break;
+ default:
+ UNREACHABLE();
+ }
+ return file_name.str();
+}
+
+Status FileManager::check_local_location(FullLocalFileLocation &location, int64 &size) {
+ constexpr int64 MAX_THUMBNAIL_SIZE = 200 * (1 << 10) /* 200KB */;
+ constexpr int64 MAX_FILE_SIZE = 1500 * (1 << 20) /* 1500MB */;
+
+ if (location.path_.empty()) {
+ return Status::Error("File must have non-empty path");
+ }
+ TRY_RESULT(path, realpath(location.path_, true));
+ if (bad_paths_.count(path) != 0) {
+ return Status::Error("Sending of internal database files is forbidden");
+ }
+ location.path_ = std::move(path);
+ TRY_RESULT(stat, stat(location.path_));
+ if (!stat.is_reg_) {
+ return Status::Error("File must be a regular file");
+ }
+ if (stat.size_ < 0) {
+ // TODO is it possible?
+ return Status::Error("File is too big");
+ }
+ if (stat.size_ == 0) {
+ return Status::Error("File must be non-empty");
+ }
+
+ if (size == 0) {
+ size = stat.size_;
+ }
+ if (location.mtime_nsec_ == 0) {
+ LOG(INFO) << "Set file \"" << location.path_ << "\" modification time to " << stat.mtime_nsec_;
+ location.mtime_nsec_ = stat.mtime_nsec_;
+ } else if (location.mtime_nsec_ != stat.mtime_nsec_) {
+ LOG(INFO) << "File \"" << location.path_ << "\" was nodified: old mtime = " << location.mtime_nsec_
+ << ", new mtime = " << stat.mtime_nsec_;
+ return Status::Error(PSLICE() << "File \"" << location.path_ << "\" was modified");
+ }
+ if ((location.file_type_ == FileType::Thumbnail || location.file_type_ == FileType::EncryptedThumbnail) &&
+ size >= MAX_THUMBNAIL_SIZE) {
+ return Status::Error(PSLICE() << "File \"" << location.path_ << "\" is too big for thumbnail "
+ << tag("size", format::as_size(size)));
+ }
+ if (size >= MAX_FILE_SIZE) {
+ return Status::Error(PSLICE() << "File \"" << location.path_ << "\" is too big "
+ << tag("size", format::as_size(size)));
+ }
+ return Status::OK();
+}
+
+static Status check_partial_local_location(const PartialLocalFileLocation &location) {
+ TRY_RESULT(stat, stat(location.path_));
+ if (!stat.is_reg_) {
+ if (stat.is_dir_) {
+ return Status::Error(PSLICE() << "Can't use directory \"" << location.path_ << "\" as a file path");
+ }
+ return Status::Error("File must be a regular file");
+ }
+ // can't check mtime. Hope nobody will mess with this file in our temporary dir.
+ return Status::OK();
+}
+
+Status FileManager::check_local_location(FileNodePtr node) {
+ Status status;
+ if (node->local_.type() == LocalFileLocation::Type::Full) {
+ status = check_local_location(node->local_.full(), node->size_);
+ } else if (node->local_.type() == LocalFileLocation::Type::Partial) {
+ status = check_partial_local_location(node->local_.partial());
+ }
+ if (status.is_error()) {
+ node->set_local_location(LocalFileLocation(), 0);
+ try_flush_node(node);
+ }
+ return status;
+}
+
+FileManager::FileIdInfo *FileManager::get_file_id_info(FileId file_id) {
+ CHECK(0 <= file_id.get() && file_id.get() < static_cast<int32>(file_id_info_.size()))
+ << file_id << " " << file_id_info_.size();
+ return &file_id_info_[file_id.get()];
+}
+
+FileId FileManager::dup_file_id(FileId file_id) {
+ int32 file_node_id;
+ auto *file_node = get_file_node_raw(file_id, &file_node_id);
+ if (!file_node) {
+ return FileId();
+ }
+ auto result = create_file_id(file_node_id, file_node);
+ LOG(INFO) << "Dup file " << file_id << " to " << result;
+ return result;
+}
+
+FileId FileManager::create_file_id(int32 file_node_id, FileNode *file_node) {
+ auto file_id = next_file_id();
+ get_file_id_info(file_id)->node_id_ = file_node_id;
+ file_node->file_ids_.push_back(file_id);
+ return file_id;
+}
+void FileManager::try_forget_file_id(FileId file_id) {
+ auto *info = get_file_id_info(file_id);
+ if (info->send_updates_flag_ || info->pin_flag_) {
+ return;
+ }
+ auto file_node = get_file_node(file_id);
+ if (file_node->main_file_id_ == file_id) {
+ return;
+ }
+
+ LOG(INFO) << "Forget file " << file_id;
+ auto it = std::find(file_node->file_ids_.begin(), file_node->file_ids_.end(), file_id);
+ CHECK(it != file_node->file_ids_.end());
+ file_node->file_ids_.erase(it);
+ *info = FileIdInfo();
+ empty_file_ids_.push_back(file_id.get());
+}
+
+FileId FileManager::register_empty(FileType type) {
+ return register_local(FullLocalFileLocation(type, "", 0), DialogId(), 0, false, true).ok();
+}
+
+void FileManager::on_file_unlink(const FullLocalFileLocation &location) {
+ auto it = local_location_to_file_id_.find(location);
+ if (it == local_location_to_file_id_.end()) {
+ return;
+ }
+ auto file_id = it->second;
+ auto file_node = get_sync_file_node(file_id);
+ CHECK(file_node);
+ file_node->set_local_location(LocalFileLocation(), 0);
+ try_flush_node_info(file_node);
+}
+
+Result<FileId> FileManager::register_local(FullLocalFileLocation location, DialogId owner_dialog_id, int64 size,
+ bool get_by_hash, bool force) {
+ // TODO: use get_by_hash
+ FileData data;
+ data.local_ = LocalFileLocation(std::move(location));
+ data.owner_dialog_id_ = owner_dialog_id;
+ data.size_ = size;
+ return register_file(std::move(data), FileLocationSource::None /*won't be used*/, "register_local", force);
+}
+
+FileId FileManager::register_remote(const FullRemoteFileLocation &location, FileLocationSource file_location_source,
+ DialogId owner_dialog_id, int64 size, int64 expected_size, string name) {
+ FileData data;
+ data.remote_ = RemoteFileLocation(location);
+ data.owner_dialog_id_ = owner_dialog_id;
+ data.size_ = size;
+ data.expected_size_ = expected_size;
+ data.remote_name_ = std::move(name);
+ return register_file(std::move(data), file_location_source, "register_remote", false).move_as_ok();
+}
+
+FileId FileManager::register_url(string url, FileType file_type, FileLocationSource file_location_source,
+ DialogId owner_dialog_id) {
+ auto file_id = register_generate(file_type, file_location_source, url, "#url#", owner_dialog_id, 0).ok();
+ auto file_node = get_file_node(file_id);
+ CHECK(file_node);
+ file_node->set_url(url);
+ return file_id;
+}
+
+Result<FileId> FileManager::register_generate(FileType file_type, FileLocationSource file_location_source,
+ string original_path, string conversion, DialogId owner_dialog_id,
+ int64 expected_size) {
+ FileData data;
+ data.generate_ = make_unique<FullGenerateFileLocation>(file_type, std::move(original_path), std::move(conversion));
+ data.owner_dialog_id_ = owner_dialog_id;
+ data.expected_size_ = expected_size;
+ return register_file(std::move(data), file_location_source, "register_generate", false);
+}
+
+Result<FileId> FileManager::register_file(FileData data, FileLocationSource file_location_source, const char *source,
+ bool force) {
+ bool has_remote = data.remote_.type() == RemoteFileLocation::Type::Full;
+ bool has_generate = data.generate_ != nullptr;
+ if (data.local_.type() == LocalFileLocation::Type::Full && !force) {
+ if (file_location_source == FileLocationSource::FromDb) {
+ PathView path_view(data.local_.full().path_);
+ if (path_view.is_relative()) {
+ data.local_.full().path_ = get_files_base_dir(data.local_.full().file_type_) + data.local_.full().path_;
+ }
+ }
+
+ auto status = check_local_location(data.local_.full(), data.size_);
+ if (status.is_error()) {
+ LOG(WARNING) << "Invalid local location: " << status << " from " << source;
+ data.local_ = LocalFileLocation();
+ if (data.remote_.type() == RemoteFileLocation::Type::Partial) {
+ data.remote_ = {};
+ }
+
+ if (!has_remote && !has_generate) {
+ return std::move(status);
+ }
+ }
+ }
+ bool has_local = data.local_.type() == LocalFileLocation::Type::Full;
+ bool has_location = has_local || has_remote || has_generate;
+ if (!has_location) {
+ return Status::Error("No location");
+ }
+
+ FileId file_id = next_file_id();
+
+ LOG(INFO) << "Register file data " << data << " as " << file_id << " from " << source;
+ // create FileNode
+ auto file_node_id = next_file_node_id();
+ auto &node = file_nodes_[file_node_id];
+ node = std::make_unique<FileNode>(std::move(data.local_), std::move(data.remote_), std::move(data.generate_),
+ data.size_, data.expected_size_, std::move(data.remote_name_), std::move(data.url_),
+ data.owner_dialog_id_, std::move(data.encryption_key_), file_id,
+ static_cast<int8>(has_remote));
+ node->remote_source_ = file_location_source;
+ node->pmc_id_ = data.pmc_id_;
+ get_file_id_info(file_id)->node_id_ = file_node_id;
+ node->file_ids_.push_back(file_id);
+
+ FileView file_view(get_file_node(file_id));
+
+ std::vector<FileId> to_merge;
+ auto register_location = [&](const auto &location, auto &mp) {
+ auto &other_id = mp[location];
+ if (other_id.empty()) {
+ other_id = file_id;
+ get_file_id_info(file_id)->pin_flag_ = true;
+ return true;
+ } else {
+ to_merge.push_back(other_id);
+ return false;
+ }
+ };
+ bool new_remote = false;
+ int32 remote_key = 0;
+ if (file_view.has_remote_location()) {
+ RemoteInfo info{file_view.remote_location(), file_id};
+ remote_key = remote_location_info_.add(info);
+ auto &stored_info = remote_location_info_.get(remote_key);
+ if (stored_info.file_id_ == file_id) {
+ get_file_id_info(file_id)->pin_flag_ = true;
+ new_remote = true;
+ } else {
+ to_merge.push_back(stored_info.file_id_);
+ if (stored_info.remote_ == file_view.remote_location() &&
+ stored_info.remote_.get_access_hash() != file_view.remote_location().get_access_hash() &&
+ file_location_source == FileLocationSource::FromServer) {
+ stored_info.remote_ = file_view.remote_location();
+ }
+ }
+ }
+ bool new_local = false;
+ if (file_view.has_local_location()) {
+ new_local = register_location(file_view.local_location(), local_location_to_file_id_);
+ }
+ bool new_generate = false;
+ if (file_view.has_generate_location()) {
+ new_generate = register_location(file_view.generate_location(), generate_location_to_file_id_);
+ }
+ std::sort(to_merge.begin(), to_merge.end());
+ to_merge.erase(std::unique(to_merge.begin(), to_merge.end()), to_merge.end());
+
+ int new_cnt = new_remote + new_local + new_generate;
+ if (data.pmc_id_ == 0 && file_db_ && new_cnt > 0) {
+ node->need_load_from_pmc_ = true;
+ }
+ bool no_sync_merge = to_merge.size() == 1 && new_cnt == 0;
+ for (auto id : to_merge) {
+ // may invalidate node
+ merge(file_id, id, no_sync_merge).ignore();
+ }
+
+ try_flush_node(get_file_node(file_id));
+ auto main_file_id = get_file_node(file_id)->main_file_id_;
+ try_forget_file_id(file_id);
+ return FileId(main_file_id.get(), remote_key);
+}
+
+// 0 -- choose x
+// 1 -- choose y
+// 2 -- choose any
+static int merge_choose(const LocalFileLocation &x, const LocalFileLocation &y) {
+ int32 x_type = static_cast<int32>(x.type());
+ int32 y_type = static_cast<int32>(y.type());
+ if (x_type != y_type) {
+ return x_type < y_type;
+ }
+ return 2;
+}
+
+static int merge_choose(const RemoteFileLocation &x, int8 x_source, const RemoteFileLocation &y, int8 y_source) {
+ int32 x_type = static_cast<int32>(x.type());
+ int32 y_type = static_cast<int32>(y.type());
+ if (x_type != y_type) {
+ return x_type < y_type;
+ }
+ // If access_hash changed use a newer one
+ if (x.type() == RemoteFileLocation::Type::Full) {
+ if (x.full().get_access_hash() != y.full().get_access_hash()) {
+ return x_source < y_source;
+ }
+ }
+ return 2;
+}
+static int merge_choose(const unique_ptr<FullGenerateFileLocation> &x, const unique_ptr<FullGenerateFileLocation> &y) {
+ int x_type = static_cast<int>(x != nullptr);
+ int y_type = static_cast<int>(y != nullptr);
+ if (x_type != y_type) {
+ return x_type < y_type;
+ }
+ return 2;
+}
+
+// -1 -- error
+static int merge_choose_size(int64 x, int64 y) {
+ if (x == 0) {
+ return 1;
+ }
+ if (y == 0) {
+ return 0;
+ }
+ if (x != y) {
+ return -1;
+ }
+ return 2;
+}
+static int merge_choose_expected_size(int64 x, int64 y) {
+ if (x == 0) {
+ return 1;
+ }
+ if (y == 0) {
+ return 0;
+ }
+ return 2;
+}
+
+static int merge_choose_name(Slice x, Slice y) {
+ if (x.empty() != y.empty()) {
+ return x.empty() > y.empty();
+ }
+ return 2;
+}
+
+static int merge_choose_owner(DialogId x, DialogId y) {
+ if (x.is_valid() != y.is_valid()) {
+ return x.is_valid() < y.is_valid();
+ }
+ return 2;
+}
+
+static int merge_choose_main_file_id(FileId a, int8 a_priority, FileId b, int8 b_priority) {
+ if (a_priority != b_priority) {
+ return a_priority < b_priority;
+ }
+ return a.get() > b.get();
+}
+
+static int merge_choose_encryption_key(const FileEncryptionKey &a, const FileEncryptionKey &b) {
+ if (a.empty() != b.empty()) {
+ return a.empty() > b.empty();
+ }
+ if (a.key_iv_ != b.key_iv_) {
+ return -1;
+ }
+ return 2;
+}
+
+void FileManager::cancel_download(FileNodePtr node) {
+ if (node->download_id_ == 0) {
+ return;
+ }
+ send_closure(file_load_manager_, &FileLoadManager::cancel, node->download_id_);
+ node->download_id_ = 0;
+ node->is_download_started_ = false;
+ node->set_download_priority(0);
+}
+
+void FileManager::cancel_upload(FileNodePtr node) {
+ if (node->upload_id_ == 0) {
+ return;
+ }
+ send_closure(file_load_manager_, &FileLoadManager::cancel, node->upload_id_);
+ node->upload_id_ = 0;
+ node->set_upload_priority(0);
+}
+
+void FileManager::cancel_generate(FileNodePtr node) {
+ if (node->generate_id_ == 0) {
+ return;
+ }
+ send_closure(file_generate_manager_, &FileGenerateManager::cancel, node->generate_id_);
+ node->generate_id_ = 0;
+ node->generate_was_update_ = false;
+ node->set_generate_priority(0, 0);
+}
+
+Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sync) {
+ LOG(INFO) << x_file_id << " VS " << y_file_id;
+
+ if (!x_file_id.is_valid()) {
+ return Status::Error("First file_id is invalid");
+ }
+ FileNodePtr x_node = no_sync ? get_file_node(x_file_id) : get_sync_file_node(x_file_id);
+ if (!x_node) {
+ return Status::Error(PSLICE() << "Can't merge files. First id is invalid: " << x_file_id << " and " << y_file_id);
+ }
+
+ if (!y_file_id.is_valid()) {
+ return x_node->main_file_id_;
+ }
+ FileNodePtr y_node = get_file_node(y_file_id);
+ if (!y_node) {
+ return Status::Error(PSLICE() << "Can't merge files. Second id is invalid: " << x_file_id << " and " << y_file_id);
+ }
+
+ if (x_file_id == x_node->upload_pause_) {
+ x_node->upload_pause_ = FileId();
+ }
+ if (x_node.get() == y_node.get()) {
+ return x_node->main_file_id_;
+ }
+ if (y_file_id == y_node->upload_pause_) {
+ y_node->upload_pause_ = FileId();
+ }
+
+ if (x_node->remote_.type() == RemoteFileLocation::Type::Full &&
+ y_node->remote_.type() == RemoteFileLocation::Type::Full &&
+ x_node->remote_.full().get_dc_id() != y_node->remote_.full().get_dc_id()) {
+ LOG(ERROR) << "File remote location was changed from " << y_node->remote_.full() << " to "
+ << x_node->remote_.full();
+ }
+
+ FileNodePtr nodes[] = {x_node, y_node, x_node};
+ FileNodeId node_ids[] = {get_file_id_info(x_file_id)->node_id_, get_file_id_info(y_file_id)->node_id_};
+
+ int local_i = merge_choose(x_node->local_, y_node->local_);
+ int remote_i = merge_choose(x_node->remote_, static_cast<int8>(x_node->remote_source_), y_node->remote_,
+ static_cast<int8>(y_node->remote_source_));
+ int generate_i = merge_choose(x_node->generate_, y_node->generate_);
+ int size_i = merge_choose_size(x_node->size_, y_node->size_);
+ int expected_size_i = merge_choose_expected_size(x_node->expected_size_, y_node->expected_size_);
+ int remote_name_i = merge_choose_name(x_node->remote_name_, y_node->remote_name_);
+ int url_i = merge_choose_name(x_node->url_, y_node->url_);
+ int owner_i = merge_choose_owner(x_node->owner_dialog_id_, y_node->owner_dialog_id_);
+ int encryption_key_i = merge_choose_encryption_key(x_node->encryption_key_, y_node->encryption_key_);
+ int main_file_id_i = merge_choose_main_file_id(x_node->main_file_id_, x_node->main_file_id_priority_,
+ y_node->main_file_id_, y_node->main_file_id_priority_);
+
+ if (size_i == -1) {
+ return Status::Error(PSLICE() << "Can't merge files. Different size: " << x_node->size_ << " and "
+ << y_node->size_);
+ }
+ if (encryption_key_i == -1) {
+ if (nodes[remote_i]->remote_.type() == RemoteFileLocation::Type::Full &&
+ nodes[local_i]->local_.type() != LocalFileLocation::Type::Partial) {
+ //???
+ LOG(ERROR) << "Different encryption key in files, but go Choose same key as remote location";
+ encryption_key_i = remote_i;
+ } else {
+ return Status::Error("Can't merge files. Different encryption keys");
+ }
+ }
+
+ int node_i = std::make_tuple(y_node->pmc_id_, y_node->file_ids_.size(), main_file_id_i == 1) >
+ std::make_tuple(x_node->pmc_id_, x_node->file_ids_.size(), main_file_id_i == 0);
+
+ auto other_node_i = 1 - node_i;
+ FileNodePtr node = nodes[node_i];
+ FileNodePtr other_node = nodes[other_node_i];
+ auto file_view = FileView(node);
+
+ LOG(INFO) << "x_node->pmc_id_ = " << x_node->pmc_id_ << ", y_node->pmc_id_ = " << y_node->pmc_id_
+ << ", x_node_size = " << x_node->file_ids_.size() << ", y_node_size = " << y_node->file_ids_.size()
+ << ", node_i = " << node_i << ", local_i = " << local_i << ", remote_i = " << remote_i
+ << ", generate_i = " << generate_i << ", size_i = " << size_i << ", remote_name_i = " << remote_name_i
+ << ", url_i = " << url_i << ", owner_i = " << owner_i << ", encryption_key_i = " << encryption_key_i
+ << ", main_file_id_i = " << main_file_id_i;
+ if (local_i == other_node_i) {
+ cancel_download(node);
+ node->set_local_location(other_node->local_, other_node->local_ready_size_);
+ node->download_id_ = other_node->download_id_;
+ node->is_download_started_ |= other_node->is_download_started_;
+ node->set_download_priority(other_node->download_priority_);
+ other_node->download_id_ = 0;
+ other_node->is_download_started_ = false;
+ other_node->download_priority_ = 0;
+
+ //cancel_generate(node);
+ //node->set_generate_location(std::move(other_node->generate_));
+ //node->generate_id_ = other_node->generate_id_;
+ //node->set_generate_priority(other_node->generate_download_priority_, other_node->generate_upload_priority_);
+ //other_node->generate_id_ = 0;
+ //other_node->generate_was_update_ = false;
+ //other_node->generate_priority_ = 0;
+ //other_node->generate_download_priority_ = 0;
+ //other_node->generate_upload_priority_ = 0;
+ } else {
+ cancel_download(other_node);
+ //cancel_generate(other_node);
+ }
+
+ if (remote_i == other_node_i) {
+ cancel_upload(node);
+ node->set_remote_location(other_node->remote_, other_node->remote_source_, other_node->remote_ready_size_);
+ node->upload_id_ = other_node->upload_id_;
+ node->set_upload_priority(other_node->upload_priority_);
+ node->upload_pause_ = other_node->upload_pause_;
+ other_node->upload_id_ = 0;
+ other_node->upload_priority_ = 0;
+ other_node->upload_pause_ = FileId();
+ } else {
+ cancel_upload(other_node);
+ }
+
+ if (generate_i == other_node_i) {
+ cancel_generate(node);
+ node->set_generate_location(std::move(other_node->generate_));
+ node->generate_id_ = other_node->generate_id_;
+ node->set_generate_priority(other_node->generate_download_priority_, other_node->generate_upload_priority_);
+ other_node->generate_id_ = 0;
+ other_node->generate_priority_ = 0;
+ other_node->generate_download_priority_ = 0;
+ other_node->generate_upload_priority_ = 0;
+ } else {
+ cancel_generate(other_node);
+ }
+
+ if (size_i == other_node_i) {
+ node->set_size(other_node->size_);
+ }
+
+ if (expected_size_i == other_node_i) {
+ node->set_expected_size(other_node->expected_size_);
+ }
+
+ if (remote_name_i == other_node_i) {
+ node->set_remote_name(other_node->remote_name_);
+ }
+
+ if (url_i == other_node_i) {
+ node->set_url(other_node->url_);
+ }
+
+ if (owner_i == other_node_i) {
+ node->set_owner_dialog_id(other_node->owner_dialog_id_);
+ }
+
+ if (encryption_key_i == other_node_i) {
+ node->set_encryption_key(other_node->encryption_key_);
+ nodes[node_i]->set_encryption_key(nodes[encryption_key_i]->encryption_key_);
+ }
+ node->need_load_from_pmc_ |= other_node->need_load_from_pmc_;
+ node->can_search_locally_ &= other_node->can_search_locally_;
+
+ if (main_file_id_i == other_node_i) {
+ node->main_file_id_ = other_node->main_file_id_;
+ node->main_file_id_priority_ = other_node->main_file_id_priority_;
+ }
+
+ bool send_updates_flag = false;
+ auto other_pmc_id = other_node->pmc_id_;
+ node->file_ids_.insert(node->file_ids_.end(), other_node->file_ids_.begin(), other_node->file_ids_.end());
+
+ for (auto file_id : other_node->file_ids_) {
+ auto file_id_info = get_file_id_info(file_id);
+ CHECK(file_id_info->node_id_ == node_ids[other_node_i])
+ << node_ids[node_i] << " " << node_ids[other_node_i] << " " << file_id << " " << file_id_info->node_id_;
+ file_id_info->node_id_ = node_ids[node_i];
+ send_updates_flag |= file_id_info->send_updates_flag_;
+ }
+ other_node = {};
+
+ if (send_updates_flag) {
+ // node might not changed, but other_node might changed, so we need to send update anyway
+ VLOG(update_file) << "File " << node->main_file_id_ << " has been merged";
+ node->on_info_changed();
+ }
+
+ // Check is some download/upload queries are ready
+ for (auto file_id : vector<FileId>(node->file_ids_)) {
+ auto *info = get_file_id_info(file_id);
+ if (info->download_priority_ != 0 && file_view.has_local_location()) {
+ info->download_priority_ = 0;
+ if (info->download_callback_) {
+ info->download_callback_->on_download_ok(file_id);
+ info->download_callback_.reset();
+ }
+ }
+ if (info->upload_priority_ != 0 && file_view.has_remote_location()) {
+ info->upload_priority_ = 0;
+ if (info->upload_callback_) {
+ info->upload_callback_->on_upload_ok(file_id, nullptr);
+ info->upload_callback_.reset();
+ }
+ }
+ }
+
+ file_nodes_[node_ids[other_node_i]] = nullptr;
+
+ run_generate(node);
+ run_download(node);
+ run_upload(node, {});
+
+ if (other_pmc_id != 0) {
+ // node might not changed, but we need to merge nodes in pmc anyway
+ node->on_pmc_changed();
+ }
+ try_flush_node(node, node_i != remote_i, node_i != local_i, node_i != generate_i, other_pmc_id);
+
+ return node->main_file_id_;
+}
+
+void FileManager::try_flush_node(FileNodePtr node, bool new_remote, bool new_local, bool new_generate,
+ FileDbId other_pmc_id) {
+ if (node->need_pmc_flush()) {
+ if (file_db_) {
+ load_from_pmc(node, true, true, true);
+ flush_to_pmc(node, new_remote, new_local, new_generate);
+ if (other_pmc_id != 0 && node->pmc_id_ != other_pmc_id) {
+ file_db_->set_file_data_ref(other_pmc_id, node->pmc_id_);
+ }
+ }
+ node->on_pmc_flushed();
+ }
+
+ try_flush_node_info(node);
+}
+
+void FileManager::try_flush_node_info(FileNodePtr node) {
+ if (node->need_info_flush()) {
+ for (auto file_id : vector<FileId>(node->file_ids_)) {
+ auto *info = get_file_id_info(file_id);
+ if (info->send_updates_flag_) {
+ VLOG(update_file) << "Send UpdateFile about file " << file_id;
+ context_->on_file_updated(file_id);
+ }
+ }
+ node->on_info_flushed();
+ }
+}
+
+void FileManager::clear_from_pmc(FileNodePtr node) {
+ if (!file_db_) {
+ return;
+ }
+ if (node->pmc_id_ == 0) {
+ return;
+ }
+
+ LOG(INFO) << "Delete files " << format::as_array(node->file_ids_) << " from pmc";
+ FileData data;
+ auto file_view = FileView(node);
+ if (file_view.has_local_location()) {
+ data.local_ = node->local_;
+ }
+ if (file_view.has_remote_location()) {
+ data.remote_ = node->remote_;
+ }
+ if (file_view.has_generate_location()) {
+ data.generate_ = std::make_unique<FullGenerateFileLocation>(*node->generate_);
+ }
+ file_db_->clear_file_data(node->pmc_id_, data);
+ node->pmc_id_ = 0;
+}
+
+void FileManager::flush_to_pmc(FileNodePtr node, bool new_remote, bool new_local, bool new_generate) {
+ if (!file_db_) {
+ return;
+ }
+ FileView view(node);
+ bool create_flag = false;
+ if (node->pmc_id_ == 0) {
+ create_flag = true;
+ node->pmc_id_ = file_db_->create_pmc_id();
+ }
+
+ FileData data;
+ data.pmc_id_ = node->pmc_id_;
+ data.local_ = node->local_;
+ if (data.local_.type() == LocalFileLocation::Type::Full) {
+ prepare_path_for_pmc(data.local_.full().file_type_, data.local_.full().path_);
+ }
+ data.remote_ = node->remote_;
+ if (node->generate_ != nullptr && !begins_with(node->generate_->conversion_, "#file_id#")) {
+ data.generate_ = std::make_unique<FullGenerateFileLocation>(*node->generate_);
+ }
+
+ // TODO: not needed when GenerateLocation has constant convertion
+ if (data.remote_.type() != RemoteFileLocation::Type::Full && data.local_.type() != LocalFileLocation::Type::Full) {
+ data.local_ = LocalFileLocation();
+ data.remote_ = RemoteFileLocation();
+ }
+
+ data.size_ = node->size_;
+ data.expected_size_ = node->expected_size_;
+ data.remote_name_ = node->remote_name_;
+ data.encryption_key_ = node->encryption_key_;
+ data.url_ = node->url_;
+ data.owner_dialog_id_ = node->owner_dialog_id_;
+
+ file_db_->set_file_data(node->pmc_id_, data, (create_flag || new_remote), (create_flag || new_local),
+ (create_flag || new_generate));
+}
+
+FileNode *FileManager::get_file_node_raw(FileId file_id, FileNodeId *file_node_id) {
+ if (file_id.get() <= 0 || file_id.get() >= static_cast<int32>(file_id_info_.size())) {
+ return nullptr;
+ }
+ FileNodeId node_id = file_id_info_[file_id.get()].node_id_;
+ if (node_id == 0) {
+ return nullptr;
+ }
+ if (file_node_id != nullptr) {
+ *file_node_id = node_id;
+ }
+ return file_nodes_[node_id].get();
+}
+
+FileNodePtr FileManager::get_sync_file_node(FileId file_id) {
+ auto file_node = get_file_node(file_id);
+ if (!file_node) {
+ return {};
+ }
+ load_from_pmc(file_node, true, true, true);
+ return file_node;
+}
+
+void FileManager::load_from_pmc(FileNodePtr node, bool new_remote, bool new_local, bool new_generate) {
+ if (!node->need_load_from_pmc_) {
+ return;
+ }
+ auto file_id = node->main_file_id_;
+ node->need_load_from_pmc_ = false;
+ if (!file_db_) {
+ return;
+ }
+ auto file_view = get_file_view(file_id);
+
+ FullRemoteFileLocation remote;
+ FullLocalFileLocation local;
+ FullGenerateFileLocation generate;
+ new_remote &= file_view.has_remote_location();
+ if (new_remote) {
+ remote = file_view.remote_location();
+ }
+ new_local &= file_view.has_local_location();
+ if (new_local) {
+ local = get_file_view(file_id).local_location();
+ prepare_path_for_pmc(local.file_type_, local.path_);
+ }
+ new_generate &= file_view.has_generate_location();
+ if (new_generate) {
+ generate = file_view.generate_location();
+ }
+
+ LOG(INFO) << "Load from pmc " << file_id << "/" << file_view.file_id() << ", new_remote = " << new_remote
+ << ", new_local = " << new_local << ", new_generate = " << new_generate;
+ auto load = [&](auto location) {
+ TRY_RESULT(file_data, file_db_->get_file_data_sync(location));
+ TRY_RESULT(new_file_id, register_file(std::move(file_data), FileLocationSource::FromDb, "load_from_pmc", false));
+ TRY_RESULT(main_file_id, merge(file_id, new_file_id));
+ file_id = main_file_id;
+ return Status::OK();
+ };
+ if (new_remote) {
+ load(remote);
+ }
+ if (new_local) {
+ load(local);
+ }
+ if (new_generate) {
+ load(generate);
+ }
+ return;
+}
+
+bool FileManager::set_encryption_key(FileId file_id, FileEncryptionKey key) {
+ auto node = get_sync_file_node(file_id);
+ if (!node) {
+ return false;
+ }
+ auto view = FileView(node);
+ if (view.has_local_location() && view.has_remote_location()) {
+ return false;
+ }
+ if (!node->encryption_key_.empty()) {
+ return false;
+ }
+ node->set_encryption_key(std::move(key));
+ try_flush_node(node);
+ return true;
+}
+
+bool FileManager::set_content(FileId file_id, BufferSlice bytes) {
+ auto node = get_sync_file_node(file_id);
+ if (!node) {
+ return false;
+ }
+
+ if (node->local_.type() == LocalFileLocation::Type::Full) {
+ // There was no download so we don't need an update
+ return true;
+ }
+
+ if (node->download_priority_ == FROM_BYTES_PRIORITY) {
+ return true;
+ }
+
+ cancel_download(node);
+
+ auto *file_info = get_file_id_info(file_id);
+ file_info->download_priority_ = FROM_BYTES_PRIORITY;
+
+ node->set_download_priority(FROM_BYTES_PRIORITY);
+
+ QueryId id = queries_container_.create(Query{file_id, Query::SetContent});
+ node->download_id_ = id;
+ node->is_download_started_ = true;
+ send_closure(file_load_manager_, &FileLoadManager::from_bytes, id, node->remote_.full().file_type_, std::move(bytes),
+ node->suggested_name());
+ return true;
+}
+
+void FileManager::get_content(FileId file_id, Promise<BufferSlice> promise) {
+ auto node = get_sync_file_node(file_id);
+ if (!node) {
+ return promise.set_error(Status::Error("Unknown file_id"));
+ }
+ auto status = check_local_location(node);
+ status.ignore();
+
+ auto file_view = FileView(node);
+ if (!file_view.has_local_location()) {
+ return promise.set_error(Status::Error("No local location"));
+ }
+
+ send_closure(file_load_manager_, &FileLoadManager::get_content, node->local_.full(), std::move(promise));
+}
+
+void FileManager::delete_file(FileId file_id, Promise<Unit> promise, const char *source) {
+ LOG(INFO) << "Trying to delete file " << file_id << " from " << source;
+ auto node = get_sync_file_node(file_id);
+ if (!node) {
+ return promise.set_value(Unit());
+ }
+
+ auto file_view = FileView(node);
+
+ // TODO: review delete condition
+ if (file_view.has_local_location()) {
+ if (begins_with(file_view.local_location().path_, get_files_dir(file_view.get_type()))) {
+ LOG(INFO) << "Unlink file " << file_id << " at " << file_view.local_location().path_;
+ clear_from_pmc(node);
+
+ unlink(file_view.local_location().path_).ignore();
+ context_->on_new_file(-file_view.size());
+ node->set_local_location(LocalFileLocation(), 0);
+ try_flush_node(node);
+ }
+ } else {
+ if (file_view.get_type() == FileType::Encrypted) {
+ clear_from_pmc(node);
+ }
+ if (node->local_.type() == LocalFileLocation::Type::Partial) {
+ LOG(INFO) << "Unlink partial file " << file_id << " at " << node->local_.partial().path_;
+ unlink(node->local_.partial().path_).ignore();
+ node->set_local_location(LocalFileLocation(), 0);
+ try_flush_node(node);
+ }
+ }
+
+ promise.set_value(Unit());
+}
+
+void FileManager::download(FileId file_id, std::shared_ptr<DownloadCallback> callback, int32 new_priority) {
+ auto node = get_sync_file_node(file_id);
+ if (!node) {
+ if (callback) {
+ callback->on_download_error(file_id, Status::Error("File not found"));
+ }
+ return;
+ }
+
+ if (node->local_.type() == LocalFileLocation::Type::Full) {
+ auto status = check_local_location(node);
+ if (status.is_error()) {
+ LOG(WARNING) << "Need to redownload file " << file_id << ": " << status.error();
+ } else {
+ if (callback) {
+ callback->on_download_ok(file_id);
+ }
+ return;
+ }
+ } else if (node->local_.type() == LocalFileLocation::Type::Partial) {
+ auto status = check_local_location(node);
+ if (status.is_error()) {
+ LOG(WARNING) << "Need to download file " << file_id << " from beginning: " << status.error();
+ }
+ }
+
+ FileView file_view(node);
+ if (!file_view.can_download_from_server() && !file_view.can_generate()) {
+ if (callback) {
+ callback->on_download_error(file_id, Status::Error("Can't download or generate file"));
+ }
+ return;
+ }
+
+ if (new_priority == -1) {
+ if (node->is_download_started_) {
+ return;
+ }
+ new_priority = 0;
+ }
+
+ auto *file_info = get_file_id_info(file_id);
+ CHECK(new_priority == 0 || callback);
+ file_info->download_priority_ = narrow_cast<int8>(new_priority);
+ file_info->download_callback_ = std::move(callback);
+ // TODO: send current progress?
+
+ run_generate(node);
+ run_download(node);
+
+ try_flush_node(node);
+}
+
+void FileManager::run_download(FileNodePtr node) {
+ if (node->need_load_from_pmc_) {
+ return;
+ }
+ if (node->generate_id_) {
+ return;
+ }
+ auto file_view = FileView(node);
+ if (!file_view.can_download_from_server()) {
+ return;
+ }
+ int8 priority = 0;
+ for (auto id : node->file_ids_) {
+ auto *info = get_file_id_info(id);
+ if (info->download_priority_ > priority) {
+ priority = info->download_priority_;
+ }
+ }
+
+ auto old_priority = node->download_priority_;
+ node->set_download_priority(priority);
+
+ if (priority == 0) {
+ if (old_priority != 0) {
+ cancel_download(node);
+ }
+ return;
+ }
+
+ if (old_priority != 0) {
+ CHECK(node->download_id_ != 0);
+ send_closure(file_load_manager_, &FileLoadManager::update_priority, node->download_id_, priority);
+ return;
+ }
+
+ CHECK(node->download_id_ == 0);
+ CHECK(!node->file_ids_.empty());
+ auto file_id = node->file_ids_.back();
+ QueryId id = queries_container_.create(Query{file_id, Query::Download});
+ node->download_id_ = id;
+ node->is_download_started_ = false;
+ send_closure(file_load_manager_, &FileLoadManager::download, id, node->remote_.full(), node->local_, node->size_,
+ node->suggested_name(), node->encryption_key_, node->can_search_locally_, priority);
+}
+
+void FileManager::resume_upload(FileId file_id, std::vector<int> bad_parts, std::shared_ptr<UploadCallback> callback,
+ int32 new_priority, uint64 upload_order) {
+ LOG(INFO) << "Resume upload of file " << file_id << " with priority " << new_priority;
+
+ auto node = get_sync_file_node(file_id);
+ if (!node) {
+ if (callback) {
+ callback->on_upload_error(file_id, Status::Error("Wrong file id to upload"));
+ }
+ return;
+ }
+ if (node->upload_pause_ == file_id) {
+ node->upload_pause_ = FileId();
+ }
+ FileView file_view(node);
+ if (file_view.has_remote_location() && file_view.get_type() != FileType::Thumbnail &&
+ file_view.get_type() != FileType::EncryptedThumbnail) {
+ if (callback) {
+ callback->on_upload_ok(file_id, nullptr);
+ }
+ return;
+ }
+
+ if (file_view.has_local_location()) {
+ auto status = check_local_location(node);
+ if (status.is_error()) {
+ LOG(INFO) << "Full local location of file " << file_id << " for upload is invalid: " << status;
+ }
+ }
+
+ if (!file_view.has_local_location() && !file_view.has_generate_location()) {
+ if (callback) {
+ callback->on_upload_error(file_id, Status::Error("Need full local (or generate) location for upload"));
+ }
+ return;
+ }
+
+ auto *file_info = get_file_id_info(file_id);
+ CHECK(new_priority == 0 || callback);
+ file_info->upload_order_ = upload_order;
+ file_info->upload_priority_ = narrow_cast<int8>(new_priority);
+ file_info->upload_callback_ = std::move(callback);
+ // TODO: send current progress?
+
+ run_generate(node);
+ run_upload(node, std::move(bad_parts));
+ try_flush_node(node);
+}
+
+bool FileManager::delete_partial_remote_location(FileId file_id) {
+ auto node = get_sync_file_node(file_id);
+ if (!node) {
+ LOG(INFO) << "Wrong file id " << file_id;
+ return false;
+ }
+ if (node->upload_pause_ == file_id) {
+ node->upload_pause_ = FileId();
+ }
+ if (node->remote_.type() == RemoteFileLocation::Type::Full) {
+ LOG(INFO) << "File " << file_id << " is already uploaded";
+ return true;
+ }
+
+ node->set_remote_location(RemoteFileLocation(), FileLocationSource::None, 0);
+ auto *file_info = get_file_id_info(file_id);
+ file_info->upload_priority_ = 0;
+
+ if (node->local_.type() != LocalFileLocation::Type::Full) {
+ LOG(INFO) << "Need full local location to upload file " << file_id;
+ return false;
+ }
+
+ auto status = check_local_location(node);
+ if (status.is_error()) {
+ LOG(INFO) << "Need full local location to upload file " << file_id << ": " << status;
+ return false;
+ }
+
+ run_upload(node, std::vector<int>());
+ try_flush_node(node);
+ return true;
+}
+
+void FileManager::external_file_generate_progress(int64 id, int32 expected_size, int32 local_prefix_size,
+ Promise<> promise) {
+ send_closure(file_generate_manager_, &FileGenerateManager::external_file_generate_progress, id, expected_size,
+ local_prefix_size, std::move(promise));
+}
+void FileManager::external_file_generate_finish(int64 id, Status status, Promise<> promise) {
+ send_closure(file_generate_manager_, &FileGenerateManager::external_file_generate_finish, id, std::move(status),
+ std::move(promise));
+}
+
+void FileManager::run_generate(FileNodePtr node) {
+ if (node->need_load_from_pmc_) {
+ return;
+ }
+ FileView file_view(node);
+ if (file_view.has_local_location() || file_view.can_download_from_server() || !file_view.can_generate()) {
+ return;
+ }
+
+ int8 download_priority = 0;
+ int8 upload_priority = 0;
+ FileId file_id = node->main_file_id_;
+ for (auto id : node->file_ids_) {
+ auto *info = get_file_id_info(id);
+ if (info->download_priority_ > download_priority) {
+ download_priority = info->download_priority_;
+ if (download_priority > upload_priority) {
+ file_id = id;
+ }
+ }
+ if (info->upload_priority_ > upload_priority) {
+ upload_priority = info->upload_priority_;
+ if (upload_priority > download_priority) {
+ file_id = id;
+ }
+ }
+ }
+
+ auto old_priority = node->generate_priority_;
+ node->set_generate_priority(download_priority, upload_priority);
+
+ if (node->generate_priority_ == 0) {
+ if (old_priority != 0) {
+ LOG(INFO) << "Cancel file " << file_id << " generation";
+ cancel_generate(node);
+ }
+ return;
+ }
+
+ if (old_priority != 0) {
+ LOG(INFO) << "TODO: change file " << file_id << " generation priority";
+ return;
+ }
+
+ QueryId id = queries_container_.create(Query{file_id, Query::Generate});
+ node->generate_id_ = id;
+ send_closure(file_generate_manager_, &FileGenerateManager::generate_file, id, *node->generate_, node->local_,
+ node->suggested_name(), [file_manager = this, id] {
+ class Callback : public FileGenerateCallback {
+ ActorId<FileManager> actor_;
+ uint64 query_id_;
+
+ public:
+ Callback(ActorId<FileManager> actor, QueryId id) : actor_(std::move(actor)), query_id_(id) {
+ }
+ void on_partial_generate(const PartialLocalFileLocation &partial_local,
+ int32 expected_size) override {
+ send_closure(actor_, &FileManager::on_partial_generate, query_id_, partial_local, expected_size);
+ }
+ void on_ok(const FullLocalFileLocation &local) override {
+ send_closure(actor_, &FileManager::on_generate_ok, query_id_, local);
+ }
+ void on_error(Status error) override {
+ send_closure(actor_, &FileManager::on_error, query_id_, std::move(error));
+ }
+ };
+ return std::make_unique<Callback>(file_manager->actor_id(file_manager), id);
+ }());
+
+ LOG(INFO) << "File " << file_id << " generate request has sent to FileGenerateManager";
+}
+
+void FileManager::run_upload(FileNodePtr node, std::vector<int> bad_parts) {
+ if (node->need_load_from_pmc_) {
+ return;
+ }
+ if (node->upload_pause_.is_valid()) {
+ return;
+ }
+ FileView file_view(node);
+ if (!file_view.has_local_location()) {
+ if (node->get_by_hash_ || node->generate_id_ == 0 || !node->generate_was_update_) {
+ return;
+ }
+ }
+ int8 priority = 0;
+ FileId file_id = node->main_file_id_;
+ for (auto id : node->file_ids_) {
+ auto *info = get_file_id_info(id);
+ if (info->upload_priority_ > priority) {
+ priority = info->upload_priority_;
+ file_id = id;
+ }
+ }
+
+ auto old_priority = node->upload_priority_;
+ node->set_upload_priority(priority);
+
+ if (priority == 0) {
+ if (old_priority != 0) {
+ LOG(INFO) << "Cancel file " << file_id << " uploading";
+ cancel_upload(node);
+ }
+ return;
+ }
+
+ // create encryption key if necessary
+ if (((file_view.has_generate_location() && file_view.generate_location().file_type_ == FileType::Encrypted) ||
+ (file_view.has_local_location() && file_view.local_location().file_type_ == FileType::Encrypted)) &&
+ file_view.encryption_key().empty()) {
+ CHECK(!node->file_ids_.empty());
+ bool success = set_encryption_key(node->file_ids_[0], FileEncryptionKey::create());
+ LOG_IF(FATAL, !success) << "Failed to set encryption key for file " << file_id;
+ }
+
+ if (old_priority != 0) {
+ LOG(INFO) << "File " << file_id << " is already uploading";
+ CHECK(node->upload_id_ != 0);
+ send_closure(file_load_manager_, &FileLoadManager::update_priority, node->upload_id_, narrow_cast<int8>(-priority));
+ return;
+ }
+
+ CHECK(node->upload_id_ == 0);
+ if (node->remote_.type() != RemoteFileLocation::Type::Partial && node->get_by_hash_) {
+ QueryId id = queries_container_.create(Query{file_id, Query::UploadByHash});
+ node->upload_id_ = id;
+
+ send_closure(file_load_manager_, &FileLoadManager::upload_by_hash, id, node->local_.full(), node->size_,
+ narrow_cast<int8>(-priority));
+ return;
+ }
+
+ QueryId id = queries_container_.create(Query{file_id, Query::Upload});
+ node->upload_id_ = id;
+ send_closure(file_load_manager_, &FileLoadManager::upload, id, node->local_, node->remote_, node->size_,
+ node->encryption_key_, narrow_cast<int8>(bad_parts.empty() ? -priority : priority),
+ std::move(bad_parts));
+
+ LOG(INFO) << "File " << file_id << " upload request has sent to FileLoadManager";
+}
+
+void FileManager::upload(FileId file_id, std::shared_ptr<UploadCallback> callback, int32 new_priority,
+ uint64 upload_order) {
+ return resume_upload(file_id, std::vector<int>(), std::move(callback), new_priority, upload_order);
+}
+
+// is't quite stupid, yep
+// 0x00 <count of zeroes>
+static string zero_decode(Slice s) {
+ string res;
+ for (size_t n = s.size(), i = 0; i < n; i++) {
+ if (i + 1 < n && s[i] == 0) {
+ res.append(static_cast<unsigned char>(s[i + 1]), 0);
+ i++;
+ continue;
+ }
+ res.push_back(s[i]);
+ }
+ return res;
+}
+
+static string zero_encode(Slice s) {
+ string res;
+ for (size_t n = s.size(), i = 0; i < n; i++) {
+ res.push_back(s[i]);
+ if (s[i] == 0) {
+ unsigned char cnt = 1;
+ while (cnt < 250 && i + cnt < n && s[i + cnt] == 0) {
+ cnt++;
+ }
+ res.push_back(cnt);
+ i += cnt - 1;
+ }
+ }
+ return res;
+}
+
+static bool is_document_type(FileType type) {
+ return type == FileType::Document || type == FileType::Sticker || type == FileType::Audio ||
+ type == FileType::Animation;
+}
+
+string FileManager::get_persistent_id(const FullRemoteFileLocation &location) {
+ auto binary = serialize(location);
+
+ binary = zero_encode(binary);
+ binary.push_back(PERSISTENT_ID_VERSION);
+ return base64url_encode(binary);
+}
+
+Result<string> FileManager::to_persistent_id(FileId file_id) {
+ auto view = get_file_view(file_id);
+ if (view.empty()) {
+ return Status::Error(10, "Unknown file id");
+ }
+ if (!view.has_remote_location()) {
+ return Status::Error(10, "File has no persistent id");
+ }
+ return get_persistent_id(view.remote_location());
+}
+
+Result<FileId> FileManager::from_persistent_id(CSlice persistent_id, FileType file_type) {
+ if (persistent_id.find('.') != string::npos) {
+ string input_url = persistent_id.str(); // TODO do not copy persistent_id
+ TRY_RESULT(http_url, parse_url(input_url));
+ auto url = http_url.get_url();
+ if (!clean_input_string(url)) {
+ return Status::Error(400, "URL must be in UTF-8");
+ }
+ return register_url(std::move(url), file_type, FileLocationSource::FromUser, DialogId());
+ }
+
+ auto r_binary = base64url_decode(persistent_id);
+ if (r_binary.is_error()) {
+ return Status::Error(10, "Wrong remote file id specified: " + r_binary.error().message().str());
+ }
+ auto binary = r_binary.move_as_ok();
+ if (binary.empty()) {
+ return Status::Error(10, "Remote file id can't be empty");
+ }
+ if (binary.back() != PERSISTENT_ID_VERSION) {
+ return Status::Error(10, "Wrong remote file id specified: can't unserialize it. Wrong last symbol");
+ }
+ binary.pop_back();
+ binary = zero_decode(binary);
+ FullRemoteFileLocation remote_location;
+ auto status = unserialize(remote_location, binary);
+ if (status.is_error()) {
+ return Status::Error(10, "Wrong remote file id specified: can't unserialize it");
+ }
+ auto &real_file_type = remote_location.file_type_;
+ if (is_document_type(real_file_type) && is_document_type(file_type)) {
+ real_file_type = file_type;
+ } else if (real_file_type != file_type && file_type != FileType::Temp) {
+ return Status::Error(10, "Type of file mismatch");
+ }
+ FileData data;
+ data.remote_ = RemoteFileLocation(std::move(remote_location));
+ return register_file(std::move(data), FileLocationSource::FromUser, "from_persistent_id", false).move_as_ok();
+}
+
+FileView FileManager::get_file_view(FileId file_id) const {
+ auto file_node = get_file_node(file_id);
+ if (!file_node) {
+ return FileView();
+ }
+ return FileView(file_node);
+}
+FileView FileManager::get_sync_file_view(FileId file_id) {
+ auto file_node = get_sync_file_node(file_id);
+ if (!file_node) {
+ return FileView();
+ }
+ return FileView(file_node);
+}
+
+tl_object_ptr<td_api::file> FileManager::get_file_object(FileId file_id, bool with_main_file_id) {
+ auto file_view = get_sync_file_view(file_id);
+
+ if (file_view.empty()) {
+ return td_api::make_object<td_api::file>(0, 0, 0, td_api::make_object<td_api::localFile>(),
+ td_api::make_object<td_api::remoteFile>());
+ }
+
+ string persistent_file_id;
+ if (file_view.has_remote_location()) {
+ persistent_file_id = get_persistent_id(file_view.remote_location());
+ } else if (file_view.has_url()) {
+ persistent_file_id = file_view.url();
+ }
+
+ int32 size = narrow_cast<int32>(file_view.size());
+ int32 expected_size = narrow_cast<int32>(file_view.expected_size());
+ int32 local_size = narrow_cast<int32>(file_view.local_size());
+ int32 local_total_size = narrow_cast<int32>(file_view.local_total_size());
+ int32 remote_size = narrow_cast<int32>(file_view.remote_size());
+ string path = file_view.path();
+ bool can_be_downloaded = file_view.can_download_from_server() || file_view.can_generate();
+ bool can_be_deleted = file_view.can_delete();
+
+ auto result_file_id = file_id;
+ auto *file_info = get_file_id_info(result_file_id);
+ if (with_main_file_id) {
+ if (!file_info->send_updates_flag_) {
+ result_file_id = file_view.file_id();
+ }
+ file_info = get_file_id_info(file_view.file_id());
+ }
+ file_info->send_updates_flag_ = true;
+ VLOG(update_file) << "Send file " << file_id << " as " << result_file_id << " and update send_updates_flag_ for file "
+ << (with_main_file_id ? file_view.file_id() : result_file_id);
+
+ return td_api::make_object<td_api::file>(
+ result_file_id.get(), size, expected_size,
+ td_api::make_object<td_api::localFile>(std::move(path), can_be_downloaded, can_be_deleted,
+ file_view.is_downloading(), file_view.has_local_location(), local_size,
+ local_total_size),
+ td_api::make_object<td_api::remoteFile>(std::move(persistent_file_id), file_view.is_uploading(),
+ file_view.has_remote_location(), remote_size));
+}
+
+Result<FileId> FileManager::check_input_file_id(FileType type, Result<FileId> result, bool is_encrypted,
+ bool allow_zero) {
+ TRY_RESULT(file_id, std::move(result));
+ if (allow_zero && !file_id.is_valid()) {
+ return FileId();
+ }
+
+ auto file_node = get_file_node(file_id);
+ if (!file_node) {
+ return Status::Error(6, "File not found");
+ }
+ auto file_view = FileView(file_node);
+ FileType real_type = file_view.get_type();
+ if (!is_encrypted) {
+ if (real_type != type && !(real_type == FileType::Temp && file_view.has_url()) &&
+ !(is_document_type(real_type) && is_document_type(type))) {
+ // TODO: send encrypted file to unencrypted chat
+ return Status::Error(6, "Type of file mismatch");
+ }
+ }
+
+ if (!file_view.has_remote_location()) {
+ // TODO why not return file_id here? We will dup it anyway
+ // But it will not be duped if has_input_media(), so for now we can't return main_file_id
+ return dup_file_id(file_id);
+ }
+ return file_node->main_file_id_;
+}
+
+Result<FileId> FileManager::get_input_thumbnail_file_id(const tl_object_ptr<td_api::InputFile> &thumbnail_input_file,
+ DialogId owner_dialog_id, bool is_encrypted) {
+ if (thumbnail_input_file == nullptr) {
+ return Status::Error(6, "inputThumbnail not specified");
+ }
+
+ switch (thumbnail_input_file->get_id()) {
+ case td_api::inputFileLocal::ID: {
+ const string &path = static_cast<const td_api::inputFileLocal *>(thumbnail_input_file.get())->path_;
+ return register_local(
+ FullLocalFileLocation(is_encrypted ? FileType::EncryptedThumbnail : FileType::Thumbnail, path, 0),
+ owner_dialog_id, 0, false);
+ }
+ case td_api::inputFileId::ID:
+ return Status::Error(6, "InputFileId is not supported for thumbnails");
+ case td_api::inputFileRemote::ID:
+ return Status::Error(6, "InputFileRemote is not supported for thumbnails");
+ case td_api::inputFileGenerated::ID: {
+ auto *generated_thumbnail = static_cast<const td_api::inputFileGenerated *>(thumbnail_input_file.get());
+ return register_generate(is_encrypted ? FileType::EncryptedThumbnail : FileType::Thumbnail,
+ FileLocationSource::FromUser, generated_thumbnail->original_path_,
+ generated_thumbnail->conversion_, owner_dialog_id, generated_thumbnail->expected_size_);
+ }
+ default:
+ UNREACHABLE();
+ return Status::Error(500, "Unreachable");
+ }
+}
+
+Result<FileId> FileManager::get_input_file_id(FileType type, const tl_object_ptr<td_api::InputFile> &file,
+ DialogId owner_dialog_id, bool allow_zero, bool is_encrypted,
+ bool get_by_hash) {
+ if (is_encrypted) {
+ get_by_hash = false;
+ }
+ if (!file) {
+ if (allow_zero) {
+ return FileId();
+ }
+ return Status::Error(6, "InputFile not specified");
+ }
+
+ auto r_file_id = [&]() -> Result<FileId> {
+ switch (file->get_id()) {
+ case td_api::inputFileLocal::ID: {
+ const string &path = static_cast<const td_api::inputFileLocal *>(file.get())->path_;
+ if (allow_zero && path.empty()) {
+ return FileId();
+ }
+ return register_local(FullLocalFileLocation(is_encrypted ? FileType::Encrypted : type, path, 0),
+ owner_dialog_id, 0, get_by_hash);
+ }
+ case td_api::inputFileId::ID: {
+ FileId file_id(static_cast<const td_api::inputFileId *>(file.get())->id_, 0);
+ if (!file_id.is_valid()) {
+ return FileId();
+ }
+ return file_id;
+ }
+ case td_api::inputFileRemote::ID: {
+ const string &file_persistent_id = static_cast<const td_api::inputFileRemote *>(file.get())->id_;
+ if (allow_zero && file_persistent_id.empty()) {
+ return FileId();
+ }
+ return from_persistent_id(file_persistent_id, type);
+ }
+ case td_api::inputFileGenerated::ID: {
+ auto *generated_file = static_cast<const td_api::inputFileGenerated *>(file.get());
+ return register_generate(is_encrypted ? FileType::Encrypted : type, FileLocationSource::FromUser,
+ generated_file->original_path_, generated_file->conversion_, owner_dialog_id,
+ generated_file->expected_size_);
+ }
+ default:
+ UNREACHABLE();
+ return Status::Error(500, "Unreachable");
+ }
+ }();
+
+ return check_input_file_id(type, std::move(r_file_id), is_encrypted, allow_zero);
+}
+
+vector<tl_object_ptr<telegram_api::InputDocument>> FileManager::get_input_documents(const vector<FileId> &file_ids) {
+ vector<tl_object_ptr<telegram_api::InputDocument>> result;
+ result.reserve(file_ids.size());
+ for (auto file_id : file_ids) {
+ auto file_view = get_file_view(file_id);
+ CHECK(!file_view.empty());
+ CHECK(file_view.has_remote_location());
+ CHECK(!file_view.remote_location().is_web());
+ result.push_back(file_view.remote_location().as_input_document());
+ }
+ return result;
+}
+
+FileId FileManager::next_file_id() {
+ if (!empty_file_ids_.empty()) {
+ auto res = empty_file_ids_.back();
+ empty_file_ids_.pop_back();
+ return FileId{res, 0};
+ }
+ FileId res(static_cast<int32>(file_id_info_.size()), 0);
+ // LOG(ERROR) << "NEXT file_id " << res;
+ file_id_info_.push_back({});
+ return res;
+}
+
+FileManager::FileNodeId FileManager::next_file_node_id() {
+ FileNodeId res = static_cast<FileNodeId>(file_nodes_.size());
+ file_nodes_.emplace_back(nullptr);
+ return res;
+}
+
+void FileManager::on_start_download(QueryId query_id) {
+ auto query = queries_container_.get(query_id);
+ CHECK(query != nullptr);
+
+ auto file_id = query->file_id_;
+ auto file_node = get_file_node(file_id);
+ if (!file_node) {
+ return;
+ }
+ if (file_node->download_id_ != query_id) {
+ return;
+ }
+
+ LOG(DEBUG) << "Start to download part of file " << file_id;
+ file_node->is_download_started_ = true;
+}
+
+void FileManager::on_partial_download(QueryId query_id, const PartialLocalFileLocation &partial_local,
+ int64 ready_size) {
+ auto query = queries_container_.get(query_id);
+ CHECK(query != nullptr);
+
+ auto file_id = query->file_id_;
+ auto file_node = get_file_node(file_id);
+ if (!file_node) {
+ return;
+ }
+ if (file_node->download_id_ != query_id) {
+ return;
+ }
+
+ file_node->set_local_location(LocalFileLocation(partial_local), ready_size);
+ try_flush_node(file_node);
+}
+
+void FileManager::on_partial_upload(QueryId query_id, const PartialRemoteFileLocation &partial_remote,
+ int64 ready_size) {
+ auto query = queries_container_.get(query_id);
+ CHECK(query != nullptr);
+
+ auto file_id = query->file_id_;
+ auto file_node = get_file_node(file_id);
+ if (!file_node) {
+ return;
+ }
+ if (file_node->upload_id_ != query_id) {
+ return;
+ }
+
+ file_node->set_remote_location(RemoteFileLocation(partial_remote), FileLocationSource::None, ready_size);
+ try_flush_node(file_node);
+}
+void FileManager::on_download_ok(QueryId query_id, const FullLocalFileLocation &local, int64 size) {
+ auto file_id = finish_query(query_id).first.file_id_;
+ LOG(INFO) << "ON DOWNLOAD OK file " << file_id << " of size " << size;
+ auto r_new_file_id = register_local(local, DialogId(), size);
+ if (r_new_file_id.is_error()) {
+ LOG(ERROR) << "Can't register local file after download: " << r_new_file_id.error();
+ } else {
+ context_->on_new_file(get_file_view(r_new_file_id.ok()).size());
+ LOG_STATUS(merge(r_new_file_id.ok(), file_id));
+ }
+}
+void FileManager::on_upload_ok(QueryId query_id, FileType file_type, const PartialRemoteFileLocation &partial_remote,
+ int64 size) {
+ CHECK(partial_remote.ready_part_count_ == partial_remote.part_count_);
+ auto some_file_id = finish_query(query_id).first.file_id_;
+ LOG(INFO) << "ON UPLOAD OK file " << some_file_id << " of size " << size;
+
+ auto file_node = get_file_node(some_file_id);
+ if (!file_node) {
+ return;
+ }
+
+ FileId file_id;
+ uint64 file_id_upload_order{std::numeric_limits<uint64>::max()};
+ for (auto id : file_node->file_ids_) {
+ auto *info = get_file_id_info(id);
+ if (info->upload_priority_ != 0 && info->upload_order_ < file_id_upload_order) {
+ file_id = id;
+ file_id_upload_order = info->upload_order_;
+ }
+ }
+ if (!file_id.is_valid()) {
+ return;
+ }
+
+ auto *file_info = get_file_id_info(file_id);
+ file_info->upload_priority_ = 0;
+ file_info->download_priority_ = 0;
+
+ FileView file_view(file_node);
+ string file_name = get_file_name(file_type, file_view.suggested_name());
+
+ if (file_view.is_encrypted()) {
+ tl_object_ptr<telegram_api::InputEncryptedFile> input_file;
+ if (partial_remote.is_big_) {
+ input_file = make_tl_object<telegram_api::inputEncryptedFileBigUploaded>(
+ partial_remote.file_id_, partial_remote.part_count_, file_view.encryption_key().calc_fingerprint());
+ } else {
+ input_file = make_tl_object<telegram_api::inputEncryptedFileUploaded>(
+ partial_remote.file_id_, partial_remote.part_count_, "", file_view.encryption_key().calc_fingerprint());
+ }
+ if (file_info->upload_callback_) {
+ file_info->upload_callback_->on_upload_encrypted_ok(file_id, std::move(input_file));
+ file_node->upload_pause_ = file_id;
+ file_info->upload_callback_.reset();
+ }
+ } else {
+ tl_object_ptr<telegram_api::InputFile> input_file;
+ if (partial_remote.is_big_) {
+ input_file = make_tl_object<telegram_api::inputFileBig>(partial_remote.file_id_, partial_remote.part_count_,
+ std::move(file_name));
+ } else {
+ input_file = make_tl_object<telegram_api::inputFile>(partial_remote.file_id_, partial_remote.part_count_,
+ std::move(file_name), "");
+ }
+ if (file_info->upload_callback_) {
+ file_info->upload_callback_->on_upload_ok(file_id, std::move(input_file));
+ file_node->upload_pause_ = file_id;
+ file_info->upload_callback_.reset();
+ }
+ }
+}
+
+void FileManager::on_upload_full_ok(QueryId query_id, const FullRemoteFileLocation &remote) {
+ LOG(INFO) << "ON UPLOAD OK";
+ auto file_id = finish_query(query_id).first.file_id_;
+ auto new_file_id = register_remote(remote, FileLocationSource::FromServer, DialogId(), 0, 0, "");
+ LOG_STATUS(merge(new_file_id, file_id));
+}
+
+void FileManager::on_partial_generate(QueryId query_id, const PartialLocalFileLocation &partial_local,
+ int32 expected_size) {
+ LOG(INFO) << "on_parital_generate: " << partial_local.path_ << " " << partial_local.ready_part_count_;
+ auto query = queries_container_.get(query_id);
+ CHECK(query != nullptr);
+
+ auto file_id = query->file_id_;
+ auto file_node = get_file_node(file_id);
+ if (!file_node) {
+ return;
+ }
+ if (file_node->generate_id_ != query_id) {
+ return;
+ }
+ file_node->set_local_location(LocalFileLocation(partial_local), 0);
+ // TODO check for size and local_size, abort generation if needed
+ if (expected_size != 0) {
+ file_node->set_expected_size(expected_size);
+ }
+ if (!file_node->generate_was_update_) {
+ file_node->generate_was_update_ = true;
+ run_upload(file_node, {});
+ }
+ if (file_node->upload_id_ != 0) {
+ send_closure(file_load_manager_, &FileLoadManager::update_local_file_location, file_node->upload_id_,
+ LocalFileLocation(partial_local));
+ }
+
+ try_flush_node(file_node);
+}
+void FileManager::on_generate_ok(QueryId query_id, const FullLocalFileLocation &local) {
+ LOG(INFO) << "on_ok_generate: " << local;
+ Query query;
+ bool was_active;
+ std::tie(query, was_active) = finish_query(query_id);
+ auto generate_file_id = query.file_id_;
+
+ auto file_node = get_file_node(generate_file_id);
+ if (!file_node) {
+ return;
+ }
+
+ auto old_upload_id = file_node->upload_id_;
+
+ auto r_new_file_id = register_local(local, DialogId(), 0);
+ Status status;
+ if (r_new_file_id.is_error()) {
+ status = Status::Error(PSLICE() << "Can't register local file after generate: " << r_new_file_id.error());
+ } else {
+ auto result = merge(r_new_file_id.ok(), generate_file_id);
+ if (result.is_error()) {
+ status = result.move_as_error();
+ }
+ }
+ file_node = get_file_node(generate_file_id);
+ if (status.is_error()) {
+ return on_error_impl(file_node, query.type_, was_active, std::move(status));
+ }
+
+ CHECK(file_node);
+ context_->on_new_file(FileView(file_node).size());
+
+ run_upload(file_node, {});
+
+ if (was_active) {
+ if (old_upload_id != 0 && old_upload_id == file_node->upload_id_) {
+ send_closure(file_load_manager_, &FileLoadManager::update_local_file_location, file_node->upload_id_,
+ LocalFileLocation(local));
+ }
+ }
+}
+
+void FileManager::on_error(QueryId query_id, Status status) {
+ Query query;
+ bool was_active;
+ std::tie(query, was_active) = finish_query(query_id);
+ auto node = get_file_node(query.file_id_);
+ if (!node) {
+ LOG(ERROR) << "Can't find file node for " << query.file_id_ << " " << status;
+ return;
+ }
+
+ if (query.type_ == Query::UploadByHash) {
+ LOG(INFO) << "Upload By Hash failed: " << status << ", restart upload";
+ node->get_by_hash_ = false;
+ run_upload(node, {});
+ return;
+ }
+ on_error_impl(node, query.type_, was_active, std::move(status));
+}
+
+void FileManager::on_error_impl(FileNodePtr node, FileManager::Query::Type type, bool was_active, Status status) {
+ SCOPE_EXIT {
+ try_flush_node(node);
+ };
+ if (status.code() != 1) {
+ LOG(WARNING) << "Failed to upload/download/generate file: " << status << ". Query type = " << type
+ << ". File type is " << file_type_name[static_cast<int32>(FileView(node).get_type())];
+ if (status.code() == 0) {
+ // Remove partial locations
+ if (node->local_.type() == LocalFileLocation::Type::Partial && status.message() != "FILE_UPLOAD_RESTART") {
+ LOG(INFO) << "Unlink file " << node->local_.partial().path_;
+ unlink(node->local_.partial().path_).ignore();
+ node->set_local_location(LocalFileLocation(), 0);
+ }
+ if (node->remote_.type() == RemoteFileLocation::Type::Partial) {
+ node->set_remote_location(RemoteFileLocation(), FileLocationSource::None, 0);
+ }
+ status = Status::Error(400, status.message());
+ }
+ }
+
+ if (status.message() == "FILE_UPLOAD_RESTART") {
+ run_upload(node, {});
+ return;
+ }
+ if (status.message() == "FILE_DOWNLOAD_RESTART") {
+ node->can_search_locally_ = false;
+ run_download(node);
+ return;
+ }
+
+ if (!was_active) {
+ return;
+ }
+
+ // Stop everything on error
+ cancel_generate(node);
+ cancel_download(node);
+ cancel_upload(node);
+
+ for (auto file_id : vector<FileId>(node->file_ids_)) {
+ auto *info = get_file_id_info(file_id);
+ if (info->download_priority_ != 0) {
+ info->download_priority_ = 0;
+ if (info->download_callback_) {
+ info->download_callback_->on_download_error(file_id, status.clone());
+ info->download_callback_.reset();
+ }
+ }
+ if (info->upload_priority_ != 0) {
+ info->upload_priority_ = 0;
+ if (info->upload_callback_) {
+ info->upload_callback_->on_upload_error(file_id, status.clone());
+ info->upload_callback_.reset();
+ }
+ }
+ }
+}
+
+std::pair<FileManager::Query, bool> FileManager::finish_query(QueryId query_id) {
+ SCOPE_EXIT {
+ queries_container_.erase(query_id);
+ };
+ auto query = queries_container_.get(query_id);
+ CHECK(query != nullptr);
+
+ auto res = *query;
+ auto node = get_file_node(res.file_id_);
+ if (!node) {
+ return std::make_pair(res, false);
+ }
+ bool was_active = false;
+ if (node->generate_id_ == query_id) {
+ node->generate_id_ = 0;
+ node->generate_was_update_ = false;
+ node->set_generate_priority(0, 0);
+ was_active = true;
+ }
+ if (node->download_id_ == query_id) {
+ node->download_id_ = 0;
+ node->is_download_started_ = false;
+ node->set_download_priority(0);
+ was_active = true;
+ }
+ if (node->upload_id_ == query_id) {
+ node->upload_id_ = 0;
+ node->set_upload_priority(0);
+ was_active = true;
+ }
+ return std::make_pair(res, was_active);
+}
+
+FullRemoteFileLocation *FileManager::get_remote(int32 key) {
+ if (key == 0) {
+ return nullptr;
+ }
+ return &remote_location_info_.get(key).remote_;
+}
+
+void FileManager::hangup() {
+ file_db_.reset();
+ file_generate_manager_.reset();
+ file_load_manager_.reset();
+ stop();
+}
+
+void FileManager::tear_down() {
+ parent_.reset();
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileManager.h b/libs/tdlib/td/td/telegram/files/FileManager.h
new file mode 100644
index 0000000000..10f961c213
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileManager.h
@@ -0,0 +1,475 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileDb.h"
+#include "td/telegram/files/FileGenerateManager.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileLoadManager.h"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileStats.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/Container.h"
+#include "td/utils/Enumerator.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <utility>
+
+namespace td {
+
+enum class FileLocationSource : int8 { None, FromUser, FromDb, FromServer };
+
+class FileNode {
+ public:
+ FileNode(LocalFileLocation local, RemoteFileLocation remote, unique_ptr<FullGenerateFileLocation> generate,
+ int64 size, int64 expected_size, string remote_name, string url, DialogId owner_dialog_id,
+ FileEncryptionKey key, FileId main_file_id, int8 main_file_id_priority)
+ : local_(std::move(local))
+ , remote_(std::move(remote))
+ , generate_(std::move(generate))
+ , size_(size)
+ , expected_size_(expected_size)
+ , remote_name_(std::move(remote_name))
+ , url_(std::move(url))
+ , owner_dialog_id_(owner_dialog_id)
+ , encryption_key_(std::move(key))
+ , main_file_id_(main_file_id)
+ , main_file_id_priority_(main_file_id_priority) {
+ }
+ void set_local_location(const LocalFileLocation &local, int64 ready_size);
+ void set_remote_location(const RemoteFileLocation &remote, FileLocationSource source, int64 ready_size);
+ void set_generate_location(unique_ptr<FullGenerateFileLocation> &&generate);
+ void set_size(int64 size);
+ void set_expected_size(int64 expected_size);
+ void set_remote_name(string remote_name);
+ void set_url(string url);
+ void set_owner_dialog_id(DialogId owner_id);
+ void set_encryption_key(FileEncryptionKey key);
+
+ void set_download_priority(int8 priority);
+ void set_upload_priority(int8 priority);
+ void set_generate_priority(int8 download_priority, int8 upload_priority);
+
+ void on_changed();
+ void on_info_changed();
+ void on_pmc_changed();
+
+ bool need_info_flush() const;
+ bool need_pmc_flush() const;
+
+ void on_pmc_flushed();
+ void on_info_flushed();
+
+ string suggested_name() const;
+
+ private:
+ friend class FileView;
+ friend class FileManager;
+
+ LocalFileLocation local_;
+ FileLoadManager::QueryId upload_id_ = 0;
+ int64 local_ready_size_ = 0;
+
+ RemoteFileLocation remote_;
+ FileLoadManager::QueryId download_id_ = 0;
+ int64 remote_ready_size_ = 0;
+
+ unique_ptr<FullGenerateFileLocation> generate_;
+ FileLoadManager::QueryId generate_id_ = 0;
+
+ int64 size_ = 0;
+ int64 expected_size_ = 0;
+ string remote_name_;
+ string url_;
+ DialogId owner_dialog_id_;
+ FileEncryptionKey encryption_key_;
+ FileDbId pmc_id_ = 0;
+ std::vector<FileId> file_ids_;
+
+ FileId main_file_id_;
+
+ FileId upload_pause_;
+ int8 upload_priority_ = 0;
+ int8 download_priority_ = 0;
+ int8 generate_priority_ = 0;
+
+ int8 generate_download_priority_ = 0;
+ int8 generate_upload_priority_ = 0;
+
+ int8 main_file_id_priority_ = 0;
+
+ FileLocationSource remote_source_ = FileLocationSource::FromUser;
+
+ bool get_by_hash_ = false;
+ bool can_search_locally_{true};
+
+ bool is_download_started_ = false;
+ bool generate_was_update_ = false;
+
+ bool need_load_from_pmc_ = false;
+
+ bool pmc_changed_flag_{false};
+ bool info_changed_flag_{false};
+};
+
+class FileManager;
+
+class FileNodePtr {
+ public:
+ FileNodePtr() = default;
+ FileNodePtr(FileId file_id, FileManager *file_manager) : file_id_(file_id), file_manager_(file_manager) {
+ }
+
+ FileNode *operator->() const;
+ FileNode &operator*() const;
+ FileNode *get() const;
+ FullRemoteFileLocation *get_remote() const;
+ explicit operator bool() const;
+
+ private:
+ FileId file_id_;
+ FileManager *file_manager_ = nullptr;
+ FileNode *get_unsafe() const;
+};
+
+class ConstFileNodePtr {
+ public:
+ ConstFileNodePtr() = default;
+ ConstFileNodePtr(FileNodePtr file_node_ptr) : file_node_ptr_(file_node_ptr) {
+ }
+
+ const FileNode *operator->() const {
+ return file_node_ptr_.operator->();
+ }
+ const FileNode &operator*() const {
+ return file_node_ptr_.operator*();
+ }
+
+ explicit operator bool() const {
+ return bool(file_node_ptr_);
+ }
+ const FullRemoteFileLocation *get_remote() const {
+ return file_node_ptr_.get_remote();
+ }
+
+ private:
+ FileNodePtr file_node_ptr_;
+};
+
+class FileView {
+ public:
+ FileView() = default;
+ explicit FileView(ConstFileNodePtr node);
+
+ bool empty() const;
+
+ bool has_local_location() const;
+ const FullLocalFileLocation &local_location() const;
+ bool has_remote_location() const;
+ const FullRemoteFileLocation &remote_location() const;
+ bool has_generate_location() const;
+ const FullGenerateFileLocation &generate_location() const;
+
+ bool has_url() const;
+ const string &url() const;
+
+ const string &remote_name() const;
+
+ string suggested_name() const;
+
+ DialogId owner_dialog_id() const;
+
+ bool get_by_hash() const;
+
+ FileId file_id() const {
+ return node_->main_file_id_;
+ }
+
+ int64 size() const;
+ int64 expected_size() const;
+ bool is_downloading() const;
+ int64 local_size() const;
+ int64 local_total_size() const;
+ bool is_uploading() const;
+ int64 remote_size() const;
+ string path() const;
+
+ bool can_download_from_server() const;
+ bool can_generate() const;
+ bool can_delete() const;
+
+ FileType get_type() const {
+ if (has_local_location()) {
+ return local_location().file_type_;
+ }
+ if (has_remote_location()) {
+ return remote_location().file_type_;
+ }
+ if (has_generate_location()) {
+ return generate_location().file_type_;
+ }
+ return FileType::Temp;
+ }
+ bool is_encrypted() const {
+ return get_type() == FileType::Encrypted;
+ }
+ const FileEncryptionKey &encryption_key() const {
+ return node_->encryption_key_;
+ }
+
+ private:
+ ConstFileNodePtr node_{};
+};
+
+class Td;
+class FileManager : public FileLoadManager::Callback {
+ public:
+ class DownloadCallback {
+ public:
+ DownloadCallback() = default;
+ DownloadCallback(const DownloadCallback &) = delete;
+ DownloadCallback &operator=(const DownloadCallback &) = delete;
+ virtual ~DownloadCallback() = default;
+ virtual void on_progress(FileId file_id) {
+ }
+
+ virtual void on_download_ok(FileId file_id) = 0;
+ virtual void on_download_error(FileId file_id, Status error) = 0;
+ };
+
+ class UploadCallback {
+ public:
+ UploadCallback() = default;
+ UploadCallback(const UploadCallback &) = delete;
+ UploadCallback &operator=(const UploadCallback &) = delete;
+ virtual ~UploadCallback() = default;
+
+ virtual void on_progress(FileId file_id) {
+ }
+
+ // After on_upload_ok all uploads of this file will be paused till merge, delete_partial_remote_location or
+ // explicit upload request with the same file_id.
+ // Also upload may be resumed after some other merges.
+ virtual void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) = 0;
+ virtual void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) = 0;
+ virtual void on_upload_error(FileId file_id, Status error) = 0;
+ };
+
+ class Context {
+ public:
+ virtual void on_new_file(int64 size) = 0;
+ virtual void on_file_updated(FileId size) = 0;
+ virtual ActorShared<> create_reference() = 0;
+ Context() = default;
+ Context(const Context &) = delete;
+ Context &operator=(const Context &) = delete;
+ virtual ~Context() = default;
+ };
+
+ explicit FileManager(std::unique_ptr<Context> context);
+ FileManager(const FileManager &other) = delete;
+ FileManager &operator=(const FileManager &other) = delete;
+ FileManager(FileManager &&other) = delete;
+ FileManager &operator=(FileManager &&other) = delete;
+ ~FileManager() override;
+
+ void init_actor();
+
+ FileId dup_file_id(FileId file_id);
+
+ void on_file_unlink(const FullLocalFileLocation &location);
+
+ FileId register_empty(FileType type);
+ Result<FileId> register_local(FullLocalFileLocation location, DialogId owner_dialog_id, int64 size,
+ bool get_by_hash = false, bool force = false) TD_WARN_UNUSED_RESULT;
+ FileId register_remote(const FullRemoteFileLocation &location, FileLocationSource file_location_source,
+ DialogId owner_dialog_id, int64 size, int64 expected_size, string name) TD_WARN_UNUSED_RESULT;
+ Result<FileId> register_generate(FileType file_type, FileLocationSource file_location_source, string original_path,
+ string conversion, DialogId owner_dialog_id,
+ int64 expected_size) TD_WARN_UNUSED_RESULT;
+ Result<FileId> register_file(FileData data, FileLocationSource file_location_source, const char *source, bool force);
+
+ Result<FileId> merge(FileId x_file_id, FileId y_file_id, bool no_sync = false) TD_WARN_UNUSED_RESULT;
+
+ bool set_encryption_key(FileId file_id, FileEncryptionKey key);
+ bool set_content(FileId file_id, BufferSlice bytes);
+
+ void download(FileId file_id, std::shared_ptr<DownloadCallback> callback, int32 new_priority);
+ void upload(FileId file_id, std::shared_ptr<UploadCallback> callback, int32 new_priority, uint64 upload_order);
+ void resume_upload(FileId file_id, std::vector<int> bad_parts, std::shared_ptr<UploadCallback> callback,
+ int32 new_priority, uint64 upload_order);
+ bool delete_partial_remote_location(FileId file_id);
+ void get_content(FileId file_id, Promise<BufferSlice> promise);
+
+ void delete_file(FileId file_id, Promise<Unit> promise, const char *source);
+
+ void external_file_generate_progress(int64 id, int32 expected_size, int32 local_prefix_size, Promise<> promise);
+ void external_file_generate_finish(int64 id, Status status, Promise<> promise);
+
+ static constexpr char PERSISTENT_ID_VERSION = 2;
+ Result<string> to_persistent_id(FileId file_id) TD_WARN_UNUSED_RESULT;
+ Result<FileId> from_persistent_id(CSlice persistent_id, FileType file_type) TD_WARN_UNUSED_RESULT;
+ FileView get_file_view(FileId file_id) const;
+ FileView get_sync_file_view(FileId file_id);
+ tl_object_ptr<td_api::file> get_file_object(FileId file_id, bool with_main_file_id = true);
+
+ Result<FileId> get_input_thumbnail_file_id(const tl_object_ptr<td_api::InputFile> &thumb_input_file,
+ DialogId owner_dialog_id, bool is_encrypted) TD_WARN_UNUSED_RESULT;
+ Result<FileId> get_input_file_id(FileType type, const tl_object_ptr<td_api::InputFile> &file,
+ DialogId owner_dialog_id, bool allow_zero, bool is_encrypted,
+ bool get_by_hash = false) TD_WARN_UNUSED_RESULT;
+
+ vector<tl_object_ptr<telegram_api::InputDocument>> get_input_documents(const vector<FileId> &file_ids);
+
+ template <class T>
+ void store_file(FileId file_id, T &storer, int32 ttl = 5) const;
+
+ template <class T>
+ FileId parse_file(T &parser);
+
+ private:
+ Result<FileId> check_input_file_id(FileType type, Result<FileId> result, bool is_encrypted,
+ bool allow_zero) TD_WARN_UNUSED_RESULT;
+
+ FileId register_url(string url, FileType file_type, FileLocationSource file_location_source,
+ DialogId owner_dialog_id);
+
+ static constexpr int8 FROM_BYTES_PRIORITY = 10;
+ using FileNodeId = int32;
+ using QueryId = FileLoadManager::QueryId;
+ class Query {
+ public:
+ FileId file_id_;
+ enum Type { UploadByHash, Upload, Download, SetContent, Generate } type_;
+ };
+ struct FileIdInfo {
+ FileNodeId node_id_{0};
+ bool send_updates_flag_{false};
+ bool pin_flag_{false};
+
+ int8 download_priority_{0};
+ int8 upload_priority_{0};
+
+ uint64 upload_order_;
+
+ std::shared_ptr<DownloadCallback> download_callback_;
+ std::shared_ptr<UploadCallback> upload_callback_;
+ };
+
+ ActorShared<> parent_;
+ std::unique_ptr<Context> context_;
+ std::shared_ptr<FileDbInterface> file_db_;
+
+ FileIdInfo *get_file_id_info(FileId file_id);
+
+ struct RemoteInfo {
+ // mutible is set to to enable changing access hash
+ mutable FullRemoteFileLocation remote_;
+ FileId file_id_;
+ bool operator==(const RemoteInfo &other) const {
+ return this->remote_ == other.remote_;
+ }
+ bool operator<(const RemoteInfo &other) const {
+ return this->remote_ < other.remote_;
+ }
+ };
+ Enumerator<RemoteInfo> remote_location_info_;
+
+ std::map<FullLocalFileLocation, FileId> local_location_to_file_id_;
+ std::map<FullGenerateFileLocation, FileId> generate_location_to_file_id_;
+ std::map<FileDbId, int32> pmc_id_to_file_node_id_;
+
+ vector<FileIdInfo> file_id_info_;
+ vector<int32> empty_file_ids_;
+ vector<std::unique_ptr<FileNode>> file_nodes_;
+ ActorOwn<FileLoadManager> file_load_manager_;
+ ActorOwn<FileGenerateManager> file_generate_manager_;
+
+ Container<Query> queries_container_;
+
+ std::set<std::string> bad_paths_;
+
+ FileId next_file_id();
+ FileNodeId next_file_node_id();
+ int32 next_pmc_file_id();
+ FileId create_file_id(int32 file_node_id, FileNode *file_node);
+ void try_forget_file_id(FileId file_id);
+
+ void load_from_pmc(FileId file_id, FullLocalFileLocation full_local);
+ void load_from_pmc(FileId file_id, const FullRemoteFileLocation &full_remote);
+ void load_from_pmc(FileId file_id, const FullGenerateFileLocation &full_generate);
+ template <class LocationT>
+ void load_from_pmc_impl(FileId file_id, const LocationT &location);
+ void load_from_pmc_result(FileId file_id, Result<FileData> &&result);
+ FileId register_pmc_file_data(FileData &&data);
+
+ Status check_local_location(FileNodePtr node);
+ Status check_local_location(FullLocalFileLocation &location, int64 &size);
+ void try_flush_node(FileNodePtr node, bool new_remote = false, bool new_local = false, bool new_generate = false,
+ FileDbId other_pmc_id = Auto());
+ void try_flush_node_info(FileNodePtr node);
+ void clear_from_pmc(FileNodePtr node);
+ void flush_to_pmc(FileNodePtr node, bool new_remote, bool new_local, bool new_generate);
+ void load_from_pmc(FileNodePtr node, bool new_remote, bool new_local, bool new_generate);
+
+ string get_persistent_id(const FullRemoteFileLocation &location);
+
+ string fix_file_extension(Slice file_name, Slice file_type, Slice file_extension);
+ string get_file_name(FileType file_type, Slice path);
+
+ ConstFileNodePtr get_file_node(FileId file_id) const {
+ return ConstFileNodePtr{FileNodePtr{file_id, const_cast<FileManager *>(this)}};
+ }
+ FileNodePtr get_file_node(FileId file_id) {
+ return FileNodePtr{file_id, this};
+ }
+ FileNode *get_file_node_raw(FileId file_id, FileNodeId *file_node_id = nullptr);
+
+ FileNodePtr get_sync_file_node(FileId file_id);
+
+ // void release_file_node(FileNodeId id);
+ void cancel_download(FileNodePtr node);
+ void cancel_upload(FileNodePtr node);
+ void cancel_generate(FileNodePtr node);
+ void run_upload(FileNodePtr node, std::vector<int> bad_parts);
+ void run_download(FileNodePtr node);
+ void run_generate(FileNodePtr node);
+
+ void on_start_download(QueryId query_id) override;
+ void on_partial_download(QueryId query_id, const PartialLocalFileLocation &partial_local, int64 ready_size) override;
+ void on_partial_upload(QueryId query_id, const PartialRemoteFileLocation &partial_remote, int64 ready_size) override;
+ void on_download_ok(QueryId query_id, const FullLocalFileLocation &local, int64 size) override;
+ void on_upload_ok(QueryId query_id, FileType file_type, const PartialRemoteFileLocation &partial_remote,
+ int64 size) override;
+ void on_upload_full_ok(QueryId query_id, const FullRemoteFileLocation &remote) override;
+ void on_error(QueryId query_id, Status status) override;
+
+ void on_error_impl(FileNodePtr node, Query::Type type, bool was_active, Status status);
+
+ void on_partial_generate(QueryId, const PartialLocalFileLocation &partial_local, int32 expected_size);
+ void on_generate_ok(QueryId, const FullLocalFileLocation &local);
+
+ std::pair<Query, bool> finish_query(QueryId query_id);
+
+ FullRemoteFileLocation *get_remote(int32 key);
+
+ void hangup() override;
+ void tear_down() override;
+
+ friend class FileNodePtr;
+};
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileManager.hpp b/libs/tdlib/td/td/telegram/files/FileManager.hpp
new file mode 100644
index 0000000000..28f481308e
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileManager.hpp
@@ -0,0 +1,222 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/Version.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+enum class FileStoreType : int32 { Empty, Url, Generate, Local, Remote };
+
+template <class StorerT>
+void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const {
+ auto file_store_type = FileStoreType::Empty;
+ auto file_view = get_file_view(file_id);
+ if (file_view.empty() || ttl <= 0) {
+ } else if (file_view.has_remote_location()) {
+ file_store_type = FileStoreType::Remote;
+ } else if (file_view.has_local_location()) {
+ file_store_type = FileStoreType::Local;
+ } else if (file_view.has_url()) {
+ file_store_type = FileStoreType::Url;
+ } else if (file_view.has_generate_location()) {
+ file_store_type = FileStoreType::Generate;
+ }
+
+ store(file_store_type, storer);
+
+ bool has_encryption_key = false;
+ bool has_expected_size =
+ file_store_type == FileStoreType::Remote && file_view.size() == 0 && file_view.expected_size() != 0;
+ if (file_store_type != FileStoreType::Empty) {
+ has_encryption_key = !file_view.empty() && file_view.is_encrypted();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_encryption_key);
+ STORE_FLAG(has_expected_size);
+ END_STORE_FLAGS();
+ }
+
+ switch (file_store_type) {
+ case FileStoreType::Empty:
+ break;
+ case FileStoreType::Url:
+ store(file_view.get_type(), storer);
+ store(file_view.url(), storer);
+ store(file_view.owner_dialog_id(), storer);
+ break;
+ case FileStoreType::Remote: {
+ store(file_view.remote_location(), storer);
+ if (has_expected_size) {
+ store(narrow_cast<int32>(file_view.expected_size()), storer);
+ } else {
+ store(narrow_cast<int32>(file_view.size()), storer);
+ }
+ store(file_view.remote_name(), storer);
+ store(file_view.owner_dialog_id(), storer);
+ break;
+ }
+ case FileStoreType::Local: {
+ store(file_view.local_location(), storer);
+ store(narrow_cast<int32>(file_view.size()), storer);
+ store(static_cast<int32>(file_view.get_by_hash()), storer);
+ store(file_view.owner_dialog_id(), storer);
+ break;
+ }
+ case FileStoreType::Generate: {
+ auto generate_location = file_view.generate_location();
+ FileId from_file_id;
+ bool have_file_id = false;
+ if (generate_location.conversion_ == "#_file_id#") {
+ break;
+ } else if (begins_with(generate_location.conversion_, "#file_id#")) {
+ // It is not the best possible way to serialize file_id
+ from_file_id =
+ FileId(to_integer<int32>(Slice(generate_location.conversion_).remove_prefix(Slice("#file_id#").size())), 0);
+ generate_location.conversion_ = "#_file_id#";
+ have_file_id = true;
+ }
+ store(generate_location, storer);
+ store(static_cast<int32>(0), storer); // expected_size
+ store(static_cast<int32>(0), storer);
+ store(file_view.owner_dialog_id(), storer);
+
+ if (have_file_id) {
+ store_file(from_file_id, storer, ttl - 1);
+ }
+ break;
+ }
+ }
+ if (has_encryption_key) {
+ store(file_view.encryption_key(), storer);
+ }
+}
+
+template <class ParserT>
+FileId FileManager::parse_file(ParserT &parser) {
+ if (parser.version() < static_cast<int32>(Version::StoreFileId)) {
+ return FileId();
+ }
+
+ FileStoreType file_store_type;
+ parse(file_store_type, parser);
+
+ bool has_encryption_key = false;
+ bool has_expected_size = false;
+ if (file_store_type != FileStoreType::Empty) {
+ if (parser.version() >= static_cast<int32>(Version::StoreFileEncryptionKey)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_encryption_key);
+ PARSE_FLAG(has_expected_size);
+ END_PARSE_FLAGS();
+ }
+ }
+
+ auto file_id = [&] {
+ switch (file_store_type) {
+ case FileStoreType::Empty:
+ return FileId();
+ case FileStoreType::Remote: {
+ FullRemoteFileLocation full_remote_location;
+ parse(full_remote_location, parser);
+ int32 size = 0;
+ int32 expected_size = 0;
+ if (has_expected_size) {
+ parse(expected_size, parser);
+ } else {
+ parse(size, parser);
+ }
+ string name;
+ parse(name, parser);
+ DialogId owner_dialog_id;
+ if (parser.version() >= static_cast<int32>(Version::StoreFileOwnerId)) {
+ parse(owner_dialog_id, parser);
+ }
+ return register_remote(full_remote_location, FileLocationSource::FromDb, owner_dialog_id, size, expected_size,
+ name);
+ }
+ case FileStoreType::Local: {
+ FullLocalFileLocation full_local_location;
+ parse(full_local_location, parser);
+ int32 size;
+ parse(size, parser);
+ int32 get_by_hash;
+ parse(get_by_hash, parser);
+ DialogId owner_dialog_id;
+ if (parser.version() >= static_cast<int32>(Version::StoreFileOwnerId)) {
+ parse(owner_dialog_id, parser);
+ }
+ auto r_file_id = register_local(full_local_location, owner_dialog_id, size, get_by_hash != 0);
+ if (r_file_id.is_ok()) {
+ return r_file_id.move_as_ok();
+ }
+ LOG(ERROR) << "Can't resend local file " << full_local_location.path_;
+ return register_empty(full_local_location.file_type_);
+ }
+ case FileStoreType::Generate: {
+ FullGenerateFileLocation full_generated_location;
+ parse(full_generated_location, parser);
+ int32 expected_size;
+ parse(expected_size, parser); // expected_size
+ int32 zero;
+ parse(zero, parser);
+ DialogId owner_dialog_id;
+ if (parser.version() >= static_cast<int32>(Version::StoreFileOwnerId)) {
+ parse(owner_dialog_id, parser);
+ }
+ if (begins_with(full_generated_location.conversion_, "#file_id#")) {
+ LOG(ERROR) << "Can't resend message with '#file_id#...' location";
+ return register_empty(full_generated_location.file_type_);
+ }
+ if (full_generated_location.conversion_ == "#_file_id#") {
+ auto file_id = parse_file(parser);
+ if (file_id.empty()) {
+ return register_empty(full_generated_location.file_type_);
+ }
+ auto download_file_id = dup_file_id(file_id);
+ full_generated_location.conversion_ = PSTRING() << "#file_id#" << download_file_id.get();
+ }
+
+ auto r_file_id = register_generate(full_generated_location.file_type_, FileLocationSource::FromDb,
+ full_generated_location.original_path_, full_generated_location.conversion_,
+ owner_dialog_id, expected_size);
+ if (r_file_id.is_ok()) {
+ return r_file_id.move_as_ok();
+ }
+ return register_empty(full_generated_location.file_type_);
+ }
+ case FileStoreType::Url: {
+ FileType type;
+ string url;
+ parse(type, parser);
+ parse(url, parser);
+ DialogId owner_dialog_id;
+ if (parser.version() >= static_cast<int32>(Version::StoreFileOwnerId)) {
+ parse(owner_dialog_id, parser);
+ }
+ return register_url(url, type, FileLocationSource::FromDb, owner_dialog_id);
+ }
+ }
+ return FileId();
+ }();
+
+ if (has_encryption_key) {
+ FileEncryptionKey encryption_key;
+ parse(encryption_key, parser);
+ set_encryption_key(file_id, std::move(encryption_key));
+ }
+
+ return file_id;
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileStats.cpp b/libs/tdlib/td/td/telegram/files/FileStats.cpp
new file mode 100644
index 0000000000..8c93f01ce3
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileStats.cpp
@@ -0,0 +1,221 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileStats.h"
+
+#include "td/telegram/td_api.h"
+
+#include "td/telegram/files/FileLoaderUtils.h"
+#include "td/telegram/Global.h"
+
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/Slice.h"
+
+#include <algorithm>
+#include <unordered_set>
+#include <utility>
+
+namespace td {
+
+tl_object_ptr<td_api::storageStatisticsFast> FileStatsFast::as_td_api() const {
+ return make_tl_object<td_api::storageStatisticsFast>(size, count, db_size);
+}
+
+void FileStats::add(StatByType &by_type, FileType file_type, int64 size) {
+ auto pos = static_cast<size_t>(file_type);
+ CHECK(pos < stat_by_type.size());
+ by_type[pos].size += size;
+ by_type[pos].cnt++;
+}
+
+void FileStats::add(FullFileInfo &&info) {
+ if (split_by_owner_dialog_id) {
+ add(stat_by_owner_dialog_id[info.owner_dialog_id], info.file_type, info.size);
+ } else {
+ add(stat_by_type, info.file_type, info.size);
+ }
+ if (need_all_files) {
+ all_files.emplace_back(std::move(info));
+ }
+}
+
+FileTypeStat get_nontemp_stat(const FileStats::StatByType &by_type) {
+ FileTypeStat stat;
+ for (size_t i = 0; i < file_type_size; i++) {
+ if (FileType(i) != FileType::Temp) {
+ stat.size += by_type[i].size;
+ stat.cnt += by_type[i].cnt;
+ }
+ }
+ return stat;
+}
+FileTypeStat FileStats::get_total_nontemp_stat() const {
+ if (!split_by_owner_dialog_id) {
+ return get_nontemp_stat(stat_by_type);
+ }
+ FileTypeStat stat;
+ for (auto &dialog : stat_by_owner_dialog_id) {
+ auto tmp = get_nontemp_stat(dialog.second);
+ stat.size += tmp.size;
+ stat.cnt += tmp.cnt;
+ }
+ return stat;
+}
+void FileStats::apply_dialog_limit(int32 limit) {
+ if (limit == -1) {
+ return;
+ }
+ if (!split_by_owner_dialog_id) {
+ return;
+ }
+
+ std::vector<std::pair<int64, DialogId>> dialogs;
+ for (auto &dialog : stat_by_owner_dialog_id) {
+ if (!dialog.first.is_valid()) {
+ continue;
+ }
+ int64 size = 0;
+ for (auto &it : dialog.second) {
+ size += it.size;
+ }
+ dialogs.push_back(std::make_pair(size, dialog.first));
+ }
+ size_t prefix = dialogs.size();
+ if (prefix > static_cast<size_t>(limit)) {
+ prefix = static_cast<size_t>(limit);
+ }
+ std::partial_sort(dialogs.begin(), dialogs.begin() + prefix, dialogs.end(),
+ [](const auto &x, const auto &y) { return x.first > y.first; });
+ dialogs.resize(prefix);
+
+ std::unordered_set<DialogId, DialogIdHash> all_dialogs;
+
+ for (auto &dialog : dialogs) {
+ all_dialogs.insert(dialog.second);
+ }
+
+ StatByType other_stats;
+ bool other_flag = false;
+ for (auto it = stat_by_owner_dialog_id.begin(); it != stat_by_owner_dialog_id.end();) {
+ if (all_dialogs.count(it->first)) {
+ it++;
+ } else {
+ for (size_t i = 0; i < file_type_size; i++) {
+ other_stats[i].size += it->second[i].size;
+ other_stats[i].cnt += it->second[i].cnt;
+ }
+ other_flag = true;
+ it = stat_by_owner_dialog_id.erase(it);
+ }
+ }
+
+ if (other_flag) {
+ DialogId other_dialog_id;
+ stat_by_owner_dialog_id[other_dialog_id] = other_stats;
+ }
+}
+
+tl_object_ptr<td_api::storageStatisticsByChat> as_td_api(DialogId dialog_id,
+ const FileStats::StatByType &stat_by_type) {
+ auto stats = make_tl_object<td_api::storageStatisticsByChat>(dialog_id.get(), 0, 0, Auto());
+ for (size_t i = 0; i < file_type_size; i++) {
+ if (stat_by_type[i].size == 0) {
+ continue;
+ }
+ stats->size_ += stat_by_type[i].size;
+ stats->count_ += stat_by_type[i].cnt;
+ stats->by_file_type_.push_back(make_tl_object<td_api::storageStatisticsByFileType>(
+ as_td_api(FileType(i)), stat_by_type[i].size, stat_by_type[i].cnt));
+ }
+ return stats;
+}
+
+tl_object_ptr<td_api::storageStatistics> FileStats::as_td_api() const {
+ auto stats = make_tl_object<td_api::storageStatistics>(0, 0, Auto());
+ if (!split_by_owner_dialog_id) {
+ stats->by_chat_.reserve(1);
+ stats->by_chat_.push_back(::td::as_td_api(DialogId(), stat_by_type));
+ } else {
+ stats->by_chat_.reserve(stat_by_owner_dialog_id.size());
+ for (auto &by_dialog : stat_by_owner_dialog_id) {
+ stats->by_chat_.push_back(::td::as_td_api(by_dialog.first, by_dialog.second));
+ }
+ std::sort(stats->by_chat_.begin(), stats->by_chat_.end(), [](const auto &x, const auto &y) {
+ if (x->chat_id_ == 0 || y->chat_id_ == 0) {
+ return (x->chat_id_ == 0) < (y->chat_id_ == 0);
+ }
+ return x->size_ > y->size_;
+ });
+ }
+ for (const auto &by_dialog : stats->by_chat_) {
+ stats->size_ += by_dialog->size_;
+ stats->count_ += by_dialog->count_;
+ }
+ return stats;
+}
+
+std::vector<DialogId> FileStats::get_dialog_ids() const {
+ std::vector<DialogId> res;
+ if (!split_by_owner_dialog_id) {
+ return res;
+ }
+ res.reserve(stat_by_owner_dialog_id.size());
+ for (auto &by_dialog : stat_by_owner_dialog_id) {
+ if (by_dialog.first.is_valid()) {
+ res.push_back(by_dialog.first);
+ }
+ }
+ return res;
+}
+
+StringBuilder &operator<<(StringBuilder &sb, const FileTypeStat &stat) {
+ return sb << tag("size", format::as_size(stat.size)) << tag("count", stat.cnt);
+}
+
+StringBuilder &operator<<(StringBuilder &sb, const FileStats &file_stats) {
+ if (!file_stats.split_by_owner_dialog_id) {
+ FileTypeStat total_stat;
+ for (auto &type_stat : file_stats.stat_by_type) {
+ total_stat.size += type_stat.size;
+ total_stat.cnt += type_stat.cnt;
+ }
+
+ sb << "[FileStat " << tag("total", total_stat);
+ for (int i = 0; i < file_type_size; i++) {
+ sb << tag(Slice(file_type_name[i]), file_stats.stat_by_type[i]);
+ }
+ sb << "]";
+ } else {
+ {
+ FileTypeStat total_stat;
+ for (auto &by_type : file_stats.stat_by_owner_dialog_id) {
+ for (auto &type_stat : by_type.second) {
+ total_stat.size += type_stat.size;
+ total_stat.cnt += type_stat.cnt;
+ }
+ }
+ sb << "[FileStat " << tag("total", total_stat);
+ }
+ for (auto &by_type : file_stats.stat_by_owner_dialog_id) {
+ FileTypeStat dialog_stat;
+ for (auto &type_stat : by_type.second) {
+ dialog_stat.size += type_stat.size;
+ dialog_stat.cnt += type_stat.cnt;
+ }
+
+ sb << "[FileStat " << tag("owner_dialog_id", by_type.first) << tag("total", dialog_stat);
+ for (int i = 0; i < file_type_size; i++) {
+ sb << tag(Slice(file_type_name[i]), by_type.second[i]);
+ }
+ sb << "]";
+ }
+ sb << "]";
+ }
+
+ return sb;
+}
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileStats.h b/libs/tdlib/td/td/telegram/files/FileStats.h
new file mode 100644
index 0000000000..5e02fd249e
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileStats.h
@@ -0,0 +1,88 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileLocation.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+#include <array>
+#include <unordered_map>
+
+namespace td {
+namespace td_api {
+class storageStatistics;
+class storageStatisticsFast;
+} // namespace td_api
+} // namespace td
+
+namespace td {
+
+struct FileTypeStat {
+ int64 size{0};
+ int32 cnt{0};
+};
+
+template <class T>
+void store(const FileTypeStat &stat, T &storer) {
+ using ::td::store;
+ store(stat.size, storer);
+ store(stat.cnt, storer);
+}
+template <class T>
+void parse(FileTypeStat &stat, T &parser) {
+ using ::td::parse;
+ parse(stat.size, parser);
+ parse(stat.cnt, parser);
+}
+
+struct FullFileInfo {
+ FileType file_type;
+ string path;
+ DialogId owner_dialog_id;
+ int64 size;
+ uint64 atime_nsec;
+ uint64 mtime_nsec;
+};
+
+struct FileStatsFast {
+ int64 size{0};
+ int32 count{0};
+ int64 db_size{0};
+ FileStatsFast(int64 size, int32 count, int64 db_size) : size(size), count(count), db_size(db_size) {
+ }
+ tl_object_ptr<td_api::storageStatisticsFast> as_td_api() const;
+};
+
+struct FileStats {
+ bool need_all_files{false};
+ bool split_by_owner_dialog_id{false};
+
+ using StatByType = std::array<FileTypeStat, file_type_size>;
+
+ StatByType stat_by_type;
+ std::unordered_map<DialogId, StatByType, DialogIdHash> stat_by_owner_dialog_id;
+
+ std::vector<FullFileInfo> all_files;
+
+ void add(FullFileInfo &&info);
+ void apply_dialog_limit(int32 limit);
+
+ tl_object_ptr<td_api::storageStatistics> as_td_api() const;
+ std::vector<DialogId> get_dialog_ids() const;
+ FileTypeStat get_total_nontemp_stat() const;
+
+ private:
+ void add(StatByType &by_type, FileType file_type, int64 size);
+};
+
+StringBuilder &operator<<(StringBuilder &sb, const FileStats &file_stats);
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileStatsWorker.cpp b/libs/tdlib/td/td/telegram/files/FileStatsWorker.cpp
new file mode 100644
index 0000000000..999402ae2b
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileStatsWorker.cpp
@@ -0,0 +1,184 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileStatsWorker.h"
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileDb.h"
+#include "td/telegram/files/FileLoaderUtils.h"
+#include "td/telegram/Global.h"
+
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/PathView.h"
+#include "td/utils/port/path.h"
+#include "td/utils/port/Stat.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/Time.h"
+#include "td/utils/tl_helpers.h"
+
+#include <functional>
+#include <unordered_map>
+
+namespace td {
+namespace {
+// Performance ideas:
+// - use slice instead of string
+// - use arena memory allocator
+// - store FileType or dir, no both
+// - store dir relative to G()->files_dir()
+
+struct DbFileInfo {
+ FileType file_type;
+ string path;
+ DialogId owner_dialog_id;
+ int64 size;
+};
+
+// long and blocking
+template <class CallbackT>
+Status scan_db(CallbackT &&callback) {
+ G()->td_db()->get_file_db_shared()->pmc().get_by_range("file0", "file:", [&](Slice key, Slice value) {
+ // skip reference to other data
+ if (value.substr(0, 2) == "@@") {
+ return;
+ }
+ FileData data;
+ auto status = unserialize(data, value);
+ if (status.is_error()) {
+ LOG(ERROR) << "Invalid FileData in the database " << tag("value", format::escaped(value));
+ return;
+ }
+ DbFileInfo info;
+ if (data.local_.type() == LocalFileLocation::Type::Full) {
+ info.file_type = data.local_.full().file_type_;
+ info.path = data.local_.full().path_;
+ } else if (data.local_.type() == LocalFileLocation::Type::Partial) {
+ info.file_type = data.local_.partial().file_type_;
+ info.path = data.local_.partial().path_;
+ } else {
+ return;
+ }
+ PathView path_view(info.path);
+ if (path_view.is_relative()) {
+ info.path = get_files_base_dir(info.file_type) + info.path;
+ }
+ // LOG(INFO) << "Found file in the database: " << data << " " << info.path;
+ info.owner_dialog_id = data.owner_dialog_id_;
+ info.size = data.size_;
+ if (info.size == 0 && data.local_.type() == LocalFileLocation::Type::Full) {
+ LOG(ERROR) << "Unknown size in the database";
+ return;
+ }
+ callback(info);
+ });
+ return Status::OK();
+}
+
+struct FsFileInfo {
+ FileType file_type;
+ string path;
+ int64 size;
+ uint64 atime_nsec;
+ uint64 mtime_nsec;
+};
+
+// long and blocking
+template <class CallbackT>
+Status scan_fs(CallbackT &&callback) {
+ for (int i = 0; i < file_type_size; i++) {
+ auto file_type = static_cast<FileType>(i);
+ auto files_dir = get_files_dir(file_type);
+ td::walk_path(files_dir, [&](CSlice path, bool is_dir) {
+ if (is_dir) {
+ // TODO: skip subdirs
+ return;
+ }
+ auto r_stat = stat(path);
+ if (r_stat.is_error()) {
+ LOG(WARNING) << "Stat in files gc failed: " << r_stat.error();
+ return;
+ }
+ auto stat = r_stat.move_as_ok();
+ FsFileInfo info;
+ info.path = path.str();
+ info.size = stat.size_;
+ info.file_type = file_type;
+ info.atime_nsec = stat.atime_nsec_;
+ info.mtime_nsec = stat.mtime_nsec_;
+ callback(info);
+ });
+ }
+ return Status::OK();
+}
+} // namespace
+
+void FileStatsWorker::get_stats(bool need_all_files, bool split_by_owner_dialog_id, Promise<FileStats> promise) {
+ if (!G()->parameters().use_chat_info_db) {
+ split_by_owner_dialog_id = false;
+ }
+ if (!split_by_owner_dialog_id) {
+ FileStats file_stats;
+ file_stats.need_all_files = need_all_files;
+ auto start = Time::now();
+ scan_fs([&](FsFileInfo &fs_info) {
+ FullFileInfo info;
+ info.file_type = fs_info.file_type;
+ info.path = std::move(fs_info.path);
+ info.size = fs_info.size;
+ info.atime_nsec = fs_info.atime_nsec;
+ info.mtime_nsec = fs_info.mtime_nsec;
+ file_stats.add(std::move(info));
+ });
+ auto passed = Time::now() - start;
+ LOG_IF(INFO, passed > 0.5) << "Get file stats took: " << format::as_time(passed);
+ promise.set_value(std::move(file_stats));
+ } else {
+ auto start = Time::now();
+
+ std::vector<FullFileInfo> full_infos;
+ scan_fs([&](FsFileInfo &fs_info) {
+ FullFileInfo info;
+ info.file_type = fs_info.file_type;
+ info.path = std::move(fs_info.path);
+ info.size = fs_info.size;
+ info.atime_nsec = fs_info.atime_nsec;
+ info.mtime_nsec = fs_info.mtime_nsec;
+
+ // LOG(INFO) << "Found file of size " << info.size << " at " << info.path;
+
+ full_infos.push_back(std::move(info));
+ });
+
+ std::unordered_map<size_t, size_t> hash_to_pos;
+ size_t pos = 0;
+ for (auto &full_info : full_infos) {
+ hash_to_pos[std::hash<std::string>()(full_info.path)] = pos;
+ pos++;
+ }
+ scan_db([&](DbFileInfo &db_info) {
+ auto it = hash_to_pos.find(std::hash<std::string>()(db_info.path));
+ if (it == hash_to_pos.end()) {
+ return;
+ }
+ // LOG(INFO) << "Match! " << db_info.path << " from " << db_info.owner_dialog_id;
+ full_infos[it->second].owner_dialog_id = db_info.owner_dialog_id;
+ });
+
+ FileStats file_stats;
+ file_stats.need_all_files = need_all_files;
+ file_stats.split_by_owner_dialog_id = split_by_owner_dialog_id;
+ for (auto &full_info : full_infos) {
+ file_stats.add(std::move(full_info));
+ }
+ auto passed = Time::now() - start;
+ LOG_IF(INFO, passed > 0.5) << "Get file stats took: " << format::as_time(passed);
+ promise.set_value(std::move(file_stats));
+ }
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileStatsWorker.h b/libs/tdlib/td/td/telegram/files/FileStatsWorker.h
new file mode 100644
index 0000000000..f70fcac1e0
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileStatsWorker.h
@@ -0,0 +1,26 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/actor/actor.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/telegram/files/FileStats.h"
+
+namespace td {
+
+class FileStatsWorker : public Actor {
+ public:
+ explicit FileStatsWorker(ActorShared<> parent) : parent_(std::move(parent)) {
+ }
+ void get_stats(bool need_all_files, bool split_by_owner_dialog_id, Promise<FileStats> promise);
+
+ private:
+ ActorShared<> parent_;
+};
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileUploader.cpp b/libs/tdlib/td/td/telegram/files/FileUploader.cpp
new file mode 100644
index 0000000000..635e5c8933
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileUploader.cpp
@@ -0,0 +1,292 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileUploader.h"
+
+#include "td/telegram/telegram_api.h"
+
+#include "td/telegram/Global.h"
+#include "td/telegram/net/NetQueryDispatcher.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/crypto.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/ScopeGuard.h"
+
+namespace td {
+FileUploader::FileUploader(const LocalFileLocation &local, const RemoteFileLocation &remote, int64 expected_size,
+ const FileEncryptionKey &encryption_key, std::vector<int> bad_parts,
+ std::unique_ptr<Callback> callback)
+ : local_(local)
+ , remote_(remote)
+ , expected_size_(expected_size)
+ , encryption_key_(encryption_key)
+ , bad_parts_(std::move(bad_parts))
+ , callback_(std::move(callback)) {
+ if (!encryption_key_.empty()) {
+ iv_ = encryption_key_.mutable_iv();
+ generate_iv_ = encryption_key_.iv_slice().str();
+ }
+}
+
+Result<FileLoader::FileInfo> FileUploader::init() {
+ if (remote_.type() == RemoteFileLocation::Type::Full) {
+ return Status::Error("File is already uploaded");
+ }
+
+ TRY_RESULT(prefix_info, on_update_local_location(local_));
+ (void)prefix_info;
+
+ int offset = 0;
+ int part_size = 0;
+ if (remote_.type() == RemoteFileLocation::Type::Partial) {
+ const auto &partial = remote_.partial();
+ file_id_ = partial.file_id_;
+ part_size = partial.part_size_;
+ big_flag_ = partial.is_big_ != 0;
+ offset = partial.ready_part_count_;
+ } else {
+ file_id_ = Random::secure_int64();
+ big_flag_ = expected_size_ > 10 * (1 << 20);
+ }
+
+ std::vector<bool> ok(offset, true);
+ for (auto bad_id : bad_parts_) {
+ if (bad_id >= 0 && bad_id < offset) {
+ ok[bad_id] = false;
+ }
+ }
+ std::vector<int> parts;
+ for (int i = 0; i < offset; i++) {
+ if (ok[i]) {
+ parts.push_back(i);
+ }
+ }
+ if (!ok.empty() && !ok[0]) {
+ parts.clear();
+ }
+ FileInfo res;
+ res.size = local_size_;
+ res.is_size_final = local_is_ready_;
+ res.part_size = part_size;
+ res.ready_parts = std::move(parts);
+ return res;
+}
+
+Result<FileLoader::PrefixInfo> FileUploader::on_update_local_location(const LocalFileLocation &location) {
+ SCOPE_EXIT {
+ try_release_fd();
+ };
+ string path;
+ int64 local_size = 0;
+ bool local_is_ready{false};
+ FileType file_type{FileType::Temp};
+ if (location.type() == LocalFileLocation::Type::Empty) {
+ path = "";
+ local_size = 0;
+ local_is_ready = false;
+ file_type = FileType::Temp;
+ } else if (location.type() == LocalFileLocation::Type::Partial) {
+ path = location.partial().path_;
+ local_size = static_cast<int64>(location.partial().part_size_) * location.partial().ready_part_count_;
+ local_is_ready = false;
+ file_type = location.partial().file_type_;
+ } else {
+ path = location.full().path_;
+ local_is_ready = true;
+ file_type = location.full().file_type_;
+ }
+
+ if (!path.empty() && path != fd_path_) {
+ auto res_fd = FileFd::open(path, FileFd::Read);
+
+ // Race: partial location could be already deleted. Just ignore such locations
+ if (res_fd.is_error()) {
+ if (location.type() == LocalFileLocation::Type::Partial) {
+ PrefixInfo info;
+ info.size = local_size_;
+ info.is_ready = local_is_ready_;
+ return info;
+ }
+ return res_fd.move_as_error();
+ }
+
+ fd_.close();
+ fd_ = res_fd.move_as_ok();
+ fd_path_ = path;
+ }
+
+ if (local_is_ready) {
+ CHECK(!fd_.empty());
+ local_size = fd_.get_size();
+ if (local_size == 0) {
+ return Status::Error("Can't upload empty file");
+ }
+ } else if (!fd_.empty()) {
+ auto real_local_size = fd_.get_size();
+ if (real_local_size < local_size) {
+ LOG(ERROR) << tag("real_local_size", real_local_size) << " < " << tag("local_size", local_size);
+ PrefixInfo info;
+ info.size = local_size_;
+ info.is_ready = local_is_ready_;
+ return info;
+ }
+ }
+
+ local_size_ = local_size;
+ if (expected_size_ < local_size_) {
+ expected_size_ = local_size_;
+ }
+ local_is_ready_ = local_is_ready;
+ file_type_ = file_type;
+
+ PrefixInfo info;
+ info.size = local_size_;
+ info.is_ready = local_is_ready_;
+ return info;
+}
+
+Status FileUploader::on_ok(int64 size) {
+ fd_.close();
+ return Status::OK();
+}
+void FileUploader::on_error(Status status) {
+ fd_.close();
+ callback_->on_error(std::move(status));
+}
+
+Status FileUploader::generate_iv_map() {
+ LOG(INFO) << "generate iv_map " << generate_offset_ << " " << local_size_;
+ auto part_size = get_part_size();
+ auto encryption_key = FileEncryptionKey(encryption_key_.key_slice(), generate_iv_);
+ BufferSlice bytes(part_size);
+ if (iv_map_.empty()) {
+ iv_map_.push_back(encryption_key.mutable_iv());
+ }
+ CHECK(!fd_.empty());
+ for (; generate_offset_ + static_cast<int64>(part_size) < local_size_;
+ generate_offset_ += static_cast<int64>(part_size)) {
+ TRY_RESULT(read_size, fd_.pread(bytes.as_slice(), generate_offset_));
+ if (read_size != part_size) {
+ return Status::Error("Failed to read file part (for iv_map)");
+ }
+ aes_ige_encrypt(encryption_key.key(), &encryption_key.mutable_iv(), bytes.as_slice(), bytes.as_slice());
+ iv_map_.push_back(encryption_key.mutable_iv());
+ }
+ generate_iv_ = encryption_key.iv_slice().str();
+ return Status::OK();
+}
+
+Status FileUploader::before_start_parts() {
+ auto status = acquire_fd();
+ if (status.is_error() && !local_is_ready_) {
+ return Status::Error(1, "Can't open temporary file");
+ }
+ return status;
+}
+void FileUploader::after_start_parts() {
+ try_release_fd();
+}
+
+Result<std::pair<NetQueryPtr, bool>> FileUploader::start_part(Part part, int32 part_count) {
+ auto padded_size = part.size;
+ if (!encryption_key_.empty()) {
+ padded_size = (padded_size + 15) & ~15;
+ }
+ BufferSlice bytes(padded_size);
+ TRY_RESULT(size, fd_.pread(bytes.as_slice().truncate(part.size), part.offset));
+ if (!encryption_key_.empty()) {
+ Random::secure_bytes(bytes.as_slice().substr(part.size));
+ if (next_offset_ == part.offset) {
+ aes_ige_encrypt(encryption_key_.key(), &iv_, bytes.as_slice(), bytes.as_slice());
+ next_offset_ += static_cast<int64>(bytes.size());
+ } else {
+ if (part.id >= static_cast<int32>(iv_map_.size())) {
+ TRY_STATUS(generate_iv_map());
+ }
+ CHECK(part.id < static_cast<int32>(iv_map_.size()) && part.id >= 0);
+ auto iv = iv_map_[part.id];
+ aes_ige_encrypt(encryption_key_.key(), &iv, bytes.as_slice(), bytes.as_slice());
+ }
+ }
+
+ if (size != part.size) {
+ LOG(ERROR) << "Need to read " << part.size << " bytes, but read " << size << " bytes instead";
+ return Status::Error("Failed to read file part");
+ }
+
+ NetQueryPtr net_query;
+ if (big_flag_) {
+ auto query =
+ telegram_api::upload_saveBigFilePart(file_id_, part.id, local_is_ready_ ? part_count : -1, std::move(bytes));
+ net_query = G()->net_query_creator().create(create_storer(query), DcId::main(), NetQuery::Type::Upload,
+ NetQuery::AuthFlag::On, NetQuery::GzipFlag::Off);
+ } else {
+ auto query = telegram_api::upload_saveFilePart(file_id_, part.id, std::move(bytes));
+ net_query = G()->net_query_creator().create(create_storer(query), DcId::main(), NetQuery::Type::Upload,
+ NetQuery::AuthFlag::On, NetQuery::GzipFlag::Off);
+ }
+ net_query->file_type_ = narrow_cast<int32>(file_type_);
+ return std::make_pair(std::move(net_query), false);
+}
+
+Result<size_t> FileUploader::process_part(Part part, NetQueryPtr net_query) {
+ if (net_query->is_error()) {
+ return std::move(net_query->error());
+ }
+ Result<bool> result;
+ if (big_flag_) {
+ result = fetch_result<telegram_api::upload_saveBigFilePart>(net_query->ok());
+ } else {
+ result = fetch_result<telegram_api::upload_saveFilePart>(net_query->ok());
+ }
+ if (result.is_error()) {
+ return result.move_as_error();
+ }
+ if (!result.ok()) {
+ // TODO: it is possible
+ return Status::Error(500, "Internal Server Error");
+ }
+ return part.size;
+}
+
+void FileUploader::on_progress(int32 part_count, int32 part_size, int32 ready_part_count, bool is_ready,
+ int64 ready_size) {
+ callback_->on_partial_upload(PartialRemoteFileLocation{file_id_, part_count, part_size, ready_part_count, big_flag_},
+ ready_size);
+ if (is_ready) {
+ callback_->on_ok(file_type_,
+ PartialRemoteFileLocation{file_id_, part_count, part_size, ready_part_count, big_flag_},
+ local_size_);
+ }
+}
+FileLoader::Callback *FileUploader::get_callback() {
+ return static_cast<FileLoader::Callback *>(callback_.get());
+}
+
+void FileUploader::keep_fd_flag(bool keep_fd) {
+ keep_fd_ = keep_fd;
+ try_release_fd();
+}
+
+void FileUploader::try_release_fd() {
+ if (!keep_fd_ && !fd_.empty()) {
+ fd_.close();
+ }
+}
+
+Status FileUploader::acquire_fd() {
+ if (fd_.empty()) {
+ TRY_RESULT(fd, FileFd::open(fd_path_, FileFd::Read));
+ fd_ = std::move(fd);
+ }
+ return Status::OK();
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/FileUploader.h b/libs/tdlib/td/td/telegram/files/FileUploader.h
new file mode 100644
index 0000000000..7f2ccfe8e8
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/FileUploader.h
@@ -0,0 +1,76 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/actor/actor.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/telegram/files/FileLoader.h"
+#include "td/telegram/files/FileLocation.h"
+
+#include "td/utils/port/FileFd.h"
+#include "td/utils/Status.h"
+
+#include <utility>
+
+namespace td {
+class FileUploader : public FileLoader {
+ public:
+ class Callback : public FileLoader::Callback {
+ public:
+ virtual void on_partial_upload(const PartialRemoteFileLocation &partial_remote, int64 ready_size) = 0;
+ virtual void on_ok(FileType file_type, const PartialRemoteFileLocation &partial_remote, int64 size) = 0;
+ virtual void on_error(Status status) = 0;
+ };
+
+ FileUploader(const LocalFileLocation &local, const RemoteFileLocation &remote, int64 expected_size,
+ const FileEncryptionKey &encryption_key, std::vector<int> bad_parts, std::unique_ptr<Callback> callback);
+
+ // Should just implement all parent pure virtual methods.
+ // Must not call any of them...
+ private:
+ ResourceState resource_state_;
+ LocalFileLocation local_;
+ RemoteFileLocation remote_;
+ int64 expected_size_;
+ FileEncryptionKey encryption_key_;
+ std::vector<int> bad_parts_;
+ std::unique_ptr<Callback> callback_;
+ int64 local_size_ = 0;
+ bool local_is_ready_ = false;
+ FileType file_type_ = FileType::Temp;
+
+ std::vector<UInt256> iv_map_;
+ UInt256 iv_;
+ string generate_iv_;
+ int64 generate_offset_ = 0;
+ int64 next_offset_ = 0;
+
+ FileFd fd_;
+ string fd_path_;
+ int64 file_id_;
+ bool big_flag_;
+
+ Result<FileInfo> init() override TD_WARN_UNUSED_RESULT;
+ Status on_ok(int64 size) override TD_WARN_UNUSED_RESULT;
+ void on_error(Status status) override;
+ Status before_start_parts() override;
+ void after_start_parts() override;
+ Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int32 part_count) override TD_WARN_UNUSED_RESULT;
+ Result<size_t> process_part(Part part, NetQueryPtr net_query) override TD_WARN_UNUSED_RESULT;
+ void on_progress(int32 part_count, int32 part_size, int32 ready_part_count, bool is_ready, int64 ready_size) override;
+ FileLoader::Callback *get_callback() override;
+ Result<PrefixInfo> on_update_local_location(const LocalFileLocation &location) override TD_WARN_UNUSED_RESULT;
+
+ Status generate_iv_map();
+
+ bool keep_fd_ = false;
+ void keep_fd_flag(bool keep_fd) override;
+ void try_release_fd();
+ Status acquire_fd() TD_WARN_UNUSED_RESULT;
+};
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/PartsManager.cpp b/libs/tdlib/td/td/telegram/files/PartsManager.cpp
new file mode 100644
index 0000000000..75debb7af1
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/PartsManager.cpp
@@ -0,0 +1,333 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/PartsManager.h"
+
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+
+#include <limits>
+#include <numeric>
+
+namespace td {
+/*** PartsManager ***/
+
+namespace {
+int64 calc_parts_count(int64 size, int64 part_size) {
+ CHECK(part_size != 0);
+ return (size + part_size - 1) / part_size;
+}
+} // namespace
+
+Status PartsManager::init_known_prefix(int64 known_prefix, size_t part_size, const std::vector<int> &ready_parts) {
+ known_prefix_flag_ = true;
+ known_prefix_size_ = known_prefix;
+ return init_no_size(part_size, ready_parts);
+}
+
+Status PartsManager::init_no_size(size_t part_size, const std::vector<int> &ready_parts) {
+ unknown_size_flag_ = true;
+ size_ = 0;
+ min_size_ = 0;
+ max_size_ = std::numeric_limits<int64>::max();
+
+ if (part_size != 0) {
+ part_size_ = part_size;
+ } else {
+ part_size_ = 32 * (1 << 10);
+ while (use_part_count_limit_ && calc_parts_count(expected_size_, part_size_) > MAX_PART_COUNT) {
+ part_size_ *= 2;
+ CHECK(part_size_ <= MAX_PART_SIZE);
+ }
+ // just in case if expected_size_ is wrong
+ if (part_size_ < MAX_PART_SIZE) {
+ part_size_ *= 2;
+ }
+ }
+ part_count_ = 0;
+ if (known_prefix_flag_) {
+ part_count_ = static_cast<int>(known_prefix_size_ / part_size_);
+ }
+ part_count_ = max(part_count_, std::accumulate(ready_parts.begin(), ready_parts.end(), 0,
+ [](auto a, auto b) { return max(a, b + 1); }));
+
+ init_common(ready_parts);
+ return Status::OK();
+}
+
+Status PartsManager::init(int64 size, int64 expected_size, bool is_size_final, size_t part_size,
+ const std::vector<int> &ready_parts, bool use_part_count_limit) {
+ CHECK(expected_size >= size);
+ use_part_count_limit_ = use_part_count_limit;
+ expected_size_ = expected_size;
+ if (expected_size_ > MAX_FILE_SIZE) {
+ return Status::Error("Too big file");
+ }
+ if (!is_size_final) {
+ return init_known_prefix(size, part_size, ready_parts);
+ }
+ if (size == 0) {
+ return init_no_size(part_size, ready_parts);
+ }
+ CHECK(size > 0) << tag("size", size);
+ unknown_size_flag_ = false;
+ size_ = size;
+
+ if (part_size != 0) {
+ part_size_ = part_size;
+ if (use_part_count_limit_ && calc_parts_count(expected_size_, part_size_) > MAX_PART_COUNT) {
+ return Status::Error("FILE_UPLOAD_RESTART");
+ }
+ } else {
+ // TODO choose part_size_ depending on size
+ part_size_ = 64 * (1 << 10);
+ while (use_part_count_limit && calc_parts_count(expected_size_, part_size_) > MAX_PART_COUNT) {
+ part_size_ *= 2;
+ CHECK(part_size_ <= MAX_PART_SIZE);
+ }
+ }
+ CHECK(1 <= size_) << tag("size_", size_);
+ CHECK(!use_part_count_limit || calc_parts_count(expected_size_, part_size_) <= MAX_PART_COUNT)
+ << tag("size_", size_) << tag("expected_size", size_) << tag("is_size_final", is_size_final)
+ << tag("part_size_", part_size_) << tag("ready_parts", ready_parts.size());
+ part_count_ = static_cast<int>(calc_parts_count(size_, part_size_));
+
+ init_common(ready_parts);
+ return Status::OK();
+}
+
+bool PartsManager::unchecked_ready() {
+ VLOG(files) << "Check readiness. Ready size is " << ready_size_ << ", total size is " << size_
+ << ", unknown_size_flag = " << unknown_size_flag_ << ", need_check = " << need_check_
+ << ", checked_prefix_size = " << checked_prefix_size_;
+ return !unknown_size_flag_ && ready_size_ == size_;
+}
+bool PartsManager::ready() {
+ return unchecked_ready() && (!need_check_ || checked_prefix_size_ == size_);
+}
+
+Status PartsManager::finish() {
+ if (!ready()) {
+ return Status::Error("File transferring not finished");
+ }
+ return Status::OK();
+}
+
+void PartsManager::update_first_empty_part() {
+ while (first_empty_part_ < part_count_ && part_status_[first_empty_part_] != PartStatus::Empty) {
+ first_empty_part_++;
+ }
+}
+
+void PartsManager::update_first_not_ready_part() {
+ while (first_not_ready_part_ < part_count_ && part_status_[first_not_ready_part_] == PartStatus::Ready) {
+ first_not_ready_part_++;
+ }
+}
+
+int32 PartsManager::get_ready_prefix_count() {
+ update_first_not_ready_part();
+ auto res = first_not_ready_part_;
+ if (need_check_) {
+ auto checked_parts = narrow_cast<int32>(checked_prefix_size_ / part_size_);
+ if (checked_parts < res) {
+ res = checked_parts;
+ }
+ }
+ return res;
+}
+
+Result<Part> PartsManager::start_part() {
+ update_first_empty_part();
+ if (first_empty_part_ == part_count_) {
+ if (unknown_size_flag_) {
+ if (known_prefix_flag_ == false) {
+ part_count_++;
+ if (part_count_ > MAX_PART_COUNT) {
+ return Status::Error("Too big file with unknown size");
+ }
+ part_status_.push_back(PartStatus::Empty);
+ } else {
+ return Status::Error(1, "Wait for prefix to be known");
+ }
+ } else {
+ return get_empty_part();
+ }
+ }
+ CHECK(part_status_[first_empty_part_] == PartStatus::Empty);
+ int id = first_empty_part_;
+ on_part_start(id);
+ return get_part(id);
+}
+
+Status PartsManager::set_known_prefix(size_t size, bool is_ready) {
+ CHECK(known_prefix_flag_);
+ CHECK(size >= static_cast<size_t>(known_prefix_size_));
+ known_prefix_size_ = narrow_cast<int64>(size);
+ expected_size_ = max(known_prefix_size_, expected_size_);
+
+ CHECK(static_cast<size_t>(part_count_) == part_status_.size());
+ if (is_ready) {
+ part_count_ = static_cast<int>(calc_parts_count(size, part_size_));
+
+ size_ = narrow_cast<int64>(size);
+ unknown_size_flag_ = false;
+ } else {
+ part_count_ = static_cast<int>(size / part_size_);
+ }
+ CHECK(static_cast<size_t>(part_count_) >= part_status_.size())
+ << size << " " << is_ready << " " << part_count_ << " " << part_size_ << " " << part_status_.size();
+ part_status_.resize(part_count_);
+ if (use_part_count_limit_ && calc_parts_count(expected_size_, part_size_) > MAX_PART_COUNT) {
+ return Status::Error("FILE_UPLOAD_RESTART");
+ }
+ return Status::OK();
+}
+
+Status PartsManager::on_part_ok(int32 id, size_t part_size, size_t actual_size) {
+ CHECK(part_status_[id] == PartStatus::Pending);
+ pending_count_--;
+
+ part_status_[id] = PartStatus::Ready;
+ ready_size_ += narrow_cast<int64>(actual_size);
+
+ VLOG(files) << "Transferred part " << id << " of size " << part_size << ", total ready size = " << ready_size_;
+
+ int64 offset = narrow_cast<int64>(part_size_) * id;
+ int64 end_offset = offset + narrow_cast<int64>(actual_size);
+ if (unknown_size_flag_) {
+ CHECK(part_size == part_size_);
+ if (actual_size < part_size_) {
+ max_size_ = min(max_size_, end_offset);
+ }
+ if (actual_size) {
+ min_size_ = max(min_size_, end_offset);
+ }
+ if (min_size_ > max_size_) {
+ auto status = Status::Error(PSLICE() << "Failed to transfer file: " << tag("min_size", min_size_)
+ << tag("max_size", max_size_));
+ LOG(ERROR) << status;
+ return status;
+ } else if (min_size_ == max_size_) {
+ unknown_size_flag_ = false;
+ size_ = min_size_;
+ }
+ } else {
+ if ((actual_size < part_size && offset < size_) || (offset >= size_ && actual_size > 0)) {
+ auto status = Status::Error(PSLICE() << "Failed to transfer file: " << tag("size", size_) << tag("offset", offset)
+ << tag("transferred size", actual_size) << tag("part size", part_size));
+ LOG(ERROR) << status;
+ return status;
+ }
+ }
+ return Status::OK();
+}
+
+void PartsManager::on_part_failed(int32 id) {
+ CHECK(part_status_[id] == PartStatus::Pending);
+ pending_count_--;
+ part_status_[id] = PartStatus::Empty;
+ if (id < first_empty_part_) {
+ first_empty_part_ = id;
+ }
+}
+
+int64 PartsManager::get_size() const {
+ CHECK(!unknown_size_flag_);
+ return size_;
+}
+int64 PartsManager::get_size_or_zero() const {
+ return size_;
+}
+
+int64 PartsManager::get_ready_size() const {
+ return ready_size_;
+}
+
+int64 PartsManager::get_expected_size() const {
+ if (unknown_size_flag_) {
+ return max(static_cast<int64>(512 * (1 << 10)), get_ready_size() * 2);
+ }
+ return get_size();
+}
+
+size_t PartsManager::get_part_size() const {
+ return part_size_;
+}
+
+int32 PartsManager::get_part_count() const {
+ return part_count_;
+}
+
+void PartsManager::init_common(const std::vector<int> &ready_parts) {
+ ready_size_ = 0;
+ pending_count_ = 0;
+ first_empty_part_ = 0;
+ first_not_ready_part_ = 0;
+ part_status_ = vector<PartStatus>(part_count_);
+
+ for (auto i : ready_parts) {
+ CHECK(0 <= i && i < part_count_) << tag("i", i) << tag("part_count", part_count_);
+ part_status_[i] = PartStatus::Ready;
+ auto part = get_part(i);
+ ready_size_ += narrow_cast<int64>(part.size);
+ }
+
+ checked_prefix_size_ = get_ready_prefix_count() * narrow_cast<int64>(part_size_);
+}
+
+void PartsManager::set_need_check() {
+ need_check_ = true;
+}
+
+void PartsManager::set_checked_prefix_size(int64 size) {
+ checked_prefix_size_ = size;
+}
+
+int64 PartsManager::get_checked_prefix_size() const {
+ return checked_prefix_size_;
+}
+int64 PartsManager::get_unchecked_ready_prefix_size() {
+ update_first_not_ready_part();
+ auto count = first_not_ready_part_;
+ if (count == 0) {
+ return 0;
+ }
+ auto part = get_part(count - 1);
+ int64 res = part.offset;
+ if (!unknown_size_flag_) {
+ res += narrow_cast<int64>(part.size);
+ res = min(res, get_size());
+ }
+ return res;
+}
+
+Part PartsManager::get_part(int id) {
+ int64 offset = narrow_cast<int64>(part_size_) * id;
+ int64 size = narrow_cast<int64>(part_size_);
+ if (!unknown_size_flag_) {
+ auto total_size = get_size();
+ if (total_size < offset) {
+ size = 0;
+ } else {
+ size = min(size, total_size - offset);
+ }
+ }
+ return Part{id, offset, static_cast<size_t>(size)};
+}
+
+Part PartsManager::get_empty_part() {
+ return Part{-1, 0, 0};
+}
+
+void PartsManager::on_part_start(int32 id) {
+ CHECK(part_status_[id] == PartStatus::Empty);
+ part_status_[id] = PartStatus::Pending;
+ pending_count_++;
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/PartsManager.h b/libs/tdlib/td/td/telegram/files/PartsManager.h
new file mode 100644
index 0000000000..26c31d5174
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/PartsManager.h
@@ -0,0 +1,87 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+/*** PartsManager***/
+struct Part {
+ int id;
+ int64 offset;
+ size_t size;
+};
+
+class PartsManager {
+ public:
+ Status init(int64 size, int64 expected_size, bool is_size_final, size_t part_size,
+ const std::vector<int> &ready_parts, bool use_part_count_limit) TD_WARN_UNUSED_RESULT;
+ bool ready();
+ bool unchecked_ready();
+ Status finish() TD_WARN_UNUSED_RESULT;
+
+ // returns empty part if nothing to return
+ Result<Part> start_part() TD_WARN_UNUSED_RESULT;
+ Status on_part_ok(int32 id, size_t part_size, size_t actual_size) TD_WARN_UNUSED_RESULT;
+ void on_part_failed(int32 id);
+ Status set_known_prefix(size_t size, bool is_ready);
+ void set_need_check();
+ void set_checked_prefix_size(int64 size);
+
+ int64 get_checked_prefix_size() const;
+ int64 get_unchecked_ready_prefix_size();
+ int64 get_size() const;
+ int64 get_size_or_zero() const;
+ int64 get_expected_size() const;
+ int64 get_ready_size() const;
+ size_t get_part_size() const;
+ int32 get_part_count() const;
+ int32 get_ready_prefix_count();
+
+ private:
+ static constexpr int MAX_PART_COUNT = 3000;
+ static constexpr int MAX_PART_SIZE = 512 * (1 << 10);
+ static constexpr int64 MAX_FILE_SIZE = MAX_PART_SIZE * MAX_PART_COUNT;
+
+ enum class PartStatus { Empty, Pending, Ready };
+
+ bool need_check_{false};
+ int64 checked_prefix_size_{0};
+
+ bool known_prefix_flag_{false};
+ int64 known_prefix_size_;
+
+ int64 size_;
+ int64 expected_size_;
+ int64 min_size_;
+ int64 max_size_;
+ bool unknown_size_flag_;
+ int64 ready_size_;
+
+ size_t part_size_;
+ int part_count_;
+ int pending_count_;
+ int first_empty_part_;
+ int first_not_ready_part_;
+ vector<PartStatus> part_status_;
+ bool use_part_count_limit_;
+
+ void init_common(const vector<int> &ready_parts);
+ Status init_known_prefix(int64 known_prefix, size_t part_size,
+ const std::vector<int> &ready_parts) TD_WARN_UNUSED_RESULT;
+ Status init_no_size(size_t part_size, const std::vector<int> &ready_parts) TD_WARN_UNUSED_RESULT;
+
+ Part get_part(int id);
+ Part get_empty_part();
+ void on_part_start(int32 id);
+ void update_first_empty_part();
+ void update_first_not_ready_part();
+};
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/ResourceManager.cpp b/libs/tdlib/td/td/telegram/files/ResourceManager.cpp
new file mode 100644
index 0000000000..00677ddd76
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/ResourceManager.cpp
@@ -0,0 +1,179 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/ResourceManager.h"
+
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/ScopeGuard.h"
+
+#include <algorithm>
+
+namespace td {
+
+void ResourceManager::register_worker(ActorShared<FileLoaderActor> callback, int8 priority) {
+ auto node_id = nodes_container_.create();
+ auto *node_ptr = nodes_container_.get(node_id);
+ *node_ptr = std::make_unique<Node>();
+ auto *node = (*node_ptr).get();
+ CHECK(node);
+ node->node_id = node_id;
+ node->callback_ = std::move(callback);
+
+ add_node(node_id, priority);
+ send_closure(node->callback_, &FileLoaderActor::set_resource_manager, actor_shared(this, node_id));
+}
+
+void ResourceManager::update_priority(int8 priority) {
+ if (stop_flag_) {
+ return;
+ }
+ auto node_id = get_link_token();
+ if (!remove_node(node_id)) {
+ return;
+ }
+ add_node(node_id, priority);
+}
+
+void ResourceManager::update_resources(const ResourceState &resource_state) {
+ if (stop_flag_) {
+ return;
+ }
+ auto node_id = get_link_token();
+ auto node_ptr = nodes_container_.get(node_id);
+ if (node_ptr == nullptr) {
+ return;
+ }
+ auto node = (*node_ptr).get();
+ CHECK(node);
+ VLOG(files) << "before total: " << resource_state_;
+ VLOG(files) << "before " << tag("node_id", node_id) << ": " << node->resource_state_;
+ resource_state_ -= node->resource_state_;
+ node->resource_state_.update_master(resource_state);
+ resource_state_ += node->resource_state_;
+ VLOG(files) << "after total: " << resource_state_;
+ VLOG(files) << "after " << tag("node_id", node_id) << ": " << node->resource_state_;
+
+ if (mode_ == Mode::Greedy) {
+ add_to_heap(node);
+ }
+ loop();
+}
+
+void ResourceManager::hangup_shared() {
+ auto node_id = get_link_token();
+ auto node_ptr = nodes_container_.get(node_id);
+ if (node_ptr == nullptr) {
+ return;
+ }
+ auto node = (*node_ptr).get();
+ CHECK(node);
+ if (node->in_heap()) {
+ by_estimated_extra_.erase(node->as_heap_node());
+ }
+ resource_state_ -= node->resource_state_;
+ remove_node(node_id);
+ nodes_container_.erase(node_id);
+ loop();
+}
+
+void ResourceManager::add_to_heap(Node *node) {
+ auto *heap_node = node->as_heap_node();
+ auto key = node->resource_state_.estimated_extra();
+ if (heap_node->in_heap()) {
+ if (key != 0) {
+ by_estimated_extra_.fix(key, heap_node);
+ } else {
+ by_estimated_extra_.erase(heap_node);
+ }
+ } else {
+ if (key != 0) {
+ by_estimated_extra_.insert(key, heap_node);
+ }
+ }
+}
+
+bool ResourceManager::satisfy_node(NodeId file_node_id) {
+ auto file_node_ptr = nodes_container_.get(file_node_id);
+ CHECK(file_node_ptr);
+ auto file_node = (*file_node_ptr).get();
+ CHECK(file_node);
+ auto part_size = narrow_cast<int64>(file_node->resource_state_.unit_size());
+ auto need = file_node->resource_state_.estimated_extra();
+ VLOG(files) << tag("need", need) << tag("part_size", part_size);
+ need = (need + part_size - 1) / part_size * part_size;
+ VLOG(files) << tag("need", need);
+ if (need == 0) {
+ return true;
+ }
+ auto give = resource_state_.unused();
+ give = min(need, give);
+ give -= give % part_size;
+ VLOG(files) << tag("give", give);
+ if (give == 0) {
+ return false;
+ }
+ resource_state_.start_use(give);
+ file_node->resource_state_.update_limit(give);
+ send_closure(file_node->callback_, &FileLoaderActor::update_resources, file_node->resource_state_);
+ return true;
+}
+
+void ResourceManager::loop() {
+ if (stop_flag_) {
+ if (nodes_container_.empty()) {
+ stop();
+ }
+ return;
+ }
+ auto active_limit = resource_state_.active_limit();
+ resource_state_.update_limit(2 * 1024 * (1 << 10) - active_limit);
+ LOG(INFO) << tag("unused", resource_state_.unused());
+
+ if (mode_ == Mode::Greedy) {
+ std::vector<Node *> to_add;
+ while (!by_estimated_extra_.empty()) {
+ auto *node = Node::from_heap_node(by_estimated_extra_.pop());
+ SCOPE_EXIT {
+ to_add.push_back(node);
+ };
+ if (!satisfy_node(node->node_id)) {
+ break;
+ }
+ }
+ for (auto *node : to_add) {
+ add_to_heap(node);
+ }
+ } else if (mode_ == Mode::Baseline) {
+ // plain
+ for (auto &it : to_xload_) {
+ auto file_node_id = it.second;
+ if (!satisfy_node(file_node_id)) {
+ break;
+ }
+ }
+ }
+}
+void ResourceManager::add_node(NodeId node_id, int8 priority) {
+ if (priority >= 0) {
+ auto it = std::find_if(to_xload_.begin(), to_xload_.end(), [&](auto &x) { return x.first <= priority; });
+ to_xload_.insert(it, std::make_pair(priority, node_id));
+ } else {
+ auto it = std::find_if(to_xload_.begin(), to_xload_.end(), [&](auto &x) { return x.first < -priority; });
+ to_xload_.insert(it, std::make_pair(-priority, node_id));
+ }
+}
+bool ResourceManager::remove_node(NodeId node_id) {
+ auto it = std::find_if(to_xload_.begin(), to_xload_.end(), [&](auto &x) { return x.second == node_id; });
+ if (it != to_xload_.end()) {
+ to_xload_.erase(it);
+ return true;
+ }
+ return false;
+}
+
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/ResourceManager.h b/libs/tdlib/td/td/telegram/files/ResourceManager.h
new file mode 100644
index 0000000000..48a32f18f6
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/ResourceManager.h
@@ -0,0 +1,66 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/actor/actor.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/telegram/files/FileLoaderActor.h"
+#include "td/telegram/files/ResourceState.h"
+
+#include "td/utils/Container.h"
+#include "td/utils/Heap.h"
+
+#include <utility>
+
+namespace td {
+class ResourceManager : public Actor {
+ public:
+ enum class Mode { Baseline, Greedy };
+ explicit ResourceManager(Mode mode) : mode_(mode) {
+ }
+ // use through ActorShared
+ void update_priority(int8 priority);
+ void update_resources(const ResourceState &resource_state);
+
+ void register_worker(ActorShared<FileLoaderActor> callback, int8 priority);
+
+ private:
+ Mode mode_;
+ using NodeId = uint64;
+ struct Node : public HeapNode {
+ NodeId node_id;
+
+ ResourceState resource_state_;
+ ActorShared<FileLoaderActor> callback_;
+
+ HeapNode *as_heap_node() {
+ return static_cast<HeapNode *>(this);
+ }
+ static Node *from_heap_node(HeapNode *heap_node) {
+ return static_cast<Node *>(heap_node);
+ }
+ };
+
+ Container<std::unique_ptr<Node>> nodes_container_;
+ vector<std::pair<int8, NodeId>> to_xload_;
+ KHeap<int64> by_estimated_extra_;
+ ResourceState resource_state_;
+
+ ActorShared<> parent_;
+ bool stop_flag_ = false;
+
+ void hangup_shared() override;
+
+ void loop() override;
+
+ void add_to_heap(Node *node);
+ bool satisfy_node(NodeId file_node_id);
+ void add_node(NodeId node_id, int8 priority);
+ bool remove_node(NodeId node_id);
+};
+} // namespace td
diff --git a/libs/tdlib/td/td/telegram/files/ResourceState.h b/libs/tdlib/td/td/telegram/files/ResourceState.h
new file mode 100644
index 0000000000..e6a251ce95
--- /dev/null
+++ b/libs/tdlib/td/td/telegram/files/ResourceState.h
@@ -0,0 +1,104 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class ResourceState {
+ public:
+ void start_use(int64 x) {
+ using_ += x;
+ CHECK(used_ + using_ <= limit_);
+ }
+
+ void stop_use(int64 x) {
+ CHECK(x <= using_);
+ using_ -= x;
+ used_ += x;
+ }
+
+ void update_limit(int64 extra) {
+ limit_ += extra;
+ }
+
+ bool update_estimated_limit(int64 extra) {
+ auto new_estimated_limit = used_ + extra;
+ if (new_estimated_limit == estimated_limit_) {
+ return false;
+ }
+ estimated_limit_ = new_estimated_limit;
+ return true;
+ }
+
+ void set_unit_size(size_t new_unit_size) {
+ unit_size_ = new_unit_size;
+ }
+
+ int64 active_limit() const {
+ return limit_ - used_;
+ }
+
+ int64 get_using() const {
+ return using_;
+ }
+
+ int64 unused() const {
+ return limit_ - using_ - used_;
+ }
+
+ int64 estimated_extra() const {
+ auto new_unused = max(limit_, estimated_limit_) - using_ - used_;
+ new_unused = static_cast<int64>((new_unused + unit_size() - 1) / unit_size() * unit_size());
+ return new_unused + using_ + used_ - limit_;
+ }
+
+ size_t unit_size() const {
+ return unit_size_;
+ }
+
+ ResourceState &operator+=(const ResourceState &other) {
+ using_ += other.active_limit();
+ used_ += other.used_;
+ return *this;
+ }
+
+ ResourceState &operator-=(const ResourceState &other) {
+ using_ -= other.active_limit();
+ used_ -= other.used_;
+ return *this;
+ }
+
+ void update_master(const ResourceState &other) {
+ estimated_limit_ = other.estimated_limit_;
+ used_ = other.used_;
+ using_ = other.using_;
+ unit_size_ = other.unit_size_;
+ }
+
+ void update_slave(const ResourceState &other) {
+ limit_ = other.limit_;
+ }
+
+ friend StringBuilder &operator<<(StringBuilder &sb, const ResourceState &state) {
+ return sb << tag("estimated_limit", state.estimated_limit_) << tag("used", state.used_)
+ << tag("using", state.using_) << tag("limit", state.limit_);
+ }
+
+ private:
+ int64 estimated_limit_ = 0; // me
+ int64 limit_ = 0; // master
+ int64 used_ = 0; // me
+ int64 using_ = 0; // me
+ size_t unit_size_ = 1; // me
+};
+
+} // namespace td