Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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()));
int64_t fileSize = 0;
QM_TRY(QM_TO_RESULT(aFile->GetFileSize(&fileSize)));
UpdateCachedQuotaUsage(aFileId, 0, fileSize);
} 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