tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

FileSystemDatabaseManagerVersion001.cpp (55030B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "FileSystemDatabaseManagerVersion001.h"
      8 
      9 #include "ErrorList.h"
     10 #include "FileSystemContentTypeGuess.h"
     11 #include "FileSystemDataManager.h"
     12 #include "FileSystemFileManager.h"
     13 #include "FileSystemParentTypes.h"
     14 #include "ResultStatement.h"
     15 #include "StartedTransaction.h"
     16 #include "mozStorageHelper.h"
     17 #include "mozilla/CheckedInt.h"
     18 #include "mozilla/dom/FileSystemDataManager.h"
     19 #include "mozilla/dom/FileSystemHandle.h"
     20 #include "mozilla/dom/FileSystemLog.h"
     21 #include "mozilla/dom/FileSystemTypes.h"
     22 #include "mozilla/dom/PFileSystemManager.h"
     23 #include "mozilla/dom/quota/Client.h"
     24 #include "mozilla/dom/quota/QuotaCommon.h"
     25 #include "mozilla/dom/quota/QuotaManager.h"
     26 #include "mozilla/dom/quota/QuotaObject.h"
     27 #include "mozilla/dom/quota/ResultExtensions.h"
     28 #include "nsString.h"
     29 
     30 namespace mozilla::dom {
     31 
     32 using FileSystemEntries = nsTArray<fs::FileSystemEntryMetadata>;
     33 
     34 namespace fs::data {
     35 
     36 namespace {
     37 
     38 constexpr const nsLiteralCString gDescendantsQuery =
     39    "WITH RECURSIVE traceChildren(handle, parent) AS ( "
     40    "SELECT handle, parent "
     41    "FROM Entries "
     42    "WHERE handle=:handle "
     43    "UNION "
     44    "SELECT Entries.handle, Entries.parent FROM traceChildren, Entries "
     45    "WHERE traceChildren.handle=Entries.parent ) "
     46    "SELECT handle, Usages.usage "
     47    "FROM traceChildren INNER JOIN Files USING (handle) "
     48    "INNER JOIN Usages USING (handle) "
     49    ";"_ns;
     50 
     51 Result<bool, QMResult> DoesFileExist(const FileSystemConnection& aConnection,
     52                                     const EntryId& aEntryId) {
     53  MOZ_ASSERT(!aEntryId.IsEmpty());
     54 
     55  const nsCString existsQuery =
     56      "SELECT EXISTS "
     57      "(SELECT 1 FROM Files WHERE handle = :handle ) "
     58      ";"_ns;
     59 
     60  QM_TRY_RETURN(ApplyEntryExistsQuery(aConnection, existsQuery, aEntryId));
     61 }
     62 
     63 Result<bool, QMResult> IsDirectoryEmpty(const FileSystemConnection& mConnection,
     64                                        const EntryId& aEntryId) {
     65  const nsLiteralCString isDirEmptyQuery =
     66      "SELECT EXISTS ("
     67      "SELECT 1 FROM Entries WHERE parent = :parent "
     68      ");"_ns;
     69 
     70  QM_TRY_UNWRAP(ResultStatement stmt,
     71                ResultStatement::Create(mConnection, isDirEmptyQuery));
     72  QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aEntryId)));
     73  QM_TRY_UNWRAP(bool childrenExist, stmt.YesOrNoQuery());
     74 
     75  return !childrenExist;
     76 }
     77 
     78 Result<bool, QMResult> DoesDirectoryExist(
     79    const FileSystemConnection& mConnection,
     80    const FileSystemChildMetadata& aHandle) {
     81  MOZ_ASSERT(!aHandle.parentId().IsEmpty());
     82 
     83  const nsCString existsQuery =
     84      "SELECT EXISTS "
     85      "(SELECT 1 FROM Directories INNER JOIN Entries USING (handle) "
     86      "WHERE Directories.name = :name AND Entries.parent = :parent ) "
     87      ";"_ns;
     88 
     89  QM_TRY_RETURN(ApplyEntryExistsQuery(mConnection, existsQuery, aHandle));
     90 }
     91 
     92 Result<bool, QMResult> DoesDirectoryExist(
     93    const FileSystemConnection& mConnection, const EntryId& aEntry) {
     94  MOZ_ASSERT(!aEntry.IsEmpty());
     95 
     96  const nsCString existsQuery =
     97      "SELECT EXISTS "
     98      "(SELECT 1 FROM Directories WHERE handle = :handle ) "
     99      ";"_ns;
    100 
    101  QM_TRY_RETURN(ApplyEntryExistsQuery(mConnection, existsQuery, aEntry));
    102 }
    103 
    104 Result<bool, QMResult> IsAncestor(const FileSystemConnection& aConnection,
    105                                  const FileSystemEntryPair& aEndpoints) {
    106  const nsCString pathQuery =
    107      "WITH RECURSIVE followPath(handle, parent) AS ( "
    108      "SELECT handle, parent "
    109      "FROM Entries "
    110      "WHERE handle=:entryId "
    111      "UNION "
    112      "SELECT Entries.handle, Entries.parent FROM followPath, Entries "
    113      "WHERE followPath.parent=Entries.handle ) "
    114      "SELECT EXISTS "
    115      "(SELECT 1 FROM followPath "
    116      "WHERE handle=:possibleAncestor ) "
    117      ";"_ns;
    118 
    119  QM_TRY_UNWRAP(ResultStatement stmt,
    120                ResultStatement::Create(aConnection, pathQuery));
    121  QM_TRY(
    122      QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEndpoints.childId())));
    123  QM_TRY(QM_TO_RESULT(
    124      stmt.BindEntryIdByName("possibleAncestor"_ns, aEndpoints.parentId())));
    125 
    126  return stmt.YesOrNoQuery();
    127 }
    128 
    129 Result<bool, QMResult> DoesFileExist(const FileSystemConnection& aConnection,
    130                                     const FileSystemChildMetadata& aHandle) {
    131  MOZ_ASSERT(!aHandle.parentId().IsEmpty());
    132 
    133  const nsCString existsQuery =
    134      "SELECT EXISTS "
    135      "(SELECT 1 FROM Files INNER JOIN Entries USING (handle) "
    136      "WHERE Files.name = :name AND Entries.parent = :parent ) "
    137      ";"_ns;
    138 
    139  QM_TRY_RETURN(ApplyEntryExistsQuery(aConnection, existsQuery, aHandle));
    140 }
    141 
    142 nsresult GetEntries(const FileSystemConnection& aConnection,
    143                    const nsACString& aUnboundStmt, const EntryId& aParent,
    144                    PageNumber aPage, bool aDirectory,
    145                    FileSystemEntries& aEntries) {
    146  // The entries inside a directory are sent to the child process in batches
    147  // of pageSize items. Large value ensures that iteration is less often delayed
    148  // by IPC messaging and querying the database.
    149  // TODO: The current value 1024 is not optimized.
    150  // TODO: Value "pageSize" is shared with the iterator implementation and
    151  // should be defined in a common place.
    152  const int32_t pageSize = 1024;
    153 
    154  QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(aConnection, aParent));
    155  if (!exists) {
    156    return NS_ERROR_DOM_NOT_FOUND_ERR;
    157  }
    158 
    159  QM_TRY_UNWRAP(ResultStatement stmt,
    160                ResultStatement::Create(aConnection, aUnboundStmt));
    161  QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aParent)));
    162  QM_TRY(QM_TO_RESULT(stmt.BindPageNumberByName("pageSize"_ns, pageSize)));
    163  QM_TRY(QM_TO_RESULT(
    164      stmt.BindPageNumberByName("pageOffset"_ns, aPage * pageSize)));
    165 
    166  QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
    167 
    168  while (moreResults) {
    169    QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u));
    170    QM_TRY_UNWRAP(Name entryName, stmt.GetNameByColumn(/* Column */ 1u));
    171 
    172    FileSystemEntryMetadata metadata(entryId, entryName, aDirectory);
    173    aEntries.AppendElement(metadata);
    174 
    175    QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
    176  }
    177 
    178  return NS_OK;
    179 }
    180 
    181 Result<EntryId, QMResult> GetUniqueEntryId(
    182    const FileSystemConnection& aConnection,
    183    const FileSystemChildMetadata& aHandle) {
    184  const nsCString existsQuery =
    185      "SELECT EXISTS "
    186      "(SELECT 1 FROM Entries "
    187      "WHERE handle = :handle )"
    188      ";"_ns;
    189 
    190  FileSystemChildMetadata generatorInput = aHandle;
    191 
    192  const size_t maxRounds = 1024u;
    193 
    194  for (size_t hangGuard = 0u; hangGuard < maxRounds; ++hangGuard) {
    195    QM_TRY_UNWRAP(EntryId entryId, fs::data::GetEntryHandle(generatorInput));
    196    QM_TRY_UNWRAP(ResultStatement stmt,
    197                  ResultStatement::Create(aConnection, existsQuery));
    198    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
    199 
    200    QM_TRY_UNWRAP(bool alreadyInUse, stmt.YesOrNoQuery());
    201 
    202    if (!alreadyInUse) {
    203      return entryId;
    204    }
    205 
    206    generatorInput.parentId() = entryId;
    207  }
    208 
    209  return Err(QMResult(NS_ERROR_UNEXPECTED));
    210 }
    211 
    212 nsresult PerformRename(const FileSystemConnection& aConnection,
    213                       const FileSystemEntryMetadata& aHandle,
    214                       const Name& aNewName, const ContentType& aNewType,
    215                       const nsLiteralCString& aNameUpdateQuery) {
    216  MOZ_ASSERT(!aHandle.entryId().IsEmpty());
    217  MOZ_ASSERT(IsValidName(aHandle.entryName()));
    218 
    219  // same-name is checked in RenameEntry()
    220  if (!IsValidName(aNewName)) {
    221    return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
    222  }
    223 
    224  // TODO: This should fail when handle doesn't exist - the
    225  // explicit file or directory existence queries are redundant
    226  QM_TRY_UNWRAP(ResultStatement stmt,
    227                ResultStatement::Create(aConnection, aNameUpdateQuery)
    228                    .mapErr(toNSResult));
    229  if (!aNewType.IsVoid()) {
    230    QM_TRY(MOZ_TO_RESULT(stmt.BindContentTypeByName("type"_ns, aNewType)));
    231  }
    232  QM_TRY(MOZ_TO_RESULT(stmt.BindNameByName("name"_ns, aNewName)));
    233  QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aHandle.entryId())));
    234  QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
    235 
    236  return NS_OK;
    237 }
    238 
    239 nsresult PerformRenameDirectory(const FileSystemConnection& aConnection,
    240                                const FileSystemEntryMetadata& aHandle,
    241                                const Name& aNewName) {
    242  const nsLiteralCString updateDirectoryNameQuery =
    243      "UPDATE Directories "
    244      "SET name = :name "
    245      "WHERE handle = :handle "
    246      ";"_ns;
    247 
    248  return PerformRename(aConnection, aHandle, aNewName, VoidCString(),
    249                       updateDirectoryNameQuery);
    250 }
    251 
    252 nsresult PerformRenameFile(const FileSystemConnection& aConnection,
    253                           const FileSystemEntryMetadata& aHandle,
    254                           const Name& aNewName, const ContentType& aNewType) {
    255  const nsLiteralCString updateFileTypeAndNameQuery =
    256      "UPDATE Files SET type = :type, name = :name "
    257      "WHERE handle = :handle ;"_ns;
    258 
    259  const nsLiteralCString updateFileNameQuery =
    260      "UPDATE Files SET name = :name WHERE handle = :handle ;"_ns;
    261 
    262  if (aNewType.IsVoid()) {
    263    return PerformRename(aConnection, aHandle, aNewName, aNewType,
    264                         updateFileNameQuery);
    265  }
    266 
    267  return PerformRename(aConnection, aHandle, aNewName, aNewType,
    268                       updateFileTypeAndNameQuery);
    269 }
    270 
    271 template <class HandlerType>
    272 nsresult SetUsageTrackingImpl(const FileSystemConnection& aConnection,
    273                              const FileId& aFileId, bool aTracked,
    274                              HandlerType&& aOnMissingFile) {
    275  const nsLiteralCString setTrackedQuery =
    276      "INSERT INTO Usages "
    277      "( handle, tracked ) "
    278      "VALUES "
    279      "( :handle, :tracked ) "
    280      "ON CONFLICT(handle) DO "
    281      "UPDATE SET tracked = excluded.tracked "
    282      ";"_ns;
    283 
    284  const nsresult customReturnValue =
    285      aTracked ? NS_ERROR_DOM_NOT_FOUND_ERR : NS_OK;
    286 
    287  QM_TRY_UNWRAP(ResultStatement stmt,
    288                ResultStatement::Create(aConnection, setTrackedQuery));
    289  QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId)));
    290  QM_TRY(MOZ_TO_RESULT(stmt.BindBooleanByName("tracked"_ns, aTracked)));
    291  QM_TRY(MOZ_TO_RESULT(stmt.Execute()), customReturnValue,
    292         std::forward<HandlerType>(aOnMissingFile));
    293 
    294  return NS_OK;
    295 }
    296 
    297 Result<nsTArray<FileId>, QMResult> GetTrackedFiles(
    298    const FileSystemConnection& aConnection) {
    299  // The same query works for both 001 and 002 schemas because handle is
    300  // an entry id and later on a file id, respectively.
    301  static const nsLiteralCString getTrackedFilesQuery =
    302      "SELECT handle FROM Usages WHERE tracked = TRUE;"_ns;
    303  nsTArray<FileId> trackedFiles;
    304 
    305  QM_TRY_UNWRAP(ResultStatement stmt,
    306                ResultStatement::Create(aConnection, getTrackedFilesQuery));
    307  QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
    308 
    309  while (moreResults) {
    310    QM_TRY_UNWRAP(FileId fileId, stmt.GetFileIdByColumn(/* Column */ 0u));
    311 
    312    trackedFiles.AppendElement(fileId);  // TODO: fallible?
    313 
    314    QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
    315  }
    316 
    317  return trackedFiles;
    318 }
    319 
    320 /** This handles the file not found error by assigning 0 usage to the dangling
    321 * handle and puts the handle to a non-tracked state. Otherwise, when the
    322 * file or database cannot be reached, the file remains in the tracked state.
    323 */
    324 template <class QuotaCacheUpdate>
    325 nsresult UpdateUsageForFileEntry(const FileSystemConnection& aConnection,
    326                                 const FileSystemFileManager& aFileManager,
    327                                 const FileId& aFileId,
    328                                 const nsLiteralCString& aUpdateQuery,
    329                                 QuotaCacheUpdate&& aUpdateCache) {
    330  QM_TRY_INSPECT(const auto& fileHandle, aFileManager.GetFile(aFileId));
    331 
    332  // A file could have changed in a way which doesn't allow to read its size.
    333  QM_TRY_UNWRAP(
    334      const Usage fileSize,
    335      QM_OR_ELSE_WARN_IF(
    336          // Expression.
    337          MOZ_TO_RESULT_INVOKE_MEMBER(fileHandle, GetFileSize),
    338          // Predicate.
    339          ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
    340          // Fallback. If the file does no longer exist, treat it as 0-sized.
    341          ErrToDefaultOk<Usage>));
    342 
    343  QM_TRY(MOZ_TO_RESULT(aUpdateCache(fileSize)));
    344 
    345  // No transaction as one statement succeeds or fails atomically
    346  QM_TRY_UNWRAP(ResultStatement stmt,
    347                ResultStatement::Create(aConnection, aUpdateQuery));
    348 
    349  QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId)));
    350 
    351  QM_TRY(MOZ_TO_RESULT(stmt.BindUsageByName("usage"_ns, fileSize)));
    352 
    353  QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
    354 
    355  return NS_OK;
    356 }
    357 
    358 nsresult UpdateUsageUnsetTracked(const FileSystemConnection& aConnection,
    359                                 const FileSystemFileManager& aFileManager,
    360                                 const FileId& aFileId) {
    361  static const nsLiteralCString updateUsagesUnsetTrackedQuery =
    362      "UPDATE Usages SET usage = :usage, tracked = FALSE "
    363      "WHERE handle = :handle;"_ns;
    364 
    365  auto noCacheUpdateNeeded = [](auto) { return NS_OK; };
    366 
    367  return UpdateUsageForFileEntry(aConnection, aFileManager, aFileId,
    368                                 updateUsagesUnsetTrackedQuery,
    369                                 std::move(noCacheUpdateNeeded));
    370 }
    371 
    372 /**
    373 * @brief Get the recorded usage only if the file is in tracked state.
    374 * During origin initialization, if the usage on disk is unreadable, the latest
    375 * recorded usage is reported to the quota manager for the tracked files.
    376 * To allow writing, we attempt to update the real usage with one database and
    377 * one file size query.
    378 */
    379 Result<Maybe<Usage>, QMResult> GetMaybeTrackedUsage(
    380    const FileSystemConnection& aConnection, const FileId& aFileId) {
    381  const nsLiteralCString trackedUsageQuery =
    382      "SELECT usage FROM Usages WHERE tracked = TRUE AND handle = :handle "
    383      ");"_ns;
    384 
    385  QM_TRY_UNWRAP(ResultStatement stmt,
    386                ResultStatement::Create(aConnection, trackedUsageQuery));
    387  QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId)));
    388 
    389  QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep());
    390  if (!moreResults) {
    391    return Maybe<Usage>(Nothing());
    392  }
    393 
    394  QM_TRY_UNWRAP(Usage trackedUsage, stmt.GetUsageByColumn(/* Column */ 0u));
    395 
    396  return Some(trackedUsage);
    397 }
    398 
    399 Result<bool, nsresult> ScanTrackedFiles(
    400    const FileSystemConnection& aConnection,
    401    const FileSystemFileManager& aFileManager) {
    402  QM_TRY_INSPECT(const nsTArray<FileId>& trackedFiles,
    403                 GetTrackedFiles(aConnection).mapErr(toNSResult));
    404 
    405  bool ok = true;
    406  for (const auto& fileId : trackedFiles) {
    407    // On success, tracked is set to false, otherwise its value is kept (= true)
    408    QM_WARNONLY_TRY(MOZ_TO_RESULT(UpdateUsageUnsetTracked(
    409                        aConnection, aFileManager, fileId)),
    410                    [&ok](const auto& /*aRv*/) { ok = false; });
    411  }
    412 
    413  return ok;
    414 }
    415 
    416 Result<Ok, QMResult> DeleteEntry(const FileSystemConnection& aConnection,
    417                                 const EntryId& aEntryId) {
    418  // If it's a directory, deleting the handle will cascade
    419  const nsLiteralCString deleteEntryQuery =
    420      "DELETE FROM Entries "
    421      "WHERE handle = :handle "
    422      ";"_ns;
    423 
    424  QM_TRY_UNWRAP(ResultStatement stmt,
    425                ResultStatement::Create(aConnection, deleteEntryQuery));
    426 
    427  QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
    428 
    429  QM_TRY(QM_TO_RESULT(stmt.Execute()));
    430 
    431  return Ok{};
    432 }
    433 
    434 Result<int32_t, QMResult> GetTrackedFilesCount(
    435    const FileSystemConnection& aConnection) {
    436  // TODO: We could query the count directly
    437  QM_TRY_INSPECT(const auto& trackedFiles, GetTrackedFiles(aConnection));
    438 
    439  CheckedInt32 checkedFileCount = trackedFiles.Length();
    440  QM_TRY(OkIf(checkedFileCount.isValid()),
    441         Err(QMResult(NS_ERROR_ILLEGAL_VALUE)));
    442 
    443  return checkedFileCount.value();
    444 }
    445 
    446 void LogWithFilename(const FileSystemFileManager& aFileManager,
    447                     const char* aFormat, const FileId& aFileId) {
    448  if (!LOG_ENABLED()) {
    449    return;
    450  }
    451 
    452  QM_TRY_INSPECT(const auto& localFile, aFileManager.GetFile(aFileId), QM_VOID);
    453 
    454  nsAutoString localPath;
    455  QM_TRY(MOZ_TO_RESULT(localFile->GetPath(localPath)), QM_VOID);
    456  LOG((aFormat, NS_ConvertUTF16toUTF8(localPath).get()));
    457 }
    458 
    459 Result<bool, QMResult> IsAnyDescendantLocked(
    460    const FileSystemConnection& aConnection,
    461    const FileSystemDataManager& aDataManager, const EntryId& aEntryId) {
    462  constexpr const nsLiteralCString descendantsQuery =
    463      "WITH RECURSIVE traceChildren(handle, parent) AS ( "
    464      "SELECT handle, parent "
    465      "FROM Entries "
    466      "WHERE handle=:handle "
    467      "UNION "
    468      "SELECT Entries.handle, Entries.parent FROM traceChildren, Entries "
    469      "WHERE traceChildren.handle=Entries.parent ) "
    470      "SELECT handle, Files.name "
    471      "FROM traceChildren INNER JOIN Files USING (handle) "
    472      ";"_ns;
    473 
    474  QM_TRY_UNWRAP(ResultStatement stmt,
    475                ResultStatement::Create(aConnection, descendantsQuery));
    476  QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
    477  QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
    478 
    479  while (moreResults) {
    480    // Works only for version 001
    481    QM_TRY_INSPECT(const EntryId& entryId,
    482                   stmt.GetEntryIdByColumn(/* Column */ 0u));
    483 
    484    QM_TRY_UNWRAP(const bool isLocked, aDataManager.IsLocked(entryId), true);
    485    if (isLocked) {
    486      return true;
    487    }
    488 
    489    QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
    490  }
    491 
    492  return false;
    493 }
    494 
    495 }  // namespace
    496 
    497 FileSystemDatabaseManagerVersion001::FileSystemDatabaseManagerVersion001(
    498    FileSystemDataManager* aDataManager, FileSystemConnection&& aConnection,
    499    UniquePtr<FileSystemFileManager>&& aFileManager, const EntryId& aRootEntry)
    500    : mDataManager(aDataManager),
    501      mConnection(aConnection),
    502      mFileManager(std::move(aFileManager)),
    503      mRootEntry(aRootEntry),
    504      mClientMetadata(aDataManager->OriginMetadataRef(),
    505                      quota::Client::FILESYSTEM),
    506      mFilesOfUnknownUsage(-1) {}
    507 
    508 /* static */
    509 nsresult FileSystemDatabaseManagerVersion001::RescanTrackedUsages(
    510    const FileSystemConnection& aConnection,
    511    const quota::OriginMetadata& aOriginMetadata) {
    512  QM_TRY_UNWRAP(UniquePtr<FileSystemFileManager> fileManager,
    513                data::FileSystemFileManager::CreateFileSystemFileManager(
    514                    aOriginMetadata));
    515 
    516  QM_TRY_UNWRAP(bool ok, ScanTrackedFiles(aConnection, *fileManager));
    517  if (ok) {
    518    return NS_OK;
    519  }
    520 
    521  // Retry once without explicit delay
    522  QM_TRY_UNWRAP(ok, ScanTrackedFiles(aConnection, *fileManager));
    523  if (!ok) {
    524    return NS_ERROR_UNEXPECTED;
    525  }
    526 
    527  return NS_OK;
    528 }
    529 
    530 /* static */
    531 Result<Usage, QMResult> FileSystemDatabaseManagerVersion001::GetFileUsage(
    532    const FileSystemConnection& aConnection) {
    533  const nsLiteralCString sumUsagesQuery = "SELECT sum(usage) FROM Usages;"_ns;
    534 
    535  QM_TRY_UNWRAP(ResultStatement stmt,
    536                ResultStatement::Create(aConnection, sumUsagesQuery));
    537 
    538  QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep());
    539  if (!moreResults) {
    540    return Err(QMResult(NS_ERROR_DOM_FILE_NOT_READABLE_ERR));
    541  }
    542 
    543  QM_TRY_UNWRAP(Usage totalFiles, stmt.GetUsageByColumn(/* Column */ 0u));
    544 
    545  return totalFiles;
    546 }
    547 
    548 Result<quota::UsageInfo, QMResult>
    549 FileSystemDatabaseManagerVersion001::GetUsage() const {
    550  return FileSystemDatabaseManager::GetUsage(mConnection, mClientMetadata);
    551 }
    552 
    553 nsresult FileSystemDatabaseManagerVersion001::UpdateUsage(
    554    const FileId& aFileId) {
    555  // We don't track directories or non-existent files.
    556  QM_TRY_UNWRAP(bool fileExists, DoesFileIdExist(aFileId).mapErr(toNSResult));
    557  if (!fileExists) {
    558    return NS_OK;  // May be deleted before update, no assert
    559  }
    560 
    561  QM_TRY_UNWRAP(nsCOMPtr<nsIFile> file, mFileManager->GetFile(aFileId));
    562  MOZ_ASSERT(file);
    563 
    564  Usage fileSize = 0;
    565  bool exists = false;
    566  QM_TRY(MOZ_TO_RESULT(file->Exists(&exists)));
    567  if (exists) {
    568    QM_TRY(MOZ_TO_RESULT(file->GetFileSize(&fileSize)));
    569  }
    570 
    571  QM_TRY(MOZ_TO_RESULT(UpdateUsageInDatabase(aFileId, fileSize)));
    572 
    573  return NS_OK;
    574 }
    575 
    576 Result<EntryId, QMResult>
    577 FileSystemDatabaseManagerVersion001::GetOrCreateDirectory(
    578    const FileSystemChildMetadata& aHandle, bool aCreate) {
    579  MOZ_ASSERT(!aHandle.parentId().IsEmpty());
    580 
    581  const auto& name = aHandle.childName();
    582  // Belt and suspenders: check here as well as in child.
    583  if (!IsValidName(name)) {
    584    return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR));
    585  }
    586  MOZ_ASSERT(!(name.IsVoid() || name.IsEmpty()));
    587 
    588  bool exists = true;
    589  QM_TRY_UNWRAP(exists, data::DoesFileExist(mConnection, aHandle));
    590 
    591  // By spec, we don't allow a file and a directory
    592  // to have the same name and parent
    593  if (exists) {
    594    return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR));
    595  }
    596 
    597  QM_TRY_UNWRAP(exists, DoesDirectoryExist(mConnection, aHandle));
    598 
    599  // exists as directory
    600  if (exists) {
    601    return FindEntryId(mConnection, aHandle, false);
    602  }
    603 
    604  if (!aCreate) {
    605    return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
    606  }
    607 
    608  const nsLiteralCString insertEntryQuery =
    609      "INSERT OR IGNORE INTO Entries "
    610      "( handle, parent ) "
    611      "VALUES "
    612      "( :handle, :parent ) "
    613      ";"_ns;
    614 
    615  const nsLiteralCString insertDirectoryQuery =
    616      "INSERT OR IGNORE INTO Directories "
    617      "( handle, name ) "
    618      "VALUES "
    619      "( :handle, :name ) "
    620      ";"_ns;
    621 
    622  QM_TRY_INSPECT(const EntryId& entryId, GetEntryId(aHandle));
    623  MOZ_ASSERT(!entryId.IsEmpty());
    624 
    625  QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
    626 
    627  {
    628    QM_TRY_UNWRAP(ResultStatement stmt,
    629                  ResultStatement::Create(mConnection, insertEntryQuery));
    630    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
    631    QM_TRY(
    632        QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId())));
    633    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    634  }
    635 
    636  {
    637    QM_TRY_UNWRAP(ResultStatement stmt,
    638                  ResultStatement::Create(mConnection, insertDirectoryQuery));
    639    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
    640    QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, name)));
    641    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    642  }
    643 
    644  QM_TRY(QM_TO_RESULT(transaction.Commit()));
    645 
    646  QM_TRY_UNWRAP(DebugOnly<bool> doesItExistNow,
    647                DoesDirectoryExist(mConnection, aHandle));
    648  MOZ_ASSERT(doesItExistNow);
    649 
    650  return entryId;
    651 }
    652 
    653 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetOrCreateFile(
    654    const FileSystemChildMetadata& aHandle, bool aCreate) {
    655  MOZ_ASSERT(!aHandle.parentId().IsEmpty());
    656 
    657  const auto& name = aHandle.childName();
    658  // Belt and suspenders: check here as well as in child.
    659  if (!IsValidName(name)) {
    660    return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR));
    661  }
    662  MOZ_ASSERT(!(name.IsVoid() || name.IsEmpty()));
    663 
    664  QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(mConnection, aHandle));
    665 
    666  // By spec, we don't allow a file and a directory
    667  // to have the same name and parent
    668  QM_TRY(OkIf(!exists), Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR)));
    669 
    670  QM_TRY_UNWRAP(exists, data::DoesFileExist(mConnection, aHandle));
    671 
    672  if (exists) {
    673    QM_TRY_RETURN(FindEntryId(mConnection, aHandle, /* aIsFile */ true));
    674  }
    675 
    676  if (!aCreate) {
    677    return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
    678  }
    679 
    680  const nsLiteralCString insertEntryQuery =
    681      "INSERT INTO Entries "
    682      "( handle, parent ) "
    683      "VALUES "
    684      "( :handle, :parent ) "
    685      ";"_ns;
    686 
    687  const nsLiteralCString insertFileQuery =
    688      "INSERT INTO Files "
    689      "( handle, type, name ) "
    690      "VALUES "
    691      "( :handle, :type, :name ) "
    692      ";"_ns;
    693 
    694  QM_TRY_INSPECT(const EntryId& entryId, GetEntryId(aHandle));
    695  MOZ_ASSERT(!entryId.IsEmpty());
    696 
    697  const ContentType type = DetermineContentType(name);
    698 
    699  QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
    700 
    701  {
    702    QM_TRY_UNWRAP(ResultStatement stmt,
    703                  ResultStatement::Create(mConnection, insertEntryQuery));
    704    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
    705    QM_TRY(
    706        QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId())));
    707    QM_TRY(QM_TO_RESULT(stmt.Execute()), QM_PROPAGATE,
    708           ([this, &aHandle](const auto& aRv) {
    709             QM_TRY_UNWRAP(bool parentExists,
    710                           DoesDirectoryExist(mConnection, aHandle.parentId()),
    711                           QM_VOID);
    712             QM_TRY(OkIf(parentExists), QM_VOID);
    713           }));
    714  }
    715 
    716  {
    717    QM_TRY_UNWRAP(ResultStatement stmt,
    718                  ResultStatement::Create(mConnection, insertFileQuery));
    719    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
    720    QM_TRY(QM_TO_RESULT(stmt.BindContentTypeByName("type"_ns, type)));
    721    QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, name)));
    722    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    723  }
    724 
    725  QM_TRY(QM_TO_RESULT(transaction.Commit()));
    726 
    727  return entryId;
    728 }
    729 
    730 nsresult FileSystemDatabaseManagerVersion001::GetFile(
    731    const EntryId& aEntryId, const FileId& aFileId, const FileMode& aMode,
    732    ContentType& aType, TimeStamp& lastModifiedMilliSeconds,
    733    nsTArray<Name>& aPath, nsCOMPtr<nsIFile>& aFile) const {
    734  MOZ_ASSERT(!aFileId.IsEmpty());
    735  MOZ_ASSERT(aMode == FileMode::EXCLUSIVE);
    736 
    737  const FileSystemEntryPair endPoints(mRootEntry, aEntryId);
    738  QM_TRY_UNWRAP(aPath, ResolveReversedPath(mConnection, endPoints));
    739  if (aPath.IsEmpty()) {
    740    return NS_ERROR_DOM_NOT_FOUND_ERR;
    741  }
    742 
    743  QM_TRY(MOZ_TO_RESULT(GetFileAttributes(mConnection, aEntryId, aType)));
    744  QM_TRY_UNWRAP(aFile, mFileManager->GetOrCreateFile(aFileId));
    745 
    746  PRTime lastModTime = 0;
    747  QM_TRY(MOZ_TO_RESULT(aFile->GetLastModifiedTime(&lastModTime)));
    748  lastModifiedMilliSeconds = static_cast<TimeStamp>(lastModTime);
    749 
    750  aPath.Reverse();
    751 
    752  return NS_OK;
    753 }
    754 
    755 Result<FileSystemDirectoryListing, QMResult>
    756 FileSystemDatabaseManagerVersion001::GetDirectoryEntries(
    757    const EntryId& aParent, PageNumber aPage) const {
    758  // TODO: Offset is reported to have bad performance - see Bug 1780386.
    759  const nsCString directoriesQuery =
    760      "SELECT Dirs.handle, Dirs.name "
    761      "FROM Directories AS Dirs "
    762      "INNER JOIN ( "
    763      "SELECT handle "
    764      "FROM Entries "
    765      "WHERE parent = :parent "
    766      "LIMIT :pageSize "
    767      "OFFSET :pageOffset ) "
    768      "AS Ents "
    769      "ON Dirs.handle = Ents.handle "
    770      ";"_ns;
    771  const nsCString filesQuery =
    772      "SELECT Files.handle, Files.name "
    773      "FROM Files "
    774      "INNER JOIN ( "
    775      "SELECT handle "
    776      "FROM Entries "
    777      "WHERE parent = :parent "
    778      "LIMIT :pageSize "
    779      "OFFSET :pageOffset ) "
    780      "AS Ents "
    781      "ON Files.handle = Ents.handle "
    782      ";"_ns;
    783 
    784  FileSystemDirectoryListing entries;
    785  QM_TRY(
    786      QM_TO_RESULT(GetEntries(mConnection, directoriesQuery, aParent, aPage,
    787                              /* aDirectory */ true, entries.directories())));
    788 
    789  QM_TRY(QM_TO_RESULT(GetEntries(mConnection, filesQuery, aParent, aPage,
    790                                 /* aDirectory */ false, entries.files())));
    791 
    792  return entries;
    793 }
    794 
    795 Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveDirectoryImpl(
    796    const EntryId& entryId) {
    797  using FileIdArrayAndUsage = std::pair<nsTArray<FileId>, Usage>;
    798 
    799  // Caller has checked that there are no exclusively locked descendants.
    800  QM_TRY_INSPECT(const FileIdArrayAndUsage& unlockedFileInfo,
    801                 FindFilesWithoutDeprecatedLocksUnderEntry(entryId));
    802 
    803  const nsTArray<FileId>& descendants = unlockedFileInfo.first;
    804  // We remove the files which have no open WritableFileStream child actors.
    805  // The files left behind get removed at the end of the child actor lifecycle.
    806  nsTArray<FileId> failedRemovals;
    807  QM_TRY_UNWRAP(DebugOnly<Usage> removedUsage,
    808                mFileManager->RemoveFiles(descendants, failedRemovals));
    809 
    810  const Usage usage = unlockedFileInfo.second;
    811  // Usage is for the current main file but we remove temporary files too.
    812  MOZ_ASSERT_IF(failedRemovals.IsEmpty() && (0 == mFilesOfUnknownUsage),
    813                usage <= removedUsage);
    814 
    815  TryRemoveDuringIdleMaintenance(failedRemovals);
    816 
    817  auto isInFailedRemovals = [&failedRemovals](const auto& aFileId) {
    818    return failedRemovals.cend() !=
    819           std::find_if(failedRemovals.cbegin(), failedRemovals.cend(),
    820                        [&aFileId](const auto& aFailedRemoval) {
    821                          return aFileId == aFailedRemoval;
    822                        });
    823  };
    824 
    825  for (const auto& fileId : descendants) {
    826    if (!isInFailedRemovals(fileId)) {
    827      QM_WARNONLY_TRY(QM_TO_RESULT(RemoveFileId(fileId)));
    828    }
    829  }
    830 
    831  if (usage > 0) {  // Performance!
    832    DecreaseCachedQuotaUsage(usage);
    833  }
    834 
    835  QM_TRY(DeleteEntry(mConnection, entryId));
    836 
    837  return true;
    838 }
    839 
    840 Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveDirectory(
    841    const FileSystemChildMetadata& aHandle, bool aRecursive) {
    842  MOZ_ASSERT(!aHandle.parentId().IsEmpty());
    843 
    844  if (aHandle.childName().IsEmpty()) {
    845    return false;
    846  }
    847 
    848  DebugOnly<Name> name = aHandle.childName();
    849  MOZ_ASSERT(!name.inspect().IsVoid());
    850 
    851  QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(mConnection, aHandle));
    852 
    853  if (!exists) {
    854    return false;
    855  }
    856 
    857  // At this point, entry exists and is a directory.
    858  QM_TRY_UNWRAP(EntryId entryId, FindEntryId(mConnection, aHandle, false));
    859  MOZ_ASSERT(!entryId.IsEmpty());
    860 
    861  QM_TRY_UNWRAP(bool isEmpty, IsDirectoryEmpty(mConnection, entryId));
    862 
    863  MOZ_ASSERT(mDataManager);
    864  QM_TRY_UNWRAP(const bool isLocked,
    865                IsAnyDescendantLocked(mConnection, *mDataManager, entryId));
    866 
    867  // XXX If there are any locked file entries, we exit here.
    868  // There has been talk that the spec might allow unconditional
    869  // overwrites in the future.
    870  QM_TRY(OkIf(!isLocked),
    871         Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)));
    872 
    873  if (!aRecursive && !isEmpty) {
    874    return Err(QMResult(NS_ERROR_DOM_INVALID_MODIFICATION_ERR));
    875  }
    876 
    877  QM_TRY_RETURN(RemoveDirectoryImpl(entryId));
    878 }
    879 
    880 Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveFile(
    881    const FileSystemChildMetadata& aHandle) {
    882  MOZ_ASSERT(!aHandle.parentId().IsEmpty());
    883 
    884  if (aHandle.childName().IsEmpty()) {
    885    return false;
    886  }
    887 
    888  DebugOnly<Name> name = aHandle.childName();
    889  MOZ_ASSERT(!name.inspect().IsVoid());
    890 
    891  // Make it more evident that we won't remove directories
    892  QM_TRY_UNWRAP(bool exists, data::DoesFileExist(mConnection, aHandle));
    893 
    894  if (!exists) {
    895    return false;
    896  }
    897 
    898  // At this point, entry exists and is a file
    899  QM_TRY_UNWRAP(EntryId entryId, FindEntryId(mConnection, aHandle, true));
    900  MOZ_ASSERT(!entryId.IsEmpty());
    901 
    902  // XXX This code assumes the spec question is resolved to state
    903  // removing an in-use file should fail.  If it shouldn't fail, we need to
    904  // do something to neuter all the extant FileAccessHandles/WritableFileStreams
    905  // that reference it
    906  QM_TRY_UNWRAP(const bool isLocked, mDataManager->IsLocked(entryId));
    907  if (isLocked) {
    908    LOG(("Trying to remove in-use file"));
    909    return Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR));
    910  }
    911 
    912  using FileIdArrayAndUsage = std::pair<nsTArray<FileId>, Usage>;
    913  QM_TRY_INSPECT(const FileIdArrayAndUsage& unlockedFileInfo,
    914                 FindFilesWithoutDeprecatedLocksUnderEntry(entryId));
    915 
    916  const nsTArray<FileId>& diskItems = unlockedFileInfo.first;
    917  nsTArray<FileId> failedRemovals;
    918  QM_TRY_UNWRAP(DebugOnly<Usage> removedUsage,
    919                mFileManager->RemoveFiles(diskItems, failedRemovals));
    920 
    921  const Usage usage = unlockedFileInfo.second;
    922  // We only check the most common case. This can fail spuriously if an external
    923  // application writes to the file, or OS reports zero size due to corruption.
    924  MOZ_ASSERT_IF(failedRemovals.IsEmpty() && (0 == mFilesOfUnknownUsage),
    925                usage == removedUsage);
    926 
    927  TryRemoveDuringIdleMaintenance(failedRemovals);
    928 
    929  auto isInFailedRemovals = [&failedRemovals](const auto& aFileId) {
    930    return failedRemovals.cend() !=
    931           std::find_if(failedRemovals.cbegin(), failedRemovals.cend(),
    932                        [&aFileId](const auto& aFailedRemoval) {
    933                          return aFileId == aFailedRemoval;
    934                        });
    935  };
    936 
    937  for (const auto& fileId : diskItems) {
    938    if (!isInFailedRemovals(fileId)) {
    939      QM_WARNONLY_TRY(QM_TO_RESULT(RemoveFileId(fileId)));
    940    }
    941  }
    942 
    943  if (usage > 0) {  // Performance!
    944    DecreaseCachedQuotaUsage(usage);
    945  }
    946 
    947  QM_TRY(DeleteEntry(mConnection, entryId));
    948 
    949  return true;
    950 }
    951 
    952 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::RenameEntry(
    953    const FileSystemEntryMetadata& aHandle, const Name& aNewName) {
    954  const auto& entryId = aHandle.entryId();
    955 
    956  // Can't rename root
    957  if (mRootEntry == entryId) {
    958    return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
    959  }
    960 
    961  // Verify the source exists
    962  QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId),
    963                Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)));
    964 
    965  // Are we actually renaming?
    966  if (aHandle.entryName() == aNewName) {
    967    return entryId;
    968  }
    969 
    970  QM_TRY(QM_TO_RESULT(PrepareRenameEntry(mConnection, mDataManager, aHandle,
    971                                         aNewName, isFile)));
    972 
    973  QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
    974 
    975  if (isFile) {
    976    const ContentType type = DetermineContentType(aNewName);
    977    QM_TRY(
    978        QM_TO_RESULT(PerformRenameFile(mConnection, aHandle, aNewName, type)));
    979  } else {
    980    QM_TRY(
    981        QM_TO_RESULT(PerformRenameDirectory(mConnection, aHandle, aNewName)));
    982  }
    983 
    984  QM_TRY(QM_TO_RESULT(transaction.Commit()));
    985 
    986  return entryId;
    987 }
    988 
    989 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::MoveEntry(
    990    const FileSystemEntryMetadata& aHandle,
    991    const FileSystemChildMetadata& aNewDesignation) {
    992  const auto& entryId = aHandle.entryId();
    993  MOZ_ASSERT(!entryId.IsEmpty());
    994 
    995  if (mRootEntry == entryId) {
    996    return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
    997  }
    998 
    999  // Verify the source exists
   1000  QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId),
   1001                Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)));
   1002 
   1003  // If the rename doesn't change the name or directory, just return success.
   1004  // XXX Needs to be added to the spec
   1005  QM_WARNONLY_TRY_UNWRAP(Maybe<bool> maybeSame,
   1006                         IsSame(mConnection, aHandle, aNewDesignation, isFile));
   1007  if (maybeSame && maybeSame.value()) {
   1008    return entryId;
   1009  }
   1010 
   1011  QM_TRY(QM_TO_RESULT(PrepareMoveEntry(mConnection, mDataManager, aHandle,
   1012                                       aNewDesignation, isFile)));
   1013 
   1014  const nsLiteralCString updateEntryParentQuery =
   1015      "UPDATE Entries "
   1016      "SET parent = :parent "
   1017      "WHERE handle = :handle "
   1018      ";"_ns;
   1019 
   1020  QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
   1021 
   1022  {
   1023    // We always change the parent because it's simpler than checking if the
   1024    // parent needs to be changed
   1025    QM_TRY_UNWRAP(ResultStatement stmt,
   1026                  ResultStatement::Create(mConnection, updateEntryParentQuery));
   1027    QM_TRY(QM_TO_RESULT(
   1028        stmt.BindEntryIdByName("parent"_ns, aNewDesignation.parentId())));
   1029    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
   1030    QM_TRY(QM_TO_RESULT(stmt.Execute()));
   1031  }
   1032 
   1033  const Name& newName = aNewDesignation.childName();
   1034 
   1035  // Are we actually renaming?
   1036  if (aHandle.entryName() == newName) {
   1037    QM_TRY(QM_TO_RESULT(transaction.Commit()));
   1038 
   1039    return entryId;
   1040  }
   1041 
   1042  if (isFile) {
   1043    const ContentType type = DetermineContentType(newName);
   1044    QM_TRY(
   1045        QM_TO_RESULT(PerformRenameFile(mConnection, aHandle, newName, type)));
   1046  } else {
   1047    QM_TRY(QM_TO_RESULT(PerformRenameDirectory(mConnection, aHandle, newName)));
   1048  }
   1049 
   1050  QM_TRY(QM_TO_RESULT(transaction.Commit()));
   1051 
   1052  return entryId;
   1053 }
   1054 
   1055 Result<Path, QMResult> FileSystemDatabaseManagerVersion001::Resolve(
   1056    const FileSystemEntryPair& aEndpoints) const {
   1057  QM_TRY_UNWRAP(Path path, ResolveReversedPath(mConnection, aEndpoints));
   1058  // Note: if not an ancestor, returns null
   1059 
   1060  path.Reverse();
   1061  return path;
   1062 }
   1063 
   1064 Result<bool, QMResult> FileSystemDatabaseManagerVersion001::DoesFileExist(
   1065    const EntryId& aEntryId) const {
   1066  MOZ_ASSERT(!aEntryId.IsEmpty());
   1067 
   1068  QM_TRY_RETURN(data::DoesFileExist(mConnection, aEntryId));
   1069 }
   1070 
   1071 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetEntryId(
   1072    const FileSystemChildMetadata& aHandle) const {
   1073  return GetUniqueEntryId(mConnection, aHandle);
   1074 }
   1075 
   1076 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetEntryId(
   1077    const FileId& aFileId) const {
   1078  return aFileId.Value();
   1079 }
   1080 
   1081 Result<FileId, QMResult> FileSystemDatabaseManagerVersion001::EnsureFileId(
   1082    const EntryId& aEntryId) {
   1083  return FileId(aEntryId);
   1084 }
   1085 
   1086 Result<FileId, QMResult>
   1087 FileSystemDatabaseManagerVersion001::EnsureTemporaryFileId(
   1088    const EntryId& aEntryId) {
   1089  return FileId(aEntryId);
   1090 }
   1091 
   1092 Result<FileId, QMResult> FileSystemDatabaseManagerVersion001::GetFileId(
   1093    const EntryId& aEntryId) const {
   1094  return FileId(aEntryId);
   1095 }
   1096 
   1097 nsresult FileSystemDatabaseManagerVersion001::MergeFileId(
   1098    const EntryId& /* aEntryId */, const FileId& /* aFileId */,
   1099    bool /* aAbort */) {
   1100  // Version 001 should always use exclusive mode and not get here.
   1101  return NS_ERROR_NOT_IMPLEMENTED;
   1102 }
   1103 
   1104 void FileSystemDatabaseManagerVersion001::Close() { mConnection->Close(); }
   1105 
   1106 nsresult FileSystemDatabaseManagerVersion001::BeginUsageTracking(
   1107    const FileId& aFileId) {
   1108  MOZ_ASSERT(!aFileId.IsEmpty());
   1109 
   1110  // If file is already tracked but we cannot read its size, error.
   1111  // If file does not exist, this will succeed because usage is zero.
   1112  QM_TRY(EnsureUsageIsKnown(aFileId));
   1113 
   1114  // If file does not exist, set usage tracking to true fails with
   1115  // file not found error.
   1116  QM_TRY(MOZ_TO_RESULT(SetUsageTracking(aFileId, true)));
   1117 
   1118  return NS_OK;
   1119 }
   1120 
   1121 nsresult FileSystemDatabaseManagerVersion001::EndUsageTracking(
   1122    const FileId& aFileId) {
   1123  // This is expected to fail only if database is unreachable.
   1124  QM_TRY(MOZ_TO_RESULT(SetUsageTracking(aFileId, false)));
   1125 
   1126  return NS_OK;
   1127 }
   1128 
   1129 Result<bool, QMResult> FileSystemDatabaseManagerVersion001::DoesFileIdExist(
   1130    const FileId& aFileId) const {
   1131  MOZ_ASSERT(!aFileId.IsEmpty());
   1132 
   1133  QM_TRY_RETURN(DoesFileExist(aFileId.Value()));
   1134 }
   1135 
   1136 nsresult FileSystemDatabaseManagerVersion001::RemoveFileId(
   1137    const FileId& /* aFileId */) {
   1138  return NS_OK;
   1139 }
   1140 
   1141 /**
   1142 * @brief Get the sum of usages for all file descendants of a directory entry.
   1143 * We obtain the values with one query, which is presumably better than having a
   1144 * separate query for each individual descendant.
   1145 * TODO: Check if this is true
   1146 *
   1147 * Please see GetFileUsage documentation for why we use the latest recorded
   1148 * value from the database instead of the file size property from the disk.
   1149 */
   1150 Result<std::pair<nsTArray<FileId>, Usage>, QMResult>
   1151 FileSystemDatabaseManagerVersion001::FindFilesWithoutDeprecatedLocksUnderEntry(
   1152    const EntryId& aEntryId) const {
   1153  nsTArray<FileId> descendants;
   1154  Usage usage{0};
   1155  {
   1156    QM_TRY_UNWRAP(ResultStatement stmt,
   1157                  ResultStatement::Create(mConnection, gDescendantsQuery));
   1158    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
   1159    QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
   1160 
   1161    while (true) {
   1162      if (!moreResults) {
   1163        break;
   1164      }
   1165 
   1166      // Works only for version 001
   1167      QM_TRY_INSPECT(const FileId& fileId,
   1168                     stmt.GetFileIdByColumn(/* Column */ 0u));
   1169 
   1170      // FileId equals EntryId for version 001
   1171      if (!mDataManager->IsLockedWithDeprecatedSharedLock(fileId.Value(),
   1172                                                          fileId)) {
   1173        QM_TRY_UNWRAP(DebugOnly<bool> isLocked,
   1174                      mDataManager->IsLocked(fileId.Value()));
   1175        MOZ_ASSERT(!isLocked);
   1176        descendants.AppendElement(fileId);
   1177 
   1178        QM_TRY_INSPECT(const Usage& fileUsage,
   1179                       stmt.GetUsageByColumn(/* Column */ 1u));
   1180        usage += fileUsage;
   1181      }
   1182 
   1183      QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
   1184    }
   1185  }
   1186 
   1187  return std::make_pair(std::move(descendants), usage);
   1188 }
   1189 
   1190 Result<nsTArray<std::pair<EntryId, FileId>>, QMResult>
   1191 FileSystemDatabaseManagerVersion001::FindFileEntriesUnderDirectory(
   1192    const EntryId& aEntryId) const {
   1193  return Err(QMResult(NS_ERROR_NOT_IMPLEMENTED));
   1194 }
   1195 
   1196 nsresult FileSystemDatabaseManagerVersion001::SetUsageTracking(
   1197    const FileId& aFileId, bool aTracked) {
   1198  auto onMissingFile = [this, &aFileId](const auto& aRv) {
   1199    // Usages constrains entryId to be present in Files
   1200    MOZ_ASSERT(NS_ERROR_STORAGE_CONSTRAINT == ToNSResult(aRv));
   1201 
   1202    // The query *should* fail if and only if file does not exist
   1203    QM_TRY_UNWRAP(DebugOnly<bool> fileExists, DoesFileIdExist(aFileId),
   1204                  QM_VOID);
   1205    MOZ_ASSERT(!fileExists);
   1206  };
   1207 
   1208  return SetUsageTrackingImpl(mConnection, aFileId, aTracked, onMissingFile);
   1209 }
   1210 
   1211 nsresult FileSystemDatabaseManagerVersion001::UpdateUsageInDatabase(
   1212    const FileId& aFileId, Usage aNewDiskUsage) {
   1213  const nsLiteralCString updateUsageQuery =
   1214      "INSERT INTO Usages "
   1215      "( handle, usage ) "
   1216      "VALUES "
   1217      "( :handle, :usage ) "
   1218      "ON CONFLICT(handle) DO "
   1219      "UPDATE SET usage = excluded.usage "
   1220      ";"_ns;
   1221 
   1222  QM_TRY_UNWRAP(ResultStatement stmt,
   1223                ResultStatement::Create(mConnection, updateUsageQuery));
   1224  QM_TRY(MOZ_TO_RESULT(stmt.BindUsageByName("usage"_ns, aNewDiskUsage)));
   1225  QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId)));
   1226  QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
   1227 
   1228  return NS_OK;
   1229 }
   1230 
   1231 Result<Ok, QMResult> FileSystemDatabaseManagerVersion001::EnsureUsageIsKnown(
   1232    const FileId& aFileId) {
   1233  if (mFilesOfUnknownUsage < 0) {  // Lazy initialization
   1234    QM_TRY_UNWRAP(mFilesOfUnknownUsage, GetTrackedFilesCount(mConnection));
   1235  }
   1236 
   1237  if (mFilesOfUnknownUsage == 0) {
   1238    return Ok{};
   1239  }
   1240 
   1241  QM_TRY_UNWRAP(Maybe<Usage> oldUsage,
   1242                GetMaybeTrackedUsage(mConnection, aFileId));
   1243  if (oldUsage.isNothing()) {
   1244    return Ok{};  // Usage is 0 or it was successfully recorded at unlocking.
   1245  }
   1246 
   1247  auto quotaCacheUpdate = [this, &aFileId,
   1248                           oldSize = oldUsage.value()](Usage aNewSize) {
   1249    return UpdateCachedQuotaUsage(aFileId, oldSize, aNewSize);
   1250  };
   1251 
   1252  static const nsLiteralCString updateUsagesKeepTrackedQuery =
   1253      "UPDATE Usages SET usage = :usage WHERE handle = :handle;"_ns;
   1254 
   1255  // If usage update fails, we log an error and keep things the way they were.
   1256  QM_TRY(QM_TO_RESULT(UpdateUsageForFileEntry(
   1257             mConnection, *mFileManager, aFileId, updateUsagesKeepTrackedQuery,
   1258             std::move(quotaCacheUpdate))),
   1259         Err(QMResult(NS_ERROR_DOM_FILE_NOT_READABLE_ERR)),
   1260         ([this, &aFileId](const auto& /*aRv*/) {
   1261           LogWithFilename(*mFileManager, "Could not read the size of file %s",
   1262                           aFileId);
   1263         }));
   1264 
   1265  // We read and updated the quota usage successfully.
   1266  --mFilesOfUnknownUsage;
   1267  MOZ_ASSERT(mFilesOfUnknownUsage >= 0);
   1268 
   1269  return Ok{};
   1270 }
   1271 
   1272 void FileSystemDatabaseManagerVersion001::DecreaseCachedQuotaUsage(
   1273    int64_t aDelta) {
   1274  quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
   1275  MOZ_ASSERT(quotaManager);
   1276 
   1277  quotaManager->DecreaseUsageForClient(mClientMetadata, aDelta);
   1278 }
   1279 
   1280 nsresult FileSystemDatabaseManagerVersion001::UpdateCachedQuotaUsage(
   1281    const FileId& aFileId, Usage aOldUsage, Usage aNewUsage) const {
   1282  quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
   1283  MOZ_ASSERT(quotaManager);
   1284 
   1285  QM_TRY_UNWRAP(nsCOMPtr<nsIFile> fileObj,
   1286                mFileManager->GetFile(aFileId).mapErr(toNSResult));
   1287 
   1288  RefPtr<quota::QuotaObject> quotaObject = quotaManager->GetQuotaObject(
   1289      quota::PERSISTENCE_TYPE_DEFAULT, mClientMetadata,
   1290      quota::Client::FILESYSTEM, fileObj, aOldUsage);
   1291  MOZ_ASSERT(quotaObject);
   1292 
   1293  QM_TRY(OkIf(quotaObject->MaybeUpdateSize(aNewUsage, /* aTruncate */ true)),
   1294         NS_ERROR_FILE_NO_DEVICE_SPACE);
   1295 
   1296  return NS_OK;
   1297 }
   1298 
   1299 nsresult FileSystemDatabaseManagerVersion001::ClearDestinationIfNotLocked(
   1300    const FileSystemConnection& aConnection,
   1301    const FileSystemDataManager* const aDataManager,
   1302    const FileSystemEntryMetadata& aHandle,
   1303    const FileSystemChildMetadata& aNewDesignation) {
   1304  // If the destination file exists, fail explicitly.  Spec author plans to
   1305  // revise the spec
   1306  QM_TRY_UNWRAP(bool exists, data::DoesFileExist(aConnection, aNewDesignation));
   1307  if (exists) {
   1308    QM_TRY_INSPECT(const EntryId& destId,
   1309                   FindEntryId(aConnection, aNewDesignation, true));
   1310    QM_TRY_UNWRAP(const bool isLocked, aDataManager->IsLocked(destId));
   1311    if (isLocked) {
   1312      LOG(("Trying to overwrite in-use file"));
   1313      return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   1314    }
   1315 
   1316    QM_TRY_UNWRAP(DebugOnly<bool> isRemoved, RemoveFile(aNewDesignation));
   1317    MOZ_ASSERT(isRemoved);
   1318  } else {
   1319    QM_TRY_UNWRAP(exists, DoesDirectoryExist(aConnection, aNewDesignation));
   1320    if (exists) {
   1321      QM_TRY_INSPECT(const EntryId& destId,
   1322                     FindEntryId(aConnection, aNewDesignation, false));
   1323 
   1324      MOZ_ASSERT(aDataManager);
   1325      QM_TRY_UNWRAP(const bool isLocked,
   1326                    IsAnyDescendantLocked(mConnection, *aDataManager, destId));
   1327 
   1328      // XXX If there are any locked file entries, we exit here.
   1329      // There has been talk that the spec might allow unconditional
   1330      // overwrites in the future.
   1331      QM_TRY(OkIf(!isLocked),
   1332             Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)));
   1333 
   1334      QM_TRY_UNWRAP(DebugOnly<bool> isRemoved,
   1335                    MOZ_TO_RESULT(RemoveDirectoryImpl(destId)));
   1336 
   1337      MOZ_ASSERT(isRemoved);
   1338    }
   1339  }
   1340 
   1341  return NS_OK;
   1342 }
   1343 
   1344 nsresult FileSystemDatabaseManagerVersion001::PrepareRenameEntry(
   1345    const FileSystemConnection& aConnection,
   1346    const FileSystemDataManager* const aDataManager,
   1347    const FileSystemEntryMetadata& aHandle, const Name& aNewName,
   1348    bool aIsFile) {
   1349  const EntryId& entryId = aHandle.entryId();
   1350 
   1351  // At this point, entry exists
   1352  if (aIsFile) {
   1353    QM_TRY_UNWRAP(const bool isLocked, aDataManager->IsLocked(entryId));
   1354    if (isLocked) {
   1355      LOG(("Trying to move in-use file"));
   1356      return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   1357    }
   1358  }
   1359 
   1360  // If the destination file exists, fail explicitly.
   1361  FileSystemChildMetadata destination;
   1362  QM_TRY_UNWRAP(EntryId parent, FindParent(mConnection, entryId));
   1363  destination.parentId() = parent;
   1364  destination.childName() = aNewName;
   1365 
   1366  QM_TRY(MOZ_TO_RESULT(ClearDestinationIfNotLocked(mConnection, mDataManager,
   1367                                                   aHandle, destination)));
   1368 
   1369  return NS_OK;
   1370 }
   1371 
   1372 nsresult FileSystemDatabaseManagerVersion001::PrepareMoveEntry(
   1373    const FileSystemConnection& aConnection,
   1374    const FileSystemDataManager* const aDataManager,
   1375    const FileSystemEntryMetadata& aHandle,
   1376    const FileSystemChildMetadata& aNewDesignation, bool aIsFile) {
   1377  const EntryId& entryId = aHandle.entryId();
   1378 
   1379  // At this point, entry exists
   1380  if (aIsFile) {
   1381    QM_TRY_UNWRAP(const bool isLocked, aDataManager->IsLocked(entryId));
   1382    if (isLocked) {
   1383      LOG(("Trying to move in-use file"));
   1384      return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   1385    }
   1386  }
   1387 
   1388  QM_TRY(QM_TO_RESULT(ClearDestinationIfNotLocked(aConnection, aDataManager,
   1389                                                  aHandle, aNewDesignation)));
   1390 
   1391  // XXX: This should be before clearing the target
   1392 
   1393  // To prevent cyclic paths, we check that there is no path from
   1394  // the item to be moved to the destination folder.
   1395  QM_TRY_UNWRAP(const bool isDestinationUnderSelf,
   1396                IsAncestor(aConnection, {entryId, aNewDesignation.parentId()}));
   1397  if (isDestinationUnderSelf) {
   1398    return NS_ERROR_DOM_INVALID_MODIFICATION_ERR;
   1399  }
   1400 
   1401  return NS_OK;
   1402 }
   1403 
   1404 /**
   1405 * Free functions
   1406 */
   1407 
   1408 Result<bool, QMResult> ApplyEntryExistsQuery(
   1409    const FileSystemConnection& aConnection, const nsACString& aQuery,
   1410    const FileSystemChildMetadata& aHandle) {
   1411  QM_TRY_UNWRAP(ResultStatement stmt,
   1412                ResultStatement::Create(aConnection, aQuery));
   1413  QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId())));
   1414  QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, aHandle.childName())));
   1415 
   1416  return stmt.YesOrNoQuery();
   1417 }
   1418 
   1419 Result<bool, QMResult> ApplyEntryExistsQuery(
   1420    const FileSystemConnection& aConnection, const nsACString& aQuery,
   1421    const EntryId& aEntry) {
   1422  QM_TRY_UNWRAP(ResultStatement stmt,
   1423                ResultStatement::Create(aConnection, aQuery));
   1424  QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntry)));
   1425 
   1426  return stmt.YesOrNoQuery();
   1427 }
   1428 
   1429 Result<bool, QMResult> IsFile(const FileSystemConnection& aConnection,
   1430                              const EntryId& aEntryId) {
   1431  QM_TRY_UNWRAP(bool exists, DoesFileExist(aConnection, aEntryId));
   1432  if (exists) {
   1433    return true;
   1434  }
   1435 
   1436  QM_TRY_UNWRAP(exists, DoesDirectoryExist(aConnection, aEntryId));
   1437  if (exists) {
   1438    return false;
   1439  }
   1440 
   1441  // Doesn't exist
   1442  return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
   1443 }
   1444 
   1445 Result<EntryId, QMResult> FindEntryId(const FileSystemConnection& aConnection,
   1446                                      const FileSystemChildMetadata& aHandle,
   1447                                      bool aIsFile) {
   1448  const nsCString aDirectoryQuery =
   1449      "SELECT Entries.handle FROM Directories "
   1450      "INNER JOIN Entries USING (handle) "
   1451      "WHERE Directories.name = :name AND Entries.parent = :parent "
   1452      ";"_ns;
   1453 
   1454  const nsCString aFileQuery =
   1455      "SELECT Entries.handle FROM Files INNER JOIN Entries USING (handle) "
   1456      "WHERE Files.name = :name AND Entries.parent = :parent "
   1457      ";"_ns;
   1458 
   1459  QM_TRY_UNWRAP(ResultStatement stmt,
   1460                ResultStatement::Create(
   1461                    aConnection, aIsFile ? aFileQuery : aDirectoryQuery));
   1462  QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId())));
   1463  QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, aHandle.childName())));
   1464  QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
   1465 
   1466  if (!moreResults) {
   1467    return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
   1468  }
   1469 
   1470  QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u));
   1471 
   1472  return entryId;
   1473 }
   1474 
   1475 Result<EntryId, QMResult> FindParent(const FileSystemConnection& aConnection,
   1476                                     const EntryId& aEntryId) {
   1477  const nsCString aParentQuery =
   1478      "SELECT handle FROM Entries "
   1479      "WHERE handle IN ( "
   1480      "SELECT parent FROM Entries WHERE "
   1481      "handle = :entryId ) "
   1482      ";"_ns;
   1483 
   1484  QM_TRY_UNWRAP(ResultStatement stmt,
   1485                ResultStatement::Create(aConnection, aParentQuery));
   1486  QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId)));
   1487  QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
   1488 
   1489  if (!moreResults) {
   1490    return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
   1491  }
   1492 
   1493  QM_TRY_UNWRAP(EntryId parentId, stmt.GetEntryIdByColumn(/* Column */ 0u));
   1494  return parentId;
   1495 }
   1496 
   1497 Result<bool, QMResult> IsSame(const FileSystemConnection& aConnection,
   1498                              const FileSystemEntryMetadata& aHandle,
   1499                              const FileSystemChildMetadata& aNewHandle,
   1500                              bool aIsFile) {
   1501  MOZ_ASSERT(!aNewHandle.parentId().IsEmpty());
   1502 
   1503  // Typically aNewHandle does not exist which is not an error
   1504  QM_TRY_RETURN(QM_OR_ELSE_LOG_VERBOSE_IF(
   1505      // Expression.
   1506      FindEntryId(aConnection, aNewHandle, aIsFile)
   1507          .map([&aHandle](const EntryId& entryId) {
   1508            return entryId == aHandle.entryId();
   1509          }),
   1510      // Predicate.
   1511      IsSpecificError<NS_ERROR_DOM_NOT_FOUND_ERR>,
   1512      // Fallback.
   1513      ErrToOkFromQMResult<false>));
   1514 }
   1515 
   1516 Result<Path, QMResult> ResolveReversedPath(
   1517    const FileSystemConnection& aConnection,
   1518    const FileSystemEntryPair& aEndpoints) {
   1519  const nsLiteralCString pathQuery =
   1520      "WITH RECURSIVE followPath(handle, parent) AS ( "
   1521      "SELECT handle, parent "
   1522      "FROM Entries "
   1523      "WHERE handle=:entryId "
   1524      "UNION "
   1525      "SELECT Entries.handle, Entries.parent FROM followPath, Entries "
   1526      "WHERE followPath.parent=Entries.handle ) "
   1527      "SELECT COALESCE(Directories.name, Files.name), handle "
   1528      "FROM followPath "
   1529      "LEFT JOIN Directories USING(handle) "
   1530      "LEFT JOIN Files USING(handle);"_ns;
   1531 
   1532  QM_TRY_UNWRAP(ResultStatement stmt,
   1533                ResultStatement::Create(aConnection, pathQuery));
   1534  QM_TRY(
   1535      QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEndpoints.childId())));
   1536  QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
   1537 
   1538  Path pathResult;
   1539  while (moreResults) {
   1540    QM_TRY_UNWRAP(Name entryName, stmt.GetNameByColumn(/* Column */ 0u));
   1541    QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 1u));
   1542 
   1543    if (aEndpoints.parentId() == entryId) {
   1544      return pathResult;
   1545    }
   1546    pathResult.AppendElement(entryName);
   1547 
   1548    QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
   1549  }
   1550 
   1551  // Spec wants us to return 'null' for not-an-ancestor case
   1552  pathResult.Clear();
   1553  return pathResult;
   1554 }
   1555 
   1556 nsresult GetFileAttributes(const FileSystemConnection& aConnection,
   1557                           const EntryId& aEntryId, ContentType& aType) {
   1558  const nsLiteralCString getFileLocation =
   1559      "SELECT type FROM Files INNER JOIN Entries USING(handle) "
   1560      "WHERE handle = :entryId "
   1561      ";"_ns;
   1562 
   1563  QM_TRY_UNWRAP(ResultStatement stmt,
   1564                ResultStatement::Create(aConnection, getFileLocation));
   1565  QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId)));
   1566  QM_TRY_UNWRAP(bool hasEntries, stmt.ExecuteStep());
   1567 
   1568  // Type is an optional attribute
   1569  if (!hasEntries || stmt.IsNullByColumn(/* Column */ 0u)) {
   1570    return NS_OK;
   1571  }
   1572 
   1573  QM_TRY_UNWRAP(aType, stmt.GetContentTypeByColumn(/* Column */ 0u));
   1574 
   1575  return NS_OK;
   1576 }
   1577 
   1578 // TODO: Implement idle maintenance
   1579 void TryRemoveDuringIdleMaintenance(
   1580    const nsTArray<FileId>& /* aItemToRemove */) {
   1581  // Not implemented
   1582 }
   1583 
   1584 ContentType DetermineContentType(const Name& aName) {
   1585  QM_TRY_UNWRAP(
   1586      auto typeResult,
   1587      QM_OR_ELSE_LOG_VERBOSE(
   1588          FileSystemContentTypeGuess::FromPath(aName),
   1589          ([](const auto& aRv) -> Result<ContentType, QMResult> {
   1590            const nsresult rv = ToNSResult(aRv);
   1591            switch (rv) {
   1592              case NS_ERROR_FAILURE: /* There is an unknown new extension. */
   1593                return ContentType(""_ns); /* We clear the old extension. */
   1594 
   1595              case NS_ERROR_INVALID_ARG: /* The name is garbled. */
   1596                [[fallthrough]];
   1597              case NS_ERROR_NOT_AVAILABLE: /* There is no extension. */
   1598                return VoidCString();      /* We keep the old extension. */
   1599 
   1600              default:
   1601                MOZ_ASSERT_UNREACHABLE("Should never get here!");
   1602                return Err(aRv);
   1603            }
   1604          })),
   1605      ContentType(""_ns));
   1606 
   1607  return typeResult;
   1608 }
   1609 
   1610 }  // namespace fs::data
   1611 
   1612 }  // namespace mozilla::dom