From 0bf39a3bdfa8c4235a766900d8030b463b226025 Mon Sep 17 00:00:00 2001 From: Adubbz Date: Mon, 16 Mar 2020 22:47:03 +1100 Subject: [PATCH] ncm: more install task progress --- .../stratosphere/ncm/ncm_content_meta.hpp | 8 +- .../stratosphere/ncm/ncm_install_task.hpp | 39 ++- .../source/ncm/ncm_install_task.cpp | 284 +++++++++++++++--- 3 files changed, 271 insertions(+), 60 deletions(-) diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp index 9e296b3a4..9f651d2bb 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp @@ -72,16 +72,14 @@ namespace ams::ncm { u8 attributes; u8 storage_id; ContentInstallType install_type; - u8 reserved_17; + bool committed; u32 required_download_system_version; u8 reserved_1C[4]; }; static_assert(sizeof(PackagedContentMetaHeader) == 0x20); static_assert(OFFSETOF(PackagedContentMetaHeader, reserved_0D) == 0x0D); - static_assert(OFFSETOF(PackagedContentMetaHeader, reserved_17) == 0x17); static_assert(OFFSETOF(PackagedContentMetaHeader, reserved_1C) == 0x1C); - /* TODO: Confirm this is correct. */ using InstallContentMetaHeader = PackagedContentMetaHeader; struct ApplicationMetaExtendedHeader { @@ -294,6 +292,10 @@ namespace ams::ncm { return false; } + StorageId GetStorageId() const { + return static_cast(this->GetHeader()->storage_id); + } + std::optional GetApplicationId(const ContentMetaKey &key) const { switch (key.type) { case ContentMetaType::Application: return ApplicationId{ key.id }; diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task.hpp index eec63ea5c..00e798407 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task.hpp @@ -19,13 +19,19 @@ namespace ams::ncm { /* protected: -PrepareContentMeta (both), WritePlaceHolderBuffer, PrepareAgain, Get/Delete InstallContentMetaData, PrepareDependency, PrepareSystemDependency, PrepareContentMetaIfLatest, GetConfig, WriteContentMetaToPlaceHolder, GetInstallStorage, GetSystemUpdateTaskApplyInfo, CanContinue +PrepareContentMeta (both), WritePlaceHolderBuffer, Get/Delete InstallContentMetaData, PrepareDependency, PrepareSystemDependency, PrepareContentMetaIfLatest, GetConfig, WriteContentMetaToPlaceHolder, GetInstallStorage, GetSystemUpdateTaskApplyInfo, CanContinue */ struct InstallThroughput { s64 installed; TimeSpan elapsed_time; }; + enum class ListContentMetaKeyFilter : u8 { + All = 0, + Committed = 1, + NotCommitted = 2, + }; + class InstallTaskBase { private: crypto::Sha256Generator sha256_generator; @@ -48,17 +54,30 @@ PrepareContentMeta (both), WritePlaceHolderBuffer, PrepareAgain, Get/Delete Inst Result Initialize(StorageId install_storage, InstallTaskDataBase *data, u32 config); Result CountInstallContentMetaData(s32 *out_count); Result GetInstallContentMetaData(InstallContentMeta *out_content_meta, s32 index); + + void PrepareAgain(); public: /* TODO: Fix access types. */ - bool IsCancelRequested(); - Result Prepare(); - void SetLastResult(Result last_result); - Result GetPreparedPlaceHolderPath(Path *out_path, u64 id, ContentMetaType meta_type, ContentType type); - Result CalculateRequiredSize(size_t *out_size); - void ResetThroughputMeasurement(); - void SetProgressState(InstallProgressState state); - void SetTotalSize(s64 size); - Result PreparePlaceHolder(); + bool IsCancelRequested(); + Result Prepare(); + void SetLastResult(Result last_result); + Result GetPreparedPlaceHolderPath(Path *out_path, u64 id, ContentMetaType meta_type, ContentType type); + Result CalculateRequiredSize(size_t *out_size); + void ResetThroughputMeasurement(); + void SetProgressState(InstallProgressState state); + + void UpdateThroughputMeasurement(s64 throughput); + bool IsNecessaryInstallTicket(const fs::RightsId &rights_id); + void SetTotalSize(s64 size); + Result PreparePlaceHolder(); + Result Cleanup(); + Result CleanupOne(const InstallContentMeta &content_meta); + void CleanupProgress(); + Result ListContentMetaKey(s32 *out_keys_written, StorageContentMetaKey *out_keys, s32 out_keys_count, s32 offset, ListContentMetaKeyFilter filter); + Result ListApplicationContentMetaKey(s32 *out_keys_written, ApplicationContentMetaKey *out_keys, s32 out_keys_count, s32 offset); + + void ResetLastResult(); + s64 GetThroughput(); protected: virtual Result OnPrepareComplete(); virtual Result PrepareDependency(); diff --git a/libraries/libstratosphere/source/ncm/ncm_install_task.cpp b/libraries/libstratosphere/source/ncm/ncm_install_task.cpp index f26085cc9..08e9f93a3 100644 --- a/libraries/libstratosphere/source/ncm/ncm_install_task.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_install_task.cpp @@ -194,61 +194,62 @@ namespace ams::ncm { std::scoped_lock lk(placeholder_mutex); InstallContentMeta content_meta; - if (R_SUCCEEDED(this->data->Get(&content_meta, i))) { - auto writer = content_meta.GetWriter(); - StorageId storage_id = static_cast(writer.GetHeader()->storage_id); + R_TRY(this->data->Get(&content_meta, i)); - /* Automatically choose a suitable storage id. */ - if (storage_id == StorageId::None) { - R_TRY(ncm::SelectDownloadableStorage(std::addressof(storage_id), storage_id, writer.CalculateContentRequiredSize())); - } + auto writer = content_meta.GetWriter(); + StorageId storage_id = static_cast(writer.GetHeader()->storage_id); - /* Update the data when we are done. */ - ON_SCOPE_EXIT { this->data->Update(content_meta, i); }; + /* Automatically choose a suitable storage id. */ + if (storage_id == StorageId::None) { + R_TRY(ncm::SelectDownloadableStorage(std::addressof(storage_id), storage_id, writer.CalculateContentRequiredSize())); + } - /* Open the relevant content storage. */ - ContentStorage content_storage; - R_TRY(ncm::OpenContentStorage(&content_storage, storage_id)); + /* Update the data when we are done. */ + ON_SCOPE_EXIT { this->data->Update(content_meta, i); }; - /* Update the storage id in the header. */ - writer.SetStorageId(storage_id); - - for (size_t j = 0; j < writer.GetContentCount(); j++) { - R_UNLESS(!this->IsCancelRequested(), ncm::ResultCreatePlaceHolderCancelled()); - auto *content_info = writer.GetWritableContentInfo(j); + /* Open the relevant content storage. */ + ContentStorage content_storage; + R_TRY(ncm::OpenContentStorage(&content_storage, storage_id)); - bool has_content; - R_TRY(content_storage.Has(&has_content, content_info->GetId())); + /* Update the storage id in the header. */ + writer.SetStorageId(storage_id); + + for (size_t j = 0; j < writer.GetContentCount(); j++) { + R_UNLESS(!this->IsCancelRequested(), ncm::ResultCreatePlaceHolderCancelled()); + auto *content_info = writer.GetWritableContentInfo(j); - if (has_content) { - /* Add the size of installed content infos to the total size. */ - if (content_info->install_state == InstallState::Installed) { - total_size += content_info->GetSize(); - } + /* Check if we have the content already exists. */ + bool has_content; + R_TRY(content_storage.Has(&has_content, content_info->GetId())); - /* Update the install state. */ - content_info->install_state = InstallState::AlreadyExists; - } else { - if (content_info->install_state == InstallState::NotPrepared) { - /* Generate a placeholder id. */ - const PlaceHolderId placeholder_id = content_storage.GeneratePlaceHolderId(); - - /* Update the placeholder id in the content info. */ - content_info->placeholder_id = placeholder_id; - - /* Create the placeholder. */ - R_TRY(content_storage.CreatePlaceHolder(placeholder_id, content_info->GetId(), content_info->GetSize())); - - /* Update the install state. */ - content_info->install_state = InstallState::Prepared; - } - - /* Update the storage id for the content info. */ - content_info->storage_id = storage_id; - - /* Add the size of this content info to the total size. */ + if (has_content) { + /* Add the size of installed content infos to the total size. */ + if (content_info->install_state == InstallState::Installed) { total_size += content_info->GetSize(); - } + } + + /* Update the install state. */ + content_info->install_state = InstallState::AlreadyExists; + } else { + if (content_info->install_state == InstallState::NotPrepared) { + /* Generate a placeholder id. */ + const PlaceHolderId placeholder_id = content_storage.GeneratePlaceHolderId(); + + /* Update the placeholder id in the content info. */ + content_info->placeholder_id = placeholder_id; + + /* Create the placeholder. */ + R_TRY(content_storage.CreatePlaceHolder(placeholder_id, content_info->GetId(), content_info->GetSize())); + + /* Update the install state. */ + content_info->install_state = InstallState::Prepared; + } + + /* Update the storage id for the content info. */ + content_info->storage_id = storage_id; + + /* Add the size of this content info to the total size. */ + total_size += content_info->GetSize(); } } } @@ -257,8 +258,183 @@ namespace ams::ncm { return ResultSuccess(); } + Result InstallTaskBase::Cleanup() { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Get the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(&content_meta, i)); + + /* Cleanup the content meta. */ + /* N doesn't check the result of this. */ + this->CleanupOne(content_meta); + } + + /* Cleanup the data and progress. */ + R_TRY(this->data->Cleanup()); + this->CleanupProgress(); + return ResultSuccess(); + } + + Result InstallTaskBase::CleanupOne(const InstallContentMeta &content_meta) { + /* Obtain a reader and get the storage id. */ + const auto reader = content_meta.GetReader(); + R_SUCCEED_IF(reader.GetStorageId() == StorageId::None); + + /* Open the relevant content storage. */ + ContentStorage content_storage; + R_TRY(ncm::OpenContentStorage(&content_storage, reader.GetStorageId())); + + /* Iterate over content infos. */ + for (size_t i = 0; i < reader.GetContentCount(); i++) { + auto *content_info = reader.GetContentInfo(i); + + /* Delete placeholders for Prepared or Installed content infos. */ + if (content_info->install_state == InstallState::Prepared || content_info->install_state == InstallState::Installed) { + content_storage.DeletePlaceHolder(content_info->placeholder_id); + } + } + + return ResultSuccess(); + } + + void InstallTaskBase::CleanupProgress() { + std::scoped_lock(this->progress_mutex); + this->progress.installed_size = 0; + this->progress.total_size = 0; + this->progress.state = InstallProgressState::NotPrepared; + this->progress.SetLastResult(ResultSuccess()); + } + + Result InstallTaskBase::ListContentMetaKey(s32 *out_keys_written, StorageContentMetaKey *out_keys, s32 out_keys_count, s32 offset, ListContentMetaKeyFilter filter) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + /* Offset exceeds keys that can be written. */ + if (count <= offset) { + *out_keys_written = 0; + return ResultSuccess(); + } + + if (filter == ListContentMetaKeyFilter::All) { + const size_t num_keys = std::min(count, offset + out_keys_count); + + /* Iterate over content meta. */ + for (s32 i = offset; i < num_keys; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(&content_meta, i)); + + /* Write output StorageContentMetaKey. */ + const auto reader = content_meta.GetReader(); + StorageContentMetaKey &storage_key = out_keys[i - offset]; + storage_key.key = reader.GetKey(); + storage_key.storage_id = reader.GetStorageId(); + } + + /* Output the number of keys written. */ + *out_keys_written = num_keys - offset; + } else { + s32 keys_written = 0; + s32 curr_offset = 0; + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(&content_meta, i)); + + /* Create a reader and check if the content has been committed. */ + const auto reader = content_meta.GetReader(); + const bool committed = reader.GetHeader()->committed; + + /* Apply filter. */ + if ((!committed && filter == ListContentMetaKeyFilter::Committed) || (committed && filter == ListContentMetaKeyFilter::NotCommitted)) { + continue; + } + + /* Write output StorageContentMetaKey if at a suitable offset. */ + if (curr_offset >= offset) { + StorageContentMetaKey &storage_key = out_keys[keys_written++]; + storage_key.key = reader.GetKey(); + storage_key.storage_id = reader.GetStorageId(); + } + + /* Increment the current offset. */ + curr_offset++; + + /* We can't write any more output keys. */ + if (keys_written >= out_keys_count) { + break; + } + } + + /* Output the number of keys written. */ + *out_keys_written = keys_written; + } + + return ResultSuccess(); + } + + Result InstallTaskBase::ListApplicationContentMetaKey(s32 *out_keys_written, ApplicationContentMetaKey *out_keys, s32 out_keys_count, s32 offset) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->data->Count(std::addressof(count))); + + s32 keys_written = 0; + + /* Iterate over content meta. */ + for (s32 i = offset; i < std::min(count, offset + out_keys_count); i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->data->Get(&content_meta, i)); + + /* Create a reader. */ + const auto reader = content_meta.GetReader(); + + /* Ensure this key has an application id. */ + if (!reader.GetApplicationId()) { + continue; + } + + /* Write output ApplicationContentMetaKey. */ + ApplicationContentMetaKey &out_key = out_keys[keys_written++]; + out_key.key = reader.GetKey(); + out_key.application_id = *reader.GetApplicationId(); + } + + *out_keys_written = keys_written; + return ResultSuccess(); + } + /* ... */ + void InstallTaskBase::UpdateThroughputMeasurement(s64 throughput) { + std::scoped_lock lk(this->throughput_mutex); + + if (this->throughput_start_time.GetNanoSeconds() != 0) { + this->throughput.installed += throughput; + /* TODO. */ + } + } + + bool InstallTaskBase::IsNecessaryInstallTicket(const fs::RightsId &rights_id) { + /* If the title has no rights, there's no ticket to install. */ + fs::RightsId empty_rights_id = {}; + if (std::memcmp(std::addressof(rights_id), std::addressof(empty_rights_id), sizeof(fs::RightsId)) == 0) { + return false; + } + + /* TODO: Support detecting if a title requires rights. */ + /* TODO: How should es be handled without undesired effects? */ + return false; + } + void InstallTaskBase::SetTotalSize(s64 size) { std::scoped_lock(this->progress_mutex); this->progress.total_size = size; @@ -266,6 +442,10 @@ namespace ams::ncm { /* ... */ + void InstallTaskBase::PrepareAgain() { + this->SetProgressState(InstallProgressState::NotPrepared); + } + Result InstallTaskBase::PrepareDependency() { return ResultSuccess(); } @@ -276,4 +456,14 @@ namespace ams::ncm { std::scoped_lock lk(this->progress_mutex); return this->progress; } + + void InstallTaskBase::ResetLastResult() { + this->SetLastResult(ResultSuccess()); + } + + s64 InstallTaskBase::GetThroughput() { + std::scoped_lock lk(this->throughput_mutex); + return this->throughput.installed; + } + }