/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #pragma once #include <AzCore/Component/Component.h> #include <AzCore/std/containers/unordered_set.h> #include <AzCore/std/containers/unordered_map.h> #include <AzCore/std/containers/deque.h> #include <AzCore/std/containers/vector.h> #include <AzCore/std/string/string.h> #include <AzCore/std/parallel/mutex.h> #include <DynamicContentFileInfo.h> #include <AzCore/std/smart_ptr/shared_ptr.h> #include <AzCore/EBus/EBus.h> #include <AzCore/Component/TickBus.h> #include <FileTransferSupport/FileTransferSupport.h> #include <DynamicContent/DynamicContentBus.h> #include <PresignedURL/PresignedURLBus.h> #include <chrono> namespace CloudCanvas { namespace DynamicContent { const int fileDownloadRetryMax = 1; // How many times will we retry the download for a file const char presignedUrlLifeTimeKey[] = "X-Amz-Expires"; const char cloudfrontPresignedUrlLifeTimeKey[] = "Expires"; struct DynamicContentRequest { AZStd::string WriteFile; AZStd::string VersionId; }; using DynamicContentRequestMap = AZStd::unordered_map<AZStd::string, DynamicContentRequest>; class DynamicContentTransferManager : public DynamicContentRequestBus::Handler, public AZ::TickBus::Handler, public AZ::Component, public PresignedURLResultBus::Handler { public: AZ_COMPONENT(DynamicContentTransferManager, "{3B15EBE9-3F45-41BF-A94D-9C1E079D30A2}"); using DynamicFileInfoPtr = AZStd::shared_ptr<DynamicContentFileInfo>; using SignatureHashVec = AZStd::vector<unsigned char>; DynamicContentTransferManager(); virtual ~DynamicContentTransferManager(); static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); static void Reflect(AZ::ReflectContext* reflection); ////////////////////////////////////////////////////////////////////////// // AZ::Component void Init() override; void Activate() override; void Deactivate() override; // Locally we save our requests as a spaced (illegal in bucket/keys) bucket key string static AZStd::string GetRequestString(const AZStd::string& bucketName, const AZStd::string& keyName); // DynamicContentBus handlers virtual bool RequestManifest(const char* manifestName) override; virtual bool RequestVersionedManifest(const char* manifestName, const char* versionId = "") override; // Convenience call for a single request virtual bool RequestFileStatus(const char* fileName, const char* outputFile) override; virtual bool RequestVersionedFileStatus(const char* fileName, const char* writeFile, const char* versionId = "") override; virtual bool UpdateFileStatusList(const AZStd::vector<AZStd::string>& uploadRequests, bool autoDownload = false) override; virtual bool UpdateVersionedFileStatusList(const AZStd::unordered_map<AZStd::string, AZStd::string>& requestMap, bool autoDownload = false) override; virtual bool UpdateFileStatus(const char* fileName, bool autoDownload = false) override; virtual bool UpdateVersionedFileStatus(const char* fileName, bool autoDownload = false, const char* versionId = "") override;; virtual bool RequestDownload(const AZStd::string& fileName, bool forceDownload) override; bool RequestUrlExpired(const AZStd::string& fileName); // Clear (And unmount) all pak records from Dynamic Content virtual bool ClearAllContent() override; // Remove (and unmount) a single pak record virtual bool RemovePak(const char* fileName) override; // Attempt to load an existing manifest on disk along with all content it describes // This allows you to load up dynamic content which has previously been downloading without checking // for updates or making any calls to aws virtual bool LoadManifest(const AZStd::string& manifestName) override; virtual bool IsUpdated(const char* fileName) override; // Load a specific pak into the dynamic content system virtual bool LoadPak(const AZStd::string& manifestName) override; // Are there any requests still left in the UPDATING state virtual bool HasUpdatingRequests() override; // Delete a specific pak from the dynamic content system virtual bool DeletePak(const AZStd::string& manifestName) override; // Delete Downloaded Paks in the Pak folder from disk virtual bool DeleteDownloadedContent() override; // Get the list of Paks the Dynamic Content system is aware of which the user may request // These are entries which were discovered in the manifest data that has been loaded labeled "userRequested" // Which aren't currently found on disk or in flight virtual AZStd::vector<AZStd::string> GetDownloadablePaks() override; virtual int GetPakStatus(const char* fileName) override; virtual AZStd::string GetPakStatusString(const char* fileName) override; // Handle a JSON status update string // Intended for messages from CloudGemWebCommunicator virtual void HandleWebCommunicatorUpdate(const AZStd::string& messageData) override; bool RequestFileStatus(const char* fileName, const char* writeFile, bool manifestRequest, const char* versionId = ""); bool RequestFileStatus(DynamicContentRequestMap& requestVec, bool manifestRequest); virtual void GotPresignedURLResult(const AZStd::string& fileRequest, int responseCode, const AZStd::string& resultString, const AZStd::string& outputFile) override; void OnDownloadSuccess(DynamicFileInfoPtr requestPtr); void OnDownloadFailure(DynamicFileInfoPtr requestPtr); DynamicFileInfoPtr GetLocalEntryFromBucketKey(const char* bucketKey); void SetFileInfo(DynamicFileInfoPtr fileInfo); DynamicFileInfoPtr GetFileInfo(const char* localFileName) const; void ManifestUpdated(const AZStd::string& manifestPath, const AZStd::string& bucketName); static AZStd::string GetDefaultWriteFolderAlias(); static AZStd::string GetUserManifestFolder(); static AZStd::string GetUserPakFolder(); static AZStd::string GetBasePakFolder(); static AZStd::string GetContentRequestFunction(); static bool IsManifestPak(const AZStd::string& fileRequest); static AZStd::string GetPakNameForManifest(const AZStd::string& fileRequest); static AZStd::string GetManifestNameForPak(const AZStd::string& fileRequest); static SignatureHashVec GetMD5Hash(const AZStd::string& filePath); static size_t GetDecodedSize(const AZStd::string& base64String); protected: ////////////////////////////////////////////////////////////////////////// // TickBus void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; ////////////////////////////////////////////////////////////////////////// private: DynamicContentTransferManager(const DynamicContentTransferManager&) = delete; bool ShouldLoadManifestEntry(const rapidjson::Value& thisFileEntry) const; void ParseManifestFileList(const rapidjson::Value& docFileList, const AZStd::string& manifestPath); void UpdateManifest(const AZStd::string& manifestName, const AZStd::string& outputFile, const char* versionId = ""); bool LoadManifestData(const AZStd::string& manifestPath); int CheckFileList(const char* bucketName); bool IsManifestEntry(const rapidjson::Value& thisFileEntry) const; bool IsPakPending(const rapidjson::Value& thisFileEntry) const; AZStd::string GetPakPath(const rapidjson::Value& thisFileEntry) const; DynamicFileInfoPtr GetPakEntry(const rapidjson::Value& thisFileEntry) const; // To reduce the number of lookups this condenses the above 3 calls into one call - If this entry is from a pak which is currently pending, return the pak's // entry DynamicContentTransferManager::DynamicFileInfoPtr GetPendingPakEntry(const rapidjson::Value& thisFileEntry) const; AZStd::string GetDownloadablePakVersionId(const AZStd::string& fileName) const; // Hold onto the request while it's out void AddPresignedURLRequest(const AZStd::string& requestURL, DynamicFileInfoPtr fileInfo); // We only need to hold onto this as long as the request is out - when it's back just clear and hand us back the record in one step DynamicFileInfoPtr GetAndRemovePresignedRequest(const AZStd::string& requestURL); bool CanRequestFile(DynamicFileInfoPtr) const; void AppendPakInfo(const rapidjson::Value& docFileList); void AddPendingPak(DynamicFileInfoPtr pakInfo); void RemovePendingPak(DynamicFileInfoPtr pakInfo); void UpdatePakFilesToMount(); void SetPakReady(DynamicFileInfoPtr pakInfo); void NewContentReady(DynamicFileInfoPtr pakInfo); void CheckPendingManifests(DynamicFileInfoPtr fileInfo); void OnFileStatusFailed(DynamicFileInfoPtr pakInfo); void RequestsCompleted(); // Performed on the TickBus update or called when attempting to immediately update void CheckUpdates(); // Check if this is a user requested file, if so: // If the file is already local, performs a state updated to Initialized and returns false // If the file is not local, returns true // If not userRequested returns false bool WaitsForUserRequest(DynamicFileInfoPtr pakInfo); AZStd::string GetDefaultPublicKeyPath() const; int ValidateSignature(DynamicFileInfoPtr pakInfo) const; int ValidateSignatureOpenSSL(const AZStd::string& checkString, const AZStd::vector<unsigned char>& signatureBuf) const; int ValidateEncodedSignature(const AZStd::string& checkString, const AZStd::string& signatureString) const; mutable AZStd::mutex m_completedDownloadMutex; mutable AZStd::recursive_mutex m_fileListMutex; mutable AZStd::mutex m_pakFileMountMutex; AZStd::mutex m_presignedURLMutex; AZStd::unordered_map<AZStd::string, DynamicFileInfoPtr> m_fileList; AZStd::deque<DynamicFileInfoPtr> m_pakFilesToMount; // For some cases - particularly our upload/list routine - we want to hash by the data we expect in our bucket // to more quickly process our listObjects return AZStd::unordered_map<AZStd::string, DynamicFileInfoPtr> m_bucketKeyToFileInfo; AZStd::unordered_map<AZStd::string, DynamicFileInfoPtr> m_presignedURLToFileInfo; AZStd::unordered_map<AZStd::string, int> m_bucketKeyToDownloadRetryCount; static AZ::EntityId m_moduleEntity; }; } }