forked from mirrors/gecko-dev
1567 lines
52 KiB
C++
1567 lines
52 KiB
C++
/* -*- 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 "FileSystemDatabaseManagerVersion001.h"
|
|
|
|
#include "ErrorList.h"
|
|
#include "FileSystemContentTypeGuess.h"
|
|
#include "FileSystemDataManager.h"
|
|
#include "FileSystemFileManager.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/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"
|
|
#include "nsString.h"
|
|
|
|
namespace mozilla::dom {
|
|
|
|
using FileSystemEntries = nsTArray<fs::FileSystemEntryMetadata>;
|
|
|
|
namespace fs::data {
|
|
|
|
namespace {
|
|
|
|
constexpr const nsLiteralCString gDescendantsQuery =
|
|
"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 handle "
|
|
"FROM traceChildren INNER JOIN Files "
|
|
"USING(handle) "
|
|
";"_ns;
|
|
|
|
Result<bool, QMResult> IsDirectoryEmpty(const FileSystemConnection& mConnection,
|
|
const EntryId& aEntryId) {
|
|
const nsLiteralCString isDirEmptyQuery =
|
|
"SELECT EXISTS ("
|
|
"SELECT 1 FROM Entries WHERE parent = :parent "
|
|
");"_ns;
|
|
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(mConnection, isDirEmptyQuery));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aEntryId)));
|
|
QM_TRY_UNWRAP(bool childrenExist, stmt.YesOrNoQuery());
|
|
|
|
return !childrenExist;
|
|
}
|
|
|
|
Result<bool, QMResult> DoesDirectoryExist(
|
|
const FileSystemConnection& mConnection,
|
|
const FileSystemChildMetadata& aHandle) {
|
|
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
|
|
|
|
const nsCString existsQuery =
|
|
"SELECT EXISTS "
|
|
"(SELECT 1 FROM Directories INNER JOIN Entries USING (handle) "
|
|
"WHERE Directories.name = :name AND Entries.parent = :parent ) "
|
|
";"_ns;
|
|
|
|
QM_TRY_RETURN(ApplyEntryExistsQuery(mConnection, existsQuery, aHandle));
|
|
}
|
|
|
|
Result<bool, QMResult> DoesDirectoryExist(
|
|
const FileSystemConnection& mConnection, const EntryId& aEntry) {
|
|
MOZ_ASSERT(!aEntry.IsEmpty());
|
|
|
|
const nsCString existsQuery =
|
|
"SELECT EXISTS "
|
|
"(SELECT 1 FROM Directories WHERE handle = :handle ) "
|
|
";"_ns;
|
|
|
|
QM_TRY_RETURN(ApplyEntryExistsQuery(mConnection, existsQuery, aEntry));
|
|
}
|
|
|
|
Result<bool, QMResult> IsAncestor(const FileSystemConnection& aConnection,
|
|
const FileSystemEntryPair& aEndpoints) {
|
|
const nsCString pathQuery =
|
|
"WITH RECURSIVE followPath(handle, parent) AS ( "
|
|
"SELECT handle, parent "
|
|
"FROM Entries "
|
|
"WHERE handle=:entryId "
|
|
"UNION "
|
|
"SELECT Entries.handle, Entries.parent FROM followPath, Entries "
|
|
"WHERE followPath.parent=Entries.handle ) "
|
|
"SELECT EXISTS "
|
|
"(SELECT 1 FROM followPath "
|
|
"WHERE handle=:possibleAncestor ) "
|
|
";"_ns;
|
|
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, pathQuery));
|
|
QM_TRY(
|
|
QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEndpoints.childId())));
|
|
QM_TRY(QM_TO_RESULT(
|
|
stmt.BindEntryIdByName("possibleAncestor"_ns, aEndpoints.parentId())));
|
|
|
|
return stmt.YesOrNoQuery();
|
|
}
|
|
|
|
Result<bool, QMResult> DoesFileExist(const FileSystemConnection& aConnection,
|
|
const FileSystemChildMetadata& aHandle) {
|
|
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
|
|
|
|
const nsCString existsQuery =
|
|
"SELECT EXISTS "
|
|
"(SELECT 1 FROM Files INNER JOIN Entries USING (handle) "
|
|
"WHERE Files.name = :name AND Entries.parent = :parent ) "
|
|
";"_ns;
|
|
|
|
QM_TRY_RETURN(ApplyEntryExistsQuery(aConnection, existsQuery, aHandle));
|
|
}
|
|
|
|
nsresult GetEntries(const FileSystemConnection& aConnection,
|
|
const nsACString& aUnboundStmt, const EntryId& aParent,
|
|
PageNumber aPage, bool aDirectory,
|
|
FileSystemEntries& aEntries) {
|
|
// The entries inside a directory are sent to the child process in batches
|
|
// of pageSize items. Large value ensures that iteration is less often delayed
|
|
// by IPC messaging and querying the database.
|
|
// TODO: The current value 1024 is not optimized.
|
|
// TODO: Value "pageSize" is shared with the iterator implementation and
|
|
// should be defined in a common place.
|
|
const int32_t pageSize = 1024;
|
|
|
|
QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(aConnection, aParent));
|
|
if (!exists) {
|
|
return NS_ERROR_DOM_NOT_FOUND_ERR;
|
|
}
|
|
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, aUnboundStmt));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aParent)));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindPageNumberByName("pageSize"_ns, pageSize)));
|
|
QM_TRY(QM_TO_RESULT(
|
|
stmt.BindPageNumberByName("pageOffset"_ns, aPage * pageSize)));
|
|
|
|
QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
|
|
|
|
while (moreResults) {
|
|
QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u));
|
|
QM_TRY_UNWRAP(Name entryName, stmt.GetNameByColumn(/* Column */ 1u));
|
|
|
|
FileSystemEntryMetadata metadata(entryId, entryName, aDirectory);
|
|
aEntries.AppendElement(metadata);
|
|
|
|
QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
Result<EntryId, QMResult> GetUniqueEntryId(
|
|
const FileSystemConnection& aConnection,
|
|
const FileSystemChildMetadata& aHandle) {
|
|
const nsCString existsQuery =
|
|
"SELECT EXISTS "
|
|
"(SELECT 1 FROM Entries "
|
|
"WHERE handle = :handle )"
|
|
";"_ns;
|
|
|
|
FileSystemChildMetadata generatorInput = aHandle;
|
|
|
|
const size_t maxRounds = 1024u;
|
|
|
|
for (size_t hangGuard = 0u; hangGuard < maxRounds; ++hangGuard) {
|
|
QM_TRY_UNWRAP(EntryId entryId, fs::data::GetEntryHandle(generatorInput));
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, existsQuery));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
|
|
|
|
QM_TRY_UNWRAP(bool alreadyInUse, stmt.YesOrNoQuery());
|
|
|
|
if (!alreadyInUse) {
|
|
return entryId;
|
|
}
|
|
|
|
generatorInput.parentId() = entryId;
|
|
}
|
|
|
|
return Err(QMResult(NS_ERROR_UNEXPECTED));
|
|
}
|
|
|
|
nsresult PerformRename(const FileSystemConnection& aConnection,
|
|
const FileSystemEntryMetadata& aHandle,
|
|
const Name& aNewName, const ContentType& aNewType,
|
|
const nsLiteralCString& aNameUpdateQuery) {
|
|
MOZ_ASSERT(!aHandle.entryId().IsEmpty());
|
|
MOZ_ASSERT(IsValidName(aHandle.entryName()));
|
|
|
|
// same-name is checked in RenameEntry()
|
|
if (!IsValidName(aNewName)) {
|
|
return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
|
|
}
|
|
|
|
// TODO: This should fail when handle doesn't exist - the
|
|
// explicit file or directory existence queries are redundant
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, aNameUpdateQuery)
|
|
.mapErr(toNSResult));
|
|
if (!aNewType.IsVoid()) {
|
|
QM_TRY(MOZ_TO_RESULT(stmt.BindContentTypeByName("type"_ns, aNewType)));
|
|
}
|
|
QM_TRY(MOZ_TO_RESULT(stmt.BindNameByName("name"_ns, aNewName)));
|
|
QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aHandle.entryId())));
|
|
QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult PerformRenameDirectory(const FileSystemConnection& aConnection,
|
|
const FileSystemEntryMetadata& aHandle,
|
|
const Name& aNewName) {
|
|
const nsLiteralCString updateDirectoryNameQuery =
|
|
"UPDATE Directories "
|
|
"SET name = :name "
|
|
"WHERE handle = :handle "
|
|
";"_ns;
|
|
|
|
return PerformRename(aConnection, aHandle, aNewName, VoidCString(),
|
|
updateDirectoryNameQuery);
|
|
}
|
|
|
|
nsresult PerformRenameFile(const FileSystemConnection& aConnection,
|
|
const FileSystemEntryMetadata& aHandle,
|
|
const Name& aNewName, const ContentType& aNewType) {
|
|
const nsLiteralCString updateFileTypeAndNameQuery =
|
|
"UPDATE Files SET type = :type, name = :name "
|
|
"WHERE handle = :handle ;"_ns;
|
|
|
|
const nsLiteralCString updateFileNameQuery =
|
|
"UPDATE Files SET name = :name WHERE handle = :handle ;"_ns;
|
|
|
|
if (aNewType.IsVoid()) {
|
|
return PerformRename(aConnection, aHandle, aNewName, aNewType,
|
|
updateFileNameQuery);
|
|
}
|
|
|
|
return PerformRename(aConnection, aHandle, aNewName, aNewType,
|
|
updateFileTypeAndNameQuery);
|
|
}
|
|
|
|
template <class HandlerType>
|
|
nsresult SetUsageTrackingImpl(const FileSystemConnection& aConnection,
|
|
const FileId& aFileId, bool aTracked,
|
|
HandlerType&& aOnMissingFile) {
|
|
const nsLiteralCString setTrackedQuery =
|
|
"INSERT INTO Usages "
|
|
"( handle, tracked ) "
|
|
"VALUES "
|
|
"( :handle, :tracked ) "
|
|
"ON CONFLICT(handle) DO "
|
|
"UPDATE SET tracked = excluded.tracked "
|
|
";"_ns;
|
|
|
|
const nsresult customReturnValue =
|
|
aTracked ? NS_ERROR_DOM_NOT_FOUND_ERR : NS_OK;
|
|
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, setTrackedQuery));
|
|
QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId)));
|
|
QM_TRY(MOZ_TO_RESULT(stmt.BindBooleanByName("tracked"_ns, aTracked)));
|
|
QM_TRY(MOZ_TO_RESULT(stmt.Execute()), customReturnValue,
|
|
std::forward<HandlerType>(aOnMissingFile));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
Result<nsTArray<FileId>, QMResult> GetTrackedFiles(
|
|
const FileSystemConnection& aConnection) {
|
|
// The same query works for both 001 and 002 schemas because handle is
|
|
// an entry id and later on a file id, respectively.
|
|
static const nsLiteralCString getTrackedFilesQuery =
|
|
"SELECT handle FROM Usages WHERE tracked = TRUE;"_ns;
|
|
nsTArray<FileId> trackedFiles;
|
|
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, getTrackedFilesQuery));
|
|
QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
|
|
|
|
while (moreResults) {
|
|
QM_TRY_UNWRAP(FileId fileId, stmt.GetFileIdByColumn(/* Column */ 0u));
|
|
|
|
trackedFiles.AppendElement(fileId); // TODO: fallible?
|
|
|
|
QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
|
|
}
|
|
|
|
return trackedFiles;
|
|
}
|
|
|
|
/** This handles the file not found error by assigning 0 usage to the dangling
|
|
* handle and puts the handle to a non-tracked state. Otherwise, when the
|
|
* file or database cannot be reached, the file remains in the tracked state.
|
|
*/
|
|
template <class QuotaCacheUpdate>
|
|
nsresult UpdateUsageForFileEntry(const FileSystemConnection& aConnection,
|
|
const FileSystemFileManager& aFileManager,
|
|
const FileId& aFileId,
|
|
const nsLiteralCString& aUpdateQuery,
|
|
QuotaCacheUpdate&& aUpdateCache) {
|
|
QM_TRY_INSPECT(const auto& fileHandle, aFileManager.GetFile(aFileId));
|
|
|
|
// A file could have changed in a way which doesn't allow to read its size.
|
|
QM_TRY_UNWRAP(
|
|
const Usage fileSize,
|
|
QM_OR_ELSE_WARN_IF(
|
|
// Expression.
|
|
MOZ_TO_RESULT_INVOKE_MEMBER(fileHandle, GetFileSize),
|
|
// Predicate.
|
|
([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
|
|
// Fallback. If the file does no longer exist, treat it as 0-sized.
|
|
ErrToDefaultOk<Usage>));
|
|
|
|
QM_TRY(MOZ_TO_RESULT(aUpdateCache(fileSize)));
|
|
|
|
// No transaction as one statement succeeds or fails atomically
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, aUpdateQuery));
|
|
|
|
QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId)));
|
|
|
|
QM_TRY(MOZ_TO_RESULT(stmt.BindUsageByName("usage"_ns, fileSize)));
|
|
|
|
QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult UpdateUsageUnsetTracked(const FileSystemConnection& aConnection,
|
|
const FileSystemFileManager& aFileManager,
|
|
const FileId& aFileId) {
|
|
static const nsLiteralCString updateUsagesUnsetTrackedQuery =
|
|
"UPDATE Usages SET usage = :usage, tracked = FALSE "
|
|
"WHERE handle = :handle;"_ns;
|
|
|
|
auto noCacheUpdateNeeded = [](auto) { return NS_OK; };
|
|
|
|
return UpdateUsageForFileEntry(aConnection, aFileManager, aFileId,
|
|
updateUsagesUnsetTrackedQuery,
|
|
std::move(noCacheUpdateNeeded));
|
|
}
|
|
|
|
/**
|
|
* @brief Get the recorded usage only if the file is in tracked state.
|
|
* During origin initialization, if the usage on disk is unreadable, the latest
|
|
* recorded usage is reported to the quota manager for the tracked files.
|
|
* To allow writing, we attempt to update the real usage with one database and
|
|
* one file size query.
|
|
*/
|
|
Result<Maybe<Usage>, QMResult> GetMaybeTrackedUsage(
|
|
const FileSystemConnection& aConnection, const FileId& aFileId) {
|
|
const nsLiteralCString trackedUsageQuery =
|
|
"SELECT usage FROM Usages WHERE tracked = TRUE AND 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 Maybe<Usage>(Nothing());
|
|
}
|
|
|
|
QM_TRY_UNWRAP(Usage trackedUsage, stmt.GetUsageByColumn(/* Column */ 0u));
|
|
|
|
return Some(trackedUsage);
|
|
}
|
|
|
|
Result<bool, nsresult> ScanTrackedFiles(
|
|
const FileSystemConnection& aConnection,
|
|
const FileSystemFileManager& aFileManager) {
|
|
QM_TRY_INSPECT(const nsTArray<FileId>& trackedFiles,
|
|
GetTrackedFiles(aConnection).mapErr(toNSResult));
|
|
|
|
bool ok = true;
|
|
for (const auto& fileId : trackedFiles) {
|
|
// On success, tracked is set to false, otherwise its value is kept (= true)
|
|
QM_WARNONLY_TRY(MOZ_TO_RESULT(UpdateUsageUnsetTracked(
|
|
aConnection, aFileManager, fileId)),
|
|
[&ok](const auto& /*aRv*/) { ok = false; });
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
Result<Ok, QMResult> DeleteEntry(const FileSystemConnection& aConnection,
|
|
const EntryId& aEntryId) {
|
|
// If it's a directory, deleting the handle will cascade
|
|
const nsLiteralCString deleteEntryQuery =
|
|
"DELETE FROM Entries "
|
|
"WHERE handle = :handle "
|
|
";"_ns;
|
|
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, deleteEntryQuery));
|
|
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
|
|
|
|
QM_TRY(QM_TO_RESULT(stmt.Execute()));
|
|
|
|
return Ok{};
|
|
}
|
|
|
|
Result<int32_t, QMResult> GetTrackedFilesCount(
|
|
const FileSystemConnection& aConnection) {
|
|
// TODO: We could query the count directly
|
|
QM_TRY_INSPECT(const auto& trackedFiles, GetTrackedFiles(aConnection));
|
|
|
|
CheckedInt32 checkedFileCount = trackedFiles.Length();
|
|
QM_TRY(OkIf(checkedFileCount.isValid()),
|
|
Err(QMResult(NS_ERROR_ILLEGAL_VALUE)));
|
|
|
|
return checkedFileCount.value();
|
|
}
|
|
|
|
void LogWithFilename(const FileSystemFileManager& aFileManager,
|
|
const char* aFormat, const FileId& aFileId) {
|
|
if (!LOG_ENABLED()) {
|
|
return;
|
|
}
|
|
|
|
QM_TRY_INSPECT(const auto& localFile, aFileManager.GetFile(aFileId), QM_VOID);
|
|
|
|
nsAutoString localPath;
|
|
QM_TRY(MOZ_TO_RESULT(localFile->GetPath(localPath)), QM_VOID);
|
|
LOG((aFormat, NS_ConvertUTF16toUTF8(localPath).get()));
|
|
}
|
|
|
|
Result<bool, QMResult> IsAnyDescendantLocked(
|
|
const FileSystemConnection& aConnection,
|
|
const FileSystemDataManager& aDataManager, const EntryId& aEntryId) {
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, gDescendantsQuery));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
|
|
QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
|
|
|
|
while (moreResults) {
|
|
// Works only for version 001
|
|
QM_TRY_INSPECT(const EntryId& entryId,
|
|
stmt.GetEntryIdByColumn(/* Column */ 0u));
|
|
|
|
QM_TRY_UNWRAP(const bool isLocked, aDataManager.IsLocked(entryId), true);
|
|
if (isLocked) {
|
|
return true;
|
|
}
|
|
|
|
QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
FileSystemDatabaseManagerVersion001::FileSystemDatabaseManagerVersion001(
|
|
FileSystemDataManager* aDataManager, FileSystemConnection&& aConnection,
|
|
UniquePtr<FileSystemFileManager>&& aFileManager, const EntryId& aRootEntry)
|
|
: mDataManager(aDataManager),
|
|
mConnection(aConnection),
|
|
mFileManager(std::move(aFileManager)),
|
|
mRootEntry(aRootEntry),
|
|
mClientMetadata(aDataManager->OriginMetadataRef(),
|
|
quota::Client::FILESYSTEM),
|
|
mFilesOfUnknownUsage(-1) {}
|
|
|
|
/* static */
|
|
nsresult FileSystemDatabaseManagerVersion001::RescanTrackedUsages(
|
|
const FileSystemConnection& aConnection,
|
|
const quota::OriginMetadata& aOriginMetadata) {
|
|
QM_TRY_UNWRAP(UniquePtr<FileSystemFileManager> fileManager,
|
|
data::FileSystemFileManager::CreateFileSystemFileManager(
|
|
aOriginMetadata));
|
|
|
|
QM_TRY_UNWRAP(bool ok, ScanTrackedFiles(aConnection, *fileManager));
|
|
if (ok) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Retry once without explicit delay
|
|
QM_TRY_UNWRAP(ok, ScanTrackedFiles(aConnection, *fileManager));
|
|
if (!ok) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static */
|
|
Result<Usage, QMResult> FileSystemDatabaseManagerVersion001::GetFileUsage(
|
|
const FileSystemConnection& aConnection) {
|
|
const nsLiteralCString sumUsagesQuery = "SELECT sum(usage) FROM Usages;"_ns;
|
|
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, sumUsagesQuery));
|
|
|
|
QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep());
|
|
if (!moreResults) {
|
|
return Err(QMResult(NS_ERROR_DOM_FILE_NOT_READABLE_ERR));
|
|
}
|
|
|
|
QM_TRY_UNWRAP(Usage totalFiles, stmt.GetUsageByColumn(/* Column */ 0u));
|
|
|
|
return totalFiles;
|
|
}
|
|
|
|
nsresult FileSystemDatabaseManagerVersion001::UpdateUsage(
|
|
const FileId& aFileId) {
|
|
// We don't track directories or non-existent files.
|
|
QM_TRY_UNWRAP(bool fileExists, DoesFileIdExist(aFileId).mapErr(toNSResult));
|
|
if (!fileExists) {
|
|
return NS_OK; // May be deleted before update, no assert
|
|
}
|
|
|
|
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> file, mFileManager->GetFile(aFileId));
|
|
MOZ_ASSERT(file);
|
|
|
|
Usage fileSize = 0;
|
|
bool exists = false;
|
|
QM_TRY(MOZ_TO_RESULT(file->Exists(&exists)));
|
|
if (exists) {
|
|
QM_TRY(MOZ_TO_RESULT(file->GetFileSize(&fileSize)));
|
|
}
|
|
|
|
QM_TRY(MOZ_TO_RESULT(UpdateUsageInDatabase(aFileId, fileSize)));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
Result<EntryId, QMResult>
|
|
FileSystemDatabaseManagerVersion001::GetOrCreateDirectory(
|
|
const FileSystemChildMetadata& aHandle, bool aCreate) {
|
|
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
|
|
|
|
const auto& name = aHandle.childName();
|
|
// Belt and suspenders: check here as well as in child.
|
|
if (!IsValidName(name)) {
|
|
return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR));
|
|
}
|
|
MOZ_ASSERT(!(name.IsVoid() || name.IsEmpty()));
|
|
|
|
bool exists = true;
|
|
QM_TRY_UNWRAP(exists, DoesFileExist(mConnection, aHandle));
|
|
|
|
// By spec, we don't allow a file and a directory
|
|
// to have the same name and parent
|
|
if (exists) {
|
|
return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR));
|
|
}
|
|
|
|
QM_TRY_UNWRAP(exists, DoesDirectoryExist(mConnection, aHandle));
|
|
|
|
// exists as directory
|
|
if (exists) {
|
|
return FindEntryId(mConnection, aHandle, false);
|
|
}
|
|
|
|
if (!aCreate) {
|
|
return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
|
|
}
|
|
|
|
const nsLiteralCString insertEntryQuery =
|
|
"INSERT OR IGNORE INTO Entries "
|
|
"( handle, parent ) "
|
|
"VALUES "
|
|
"( :handle, :parent ) "
|
|
";"_ns;
|
|
|
|
const nsLiteralCString insertDirectoryQuery =
|
|
"INSERT OR IGNORE INTO Directories "
|
|
"( handle, name ) "
|
|
"VALUES "
|
|
"( :handle, :name ) "
|
|
";"_ns;
|
|
|
|
QM_TRY_INSPECT(const EntryId& entryId, GetEntryId(aHandle));
|
|
MOZ_ASSERT(!entryId.IsEmpty());
|
|
|
|
QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
|
|
|
|
{
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(mConnection, insertEntryQuery));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
|
|
QM_TRY(
|
|
QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId())));
|
|
QM_TRY(QM_TO_RESULT(stmt.Execute()));
|
|
}
|
|
|
|
{
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(mConnection, insertDirectoryQuery));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, name)));
|
|
QM_TRY(QM_TO_RESULT(stmt.Execute()));
|
|
}
|
|
|
|
QM_TRY(QM_TO_RESULT(transaction.Commit()));
|
|
|
|
QM_TRY_UNWRAP(DebugOnly<bool> doesItExistNow,
|
|
DoesDirectoryExist(mConnection, aHandle));
|
|
MOZ_ASSERT(doesItExistNow);
|
|
|
|
return entryId;
|
|
}
|
|
|
|
Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetOrCreateFile(
|
|
const FileSystemChildMetadata& aHandle, bool aCreate) {
|
|
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
|
|
|
|
const auto& name = aHandle.childName();
|
|
// Belt and suspenders: check here as well as in child.
|
|
if (!IsValidName(name)) {
|
|
return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR));
|
|
}
|
|
MOZ_ASSERT(!(name.IsVoid() || name.IsEmpty()));
|
|
|
|
QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(mConnection, aHandle));
|
|
|
|
// By spec, we don't allow a file and a directory
|
|
// to have the same name and parent
|
|
QM_TRY(OkIf(!exists), Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR)));
|
|
|
|
QM_TRY_UNWRAP(exists, DoesFileExist(mConnection, aHandle));
|
|
|
|
if (exists) {
|
|
QM_TRY_RETURN(FindEntryId(mConnection, aHandle, /* aIsFile */ true));
|
|
}
|
|
|
|
if (!aCreate) {
|
|
return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
|
|
}
|
|
|
|
const nsLiteralCString insertEntryQuery =
|
|
"INSERT INTO Entries "
|
|
"( handle, parent ) "
|
|
"VALUES "
|
|
"( :handle, :parent ) "
|
|
";"_ns;
|
|
|
|
const nsLiteralCString insertFileQuery =
|
|
"INSERT INTO Files "
|
|
"( handle, type, name ) "
|
|
"VALUES "
|
|
"( :handle, :type, :name ) "
|
|
";"_ns;
|
|
|
|
QM_TRY_INSPECT(const EntryId& entryId, GetEntryId(aHandle));
|
|
MOZ_ASSERT(!entryId.IsEmpty());
|
|
|
|
const ContentType type = DetermineContentType(name);
|
|
|
|
QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
|
|
|
|
{
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(mConnection, insertEntryQuery));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
|
|
QM_TRY(
|
|
QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId())));
|
|
QM_TRY(QM_TO_RESULT(stmt.Execute()), QM_PROPAGATE,
|
|
([this, &aHandle](const auto& aRv) {
|
|
QM_TRY_UNWRAP(bool parentExists,
|
|
DoesDirectoryExist(mConnection, aHandle.parentId()),
|
|
QM_VOID);
|
|
QM_TRY(OkIf(parentExists), QM_VOID);
|
|
}));
|
|
}
|
|
|
|
{
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(mConnection, insertFileQuery));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindContentTypeByName("type"_ns, type)));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, name)));
|
|
QM_TRY(QM_TO_RESULT(stmt.Execute()));
|
|
}
|
|
|
|
QM_TRY(QM_TO_RESULT(transaction.Commit()));
|
|
|
|
return entryId;
|
|
}
|
|
|
|
nsresult FileSystemDatabaseManagerVersion001::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());
|
|
MOZ_ASSERT(aMode == FileMode::EXCLUSIVE);
|
|
|
|
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)));
|
|
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<FileSystemDirectoryListing, QMResult>
|
|
FileSystemDatabaseManagerVersion001::GetDirectoryEntries(
|
|
const EntryId& aParent, PageNumber aPage) const {
|
|
// TODO: Offset is reported to have bad performance - see Bug 1780386.
|
|
const nsCString directoriesQuery =
|
|
"SELECT Dirs.handle, Dirs.name "
|
|
"FROM Directories AS Dirs "
|
|
"INNER JOIN ( "
|
|
"SELECT handle "
|
|
"FROM Entries "
|
|
"WHERE parent = :parent "
|
|
"LIMIT :pageSize "
|
|
"OFFSET :pageOffset ) "
|
|
"AS Ents "
|
|
"ON Dirs.handle = Ents.handle "
|
|
";"_ns;
|
|
const nsCString filesQuery =
|
|
"SELECT Files.handle, Files.name "
|
|
"FROM Files "
|
|
"INNER JOIN ( "
|
|
"SELECT handle "
|
|
"FROM Entries "
|
|
"WHERE parent = :parent "
|
|
"LIMIT :pageSize "
|
|
"OFFSET :pageOffset ) "
|
|
"AS Ents "
|
|
"ON Files.handle = Ents.handle "
|
|
";"_ns;
|
|
|
|
FileSystemDirectoryListing entries;
|
|
QM_TRY(
|
|
QM_TO_RESULT(GetEntries(mConnection, directoriesQuery, aParent, aPage,
|
|
/* aDirectory */ true, entries.directories())));
|
|
|
|
QM_TRY(QM_TO_RESULT(GetEntries(mConnection, filesQuery, aParent, aPage,
|
|
/* aDirectory */ false, entries.files())));
|
|
|
|
return entries;
|
|
}
|
|
|
|
Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveDirectory(
|
|
const FileSystemChildMetadata& aHandle, bool aRecursive) {
|
|
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
|
|
|
|
if (aHandle.childName().IsEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
DebugOnly<Name> name = aHandle.childName();
|
|
MOZ_ASSERT(!name.inspect().IsVoid());
|
|
|
|
QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(mConnection, aHandle));
|
|
|
|
if (!exists) {
|
|
return false;
|
|
}
|
|
|
|
// At this point, entry exists and is a directory.
|
|
QM_TRY_UNWRAP(EntryId entryId, FindEntryId(mConnection, aHandle, false));
|
|
MOZ_ASSERT(!entryId.IsEmpty());
|
|
|
|
QM_TRY_UNWRAP(bool isEmpty, IsDirectoryEmpty(mConnection, entryId));
|
|
|
|
MOZ_ASSERT(mDataManager);
|
|
QM_TRY_UNWRAP(const bool isLocked,
|
|
IsAnyDescendantLocked(mConnection, *mDataManager, entryId));
|
|
|
|
QM_TRY(OkIf(!isLocked),
|
|
Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)));
|
|
|
|
if (!aRecursive && !isEmpty) {
|
|
return Err(QMResult(NS_ERROR_DOM_INVALID_MODIFICATION_ERR));
|
|
}
|
|
|
|
QM_TRY_UNWRAP(Usage usage, GetUsagesOfDescendants(entryId));
|
|
|
|
QM_TRY_INSPECT(const nsTArray<FileId>& descendants,
|
|
FindFilesUnderEntry(entryId));
|
|
|
|
nsTArray<FileId> failedRemovals;
|
|
QM_TRY_UNWRAP(DebugOnly<Usage> removedUsage,
|
|
mFileManager->RemoveFiles(descendants, failedRemovals));
|
|
|
|
// Usage is for the current main file but we remove temporary files too.
|
|
MOZ_ASSERT_IF(failedRemovals.IsEmpty() && (0 == mFilesOfUnknownUsage),
|
|
usage <= removedUsage);
|
|
|
|
TryRemoveDuringIdleMaintenance(failedRemovals);
|
|
|
|
auto isInFailedRemovals = [&failedRemovals](const auto& aFileId) {
|
|
return failedRemovals.cend() !=
|
|
std::find_if(failedRemovals.cbegin(), failedRemovals.cend(),
|
|
[&aFileId](const auto& aFailedRemoval) {
|
|
return aFileId == aFailedRemoval;
|
|
});
|
|
};
|
|
|
|
for (const auto& fileId : descendants) {
|
|
if (!isInFailedRemovals(fileId)) {
|
|
QM_WARNONLY_TRY(QM_TO_RESULT(RemoveFileId(fileId)));
|
|
}
|
|
}
|
|
|
|
if (usage > 0) { // Performance!
|
|
DecreaseCachedQuotaUsage(usage);
|
|
}
|
|
|
|
QM_TRY(DeleteEntry(mConnection, entryId));
|
|
|
|
return true;
|
|
}
|
|
|
|
Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveFile(
|
|
const FileSystemChildMetadata& aHandle) {
|
|
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
|
|
|
|
if (aHandle.childName().IsEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
DebugOnly<Name> name = aHandle.childName();
|
|
MOZ_ASSERT(!name.inspect().IsVoid());
|
|
|
|
// Make it more evident that we won't remove directories
|
|
QM_TRY_UNWRAP(bool exists, DoesFileExist(mConnection, aHandle));
|
|
|
|
if (!exists) {
|
|
return false;
|
|
}
|
|
|
|
// At this point, entry exists and is a file
|
|
QM_TRY_UNWRAP(EntryId entryId, FindEntryId(mConnection, aHandle, true));
|
|
MOZ_ASSERT(!entryId.IsEmpty());
|
|
|
|
// XXX This code assumes the spec question is resolved to state
|
|
// removing an in-use file should fail. If it shouldn't fail, we need to
|
|
// do something to neuter all the extant FileAccessHandles/WritableFileStreams
|
|
// that reference it
|
|
QM_TRY_UNWRAP(const bool isLocked, mDataManager->IsLocked(entryId));
|
|
if (isLocked) {
|
|
LOG(("Trying to remove in-use file"));
|
|
return Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR));
|
|
}
|
|
|
|
QM_TRY_INSPECT(const nsTArray<FileId>& diskItems,
|
|
FindFilesUnderEntry(entryId));
|
|
|
|
QM_TRY_UNWRAP(Usage usage, GetUsagesOfDescendants(entryId));
|
|
|
|
nsTArray<FileId> failedRemovals;
|
|
QM_TRY_UNWRAP(DebugOnly<Usage> removedUsage,
|
|
mFileManager->RemoveFiles(diskItems, failedRemovals));
|
|
|
|
// 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(failedRemovals.IsEmpty() && (0 == mFilesOfUnknownUsage),
|
|
usage == removedUsage);
|
|
|
|
TryRemoveDuringIdleMaintenance(failedRemovals);
|
|
|
|
auto isInFailedRemovals = [&failedRemovals](const auto& aFileId) {
|
|
return failedRemovals.cend() !=
|
|
std::find_if(failedRemovals.cbegin(), failedRemovals.cend(),
|
|
[&aFileId](const auto& aFailedRemoval) {
|
|
return aFileId == aFailedRemoval;
|
|
});
|
|
};
|
|
|
|
for (const auto& fileId : diskItems) {
|
|
if (!isInFailedRemovals(fileId)) {
|
|
QM_WARNONLY_TRY(QM_TO_RESULT(RemoveFileId(fileId)));
|
|
}
|
|
}
|
|
|
|
if (usage > 0) { // Performance!
|
|
DecreaseCachedQuotaUsage(usage);
|
|
}
|
|
|
|
QM_TRY(DeleteEntry(mConnection, entryId));
|
|
|
|
return true;
|
|
}
|
|
|
|
Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::RenameEntry(
|
|
const FileSystemEntryMetadata& aHandle, const Name& aNewName) {
|
|
const auto& entryId = aHandle.entryId();
|
|
|
|
// 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(auto transaction, StartedTransaction::Create(mConnection));
|
|
|
|
if (isFile) {
|
|
const ContentType type = DetermineContentType(aNewName);
|
|
QM_TRY(
|
|
QM_TO_RESULT(PerformRenameFile(mConnection, aHandle, aNewName, type)));
|
|
} else {
|
|
QM_TRY(
|
|
QM_TO_RESULT(PerformRenameDirectory(mConnection, aHandle, aNewName)));
|
|
}
|
|
|
|
QM_TRY(QM_TO_RESULT(transaction.Commit()));
|
|
|
|
return entryId;
|
|
}
|
|
|
|
Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::MoveEntry(
|
|
const FileSystemEntryMetadata& aHandle,
|
|
const FileSystemChildMetadata& aNewDesignation) {
|
|
const auto& entryId = aHandle.entryId();
|
|
MOZ_ASSERT(!entryId.IsEmpty());
|
|
|
|
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)));
|
|
|
|
const nsLiteralCString updateEntryParentQuery =
|
|
"UPDATE Entries "
|
|
"SET parent = :parent "
|
|
"WHERE handle = :handle "
|
|
";"_ns;
|
|
|
|
QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
|
|
|
|
{
|
|
// We always change the parent because it's simpler than checking if the
|
|
// parent needs to be changed
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(mConnection, updateEntryParentQuery));
|
|
QM_TRY(QM_TO_RESULT(
|
|
stmt.BindEntryIdByName("parent"_ns, aNewDesignation.parentId())));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
|
|
QM_TRY(QM_TO_RESULT(stmt.Execute()));
|
|
}
|
|
|
|
const Name& newName = aNewDesignation.childName();
|
|
|
|
// Are we actually renaming?
|
|
if (aHandle.entryName() == newName) {
|
|
QM_TRY(QM_TO_RESULT(transaction.Commit()));
|
|
|
|
return entryId;
|
|
}
|
|
|
|
if (isFile) {
|
|
const ContentType type = DetermineContentType(newName);
|
|
QM_TRY(
|
|
QM_TO_RESULT(PerformRenameFile(mConnection, aHandle, newName, type)));
|
|
} else {
|
|
QM_TRY(QM_TO_RESULT(PerformRenameDirectory(mConnection, aHandle, newName)));
|
|
}
|
|
|
|
QM_TRY(QM_TO_RESULT(transaction.Commit()));
|
|
|
|
return entryId;
|
|
}
|
|
|
|
Result<Path, QMResult> FileSystemDatabaseManagerVersion001::Resolve(
|
|
const FileSystemEntryPair& aEndpoints) const {
|
|
QM_TRY_UNWRAP(Path path, ResolveReversedPath(mConnection, aEndpoints));
|
|
// Note: if not an ancestor, returns null
|
|
|
|
path.Reverse();
|
|
return path;
|
|
}
|
|
|
|
Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetEntryId(
|
|
const FileSystemChildMetadata& aHandle) const {
|
|
return GetUniqueEntryId(mConnection, aHandle);
|
|
}
|
|
|
|
Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetEntryId(
|
|
const FileId& aFileId) const {
|
|
return aFileId.Value();
|
|
}
|
|
|
|
Result<FileId, QMResult> FileSystemDatabaseManagerVersion001::EnsureFileId(
|
|
const EntryId& aEntryId) {
|
|
return FileId(aEntryId);
|
|
}
|
|
|
|
Result<FileId, QMResult>
|
|
FileSystemDatabaseManagerVersion001::EnsureTemporaryFileId(
|
|
const EntryId& aEntryId) {
|
|
return FileId(aEntryId);
|
|
}
|
|
|
|
Result<FileId, QMResult> FileSystemDatabaseManagerVersion001::GetFileId(
|
|
const EntryId& aEntryId) const {
|
|
return FileId(aEntryId);
|
|
}
|
|
|
|
nsresult FileSystemDatabaseManagerVersion001::MergeFileId(
|
|
const EntryId& /* aEntryId */, const FileId& /* aFileId */,
|
|
bool /* aAbort */) {
|
|
// Version 001 should always use exclusive mode and not get here.
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
void FileSystemDatabaseManagerVersion001::Close() { mConnection->Close(); }
|
|
|
|
nsresult FileSystemDatabaseManagerVersion001::BeginUsageTracking(
|
|
const FileId& aFileId) {
|
|
MOZ_ASSERT(!aFileId.IsEmpty());
|
|
|
|
// If file is already tracked but we cannot read its size, error.
|
|
// If file does not exist, this will succeed because usage is zero.
|
|
QM_TRY(EnsureUsageIsKnown(aFileId));
|
|
|
|
// If file does not exist, set usage tracking to true fails with
|
|
// file not found error.
|
|
QM_TRY(MOZ_TO_RESULT(SetUsageTracking(aFileId, true)));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult FileSystemDatabaseManagerVersion001::EndUsageTracking(
|
|
const FileId& aFileId) {
|
|
// This is expected to fail only if database is unreachable.
|
|
QM_TRY(MOZ_TO_RESULT(SetUsageTracking(aFileId, false)));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
Result<bool, QMResult> FileSystemDatabaseManagerVersion001::DoesFileIdExist(
|
|
const FileId& aFileId) const {
|
|
MOZ_ASSERT(!aFileId.IsEmpty());
|
|
|
|
QM_TRY_RETURN(DoesFileExist(mConnection, aFileId.Value()));
|
|
}
|
|
|
|
nsresult FileSystemDatabaseManagerVersion001::RemoveFileId(
|
|
const FileId& /* aFileId */) {
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the sum of usages for all file descendants of a directory entry.
|
|
* We obtain the value with one query, which is presumably better than having a
|
|
* separate query for each individual descendant.
|
|
* TODO: Check if this is true
|
|
*
|
|
* Please see GetFileUsage documentation for why we use the latest recorded
|
|
* value from the database instead of the file size property from the disk.
|
|
*/
|
|
Result<Usage, QMResult>
|
|
FileSystemDatabaseManagerVersion001::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 Usages "
|
|
"USING(handle) "
|
|
";"_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>
|
|
FileSystemDatabaseManagerVersion001::FindFilesUnderEntry(
|
|
const EntryId& aEntryId) const {
|
|
nsTArray<FileId> descendants;
|
|
{
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(mConnection, gDescendantsQuery));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
|
|
QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
|
|
|
|
while (moreResults) {
|
|
// Works only for version 001
|
|
QM_TRY_INSPECT(const FileId& fileId,
|
|
stmt.GetFileIdByColumn(/* Column */ 0u));
|
|
|
|
descendants.AppendElement(fileId);
|
|
|
|
QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
|
|
}
|
|
}
|
|
|
|
return descendants;
|
|
}
|
|
|
|
nsresult FileSystemDatabaseManagerVersion001::SetUsageTracking(
|
|
const FileId& aFileId, bool aTracked) {
|
|
auto onMissingFile = [this, &aFileId](const auto& aRv) {
|
|
// Usages constrains entryId to be present in Files
|
|
MOZ_ASSERT(NS_ERROR_STORAGE_CONSTRAINT == ToNSResult(aRv));
|
|
|
|
// The query *should* fail if and only if file does not exist
|
|
QM_TRY_UNWRAP(DebugOnly<bool> fileExists, DoesFileIdExist(aFileId),
|
|
QM_VOID);
|
|
MOZ_ASSERT(!fileExists);
|
|
};
|
|
|
|
return SetUsageTrackingImpl(mConnection, aFileId, aTracked, onMissingFile);
|
|
}
|
|
|
|
nsresult FileSystemDatabaseManagerVersion001::UpdateUsageInDatabase(
|
|
const FileId& aFileId, Usage aNewDiskUsage) {
|
|
const nsLiteralCString updateUsageQuery =
|
|
"INSERT INTO Usages "
|
|
"( handle, usage ) "
|
|
"VALUES "
|
|
"( :handle, :usage ) "
|
|
"ON CONFLICT(handle) DO "
|
|
"UPDATE SET usage = excluded.usage "
|
|
";"_ns;
|
|
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(mConnection, updateUsageQuery));
|
|
QM_TRY(MOZ_TO_RESULT(stmt.BindUsageByName("usage"_ns, aNewDiskUsage)));
|
|
QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId)));
|
|
QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
Result<Ok, QMResult> FileSystemDatabaseManagerVersion001::EnsureUsageIsKnown(
|
|
const FileId& aFileId) {
|
|
if (mFilesOfUnknownUsage < 0) { // Lazy initialization
|
|
QM_TRY_UNWRAP(mFilesOfUnknownUsage, GetTrackedFilesCount(mConnection));
|
|
}
|
|
|
|
if (mFilesOfUnknownUsage == 0) {
|
|
return Ok{};
|
|
}
|
|
|
|
QM_TRY_UNWRAP(Maybe<Usage> oldUsage,
|
|
GetMaybeTrackedUsage(mConnection, aFileId));
|
|
if (oldUsage.isNothing()) {
|
|
return Ok{}; // Usage is 0 or it was successfully recorded at unlocking.
|
|
}
|
|
|
|
auto quotaCacheUpdate = [this, &aFileId,
|
|
oldSize = oldUsage.value()](Usage aNewSize) {
|
|
return UpdateCachedQuotaUsage(aFileId, oldSize, aNewSize);
|
|
};
|
|
|
|
static const nsLiteralCString updateUsagesKeepTrackedQuery =
|
|
"UPDATE Usages SET usage = :usage WHERE handle = :handle;"_ns;
|
|
|
|
// If usage update fails, we log an error and keep things the way they were.
|
|
QM_TRY(QM_TO_RESULT(UpdateUsageForFileEntry(
|
|
mConnection, *mFileManager, aFileId, updateUsagesKeepTrackedQuery,
|
|
std::move(quotaCacheUpdate))),
|
|
Err(QMResult(NS_ERROR_DOM_FILE_NOT_READABLE_ERR)),
|
|
([this, &aFileId](const auto& /*aRv*/) {
|
|
LogWithFilename(*mFileManager, "Could not read the size of file %s",
|
|
aFileId);
|
|
}));
|
|
|
|
// We read and updated the quota usage successfully.
|
|
--mFilesOfUnknownUsage;
|
|
MOZ_ASSERT(mFilesOfUnknownUsage >= 0);
|
|
|
|
return Ok{};
|
|
}
|
|
|
|
void FileSystemDatabaseManagerVersion001::DecreaseCachedQuotaUsage(
|
|
int64_t aDelta) {
|
|
quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
quotaManager->DecreaseUsageForClient(mClientMetadata, aDelta);
|
|
}
|
|
|
|
nsresult FileSystemDatabaseManagerVersion001::UpdateCachedQuotaUsage(
|
|
const FileId& aFileId, Usage aOldUsage, Usage aNewUsage) const {
|
|
quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> fileObj,
|
|
mFileManager->GetFile(aFileId).mapErr(toNSResult));
|
|
|
|
RefPtr<quota::QuotaObject> quotaObject = quotaManager->GetQuotaObject(
|
|
quota::PERSISTENCE_TYPE_DEFAULT, mClientMetadata,
|
|
quota::Client::FILESYSTEM, fileObj, aOldUsage);
|
|
MOZ_ASSERT(quotaObject);
|
|
|
|
QM_TRY(OkIf(quotaObject->MaybeUpdateSize(aNewUsage, /* aTruncate */ true)),
|
|
NS_ERROR_FILE_NO_DEVICE_SPACE);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult FileSystemDatabaseManagerVersion001::ClearDestinationIfNotLocked(
|
|
const FileSystemConnection& aConnection,
|
|
const FileSystemDataManager* const aDataManager,
|
|
const FileSystemEntryMetadata& aHandle,
|
|
const FileSystemChildMetadata& aNewDesignation) {
|
|
// If the destination file exists, fail explicitly. Spec author plans to
|
|
// revise the spec
|
|
QM_TRY_UNWRAP(bool exists, DoesFileExist(aConnection, aNewDesignation));
|
|
if (exists) {
|
|
QM_TRY_INSPECT(const EntryId& destId,
|
|
FindEntryId(aConnection, aNewDesignation, true));
|
|
QM_TRY_UNWRAP(const bool isLocked, aDataManager->IsLocked(destId));
|
|
if (isLocked) {
|
|
LOG(("Trying to overwrite in-use file"));
|
|
return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
|
|
}
|
|
|
|
QM_TRY_UNWRAP(DebugOnly<bool> isRemoved, RemoveFile(aNewDesignation));
|
|
MOZ_ASSERT(isRemoved);
|
|
} else {
|
|
QM_TRY_UNWRAP(exists, DoesDirectoryExist(aConnection, aNewDesignation));
|
|
if (exists) {
|
|
// Fails if directory contains locked files, otherwise total wipeout
|
|
QM_TRY_UNWRAP(DebugOnly<bool> isRemoved,
|
|
MOZ_TO_RESULT(RemoveDirectory(aNewDesignation,
|
|
/* recursive */ true)));
|
|
MOZ_ASSERT(isRemoved);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult FileSystemDatabaseManagerVersion001::PrepareRenameEntry(
|
|
const FileSystemConnection& aConnection,
|
|
const FileSystemDataManager* const aDataManager,
|
|
const FileSystemEntryMetadata& aHandle, const Name& aNewName,
|
|
bool aIsFile) {
|
|
const EntryId& entryId = aHandle.entryId();
|
|
|
|
// At this point, entry exists
|
|
if (aIsFile) {
|
|
QM_TRY_UNWRAP(const bool isLocked, aDataManager->IsLocked(entryId));
|
|
if (isLocked) {
|
|
LOG(("Trying to move in-use file"));
|
|
return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
|
|
}
|
|
}
|
|
|
|
// If the destination file exists, fail explicitly.
|
|
FileSystemChildMetadata destination;
|
|
QM_TRY_UNWRAP(EntryId parent, FindParent(mConnection, entryId));
|
|
destination.parentId() = parent;
|
|
destination.childName() = aNewName;
|
|
|
|
QM_TRY(MOZ_TO_RESULT(ClearDestinationIfNotLocked(mConnection, mDataManager,
|
|
aHandle, destination)));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult FileSystemDatabaseManagerVersion001::PrepareMoveEntry(
|
|
const FileSystemConnection& aConnection,
|
|
const FileSystemDataManager* const aDataManager,
|
|
const FileSystemEntryMetadata& aHandle,
|
|
const FileSystemChildMetadata& aNewDesignation, bool aIsFile) {
|
|
const EntryId& entryId = aHandle.entryId();
|
|
|
|
// At this point, entry exists
|
|
if (aIsFile) {
|
|
QM_TRY_UNWRAP(const bool isLocked, aDataManager->IsLocked(entryId));
|
|
if (isLocked) {
|
|
LOG(("Trying to move in-use file"));
|
|
return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
|
|
}
|
|
}
|
|
|
|
QM_TRY(QM_TO_RESULT(ClearDestinationIfNotLocked(aConnection, aDataManager,
|
|
aHandle, aNewDesignation)));
|
|
|
|
// XXX: This should be before clearing the target
|
|
|
|
// To prevent cyclic paths, we check that there is no path from
|
|
// the item to be moved to the destination folder.
|
|
QM_TRY_UNWRAP(const bool isDestinationUnderSelf,
|
|
IsAncestor(aConnection, {entryId, aNewDesignation.parentId()}));
|
|
if (isDestinationUnderSelf) {
|
|
return NS_ERROR_DOM_INVALID_MODIFICATION_ERR;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* Free functions
|
|
*/
|
|
|
|
Result<bool, QMResult> ApplyEntryExistsQuery(
|
|
const FileSystemConnection& aConnection, const nsACString& aQuery,
|
|
const FileSystemChildMetadata& aHandle) {
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, aQuery));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId())));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, aHandle.childName())));
|
|
|
|
return stmt.YesOrNoQuery();
|
|
}
|
|
|
|
Result<bool, QMResult> ApplyEntryExistsQuery(
|
|
const FileSystemConnection& aConnection, const nsACString& aQuery,
|
|
const EntryId& aEntry) {
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, aQuery));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntry)));
|
|
|
|
return stmt.YesOrNoQuery();
|
|
}
|
|
|
|
Result<bool, QMResult> DoesFileExist(const FileSystemConnection& aConnection,
|
|
const EntryId& aEntryId) {
|
|
MOZ_ASSERT(!aEntryId.IsEmpty());
|
|
|
|
const nsCString existsQuery =
|
|
"SELECT EXISTS "
|
|
"(SELECT 1 FROM Files WHERE handle = :handle ) "
|
|
";"_ns;
|
|
|
|
QM_TRY_RETURN(ApplyEntryExistsQuery(aConnection, existsQuery, aEntryId));
|
|
}
|
|
|
|
Result<bool, QMResult> IsFile(const FileSystemConnection& aConnection,
|
|
const EntryId& aEntryId) {
|
|
QM_TRY_UNWRAP(bool exists, DoesFileExist(aConnection, aEntryId));
|
|
if (exists) {
|
|
return true;
|
|
}
|
|
|
|
QM_TRY_UNWRAP(exists, DoesDirectoryExist(aConnection, aEntryId));
|
|
if (exists) {
|
|
return false;
|
|
}
|
|
|
|
// Doesn't exist
|
|
return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
|
|
}
|
|
|
|
Result<EntryId, QMResult> FindEntryId(const FileSystemConnection& aConnection,
|
|
const FileSystemChildMetadata& aHandle,
|
|
bool aIsFile) {
|
|
const nsCString aDirectoryQuery =
|
|
"SELECT Entries.handle FROM Directories "
|
|
"INNER JOIN Entries USING (handle) "
|
|
"WHERE Directories.name = :name AND Entries.parent = :parent "
|
|
";"_ns;
|
|
|
|
const nsCString aFileQuery =
|
|
"SELECT Entries.handle FROM Files INNER JOIN Entries USING (handle) "
|
|
"WHERE Files.name = :name AND Entries.parent = :parent "
|
|
";"_ns;
|
|
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(
|
|
aConnection, aIsFile ? aFileQuery : aDirectoryQuery));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId())));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, aHandle.childName())));
|
|
QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
|
|
|
|
if (!moreResults) {
|
|
return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
|
|
}
|
|
|
|
QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u));
|
|
|
|
return entryId;
|
|
}
|
|
|
|
Result<EntryId, QMResult> FindParent(const FileSystemConnection& aConnection,
|
|
const EntryId& aEntryId) {
|
|
const nsCString aParentQuery =
|
|
"SELECT handle FROM Entries "
|
|
"WHERE handle IN ( "
|
|
"SELECT parent FROM Entries WHERE "
|
|
"handle = :entryId ) "
|
|
";"_ns;
|
|
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, aParentQuery));
|
|
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_UNWRAP(EntryId parentId, stmt.GetEntryIdByColumn(/* Column */ 0u));
|
|
return parentId;
|
|
}
|
|
|
|
Result<bool, QMResult> IsSame(const FileSystemConnection& aConnection,
|
|
const FileSystemEntryMetadata& aHandle,
|
|
const FileSystemChildMetadata& aNewHandle,
|
|
bool aIsFile) {
|
|
MOZ_ASSERT(!aNewHandle.parentId().IsEmpty());
|
|
|
|
// Typically aNewHandle does not exist which is not an error
|
|
QM_TRY_RETURN(QM_OR_ELSE_LOG_VERBOSE_IF(
|
|
// Expression.
|
|
FindEntryId(aConnection, aNewHandle, aIsFile)
|
|
.map([&aHandle](const EntryId& entryId) {
|
|
return entryId == aHandle.entryId();
|
|
}),
|
|
// Predicate.
|
|
IsSpecificError<NS_ERROR_DOM_NOT_FOUND_ERR>,
|
|
// Fallback.
|
|
ErrToOkFromQMResult<false>));
|
|
}
|
|
|
|
Result<Path, QMResult> ResolveReversedPath(
|
|
const FileSystemConnection& aConnection,
|
|
const FileSystemEntryPair& aEndpoints) {
|
|
const nsLiteralCString pathQuery =
|
|
"WITH RECURSIVE followPath(handle, parent) AS ( "
|
|
"SELECT handle, parent "
|
|
"FROM Entries "
|
|
"WHERE handle=:entryId "
|
|
"UNION "
|
|
"SELECT Entries.handle, Entries.parent FROM followPath, Entries "
|
|
"WHERE followPath.parent=Entries.handle ) "
|
|
"SELECT COALESCE(Directories.name, Files.name), handle "
|
|
"FROM followPath "
|
|
"LEFT JOIN Directories USING(handle) "
|
|
"LEFT JOIN Files USING(handle);"_ns;
|
|
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, pathQuery));
|
|
QM_TRY(
|
|
QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEndpoints.childId())));
|
|
QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
|
|
|
|
Path pathResult;
|
|
while (moreResults) {
|
|
QM_TRY_UNWRAP(Name entryName, stmt.GetNameByColumn(/* Column */ 0u));
|
|
QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 1u));
|
|
|
|
if (aEndpoints.parentId() == entryId) {
|
|
return pathResult;
|
|
}
|
|
pathResult.AppendElement(entryName);
|
|
|
|
QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
|
|
}
|
|
|
|
// Spec wants us to return 'null' for not-an-ancestor case
|
|
pathResult.Clear();
|
|
return pathResult;
|
|
}
|
|
|
|
nsresult GetFileAttributes(const FileSystemConnection& aConnection,
|
|
const EntryId& aEntryId, ContentType& aType) {
|
|
const nsLiteralCString getFileLocation =
|
|
"SELECT type FROM Files INNER JOIN Entries USING(handle) "
|
|
"WHERE handle = :entryId "
|
|
";"_ns;
|
|
|
|
QM_TRY_UNWRAP(ResultStatement stmt,
|
|
ResultStatement::Create(aConnection, getFileLocation));
|
|
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId)));
|
|
QM_TRY_UNWRAP(bool hasEntries, stmt.ExecuteStep());
|
|
|
|
// Type is an optional attribute
|
|
if (!hasEntries || stmt.IsNullByColumn(/* Column */ 0u)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
QM_TRY_UNWRAP(aType, stmt.GetContentTypeByColumn(/* Column */ 0u));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// TODO: Implement idle maintenance
|
|
void TryRemoveDuringIdleMaintenance(
|
|
const nsTArray<FileId>& /* aItemToRemove */) {
|
|
// Not implemented
|
|
}
|
|
|
|
ContentType DetermineContentType(const Name& aName) {
|
|
QM_TRY_UNWRAP(
|
|
auto typeResult,
|
|
QM_OR_ELSE_LOG_VERBOSE(
|
|
FileSystemContentTypeGuess::FromPath(aName),
|
|
([](const auto& aRv) -> Result<ContentType, QMResult> {
|
|
const nsresult rv = ToNSResult(aRv);
|
|
switch (rv) {
|
|
case NS_ERROR_FAILURE: /* There is an unknown new extension. */
|
|
return ContentType(""_ns); /* We clear the old extension. */
|
|
|
|
case NS_ERROR_INVALID_ARG: /* The name is garbled. */
|
|
[[fallthrough]];
|
|
case NS_ERROR_NOT_AVAILABLE: /* There is no extension. */
|
|
return VoidCString(); /* We keep the old extension. */
|
|
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Should never get here!");
|
|
return Err(aRv);
|
|
}
|
|
})),
|
|
ContentType(""_ns));
|
|
|
|
return typeResult;
|
|
}
|
|
|
|
} // namespace fs::data
|
|
|
|
} // namespace mozilla::dom
|