diff options
Diffstat (limited to 'dom/fs/parent/datamodel/FileSystemFileManager.cpp')
-rw-r--r-- | dom/fs/parent/datamodel/FileSystemFileManager.cpp | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/dom/fs/parent/datamodel/FileSystemFileManager.cpp b/dom/fs/parent/datamodel/FileSystemFileManager.cpp new file mode 100644 index 0000000000..c43ee67ae5 --- /dev/null +++ b/dom/fs/parent/datamodel/FileSystemFileManager.cpp @@ -0,0 +1,358 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemFileManager.h" + +#include "FileSystemDataManager.h" +#include "FileSystemHashSource.h" +#include "mozilla/Assertions.h" +#include "mozilla/NotNull.h" +#include "mozilla/Result.h" +#include "mozilla/ResultVariant.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsIFile.h" +#include "nsIFileProtocolHandler.h" +#include "nsIFileURL.h" +#include "nsIURIMutator.h" +#include "nsTHashMap.h" +#include "nsXPCOM.h" + +namespace mozilla::dom::fs::data { + +namespace { + +constexpr nsLiteralString kDatabaseFileName = u"metadata.sqlite"_ns; + +Result<nsCOMPtr<nsIFile>, QMResult> GetFileDestination( + const nsCOMPtr<nsIFile>& aTopDirectory, const EntryId& aEntryId) { + MOZ_ASSERT(32u == aEntryId.Length()); + + nsCOMPtr<nsIFile> destination; + + // nsIFile Clone is not a constant method + QM_TRY(QM_TO_RESULT(aTopDirectory->Clone(getter_AddRefs(destination)))); + + QM_TRY_UNWRAP(Name encoded, FileSystemHashSource::EncodeHash(aEntryId)); + + MOZ_ALWAYS_TRUE(IsAscii(encoded)); + + nsString relativePath; + relativePath.Append(Substring(encoded, 0, 2)); + + QM_TRY(QM_TO_RESULT(destination->AppendRelativePath(relativePath))); + + QM_TRY(QM_TO_RESULT(destination->AppendRelativePath(encoded))); + + return destination; +} + +Result<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFileImpl( + const nsAString& aFilePath) { + MOZ_ASSERT(!aFilePath.IsEmpty()); + + nsCOMPtr<nsIFile> result; + QM_TRY(QM_TO_RESULT(NS_NewLocalFile(aFilePath, + /* aFollowLinks */ false, + getter_AddRefs(result)))); + + bool exists = true; + QM_TRY(QM_TO_RESULT(result->Exists(&exists))); + + if (!exists) { + QM_TRY(QM_TO_RESULT(result->Create(nsIFile::NORMAL_FILE_TYPE, 0644))); + + return result; + } + + bool isDirectory = true; + QM_TRY(QM_TO_RESULT(result->IsDirectory(&isDirectory))); + QM_TRY(OkIf(!isDirectory), Err(QMResult(NS_ERROR_FILE_IS_DIRECTORY))); + + return result; +} + +Result<nsCOMPtr<nsIFile>, QMResult> GetFile( + const nsCOMPtr<nsIFile>& aTopDirectory, const EntryId& aEntryId) { + MOZ_ASSERT(!aEntryId.IsEmpty()); + + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject, + GetFileDestination(aTopDirectory, aEntryId)); + + nsString desiredPath; + QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath))); + + nsCOMPtr<nsIFile> result; + QM_TRY(QM_TO_RESULT(NS_NewLocalFile(desiredPath, + /* aFollowLinks */ false, + getter_AddRefs(result)))); + + return result; +} + +Result<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFile( + const nsCOMPtr<nsIFile>& aTopDirectory, const EntryId& aEntryId) { + MOZ_ASSERT(!aEntryId.IsEmpty()); + + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject, + GetFileDestination(aTopDirectory, aEntryId)); + + nsString desiredPath; + QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath))); + + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> result, GetOrCreateFileImpl(desiredPath)); + + return result; +} + +nsresult RemoveFileObject(const nsCOMPtr<nsIFile>& aFilePtr) { + // If we cannot tell whether the object is file or directory, or it is a + // directory, it is abandoned as an unknown object. If an attempt is made to + // create a new object with the same path on disk, we regenerate the entryId + // until the collision is resolved. + + bool isFile = false; + QM_TRY(MOZ_TO_RESULT(aFilePtr->IsFile(&isFile))); + + QM_TRY(OkIf(isFile), NS_ERROR_FILE_IS_DIRECTORY); + + QM_TRY(QM_TO_RESULT(aFilePtr->Remove(/* recursive */ false))); + + return NS_OK; +} + +#ifdef DEBUG +// Unused in release builds +Result<Usage, QMResult> GetFileSize(const nsCOMPtr<nsIFile>& aFileObject) { + bool exists = false; + QM_TRY(QM_TO_RESULT(aFileObject->Exists(&exists))); + + if (!exists) { + return 0; + } + + bool isFile = false; + QM_TRY(QM_TO_RESULT(aFileObject->IsFile(&isFile))); + + // We never create directories with this path: this is an unknown object + // and the file does not exist + QM_TRY(OkIf(isFile), 0); + + QM_TRY_UNWRAP(Usage fileSize, + QM_TO_RESULT_INVOKE_MEMBER(aFileObject, GetFileSize)); + + return fileSize; +} +#endif + +} // namespace + +Result<nsCOMPtr<nsIFile>, QMResult> GetFileSystemDirectory( + const quota::OriginMetadata& aOriginMetadata) { + MOZ_ASSERT(aOriginMetadata.mPersistenceType == + quota::PERSISTENCE_TYPE_DEFAULT); + + quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> fileSystemDirectory, + QM_TO_RESULT_TRANSFORM( + quotaManager->GetOriginDirectory(aOriginMetadata))); + + QM_TRY(QM_TO_RESULT(fileSystemDirectory->AppendRelativePath( + NS_LITERAL_STRING_FROM_CSTRING(FILESYSTEM_DIRECTORY_NAME)))); + + return fileSystemDirectory; +} + +nsresult EnsureFileSystemDirectory( + const quota::OriginMetadata& aOriginMetadata) { + quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureStorageIsInitialized())); + + QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureTemporaryStorageIsInitialized())); + + QM_TRY_INSPECT(const auto& fileSystemDirectory, + quotaManager + ->EnsureTemporaryOriginIsInitialized( + quota::PERSISTENCE_TYPE_DEFAULT, aOriginMetadata) + .map([](const auto& aPair) { return aPair.first; })); + + QM_TRY(QM_TO_RESULT(fileSystemDirectory->AppendRelativePath( + NS_LITERAL_STRING_FROM_CSTRING(FILESYSTEM_DIRECTORY_NAME)))); + + bool exists = true; + QM_TRY(QM_TO_RESULT(fileSystemDirectory->Exists(&exists))); + + if (!exists) { + QM_TRY(QM_TO_RESULT( + fileSystemDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755))); + + return NS_OK; + } + + bool isDirectory = true; + QM_TRY(QM_TO_RESULT(fileSystemDirectory->IsDirectory(&isDirectory))); + QM_TRY(OkIf(isDirectory), NS_ERROR_FILE_NOT_DIRECTORY); + + return NS_OK; +} + +Result<nsCOMPtr<nsIFile>, QMResult> GetDatabaseFile( + const quota::OriginMetadata& aOriginMetadata) { + MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty()); + + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> databaseFile, + GetFileSystemDirectory(aOriginMetadata)); + + QM_TRY(QM_TO_RESULT(databaseFile->AppendRelativePath(kDatabaseFileName))); + + return databaseFile; +} + +/** + * TODO: This is almost identical to the corresponding function of IndexedDB + */ +Result<nsCOMPtr<nsIFileURL>, QMResult> GetDatabaseFileURL( + const quota::OriginMetadata& aOriginMetadata, + const int64_t aDirectoryLockId) { + MOZ_ASSERT(aDirectoryLockId >= 0); + + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> databaseFile, + GetDatabaseFile(aOriginMetadata)); + + QM_TRY_INSPECT( + const auto& protocolHandler, + QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED( + nsCOMPtr<nsIProtocolHandler>, MOZ_SELECT_OVERLOAD(do_GetService), + NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file"))); + + QM_TRY_INSPECT(const auto& fileHandler, + QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED( + nsCOMPtr<nsIFileProtocolHandler>, + MOZ_SELECT_OVERLOAD(do_QueryInterface), protocolHandler))); + + QM_TRY_INSPECT(const auto& mutator, + QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( + nsCOMPtr<nsIURIMutator>, fileHandler, NewFileURIMutator, + databaseFile))); + + nsCString directoryLockIdClause = "&directoryLockId="_ns; + directoryLockIdClause.AppendInt(aDirectoryLockId); + + nsCOMPtr<nsIFileURL> result; + QM_TRY(QM_TO_RESULT( + NS_MutateURI(mutator).SetQuery(directoryLockIdClause).Finalize(result))); + + return result; +} + +/* static */ +Result<FileSystemFileManager, QMResult> +FileSystemFileManager::CreateFileSystemFileManager( + nsCOMPtr<nsIFile>&& topDirectory) { + return FileSystemFileManager(std::move(topDirectory)); +} + +/* static */ +Result<FileSystemFileManager, QMResult> +FileSystemFileManager::CreateFileSystemFileManager( + const quota::OriginMetadata& aOriginMetadata) { + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> topDirectory, + GetFileSystemDirectory(aOriginMetadata)); + + return FileSystemFileManager(std::move(topDirectory)); +} + +FileSystemFileManager::FileSystemFileManager(nsCOMPtr<nsIFile>&& aTopDirectory) + : mTopDirectory(std::move(aTopDirectory)) {} + +Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::GetFile( + const EntryId& aEntryId) const { + return data::GetFile(mTopDirectory, aEntryId); +} + +Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::GetOrCreateFile( + const EntryId& aEntryId) { + return data::GetOrCreateFile(mTopDirectory, aEntryId); +} + +Result<Usage, QMResult> FileSystemFileManager::RemoveFile( + const EntryId& aEntryId) { + MOZ_ASSERT(!aEntryId.IsEmpty()); + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject, + GetFileDestination(mTopDirectory, aEntryId)); + + bool exists = false; + QM_TRY(QM_TO_RESULT(pathObject->Exists(&exists))); + + if (!exists) { + return 0; + } + + bool isFile = false; + QM_TRY(QM_TO_RESULT(pathObject->IsFile(&isFile))); + + // We could handle this also as a nonexistent file. + if (!isFile) { + return Err(QMResult(NS_ERROR_FILE_IS_DIRECTORY)); + } + + Usage totalUsage = 0; +#ifdef DEBUG + QM_TRY_UNWRAP(totalUsage, + QM_TO_RESULT_INVOKE_MEMBER(pathObject, GetFileSize)); +#endif + + QM_TRY(QM_TO_RESULT(pathObject->Remove(/* recursive */ false))); + + return totalUsage; +} + +Result<DebugOnly<Usage>, QMResult> FileSystemFileManager::RemoveFiles( + const nsTArray<EntryId>& aEntryIds, nsTArray<EntryId>& aRemoveFails) { + if (aEntryIds.IsEmpty()) { + return DebugOnly<Usage>(0); + } + + CheckedInt64 totalUsage = 0; + for (const auto& entryId : aEntryIds) { + QM_WARNONLY_TRY_UNWRAP(Maybe<nsCOMPtr<nsIFile>> maybeFile, + GetFileDestination(mTopDirectory, entryId)); + if (!maybeFile) { + aRemoveFails.AppendElement(entryId); + continue; + } + nsCOMPtr<nsIFile> fileObject = maybeFile.value(); + +// Size recorded at close is checked to be equal to the sum of sizes on disk +#ifdef DEBUG + QM_WARNONLY_TRY_UNWRAP(Maybe<Usage> fileSize, GetFileSize(fileObject)); + if (!fileSize) { + aRemoveFails.AppendElement(entryId); + continue; + } + totalUsage += fileSize.value(); +#endif + + QM_WARNONLY_TRY_UNWRAP(Maybe<Ok> ok, + MOZ_TO_RESULT(RemoveFileObject(fileObject))); + if (!ok) { + aRemoveFails.AppendElement(entryId); + } + } + + MOZ_ASSERT(totalUsage.isValid()); + + return DebugOnly<Usage>(totalUsage.value()); +} + +} // namespace mozilla::dom::fs::data |