diff options
Diffstat (limited to 'dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion002.cpp')
-rw-r--r-- | dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion002.cpp | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion002.cpp b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion002.cpp new file mode 100644 index 0000000000..3543346ff0 --- /dev/null +++ b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion002.cpp @@ -0,0 +1,832 @@ +/* -*- 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 "FileSystemDatabaseManagerVersion002.h" + +#include "ErrorList.h" +#include "FileSystemContentTypeGuess.h" +#include "FileSystemDataManager.h" +#include "FileSystemFileManager.h" +#include "FileSystemHashSource.h" +#include "FileSystemHashStorageFunction.h" +#include "FileSystemParentTypes.h" +#include "ResultStatement.h" +#include "StartedTransaction.h" +#include "mozStorageHelper.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/dom/FileSystemDataManager.h" +#include "mozilla/dom/FileSystemHandle.h" +#include "mozilla/dom/FileSystemLog.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/PFileSystemManager.h" +#include "mozilla/dom/QMResult.h" +#include "mozilla/dom/quota/Client.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/QuotaObject.h" +#include "mozilla/dom/quota/ResultExtensions.h" + +namespace mozilla::dom::fs::data { + +namespace { + +Result<FileId, QMResult> GetFileId002(const FileSystemConnection& aConnection, + const EntryId& aEntryId) { + const nsLiteralCString fileIdQuery = + "SELECT fileId FROM MainFiles WHERE handle = :entryId ;"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, fileIdQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId))); + QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); + + if (!moreResults) { + return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); + } + + QM_TRY_INSPECT(const FileId& fileId, stmt.GetFileIdByColumn(/* Column */ 0u)); + + return fileId; +} + +Result<bool, QMResult> DoesFileIdExist(const FileSystemConnection& aConnection, + const FileId& aFileId) { + MOZ_ASSERT(!aFileId.IsEmpty()); + + const nsLiteralCString existsQuery = + "SELECT EXISTS " + "(SELECT 1 FROM FileIds WHERE fileId = :handle ) " + ";"_ns; + + QM_TRY_RETURN( + ApplyEntryExistsQuery(aConnection, existsQuery, aFileId.Value())); +} + +nsresult RehashFile(const FileSystemConnection& aConnection, + const EntryId& aEntryId, + const FileSystemChildMetadata& aNewDesignation, + const ContentType& aNewType) { + QM_TRY_INSPECT(const EntryId& newId, + FileSystemHashSource::GenerateHash( + aNewDesignation.parentId(), aNewDesignation.childName())); + + // The destination should be empty at this point: either we exited because + // overwrite was not desired, or the existing content was removed. + const nsLiteralCString insertNewEntryQuery = + "INSERT INTO Entries ( handle, parent ) " + "VALUES ( :newId, :newParent ) " + ";"_ns; + + const nsLiteralCString insertNewFileAndTypeQuery = + "INSERT INTO Files ( handle, type, name ) " + "VALUES ( :newId, :type, :newName ) " + ";"_ns; + + const nsLiteralCString insertNewFileKeepTypeQuery = + "INSERT INTO Files ( handle, type, name ) " + "SELECT :newId, type, :newName FROM Files " + "WHERE handle = :oldId ;"_ns; + + const auto& insertNewFileQuery = aNewType.IsVoid() + ? insertNewFileKeepTypeQuery + : insertNewFileAndTypeQuery; + + const nsLiteralCString updateFileMappingsQuery = + "UPDATE FileIds SET handle = :newId WHERE handle = :handle ;"_ns; + + const nsLiteralCString updateMainFilesQuery = + "UPDATE MainFiles SET handle = :newId WHERE handle = :handle ;"_ns; + + const nsLiteralCString cleanupOldEntryQuery = + "DELETE FROM Entries WHERE handle = :handle ;"_ns; + + QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConnection)); + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, insertNewEntryQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId))); + QM_TRY(QM_TO_RESULT( + stmt.BindEntryIdByName("newParent"_ns, aNewDesignation.parentId()))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, insertNewFileQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId))); + if (aNewType.IsVoid()) { + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("oldId"_ns, aEntryId))); + } else { + QM_TRY(QM_TO_RESULT(stmt.BindContentTypeByName("type"_ns, aNewType))); + } + QM_TRY(QM_TO_RESULT( + stmt.BindNameByName("newName"_ns, aNewDesignation.childName()))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP( + ResultStatement stmt, + ResultStatement::Create(aConnection, updateFileMappingsQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId))); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, updateMainFilesQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId))); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, cleanupOldEntryQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + QM_TRY(QM_TO_RESULT(transaction.Commit())); + + return NS_OK; +} + +nsresult RehashDirectory(const FileSystemConnection& aConnection, + const EntryId& aEntryId, + const FileSystemChildMetadata& aNewDesignation) { + // This name won't match up with the entryId for the old path but + // it will be removed at the end + const nsLiteralCString updateNameQuery = + "UPDATE Directories SET name = :newName WHERE handle = :handle " + "; "_ns; + + const nsLiteralCString calculateHashesQuery = + "CREATE TEMPORARY TABLE ParentChildHash AS " + "WITH RECURSIVE " + "rehashMap( depth, isFile, handle, parent, name, hash ) AS ( " + "SELECT 0, isFile, handle, parent, name, hashEntry( :newParent, name ) " + "FROM EntryNames WHERE handle = :handle UNION SELECT " + "1 + depth, EntryNames.isFile, EntryNames.handle, EntryNames.parent, " + "EntryNames.name, hashEntry( rehashMap.hash, EntryNames.name ) " + "FROM rehashMap, EntryNames WHERE rehashMap.handle = EntryNames.parent ) " + "SELECT depth, isFile, handle, parent, name, hash FROM rehashMap " + ";"_ns; + + const nsLiteralCString createIndexByDepthQuery = + "CREATE INDEX indexOnDepth ON ParentChildHash ( depth ); "_ns; + + // To avoid constraint violation, we insert new entries under the old parent. + // The destination should be empty at this point: either we exited because + // overwrite was not desired, or the existing content was removed. + const nsLiteralCString insertNewEntriesQuery = + "INSERT INTO Entries ( handle, parent ) " + "SELECT hash, :parent FROM ParentChildHash " + ";"_ns; + + const nsLiteralCString insertNewDirectoriesQuery = + "INSERT INTO Directories ( handle, name ) " + "SELECT hash, name FROM ParentChildHash WHERE isFile = 0 " + "ORDER BY depth " + ";"_ns; + + const nsLiteralCString insertNewFilesQuery = + "INSERT INTO Files ( handle, type, name ) " + "SELECT ParentChildHash.hash, Files.type, ParentChildHash.name " + "FROM ParentChildHash INNER JOIN Files USING (handle) " + "WHERE ParentChildHash.isFile = 1 " + ";"_ns; + + const nsLiteralCString updateFileMappingsQuery = + "UPDATE FileIds SET handle = hash " + "FROM ( SELECT handle, hash FROM ParentChildHash ) AS replacement " + "WHERE FileIds.handle = replacement.handle " + ";"_ns; + + const nsLiteralCString updateMainFilesQuery = + "UPDATE MainFiles SET handle = hash " + "FROM ( SELECT handle, hash FROM ParentChildHash ) AS replacement " + "WHERE MainFiles.handle = replacement.handle " + ";"_ns; + + // Now fix the parents + const nsLiteralCString updateEntryMappingsQuery = + "UPDATE Entries SET parent = hash " + "FROM ( SELECT Lhs.hash AS handle, Rhs.hash AS hash, Lhs.depth AS depth " + "FROM ParentChildHash AS Lhs " + "INNER JOIN ParentChildHash AS Rhs " + "ON Rhs.handle = Lhs.parent ORDER BY depth ) AS replacement " + "WHERE Entries.handle = replacement.handle " + ";"_ns; + + const nsLiteralCString cleanupOldEntriesQuery = + "DELETE FROM Entries WHERE handle = :handle " + ";"_ns; + + // Index is automatically deleted + const nsLiteralCString cleanupTemporaries = + "DROP TABLE ParentChildHash " + ";"_ns; + + nsCOMPtr<mozIStorageFunction> rehashFunction = + new data::FileSystemHashStorageFunction(); + QM_TRY(MOZ_TO_RESULT(aConnection->CreateFunction("hashEntry"_ns, + /* number of arguments */ 2, + rehashFunction))); + auto finallyRemoveFunction = MakeScopeExit([&aConnection]() { + QM_WARNONLY_TRY(MOZ_TO_RESULT(aConnection->RemoveFunction("hashEntry"_ns))); + }); + + QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConnection)); + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, updateNameQuery)); + QM_TRY(QM_TO_RESULT( + stmt.BindNameByName("newName"_ns, aNewDesignation.childName()))); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, calculateHashesQuery)); + QM_TRY(QM_TO_RESULT( + stmt.BindEntryIdByName("newParent"_ns, aNewDesignation.parentId()))); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + QM_TRY(QM_TO_RESULT(aConnection->ExecuteSimpleSQL(createIndexByDepthQuery))); + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, insertNewEntriesQuery)); + QM_TRY(QM_TO_RESULT( + stmt.BindEntryIdByName("parent"_ns, aNewDesignation.parentId()))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP( + ResultStatement stmt, + ResultStatement::Create(aConnection, insertNewDirectoriesQuery)); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, insertNewFilesQuery)); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP( + ResultStatement stmt, + ResultStatement::Create(aConnection, updateFileMappingsQuery)); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, updateMainFilesQuery)); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP( + ResultStatement stmt, + ResultStatement::Create(aConnection, updateEntryMappingsQuery)); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, cleanupOldEntriesQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, cleanupTemporaries)); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + QM_TRY(QM_TO_RESULT(transaction.Commit())); + + return NS_OK; +} + +/** + * @brief Each entryId is interpreted as a large integer, which is increased + * until an unused value is found. This process is in principle infallible. + * The files associated with a given path will form a cluster next to the + * entryId which could be used for recovery because our hash function is + * expected to distribute all clusters far from each other. + */ +Result<FileId, QMResult> GetNextFreeFileId( + const FileSystemConnection& aConnection, + const FileSystemFileManager& aFileManager, const EntryId& aEntryId) { + MOZ_ASSERT(32u == aEntryId.Length()); + + auto DoesExist = [&aConnection, &aFileManager]( + const FileId& aId) -> Result<bool, QMResult> { + QM_TRY_INSPECT(const nsCOMPtr<nsIFile>& diskFile, + aFileManager.GetFile(aId)); + + bool result = true; + QM_TRY(QM_TO_RESULT(diskFile->Exists(&result))); + if (result) { + return true; + } + + QM_TRY_RETURN(DoesFileIdExist(aConnection, aId)); + }; + + auto Next = [](FileId& aId) { + // Using a larger integer would make fileIds depend on platform endianness. + using IntegerType = uint8_t; + constexpr int32_t bufferSize = 32 / sizeof(IntegerType); + using IdBuffer = std::array<IntegerType, bufferSize>; + + auto Increase = [](IdBuffer& aIn) { + for (int i = 0; i < bufferSize; ++i) { + if (1u + aIn[i] != 0u) { + ++aIn[i]; + return; + } + aIn[i] = 0u; + } + }; + + DebugOnly<nsCString> original = aId.Value(); + Increase(*reinterpret_cast<IdBuffer*>(aId.mValue.BeginWriting())); + MOZ_ASSERT(!aId.Value().Equals(original)); + }; + + FileId id = FileId(aEntryId); + + while (true) { + QM_WARNONLY_TRY_UNWRAP(Maybe<bool> maybeExists, DoesExist(id)); + if (maybeExists.isSome() && !maybeExists.value()) { + return id; + } + + Next(id); + } +} + +Result<FileId, QMResult> AddNewFileId(const FileSystemConnection& aConnection, + const FileSystemFileManager& aFileManager, + const EntryId& aEntryId) { + QM_TRY_INSPECT(const FileId& nextFreeId, + GetNextFreeFileId(aConnection, aFileManager, aEntryId)); + + const nsLiteralCString insertNewFileIdQuery = + "INSERT INTO FileIds ( fileId, handle ) " + "VALUES ( :fileId, :entryId ) " + "; "_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, insertNewFileIdQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, nextFreeId))); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId))); + + QM_TRY(QM_TO_RESULT(stmt.Execute())); + + return nextFreeId; +} + +/** + * @brief Get recorded usage or zero if nothing was ever written to the file. + * Removing files is only allowed when there is no lock on the file, and their + * usage is either correctly recorded in the database during unlock, or nothing, + * or they remain in tracked state and the quota manager assumes their usage to + * be equal to the latest recorded value. In all cases, the latest recorded + * value (or nothing) is the correct amount of quota to be released. + */ +Result<Usage, QMResult> GetKnownUsage(const FileSystemConnection& aConnection, + const FileId& aFileId) { + const nsLiteralCString trackedUsageQuery = + "SELECT usage FROM Usages WHERE handle = :handle ;"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, trackedUsageQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId))); + + QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep()); + if (!moreResults) { + return 0; + } + + QM_TRY_RETURN(stmt.GetUsageByColumn(/* Column */ 0u)); +} + +} // namespace + +/* static */ +nsresult FileSystemDatabaseManagerVersion002::RescanTrackedUsages( + const FileSystemConnection& aConnection, + const quota::OriginMetadata& aOriginMetadata) { + return FileSystemDatabaseManagerVersion001::RescanTrackedUsages( + aConnection, aOriginMetadata); +} + +/* static */ +Result<Usage, QMResult> FileSystemDatabaseManagerVersion002::GetFileUsage( + const FileSystemConnection& aConnection) { + return FileSystemDatabaseManagerVersion001::GetFileUsage(aConnection); +} + +nsresult FileSystemDatabaseManagerVersion002::GetFile( + const EntryId& aEntryId, const FileId& aFileId, const FileMode& aMode, + ContentType& aType, TimeStamp& lastModifiedMilliSeconds, + nsTArray<Name>& aPath, nsCOMPtr<nsIFile>& aFile) const { + MOZ_ASSERT(!aFileId.IsEmpty()); + + const FileSystemEntryPair endPoints(mRootEntry, aEntryId); + QM_TRY_UNWRAP(aPath, ResolveReversedPath(mConnection, endPoints)); + if (aPath.IsEmpty()) { + return NS_ERROR_DOM_NOT_FOUND_ERR; + } + + QM_TRY(MOZ_TO_RESULT(GetFileAttributes(mConnection, aEntryId, aType))); + + if (aMode == FileMode::SHARED_FROM_COPY) { + QM_WARNONLY_TRY_UNWRAP(Maybe<FileId> mainFileId, GetFileId(aEntryId)); + if (mainFileId) { + QM_TRY_UNWRAP(aFile, + mFileManager->CreateFileFrom(aFileId, mainFileId.value())); + } else { + // LockShared/EnsureTemporaryFileId has provided a brand new fileId. + QM_TRY_UNWRAP(aFile, mFileManager->GetOrCreateFile(aFileId)); + } + } else { + MOZ_ASSERT(aMode == FileMode::EXCLUSIVE || + aMode == FileMode::SHARED_FROM_EMPTY); + + QM_TRY_UNWRAP(aFile, mFileManager->GetOrCreateFile(aFileId)); + } + + PRTime lastModTime = 0; + QM_TRY(MOZ_TO_RESULT(aFile->GetLastModifiedTime(&lastModTime))); + lastModifiedMilliSeconds = static_cast<TimeStamp>(lastModTime); + + aPath.Reverse(); + + return NS_OK; +} + +Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::RenameEntry( + const FileSystemEntryMetadata& aHandle, const Name& aNewName) { + MOZ_ASSERT(!aNewName.IsEmpty()); + + const auto& entryId = aHandle.entryId(); + MOZ_ASSERT(!entryId.IsEmpty()); + + // Can't rename root + if (mRootEntry == entryId) { + return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); + } + + // Verify the source exists + QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId), + Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR))); + + // Are we actually renaming? + if (aHandle.entryName() == aNewName) { + return entryId; + } + + QM_TRY(QM_TO_RESULT(PrepareRenameEntry(mConnection, mDataManager, aHandle, + aNewName, isFile))); + + QM_TRY_UNWRAP(EntryId parentId, FindParent(mConnection, entryId)); + FileSystemChildMetadata newDesignation(parentId, aNewName); + + if (isFile) { + const ContentType type = DetermineContentType(aNewName); + QM_TRY( + QM_TO_RESULT(RehashFile(mConnection, entryId, newDesignation, type))); + } else { + QM_TRY(QM_TO_RESULT(RehashDirectory(mConnection, entryId, newDesignation))); + } + + QM_TRY_UNWRAP(DebugOnly<EntryId> dbId, + FindEntryId(mConnection, newDesignation, isFile)); + QM_TRY_UNWRAP(EntryId generated, + FileSystemHashSource::GenerateHash(parentId, aNewName)); + MOZ_ASSERT(static_cast<EntryId&>(dbId).Equals(generated)); + + return generated; +} + +Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::MoveEntry( + const FileSystemEntryMetadata& aHandle, + const FileSystemChildMetadata& aNewDesignation) { + MOZ_ASSERT(!aHandle.entryId().IsEmpty()); + + const auto& entryId = aHandle.entryId(); + + if (mRootEntry == entryId) { + return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); + } + + // Verify the source exists + QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId), + Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR))); + + // If the rename doesn't change the name or directory, just return success. + // XXX Needs to be added to the spec + QM_WARNONLY_TRY_UNWRAP(Maybe<bool> maybeSame, + IsSame(mConnection, aHandle, aNewDesignation, isFile)); + if (maybeSame && maybeSame.value()) { + return entryId; + } + + QM_TRY(QM_TO_RESULT(PrepareMoveEntry(mConnection, mDataManager, aHandle, + aNewDesignation, isFile))); + + if (isFile) { + const ContentType type = DetermineContentType(aNewDesignation.childName()); + QM_TRY( + QM_TO_RESULT(RehashFile(mConnection, entryId, aNewDesignation, type))); + } else { + QM_TRY( + QM_TO_RESULT(RehashDirectory(mConnection, entryId, aNewDesignation))); + } + + QM_TRY_UNWRAP(DebugOnly<EntryId> dbId, + FindEntryId(mConnection, aNewDesignation, isFile)); + QM_TRY_UNWRAP(EntryId generated, + FileSystemHashSource::GenerateHash( + aNewDesignation.parentId(), aNewDesignation.childName())); + MOZ_ASSERT(static_cast<EntryId&>(dbId).Equals(generated)); + + return generated; +} + +Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::GetEntryId( + const FileSystemChildMetadata& aHandle) const { + return fs::data::GetEntryHandle(aHandle); +} + +Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::GetEntryId( + const FileId& aFileId) const { + const nsLiteralCString getEntryIdQuery = + "SELECT handle FROM FileIds WHERE fileId = :fileId ;"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, getEntryIdQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, aFileId))); + QM_TRY_UNWRAP(bool hasEntries, stmt.ExecuteStep()); + + if (!hasEntries || stmt.IsNullByColumn(/* Column */ 0u)) { + return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); + } + + QM_TRY_RETURN(stmt.GetEntryIdByColumn(/* Column */ 0u)); +} + +Result<FileId, QMResult> FileSystemDatabaseManagerVersion002::EnsureFileId( + const EntryId& aEntryId) { + QM_TRY_UNWRAP(bool exists, DoesFileExist(mConnection, aEntryId)); + if (!exists) { + return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); + } + + QM_TRY_UNWRAP(Maybe<FileId> maybeMainFileId, + QM_OR_ELSE_LOG_VERBOSE_IF( + // Expression. + GetFileId(aEntryId).map([](auto mainFileId) { + return Some(std::move(mainFileId)); + }), + // Predicate. + IsSpecificError<NS_ERROR_DOM_NOT_FOUND_ERR>, + // Fallback. + ([](const auto&) -> Result<Maybe<FileId>, QMResult> { + return Maybe<FileId>{}; + }))); + + if (maybeMainFileId) { + return *maybeMainFileId; + } + + QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection)); + + QM_TRY_INSPECT(const FileId& fileId, + AddNewFileId(mConnection, *mFileManager, aEntryId)); + + QM_TRY(QM_TO_RESULT(MergeFileId(aEntryId, fileId, /* aAbort */ false))); + + QM_TRY(QM_TO_RESULT(transaction.Commit())); + + return fileId; +} + +Result<FileId, QMResult> +FileSystemDatabaseManagerVersion002::EnsureTemporaryFileId( + const EntryId& aEntryId) { + QM_TRY_UNWRAP(bool exists, DoesFileExist(mConnection, aEntryId)); + if (!exists) { + return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); + } + + QM_TRY_RETURN(AddNewFileId(mConnection, *mFileManager, aEntryId)); +} + +Result<FileId, QMResult> FileSystemDatabaseManagerVersion002::GetFileId( + const EntryId& aEntryId) const { + MOZ_ASSERT(mConnection); + return data::GetFileId002(mConnection, aEntryId); +} + +nsresult FileSystemDatabaseManagerVersion002::MergeFileId( + const EntryId& aEntryId, const FileId& aFileId, bool aAbort) { + MOZ_ASSERT(mConnection); + + auto doCleanUp = [this](const FileId& aCleanable) -> nsresult { + // We need to clean up the old main file. + QM_TRY_UNWRAP(Usage usage, + GetKnownUsage(mConnection, aCleanable).mapErr(toNSResult)); + + QM_WARNONLY_TRY_UNWRAP(Maybe<Usage> removedUsage, + mFileManager->RemoveFile(aCleanable)); + + if (removedUsage) { + // Removal of file data was ok, update the related fileId and usage + QM_WARNONLY_TRY(QM_TO_RESULT(RemoveFileId(aCleanable))); + + if (usage > 0) { // Performance! + DecreaseCachedQuotaUsage(usage); + } + + // We only check the most common case. This can fail spuriously if an + // external application writes to the file, or OS reports zero size due to + // corruption. + MOZ_ASSERT_IF(0 == mFilesOfUnknownUsage, usage == removedUsage.value()); + + return NS_OK; + } + + // Removal failed + const nsLiteralCString forgetCleanable = + "UPDATE FileIds SET handle = NULL WHERE fileId = :fileId ; "_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, forgetCleanable) + .mapErr(toNSResult)); + QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, aCleanable))); + QM_TRY(MOZ_TO_RESULT(stmt.Execute())); + + TryRemoveDuringIdleMaintenance({aCleanable}); + + return NS_OK; + }; + + if (aAbort) { + QM_TRY(MOZ_TO_RESULT(doCleanUp(aFileId))); + + return NS_OK; + } + + QM_TRY_UNWRAP( + Maybe<FileId> maybeOldFileId, + QM_OR_ELSE_LOG_VERBOSE_IF( + // Expression. + GetFileId(aEntryId) + .map([](auto oldFileId) { return Some(std::move(oldFileId)); }) + .mapErr(toNSResult), + // Predicate. + IsSpecificError<NS_ERROR_DOM_NOT_FOUND_ERR>, + // Fallback. + ErrToDefaultOk<Maybe<FileId>>)); + + if (maybeOldFileId && *maybeOldFileId == aFileId) { + return NS_OK; // Nothing to do + } + + // Main file changed + const nsLiteralCString flagAsMainFileQuery = + "INSERT INTO MainFiles ( handle, fileId ) " + "VALUES ( :entryId, :fileId ) " + "ON CONFLICT (handle) " + "DO UPDATE SET fileId = excluded.fileId " + "; "_ns; + + QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection)); + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, flagAsMainFileQuery) + .mapErr(toNSResult)); + QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId))); + QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, aFileId))); + + QM_TRY(MOZ_TO_RESULT(stmt.Execute())); + + if (!maybeOldFileId) { + // We successfully added a new main file and there is nothing to clean up. + QM_TRY(MOZ_TO_RESULT(transaction.Commit())); + + return NS_OK; + } + + MOZ_ASSERT(maybeOldFileId); + MOZ_ASSERT(*maybeOldFileId != aFileId); + + QM_TRY(MOZ_TO_RESULT(doCleanUp(*maybeOldFileId))); + + // If the old fileId and usage were not deleted, main file update fails. + QM_TRY(MOZ_TO_RESULT(transaction.Commit())); + + return NS_OK; +} + +Result<bool, QMResult> FileSystemDatabaseManagerVersion002::DoesFileIdExist( + const FileId& aFileId) const { + QM_TRY_RETURN(data::DoesFileIdExist(mConnection, aFileId)); +} + +nsresult FileSystemDatabaseManagerVersion002::RemoveFileId( + const FileId& aFileId) { + const nsLiteralCString removeFileIdQuery = + "DELETE FROM FileIds " + "WHERE fileId = :fileId " + ";"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, removeFileIdQuery) + .mapErr(toNSResult)); + + QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("fileId"_ns, aFileId.Value()))); + + return stmt.Execute(); +} + +Result<Usage, QMResult> +FileSystemDatabaseManagerVersion002::GetUsagesOfDescendants( + const EntryId& aEntryId) const { + const nsLiteralCString descendantUsagesQuery = + "WITH RECURSIVE traceChildren(handle, parent) AS ( " + "SELECT handle, parent FROM Entries WHERE handle = :handle " + "UNION " + "SELECT Entries.handle, Entries.parent FROM traceChildren, Entries " + "WHERE traceChildren.handle=Entries.parent ) " + "SELECT sum(Usages.usage) " + "FROM traceChildren " + "INNER JOIN FileIds ON traceChildren.handle = FileIds.handle " + "INNER JOIN Usages ON Usages.handle = FileIds.fileId " + ";"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, descendantUsagesQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); + QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep()); + if (!moreResults) { + return 0; + } + + QM_TRY_RETURN(stmt.GetUsageByColumn(/* Column */ 0u)); +} + +Result<nsTArray<FileId>, QMResult> +FileSystemDatabaseManagerVersion002::FindFilesUnderEntry( + const EntryId& aEntryId) const { + const nsLiteralCString descendantsQuery = + "WITH RECURSIVE traceChildren(handle, parent) AS ( " + "SELECT handle, parent FROM Entries WHERE handle = :handle " + "UNION " + "SELECT Entries.handle, Entries.parent FROM traceChildren, Entries " + "WHERE traceChildren.handle = Entries.parent ) " + "SELECT FileIds.fileId " + "FROM traceChildren INNER JOIN FileIds USING (handle) " + ";"_ns; + + nsTArray<FileId> descendants; + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, descendantsQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); + + while (true) { + QM_TRY_INSPECT(const bool& moreResults, stmt.ExecuteStep()); + if (!moreResults) { + break; + } + + QM_TRY_INSPECT(const FileId& fileId, + stmt.GetFileIdByColumn(/* Column */ 0u)); + descendants.AppendElement(fileId); + } + } + + return descendants; +} + +} // namespace mozilla::dom::fs::data |