diff options
Diffstat (limited to 'protocols/Telegram/tdlib/td/td/telegram/files/FileManager.cpp')
-rw-r--r-- | protocols/Telegram/tdlib/td/td/telegram/files/FileManager.cpp | 2941 |
1 files changed, 2343 insertions, 598 deletions
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.cpp index dc5e2d1caf..c288744372 100644 --- a/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.cpp +++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.cpp @@ -1,40 +1,124 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // 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/DownloadManager.h" +#include "td/telegram/FileReferenceManager.h" +#include "td/telegram/files/FileData.h" +#include "td/telegram/files/FileDb.h" #include "td/telegram/files/FileLoaderUtils.h" #include "td/telegram/files/FileLocation.h" -#include "td/telegram/files/FileUploader.h" +#include "td/telegram/files/FileLocation.hpp" #include "td/telegram/Global.h" +#include "td/telegram/logevent/LogEvent.h" #include "td/telegram/misc.h" -#include "td/telegram/Td.h" +#include "td/telegram/SecureStorage.h" +#include "td/telegram/TdDb.h" +#include "td/telegram/TdParameters.h" +#include "td/telegram/Version.h" + +#include "td/actor/SleepActor.h" +#include "td/utils/algorithm.h" #include "td/utils/base64.h" +#include "td/utils/crypto.h" +#include "td/utils/filesystem.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/SliceBuilder.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/Time.h" #include "td/utils/tl_helpers.h" +#include "td/utils/tl_parsers.h" #include <algorithm> +#include <cmath> #include <limits> +#include <numeric> #include <tuple> #include <utility> namespace td { +namespace { +constexpr int64 MAX_FILE_SIZE = static_cast<int64>(4000) << 20; // 4000MB +} // namespace + +int VERBOSITY_NAME(update_file) = VERBOSITY_NAME(INFO); + +StringBuilder &operator<<(StringBuilder &string_builder, FileLocationSource source) { + switch (source) { + case FileLocationSource::None: + return string_builder << "None"; + case FileLocationSource::FromUser: + return string_builder << "User"; + case FileLocationSource::FromBinlog: + return string_builder << "Binlog"; + case FileLocationSource::FromDatabase: + return string_builder << "Database"; + case FileLocationSource::FromServer: + return string_builder << "Server"; + default: + UNREACHABLE(); + return string_builder << "Unknown"; + } +} + +StringBuilder &operator<<(StringBuilder &string_builder, FileManager::Query::Type type) { + switch (type) { + case FileManager::Query::Type::UploadByHash: + return string_builder << "UploadByHash"; + case FileManager::Query::Type::UploadWaitFileReference: + return string_builder << "UploadWaitFileReference"; + case FileManager::Query::Type::Upload: + return string_builder << "Upload"; + case FileManager::Query::Type::DownloadWaitFileReference: + return string_builder << "DownloadWaitFileReference"; + case FileManager::Query::Type::DownloadReloadDialog: + return string_builder << "DownloadReloadDialog"; + case FileManager::Query::Type::Download: + return string_builder << "Download"; + case FileManager::Query::Type::SetContent: + return string_builder << "SetContent"; + case FileManager::Query::Type::Generate: + return string_builder << "Generate"; + default: + UNREACHABLE(); + return string_builder << "Unknown"; + } +} + +NewRemoteFileLocation::NewRemoteFileLocation(RemoteFileLocation remote, FileLocationSource source) { + switch (remote.type()) { + case RemoteFileLocation::Type::Empty: + break; + case RemoteFileLocation::Type::Partial: + partial = make_unique<PartialRemoteFileLocation>(remote.partial()); + break; + case RemoteFileLocation::Type::Full: + full = remote.full(); + full_source = source; + is_full_alive = true; + break; + default: + UNREACHABLE(); + } +} -static int VERBOSITY_NAME(update_file) = VERBOSITY_NAME(INFO); +RemoteFileLocation NewRemoteFileLocation::partial_or_empty() const { + if (partial) { + return RemoteFileLocation(*partial); + } + return {}; +} FileNode *FileNodePtr::operator->() const { return get(); @@ -59,45 +143,202 @@ FileNode *FileNodePtr::get_unsafe() const { return file_manager_->get_file_node_raw(file_id_); } -FileNodePtr::operator bool() const { +FileNodePtr::operator bool() const noexcept { return file_manager_ != nullptr && get_unsafe() != nullptr; } -void FileNode::set_local_location(const LocalFileLocation &local, int64 ready_size) { +void FileNode::recalc_ready_prefix_size(int64 prefix_offset, int64 ready_prefix_size) { + if (local_.type() != LocalFileLocation::Type::Partial) { + return; + } + int64 new_local_ready_prefix_size; + if (download_offset_ == prefix_offset) { + new_local_ready_prefix_size = ready_prefix_size; + } else { + new_local_ready_prefix_size = Bitmask(Bitmask::Decode{}, local_.partial().ready_bitmask_) + .get_ready_prefix_size(download_offset_, local_.partial().part_size_, size_); + } + if (new_local_ready_prefix_size != local_ready_prefix_size_) { + VLOG(update_file) << "File " << main_file_id_ << " has changed local_ready_prefix_size from " + << local_ready_prefix_size_ << " to " << new_local_ready_prefix_size; + local_ready_prefix_size_ = new_local_ready_prefix_size; + on_info_changed(); + } +} + +void FileNode::init_ready_size() { + if (local_.type() != LocalFileLocation::Type::Partial) { + return; + } + auto bitmask = Bitmask(Bitmask::Decode{}, local_.partial().ready_bitmask_); + local_ready_prefix_size_ = bitmask.get_ready_prefix_size(0, local_.partial().part_size_, size_); + local_ready_size_ = bitmask.get_total_size(local_.partial().part_size_, size_); +} + +void FileNode::set_download_offset(int64 download_offset) { + if (download_offset < 0 || download_offset > MAX_FILE_SIZE) { + // KEEP_DOWNLOAD_OFFSET is handled here + return; + } + if (download_offset == download_offset_) { + return; + } + + VLOG(update_file) << "File " << main_file_id_ << " has changed download_offset from " << download_offset_ << " to " + << download_offset; + download_offset_ = download_offset; + is_download_offset_dirty_ = true; + recalc_ready_prefix_size(-1, -1); + on_info_changed(); +} + +int64 FileNode::get_download_limit() const { + if (ignore_download_limit_) { + return 0; + } + return private_download_limit_; +} + +void FileNode::update_effective_download_limit(int64 old_download_limit) { + if (get_download_limit() == old_download_limit) { + return; + } + + // There should be no false positives here + // When we use IGNORE_DOWNLOAD_LIMIT, set_download_limit will be ignored + // And in case we turn off ignore_download_limit, set_download_limit will not change effective download limit + VLOG(update_file) << "File " << main_file_id_ << " has changed download_limit from " << old_download_limit << " to " + << get_download_limit() << " (limit=" << private_download_limit_ + << ";ignore=" << ignore_download_limit_ << ")"; + is_download_limit_dirty_ = true; +} + +void FileNode::set_download_limit(int64 download_limit) { + if (download_limit < 0) { + // KEEP_DOWNLOAD_LIMIT is handled here + return; + } + if (download_limit > MAX_FILE_SIZE) { + download_limit = MAX_FILE_SIZE; + } + auto old_download_limit = get_download_limit(); + private_download_limit_ = download_limit; + update_effective_download_limit(old_download_limit); +} + +void FileNode::set_ignore_download_limit(bool ignore_download_limit) { + auto old_download_limit = get_download_limit(); + ignore_download_limit_ = ignore_download_limit; + update_effective_download_limit(old_download_limit); +} + +void FileNode::drop_local_location() { + set_local_location(LocalFileLocation(), 0, -1, -1); +} + +void FileNode::set_local_location(const LocalFileLocation &local, int64 ready_size, int64 prefix_offset, + int64 ready_prefix_size) { if (local_ready_size_ != ready_size) { + VLOG(update_file) << "File " << main_file_id_ << " has changed local ready size from " << local_ready_size_ + << " to " << 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; + + recalc_ready_prefix_size(prefix_offset, ready_prefix_size); + 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; +void FileNode::set_new_remote_location(NewRemoteFileLocation new_remote) { + if (new_remote.full) { + if (remote_.full && remote_.full.value() == new_remote.full.value()) { + if (remote_.full.value().get_access_hash() != new_remote.full.value().get_access_hash() || + remote_.full.value().get_file_reference() != new_remote.full.value().get_file_reference() || + remote_.full.value().get_source() != new_remote.full.value().get_source()) { + on_pmc_changed(); } } else { - return; + VLOG(update_file) << "File " << main_file_id_ << " has changed remote location"; + on_changed(); } + remote_.full = new_remote.full; + remote_.full_source = new_remote.full_source; + remote_.is_full_alive = new_remote.is_full_alive; + } else { + if (remote_.full) { + VLOG(update_file) << "File " << main_file_id_ << " has lost remote location"; + remote_.full = {}; + remote_.is_full_alive = false; + remote_.full_source = FileLocationSource::None; + on_changed(); + } + } + + if (new_remote.partial) { + set_partial_remote_location(*new_remote.partial, new_remote.ready_size); + } else { + delete_partial_remote_location(); + } +} +void FileNode::delete_partial_remote_location() { + if (remote_.partial) { + VLOG(update_file) << "File " << main_file_id_ << " has lost partial remote location"; + remote_.partial.reset(); + on_changed(); + } +} + +void FileNode::set_partial_remote_location(PartialRemoteFileLocation remote, int64 ready_size) { + if (remote_.is_full_alive) { + VLOG(update_file) << "File " << main_file_id_ << " remote is still alive, so there is NO reason to update partial"; + return; + } + if (remote_.ready_size != ready_size) { + VLOG(update_file) << "File " << main_file_id_ << " has changed remote ready size from " << remote_.ready_size + << " to " << ready_size; + remote_.ready_size = ready_size; + on_info_changed(); + } + if (remote_.partial && *remote_.partial == remote) { + VLOG(update_file) << "Partial location of " << main_file_id_ << " is NOT changed"; + return; + } + if (!remote_.partial && remote.ready_part_count_ == 0) { + // empty partial remote is equal to empty remote + VLOG(update_file) << "Partial location of " << main_file_id_ + << " is still empty, so there is NO reason to update it"; + return; } - VLOG(update_file) << "File " << main_file_id_ << " has changed remote location"; - remote_ = remote; - remote_source_ = source; + VLOG(update_file) << "File " << main_file_id_ << " partial location has changed to " << remote; + remote_.partial = make_unique<PartialRemoteFileLocation>(std::move(remote)); on_changed(); } +bool FileNode::delete_file_reference(Slice file_reference) { + if (!remote_.full) { + VLOG(file_references) << "Can't delete file reference, because there is no remote location"; + return false; + } + + if (!remote_.full.value().delete_file_reference(file_reference)) { + VLOG(file_references) << "Can't delete unmatching file reference " << format::escaped(file_reference) << ", have " + << format::escaped(remote_.full.value().get_file_reference()); + return false; + } + + VLOG(file_references) << "Do delete file reference of main file " << main_file_id_; + upload_was_update_file_reference_ = false; + download_was_update_file_reference_ = false; + on_pmc_changed(); + return true; +} + void FileNode::set_generate_location(unique_ptr<FullGenerateFileLocation> &&generate) { bool is_changed = generate_ == nullptr ? generate != nullptr : generate == nullptr || *generate_ != *generate; if (is_changed) { @@ -151,6 +392,16 @@ void FileNode::set_encryption_key(FileEncryptionKey key) { } } +void FileNode::set_upload_pause(FileId upload_pause) { + if (upload_pause_ != upload_pause) { + LOG(INFO) << "Change file " << main_file_id_ << " upload_pause from " << upload_pause_ << " to " << upload_pause; + if (upload_pause_.is_valid() != upload_pause.is_valid()) { + on_info_changed(); + } + upload_pause_ = upload_pause; + } +} + 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; @@ -160,7 +411,7 @@ void FileNode::set_download_priority(int8 priority) { } void FileNode::set_upload_priority(int8 priority) { - if ((upload_priority_ == 0) != (priority == 0)) { + if (!remote_.is_full_alive && (upload_priority_ == 0) != (priority == 0)) { VLOG(update_file) << "File " << main_file_id_ << " has changed upload priority to " << priority; on_info_changed(); } @@ -168,7 +419,8 @@ void FileNode::set_upload_priority(int8 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)) { + if ((generate_download_priority_ == 0) != (download_priority == 0) || + (generate_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(); @@ -182,9 +434,11 @@ 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; } @@ -199,7 +453,7 @@ bool FileNode::need_pmc_flush() const { } // already in pmc - if (pmc_id_ != 0) { + if (pmc_id_.is_valid()) { return true; } @@ -215,12 +469,12 @@ bool FileNode::need_pmc_flush() const { has_generate_location = false; } - if (remote_.type() == RemoteFileLocation::Type::Full && - (has_generate_location || local_.type() != LocalFileLocation::Type::Empty)) { + if (remote_.full/* && + (has_generate_location || local_.type() != LocalFileLocation::Type::Empty)*/) { + // we need to always save file sources return true; } - if (local_.type() == LocalFileLocation::Type::Full && - (has_generate_location || remote_.type() != RemoteFileLocation::Type::Empty)) { + if (local_.type() == LocalFileLocation::Type::Full && (has_generate_location || remote_.full || remote_.partial)) { return true; } @@ -236,7 +490,7 @@ void FileNode::on_info_flushed() { info_changed_flag_ = false; } -string FileNode::suggested_name() const { +string FileNode::suggested_path() const { if (!remote_name_.empty()) { return remote_name_; } @@ -258,24 +512,61 @@ string FileNode::suggested_name() const { 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; + return static_cast<bool>(node_->remote_.full); } + +bool FileView::has_alive_remote_location() const { + return node_->remote_.is_full_alive; +} + +bool FileView::has_active_upload_remote_location() const { + if (!has_remote_location()) { + return false; + } + if (!has_alive_remote_location()) { + return false; + } + if (main_remote_location().is_encrypted_any()) { + return true; + } + return main_remote_location().has_file_reference(); +} + +bool FileView::has_active_download_remote_location() const { + if (!has_remote_location()) { + return false; + } + if (remote_location().is_encrypted_any()) { + return true; + } + return remote_location().has_file_reference(); +} + const FullRemoteFileLocation &FileView::remote_location() const { CHECK(has_remote_location()); auto *remote = node_.get_remote(); if (remote) { return *remote; } - return node_->remote_.full(); + return node_->remote_.full.value(); } + +const FullRemoteFileLocation &FileView::main_remote_location() const { + CHECK(has_remote_location()); + return node_->remote_.full.value(); +} + bool FileView::has_generate_location() const { return node_->generate_ != nullptr; } + const FullGenerateFileLocation &FileView::generate_location() const { CHECK(has_generate_location()); return *node_->generate_; @@ -285,23 +576,73 @@ int64 FileView::size() const { return node_->size_; } -int64 FileView::expected_size() const { +int64 FileView::get_allocated_local_size() const { + auto file_path = path(); + if (file_path.empty()) { + return 0; + } + auto r_stat = stat(file_path); + if (r_stat.is_error()) { + return 0; + } + return r_stat.ok().real_size_; +} + +int64 FileView::expected_size(bool may_guess) const { if (node_->size_ != 0) { return node_->size_; } - return node_->expected_size_; + int64 current_size = local_total_size(); // TODO: this is not the best approximation + if (node_->expected_size_ != 0) { + return max(current_size, node_->expected_size_); + } + if (may_guess && node_->local_.type() == LocalFileLocation::Type::Partial) { + current_size *= 3; + } + return current_size; } bool FileView::is_downloading() const { return node_->download_priority_ != 0 || node_->generate_download_priority_ != 0; } -int64 FileView::local_size() const { +int64 FileView::download_offset() const { + return node_->download_offset_; +} + +int64 FileView::downloaded_prefix(int64 offset) const { switch (node_->local_.type()) { + case LocalFileLocation::Type::Empty: + return 0; case LocalFileLocation::Type::Full: - return node_->size_; + if (offset < node_->size_) { + return node_->size_ - offset; + } + return 0; case LocalFileLocation::Type::Partial: - return node_->local_.partial().part_size_ * node_->local_.partial().ready_part_count_; + if (is_encrypted_secure()) { + // File is not decrypted and verified yet + return 0; + } + return Bitmask(Bitmask::Decode{}, node_->local_.partial().ready_bitmask_) + .get_ready_prefix_size(offset, node_->local_.partial().part_size_, node_->size_); + default: + UNREACHABLE(); + return 0; + } +} + +int64 FileView::local_prefix_size() const { + switch (node_->local_.type()) { + case LocalFileLocation::Type::Full: + return node_->download_offset_ <= node_->size_ ? node_->size_ - node_->download_offset_ : 0; + case LocalFileLocation::Type::Partial: { + if (is_encrypted_secure()) { + // File is not decrypted and verified yet + return 0; + } + return node_->local_ready_prefix_size_; + } default: return 0; } @@ -313,8 +654,9 @@ int64 FileView::local_total_size() const { 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_); + VLOG(update_file) << "Have local_ready_prefix_size = " << node_->local_ready_prefix_size_ + << " and local_ready_size = " << node_->local_ready_size_; + return max(node_->local_ready_prefix_size_, node_->local_ready_size_); default: UNREACHABLE(); return 0; @@ -322,25 +664,26 @@ int64 FileView::local_total_size() const { } bool FileView::is_uploading() const { - return node_->upload_priority_ != 0 || node_->generate_upload_priority_ != 0; + return node_->upload_priority_ != 0 || node_->generate_upload_priority_ != 0 || node_->upload_pause_.is_valid(); } 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; + if (node_->remote_.is_full_alive) { + return node_->size_; + } + if (node_->remote_.partial) { + auto part_size = static_cast<int64>(node_->remote_.partial->part_size_); + auto ready_part_count = node_->remote_.partial->ready_part_count_; + auto remote_ready_size = node_->remote_.ready_size; + VLOG(update_file) << "Have part_size = " << part_size << ", remote_ready_part_count = " << ready_part_count + << ", remote_ready_size = " << remote_ready_size << ", size = " << size(); + auto res = max(part_size * ready_part_count, remote_ready_size); + if (size() != 0 && size() < res) { + res = size(); } - default: - return 0; + return res; } + return node_->remote_.ready_size; //??? } string FileView::path() const { @@ -350,7 +693,7 @@ string FileView::path() const { case LocalFileLocation::Type::Partial: return node_->local_.partial().path_; default: - return ""; + return string(); } } @@ -366,8 +709,8 @@ const string &FileView::remote_name() const { return node_->remote_name_; } -string FileView::suggested_name() const { - return node_->suggested_name(); +string FileView::suggested_path() const { + return node_->suggested_path(); } DialogId FileView::owner_dialog_id() const { @@ -392,11 +735,19 @@ bool FileView::can_download_from_server() const { if (remote_location().file_type_ == FileType::Encrypted && encryption_key().empty()) { return false; } + if (remote_location().is_web()) { + return true; + } if (remote_location().get_dc_id().is_empty()) { return false; } + if (!remote_location().is_encrypted_any() && !remote_location().has_file_reference() && + ((node_->download_id_ == 0 && node_->download_was_update_file_reference_) || !node_->remote_.is_full_alive)) { + return false; + } return true; } + bool FileView::can_generate() const { return has_generate_location(); } @@ -408,14 +759,68 @@ bool FileView::can_delete() const { return node_->local_.type() == LocalFileLocation::Type::Partial; } +string FileView::get_unique_id(const FullGenerateFileLocation &location) { + return base64url_encode(zero_encode('\xff' + serialize(location))); +} + +string FileView::get_unique_id(const FullRemoteFileLocation &location) { + return base64url_encode(zero_encode(serialize(location.as_unique()))); +} + +string FileView::get_persistent_id(const FullGenerateFileLocation &location) { + auto binary = serialize(location); + + binary = zero_encode(binary); + binary.push_back(FileNode::PERSISTENT_ID_VERSION_GENERATED); + return base64url_encode(binary); +} + +string FileView::get_persistent_id(const FullRemoteFileLocation &location) { + auto binary = serialize(location); + + binary = zero_encode(binary); + binary.push_back(static_cast<char>(narrow_cast<uint8>(Version::Next) - 1)); + binary.push_back(FileNode::PERSISTENT_ID_VERSION); + return base64url_encode(binary); +} + +string FileView::get_persistent_file_id() const { + if (!empty()) { + if (has_alive_remote_location()) { + return get_persistent_id(remote_location()); + } else if (has_url()) { + return url(); + } else if (has_generate_location() && FileManager::is_remotely_generated_file(generate_location().conversion_)) { + return get_persistent_id(generate_location()); + } + } + return string(); +} + +string FileView::get_unique_file_id() const { + if (!empty()) { + if (has_alive_remote_location()) { + if (!remote_location().is_web()) { + return get_unique_id(remote_location()); + } + } else if (has_generate_location() && FileManager::is_remotely_generated_file(generate_location().conversion_)) { + return get_unique_id(generate_location()); + } + } + return string(); +} + /*** FileManager ***/ +static int merge_choose_remote_location(const FullRemoteFileLocation &x, FileLocationSource x_source, + const FullRemoteFileLocation &y, FileLocationSource y_source); + 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)) { +FileManager::FileManager(unique_ptr<Context> context) : context_(std::move(context)) { if (G()->parameters().use_file_db) { file_db_ = G()->td_db()->get_file_db_shared(); } @@ -424,32 +829,7 @@ FileManager::FileManager(std::unique_ptr<Context> context) : context_(std::move( 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()); }); + G()->td_db()->with_db_path([bad_paths = &bad_paths_](CSlice path) { bad_paths->insert(path.str()); }); } void FileManager::init_actor() { @@ -458,13 +838,15 @@ void FileManager::init_actor() { 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); + Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), remote_location_info_, file_hash_to_file_id_, + local_location_to_file_id_, generate_location_to_file_id_, + pmc_id_to_file_node_id_, file_id_info_, empty_file_ids_, file_nodes_); } 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(); + return PSTRING() << (file_name.empty() ? file_type : file_name) << '.' << file_extension; } string FileManager::get_file_name(FileType file_type, Slice path) { @@ -486,13 +868,14 @@ string FileManager::get_file_name(FileType file_type, Slice path) { break; case FileType::VoiceNote: if (extension != "ogg" && extension != "oga" && extension != "mp3" && extension != "mpeg3" && - extension != "m4a") { + extension != "m4a" && extension != "opus") { return fix_file_extension(file_name, "voice", "oga"); } break; case FileType::Video: case FileType::VideoNote: - if (extension != "mov" && extension != "3gp" && extension != "mpeg4" && extension != "mp4") { + if (extension != "mov" && extension != "3gp" && extension != "mpeg4" && extension != "mp4" && + extension != "mkv") { return fix_file_extension(file_name, "video", "mp4"); } break; @@ -502,13 +885,31 @@ string FileManager::get_file_name(FileType file_type, Slice path) { return fix_file_extension(file_name, "audio", "mp3"); } break; - case FileType::Document: + case FileType::Wallpaper: + case FileType::Background: + if (extension != "jpg" && extension != "jpeg" && extension != "png") { + return fix_file_extension(file_name, "wallpaper", "jpg"); + } + break; case FileType::Sticker: + if (extension != "webp" && extension != "tgs" && extension != "webm") { + return fix_file_extension(file_name, "sticker", "webp"); + } + break; + case FileType::Ringtone: + if (extension != "ogg" && extension != "oga" && extension != "mp3" && extension != "mpeg3") { + return fix_file_extension(file_name, "notification_sound", "mp3"); + } + break; + case FileType::Document: case FileType::Animation: case FileType::Encrypted: case FileType::Temp: case FileType::EncryptedThumbnail: - case FileType::Wallpaper: + case FileType::SecureEncrypted: + case FileType::SecureDecrypted: + case FileType::DocumentAsFile: + case FileType::CallLog: break; default: UNREACHABLE(); @@ -516,93 +917,186 @@ string FileManager::get_file_name(FileType file_type, Slice path) { 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 */; +bool FileManager::is_remotely_generated_file(Slice conversion) { + return begins_with(conversion, "#map#") || begins_with(conversion, "#audio_t#"); +} - if (location.path_.empty()) { - return Status::Error("File must have non-empty path"); +void FileManager::check_local_location(FileId file_id, bool skip_file_size_checks) { + auto node = get_sync_file_node(file_id); + if (node) { + check_local_location(node, skip_file_size_checks).ignore(); } - TRY_RESULT(path, realpath(location.path_, true)); - if (bad_paths_.count(path) != 0) { - return Status::Error("Sending of internal database files is forbidden"); +} + +Status FileManager::check_local_location(FileNodePtr node, bool skip_file_size_checks) { + Status status; + if (node->local_.type() == LocalFileLocation::Type::Full) { + auto r_info = check_full_local_location({node->local_.full(), node->size_}, skip_file_size_checks); + if (r_info.is_error()) { + status = r_info.move_as_error(); + } else if (bad_paths_.count(r_info.ok().location_.path_) != 0) { + status = Status::Error(400, "Sending of internal database files is forbidden"); + } else if (r_info.ok().location_ != node->local_.full() || r_info.ok().size_ != node->size_) { + LOG(ERROR) << "Local location changed from " << node->local_.full() << " with size " << node->size_ << " to " + << r_info.ok().location_ << " with size " << r_info.ok().size_; + } + } else if (node->local_.type() == LocalFileLocation::Type::Partial) { + status = check_partial_local_location(node->local_.partial()); } - 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 (status.is_error()) { + on_failed_check_local_location(node); } - if (stat.size_ < 0) { - // TODO is it possible? - return Status::Error("File is too big"); + return status; +} + +void FileManager::on_failed_check_local_location(FileNodePtr node) { + send_closure(G()->download_manager(), &DownloadManager::remove_file_if_finished, node->main_file_id_); + node->drop_local_location(); + try_flush_node(node, "on_failed_check_local_location"); +} + +void FileManager::check_local_location_async(FileNodePtr node, bool skip_file_size_checks, Promise<Unit> promise) { + if (node->local_.type() == LocalFileLocation::Type::Empty) { + return promise.set_value(Unit()); } - if (stat.size_ == 0) { - return Status::Error("File must be non-empty"); + + if (node->local_.type() == LocalFileLocation::Type::Full) { + send_closure(file_load_manager_, &FileLoadManager::check_full_local_location, + FullLocalLocationInfo{node->local_.full(), node->size_}, skip_file_size_checks, + PromiseCreator::lambda([actor_id = actor_id(this), file_id = node->main_file_id_, + checked_location = node->local_, + promise = std::move(promise)](Result<FullLocalLocationInfo> result) mutable { + send_closure(actor_id, &FileManager::on_check_full_local_location, file_id, + std::move(checked_location), std::move(result), std::move(promise)); + })); + } else { + CHECK(node->local_.type() == LocalFileLocation::Type::Partial); + send_closure(file_load_manager_, &FileLoadManager::check_partial_local_location, node->local_.partial(), + PromiseCreator::lambda([actor_id = actor_id(this), file_id = node->main_file_id_, + checked_location = node->local_, + promise = std::move(promise)](Result<Unit> result) mutable { + send_closure(actor_id, &FileManager::on_check_partial_local_location, file_id, + std::move(checked_location), std::move(result), std::move(promise)); + })); } +} + +void FileManager::on_check_full_local_location(FileId file_id, LocalFileLocation checked_location, + Result<FullLocalLocationInfo> r_info, Promise<Unit> promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); - if (size == 0) { - size = stat.size_; + auto node = get_file_node(file_id); + if (!node) { + return; } - 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 (node->local_ != checked_location) { + LOG(INFO) << "Full location changed while being checked; ignore check result"; + return promise.set_value(Unit()); } - 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))); + Status status; + if (r_info.is_error()) { + status = r_info.move_as_error(); + } else if (bad_paths_.count(r_info.ok().location_.path_) != 0) { + status = Status::Error(400, "Sending of internal database files is forbidden"); + } else if (r_info.ok().location_ != node->local_.full() || r_info.ok().size_ != node->size_) { + LOG(ERROR) << "Local location changed from " << node->local_.full() << " with size " << node->size_ << " to " + << r_info.ok().location_ << " with size " << r_info.ok().size_; } - if (size >= MAX_FILE_SIZE) { - return Status::Error(PSLICE() << "File \"" << location.path_ << "\" is too big " - << tag("size", format::as_size(size))); + if (status.is_error()) { + on_failed_check_local_location(node); + promise.set_error(std::move(status)); + } else { + promise.set_value(Unit()); } - 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"); +void FileManager::on_check_partial_local_location(FileId file_id, LocalFileLocation checked_location, + Result<Unit> result, Promise<Unit> promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + auto node = get_file_node(file_id); + CHECK(node); + if (node->local_ != checked_location) { + LOG(INFO) << "Partial location changed while being checked; ignore check result"; + return promise.set_value(Unit()); + } + if (result.is_error()) { + on_failed_check_local_location(node); + promise.set_error(result.move_as_error()); + } else { + promise.set_value(Unit()); } - // 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()); +void FileManager::recheck_full_local_location(FullLocalLocationInfo location_info, bool skip_file_size_checks) { + auto promise = PromiseCreator::lambda([actor_id = actor_id(this), checked_location = location_info.location_]( + Result<FullLocalLocationInfo> result) mutable { + send_closure(actor_id, &FileManager::on_recheck_full_local_location, std::move(checked_location), + std::move(result)); + }); + send_closure(file_load_manager_, &FileLoadManager::check_full_local_location, std::move(location_info), + skip_file_size_checks, std::move(promise)); +} + +void FileManager::on_recheck_full_local_location(FullLocalFileLocation checked_location, + Result<FullLocalLocationInfo> r_info) { + if (G()->close_flag()) { + return; } - if (status.is_error()) { - node->set_local_location(LocalFileLocation(), 0); - try_flush_node(node); + + auto file_id_it = local_location_to_file_id_.find(checked_location); + if (file_id_it == local_location_to_file_id_.end()) { + return; } - return status; + auto file_id = file_id_it->second; + + on_check_full_local_location(file_id, LocalFileLocation(checked_location), std::move(r_info), Promise<Unit>()); +} + +bool FileManager::try_fix_partial_local_location(FileNodePtr node) { + LOG(INFO) << "Trying to fix partial local location"; + if (node->local_.type() != LocalFileLocation::Type::Partial) { + LOG(INFO) << " failed - not a partial location"; + return false; + } + auto partial = node->local_.partial(); + if (!partial.iv_.empty()) { + // can't recalc iv_ + LOG(INFO) << " failed - partial location has nonempty iv"; + return false; + } + if (partial.part_size_ >= 512 * (1 << 10) || (partial.part_size_ & (partial.part_size_ - 1)) != 0) { + LOG(INFO) << " failed - too big part_size already: " << partial.part_size_; + return false; + } + auto old_part_size = narrow_cast<int32>(partial.part_size_); + int new_part_size = 512 * (1 << 10); + auto k = new_part_size / old_part_size; + Bitmask mask(Bitmask::Decode(), partial.ready_bitmask_); + auto new_mask = mask.compress(k); + + partial.part_size_ = new_part_size; + partial.ready_bitmask_ = new_mask.encode(); + + auto ready_size = new_mask.get_total_size(partial.part_size_, node->size_); + node->set_local_location(LocalFileLocation(std::move(partial)), ready_size, -1, -1); + LOG(INFO) << " ok: increase part_size " << old_part_size << "->" << new_part_size; + return true; } 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(); + CHECK(static_cast<size_t>(file_id.get()) < file_id_info_.size()); return &file_id_info_[file_id.get()]; } -FileId FileManager::dup_file_id(FileId file_id) { +FileId FileManager::dup_file_id(FileId file_id, const char *source) { 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; + auto result = FileId(create_file_id(file_node_id, file_node).get(), file_id.get_remote()); + LOG(INFO) << "Dup file " << file_id << " to " << result << " from " << source; return result; } @@ -612,20 +1106,24 @@ FileId FileManager::create_file_id(int32 file_node_id, FileNode *file_node) { 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_) { + if (info->send_updates_flag_ || info->pin_flag_ || info->sent_file_id_flag_) { + LOG(DEBUG) << "Can't forget file " << file_id << ", because of" + << (info->send_updates_flag_ ? " (sent updates)" : "") << (info->pin_flag_ ? " (pin)" : "") + << (info->sent_file_id_flag_ ? " (sent file identifier)" : ""); return; } auto file_node = get_file_node(file_id); if (file_node->main_file_id_ == file_id) { + LOG(DEBUG) << "Can't forget main file " << 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); + LOG(DEBUG) << "Forget file " << file_id; + bool is_removed = td::remove(file_node->file_ids_, file_id); + CHECK(is_removed); *info = FileIdInfo(); empty_file_ids_.push_back(file_id.get()); } @@ -642,29 +1140,41 @@ void FileManager::on_file_unlink(const FullLocalFileLocation &location) { 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); + clear_from_pmc(file_node); + send_closure(G()->download_manager(), &DownloadManager::remove_file_if_finished, file_node->main_file_id_); + file_node->drop_local_location(); + try_flush_node(file_node, "on_file_unlink"); } Result<FileId> FileManager::register_local(FullLocalFileLocation location, DialogId owner_dialog_id, int64 size, - bool get_by_hash, bool force) { + bool get_by_hash, bool force, bool skip_file_size_checks, + FileId merge_file_id) { // 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); + return register_file(std::move(data), FileLocationSource::None /*won't be used*/, merge_file_id, "register_local", + force, skip_file_size_checks); } -FileId FileManager::register_remote(const FullRemoteFileLocation &location, FileLocationSource file_location_source, - DialogId owner_dialog_id, int64 size, int64 expected_size, string name) { +FileId FileManager::register_remote(FullRemoteFileLocation location, FileLocationSource file_location_source, + DialogId owner_dialog_id, int64 size, int64 expected_size, string remote_name) { FileData data; - data.remote_ = RemoteFileLocation(location); + auto url = location.get_url(); + data.remote_ = RemoteFileLocation(std::move(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(); + data.remote_name_ = std::move(remote_name); + + auto file_id = register_file(std::move(data), file_location_source, FileId(), "register_remote", false).move_as_ok(); + if (!url.empty()) { + auto file_node = get_file_node(file_id); + CHECK(file_node); + file_node->set_url(url); + } + return file_id; } FileId FileManager::register_url(string url, FileType file_type, FileLocationSource file_location_source, @@ -679,42 +1189,80 @@ FileId FileManager::register_url(string url, FileType file_type, FileLocationSou Result<FileId> FileManager::register_generate(FileType file_type, FileLocationSource file_location_source, string original_path, string conversion, DialogId owner_dialog_id, int64 expected_size) { + // add #mtime# into conversion + if (!original_path.empty() && conversion[0] != '#' && PathView(original_path).is_absolute()) { + auto file_paths = log_interface->get_file_paths(); + if (!td::contains(file_paths, original_path)) { + auto r_stat = stat(original_path); + uint64 mtime = r_stat.is_ok() ? r_stat.ok().mtime_nsec_ : 0; + conversion = PSTRING() << "#mtime#" << lpad0(to_string(mtime), 20) << '#' << conversion; + } + } + FileData data; - data.generate_ = make_unique<FullGenerateFileLocation>(file_type, std::move(original_path), std::move(conversion)); + data.generate_ = + td::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); + return register_file(std::move(data), file_location_source, FileId(), "register_generate", false); } -Result<FileId> FileManager::register_file(FileData data, FileLocationSource file_location_source, const char *source, - bool force) { +Result<FileId> FileManager::register_file(FileData &&data, FileLocationSource file_location_source, + FileId merge_file_id, const char *source, bool force, + bool skip_file_size_checks) { 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) { + bool is_from_database = file_location_source == FileLocationSource::FromBinlog || + file_location_source == FileLocationSource::FromDatabase; + if (is_from_database) { 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_; + data.local_.full().path_ = PSTRING() + << 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 (file_location_source != FileLocationSource::FromDatabase) { + Status status; + auto r_info = check_full_local_location({data.local_.full(), data.size_}, skip_file_size_checks); + if (r_info.is_error()) { + status = r_info.move_as_error(); + } else if (bad_paths_.count(r_info.ok().location_.path_) != 0) { + status = Status::Error(400, "Sending of internal database files is forbidden"); } + if (status.is_error()) { + LOG(INFO) << "Invalid " << data.local_.full() << ": " << status << " from " << source; + data.local_ = LocalFileLocation(); + if (data.remote_.type() == RemoteFileLocation::Type::Partial) { + data.remote_ = {}; + } - if (!has_remote && !has_generate) { - return std::move(status); + if (!has_remote && !has_generate) { + return std::move(status); + } + } else { + data.local_ = LocalFileLocation(std::move(r_info.ok().location_)); + data.size_ = r_info.ok().size_; } + } else { + // the location has been checked previously, but recheck it just in case + recheck_full_local_location({data.local_.full(), data.size_}, skip_file_size_checks); } } 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"); + return Status::Error(400, "No location"); + } + + if (data.size_ < 0) { + LOG(ERROR) << "Receive file of size " << data.size_; + data.size_ = 0; + } + if (data.expected_size_ < 0) { + LOG(ERROR) << "Receive file of expected size " << data.expected_size_; + data.expected_size_ = 0; } FileId file_id = next_file_id(); @@ -723,33 +1271,31 @@ Result<FileId> FileManager::register_file(FileData data, FileLocationSource file // 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_; + node = td::make_unique<FileNode>(std::move(data.local_), NewRemoteFileLocation(data.remote_, file_location_source), + 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->pmc_id_ = FileDbId(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) { + vector<FileId> to_merge; + auto register_location = [&](const auto &location, auto &mp) -> FileId * { auto &other_id = mp[location]; if (other_id.empty()) { other_id = file_id; - get_file_id_info(file_id)->pin_flag_ = true; - return true; + return &other_id; } else { to_merge.push_back(other_id); - return false; + return nullptr; } }; bool new_remote = false; int32 remote_key = 0; if (file_view.has_remote_location()) { - RemoteInfo info{file_view.remote_location(), file_id}; + RemoteInfo info{file_view.remote_location(), file_location_source, 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) { @@ -757,25 +1303,24 @@ Result<FileId> FileManager::register_file(FileData data, FileLocationSource file 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) { + if (merge_choose_remote_location(file_view.remote_location(), file_location_source, stored_info.remote_, + stored_info.file_location_source_) == 0) { stored_info.remote_ = file_view.remote_location(); + stored_info.file_location_source_ = file_location_source; } } } - bool new_local = false; + FileId *new_local_file_id = nullptr; if (file_view.has_local_location()) { - new_local = register_location(file_view.local_location(), local_location_to_file_id_); + new_local_file_id = register_location(file_view.local_location(), local_location_to_file_id_); } - bool new_generate = false; + FileId *new_generate_file_id = nullptr; if (file_view.has_generate_location()) { - new_generate = register_location(file_view.generate_location(), generate_location_to_file_id_); + new_generate_file_id = 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()); + td::unique(to_merge); - int new_cnt = new_remote + new_local + new_generate; + int new_cnt = new_remote + (new_local_file_id != nullptr) + (new_generate_file_id != nullptr); if (data.pmc_id_ == 0 && file_db_ && new_cnt > 0) { node->need_load_from_pmc_ = true; } @@ -784,44 +1329,107 @@ Result<FileId> FileManager::register_file(FileData data, FileLocationSource file // may invalidate node merge(file_id, id, no_sync_merge).ignore(); } + Status status; + if (merge_file_id.is_valid()) { + status = merge(file_id, merge_file_id); + } - try_flush_node(get_file_node(file_id)); + try_flush_node(get_file_node(file_id), "register_file"); auto main_file_id = get_file_node(file_id)->main_file_id_; - try_forget_file_id(file_id); + if (main_file_id != file_id) { + if (new_local_file_id != nullptr) { + *new_local_file_id = main_file_id; + } + if (new_generate_file_id != nullptr) { + *new_generate_file_id = main_file_id; + } + try_forget_file_id(file_id); + } + if ((new_local_file_id != nullptr) || (new_generate_file_id != nullptr)) { + get_file_id_info(main_file_id)->pin_flag_ = true; + } + + if (!data.file_source_ids_.empty()) { + VLOG(file_references) << "Loaded " << data.file_source_ids_ << " for file " << main_file_id << " from " << source; + for (auto file_source_id : data.file_source_ids_) { + CHECK(file_source_id.is_valid()); + context_->add_file_source(main_file_id, file_source_id); + } + } + if (status.is_error()) { + return std::move(status); + } 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()); +static int merge_choose_local_location(const LocalFileLocation &x, const LocalFileLocation &y) { + auto x_type = static_cast<int32>(x.type()); + auto 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; +static int merge_choose_file_source_location(FileLocationSource x, FileLocationSource y) { + return static_cast<int>(x) < static_cast<int>(y); +} + +static int merge_choose_remote_location(const FullRemoteFileLocation &x, FileLocationSource x_source, + const FullRemoteFileLocation &y, FileLocationSource y_source) { + LOG(INFO) << "Choose between " << x << " from " << x_source << " and " << y << " from " << y_source; + if (x.is_web() != y.is_web()) { + return x.is_web(); // prefer non-web } - // 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; + auto x_ref = x.has_file_reference(); + auto y_ref = y.has_file_reference(); + if (x_ref || y_ref) { + if (x_ref != y_ref) { + return !x_ref; + } + if (x.get_file_reference() != y.get_file_reference()) { + return merge_choose_file_source_location(x_source, y_source); } } + if ((x.get_access_hash() != y.get_access_hash() || x.get_source() != y.get_source()) && + (x_source != y_source || x.is_web() || x.get_id() == y.get_id())) { + return merge_choose_file_source_location(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; + +static int merge_choose_remote_location(const NewRemoteFileLocation &x, const NewRemoteFileLocation &y) { + if (x.is_full_alive != y.is_full_alive) { + return !x.is_full_alive; + } + if (x.is_full_alive) { + return merge_choose_remote_location(x.full.value(), x.full_source, y.full.value(), y.full_source); + } + if (!x.partial != !y.partial) { + return !x.partial; + } + return 2; +} + +static int merge_choose_generate_location(const unique_ptr<FullGenerateFileLocation> &x, + const unique_ptr<FullGenerateFileLocation> &y) { + int x_empty = (x == nullptr); + int y_empty = (y == nullptr); + if (x_empty != y_empty) { + return x_empty ? 1 : 0; + } + if (!x_empty && *x != *y) { + bool x_has_mtime = begins_with(x->conversion_, "#mtime#"); + bool y_has_mtime = begins_with(y->conversion_, "#mtime#"); + if (x_has_mtime != y_has_mtime) { + return x_has_mtime ? 0 : 1; + } + return x->conversion_ >= y->conversion_ + ? 0 + : 1; // the bigger conversion, the bigger mtime or at least more stable choise } return 2; } @@ -839,6 +1447,7 @@ static int merge_choose_size(int64 x, int64 y) { } return 2; } + static int merge_choose_expected_size(int64 x, int64 y) { if (x == 0) { return 1; @@ -874,32 +1483,34 @@ static int merge_choose_encryption_key(const FileEncryptionKey &a, const FileEnc if (a.empty() != b.empty()) { return a.empty() > b.empty(); } - if (a.key_iv_ != b.key_iv_) { + if (a != b) { return -1; } return 2; } -void FileManager::cancel_download(FileNodePtr node) { +void FileManager::do_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->download_was_update_file_reference_ = false; node->set_download_priority(0); } -void FileManager::cancel_upload(FileNodePtr node) { +void FileManager::do_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->upload_was_update_file_reference_ = false; node->set_upload_priority(0); } -void FileManager::cancel_generate(FileNodePtr node) { +void FileManager::do_cancel_generate(FileNodePtr node) { if (node->generate_id_ == 0) { return; } @@ -909,49 +1520,75 @@ void FileManager::cancel_generate(FileNodePtr node) { 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; - +Status FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sync) { 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); + return Status::Error(PSLICE() << "Can't merge files. First identifier is invalid: " << x_file_id << " and " + << y_file_id); } if (!y_file_id.is_valid()) { - return x_node->main_file_id_; + LOG(DEBUG) << "Old file is invalid"; + return Status::OK(); } 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); + return Status::Error(PSLICE() << "Can't merge files. Second identifier is invalid: " << x_file_id << " and " + << y_file_id); } if (x_file_id == x_node->upload_pause_) { - x_node->upload_pause_ = FileId(); + x_node->set_upload_pause(FileId()); } if (x_node.get() == y_node.get()) { - return x_node->main_file_id_; + if (x_file_id != y_file_id) { + LOG(DEBUG) << "New file " << x_file_id << " and old file " << y_file_id << " are already merged"; + } + try_flush_node_info(x_node, "merge 1"); + return Status::OK(); } if (y_file_id == y_node->upload_pause_) { - y_node->upload_pause_ = FileId(); + y_node->set_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(); + LOG(INFO) << "Merge new file " << x_file_id << " and old file " << y_file_id; + if (x_node->remote_.full && y_node->remote_.full && !x_node->remote_.full.value().is_web() && + !y_node->remote_.full.value().is_web() && y_node->remote_.is_full_alive && + x_node->remote_.full_source == FileLocationSource::FromServer && + y_node->remote_.full_source == FileLocationSource::FromServer && + x_node->remote_.full.value().get_dc_id() != y_node->remote_.full.value().get_dc_id()) { + LOG(ERROR) << "File remote location was changed from " << y_node->remote_.full.value() << " to " + << x_node->remote_.full.value(); + } + + bool drop_last_successful_force_reupload_time = x_node->last_successful_force_reupload_time_ <= 0 && + x_node->remote_.full && + x_node->remote_.full_source == FileLocationSource::FromServer; + + auto count_local = [](auto &node) { + return std::accumulate(node->file_ids_.begin(), node->file_ids_.end(), 0, + [](const auto &x, const auto &y) { return x + (y.get_remote() != 0); }); + }; + auto x_local_file_ids = count_local(x_node); + auto y_local_file_ids = count_local(y_node); + if (x_local_file_ids + y_local_file_ids > 100) { + } + + if (y_node->file_ids_.size() >= 100 || x_node->file_ids_.size() >= 100) { + LOG(INFO) << "Merge files with " << x_local_file_ids << '/' << x_node->file_ids_.size() << " and " + << y_local_file_ids << '/' << y_node->file_ids_.size() << " file IDs"; } 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 trusted_by_source = merge_choose_file_source_location(x_node->remote_.full_source, y_node->remote_.full_source); - 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 local_i = merge_choose_local_location(x_node->local_, y_node->local_); + int remote_i = merge_choose_remote_location(x_node->remote_, y_node->remote_); + int generate_i = merge_choose_generate_location(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_); @@ -962,45 +1599,66 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy y_node->main_file_id_, y_node->main_file_id_priority_); if (size_i == -1) { + try_flush_node_info(x_node, "merge 2"); + try_flush_node_info(y_node, "merge 3"); 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"; + if (nodes[remote_i]->remote_.full && nodes[local_i]->local_.type() != LocalFileLocation::Type::Partial) { + LOG(ERROR) << "Different encryption key in files, but lets choose same key as remote location"; encryption_key_i = remote_i; } else { + try_flush_node_info(x_node, "merge 4"); + try_flush_node_info(y_node, "merge 5"); 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); + // prefer more trusted source + if (remote_name_i == 2) { + remote_name_i = trusted_by_source; + } + if (url_i == 2) { + url_i = trusted_by_source; + } + if (expected_size_i == 2) { + expected_size_i = trusted_by_source; + } + + int node_i = + std::make_tuple(y_node->pmc_id_.is_valid(), x_node->pmc_id_, y_node->file_ids_.size(), main_file_id_i == 1) > + std::make_tuple(x_node->pmc_id_.is_valid(), y_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; + LOG(DEBUG) << "Have x_node->pmc_id_ = " << x_node->pmc_id_.get() << ", y_node->pmc_id_ = " << y_node->pmc_id_.get() + << ", 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 << ", trusted_by_source = " << trusted_by_source + << ", x_source = " << x_node->remote_.full_source << ", y_source = " << y_node->remote_.full_source; if (local_i == other_node_i) { - cancel_download(node); - node->set_local_location(other_node->local_, other_node->local_ready_size_); + do_cancel_download(node); + node->set_download_offset(other_node->download_offset_); + node->set_local_location(other_node->local_, other_node->local_ready_size_, other_node->download_offset_, + other_node->local_ready_prefix_size_); node->download_id_ = other_node->download_id_; + node->download_was_update_file_reference_ = other_node->download_was_update_file_reference_; node->is_download_started_ |= other_node->is_download_started_; node->set_download_priority(other_node->download_priority_); other_node->download_id_ = 0; + other_node->download_was_update_file_reference_ = false; other_node->is_download_started_ = false; other_node->download_priority_ = 0; + other_node->download_offset_ = 0; + other_node->local_ready_prefix_size_ = 0; - //cancel_generate(node); + //do_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_); @@ -1010,25 +1668,27 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy //other_node->generate_download_priority_ = 0; //other_node->generate_upload_priority_ = 0; } else { - cancel_download(other_node); - //cancel_generate(other_node); + do_cancel_download(other_node); + //do_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_); + do_cancel_upload(node); + node->set_new_remote_location(std::move(other_node->remote_)); node->upload_id_ = other_node->upload_id_; + node->upload_was_update_file_reference_ = other_node->upload_was_update_file_reference_; node->set_upload_priority(other_node->upload_priority_); - node->upload_pause_ = other_node->upload_pause_; + node->set_upload_pause(other_node->upload_pause_); other_node->upload_id_ = 0; + other_node->upload_was_update_file_reference_ = false; other_node->upload_priority_ = 0; other_node->upload_pause_ = FileId(); } else { - cancel_upload(other_node); + do_cancel_upload(other_node); } if (generate_i == other_node_i) { - cancel_generate(node); + do_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_); @@ -1037,7 +1697,7 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy other_node->generate_download_priority_ = 0; other_node->generate_upload_priority_ = 0; } else { - cancel_generate(other_node); + do_cancel_generate(other_node); } if (size_i == other_node_i) { @@ -1066,10 +1726,20 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy } node->need_load_from_pmc_ |= other_node->need_load_from_pmc_; node->can_search_locally_ &= other_node->can_search_locally_; + node->upload_prefer_small_ |= other_node->upload_prefer_small_; + + if (drop_last_successful_force_reupload_time) { + node->last_successful_force_reupload_time_ = -1e10; + } else if (other_node->last_successful_force_reupload_time_ > node->last_successful_force_reupload_time_) { + node->last_successful_force_reupload_time_ = other_node->last_successful_force_reupload_time_; + } if (main_file_id_i == other_node_i) { + context_->on_merge_files(other_node->main_file_id_, node->main_file_id_); node->main_file_id_ = other_node->main_file_id_; node->main_file_id_priority_ = other_node->main_file_id_priority_; + } else { + context_->on_merge_files(node->main_file_id_, other_node->main_file_id_); } bool send_updates_flag = false; @@ -1078,8 +1748,7 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy 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_; + CHECK(file_id_info->node_id_ == node_ids[other_node_i]); file_id_info->node_id_ = node_ids[node_i]; send_updates_flag |= file_id_info->send_updates_flag_; } @@ -1091,7 +1760,12 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy node->on_info_changed(); } - // Check is some download/upload queries are ready + if (node->file_ids_.size() > (static_cast<size_t>(1) << file_node_size_warning_exp_)) { + LOG(WARNING) << "File of type " << file_view.get_type() << " has " << node->file_ids_.size() << " file identifiers"; + file_node_size_warning_exp_++; + } + + // Check if 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()) { @@ -1101,7 +1775,7 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy info->download_callback_.reset(); } } - if (info->upload_priority_ != 0 && file_view.has_remote_location()) { + if (info->upload_priority_ != 0 && file_view.has_active_upload_remote_location()) { info->upload_priority_ = 0; if (info->upload_callback_) { info->upload_callback_->on_upload_ok(file_id, nullptr); @@ -1113,42 +1787,137 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy file_nodes_[node_ids[other_node_i]] = nullptr; run_generate(node); - run_download(node); + run_download(node, false); run_upload(node, {}); - if (other_pmc_id != 0) { + if (other_pmc_id.is_valid()) { // 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); + try_flush_node_full(node, node_i != remote_i, node_i != local_i, node_i != generate_i, other_pmc_id); + + return Status::OK(); +} + +void FileManager::add_file_source(FileId file_id, FileSourceId file_source_id) { + auto node = get_sync_file_node(file_id); // synchronously load the file to preload known file sources + if (!node) { + return; + } + + CHECK(file_source_id.is_valid()); + if (context_->add_file_source(node->main_file_id_, file_source_id)) { + node->on_pmc_changed(); + try_flush_node_pmc(node, "add_file_source"); + } +} + +void FileManager::remove_file_source(FileId file_id, FileSourceId file_source_id) { + auto node = get_sync_file_node(file_id); // synchronously load the file to preload known file sources + if (!node) { + return; + } + + CHECK(file_source_id.is_valid()); + if (context_->remove_file_source(node->main_file_id_, file_source_id)) { + node->on_pmc_changed(); + try_flush_node_pmc(node, "remove_file_source"); + } +} + +void FileManager::change_files_source(FileSourceId file_source_id, const vector<FileId> &old_file_ids, + const vector<FileId> &new_file_ids) { + if (old_file_ids == new_file_ids) { + return; + } + CHECK(file_source_id.is_valid()); - return node->main_file_id_; + auto old_main_file_ids = get_main_file_ids(old_file_ids); + auto new_main_file_ids = get_main_file_ids(new_file_ids); + for (auto file_id : old_main_file_ids) { + auto it = new_main_file_ids.find(file_id); + if (it == new_main_file_ids.end()) { + remove_file_source(file_id, file_source_id); + } else { + new_main_file_ids.erase(it); + } + } + for (auto file_id : new_main_file_ids) { + add_file_source(file_id, file_source_id); + } +} + +void FileManager::on_file_reference_repaired(FileId file_id, FileSourceId file_source_id, Result<Unit> &&result, + Promise<Unit> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + auto file_view = get_file_view(file_id); + CHECK(!file_view.empty()); + if (result.is_ok() && + (!file_view.has_active_upload_remote_location() || !file_view.has_active_download_remote_location())) { + result = Status::Error("No active remote location"); + } + if (result.is_error() && result.error().code() != 429 && result.error().code() < 500) { + VLOG(file_references) << "Invalid " << file_source_id << " " << result.error(); + remove_file_source(file_id, file_source_id); + } + promise.set_result(std::move(result)); } -void FileManager::try_flush_node(FileNodePtr node, bool new_remote, bool new_local, bool new_generate, - FileDbId other_pmc_id) { +FlatHashSet<FileId, FileIdHash> FileManager::get_main_file_ids(const vector<FileId> &file_ids) { + FlatHashSet<FileId, FileIdHash> result; + for (auto file_id : file_ids) { + auto node = get_file_node(file_id); + if (node) { + result.insert(node->main_file_id_); + } + } + return result; +} + +void FileManager::try_flush_node_full(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) { + flush_to_pmc(node, new_remote, new_local, new_generate, "try_flush_node_full"); + if (other_pmc_id.is_valid() && 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); + try_flush_node_info(node, "try_flush_node_full"); +} + +void FileManager::try_flush_node(FileNodePtr node, const char *source) { + try_flush_node_pmc(node, source); + try_flush_node_info(node, source); } -void FileManager::try_flush_node_info(FileNodePtr node) { +void FileManager::try_flush_node_pmc(FileNodePtr node, const char *source) { + if (node->need_pmc_flush()) { + if (file_db_) { + load_from_pmc(node, true, true, true); + flush_to_pmc(node, false, false, false, source); + } + node->on_pmc_flushed(); + } +} + +void FileManager::try_flush_node_info(FileNodePtr node, const char *source) { 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; + VLOG(update_file) << "Send UpdateFile about file " << file_id << " from " << source; context_->on_file_updated(file_id); } + if (info->download_callback_) { + // For DownloadManager. For everybody else it is just an empty function call (I hope). + info->download_callback_->on_progress(file_id); + } } node->on_info_flushed(); } @@ -1158,7 +1927,7 @@ void FileManager::clear_from_pmc(FileNodePtr node) { if (!file_db_) { return; } - if (node->pmc_id_ == 0) { + if (node->pmc_id_.empty()) { return; } @@ -1167,44 +1936,53 @@ void FileManager::clear_from_pmc(FileNodePtr node) { auto file_view = FileView(node); if (file_view.has_local_location()) { data.local_ = node->local_; + prepare_path_for_pmc(data.local_.full().file_type_, data.local_.full().path_); } if (file_view.has_remote_location()) { - data.remote_ = node->remote_; + data.remote_ = RemoteFileLocation(*node->remote_.full); } if (file_view.has_generate_location()) { - data.generate_ = std::make_unique<FullGenerateFileLocation>(*node->generate_); + data.generate_ = make_unique<FullGenerateFileLocation>(*node->generate_); } file_db_->clear_file_data(node->pmc_id_, data); - node->pmc_id_ = 0; + node->pmc_id_ = FileDbId(); } -void FileManager::flush_to_pmc(FileNodePtr node, bool new_remote, bool new_local, bool new_generate) { +void FileManager::flush_to_pmc(FileNodePtr node, bool new_remote, bool new_local, bool new_generate, + const char *source) { if (!file_db_) { return; } FileView view(node); bool create_flag = false; - if (node->pmc_id_ == 0) { + if (node->pmc_id_.empty()) { create_flag = true; node->pmc_id_ = file_db_->create_pmc_id(); } FileData data; - data.pmc_id_ = node->pmc_id_; + data.pmc_id_ = node->pmc_id_.get(); 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->remote_.full) { + data.remote_ = RemoteFileLocation(node->remote_.full.value()); + } else if (node->remote_.partial) { + data.remote_ = RemoteFileLocation(*node->remote_.partial); + } if (node->generate_ != nullptr && !begins_with(node->generate_->conversion_, "#file_id#")) { - data.generate_ = std::make_unique<FullGenerateFileLocation>(*node->generate_); + data.generate_ = make_unique<FullGenerateFileLocation>(*node->generate_); } - // TODO: not needed when GenerateLocation has constant convertion + // TODO: not needed when GenerateLocation has constant conversion if (data.remote_.type() != RemoteFileLocation::Type::Full && data.local_.type() != LocalFileLocation::Type::Full) { data.local_ = LocalFileLocation(); data.remote_ = RemoteFileLocation(); } + if (data.remote_.type() != RemoteFileLocation::Type::Full && node->encryption_key_.is_secure()) { + data.remote_ = RemoteFileLocation(); + } data.size_ = node->size_; data.expected_size_ = node->expected_size_; @@ -1212,6 +1990,9 @@ void FileManager::flush_to_pmc(FileNodePtr node, bool new_remote, bool new_local data.encryption_key_ = node->encryption_key_; data.url_ = node->url_; data.owner_dialog_id_ = node->owner_dialog_id_; + data.file_source_ids_ = context_->get_some_file_sources(view.get_main_file_id()); + VLOG(file_references) << "Save file " << view.get_main_file_id() << " to database with " << data.file_source_ids_ + << " from " << source; file_db_->set_file_data(node->pmc_id_, data, (create_flag || new_remote), (create_flag || new_local), (create_flag || new_generate)); @@ -1250,6 +2031,7 @@ void FileManager::load_from_pmc(FileNodePtr node, bool new_remote, bool new_loca return; } auto file_view = get_file_view(file_id); + CHECK(!file_view.empty()); FullRemoteFileLocation remote; FullLocalFileLocation local; @@ -1260,7 +2042,7 @@ void FileManager::load_from_pmc(FileNodePtr node, bool new_remote, bool new_loca } new_local &= file_view.has_local_location(); if (new_local) { - local = get_file_view(file_id).local_location(); + local = file_view.local_location(); prepare_path_for_pmc(local.file_type_, local.path_); } new_generate &= file_view.has_generate_location(); @@ -1268,25 +2050,24 @@ void FileManager::load_from_pmc(FileNodePtr node, bool new_remote, bool new_loca 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) { + LOG(DEBUG) << "Load from pmc file " << file_id << '/' << file_view.get_main_file_id() + << ", new_remote = " << new_remote << ", new_local = " << new_local << ", new_generate = " << new_generate; + auto load = [&](auto location, const char *source) { 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; + TRY_RESULT(new_file_id, + register_file(std::move(file_data), FileLocationSource::FromDatabase, FileId(), source, false)); + TRY_STATUS(merge(file_id, new_file_id)); // merge manually to keep merge parameters order return Status::OK(); }; if (new_remote) { - load(remote); + load(remote, "load remote from database").ignore(); } if (new_local) { - load(local); + load(local, "load local from database").ignore(); } if (new_generate) { - load(generate); + load(generate, "load generate from database").ignore(); } - return; } bool FileManager::set_encryption_key(FileId file_id, FileEncryptionKey key) { @@ -1302,11 +2083,15 @@ bool FileManager::set_encryption_key(FileId file_id, FileEncryptionKey key) { return false; } node->set_encryption_key(std::move(key)); - try_flush_node(node); + try_flush_node_pmc(node, "set_encryption_key"); return true; } bool FileManager::set_content(FileId file_id, BufferSlice bytes) { + if (G()->get_option_boolean("ignore_inline_thumbnails")) { + return false; + } + auto node = get_sync_file_node(file_id); if (!node) { return false; @@ -1321,18 +2106,18 @@ bool FileManager::set_content(FileId file_id, BufferSlice bytes) { return true; } - cancel_download(node); + do_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}); + QueryId id = queries_container_.create(Query{file_id, Query::Type::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()); + send_closure(file_load_manager_, &FileLoadManager::from_bytes, id, node->remote_.full.value().file_type_, + std::move(bytes), node->suggested_path()); return true; } @@ -1341,15 +2126,88 @@ void FileManager::get_content(FileId file_id, Promise<BufferSlice> promise) { if (!node) { return promise.set_error(Status::Error("Unknown file_id")); } - auto status = check_local_location(node); - status.ignore(); + check_local_location(node, true).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)); + send_closure(file_load_manager_, &FileLoadManager::get_content, node->local_.full().path_, std::move(promise)); +} + +void FileManager::read_file_part(FileId file_id, int64 offset, int64 count, int left_tries, + Promise<td_api::object_ptr<td_api::filePart>> promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + if (!file_id.is_valid()) { + return promise.set_error(Status::Error(400, "File identifier is invalid")); + } + auto node = get_sync_file_node(file_id); + if (!node) { + return promise.set_error(Status::Error(400, "File not found")); + } + if (offset < 0) { + return promise.set_error(Status::Error(400, "Parameter offset must be non-negative")); + } + if (count < 0) { + return promise.set_error(Status::Error(400, "Parameter count must be non-negative")); + } + + auto file_view = FileView(node); + + if (count == 0) { + count = file_view.downloaded_prefix(offset); + if (count == 0) { + return promise.set_value(td_api::make_object<td_api::filePart>()); + } + } else if (file_view.downloaded_prefix(offset) < count) { + // TODO this check is safer to do in another thread + return promise.set_error(Status::Error(400, "There is not enough downloaded bytes in the file to read")); + } + if (count >= static_cast<int64>(std::numeric_limits<size_t>::max() / 2 - 1)) { + return promise.set_error(Status::Error(400, "Part length is too big")); + } + + const string *path = nullptr; + bool is_partial = false; + if (file_view.has_local_location()) { + path = &file_view.local_location().path_; + if (!begins_with(*path, get_files_dir(file_view.get_type()))) { + return promise.set_error(Status::Error(400, "File is not inside the cache")); + } + } else { + CHECK(node->local_.type() == LocalFileLocation::Type::Partial); + path = &node->local_.partial().path_; + is_partial = true; + } + + auto read_file_part_promise = + PromiseCreator::lambda([actor_id = actor_id(this), file_id, offset, count, left_tries, is_partial, + promise = std::move(promise)](Result<string> r_bytes) mutable { + if (r_bytes.is_error()) { + LOG(INFO) << "Failed to read file bytes: " << r_bytes.error(); + if (left_tries == 1 || !is_partial) { + return promise.set_error(Status::Error(400, "Failed to read the file")); + } + + // the temporary file could be moved from temp to persistent directory + // we need to wait for the corresponding update and repeat the reading + create_actor<SleepActor>("RepeatReadFilePartActor", 0.01, + PromiseCreator::lambda([actor_id, file_id, offset, count, left_tries, + promise = std::move(promise)](Result<Unit> result) mutable { + send_closure(actor_id, &FileManager::read_file_part, file_id, offset, count, + left_tries - 1, std::move(promise)); + })) + .release(); + } else { + auto result = td_api::make_object<td_api::filePart>(); + result->data_ = r_bytes.move_as_ok(); + promise.set_value(std::move(result)); + } + }); + send_closure(file_load_manager_, &FileLoadManager::read_file_part, *path, offset, count, + std::move(read_file_part_promise)); } void FileManager::delete_file(FileId file_id, Promise<Unit> promise, const char *source) { @@ -1361,169 +2219,496 @@ void FileManager::delete_file(FileId file_id, Promise<Unit> promise, const char auto file_view = FileView(node); - // TODO: review delete condition + send_closure(G()->download_manager(), &DownloadManager::remove_file_if_finished, file_view.get_main_file_id()); + string path; 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); + if (context_->need_notify_on_new_files()) { + context_->on_new_file(-file_view.size(), -file_view.get_allocated_local_size(), -1); + } + path = std::move(node->local_.full().path_); } } 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); + path = std::move(node->local_.partial().path_); } } - promise.set_value(Unit()); + if (path.empty()) { + return promise.set_value(Unit()); + } + + LOG(INFO) << "Unlink file " << file_id << " at " << path; + node->drop_local_location(); + try_flush_node(node, "delete_file"); + send_closure(file_load_manager_, &FileLoadManager::unlink_file, path, std::move(promise)); } -void FileManager::download(FileId file_id, std::shared_ptr<DownloadCallback> callback, int32 new_priority) { +void FileManager::download(FileId file_id, std::shared_ptr<DownloadCallback> callback, int32 new_priority, int64 offset, + int64 limit, Promise<td_api::object_ptr<td_api::file>> promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + auto node = get_sync_file_node(file_id); if (!node) { + LOG(INFO) << "File " << file_id << " not found"; + auto error = Status::Error(400, "File not found"); if (callback) { - callback->on_download_error(file_id, Status::Error("File not found")); + callback->on_download_error(file_id, error.clone()); } - return; + return promise.set_error(std::move(error)); + } + + if ((callback == nullptr && new_priority <= 0) || node->local_.type() == LocalFileLocation::Type::Empty) { + // skip local location check if download is canceled or there is no local location + return download_impl(file_id, std::move(callback), new_priority, offset, limit, Status::OK(), std::move(promise)); } + LOG(INFO) << "Asynchronously check location of file " << file_id << " before downloading"; + auto check_promise = + PromiseCreator::lambda([actor_id = actor_id(this), file_id, callback = std::move(callback), new_priority, offset, + limit, promise = std::move(promise)](Result<Unit> result) mutable { + Status check_status; + if (result.is_error()) { + check_status = result.move_as_error(); + } + send_closure(actor_id, &FileManager::download_impl, file_id, std::move(callback), new_priority, offset, limit, + std::move(check_status), std::move(promise)); + }); + check_local_location_async(node, true, std::move(check_promise)); +} + +void FileManager::download_impl(FileId file_id, std::shared_ptr<DownloadCallback> callback, int32 new_priority, + int64 offset, int64 limit, Status check_status, + Promise<td_api::object_ptr<td_api::file>> promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + LOG(INFO) << "Download file " << file_id << " with priority " << new_priority; + auto node = get_file_node(file_id); + CHECK(node); + + if (check_status.is_error()) { + LOG(WARNING) << "Need to redownload file " << file_id << ": " << check_status; + } 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(); + LOG(INFO) << "File " << file_id << " is already downloaded"; + if (callback) { + callback->on_download_ok(file_id); } + return promise.set_value(get_file_object(file_id, false)); } FileView file_view(node); if (!file_view.can_download_from_server() && !file_view.can_generate()) { + LOG(INFO) << "File " << file_id << " can't be downloaded"; + auto error = Status::Error(400, "Can't download or generate the file"); if (callback) { - callback->on_download_error(file_id, Status::Error("Can't download or generate file")); + callback->on_download_error(file_id, error.clone()); } - return; + return promise.set_error(std::move(error)); } if (new_priority == -1) { if (node->is_download_started_) { - return; + LOG(INFO) << "File " << file_id << " is being downloaded"; + return promise.set_value(get_file_object(file_id, false)); } new_priority = 0; } + LOG(INFO) << "Change download priority of file " << file_id << " to " << new_priority << " with callback " + << callback.get(); + node->set_download_offset(offset); + node->set_download_limit(limit); auto *file_info = get_file_id_info(file_id); CHECK(new_priority == 0 || callback); + if (file_info->download_callback_ != nullptr && file_info->download_callback_.get() != callback.get()) { + // the old callback will be destroyed soon and lost forever + // this is a bug and must never happen, unless we cancel previous download query + // but still there is no way to prevent this with the current FileManager implementation + if (new_priority == 0) { + file_info->download_callback_->on_download_error(file_id, Status::Error(200, "Canceled")); + } else { + LOG(ERROR) << "File " << file_id << " is used with different download callbacks"; + file_info->download_callback_->on_download_error(file_id, Status::Error(500, "Internal Server Error")); + } + } + file_info->ignore_download_limit = limit == IGNORE_DOWNLOAD_LIMIT; file_info->download_priority_ = narrow_cast<int8>(new_priority); file_info->download_callback_ = std::move(callback); + + if (file_info->download_callback_) { + file_info->download_callback_->on_progress(file_id); + } // TODO: send current progress? run_generate(node); - run_download(node); + run_download(node, true); - try_flush_node(node); + try_flush_node(node, "download"); + promise.set_value(get_file_object(file_id, false)); } -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; - } +void FileManager::run_download(FileNodePtr node, bool force_update_priority) { int8 priority = 0; + bool ignore_download_limit = false; for (auto id : node->file_ids_) { auto *info = get_file_id_info(id); if (info->download_priority_ > priority) { priority = info->download_priority_; } + ignore_download_limit |= info->ignore_download_limit; } auto old_priority = node->download_priority_; - node->set_download_priority(priority); if (priority == 0) { + node->set_download_priority(priority); if (old_priority != 0) { - cancel_download(node); + LOG(INFO) << "Cancel downloading of file " << node->main_file_id_; + do_cancel_download(node); } return; } + if (node->need_load_from_pmc_) { + LOG(INFO) << "Skip run_download, because file " << node->main_file_id_ << " needs to be loaded from PMC"; + return; + } + if (node->generate_id_) { + LOG(INFO) << "Skip run_download, because file " << node->main_file_id_ << " is being generated"; + return; + } + auto file_view = FileView(node); + if (!file_view.can_download_from_server()) { + LOG(INFO) << "Skip run_download, because file " << node->main_file_id_ << " can't be downloaded from server"; + return; + } + node->set_download_priority(priority); + node->set_ignore_download_limit(ignore_download_limit); + bool need_update_offset = node->is_download_offset_dirty_; + node->is_download_offset_dirty_ = false; + + bool need_update_limit = node->is_download_limit_dirty_; + node->is_download_limit_dirty_ = false; + if (old_priority != 0) { + LOG(INFO) << "Update download offset and limits of file " << node->main_file_id_; CHECK(node->download_id_ != 0); - send_closure(file_load_manager_, &FileLoadManager::update_priority, node->download_id_, priority); + if (force_update_priority || priority != old_priority) { + send_closure(file_load_manager_, &FileLoadManager::update_priority, node->download_id_, priority); + } + if (need_update_limit || need_update_offset) { + auto download_offset = node->download_offset_; + auto download_limit = node->get_download_limit(); + if (file_view.is_encrypted_any()) { + CHECK(download_offset <= MAX_FILE_SIZE); + CHECK(download_limit <= MAX_FILE_SIZE); + download_limit += download_offset; + download_offset = 0; + } + send_closure(file_load_manager_, &FileLoadManager::update_downloaded_part, node->download_id_, download_offset, + download_limit); + } 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}); + auto file_id = node->main_file_id_; + + if (node->need_reload_photo_ && file_view.may_reload_photo()) { + LOG(INFO) << "Reload photo from file " << node->main_file_id_; + QueryId id = queries_container_.create(Query{file_id, Query::Type::DownloadReloadDialog}); + node->download_id_ = id; + context_->reload_photo(file_view.remote_location().get_source(), + PromiseCreator::lambda([id, actor_id = actor_id(this), file_id](Result<Unit> res) { + Status error; + if (res.is_ok()) { + error = Status::Error("FILE_DOWNLOAD_ID_INVALID"); + } else { + error = res.move_as_error(); + } + VLOG(file_references) + << "Got result from reload photo for file " << file_id << ": " << error; + send_closure(actor_id, &FileManager::on_error, id, std::move(error)); + })); + node->need_reload_photo_ = false; + return; + } + + // If file reference is needed + if (!file_view.has_active_download_remote_location()) { + VLOG(file_references) << "Do not have valid file_reference for file " << file_id; + QueryId id = queries_container_.create(Query{file_id, Query::Type::DownloadWaitFileReference}); + node->download_id_ = id; + if (node->download_was_update_file_reference_) { + on_error(id, Status::Error("Can't download file: have no valid file reference")); + return; + } + node->download_was_update_file_reference_ = true; + + context_->repair_file_reference( + file_id, PromiseCreator::lambda([id, actor_id = actor_id(this), file_id](Result<Unit> res) { + Status error; + if (res.is_ok()) { + error = Status::Error("FILE_DOWNLOAD_RESTART_WITH_FILE_REFERENCE"); + } else { + error = res.move_as_error(); + } + VLOG(file_references) << "Got result from FileSourceManager for file " << file_id << ": " << error; + send_closure(actor_id, &FileManager::on_error, id, std::move(error)); + })); + return; + } + + QueryId id = queries_container_.create(Query{file_id, Query::Type::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); -} + LOG(INFO) << "Run download of file " << file_id << " of size " << node->size_ << " from " + << node->remote_.full.value() << " with suggested name " << node->suggested_path() << " and encyption key " + << node->encryption_key_; + auto download_offset = node->download_offset_; + auto download_limit = node->get_download_limit(); + if (file_view.is_encrypted_any()) { + CHECK(download_offset <= MAX_FILE_SIZE); + CHECK(download_limit <= MAX_FILE_SIZE); + download_limit += download_offset; + download_offset = 0; + } + send_closure(file_load_manager_, &FileLoadManager::download, id, node->remote_.full.value(), node->local_, + node->size_, node->suggested_path(), node->encryption_key_, node->can_search_locally_, download_offset, + download_limit, priority); +} + +class FileManager::ForceUploadActor final : public Actor { + public: + ForceUploadActor(FileManager *file_manager, FileId file_id, std::shared_ptr<FileManager::UploadCallback> callback, + int32 new_priority, uint64 upload_order, bool prefer_small, ActorShared<> parent) + : file_manager_(file_manager) + , file_id_(file_id) + , callback_(std::move(callback)) + , new_priority_(new_priority) + , upload_order_(upload_order) + , prefer_small_(prefer_small) + , parent_(std::move(parent)) { + } + + private: + FileManager *file_manager_; + FileId file_id_; + std::shared_ptr<FileManager::UploadCallback> callback_; + int32 new_priority_; + uint64 upload_order_; + bool prefer_small_; + ActorShared<> parent_; + bool is_active_{false}; + int attempt_{0}; + + class UploadCallback final : public FileManager::UploadCallback { + public: + explicit UploadCallback(ActorId<ForceUploadActor> callback) : callback_(std::move(callback)) { + } + void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final { + send_closure(std::move(callback_), &ForceUploadActor::on_upload_ok, std::move(input_file)); + } -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; + void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final { + send_closure(std::move(callback_), &ForceUploadActor::on_upload_encrypted_ok, std::move(input_file)); + } + + void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final { + send_closure(std::move(callback_), &ForceUploadActor::on_upload_secure_ok, std::move(input_file)); + } + void on_upload_error(FileId file_id, Status error) final { + send_closure(std::move(callback_), &ForceUploadActor::on_upload_error, std::move(error)); + } + ~UploadCallback() final { + if (callback_.empty()) { + return; + } + send_closure(std::move(callback_), &ForceUploadActor::on_upload_error, Status::Error(200, "Canceled")); + } + + private: + ActorId<ForceUploadActor> callback_; + }; + + void on_upload_ok(tl_object_ptr<telegram_api::InputFile> input_file) { + is_active_ = false; + if (input_file || is_ready()) { + callback_->on_upload_ok(file_id_, std::move(input_file)); + on_ok(); + } else { + loop(); + } + } + + void on_upload_encrypted_ok(tl_object_ptr<telegram_api::InputEncryptedFile> input_file) { + is_active_ = false; + if (input_file || is_ready()) { + callback_->on_upload_encrypted_ok(file_id_, std::move(input_file)); + on_ok(); + } else { + loop(); + } + } + + void on_upload_secure_ok(tl_object_ptr<telegram_api::InputSecureFile> input_file) { + is_active_ = false; + if (input_file || is_ready()) { + callback_->on_upload_secure_ok(file_id_, std::move(input_file)); + on_ok(); + } else { + loop(); + } + } + + bool is_ready() const { + return !G()->close_flag() && file_manager_->get_file_view(file_id_).has_active_upload_remote_location(); + } + + void on_ok() { + callback_.reset(); + send_closure(G()->file_manager(), &FileManager::on_force_reupload_success, file_id_); + stop(); + } + + void on_upload_error(Status error) { + if (attempt_ == 2) { + callback_->on_upload_error(file_id_, std::move(error)); + callback_.reset(); + stop(); + } else { + is_active_ = false; + loop(); + } + } + + auto create_callback() { + return std::make_shared<UploadCallback>(actor_id(this)); + } + + void loop() final { + if (is_active_) { + return; + } + if (G()->close_flag()) { + return stop(); + } + + is_active_ = true; + attempt_++; + send_closure(G()->file_manager(), &FileManager::resume_upload, file_id_, vector<int>(), create_callback(), + new_priority_, upload_order_, attempt_ == 2, prefer_small_); + } + + void tear_down() final { + if (callback_) { + callback_->on_upload_error(file_id_, Status::Error(200, "Canceled")); + } + } +}; + +void FileManager::on_force_reupload_success(FileId file_id) { + auto node = get_sync_file_node(file_id); + CHECK(node); + if (!node->remote_.is_full_alive) { // do not update for multiple simultaneous uploads + node->last_successful_force_reupload_time_ = Time::now(); + } +} + +void FileManager::resume_upload(FileId file_id, vector<int> bad_parts, std::shared_ptr<UploadCallback> callback, + int32 new_priority, uint64 upload_order, bool force, bool prefer_small) { auto node = get_sync_file_node(file_id); if (!node) { + LOG(INFO) << "File " << file_id << " not found"; if (callback) { - callback->on_upload_error(file_id, Status::Error("Wrong file id to upload")); + callback->on_upload_error(file_id, Status::Error(400, "File not found")); } return; } + + if (bad_parts.size() == 1 && bad_parts[0] == -1) { + if (node->last_successful_force_reupload_time_ >= Time::now() - 60) { + LOG(INFO) << "Recently reuploaded file " << file_id << ", do not try again"; + if (callback) { + callback->on_upload_error(file_id, Status::Error(400, "Failed to reupload file")); + } + return; + } + + create_actor<ForceUploadActor>("ForceUploadActor", this, file_id, std::move(callback), new_priority, upload_order, + prefer_small, context_->create_reference()) + .release(); + return; + } + LOG(INFO) << "Resume upload of file " << file_id << " with priority " << new_priority << " and force = " << force; + + if (force) { + node->remote_.is_full_alive = false; + } + if (prefer_small) { + node->upload_prefer_small_ = true; + } if (node->upload_pause_ == file_id) { - node->upload_pause_ = FileId(); + node->set_upload_pause(FileId()); } + SCOPE_EXIT { + try_flush_node(node, "resume_upload"); + }; FileView file_view(node); - if (file_view.has_remote_location() && file_view.get_type() != FileType::Thumbnail && - file_view.get_type() != FileType::EncryptedThumbnail) { + if (file_view.has_active_upload_remote_location() && can_reuse_remote_file(file_view.get_type())) { + LOG(INFO) << "File " << file_id << " is already uploaded"; if (callback) { callback->on_upload_ok(file_id, nullptr); } return; } - if (file_view.has_local_location()) { - auto status = check_local_location(node); + if (file_view.has_local_location() && new_priority != 0) { + auto status = check_local_location(node, false); 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 (!file_view.has_local_location() && !file_view.has_generate_location() && !file_view.has_alive_remote_location()) { + LOG(INFO) << "File " << file_id << " can't be uploaded"; if (callback) { - callback->on_upload_error(file_id, Status::Error("Need full local (or generate) location for upload")); + callback->on_upload_error( + file_id, Status::Error(400, "Need full local (or generate, or inactive remote) location for upload")); + } + return; + } + if (file_view.get_type() == FileType::Thumbnail && + (!file_view.has_local_location() && file_view.can_download_from_server())) { + // TODO + if (callback) { + callback->on_upload_error(file_id, Status::Error(400, "Failed to upload thumbnail without local location")); } return; } + LOG(INFO) << "Change upload priority of file " << file_id << " to " << new_priority << " with callback " + << callback.get(); auto *file_info = get_file_id_info(file_id); CHECK(new_priority == 0 || callback); + if (file_info->upload_callback_ != nullptr && file_info->upload_callback_.get() != callback.get()) { + // the old callback will be destroyed soon and lost forever + // this is a bug and must never happen, unless we cancel previous upload query + // but still there is no way to prevent this with the current FileManager implementation + if (new_priority == 0) { + file_info->upload_callback_->on_upload_error(file_id, Status::Error(200, "Canceled")); + } else { + LOG(ERROR) << "File " << file_id << " is used with different upload callbacks"; + file_info->upload_callback_->on_upload_error(file_id, Status::Error(500, "Internal Server Error")); + } + } file_info->upload_order_ = upload_order; file_info->upload_priority_ = narrow_cast<int8>(new_priority); file_info->upload_callback_ = std::move(callback); @@ -1531,24 +2716,26 @@ void FileManager::resume_upload(FileId file_id, std::vector<int> bad_parts, std: 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; + LOG(INFO) << "Wrong file identifier " << file_id; return false; } if (node->upload_pause_ == file_id) { - node->upload_pause_ = FileId(); + node->set_upload_pause(FileId()); } - if (node->remote_.type() == RemoteFileLocation::Type::Full) { + SCOPE_EXIT { + try_flush_node(node, "delete_partial_remote_location"); + }; + if (node->remote_.is_full_alive) { LOG(INFO) << "File " << file_id << " is already uploaded"; return true; } - node->set_remote_location(RemoteFileLocation(), FileLocationSource::None, 0); + node->delete_partial_remote_location(); auto *file_info = get_file_id_info(file_id); file_info->upload_priority_ = 0; @@ -1557,22 +2744,49 @@ bool FileManager::delete_partial_remote_location(FileId file_id) { return false; } - auto status = check_local_location(node); + auto status = check_local_location(node, false); 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); + run_upload(node, vector<int>()); return true; } -void FileManager::external_file_generate_progress(int64 id, int32 expected_size, int32 local_prefix_size, +void FileManager::delete_file_reference(FileId file_id, Slice file_reference) { + VLOG(file_references) << "Delete file reference of file " << file_id << " " + << tag("reference_base64", base64_encode(file_reference)); + auto node = get_sync_file_node(file_id); + if (!node) { + LOG(ERROR) << "Wrong file identifier " << file_id; + return; + } + node->delete_file_reference(file_reference); + auto remote = get_remote(file_id.get_remote()); + if (remote != nullptr) { + VLOG(file_references) << "Do delete file reference of remote file " << file_id; + if (remote->delete_file_reference(file_reference)) { + VLOG(file_references) << "Successfully deleted file reference of remote file " << file_id; + node->upload_was_update_file_reference_ = false; + node->download_was_update_file_reference_ = false; + node->on_pmc_changed(); + } + } + try_flush_node_pmc(node, "delete_file_reference"); +} + +void FileManager::external_file_generate_write_part(int64 id, int64 offset, string data, Promise<> promise) { + send_closure(file_generate_manager_, &FileGenerateManager::external_file_generate_write_part, id, offset, + std::move(data), std::move(promise)); +} + +void FileManager::external_file_generate_progress(int64 id, int64 expected_size, int64 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)); @@ -1580,10 +2794,20 @@ void FileManager::external_file_generate_finish(int64 id, Status status, Promise void FileManager::run_generate(FileNodePtr node) { if (node->need_load_from_pmc_) { + LOG(INFO) << "Skip run_generate, because file " << node->main_file_id_ << " needs to be loaded from PMC"; return; } FileView file_view(node); - if (file_view.has_local_location() || file_view.can_download_from_server() || !file_view.can_generate()) { + if (!file_view.can_generate()) { + // LOG(INFO) << "Skip run_generate, because file " << node->main_file_id_ << " can't be generated"; + return; + } + if (file_view.has_local_location()) { + LOG(INFO) << "Skip run_generate, because file " << node->main_file_id_ << " has local location"; + return; + } + if (file_view.can_download_from_server()) { + LOG(INFO) << "Skip run_generate, because file " << node->main_file_id_ << " can be downloaded from server"; return; } @@ -1612,7 +2836,7 @@ void FileManager::run_generate(FileNodePtr node) { if (node->generate_priority_ == 0) { if (old_priority != 0) { LOG(INFO) << "Cancel file " << file_id << " generation"; - cancel_generate(node); + do_cancel_generate(node); } return; } @@ -1622,47 +2846,35 @@ void FileManager::run_generate(FileNodePtr node) { return; } - QueryId id = queries_container_.create(Query{file_id, Query::Generate}); + QueryId id = queries_container_.create(Query{file_id, Query::Type::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 { + node->suggested_path(), [file_manager = this, id] { + class Callback final : 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_partial_generate(PartialLocalFileLocation partial_local, int64 expected_size) final { + send_closure(actor_, &FileManager::on_partial_generate, query_id_, std::move(partial_local), + expected_size); } - void on_ok(const FullLocalFileLocation &local) override { - send_closure(actor_, &FileManager::on_generate_ok, query_id_, local); + void on_ok(FullLocalFileLocation local) final { + send_closure(actor_, &FileManager::on_generate_ok, query_id_, std::move(local)); } - void on_error(Status error) override { + void on_error(Status error) final { send_closure(actor_, &FileManager::on_error, query_id_, std::move(error)); } }; - return std::make_unique<Callback>(file_manager->actor_id(file_manager), id); + return 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; - } - } +void FileManager::run_upload(FileNodePtr node, vector<int> bad_parts) { int8 priority = 0; FileId file_id = node->main_file_id_; for (auto id : node->file_ids_) { @@ -1674,16 +2886,41 @@ void FileManager::run_upload(FileNodePtr node, std::vector<int> bad_parts) { } auto old_priority = node->upload_priority_; - node->set_upload_priority(priority); if (priority == 0) { + node->set_upload_priority(priority); if (old_priority != 0) { LOG(INFO) << "Cancel file " << file_id << " uploading"; - cancel_upload(node); + do_cancel_upload(node); } return; } + if (node->need_load_from_pmc_) { + LOG(INFO) << "File " << node->main_file_id_ << " needs to be loaded from database before upload"; + return; + } + if (node->upload_pause_.is_valid()) { + LOG(INFO) << "File " << node->main_file_id_ << " upload is paused: " << node->upload_pause_; + return; + } + + FileView file_view(node); + if (!file_view.has_local_location() && !file_view.has_remote_location()) { + if (node->get_by_hash_ || node->generate_id_ == 0 || !node->generate_was_update_) { + LOG(INFO) << "Have no local location for file: get_by_hash = " << node->get_by_hash_ + << ", generate_id = " << node->generate_id_ << ", generate_was_update = " << node->generate_was_update_; + return; + } + if (file_view.has_generate_location() && file_view.generate_location().file_type_ == FileType::SecureEncrypted) { + // Can't upload secure file before its size is known + LOG(INFO) << "Can't upload secure file " << node->main_file_id_ << " before it's size is known"; + return; + } + } + + node->set_upload_priority(priority); + // 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)) && @@ -1693,6 +2930,14 @@ void FileManager::run_upload(FileNodePtr node, std::vector<int> bad_parts) { LOG_IF(FATAL, !success) << "Failed to set encryption key for file " << file_id; } + // create encryption key if necessary + if (file_view.has_local_location() && file_view.local_location().file_type_ == FileType::SecureEncrypted && + file_view.encryption_key().empty()) { + CHECK(!node->file_ids_.empty()); + bool success = set_encryption_key(node->file_ids_[0], FileEncryptionKey::create_secure_key()); + 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); @@ -1701,8 +2946,26 @@ void FileManager::run_upload(FileNodePtr node, std::vector<int> bad_parts) { } 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}); + if (file_view.has_alive_remote_location() && !file_view.has_active_upload_remote_location() && + can_reuse_remote_file(file_view.get_type())) { + QueryId id = queries_container_.create(Query{file_id, Query::Type::UploadWaitFileReference}); + node->upload_id_ = id; + if (node->upload_was_update_file_reference_) { + on_error(id, Status::Error("Can't upload file: have no valid file reference")); + return; + } + node->upload_was_update_file_reference_ = true; + + context_->repair_file_reference( + node->main_file_id_, PromiseCreator::lambda([id, actor_id = actor_id(this)](Result<Unit> res) { + send_closure(actor_id, &FileManager::on_error, id, Status::Error("FILE_UPLOAD_RESTART_WITH_FILE_REFERENCE")); + })); + return; + } + + if (!node->remote_.partial && node->get_by_hash_) { + LOG(INFO) << "Get file " << node->main_file_id_ << " by hash"; + QueryId id = queries_container_.create(Query{file_id, Query::Type::UploadByHash}); node->upload_id_ = id; send_closure(file_load_manager_, &FileLoadManager::upload_by_hash, id, node->local_.full(), node->size_, @@ -1710,80 +2973,42 @@ void FileManager::run_upload(FileNodePtr node, std::vector<int> bad_parts) { return; } - QueryId id = queries_container_.create(Query{file_id, Query::Upload}); + auto new_priority = narrow_cast<int8>(bad_parts.empty() ? -priority : priority); + td::remove_if(bad_parts, [](auto part_id) { return part_id < 0; }); + + auto expected_size = file_view.expected_size(true); + if (node->upload_prefer_small_ && (10 << 20) < expected_size && expected_size < (30 << 20)) { + expected_size = 10 << 20; + } + + QueryId id = queries_container_.create(Query{file_id, Query::Type::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)); + send_closure(file_load_manager_, &FileLoadManager::upload, id, node->local_, node->remote_.partial_or_empty(), + expected_size, node->encryption_key_, new_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; + return resume_upload(file_id, vector<int>(), std::move(callback), new_priority, upload_order); } -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); +void FileManager::cancel_upload(FileId file_id) { + return resume_upload(file_id, vector<int>(), nullptr, 0, 0); } -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()); +static bool is_background_type(FileType type) { + return type == FileType::Wallpaper || type == FileType::Background; } 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(); + auto r_http_url = parse_url(persistent_id); + if (r_http_url.is_error()) { + return Status::Error(400, PSLICE() << "Invalid file HTTP URL specified: " << r_http_url.error().message()); + } + auto url = r_http_url.ok().get_url(); if (!clean_input_string(url)) { return Status::Error(400, "URL must be in UTF-8"); } @@ -1792,31 +3017,88 @@ Result<FileId> FileManager::from_persistent_id(CSlice persistent_id, FileType fi 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()); + return Status::Error(400, PSLICE() << "Wrong remote file identifier specified: " << r_binary.error().message()); } auto binary = r_binary.move_as_ok(); if (binary.empty()) { - return Status::Error(10, "Remote file id can't be empty"); + return Status::Error(400, "Remote file identifier must be non-empty"); + } + if (binary.back() == FileNode::PERSISTENT_ID_VERSION_OLD) { + return from_persistent_id_v2(binary, file_type); + } + if (binary.back() == FileNode::PERSISTENT_ID_VERSION) { + return from_persistent_id_v3(binary, file_type); + } + if (binary.back() == FileNode::PERSISTENT_ID_VERSION_GENERATED) { + return from_persistent_id_generated(binary, file_type); + } + return Status::Error(400, "Wrong remote file identifier specified: can't unserialize it. Wrong last symbol"); +} + +Result<FileId> FileManager::from_persistent_id_generated(Slice binary, FileType file_type) { + binary.remove_suffix(1); + auto decoded_binary = zero_decode(binary); + FullGenerateFileLocation generate_location; + auto status = unserialize(generate_location, decoded_binary); + if (status.is_error()) { + return Status::Error(400, "Wrong remote file identifier specified: can't unserialize it"); } - if (binary.back() != PERSISTENT_ID_VERSION) { - return Status::Error(10, "Wrong remote file id specified: can't unserialize it. Wrong last symbol"); + auto real_file_type = generate_location.file_type_; + if ((real_file_type != file_type && file_type != FileType::Temp) || + (real_file_type != FileType::Thumbnail && real_file_type != FileType::EncryptedThumbnail)) { + return Status::Error(400, PSLICE() << "Can't use file of type " << real_file_type << " as " << file_type); } - binary.pop_back(); - binary = zero_decode(binary); + if (!is_remotely_generated_file(generate_location.conversion_)) { + return Status::Error(400, "Unexpected conversion type"); + } + FileData data; + data.generate_ = make_unique<FullGenerateFileLocation>(std::move(generate_location)); + return register_file(std::move(data), FileLocationSource::FromUser, FileId(), "from_persistent_id_generated", false) + .move_as_ok(); +} + +Result<FileId> FileManager::from_persistent_id_v23(Slice binary, FileType file_type, int32 version) { + if (version < 0 || version >= static_cast<int32>(Version::Next)) { + return Status::Error(400, "Invalid remote file identifier"); + } + auto decoded_binary = zero_decode(binary); FullRemoteFileLocation remote_location; - auto status = unserialize(remote_location, binary); + log_event::WithVersion<TlParser> parser(decoded_binary); + parser.set_version(version); + parse(remote_location, parser); + parser.fetch_end(); + auto status = parser.get_status(); if (status.is_error()) { - return Status::Error(10, "Wrong remote file id specified: can't unserialize it"); + return Status::Error(400, "Wrong remote file identifier 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)) { + if (is_document_file_type(real_file_type) && is_document_file_type(file_type)) { real_file_type = file_type; + } else if (is_background_type(real_file_type) && is_background_type(file_type)) { + // type of file matches, but real type is in the stored remote location } else if (real_file_type != file_type && file_type != FileType::Temp) { - return Status::Error(10, "Type of file mismatch"); + return Status::Error(400, PSLICE() << "Can't use file of type " << real_file_type << " as " << file_type); } FileData data; data.remote_ = RemoteFileLocation(std::move(remote_location)); - return register_file(std::move(data), FileLocationSource::FromUser, "from_persistent_id", false).move_as_ok(); + auto file_id = register_file(std::move(data), FileLocationSource::FromUser, FileId(), "from_persistent_id_v23", false) + .move_as_ok(); + return file_id; +} + +Result<FileId> FileManager::from_persistent_id_v2(Slice binary, FileType file_type) { + binary.remove_suffix(1); + return from_persistent_id_v23(binary, file_type, 0); +} + +Result<FileId> FileManager::from_persistent_id_v3(Slice binary, FileType file_type) { + binary.remove_suffix(1); + if (binary.empty()) { + return Status::Error(400, "Invalid remote file identifier"); + } + int32 version = static_cast<uint8>(binary.back()); + binary.remove_suffix(1); + return from_persistent_id_v23(binary, file_type, version); } FileView FileManager::get_file_view(FileId file_id) const { @@ -1826,6 +3108,7 @@ FileView FileManager::get_file_view(FileId file_id) const { } 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) { @@ -1834,7 +3117,7 @@ FileView FileManager::get_sync_file_view(FileId file_id) { return FileView(file_node); } -tl_object_ptr<td_api::file> FileManager::get_file_object(FileId file_id, bool with_main_file_id) { +td_api::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()) { @@ -1842,18 +3125,15 @@ tl_object_ptr<td_api::file> FileManager::get_file_object(FileId file_id, bool wi 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 persistent_file_id = file_view.get_persistent_file_id(); + string unique_file_id = file_view.get_unique_file_id(); + bool is_uploading_completed = !persistent_file_id.empty(); + auto size = file_view.size(); + auto expected_size = file_view.expected_size(); + auto download_offset = file_view.download_offset(); + auto local_prefix_size = file_view.local_prefix_size(); + auto local_total_size = file_view.local_total_size(); + auto remote_size = 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(); @@ -1862,56 +3142,93 @@ tl_object_ptr<td_api::file> FileManager::get_file_object(FileId file_id, bool wi 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(); + result_file_id = file_view.get_main_file_id(); } - file_info = get_file_id_info(file_view.file_id()); + file_info = get_file_id_info(file_view.get_main_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); + << (with_main_file_id ? file_view.get_main_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)); + file_view.is_downloading(), file_view.has_local_location(), + download_offset, local_prefix_size, local_total_size), + td_api::make_object<td_api::remoteFile>(std::move(persistent_file_id), std::move(unique_file_id), + file_view.is_uploading(), is_uploading_completed, remote_size)); +} + +vector<int32> FileManager::get_file_ids_object(const vector<FileId> &file_ids, bool with_main_file_id) { + return transform(file_ids, [this, with_main_file_id](FileId file_id) { + auto file_view = get_sync_file_view(file_id); + auto result_file_id = file_id; + auto *file_info = get_file_id_info(result_file_id); + if (with_main_file_id) { + if (!file_info->sent_file_id_flag_ && !file_info->send_updates_flag_) { + result_file_id = file_view.get_main_file_id(); + } + file_info = get_file_id_info(file_view.get_main_file_id()); + } + file_info->sent_file_id_flag_ = true; + + return result_file_id.get(); + }); } Result<FileId> FileManager::check_input_file_id(FileType type, Result<FileId> result, bool is_encrypted, - bool allow_zero) { + bool allow_zero, bool is_secure) { TRY_RESULT(file_id, std::move(result)); if (allow_zero && !file_id.is_valid()) { return FileId(); } - auto file_node = get_file_node(file_id); + auto file_node = get_sync_file_node(file_id); // we need full data about sent files if (!file_node) { - return Status::Error(6, "File not found"); + return Status::Error(400, "File not found"); } auto file_view = FileView(file_node); FileType real_type = file_view.get_type(); - if (!is_encrypted) { + LOG(INFO) << "Checking file " << file_id << " of type " << type << "/" << real_type; + if (!is_encrypted && !is_secure) { if (real_type != type && !(real_type == FileType::Temp && file_view.has_url()) && - !(is_document_type(real_type) && is_document_type(type))) { + !(is_document_file_type(real_type) && is_document_file_type(type)) && + !(is_background_type(real_type) && is_background_type(type)) && + !(file_view.is_encrypted() && type == FileType::Ringtone)) { // TODO: send encrypted file to unencrypted chat - return Status::Error(6, "Type of file mismatch"); + return Status::Error(400, PSLICE() << "Can't use file of type " << real_type << " as " << type); } } 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); + // There are no reasons to dup file_id, because it will be duped anyway before upload/reupload + // It will not be duped in dup_message_content only if has_input_media(), + // but currently in this case the file never needs to be reuploaded + + if (!is_encrypted) { + // URLs in non-secret chats never needs to be reuploaded, so they don't need to be duped + // non-URLs without remote location will be duped at dup_message_content, because they have no input media + return file_node->main_file_id_; + } + + return dup_file_id(file_id, "check_input_file_id"); } - return file_node->main_file_id_; + + int32 remote_id = file_id.get_remote(); + if (remote_id == 0) { + RemoteInfo info{file_view.remote_location(), FileLocationSource::FromUser, file_id}; + remote_id = remote_location_info_.add(info); + if (remote_location_info_.get(remote_id).file_id_ == file_id) { + get_file_id_info(file_id)->pin_flag_ = true; + } + } + return FileId(file_node->main_file_id_.get(), remote_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"); + return Status::Error(400, "inputThumbnail not specified"); } switch (thumbnail_input_file->get_id()) { @@ -1922,9 +3239,9 @@ Result<FileId> FileManager::get_input_thumbnail_file_id(const tl_object_ptr<td_a owner_dialog_id, 0, false); } case td_api::inputFileId::ID: - return Status::Error(6, "InputFileId is not supported for thumbnails"); + return Status::Error(400, "InputFileId is not supported for thumbnails"); case td_api::inputFileRemote::ID: - return Status::Error(6, "InputFileRemote is not supported for thumbnails"); + return Status::Error(400, "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, @@ -1939,17 +3256,20 @@ Result<FileId> FileManager::get_input_thumbnail_file_id(const tl_object_ptr<td_a 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) { + bool get_by_hash, bool is_secure, bool force_reuse) { + if (file == nullptr) { if (allow_zero) { return FileId(); } - return Status::Error(6, "InputFile not specified"); + return Status::Error(400, "InputFile is not specified"); + } + + if (is_encrypted || is_secure) { + get_by_hash = false; } + auto new_type = is_encrypted ? FileType::Encrypted : (is_secure ? FileType::SecureEncrypted : type); + auto r_file_id = [&]() -> Result<FileId> { switch (file->get_id()) { case td_api::inputFileLocal::ID: { @@ -1957,8 +3277,37 @@ Result<FileId> FileManager::get_input_file_id(FileType type, const tl_object_ptr 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); + string hash; + if (G()->get_option_boolean("reuse_uploaded_photos_by_hash") && new_type == FileType::Photo) { + auto r_stat = stat(path); + if (r_stat.is_ok() && r_stat.ok().size_ > 0 && r_stat.ok().size_ < 11000000) { + auto r_file_content = read_file_str(path, r_stat.ok().size_); + if (r_file_content.is_ok()) { + hash = sha256(r_file_content.ok()); + auto file_id = file_hash_to_file_id_.get(hash); + LOG(INFO) << "Found file " << file_id << " by hash " << hex_encode(hash); + if (file_id.is_valid()) { + auto file_view = get_file_view(file_id); + if (!file_view.empty()) { + if (force_reuse) { + return file_id; + } + if (file_view.has_remote_location() && !file_view.remote_location().is_web()) { + return file_id; + } + if (file_view.is_uploading()) { + hash.clear(); + } + } + } + } + } + } + TRY_RESULT(file_id, register_local(FullLocalFileLocation(new_type, path, 0), owner_dialog_id, 0, get_by_hash)); + if (!hash.empty()) { + file_hash_to_file_id_.set(hash, file_id); + } + return file_id; } case td_api::inputFileId::ID: { FileId file_id(static_cast<const td_api::inputFileId *>(file.get())->id_, 0); @@ -1976,9 +3325,8 @@ Result<FileId> FileManager::get_input_file_id(FileType type, const tl_object_ptr } 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_); + return register_generate(new_type, FileLocationSource::FromUser, generated_file->original_path_, + generated_file->conversion_, owner_dialog_id, generated_file->expected_size_); } default: UNREACHABLE(); @@ -1986,7 +3334,134 @@ Result<FileId> FileManager::get_input_file_id(FileType type, const tl_object_ptr } }(); - return check_input_file_id(type, std::move(r_file_id), is_encrypted, allow_zero); + return check_input_file_id(type, std::move(r_file_id), is_encrypted, allow_zero, is_secure); +} + +Result<FileId> FileManager::get_map_thumbnail_file_id(Location location, int32 zoom, int32 width, int32 height, + int32 scale, DialogId owner_dialog_id) { + if (!location.is_valid_map_point()) { + return Status::Error(400, "Invalid location specified"); + } + if (zoom < 13 || zoom > 20) { + return Status::Error(400, "Wrong zoom"); + } + if (width < 16 || width > 1024) { + return Status::Error(400, "Wrong width"); + } + if (height < 16 || height > 1024) { + return Status::Error(400, "Wrong height"); + } + if (scale < 1 || scale > 3) { + return Status::Error(400, "Wrong scale"); + } + + const double PI = 3.14159265358979323846; + double sin_latitude = std::sin(location.get_latitude() * PI / 180); + int32 size = 256 * (1 << zoom); + auto x = static_cast<int32>((location.get_longitude() + 180) / 360 * size); + auto y = static_cast<int32>((0.5 - std::log((1 + sin_latitude) / (1 - sin_latitude)) / (4 * PI)) * size); + x = clamp(x, 0, size - 1); // just in case + y = clamp(y, 0, size - 1); // just in case + + string conversion = PSTRING() << "#map#" << zoom << '#' << x << '#' << y << '#' << width << '#' << height << '#' + << scale << '#'; + return register_generate( + owner_dialog_id.get_type() == DialogType::SecretChat ? FileType::EncryptedThumbnail : FileType::Thumbnail, + FileLocationSource::FromUser, string(), std::move(conversion), owner_dialog_id, 0); +} + +Result<FileId> FileManager::get_audio_thumbnail_file_id(string title, string performer, bool is_small, + DialogId owner_dialog_id) { + if (!clean_input_string(title)) { + return Status::Error(400, "Title must be encoded in UTF-8"); + } + if (!clean_input_string(performer)) { + return Status::Error(400, "Performer must be encoded in UTF-8"); + } + for (auto &c : title) { + if (c == '\n' || c == '#') { + c = ' '; + } + } + for (auto &c : performer) { + if (c == '\n' || c == '#') { + c = ' '; + } + } + title = trim(title); + performer = trim(performer); + if (title.empty() && performer.empty()) { + return Status::Error(400, "Title or performer must be non-empty"); + } + + string conversion = PSTRING() << "#audio_t#" << title << '#' << performer << '#' << (is_small ? '1' : '0') << '#'; + return register_generate( + owner_dialog_id.get_type() == DialogType::SecretChat ? FileType::EncryptedThumbnail : FileType::Thumbnail, + FileLocationSource::FromUser, string(), std::move(conversion), owner_dialog_id, 0); +} + +FileType FileManager::guess_file_type(const tl_object_ptr<td_api::InputFile> &file) { + if (file == nullptr) { + return FileType::Temp; + } + + auto guess_file_type_by_path = [](const string &file_path) { + PathView path_view(file_path); + auto file_name = path_view.file_name(); + auto extension = path_view.extension(); + if (extension == "jpg" || extension == "jpeg") { + return FileType::Photo; + } + if (extension == "ogg" || extension == "oga" || extension == "opus") { + return FileType::VoiceNote; + } + if (extension == "3gp" || extension == "mov") { + return FileType::Video; + } + if (extension == "mp3" || extension == "mpeg3" || extension == "m4a") { + return FileType::Audio; + } + if (extension == "webp" || extension == "tgs" || extension == "webm") { + return FileType::Sticker; + } + if (extension == "gif") { + return FileType::Animation; + } + if (extension == "mp4" || extension == "mpeg4") { + return to_lower(file_name).find("-gif-") != string::npos ? FileType::Animation : FileType::Video; + } + return FileType::Document; + }; + + switch (file->get_id()) { + case td_api::inputFileLocal::ID: + return guess_file_type_by_path(static_cast<const td_api::inputFileLocal *>(file.get())->path_); + case td_api::inputFileId::ID: { + FileId file_id(static_cast<const td_api::inputFileId *>(file.get())->id_, 0); + auto file_view = get_file_view(file_id); + if (file_view.empty()) { + return FileType::Temp; + } + return file_view.get_type(); + } + case td_api::inputFileRemote::ID: { + const string &file_persistent_id = static_cast<const td_api::inputFileRemote *>(file.get())->id_; + Result<FileId> r_file_id = from_persistent_id(file_persistent_id, FileType::Temp); + if (r_file_id.is_error()) { + return FileType::Temp; + } + auto file_view = get_file_view(r_file_id.ok()); + if (file_view.empty()) { + return FileType::Temp; + } + return file_view.get_type(); + } + case td_api::inputFileGenerated::ID: + return guess_file_type_by_path(static_cast<const td_api::inputFileGenerated *>(file.get())->original_path_); + default: + UNREACHABLE(); + return FileType::Temp; + } } vector<tl_object_ptr<telegram_api::InputDocument>> FileManager::get_input_documents(const vector<FileId> &file_ids) { @@ -2002,30 +3477,97 @@ vector<tl_object_ptr<telegram_api::InputDocument>> FileManager::get_input_docume return result; } +bool FileManager::extract_was_uploaded(const tl_object_ptr<telegram_api::InputMedia> &input_media) { + if (input_media == nullptr) { + return false; + } + + auto input_media_id = input_media->get_id(); + return input_media_id == telegram_api::inputMediaUploadedPhoto::ID || + input_media_id == telegram_api::inputMediaUploadedDocument::ID; +} + +bool FileManager::extract_was_thumbnail_uploaded(const tl_object_ptr<telegram_api::InputMedia> &input_media) { + if (input_media == nullptr || input_media->get_id() != telegram_api::inputMediaUploadedDocument::ID) { + return false; + } + + return static_cast<const telegram_api::inputMediaUploadedDocument *>(input_media.get())->thumb_ != nullptr; +} + +string FileManager::extract_file_reference(const tl_object_ptr<telegram_api::InputMedia> &input_media) { + if (input_media == nullptr) { + return string(); + } + + switch (input_media->get_id()) { + case telegram_api::inputMediaDocument::ID: + return extract_file_reference(static_cast<const telegram_api::inputMediaDocument *>(input_media.get())->id_); + case telegram_api::inputMediaPhoto::ID: + return extract_file_reference(static_cast<const telegram_api::inputMediaPhoto *>(input_media.get())->id_); + default: + return string(); + } +} + +string FileManager::extract_file_reference(const tl_object_ptr<telegram_api::InputDocument> &input_document) { + if (input_document == nullptr || input_document->get_id() != telegram_api::inputDocument::ID) { + return string(); + } + + return static_cast<const telegram_api::inputDocument *>(input_document.get())->file_reference_.as_slice().str(); +} + +string FileManager::extract_file_reference(const tl_object_ptr<telegram_api::InputPhoto> &input_photo) { + if (input_photo == nullptr || input_photo->get_id() != telegram_api::inputPhoto::ID) { + return string(); + } + + return static_cast<const telegram_api::inputPhoto *>(input_photo.get())->file_reference_.as_slice().str(); +} + +bool FileManager::extract_was_uploaded(const tl_object_ptr<telegram_api::InputChatPhoto> &input_chat_photo) { + return input_chat_photo != nullptr && input_chat_photo->get_id() == telegram_api::inputChatUploadedPhoto::ID; +} + +string FileManager::extract_file_reference(const tl_object_ptr<telegram_api::InputChatPhoto> &input_chat_photo) { + if (input_chat_photo == nullptr || input_chat_photo->get_id() != telegram_api::inputChatPhoto::ID) { + return string(); + } + + return extract_file_reference(static_cast<const telegram_api::inputChatPhoto *>(input_chat_photo.get())->id_); +} + 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}; } + CHECK(file_id_info_.size() <= static_cast<size_t>(std::numeric_limits<int32>::max())); 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()); + CHECK(file_nodes_.size() <= static_cast<size_t>(std::numeric_limits<FileNodeId>::max())); + auto res = static_cast<FileNodeId>(file_nodes_.size()); file_nodes_.emplace_back(nullptr); return res; } void FileManager::on_start_download(QueryId query_id) { + if (is_closed_) { + return; + } + auto query = queries_container_.get(query_id); CHECK(query != nullptr); auto file_id = query->file_id_; auto file_node = get_file_node(file_id); + LOG(DEBUG) << "Receive on_start_download for file " << file_id; if (!file_node) { return; } @@ -2037,13 +3579,19 @@ void FileManager::on_start_download(QueryId query_id) { file_node->is_download_started_ = true; } -void FileManager::on_partial_download(QueryId query_id, const PartialLocalFileLocation &partial_local, - int64 ready_size) { +void FileManager::on_partial_download(QueryId query_id, PartialLocalFileLocation partial_local, int64 ready_size, + int64 size) { + if (is_closed_) { + return; + } + auto query = queries_container_.get(query_id); CHECK(query != nullptr); auto file_id = query->file_id_; auto file_node = get_file_node(file_id); + LOG(DEBUG) << "Receive on_partial_download for file " << file_id << " with " << partial_local + << ", ready_size = " << ready_size << " and size = " << size; if (!file_node) { return; } @@ -2051,17 +3599,28 @@ void FileManager::on_partial_download(QueryId query_id, const PartialLocalFileLo return; } - file_node->set_local_location(LocalFileLocation(partial_local), ready_size); - try_flush_node(file_node); + if (size != 0) { + FileView file_view(file_node); + if (!file_view.is_encrypted_secure()) { + file_node->set_size(size); + } + } + file_node->set_local_location(LocalFileLocation(std::move(partial_local)), ready_size, -1, -1 /* TODO */); + try_flush_node(file_node, "on_partial_download"); } -void FileManager::on_partial_upload(QueryId query_id, const PartialRemoteFileLocation &partial_remote, - int64 ready_size) { +void FileManager::on_hash(QueryId query_id, string hash) { + if (is_closed_) { + return; + } + auto query = queries_container_.get(query_id); CHECK(query != nullptr); auto file_id = query->file_id_; + auto file_node = get_file_node(file_id); + LOG(DEBUG) << "Receive on_hash for file " << file_id; if (!file_node) { return; } @@ -2069,22 +3628,66 @@ void FileManager::on_partial_upload(QueryId query_id, const PartialRemoteFileLoc return; } - file_node->set_remote_location(RemoteFileLocation(partial_remote), FileLocationSource::None, ready_size); - try_flush_node(file_node); + file_node->encryption_key_.set_value_hash(secure_storage::ValueHash::create(hash).move_as_ok()); } -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); + +void FileManager::on_partial_upload(QueryId query_id, PartialRemoteFileLocation partial_remote, int64 ready_size) { + if (is_closed_) { + return; + } + + auto query = queries_container_.get(query_id); + CHECK(query != nullptr); + + auto file_id = query->file_id_; + auto file_node = get_file_node(file_id); + LOG(DEBUG) << "Receive on_partial_upload for file " << file_id << " with " << partial_remote << " and ready size " + << ready_size; + if (!file_node) { + LOG(ERROR) << "Can't find being uploaded file " << file_id; + return; + } + if (file_node->upload_id_ != query_id) { + LOG(DEBUG) << "Upload identifier of file " << file_id << " is " << file_node->upload_id_ << " instead of " + << query_id; + return; + } + + file_node->set_partial_remote_location(std::move(partial_remote), ready_size); + try_flush_node(file_node, "on_partial_upload"); +} + +void FileManager::on_download_ok(QueryId query_id, FullLocalFileLocation local, int64 size, bool is_new) { + if (is_closed_) { + return; + } + + Query query; + bool was_active; + std::tie(query, was_active) = finish_query(query_id); + auto file_id = query.file_id_; + LOG(INFO) << "ON DOWNLOAD OK of " << (is_new ? "new" : "checked") << " file " << file_id << " of size " << size; + auto r_new_file_id = register_local(std::move(local), DialogId(), size, false, false, true, file_id); + Status status = Status::OK(); if (r_new_file_id.is_error()) { - LOG(ERROR) << "Can't register local file after download: " << r_new_file_id.error(); + status = Status::Error(PSLICE() << "Can't register local file after download: " << r_new_file_id.error().message()); } else { - context_->on_new_file(get_file_view(r_new_file_id.ok()).size()); - LOG_STATUS(merge(r_new_file_id.ok(), file_id)); + if (is_new && context_->need_notify_on_new_files()) { + context_->on_new_file(size, get_file_view(r_new_file_id.ok()).get_allocated_local_size(), 1); + } + } + if (status.is_error()) { + LOG(ERROR) << status.message(); + return on_error_impl(get_file_node(file_id), query.type_, was_active, std::move(status)); } } -void FileManager::on_upload_ok(QueryId query_id, FileType file_type, const PartialRemoteFileLocation &partial_remote, + +void FileManager::on_upload_ok(QueryId query_id, FileType file_type, PartialRemoteFileLocation partial_remote, int64 size) { + if (is_closed_) { + return; + } + 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; @@ -2108,13 +3711,14 @@ void FileManager::on_upload_ok(QueryId query_id, FileType file_type, const Parti } auto *file_info = get_file_id_info(file_id); + LOG(INFO) << "Found being uploaded file " << file_id << " with priority " << file_info->upload_priority_; 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()); + string file_name = get_file_name(file_type, file_view.suggested_path()); - if (file_view.is_encrypted()) { + if (file_view.is_encrypted_secret()) { tl_object_ptr<telegram_api::InputEncryptedFile> input_file; if (partial_remote.is_big_) { input_file = make_tl_object<telegram_api::inputEncryptedFileBigUploaded>( @@ -2124,8 +3728,18 @@ void FileManager::on_upload_ok(QueryId query_id, FileType file_type, const Parti partial_remote.file_id_, partial_remote.part_count_, "", file_view.encryption_key().calc_fingerprint()); } if (file_info->upload_callback_) { + file_node->set_upload_pause(file_id); 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 if (file_view.is_secure()) { + tl_object_ptr<telegram_api::InputSecureFile> input_file; + input_file = make_tl_object<telegram_api::inputSecureFileUploaded>( + partial_remote.file_id_, partial_remote.part_count_, "" /*md5*/, BufferSlice() /*file_hash*/, + BufferSlice() /*encrypted_secret*/); + if (file_info->upload_callback_) { + file_node->set_upload_pause(file_id); + file_info->upload_callback_->on_upload_secure_ok(file_id, std::move(input_file)); file_info->upload_callback_.reset(); } } else { @@ -2138,37 +3752,48 @@ void FileManager::on_upload_ok(QueryId query_id, FileType file_type, const Parti std::move(file_name), ""); } if (file_info->upload_callback_) { + file_node->set_upload_pause(file_id); file_info->upload_callback_->on_upload_ok(file_id, std::move(input_file)); - file_node->upload_pause_ = file_id; file_info->upload_callback_.reset(); } } + // don't flush node info, because nothing actually changed } -void FileManager::on_upload_full_ok(QueryId query_id, const FullRemoteFileLocation &remote) { - LOG(INFO) << "ON UPLOAD OK"; +// for upload by hash +void FileManager::on_upload_full_ok(QueryId query_id, FullRemoteFileLocation remote) { + if (is_closed_) { + return; + } + auto file_id = finish_query(query_id).first.file_id_; - auto new_file_id = register_remote(remote, FileLocationSource::FromServer, DialogId(), 0, 0, ""); + LOG(INFO) << "ON UPLOAD FULL OK for file " << file_id; + auto new_file_id = register_remote(std::move(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_; +void FileManager::on_partial_generate(QueryId query_id, PartialLocalFileLocation partial_local, int64 expected_size) { + if (is_closed_) { + return; + } + auto query = queries_container_.get(query_id); CHECK(query != nullptr); auto file_id = query->file_id_; auto file_node = get_file_node(file_id); + auto bitmask = Bitmask(Bitmask::Decode{}, partial_local.ready_bitmask_); + LOG(DEBUG) << "Receive on_partial_generate for file " << file_id << ": " << partial_local.path_ << " " << bitmask; if (!file_node) { return; } if (file_node->generate_id_ != query_id) { return; } - file_node->set_local_location(LocalFileLocation(partial_local), 0); + auto ready_size = bitmask.get_total_size(partial_local.part_size_, file_node->size_); + file_node->set_local_location(LocalFileLocation(partial_local), ready_size, -1, -1 /* TODO */); // TODO check for size and local_size, abort generation if needed - if (expected_size != 0) { + if (expected_size > 0) { file_node->set_expected_size(expected_size); } if (!file_node->generate_was_update_) { @@ -2177,18 +3802,23 @@ void FileManager::on_partial_generate(QueryId query_id, const PartialLocalFileLo } if (file_node->upload_id_ != 0) { send_closure(file_load_manager_, &FileLoadManager::update_local_file_location, file_node->upload_id_, - LocalFileLocation(partial_local)); + LocalFileLocation(std::move(partial_local))); } - try_flush_node(file_node); + try_flush_node(file_node, "on_partial_generate"); } -void FileManager::on_generate_ok(QueryId query_id, const FullLocalFileLocation &local) { - LOG(INFO) << "on_ok_generate: " << local; + +void FileManager::on_generate_ok(QueryId query_id, FullLocalFileLocation local) { + if (is_closed_) { + return; + } + Query query; bool was_active; std::tie(query, was_active) = finish_query(query_id); auto generate_file_id = query.file_id_; + LOG(INFO) << "Receive on_generate_ok for file " << generate_file_id << ": " << local; auto file_node = get_file_node(generate_file_id); if (!file_node) { return; @@ -2196,35 +3826,37 @@ void FileManager::on_generate_ok(QueryId query_id, const FullLocalFileLocation & 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(); - } - } + auto r_new_file_id = register_local(local, DialogId(), 0, false, false, false, generate_file_id); 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)); + if (r_new_file_id.is_error()) { + return on_error_impl( + file_node, query.type_, was_active, + Status::Error(PSLICE() << "Can't register local file after generate: " << r_new_file_id.error())); } - CHECK(file_node); - context_->on_new_file(FileView(file_node).size()); + + FileView file_view(file_node); + if (context_->need_notify_on_new_files()) { + if (!file_view.has_generate_location() || !begins_with(file_view.generate_location().conversion_, "#file_id#")) { + context_->on_new_file(file_view.size(), file_view.get_allocated_local_size(), 1); + } + } 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)); + LocalFileLocation(std::move(local))); } } } void FileManager::on_error(QueryId query_id, Status status) { + if (is_closed_) { + return; + } + Query query; bool was_active; std::tie(query, was_active) = finish_query(query_id); @@ -2234,7 +3866,7 @@ void FileManager::on_error(QueryId query_id, Status status) { return; } - if (query.type_ == Query::UploadByHash) { + if (query.type_ == Query::Type::UploadByHash && !G()->close_flag()) { LOG(INFO) << "Upload By Hash failed: " << status << ", restart upload"; node->get_by_hash_ = false; run_upload(node, {}); @@ -2243,34 +3875,87 @@ void FileManager::on_error(QueryId query_id, Status status) { 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) { +void FileManager::on_error_impl(FileNodePtr node, Query::Type type, bool was_active, Status status) { SCOPE_EXIT { - try_flush_node(node); + try_flush_node(node, "on_error_impl"); }; - 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); + + if (status.message() == "FILE_PART_INVALID") { + bool has_partial_small_location = node->remote_.partial && !node->remote_.partial->is_big_; + FileView file_view(node); + auto expected_size = file_view.expected_size(true); + bool should_be_big_location = is_file_big(file_view.get_type(), expected_size); + + node->delete_partial_remote_location(); + if (has_partial_small_location && should_be_big_location) { + run_upload(node, {}); + return; + } + + LOG(ERROR) << "Failed to upload file " << node->main_file_id_ << ": unexpected " << status + << ", is_small = " << has_partial_small_location << ", should_be_big = " << should_be_big_location + << ", expected size = " << expected_size; + } + + if (begins_with(status.message(), "FILE_GENERATE_LOCATION_INVALID")) { + node->set_generate_location(nullptr); + } + + if ((status.message() == "FILE_ID_INVALID" || status.message() == "LOCATION_INVALID") && + FileView(node).may_reload_photo()) { + node->need_reload_photo_ = true; + run_download(node, true); + return; + } + + if (FileReferenceManager::is_file_reference_error(status)) { + string file_reference; + Slice prefix = "#BASE64"; + Slice error_message = status.message(); + auto pos = error_message.rfind('#'); + if (pos < error_message.size() && begins_with(error_message.substr(pos), prefix)) { + auto r_file_reference = base64_decode(error_message.substr(pos + prefix.size())); + if (r_file_reference.is_ok()) { + file_reference = r_file_reference.move_as_ok(); + } else { + LOG(ERROR) << "Can't decode file reference from error " << status << ": " << r_file_reference.error(); } - status = Status::Error(400, status.message()); + } else { + LOG(ERROR) << "Unexpected error, file_reference will be deleted just in case " << status; } + CHECK(!node->file_ids_.empty()); + delete_file_reference(node->file_ids_.back(), file_reference); + run_download(node, true); + return; } - if (status.message() == "FILE_UPLOAD_RESTART") { + if (begins_with(status.message(), "FILE_UPLOAD_RESTART")) { + if (ends_with(status.message(), "WITH_FILE_REFERENCE")) { + node->upload_was_update_file_reference_ = true; + } run_upload(node, {}); return; } - if (status.message() == "FILE_DOWNLOAD_RESTART") { - node->can_search_locally_ = false; - run_download(node); + + if (begins_with(status.message(), "FILE_DOWNLOAD_RESTART")) { + if (ends_with(status.message(), "WITH_FILE_REFERENCE")) { + node->download_was_update_file_reference_ = true; + run_download(node, true); + return; + } else if (ends_with(status.message(), "INCREASE_PART_SIZE")) { + if (try_fix_partial_local_location(node)) { + run_download(node, true); + return; + } + } else { + node->can_search_locally_ = false; + run_download(node, true); + return; + } + } + + if (status.message() == "MTPROTO_CLUSTER_INVALID") { + run_download(node, true); return; } @@ -2278,10 +3963,41 @@ void FileManager::on_error_impl(FileNodePtr node, FileManager::Query::Type type, return; } + if (G()->close_flag() && status.code() < 400) { + status = Global::request_aborted_error(); + } else { + if (status.code() != -1) { + if (type == Query::Type::Generate && node->generate_ != nullptr) { + LOG(WARNING) << "Failed to generate file " << node->main_file_id_ << " with " << *node->generate_ << ": " + << status; + } else { + LOG(WARNING) << "Failed to " << type << " file " << node->main_file_id_ << " of type " + << FileView(node).get_type() << ": " << status; + } + } + if (status.code() == 0) { + // Remove partial locations + if (node->local_.type() == LocalFileLocation::Type::Partial && + !begins_with(status.message(), "FILE_DOWNLOAD_ID_INVALID") && + !begins_with(status.message(), "FILE_DOWNLOAD_LIMIT")) { + CSlice path = node->local_.partial().path_; + if (begins_with(path, get_files_temp_dir(FileType::SecureDecrypted)) || + begins_with(path, get_files_temp_dir(FileType::Video))) { + LOG(INFO) << "Unlink file " << path; + send_closure(file_load_manager_, &FileLoadManager::unlink_file, std::move(node->local_.partial().path_), + Promise<Unit>()); + node->drop_local_location(); + } + } + node->delete_partial_remote_location(); + } + status = Status::Error(400, status.message()); + } + // Stop everything on error - cancel_generate(node); - cancel_download(node); - cancel_upload(node); + do_cancel_generate(node); + do_cancel_download(node); + do_cancel_upload(node); for (auto file_id : vector<FileId>(node->file_ids_)) { auto *info = get_file_id_info(file_id); @@ -2323,12 +4039,14 @@ std::pair<FileManager::Query, bool> FileManager::finish_query(QueryId query_id) } if (node->download_id_ == query_id) { node->download_id_ = 0; + node->download_was_update_file_reference_ = false; node->is_download_started_ = false; node->set_download_priority(0); was_active = true; } if (node->upload_id_ == query_id) { node->upload_id_ = 0; + node->upload_was_update_file_reference_ = false; node->set_upload_priority(0); was_active = true; } @@ -2342,15 +4060,42 @@ FullRemoteFileLocation *FileManager::get_remote(int32 key) { return &remote_location_info_.get(key).remote_; } +Result<string> FileManager::get_suggested_file_name(FileId file_id, const string &directory) { + if (!file_id.is_valid()) { + return Status::Error(400, "Invalid file identifier"); + } + auto node = get_sync_file_node(file_id); + if (!node) { + return Status::Error(400, "Wrong file identifier"); + } + + return ::td::get_suggested_file_name(directory, PathView(node->suggested_path()).file_name()); +} + void FileManager::hangup() { file_db_.reset(); file_generate_manager_.reset(); file_load_manager_.reset(); + while (!queries_container_.empty()) { + auto ids = queries_container_.ids(); + for (auto id : ids) { + on_error(id, Global::request_aborted_error()); + } + } + is_closed_ = true; stop(); } void FileManager::tear_down() { parent_.reset(); + + LOG(DEBUG) << "Have " << file_id_info_.size() << " files with " << file_nodes_.size() << " file nodes, " + << local_location_to_file_id_.size() << " local locations and " << remote_location_info_.size() + << " remote locations to free"; } +constexpr int64 FileManager::KEEP_DOWNLOAD_LIMIT; +constexpr int64 FileManager::KEEP_DOWNLOAD_OFFSET; +constexpr int64 FileManager::IGNORE_DOWNLOAD_LIMIT; + } // namespace td |