// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "manifest/manifest_builder.h" #include #include #include "absl/strings/str_format.h" #include "absl/time/time.h" #include "common/log.h" #include "common/path.h" #include "common/status.h" #include "common/status_macros.h" #include "common/util.h" #include "manifest/asset_builder.h" #include "manifest/content_id.h" namespace cdc_ft { namespace { // Splits the given Unix path into its components. inline std::vector SplitUnixPath(const std::string& path) { return SplitString(path, '/', false); } // Joins the given path components using the Unix path separator. This function // assumes that none of the path components have trailing path separators. inline std::string JoinUnixPath(const std::vector& path) { return JoinStrings(path, 0, path.size(), '/'); } } // namespace ManifestBuilder::ManifestBuilder(CdcParamsProto cdc_params, DataStoreWriter* chunk_store) : data_store_(chunk_store), cdc_params_(std::move(cdc_params)) { Reset(); } ManifestBuilder::~ManifestBuilder() = default; absl::Status ManifestBuilder::LoadManifest(const std::string& manifest_hex_id) { ContentIdProto manifest_id; if (!ContentId::FromHexString(manifest_hex_id, &manifest_id)) { return absl::InvalidArgumentError( absl::StrFormat("Invalid manifest ID: '%s'", manifest_hex_id)); } return LoadManifest(manifest_id); } absl::Status ManifestBuilder::LoadManifest(const ContentIdProto& manifest_id) { Reset(); RETURN_IF_ERROR(data_store_->GetProto(manifest_id, manifest_)); manifest_id_.CopyFrom(manifest_id); return absl::OkStatus(); } void ManifestBuilder::Reset() { asset_lists_.clear(); manifest_id_.Clear(); manifest_bytes_written_ = 0; manifest_chunks_written_ = 0; arena_.Reset(); manifest_ = MakeProto(); *manifest_->mutable_cdc_params() = cdc_params_; } absl::StatusOr ManifestBuilder::GetOrCreateAsset( const std::string& path, AssetProto::Type type, bool force_create, bool* created) { // We must keep |unix_path| allocated while the string_views in |parts| are // being used. if (created) *created = false; std::string unix_path = path::ToUnix(path); std::vector parts = SplitUnixPath(unix_path); absl::string_view name; if (!parts.empty()) { name = parts.back(); parts.pop_back(); } DirCreateMode create_mode = type == AssetProto::UNKNOWN ? DirCreateMode::kNoCreate : force_create ? DirCreateMode::kForceCreate : DirCreateMode::kCreate; AssetProto* dir; ASSIGN_OR_RETURN(dir, FindOrCreateDirPath(parts, create_mode), "Failed to create directory '%s'", JoinUnixPath(parts)); if (name.empty()) { // Special case: return the root directory for a DIRECTORY with empty name. if (type == AssetProto::DIRECTORY || type == AssetProto::UNKNOWN) { return AssetBuilder(dir, std::string()); } return absl::InvalidArgumentError("Empty path given"); } // Check if the asset already exists. absl::StatusOr result = FindAssetInDir(name, dir); AssetProto* asset = nullptr; if (result.ok()) { asset = result.value(); // If the asset type is unknown, we return any type. if (type == AssetProto::UNKNOWN) { return AssetBuilder(asset, path::DirName(unix_path)); } // Verify that both assets are of the same type. if (asset->type() != type) { if (force_create) { RETURN_IF_ERROR(DeleteAsset(path)); asset = nullptr; } else { return absl::AlreadyExistsError(absl::StrFormat( "Asset '%s' already exists in '%s' as %s.", path, JoinUnixPath(parts), AssetProto::Type_Name(asset->type()))); } } } else if (!absl::IsNotFound(result.status())) { // Return any unexpected error. return result.status(); } // Create the asset if it was not found or it was deleted. if (!asset) { if (type == AssetProto::UNKNOWN) { return absl::NotFoundError( absl::StrFormat("Asset '%s' does not exist.", path)); } asset = dir->add_dir_assets(); InitNewAsset(name, type, asset); if (created) *created = true; } return AssetBuilder(asset, path::DirName(unix_path)); } absl::Status ManifestBuilder::DeleteAsset(const std::string& path) { // We must keep |unix_path| allocated while the string_views in |parts| are // being used. std::string unix_path = path::ToUnix(path); std::vector parts = SplitUnixPath(unix_path); if (parts.empty()) return absl::InvalidArgumentError("Empty path given"); absl::string_view name = parts.back(); parts.pop_back(); absl::StatusOr dir = FindOrCreateDirPath(parts, DirCreateMode::kNoCreate); if (!dir.ok()) { // We can get an absl::InvalidArgumentError here if one of the path // components is not a directory, which means the asset to be deleted does // not exist. if (absl::IsNotFound(dir.status()) || absl::IsInvalidArgument(dir.status())) { return absl::OkStatus(); } // Return any unexpected error. return WrapStatus(dir.status(), "Failed to look up path '%s'", JoinUnixPath(parts)); } // Check if the asset exists. return DeleteAssetFromDir(name, *dir); } absl::StatusOr ManifestBuilder::FindOrCreateDirPath( const std::vector& path, DirCreateMode create_dirs) { // Create the first manifest, if needed, independent of |create_dirs|. if (!manifest_->has_root_dir()) { InitNewAsset(absl::string_view(), AssetProto::DIRECTORY, manifest_->mutable_root_dir()); } return FindOrCreateDirPathRec(path, 0, manifest_->mutable_root_dir(), create_dirs); } absl::StatusOr ManifestBuilder::FindOrCreateDirPathRec( const std::vector& path, size_t path_idx, AssetProto* dir, DirCreateMode create_dirs) { if (path_idx >= path.size()) return dir; absl::string_view name = path[path_idx]; // Try to find the name in the direct assets. bool overwrite = create_dirs == DirCreateMode::kForceCreate; absl::StatusOr result = FindMutableAssetInList( name, AssetProto::DIRECTORY, overwrite, dir->mutable_dir_assets()); if (result.ok()) { // Recurse into the sub-directory. return FindOrCreateDirPathRec(path, path_idx + 1, result.value(), create_dirs); } if (!absl::IsNotFound(result.status())) { // Return any unexpected error. return result; } // Try to find the name in the list of indirect assets. for (const ContentIdProto& asset_list_id : dir->dir_indirect_assets()) { AssetListProto* asset_list; ASSIGN_OR_RETURN(asset_list, GetAssetList(asset_list_id)); // In theory it can happen that the loaded asset_list is empty, in which // case it is null. if (!asset_list) continue; result = FindMutableAssetInList(name, AssetProto::DIRECTORY, overwrite, asset_list->mutable_assets()); if (result.ok()) { // Recurse into the sub-directory. return FindOrCreateDirPathRec(path, path_idx + 1, result.value(), create_dirs); } if (!absl::IsNotFound(result.status())) { // Return any unexpected error. return WrapStatus(result.status(), "Failed to look up directory '%s' in AssetListProto %s", name, ContentId::ToHexString(asset_list_id)); } } // If we're not supposed to create the directory, return an error. if (create_dirs == DirCreateMode::kNoCreate) { return absl::NotFoundError(absl::string_view()); } // Create the missing directory. AssetProto* child = dir->add_dir_assets(); InitNewAsset(name, AssetProto::DIRECTORY, child); return FindOrCreateDirPathRec(path, path_idx + 1, child, create_dirs); } absl::StatusOr ManifestBuilder::FindAssetInDir( absl::string_view name, AssetProto* dir) { if (dir->type() != AssetProto::DIRECTORY) { return WrongAssetTypeError(dir->name(), dir->type(), AssetProto::DIRECTORY); } // Try to find the name in the direct assets. absl::StatusOr result = FindMutableAssetInList(name, dir->mutable_dir_assets()); if (result.ok()) { return result.value(); } if (!absl::IsNotFound(result.status())) { // Return any unexpected error. return result; } // Try to find the name in the list of indirect assets. for (const ContentIdProto& asset_list_id : dir->dir_indirect_assets()) { AssetListProto* asset_list; ASSIGN_OR_RETURN(asset_list, GetAssetList(asset_list_id), "Failed to look up asset '%s' in directory '%s'", name, dir->name()); result = FindMutableAssetInList(name, asset_list->mutable_assets()); if (result.ok()) { return result.value(); } if (!absl::IsNotFound(result.status())) { // Return any unexpected error. return result; } } return absl::NotFoundError(absl::string_view()); } absl::StatusOr ManifestBuilder::FindMutableAssetInList( absl::string_view name, RepeatedAssetProto* assets) const { for (AssetProto& asset : *assets) { if (asset.name() == name) return &asset; } return absl::NotFoundError(absl::string_view()); } absl::StatusOr ManifestBuilder::FindMutableAssetInList( absl::string_view name, AssetProto::Type type, bool overwrite, RepeatedAssetProto* assets) const { AssetProto* asset; ASSIGN_OR_RETURN(asset, FindMutableAssetInList(name, assets)); if (asset->type() != type) { // Return an error if the asset is not of the desired type and we're not // supposed to overwrite it. if (!overwrite) { return WrongAssetTypeError(asset->name(), asset->type(), type); } // Replace the asset with the new type. InitNewAsset(std::string(asset->name()), type, asset); } return asset; } absl::Status ManifestBuilder::DeleteAssetFromDir(absl::string_view name, AssetProto* dir) { if (dir->type() != AssetProto::DIRECTORY) { return WrongAssetTypeError(dir->name(), dir->type(), AssetProto::DIRECTORY); } // Try to find the name in the direct assets. if (DeleteAssetFromList(name, dir->mutable_dir_assets())) { return absl::OkStatus(); } // Try to find the name in the list of indirect assets. for (const ContentIdProto& asset_list_id : dir->dir_indirect_assets()) { AssetListProto* asset_list; ASSIGN_OR_RETURN(asset_list, GetAssetList(asset_list_id), "Failed to look up asset '%s' in directory '%s'", name, dir->name()); if (DeleteAssetFromList(name, asset_list->mutable_assets())) { return absl::OkStatus(); } } return absl::OkStatus(); } bool ManifestBuilder::DeleteAssetFromList(absl::string_view name, RepeatedAssetProto* assets) const { for (int i = 0; i < assets->size(); ++i) { if (assets->at(i).name() == name) { // Move the asset to the end of the list, then remove it, to avoid all // other elements being moved. if (i != assets->size() - 1) { assets->SwapElements(i, assets->size() - 1); } assets->RemoveLast(); return true; } } return false; } void ManifestBuilder::InitNewAsset(absl::string_view name, AssetProto::Type type, AssetProto* asset) const { asset->Clear(); asset->set_name(name.data(), name.size()); asset->set_type(type); asset->set_mtime_seconds(absl::ToUnixSeconds(absl::Now())); asset->set_permissions(type == AssetProto::DIRECTORY ? kDefaultDirPerms : kDefaultFilePerms); } absl::StatusOr ManifestBuilder::GetAssetList( const ContentIdProto& id) { // See if we loaded this proto already. AssetListMap::iterator it = asset_lists_.find(id); if (it != asset_lists_.end()) return it->second; // If not, we need to load it. AssetListProto* asset_list = MakeProto(); RETURN_IF_ERROR(data_store_->GetProto(id, asset_list), "Failed to read the AssetListProto with ID %s from storage", ContentId::ToHexString(id)); asset_lists_[id] = asset_list; return asset_list; } absl::StatusOr ManifestBuilder::TakeOutAssetList( const ContentIdProto& id) { AssetListProto* list; ASSIGN_OR_RETURN(list, GetAssetList(id)); asset_lists_.erase(id); return list; } absl::Status ManifestBuilder::WrongAssetTypeError( absl::string_view name, AssetProto::Type found, AssetProto::Type expected) const { return absl::InvalidArgumentError(absl::StrFormat( "Asset '%s' is of type %s, expected %s.", name, AssetProto::Type_Name(found), AssetProto::Type_Name(expected))); } size_t ManifestBuilder::ManifestBytesWritten() const { return manifest_bytes_written_; } size_t ManifestBuilder::ManifestsChunksWritten() const { return manifest_chunks_written_; } const ContentIdProto& ManifestBuilder::ManifestId() const { return manifest_id_; } const ManifestProto* ManifestBuilder::Manifest() const { return manifest_; } const std::vector& ManifestBuilder::FlushedContentIds() const { return flushed_content_ids_; } absl::Status ManifestBuilder::FlushDir(AssetProto* dir) { // Flush all direct assets. RETURN_IF_ERROR(FlushAssetList(dir->mutable_dir_assets()), "Failed to flush directs assets of directory '%s'", dir->name()); RepeatedAssetProto overflow; RepeatedContentIdProto* indirect_assets = dir->mutable_dir_indirect_assets(); // Flush all indirect asset lists that were previously loaded. RepeatedContentIdProto::iterator it = indirect_assets->begin(); while (it != indirect_assets->end()) { ContentIdProto& asset_list_id = *it; // Skip any list that was never loaded. AssetListMap::iterator asset_list_it = asset_lists_.find(asset_list_id); if (asset_list_it == asset_lists_.end()) { ++it; continue; } AssetListProto* asset_list = asset_list_it->second; // Flush the list and enforce the chunk size limit. RETURN_IF_ERROR(FlushAssetList(asset_list->mutable_assets()), "Failed to flush indirect asset list %s in directory '%s'", ContentId::ToHexString(asset_list_id), dir->name()); EnforceAssetListProtoSize(asset_list, &overflow); // If the asset list is empty, just delete it from the indirect asset list. if (asset_list->assets_size() <= 0) { it = indirect_assets->erase(it); continue; } // Write the list to the chunk store and update the content ID. RETURN_IF_ERROR(WriteProto(*asset_list, &asset_list_id), "Failed to write indirect asset list proto for directory " "'%s' to storage", dir->name()); // If the content ID changed, we need to update the list's key in the map. if (asset_list_it->first != asset_list_id) { AssetListProto* list = asset_list_it->second; asset_lists_.erase(asset_list_it); asset_lists_[asset_list_id] = list; } ++it; } // Enforce size limit for this DIRECTORY asset. RETURN_IF_ERROR(EnforceDirProtoSize(dir, &overflow)); // Add the overflown assets to the indirect assets list. return AppendAllocatedIndirectAssets(dir, &overflow); } absl::Status ManifestBuilder::FlushAssetList(RepeatedAssetProto* assets) { // Flush all sub-directories. for (AssetProto& asset : *assets) { if (asset.type() == AssetProto::DIRECTORY) RETURN_IF_ERROR(FlushDir(&asset)); } return absl::OkStatus(); } inline void SortByProtoSizeDesc(RepeatedAssetProto* assets) { std::sort(assets->begin(), assets->end(), [](const AssetProto& a, const AssetProto& b) -> bool { // Compare greater than for descending order. return a.ByteSizeLong() > b.ByteSizeLong(); }); } absl::Status ManifestBuilder::EnforceDirProtoSize( AssetProto* dir, RepeatedAssetProto* overflow) { // A max. size of zero means no limit. const size_t max_size = manifest_->cdc_params().avg_chunk_size(); if (!max_size) return absl::OkStatus(); // We cannot change the size of non-directory assets. if (dir->type() != AssetProto::DIRECTORY) return absl::OkStatus(); // Calculate the full proto size only once. size_t proto_size = dir->ByteSizeLong(); if (proto_size <= max_size) return absl::OkStatus(); // Sort asset list by size so that we start with the largest assets. SortByProtoSizeDesc(dir->mutable_dir_assets()); // Enforce the size limit of large FILE assets, where "large" is defined as // 1/16th of the target chunk size. const size_t max_asset_proto_size = max_size >> 4; if (max_asset_proto_size) { for (AssetProto& asset : *dir->mutable_dir_assets()) { size_t asset_proto_size = asset.ByteSizeLong(); // Stop if the remaining assets are no longer large. if (proto_size <= max_size || asset_proto_size <= max_asset_proto_size) { break; } if (asset.type() != AssetProto::FILE) continue; RETURN_IF_ERROR(EnforceFileProtoSize(&asset, max_asset_proto_size)); // Adjust the directory proto size. proto_size = proto_size + asset.ByteSizeLong() - asset_proto_size; } } // Move assets to the overflow list until the limit is respected. while (dir->dir_assets_size() && proto_size > max_size) { // Use the UnsafeArena* function to avoid a heap copy of the message. AssetProto* asset = dir->mutable_dir_assets()->UnsafeArenaReleaseLast(); proto_size -= asset->ByteSizeLong() + kRepeatedProtoFieldOverhead; // When the estimates get us below the limit, calculate the accurate size. if (proto_size <= max_size) proto_size = dir->ByteSizeLong(); overflow->UnsafeArenaAddAllocated(asset); } // At this point, we might still be over the size limit for a combination of // a very small chunk size and a very large directories. There's nothing we // can do about it with the current structure of the manifest proto. if (proto_size > max_size) { LOG_WARNING( "Manifest for directory '%s' is over the configured chunk size limit " "(%d > %d). Consider increasing the chunk size.", dir->name(), proto_size, max_size); } return absl::OkStatus(); } absl::Status ManifestBuilder::EnforceFileProtoSize( AssetProto* file, size_t max_asset_proto_size) { if (!max_asset_proto_size) return absl::OkStatus(); assert(file->type() == AssetProto::FILE); // If there is only a single direct chunk, we cannot reduce the proto size. if (file->file_chunks_size() <= 1) return absl::OkStatus(); // We expect no indirect chunk lists at this point. If we ever decide to // "rebalance" existing manifests with a smaller chunk size, we need to push // the indirect chunks before the existing ones. if (file->file_indirect_chunks_size() > 0) { return MakeStatus( "Given asset '%s' already has %d indirect chunk lists which is not " "supported", file->name(), file->file_indirect_chunks_size()); } std::deque overflow; size_t proto_size = file->ByteSizeLong(); // Remove chunks until the size limit is respected. while (file->file_chunks_size() && proto_size > max_asset_proto_size) { // Use the UnsafeArena* function to avoid a heap copy of the message. ChunkRefProto* ref = file->mutable_file_chunks()->UnsafeArenaReleaseLast(); proto_size -= ref->ByteSizeLong() + kRepeatedProtoFieldOverhead; // When the estimates get us below the limit, calculate the accurate size. if (proto_size <= max_asset_proto_size) proto_size = file->ByteSizeLong(); overflow.push_back(ref); } if (overflow.empty()) return absl::OkStatus(); // Move chunks to indirect chunk lists. All proto memory is owned by the // |arena_|, we don't need to worry about leaking memory here. ChunkListProto* chunk_list = MakeProto(); size_t chunk_list_size = 0; uint64_t chunk_list_offset = overflow.back()->offset(); const size_t max_size = manifest_->cdc_params().avg_chunk_size(); while (!overflow.empty()) { ChunkRefProto* chunk_ref = overflow.back(); overflow.pop_back(); // Convert the chunk's absolute offset to a relative one. uint64_t chunk_absolute_offset = chunk_ref->offset(); chunk_ref->set_offset(chunk_absolute_offset - chunk_list_offset); size_t chunkref_proto_size = chunk_ref->ByteSizeLong() + kRepeatedProtoFieldOverhead; // Write back a full chunk list and set offset and content ID accordingly. if (chunk_list_size > 0 && chunk_list_size + chunkref_proto_size > max_size) { RETURN_IF_ERROR(WriteBackChunkList(chunk_list_offset, *chunk_list, file->add_file_indirect_chunks())); chunk_list->Clear(); chunk_list_size = 0; // The first chunk in the list defines the chunk list's offset. chunk_list_offset = chunk_absolute_offset; chunk_ref->set_offset(0); chunkref_proto_size = chunk_ref->ByteSizeLong() + kRepeatedProtoFieldOverhead; } // Move chunk reference to the indirect list. Use the UnsafeArena* function // again to pass ownership without copying the data. chunk_list->mutable_chunks()->UnsafeArenaAddAllocated(chunk_ref); chunk_list_size += chunkref_proto_size; // When the estimates get us above the limit, calculate the accurate size. if (chunk_list_size > max_size) chunk_list_size = chunk_list->ByteSizeLong(); } // Write back final chunk list. return WriteBackChunkList(chunk_list_offset, *chunk_list, file->add_file_indirect_chunks()); } bool ManifestBuilder::EnforceAssetListProtoSize( AssetListProto* asset_list, RepeatedAssetProto* overflow) const { // A max. size of zero means no limit. const size_t max_size = manifest_->cdc_params().avg_chunk_size(); if (!max_size) return false; size_t proto_size = asset_list->ByteSizeLong(); bool changed = false; while (proto_size > max_size) { // Use the UnsafeArena* function to avoid a heap copy of the message. AssetProto* asset = asset_list->mutable_assets()->UnsafeArenaReleaseLast(); proto_size -= asset->ByteSizeLong() + kRepeatedProtoFieldOverhead; // When the estimates get us below the limit, calculate the accurate size. if (proto_size <= max_size) proto_size = asset_list->ByteSizeLong(); overflow->UnsafeArenaAddAllocated(asset); changed = true; } return changed; } absl::Status ManifestBuilder::WriteBackAssetList( AssetListProto* asset_list, ContentIdProto* asset_list_id) { RETURN_IF_ERROR(WriteProto(*asset_list, asset_list_id), "Failed to write back AssetListProto"); asset_lists_[*asset_list_id] = asset_list; return absl::OkStatus(); } absl::Status ManifestBuilder::WriteBackChunkList( uint64_t chunk_list_offset, const ChunkListProto& chunk_list, IndirectChunkListProto* indirect_chunk_list) { assert(chunk_list.chunks_size() > 0); RETURN_IF_ERROR( WriteProto(chunk_list, indirect_chunk_list->mutable_chunk_list_id())); indirect_chunk_list->set_offset(chunk_list_offset); return absl::OkStatus(); } absl::Status ManifestBuilder::WriteProto( const google::protobuf::MessageLite& proto, ContentIdProto* content_id) { size_t proto_size = 0; RETURN_IF_ERROR(data_store_->PutProto(proto, content_id, &proto_size)); flushed_content_ids_.push_back(*content_id); // Update stats. manifest_bytes_written_ += proto_size; ++manifest_chunks_written_; return absl::OkStatus(); } absl::Status ManifestBuilder::AppendAllocatedIndirectAssets( AssetProto* dir, RepeatedAssetProto* assets) { if (assets->empty()) return absl::OkStatus(); // The max. manifest chunk size that we try to stay under. const size_t max_size = manifest_->cdc_params().avg_chunk_size(); // Use asset_list to track the last allocated list, if any. AssetListProto* asset_list = nullptr; // Index to the indirect asset list within |dir| currently in use. Defaults to // zero, which means that if |dir| does not have any indirect asset lists, the // code below will create the first one and store it at index zero. int asset_list_index = 0; // Approximate byte size of the asset list proto currently in use. This size // is updated with the byte size of any asset proto that is appended to the // list, but ignores any overhead from the embedding proto format (which // should be negliable). size_t proto_size = 0; // Find or create the AssetListProto where we can append the assets. if (dir->dir_indirect_assets_size() > 0) { // Load the last indirect asset list and see if we can append to it. asset_list_index = dir->dir_indirect_assets_size() - 1; const ContentIdProto& asset_list_id = dir->dir_indirect_assets(asset_list_index); // Take out the asset list from its original location since the content ID // will be updated anyway once we append more assets to it. ASSIGN_OR_RETURN(asset_list, TakeOutAssetList(asset_list_id)); proto_size = asset_list->ByteSizeLong(); } else { // Add the first indirect asset to |dir|, asset_list_index is already // initialized to zero. dir->add_dir_indirect_assets(); asset_list = MakeProto(); } while (!assets->empty()) { // Use the UnsafeArena* function to avoid a heap copy of the message. Even // though it is released from the proto, the memory is still owned by the // |arena_| and shares its lifetime. AssetProto* asset = assets->UnsafeArenaReleaseLast(); size_t asset_proto_size = asset->ByteSizeLong() + kRepeatedProtoFieldOverhead; // See if we need to create a new AssetListProto. if (max_size > 0 && proto_size > 0 && proto_size + asset_proto_size > max_size) { // Write back the full list to the data store. RETURN_IF_ERROR( WriteBackAssetList( asset_list, dir->mutable_dir_indirect_assets(asset_list_index)), "Failed to write back asset list for directory '%s'", dir->name()); // Create a new list. asset_list = MakeProto(); proto_size = 0; asset_list_index = dir->dir_indirect_assets_size(); dir->add_dir_indirect_assets(); } // Append the allocated asset to the current list. asset_list->mutable_assets()->UnsafeArenaAddAllocated(asset); proto_size += asset_proto_size; } // Write back the final asset list. RETURN_IF_ERROR( WriteBackAssetList(asset_list, dir->mutable_dir_indirect_assets(asset_list_index)), "Failed to write back final asset list for directory '%s'", dir->name()); return absl::OkStatus(); } absl::StatusOr ManifestBuilder::Flush() { manifest_bytes_written_ = 0; manifest_chunks_written_ = 0; flushed_content_ids_.clear(); if (!manifest_->has_root_dir()) { InitNewAsset("", AssetProto::DIRECTORY, manifest_->mutable_root_dir()); } RETURN_IF_ERROR(FlushDir(manifest_->mutable_root_dir())); RETURN_IF_ERROR(WriteProto(*manifest_, &manifest_id_)); return manifest_id_; } ManifestBuilder::FileLookupMap ManifestBuilder::CreateFileLookup() { std::unordered_map lookup; CreateFileLookupRec(std::string(), manifest_->mutable_root_dir(), lookup); return lookup; } void ManifestBuilder::CreateFileLookupRec(const std::string& rel_path, AssetProto* asset, FileLookupMap& lookup) { std::string rel_file_path = path::JoinUnix(rel_path, asset->name()); if (asset->type() == AssetProto::FILE) { lookup[rel_file_path] = asset; return; } // Handle all direct assets. for (AssetProto& child : *asset->mutable_dir_assets()) CreateFileLookupRec(rel_file_path, &child, lookup); // Add all (loaded!) indirect assets as well. for (const ContentIdProto& id : asset->dir_indirect_assets()) { const auto iter = asset_lists_.find(id); if (iter == asset_lists_.end()) continue; AssetListProto* asset_list = iter->second; assert(asset_list); for (AssetProto& child : *asset_list->mutable_assets()) CreateFileLookupRec(rel_file_path, &child, lookup); } } // Returns used CDC parameters CdcParamsProto ManifestBuilder::CdcParameters() const { return manifest_->cdc_params(); } template T* ManifestBuilder::MakeProto() { return google::protobuf::Arena::CreateMessage(&arena_); } } // namespace cdc_ft