summaryrefslogtreecommitdiffstats
path: root/dom/fs/parent/datamodel/FileSystemFileManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/fs/parent/datamodel/FileSystemFileManager.cpp')
-rw-r--r--dom/fs/parent/datamodel/FileSystemFileManager.cpp358
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