Bug 1758092 - Establish database support for origin private file system; r=jesup

Differential Revision: https://phabricator.services.mozilla.com/D140308
This commit is contained in:
Jari Jalkanen 2022-09-01 12:58:08 +00:00
Родитель e3a468fafb
Коммит bbf9109b68
31 изменённых файлов: 2166 добавлений и 67 удалений

Просмотреть файл

@ -48,6 +48,8 @@ class FileSystemHandle : public nsISupports, public nsWrapperCache {
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FileSystemHandle)
const fs::EntryId& GetId() const { return mMetadata.entryId(); }
// WebIDL Boilerplate
nsIGlobalObject* GetParentObject() const;

Просмотреть файл

@ -36,29 +36,32 @@ RefPtr<File> MakeGetFileResult(nsIGlobalObject* aGlobal, const nsString& aName,
return result;
}
void GetDirectoryContentsResponseHandler(nsIGlobalObject* aGlobal,
FileSystemDirectoryListing&& aResponse,
ArrayAppendable& /* aSink */,
RefPtr<FileSystemManager>& aManager) {
void GetDirectoryContentsResponseHandler(
nsIGlobalObject* aGlobal, FileSystemGetEntriesResponse&& aResponse,
ArrayAppendable& aSink, RefPtr<FileSystemManager>& aManager) {
// TODO: Add page size to FileSystemConstants, preallocate and handle overflow
const auto& listing = aResponse.get_FileSystemDirectoryListing();
nsTArray<RefPtr<FileSystemHandle>> batch;
for (const auto& it : aResponse.files()) {
for (const auto& it : listing.files()) {
RefPtr<FileSystemHandle> handle =
new FileSystemFileHandle(aGlobal, aManager, it);
batch.AppendElement(handle);
}
for (const auto& it : aResponse.directories()) {
for (const auto& it : listing.directories()) {
RefPtr<FileSystemHandle> handle =
new FileSystemDirectoryHandle(aGlobal, aManager, it);
batch.AppendElement(handle);
}
aSink.append(batch);
}
RefPtr<FileSystemDirectoryHandle> MakeResolution(
nsIGlobalObject* aGlobal, FileSystemGetHandleResponse&& aResponse,
const RefPtr<FileSystemDirectoryHandle>& /* aResolution */,
const RefPtr<FileSystemDirectoryHandle>& /* aResult */,
RefPtr<FileSystemManager>& aManager) {
RefPtr<FileSystemDirectoryHandle> result = new FileSystemDirectoryHandle(
aGlobal, aManager,
@ -68,8 +71,8 @@ RefPtr<FileSystemDirectoryHandle> MakeResolution(
RefPtr<FileSystemDirectoryHandle> MakeResolution(
nsIGlobalObject* aGlobal, FileSystemGetHandleResponse&& aResponse,
const RefPtr<FileSystemDirectoryHandle>& /* aResolution */,
const Name& aName, RefPtr<FileSystemManager>& aManager) {
const RefPtr<FileSystemDirectoryHandle>& /* aResult */, const Name& aName,
RefPtr<FileSystemManager>& aManager) {
RefPtr<FileSystemDirectoryHandle> result = new FileSystemDirectoryHandle(
aGlobal, aManager,
FileSystemEntryMetadata(aResponse.get_EntryId(), aName));
@ -79,7 +82,7 @@ RefPtr<FileSystemDirectoryHandle> MakeResolution(
RefPtr<FileSystemFileHandle> MakeResolution(
nsIGlobalObject* aGlobal, FileSystemGetHandleResponse&& aResponse,
const RefPtr<FileSystemFileHandle>& /* aResolution */, const Name& aName,
const RefPtr<FileSystemFileHandle>& /* aResult */, const Name& aName,
RefPtr<FileSystemManager>& aManager) {
RefPtr<FileSystemFileHandle> result = new FileSystemFileHandle(
aGlobal, aManager,
@ -89,7 +92,7 @@ RefPtr<FileSystemFileHandle> MakeResolution(
RefPtr<File> MakeResolution(nsIGlobalObject* aGlobal,
FileSystemGetFileResponse&& aResponse,
const RefPtr<File>& /* aResolution */,
const RefPtr<File>& /* aResult */,
const Name& aName,
RefPtr<FileSystemManager>& aManager) {
auto& fileProperties = aResponse.get_FileSystemFileProperties();
@ -167,6 +170,27 @@ void ResolveCallback(FileSystemGetEntriesResponse&& aResponse,
aPromise->MaybeReject(NS_ERROR_NOT_IMPLEMENTED);
}
template <>
void ResolveCallback(FileSystemResolveResponse&& aResponse,
// NOLINTNEXTLINE(performance-unnecessary-value-param)
RefPtr<Promise> aPromise) {
MOZ_ASSERT(aPromise);
QM_TRY(OkIf(Promise::PromiseState::Pending == aPromise->State()), QM_VOID);
if (FileSystemResolveResponse::Tnsresult == aResponse.type()) {
aPromise->MaybeReject(aResponse.get_nsresult());
return;
}
auto& maybePath = aResponse.get_MaybeFileSystemPath();
if (maybePath.isSome()) {
aPromise->MaybeResolve(maybePath.value().path());
return;
}
aPromise->MaybeResolveWithUndefined();
}
template <class TResponse, class TReturns, class... Args,
std::enable_if_t<std::is_same<TReturns, void>::value, bool> = true>
mozilla::ipc::ResolveCallback<TResponse> SelectResolveCallback(
@ -374,4 +398,27 @@ void FileSystemRequestHandler::RemoveEntry(
std::move(onReject));
}
void FileSystemRequestHandler::Resolve(
RefPtr<FileSystemManager>& aManager,
// NOLINTNEXTLINE(performance-unnecessary-value-param)
const FileSystemEntryPair& aEndpoints, RefPtr<Promise> aPromise) {
MOZ_ASSERT(aManager);
MOZ_ASSERT(!aEndpoints.parentId().IsEmpty());
MOZ_ASSERT(!aEndpoints.childId().IsEmpty());
MOZ_ASSERT(aPromise);
FileSystemResolveRequest request(aEndpoints);
auto&& onResolve =
SelectResolveCallback<FileSystemResolveResponse, void>(aPromise);
auto&& onReject = GetRejectCallback(aPromise);
QM_TRY(OkIf(aManager->Actor()), QM_VOID, [aPromise](const auto&) {
aPromise->MaybeRejectWithUnknownError("Invalid actor");
});
aManager->Actor()->SendResolve(request, std::move(onResolve),
std::move(onReject));
}
} // namespace mozilla::dom::fs

Просмотреть файл

@ -14,14 +14,20 @@ class RefPtr;
namespace mozilla::dom {
class FileSystemHandle;
class FileSystemManager;
class Promise;
namespace fs {
class ArrayAppendable {};
class FileSystemChildMetadata;
class FileSystemEntryMetadata;
class FileSystemEntryPair;
class ArrayAppendable {
public:
void append(const nsTArray<RefPtr<FileSystemHandle>>& /* aBatch */) {}
};
class FileSystemRequestHandler {
public:
@ -48,6 +54,10 @@ class FileSystemRequestHandler {
const FileSystemChildMetadata& aEntry,
bool aRecursive, RefPtr<Promise> aPromise);
virtual void Resolve(RefPtr<FileSystemManager>& aManager,
const FileSystemEntryPair& aEndpoints,
RefPtr<Promise> aPromise);
virtual ~FileSystemRequestHandler() = default;
}; // class FileSystemRequestHandler

Просмотреть файл

@ -5,11 +5,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "FileSystemManagerParent.h"
#include "nsNetCID.h"
#include "FileSystemDatabaseManager.h"
#include "mozilla/Maybe.h"
#include "mozilla/dom/FileSystemDataManager.h"
#include "mozilla/dom/FileSystemTypes.h"
#include "mozilla/dom/QMResult.h"
#include "mozilla/dom/quota/ForwardDecls.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "nsString.h"
#include "nsTArray.h"
using IPCResult = mozilla::ipc::IPCResult;
@ -42,9 +48,23 @@ IPCResult FileSystemManagerParent::RecvGetRootHandleMsg(
}
IPCResult FileSystemManagerParent::RecvGetDirectoryHandleMsg(
FileSystemGetHandleRequest&& /* aRequest */,
FileSystemGetHandleRequest&& aRequest,
GetDirectoryHandleMsgResolver&& aResolver) {
FileSystemGetHandleResponse response(NS_ERROR_NOT_IMPLEMENTED);
MOZ_ASSERT(!aRequest.handle().parentId().IsEmpty());
MOZ_ASSERT(mDataManager);
auto reportError = [&aResolver](const QMResult& aRv) {
FileSystemGetHandleResponse response(aRv.NSResult());
aResolver(response);
};
QM_TRY_UNWRAP(fs::EntryId entryId,
mDataManager->MutableDatabaseManagerPtr()->GetOrCreateDirectory(
aRequest.handle(), aRequest.create()),
IPC_OK(), reportError);
MOZ_ASSERT(!entryId.IsEmpty());
FileSystemGetHandleResponse response(entryId);
aResolver(response);
return IPC_OK();
@ -53,7 +73,21 @@ IPCResult FileSystemManagerParent::RecvGetDirectoryHandleMsg(
IPCResult FileSystemManagerParent::RecvGetFileHandleMsg(
FileSystemGetHandleRequest&& aRequest,
GetFileHandleMsgResolver&& aResolver) {
FileSystemGetHandleResponse response(NS_ERROR_NOT_IMPLEMENTED);
MOZ_ASSERT(!aRequest.handle().parentId().IsEmpty());
MOZ_ASSERT(mDataManager);
auto reportError = [&aResolver](const QMResult& aRv) {
FileSystemGetHandleResponse response(aRv.NSResult());
aResolver(response);
};
QM_TRY_UNWRAP(fs::EntryId entryId,
mDataManager->MutableDatabaseManagerPtr()->GetOrCreateFile(
aRequest.handle(), aRequest.create()),
IPC_OK(), reportError);
MOZ_ASSERT(!entryId.IsEmpty());
FileSystemGetHandleResponse response(entryId);
aResolver(response);
return IPC_OK();
@ -69,7 +103,36 @@ IPCResult FileSystemManagerParent::RecvGetFileMsg(
IPCResult FileSystemManagerParent::RecvResolveMsg(
FileSystemResolveRequest&& aRequest, ResolveMsgResolver&& aResolver) {
FileSystemResolveResponse response(NS_ERROR_NOT_IMPLEMENTED);
MOZ_ASSERT(!aRequest.endpoints().parentId().IsEmpty());
MOZ_ASSERT(!aRequest.endpoints().childId().IsEmpty());
MOZ_ASSERT(mDataManager);
fs::Path filePath;
if (aRequest.endpoints().parentId() == aRequest.endpoints().childId()) {
FileSystemResolveResponse response(Some(FileSystemPath(filePath)));
aResolver(response);
return IPC_OK();
}
auto reportError = [&aResolver](const QMResult& aRv) {
FileSystemResolveResponse response(aRv.NSResult());
aResolver(response);
};
QM_TRY_UNWRAP(
filePath,
mDataManager->MutableDatabaseManagerPtr()->Resolve(aRequest.endpoints()),
IPC_OK(), reportError);
if (filePath.IsEmpty()) {
FileSystemResolveResponse response(Nothing{});
aResolver(response);
return IPC_OK();
}
FileSystemResolveResponse response(Some(FileSystemPath(filePath)));
aResolver(response);
return IPC_OK();
@ -86,7 +149,39 @@ IPCResult FileSystemManagerParent::RecvGetEntriesMsg(
IPCResult FileSystemManagerParent::RecvRemoveEntryMsg(
FileSystemRemoveEntryRequest&& aRequest,
RemoveEntryMsgResolver&& aResolver) {
FileSystemRemoveEntryResponse response(NS_ERROR_NOT_IMPLEMENTED);
MOZ_ASSERT(!aRequest.handle().parentId().IsEmpty());
MOZ_ASSERT(mDataManager);
auto reportError = [&aResolver](const QMResult& aRv) {
FileSystemRemoveEntryResponse response(aRv.NSResult());
aResolver(response);
};
QM_TRY_UNWRAP(
bool isDeleted,
mDataManager->MutableDatabaseManagerPtr()->RemoveFile(aRequest.handle()),
IPC_OK(), reportError);
if (isDeleted) {
FileSystemRemoveEntryResponse response(NS_OK);
aResolver(response);
return IPC_OK();
}
QM_TRY_UNWRAP(isDeleted,
mDataManager->MutableDatabaseManagerPtr()->RemoveDirectory(
aRequest.handle(), aRequest.recursive()),
IPC_OK(), reportError);
if (!isDeleted) {
FileSystemRemoveEntryResponse response(NS_ERROR_UNEXPECTED);
aResolver(response);
return IPC_OK();
}
FileSystemRemoveEntryResponse response(NS_OK);
aResolver(response);
return IPC_OK();

Просмотреть файл

@ -12,6 +12,7 @@
#include "mozilla/dom/FileSystemTypes.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/ipc/Endpoint.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsString.h"
@ -27,12 +28,6 @@ LazyLogModule gOPFSLog("OPFS");
namespace mozilla::dom {
namespace fs::data {
EntryId GetRootHandle(const Origin& aOrigin) { return "not implemented"_ns; }
} // namespace fs::data
mozilla::ipc::IPCResult CreateFileSystemManagerParent(
const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
mozilla::ipc::Endpoint<PFileSystemManagerParent>&& aParentEndpoint,
@ -46,18 +41,26 @@ mozilla::ipc::IPCResult CreateFileSystemManagerParent(
QM_TRY(OkIf(aParentEndpoint.IsValid()), IPC_OK(),
[aResolver](const auto&) { aResolver(NS_ERROR_INVALID_ARG); });
nsAutoCString origin =
quota::QuotaManager::GetOriginFromValidatedPrincipalInfo(aPrincipalInfo);
quota::OriginMetadata originMetadata(
quota::QuotaManager::GetInfoFromValidatedPrincipalInfo(aPrincipalInfo),
quota::PERSISTENCE_TYPE_DEFAULT);
LOG(("CreateFileSystemManagerParent, origin: %s",
originMetadata.mOrigin.get()));
// This creates the file system data manager, which has to be done on
// PBackground
fs::data::FileSystemDataManager::GetOrCreateFileSystemDataManager(origin)
fs::data::FileSystemDataManager::GetOrCreateFileSystemDataManager(
originMetadata)
->Then(
GetCurrentSerialEventTarget(), __func__,
[origin, parentEndpoint = std::move(aParentEndpoint),
[origin = originMetadata.mOrigin,
parentEndpoint = std::move(aParentEndpoint),
aResolver](const fs::Registered<fs::data::FileSystemDataManager>&
dataManager) mutable {
fs::EntryId rootId = fs::data::GetRootHandle(origin);
QM_TRY_UNWRAP(
fs::EntryId rootId, fs::data::GetRootHandle(origin), QM_VOID,
[aResolver](const auto& aRv) { aResolver(aRv.NSResult()); });
InvokeAsync(
dataManager->MutableIOTargetPtr(), __func__,

Просмотреть файл

@ -0,0 +1,41 @@
/* -*- 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 "GetDirectoryForOrigin.h"
#include "FileSystemHashSource.h"
#include "nsCOMPtr.h"
#include "nsIFile.h"
#include "nsDirectoryServiceDefs.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/dom/QMResult.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/ResultExtensions.h"
namespace mozilla::dom::fs {
Result<nsCOMPtr<nsIFile>, QMResult> GetDirectoryForOrigin(
const quota::QuotaManager& aQuotaManager, const Origin& aOrigin) {
QM_TRY_UNWRAP(auto directory, QM_TO_RESULT_TRANSFORM(quota::QM_NewLocalFile(
aQuotaManager.GetBasePath())));
QM_TRY(QM_TO_RESULT(directory->Append(u"opfs-storage"_ns)));
QM_TRY_UNWRAP(auto originHash,
data::FileSystemHashSource::GenerateHash(
"parent"_ns, NS_ConvertUTF8toUTF16(aOrigin)));
QM_TRY_UNWRAP(auto encodedOrigin,
data::FileSystemHashSource::EncodeHash(originHash));
QM_TRY(QM_TO_RESULT(directory->Append(encodedOrigin)));
return directory;
}
} // namespace mozilla::dom::fs

Просмотреть файл

@ -0,0 +1,36 @@
/* -*- 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/. */
#ifndef DOM_FS_PARENT_GETDIRECTORYFORORIGIN_H_
#define DOM_FS_PARENT_GETDIRECTORYFORORIGIN_H_
#include "mozilla/dom/FileSystemTypes.h"
#include "mozilla/dom/quota/ForwardDecls.h"
template <class T>
class nsCOMPtr;
class nsIFile;
namespace mozilla {
template <typename V, typename E>
class Result;
}
namespace mozilla::dom::quota {
class QuotaManager;
}
namespace mozilla::dom::fs {
// XXX This function can be removed once the integration with quota manager is
// finished.
Result<nsCOMPtr<nsIFile>, QMResult> GetDirectoryForOrigin(
const quota::QuotaManager& aQuotaManager, const Origin& aOrigin);
} // namespace mozilla::dom::fs
#endif // DOM_FS_PARENT_GETDIRECTORYFORORIGIN_H_

Просмотреть файл

@ -0,0 +1,19 @@
/* -*- 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/. */
#ifndef DOM_FS_PARENT_RESULTCONNECTION_H_
#define DOM_FS_PARENT_RESULTCONNECTION_H_
#include "mozIStorageConnection.h"
#include "nsCOMPtr.h"
namespace mozilla::dom::fs {
using ResultConnection = nsCOMPtr<mozIStorageConnection>;
} // namespace mozilla::dom::fs
#endif // DOM_FS_PARENT_RESULTCONNECTION_H_

Просмотреть файл

@ -0,0 +1,23 @@
/* -*- 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 "ResultStatement.h"
#include "mozIStorageConnection.h"
namespace mozilla::dom::fs {
Result<ResultStatement, QMResult> ResultStatement::Create(
const ResultConnection& aConnection, const nsACString& aSQLStatement) {
nsCOMPtr<mozIStorageStatement> stmt;
QM_TRY(QM_TO_RESULT(
aConnection->CreateStatement(aSQLStatement, getter_AddRefs(stmt))));
return ResultStatement(stmt);
};
} // namespace mozilla::dom::fs

Просмотреть файл

@ -0,0 +1,163 @@
/* -*- 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/. */
#ifndef DOM_FS_PARENT_RESULTSTATEMENT_H_
#define DOM_FS_PARENT_RESULTSTATEMENT_H_
#include "mozilla/dom/FileSystemTypes.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozIStorageStatement.h"
#include "nsCOMPtr.h"
#include "nsString.h"
class mozIStorageConnection;
namespace mozilla::dom::fs {
using Column = uint32_t;
using ResultConnection = nsCOMPtr<mozIStorageConnection>;
/**
* @brief ResultStatement
* - provides error monad Result<T, E> compatible interface to the lower level
* error code-based statement implementation in order to enable remote
* debugging with error stack traces
* - converts between OPFS internal data types and the generic data types of
* the lower level implementation
* - provides a customization point for requests aimed at the lower level
* implementation allowing for example to remap errors or implement mocks
*/
class ResultStatement {
public:
using underlying_t = nsCOMPtr<mozIStorageStatement>;
explicit ResultStatement(underlying_t aStmt) : mStmt(std::move(aStmt)) {}
ResultStatement(const ResultStatement& aOther)
: ResultStatement(aOther.mStmt) {}
ResultStatement(ResultStatement&& aOther) noexcept
: ResultStatement(std::move(aOther.mStmt)) {}
ResultStatement& operator=(const ResultStatement& aOther) = default;
ResultStatement& operator=(ResultStatement&& aOther) noexcept {
mStmt = std::move(aOther.mStmt);
return *this;
}
static Result<ResultStatement, QMResult> Create(
const ResultConnection& aConnection, const nsACString& aSQLStatement);
// XXX Consider moving all these "inline" methods into a separate file
// called ResultStatementInlines.h. ResultStatement.h wouldn't have to then
// include ResultExtensions.h, QuotaCommon.h and mozIStorageStatement.h
// which are quite large and should be preferable only included from cpp
// files or special headers like ResultStatementInlines.h. So in the end,
// other headers would include ResultStatement.h only and other cpp files
// would include ResultStatementInlines.h. See also IndedexDababase.h and
// IndexedDatabaseInlines.h to see how it's done.
inline nsresult BindEntryIdByName(const nsACString& aField,
const EntryId& aValue) {
return mStmt->BindUTF8StringAsBlobByName(aField, aValue);
}
inline nsresult BindContentTypeByName(const nsACString& aField,
const ContentType& aValue) {
return mStmt->BindStringByName(aField, aValue);
}
inline nsresult BindNameByName(const nsACString& aField, const Name& aValue) {
return mStmt->BindStringAsBlobByName(aField, aValue);
}
inline nsresult BindPageNumberByName(const nsACString& aField,
PageNumber aValue) {
return mStmt->BindInt32ByName(aField, aValue);
}
inline nsresult BindUsageByName(const nsACString& aField, Usage aValue) {
return mStmt->BindInt64ByName(aField, aValue);
}
inline Result<bool, QMResult> GetBoolByColumn(Column aColumn) {
int32_t value = 0;
QM_TRY(QM_TO_RESULT(mStmt->GetInt32(aColumn, &value)));
return 0 != value;
}
inline Result<ContentType, QMResult> GetContentTypeByColumn(Column aColumn) {
ContentType value;
QM_TRY(QM_TO_RESULT(mStmt->GetString(aColumn, value)));
return value;
}
inline Result<DatabaseVersion, QMResult> GetDatabaseVersion() {
bool hasEntries = false;
QM_TRY(QM_TO_RESULT(mStmt->ExecuteStep(&hasEntries)));
MOZ_ALWAYS_TRUE(hasEntries);
DatabaseVersion value = 0;
QM_TRY(QM_TO_RESULT(mStmt->GetInt32(0u, &value)));
return value;
}
inline Result<EntryId, QMResult> GetEntryIdByColumn(Column aColumn) {
EntryId value;
QM_TRY(QM_TO_RESULT(mStmt->GetBlobAsUTF8String(aColumn, value)));
return value;
}
inline Result<Name, QMResult> GetNameByColumn(Column aColumn) {
Name value;
QM_TRY(QM_TO_RESULT(mStmt->GetBlobAsString(aColumn, value)));
return value;
}
inline Result<Usage, QMResult> GetUsageByColumn(Column aColumn) {
Usage value = 0;
QM_TRY(QM_TO_RESULT(mStmt->GetInt64(aColumn, &value)));
return value;
}
inline bool IsNullByColumn(Column aColumn) const {
bool value = mStmt->IsNull(aColumn);
return value;
}
inline nsresult Execute() { return mStmt->Execute(); }
inline Result<bool, QMResult> ExecuteStep() {
bool hasEntries = false;
QM_TRY(QM_TO_RESULT(mStmt->ExecuteStep(&hasEntries)));
return hasEntries;
}
inline Result<bool, QMResult> YesOrNoQuery() {
bool hasEntries = false;
QM_TRY(QM_TO_RESULT(mStmt->ExecuteStep(&hasEntries)));
MOZ_ALWAYS_TRUE(hasEntries);
return GetBoolByColumn(0u);
}
private:
underlying_t mStmt;
};
} // namespace mozilla::dom::fs
#endif // DOM_FS_PARENT_RESULTSTATEMENT_H_

Просмотреть файл

@ -6,6 +6,11 @@
#include "FileSystemDataManager.h"
#include "FileSystemDatabaseManager.h"
#include "FileSystemDatabaseManagerVersion001.h"
#include "FileSystemHashSource.h"
#include "fs/FileSystemConstants.h"
#include "GetDirectoryForOrigin.h"
#include "mozilla/Result.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/dom/FileSystemManagerParent.h"
@ -13,12 +18,18 @@
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozIStorageService.h"
#include "mozStorageCID.h"
#include "nsBaseHashtable.h"
#include "nsCOMPtr.h"
#include "nsHashKeys.h"
#include "nsIFile.h"
#include "nsNetCID.h"
#include "nsProxyRelease.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "SchemaVersion001.h"
namespace mozilla::dom::fs::data {
@ -93,11 +104,106 @@ void RemoveFileSystemDataManager(const Origin& aOrigin) {
}
}
nsresult GetOrCreateEntry(const nsAString& aDesiredFilePath, bool& aExists,
nsCOMPtr<nsIFile>& aFile,
decltype(nsIFile::NORMAL_FILE_TYPE) aKind,
uint32_t aPermissions) {
MOZ_ASSERT(!aDesiredFilePath.IsEmpty());
QM_TRY(MOZ_TO_RESULT(NS_NewLocalFile(aDesiredFilePath,
/* aFollowLinks */ false,
getter_AddRefs(aFile))));
QM_TRY(MOZ_TO_RESULT(aFile->Exists(&aExists)));
if (aExists) {
return NS_OK;
}
QM_TRY(MOZ_TO_RESULT(aFile->Create(aKind, aPermissions)));
return NS_OK;
}
nsresult GetFileSystemDirectory(const Origin& aOrigin,
nsCOMPtr<nsIFile>& aDirectory) {
QM_TRY_UNWRAP(RefPtr<quota::QuotaManager> quotaManager,
quota::QuotaManager::GetOrCreate());
#if 0
QM_TRY_UNWRAP(aDirectory, quotaManager->GetDirectoryForOrigin(
quota::PERSISTENCE_TYPE_DEFAULT, aOrigin));
#else
QM_TRY_UNWRAP(aDirectory, GetDirectoryForOrigin(*quotaManager, aOrigin));
#endif
QM_TRY(MOZ_TO_RESULT(aDirectory->AppendRelativePath(u"fs"_ns)));
return NS_OK;
}
nsresult GetFileSystemDatabaseFile(const Origin& aOrigin,
nsString& aDatabaseFile) {
nsCOMPtr<nsIFile> directoryPath;
QM_TRY(MOZ_TO_RESULT(GetFileSystemDirectory(aOrigin, directoryPath)));
QM_TRY(
MOZ_TO_RESULT(directoryPath->AppendRelativePath(u"metadata.sqlite"_ns)));
QM_TRY(MOZ_TO_RESULT(directoryPath->GetPath(aDatabaseFile)));
return NS_OK;
}
nsresult GetDatabasePath(const Origin& aOrigin, bool& aExists,
nsCOMPtr<nsIFile>& aFile) {
nsString databaseFilePath;
QM_TRY(MOZ_TO_RESULT(GetFileSystemDatabaseFile(aOrigin, databaseFilePath)));
MOZ_ASSERT(!databaseFilePath.IsEmpty());
return GetOrCreateEntry(databaseFilePath, aExists, aFile,
nsIFile::NORMAL_FILE_TYPE, 0644);
}
Result<ResultConnection, QMResult> GetStorageConnection(const Origin& aOrigin) {
bool exists = false;
nsCOMPtr<nsIFile> databaseFile;
QM_TRY(QM_TO_RESULT(GetDatabasePath(aOrigin, exists, databaseFile)));
QM_TRY_INSPECT(
const auto& storageService,
QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED(
nsCOMPtr<mozIStorageService>, MOZ_SELECT_OVERLOAD(do_GetService),
MOZ_STORAGE_SERVICE_CONTRACTID)));
const auto flags = mozIStorageService::CONNECTION_DEFAULT;
ResultConnection connection;
QM_TRY(QM_TO_RESULT(storageService->OpenDatabase(
databaseFile, flags, getter_AddRefs(connection))));
return connection;
}
} // namespace
Result<EntryId, QMResult> GetRootHandle(const Origin& origin) {
MOZ_ASSERT(!origin.IsEmpty());
return FileSystemHashSource::GenerateHash(origin, kRootName);
}
Result<EntryId, QMResult> GetEntryHandle(
const FileSystemChildMetadata& aHandle) {
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
return FileSystemHashSource::GenerateHash(aHandle.parentId(),
aHandle.childName());
}
FileSystemDataManager::FileSystemDataManager(
const Origin& aOrigin, MovingNotNull<RefPtr<TaskQueue>> aIOTaskQueue)
: mOrigin(aOrigin),
const quota::OriginMetadata& aOriginMetadata,
MovingNotNull<RefPtr<TaskQueue>> aIOTaskQueue)
: mOriginMetadata(aOriginMetadata),
mBackgroundTarget(WrapNotNull(GetCurrentSerialEventTarget())),
mIOTaskQueue(std::move(aIOTaskQueue)),
mRegCount(0),
@ -108,13 +214,14 @@ FileSystemDataManager::~FileSystemDataManager() {
}
RefPtr<FileSystemDataManager::CreatePromise>
FileSystemDataManager::GetOrCreateFileSystemDataManager(const Origin& aOrigin) {
FileSystemDataManager::GetOrCreateFileSystemDataManager(
const quota::OriginMetadata& aOriginMetadata) {
if (quota::QuotaManager::IsShuttingDown()) {
return CreatePromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
if (RefPtr<FileSystemDataManager> dataManager =
GetFileSystemDataManager(aOrigin)) {
GetFileSystemDataManager(aOriginMetadata.mOrigin)) {
if (dataManager->IsOpening()) {
// We have to wait for the open to be finished before resolving the
// promise. The manager can't close itself in the meantime because we
@ -137,8 +244,8 @@ FileSystemDataManager::GetOrCreateFileSystemDataManager(const Origin& aOrigin) {
// call to `GetOrCreateFileSystemManager` will handle any new situation.
return dataManager->OnClose()->Then(
GetCurrentSerialEventTarget(), __func__,
[aOrigin](const BoolPromise::ResolveOrRejectValue&) {
return GetOrCreateFileSystemDataManager(aOrigin);
[aOriginMetadata](const BoolPromise::ResolveOrRejectValue&) {
return GetOrCreateFileSystemDataManager(aOriginMetadata);
});
}
@ -152,15 +259,15 @@ FileSystemDataManager::GetOrCreateFileSystemDataManager(const Origin& aOrigin) {
NS_STREAMTRANSPORTSERVICE_CONTRACTID),
CreatePromise::CreateAndReject(NS_ERROR_FAILURE, __func__));
nsCString taskQueueName("OPFS "_ns + aOrigin);
nsCString taskQueueName("OPFS "_ns + aOriginMetadata.mOrigin);
RefPtr<TaskQueue> ioTaskQueue =
TaskQueue::Create(streamTransportService.forget(), taskQueueName.get());
auto dataManager = MakeRefPtr<FileSystemDataManager>(
aOrigin, WrapMovingNotNull(ioTaskQueue));
aOriginMetadata, WrapMovingNotNull(ioTaskQueue));
AddFileSystemDataManager(aOrigin, dataManager);
AddFileSystemDataManager(aOriginMetadata.mOrigin, dataManager);
return dataManager->BeginOpen()->Then(
GetCurrentSerialEventTarget(), __func__,
@ -259,18 +366,79 @@ RefPtr<BoolPromise> FileSystemDataManager::BeginOpen() {
mState = State::Opening;
// XXX Add storage initialization to the chain.
// These have to be done on the PBackground thread
auto connectionRes = fs::data::GetStorageConnection(mOriginMetadata.mOrigin);
if (NS_WARN_IF(connectionRes.isErr())) {
return BoolPromise::CreateAndReject(connectionRes.unwrapErr().NSResult(),
__func__);
}
auto connection = connectionRes.unwrap();
QM_TRY_UNWRAP(DatabaseVersion version,
SchemaVersion001::InitializeConnection(connection,
mOriginMetadata.mOrigin),
BoolPromise::CreateAndReject(NS_ERROR_FAILURE, __func__));
InvokeAsync(MutableIOTargetPtr(), __func__,
[self = RefPtr<FileSystemDataManager>(this)]() mutable {
nsCOMPtr<nsISerialEventTarget> target =
self->MutableBackgroundTargetPtr();
auto quotaManagerRes = quota::QuotaManager::GetOrCreate();
if (NS_WARN_IF(quotaManagerRes.isErr())) {
return BoolPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
RefPtr<quota::QuotaManager> quotamanager = quotaManagerRes.unwrap();
NS_ProxyRelease("ReleaseFileSystemDataManager", target,
self.forget());
if (1 == version) {
mDatabaseManager =
MakeUnique<FileSystemDatabaseManagerVersion001>(std::move(connection));
}
return BoolPromise::CreateAndResolve(true, __func__);
})
// XXX Take a directory lock here so our origin isn't evicted
InvokeAsync(
quotamanager->IOThread(), __func__,
[self = RefPtr<FileSystemDataManager>(this)]() mutable {
auto autoProxyReleaseManager = MakeScopeExit([&self] {
nsCOMPtr<nsISerialEventTarget> target =
self->MutableBackgroundTargetPtr();
NS_ProxyRelease("ReleaseFileSystemDataManager", target,
self.forget());
});
quota::QuotaManager* quotamanager = quota::QuotaManager::Get();
if (NS_WARN_IF(!quotamanager)) {
return BoolPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__);
}
nsresult rv = quotamanager->EnsureStorageIsInitialized();
if (NS_WARN_IF(NS_FAILED(rv))) {
return BoolPromise::CreateAndReject(rv, __func__);
}
rv = quotamanager->EnsureTemporaryStorageIsInitialized();
if (NS_WARN_IF(NS_FAILED(rv))) {
return BoolPromise::CreateAndReject(rv, __func__);
}
auto result = quotamanager->EnsureTemporaryOriginIsInitialized(
quota::PERSISTENCE_TYPE_DEFAULT, self->mOriginMetadata);
if (result.isOk()) {
return BoolPromise::CreateAndResolve(true, __func__);
}
return BoolPromise::CreateAndReject(result.unwrapErr(), __func__);
})
->Then(MutableIOTargetPtr(), __func__,
[self = RefPtr<FileSystemDataManager>(this)](
const BoolPromise::ResolveOrRejectValue& value) mutable {
auto autoProxyReleaseManager = MakeScopeExit([&self] {
nsCOMPtr<nsISerialEventTarget> target =
self->MutableBackgroundTargetPtr();
NS_ProxyRelease("ReleaseFileSystemDataManager", target,
self.forget());
});
if (value.IsReject()) {
return BoolPromise::CreateAndReject(value.RejectValue(),
__func__);
}
return BoolPromise::CreateAndResolve(true, __func__);
})
->Then(GetCurrentSerialEventTarget(), __func__,
[self = RefPtr<FileSystemDataManager>(this)](
const BoolPromise::ResolveOrRejectValue& value) {
@ -306,7 +474,7 @@ RefPtr<BoolPromise> FileSystemDataManager::BeginClose() {
->Then(MutableBackgroundTargetPtr(), __func__,
[self = RefPtr<FileSystemDataManager>(this)](
const ShutdownPromise::ResolveOrRejectValue&) {
RemoveFileSystemDataManager(self->mOrigin);
RemoveFileSystemDataManager(self->mOriginMetadata.mOrigin);
self->mState = State::Closed;

Просмотреть файл

@ -12,6 +12,7 @@
#include "mozilla/dom/FileSystemTypes.h"
#include "mozilla/dom/FileSystemHelpers.h"
#include "mozilla/dom/quota/CheckedUnsafePtr.h"
#include "mozilla/dom/quota/CommonMetadata.h"
#include "mozilla/dom/quota/ForwardDecls.h"
#include "nsCOMPtr.h"
#include "nsISupportsUtils.h"
@ -27,20 +28,31 @@ namespace dom {
class FileSystemManagerParent;
namespace fs {
class FileSystemChildMetadata;
} // namespace fs
namespace fs::data {
class FileSystemDatabaseManager;
Result<EntryId, QMResult> GetRootHandle(const Origin& origin);
Result<EntryId, QMResult> GetEntryHandle(
const FileSystemChildMetadata& aHandle);
class FileSystemDataManager
: public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
public:
enum struct State : uint8_t { Initial = 0, Opening, Open, Closing, Closed };
FileSystemDataManager(const Origin& aOrigin,
FileSystemDataManager(const quota::OriginMetadata& aOriginMetadata,
MovingNotNull<RefPtr<TaskQueue>> aIOTaskQueue);
using CreatePromise = MozPromise<Registered<FileSystemDataManager>, nsresult,
/* IsExclusive */ true>;
static RefPtr<CreatePromise> GetOrCreateFileSystemDataManager(
const Origin& aOrigin);
const quota::OriginMetadata& aOriginMetadata);
static void InitiateShutdown();
@ -56,6 +68,12 @@ class FileSystemDataManager
return mIOTaskQueue.get();
}
FileSystemDatabaseManager* MutableDatabaseManagerPtr() const {
MOZ_ASSERT(mDatabaseManager);
return mDatabaseManager.get();
}
void Register();
void Unregister();
@ -86,9 +104,10 @@ class FileSystemDataManager
RefPtr<BoolPromise> BeginClose();
nsTHashSet<FileSystemManagerParent*> mActors;
const Origin mOrigin;
const quota::OriginMetadata mOriginMetadata;
const NotNull<nsCOMPtr<nsISerialEventTarget>> mBackgroundTarget;
const NotNull<RefPtr<TaskQueue>> mIOTaskQueue;
UniquePtr<FileSystemDatabaseManager> mDatabaseManager;
MozPromiseHolder<BoolPromise> mOpenPromiseHolder;
MozPromiseHolder<BoolPromise> mClosePromiseHolder;
uint32_t mRegCount;

Просмотреть файл

@ -0,0 +1,115 @@
/* -*- 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/. */
#ifndef DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGER_H_
#define DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGER_H_
#include "mozilla/dom/FileSystemTypes.h"
#include "mozilla/dom/quota/ForwardDecls.h"
#include "nsStringFwd.h"
#include "ResultConnection.h"
template <class T>
class nsCOMPtr;
class nsIFile;
namespace mozilla {
template <typename V, typename E>
class Result;
namespace dom::fs {
class FileSystemChildMetadata;
class FileSystemDirectoryListing;
class FileSystemEntryPair;
namespace data {
using FileSystemConnection = fs::ResultConnection;
class FileSystemDatabaseManager {
public:
/**
* @brief Returns current total usage
*
* @return Result<int64_t, QMResult> Usage or error
*/
virtual Result<int64_t, QMResult> GetUsage() const = 0;
/**
* @brief Returns directory identifier, optionally creating it if it doesn't
* exist
*
* @param aHandle Current directory and filename
* @return Result<bool, QMResult> Directory identifier or error
*/
virtual Result<EntryId, QMResult> GetOrCreateDirectory(
const FileSystemChildMetadata& aHandle, bool aCreate) = 0;
/**
* @brief Returns file identifier, optionally creating it if it doesn't exist
*
* @param aHandle Current directory and filename
* @return Result<bool, QMResult> File identifier or error
*/
virtual Result<EntryId, QMResult> GetOrCreateFile(
const FileSystemChildMetadata& aHandle, bool aCreate) = 0;
/**
* @brief Returns the properties of a file corresponding to a file handle
*/
virtual nsresult GetFile(const FileSystemEntryPair& aEndpoints,
nsString& aType, TimeStamp& lastModifiedMilliSeconds,
Path& aPath, nsCOMPtr<nsIFile>& aFile) const = 0;
virtual Result<FileSystemDirectoryListing, QMResult> GetDirectoryEntries(
const EntryId& aParent, PageNumber aPage) const = 0;
/**
* @brief Removes a directory
*
* @param aHandle Current directory and filename
* @return Result<bool, QMResult> False if file did not exist, otherwise true
* or error
*/
virtual Result<bool, QMResult> RemoveDirectory(
const FileSystemChildMetadata& aHandle, bool aRecursive) = 0;
/**
* @brief Removes a file
*
* @param aHandle Current directory and filename
* @return Result<bool, QMResult> False if file did not exist, otherwise true
* or error
*/
virtual Result<bool, QMResult> RemoveFile(
const FileSystemChildMetadata& aHandle) = 0;
/**
* @brief Tries to connect a parent directory to a file system item with a
* path, excluding the parent directory
*
* @param aHandle Pair of parent directory and child item candidates
* @return Result<Path, QMResult> Path or error if no it didn't exists
*/
virtual Result<Path, QMResult> Resolve(
const FileSystemEntryPair& aEndpoints) const = 0;
/**
* @brief Close database connection.
*/
virtual void Close() = 0;
virtual ~FileSystemDatabaseManager() = default;
};
} // namespace data
} // namespace dom::fs
} // namespace mozilla
#endif // DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGER_H_

Просмотреть файл

@ -0,0 +1,581 @@
/* -*- 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 "mozilla/dom/FileSystemDataManager.h"
#include "mozilla/dom/FileSystemTypes.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/dom/PFileSystemManager.h"
#include "mozStorageHelper.h"
#include "ResultStatement.h"
namespace mozilla::dom {
using FileSystemEntries = nsTArray<fs::FileSystemEntryMetadata>;
namespace fs::data {
namespace {
Result<bool, QMResult> ApplyEntryExistsQuery(
const FileSystemConnection& aConnection, const nsACString& aQuery,
const EntryId& aEntryId) {
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConnection, aQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
return stmt.YesOrNoQuery();
}
Result<bool, QMResult> IsDirectoryEmpty(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;
}
// TODO: Running two aggregations simultaneously leads to
// undefined outcomes and should be prevented.
// However, appending new values while
// one aggreration is ongoing should work if the aggregation is only
// done up to a row which is fixed in the beginning.
nsresult AggregateUsages(FileSystemConnection& mConnection) {
bool rollbackOnScopeExit{false}; // We roll back unless explicitly committed
mozStorageTransaction transaction(
mConnection.get(), rollbackOnScopeExit,
mozIStorageConnection::TRANSACTION_IMMEDIATE);
QM_TRY(MOZ_TO_RESULT(mConnection->ExecuteSimpleSQL(
"INSERT INTO Usages "
"( usage, aggregated ) "
"SELECT SUM(usage) OVER (ROWS UNBOUNDED PRECEDING), TRUE "
"FROM Usages "
"WHERE aggregated = FALSE "
";"_ns)));
QM_TRY(
MOZ_TO_RESULT(mConnection->ExecuteSimpleSQL("DELETE FROM Usages "
"WHERE aggregated = FALSE "
";"_ns)));
QM_TRY(MOZ_TO_RESULT(
mConnection->ExecuteSimpleSQL("UPDATE Usages "
"SET aggregated = NOT aggregated "
";"_ns)));
return transaction.Commit();
}
Result<bool, QMResult> DoesDirectoryExist(
const FileSystemConnection& mConnection, const EntryId& aEntryId) {
MOZ_ASSERT(!aEntryId.IsEmpty());
const nsCString existsQuery =
"SELECT EXISTS "
"(SELECT 1 FROM Directories "
"WHERE handle = :handle )"
";"_ns;
QM_TRY_UNWRAP(bool exists,
ApplyEntryExistsQuery(mConnection, existsQuery, aEntryId));
return exists;
}
Result<Path, QMResult> ResolveReversedPath(
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 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());
}
return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
}
Result<bool, QMResult> DoesFileExist(const FileSystemConnection& mConnection,
const EntryId& aEntryId) {
MOZ_ASSERT(!aEntryId.IsEmpty());
const nsCString existsQuery =
"SELECT EXISTS "
"(SELECT 1 FROM Files "
"WHERE handle = :handle ) "
";"_ns;
QM_TRY_UNWRAP(bool exists,
ApplyEntryExistsQuery(mConnection, existsQuery, aEntryId));
return exists;
}
nsresult GetFileAttributes(const FileSystemConnection& aConnection,
const EntryId& aEntryId,
TimeStamp& aLastModifiedMilliSeconds,
ContentType& aType) {
const nsLiteralCString getFileLocation =
"SELECT type FROM Files INNER JOIN Entries USING(handle) WHERE handle = :entryId;"_ns;
// TODO: Request this from filemanager who makes the system call
aLastModifiedMilliSeconds = 0;
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;
}
nsresult GetEntries(const FileSystemConnection& aConnection,
const nsACString& aUnboundStmt, const EntryId& aParent,
PageNumber aPage, 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(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);
aEntries.AppendElement(metadata);
QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
}
return NS_OK;
}
} // namespace
Result<Usage, QMResult> FileSystemDatabaseManagerVersion001::GetUsage() const {
const nsLiteralCString sumUsagesQuery =
"SELECT sum(deltas) FROM Usages WHERE aggregated = FALSE;"_ns;
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(mConnection, sumUsagesQuery));
QM_TRY_UNWRAP(Usage total, stmt.GetUsageByColumn(/* Column */ 0u));
return total;
}
nsresult FileSystemDatabaseManagerVersion001::UpdateUsage(int64_t aDelta) {
const nsLiteralCString addUsageQuery =
"INSERT INTO Usages "
"( usage ) "
"VALUES "
"( :usage ) "
";"_ns;
{
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(mConnection, addUsageQuery));
QM_TRY(MOZ_TO_RESULT(stmt.BindUsageByName("usage"_ns, aDelta)));
QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
}
// Try again later, no harm done;
QM_WARNONLY_TRY(MOZ_TO_RESULT(AggregateUsages(mConnection)));
return NS_OK;
}
Result<EntryId, QMResult>
FileSystemDatabaseManagerVersion001::GetOrCreateDirectory(
const FileSystemChildMetadata& aHandle, bool aCreate) {
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
const auto& name = aHandle.childName();
MOZ_ASSERT(!name.IsVoid() && !name.IsEmpty());
QM_TRY_UNWRAP(EntryId entryId, fs::data::GetEntryHandle(aHandle));
MOZ_ASSERT(!entryId.IsEmpty());
bool exists = true;
QM_TRY_UNWRAP(exists, DoesFileExist(mConnection, entryId));
// 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, entryId));
// exists as directory
if (exists) {
return entryId;
}
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;
mozStorageTransaction transaction(
mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
{
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(UpdateUsage(name.Length())));
QM_TRY(QM_TO_RESULT(transaction.Commit()));
QM_TRY_UNWRAP(DebugOnly<bool> doesItExistNow,
DoesDirectoryExist(mConnection, entryId));
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();
MOZ_ASSERT(!name.IsVoid() && !name.IsEmpty());
QM_TRY_UNWRAP(EntryId entryId, fs::data::GetEntryHandle(aHandle));
MOZ_ASSERT(!entryId.IsEmpty());
QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(mConnection, entryId));
// 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, DoesFileExist(mConnection, entryId));
if (exists) {
return entryId;
}
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, name ) "
"VALUES "
"( :handle, :name ) "
";"_ns;
// TODO: This needs a scope quard
mozStorageTransaction transaction(
mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
{
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, insertFileQuery));
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(UpdateUsage(name.Length())));
QM_TRY(QM_TO_RESULT(transaction.Commit()));
return entryId;
}
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,
entries.directories())));
QM_TRY(QM_TO_RESULT(
GetEntries(mConnection, filesQuery, aParent, aPage, entries.files())));
return entries;
}
nsresult FileSystemDatabaseManagerVersion001::GetFile(
const FileSystemEntryPair& aEndpoints, nsString& aType,
TimeStamp& lastModifiedMilliSeconds, nsTArray<Name>& aPath,
nsCOMPtr<nsIFile>& aFile) const {
MOZ_ASSERT(!aEndpoints.parentId().IsEmpty());
MOZ_ASSERT(!aEndpoints.childId().IsEmpty());
const auto& entryId = aEndpoints.childId();
QM_TRY(MOZ_TO_RESULT(GetFileAttributes(mConnection, entryId,
lastModifiedMilliSeconds, aType)));
QM_TRY_UNWRAP(aPath, ResolveReversedPath(mConnection, aEndpoints));
aPath.Reverse();
return NS_OK;
}
Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveDirectory(
const FileSystemChildMetadata& aHandle, bool aRecursive) {
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
DebugOnly<Name> name = aHandle.childName();
MOZ_ASSERT(!name.inspect().IsVoid() && !name.inspect().IsEmpty());
QM_TRY_UNWRAP(EntryId entryId, fs::data::GetEntryHandle(aHandle));
MOZ_ASSERT(!entryId.IsEmpty());
QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(mConnection, entryId));
if (!exists) {
return false;
}
// At this point, entry exists and is a directory.
QM_TRY_UNWRAP(bool isEmpty, IsDirectoryEmpty(mConnection, entryId));
if (!aRecursive && !isEmpty) {
return Err(QMResult(
NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR)); // Is this correct error?
}
// If it's empty or we can delete recursively, deleting the handle will
// cascade
const nsLiteralCString descendantsQuery =
"WITH RECURSIVE traceChildren(handle, parent) AS ( "
"SELECT handle, parent "
"FROM Entries "
"WHERE handle=:handle "
"UNION "
"SELECT Entries.handle, Entries.parent FROM traceChildren, Entries "
"WHERE traceChildren.handle=Entries.parent ) "
"SELECT handle "
"FROM traceChildren INNER JOIN Files "
"USING(handle) "
";"_ns;
const nsLiteralCString deleteEntryQuery =
"DELETE FROM Entries "
"WHERE handle = :handle "
";"_ns;
mozStorageTransaction transaction(
mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
nsTArray<EntryId> descendants;
{
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(mConnection, descendantsQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
while (moreResults) {
QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u));
descendants.AppendElement(entryId);
QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
}
}
{
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(mConnection, deleteEntryQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
QM_TRY(QM_TO_RESULT(stmt.Execute()));
}
QM_TRY(QM_TO_RESULT(
UpdateUsage(static_cast<int64_t>(aHandle.childName().Length()))));
QM_TRY(QM_TO_RESULT(transaction.Commit()));
/* TODO: Delete descendants here */
return true;
}
Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveFile(
const FileSystemChildMetadata& aHandle) {
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
DebugOnly<Name> name = aHandle.childName();
MOZ_ASSERT(!name.inspect().IsVoid() && !name.inspect().IsEmpty());
QM_TRY_UNWRAP(EntryId entryId, fs::data::GetEntryHandle(aHandle));
MOZ_ASSERT(!entryId.IsEmpty());
// Make it more evident that we won't remove directories
QM_TRY_UNWRAP(bool exists, DoesFileExist(mConnection, entryId));
if (!exists) {
return false;
}
// At this point, entry exists and is a file
const nsLiteralCString deleteEntryQuery =
"DELETE FROM Entries "
"WHERE handle = :handle "
";"_ns;
mozStorageTransaction transaction(
mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
{
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(mConnection, deleteEntryQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
QM_TRY(QM_TO_RESULT(stmt.Execute()));
}
QM_TRY(QM_TO_RESULT(
UpdateUsage(static_cast<int64_t>(aHandle.childName().Length()))));
QM_TRY(QM_TO_RESULT(transaction.Commit()));
/* TODO: Delete file from the disk */
return true;
}
Result<Path, QMResult> FileSystemDatabaseManagerVersion001::Resolve(
const FileSystemEntryPair& aEndpoints) const {
QM_TRY_UNWRAP(Path path, ResolveReversedPath(mConnection, aEndpoints));
path.Reverse();
return path;
}
void FileSystemDatabaseManagerVersion001::Close() { mConnection->Close(); }
} // namespace fs::data
} // namespace mozilla::dom

Просмотреть файл

@ -0,0 +1,75 @@
/* -*- 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/. */
#ifndef DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGERVERSION001_H_
#define DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGERVERSION001_H_
#include "FileSystemDatabaseManager.h"
namespace mozilla::dom::fs::data {
/**
* @brief Versioned implementation of database interface enables backwards
* support after the schema has changed. Version number 0 refers to
* uninitialized database, and versions after that are sequential upgrades.
*
* To change the schema to the next version x,
* - a new implementation FileSystemDatabaseManagerVersion00x is derived from
* the previous version and the required methods are overridden
* - a new apppropriate schema initialization class SchemaVersion00x is created
* or derived
* - the factory method of FileSystemDatabaseManager is extended to try to
* migrate the data from the previous version to version x, and to return
* FileSystemDatabaseManagerVersion00x implementation if the database version
* after the migrations is x
* - note that if the migration fails at some old version, the corresponding
* old implementation should be returned: this way the users whose migrations
* fail systematically due to hardware or other issues will not get locked out
*/
class FileSystemDatabaseManagerVersion001 : public FileSystemDatabaseManager {
public:
explicit FileSystemDatabaseManagerVersion001(
fs::data::FileSystemConnection&& aConnection)
: mConnection(aConnection) {}
virtual Result<int64_t, QMResult> GetUsage() const override;
virtual Result<EntryId, QMResult> GetOrCreateDirectory(
const FileSystemChildMetadata& aHandle, bool aCreate) override;
virtual Result<EntryId, QMResult> GetOrCreateFile(
const FileSystemChildMetadata& aHandle, bool aCreate) override;
virtual nsresult GetFile(const FileSystemEntryPair& aEndpoints,
nsString& aType, TimeStamp& lastModifiedMilliSeconds,
Path& aPath,
nsCOMPtr<nsIFile>& aFile) const override;
virtual Result<FileSystemDirectoryListing, QMResult> GetDirectoryEntries(
const EntryId& aParent, PageNumber aPage) const override;
virtual Result<bool, QMResult> RemoveDirectory(
const FileSystemChildMetadata& aHandle, bool aRecursive) override;
virtual Result<bool, QMResult> RemoveFile(
const FileSystemChildMetadata& aHandle) override;
virtual Result<Path, QMResult> Resolve(
const FileSystemEntryPair& aEndpoints) const override;
virtual void Close() override;
virtual ~FileSystemDatabaseManagerVersion001() = default;
private:
nsresult UpdateUsage(int64_t aDelta);
FileSystemConnection mConnection;
};
} // namespace mozilla::dom::fs::data
#endif // DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGERVERSION001_H_

Просмотреть файл

@ -0,0 +1,193 @@
/* -*- 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 "SchemaVersion001.h"
#include "FileSystemHashSource.h"
#include "fs/FileSystemConstants.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozStorageHelper.h"
#include "ResultStatement.h"
namespace mozilla::dom::fs {
namespace {
nsresult SetEncoding(ResultConnection& aConn) {
return aConn->ExecuteSimpleSQL(R"(PRAGMA encoding = "UTF-16";)"_ns);
}
nsresult CreateEntries(ResultConnection& aConn) {
return aConn->ExecuteSimpleSQL(
"CREATE TABLE IF NOT EXISTS Entries ( "
"handle BLOB PRIMARY KEY, " // Generated from parent + name, unique
"parent BLOB, " // Not null due to constraint
"CONSTRAINT parent_is_a_directory "
"FOREIGN KEY (parent) "
"REFERENCES Directories (handle) "
"ON DELETE CASCADE ) "
";"_ns);
}
nsresult CreateDirectories(ResultConnection& aConn) {
return aConn->ExecuteSimpleSQL(
"CREATE TABLE IF NOT EXISTS Directories ( "
"handle BLOB PRIMARY KEY, "
"name BLOB NOT NULL, "
"CONSTRAINT directories_are_entries "
"FOREIGN KEY (handle) "
"REFERENCES Entries (handle) "
"ON DELETE CASCADE ) "
";"_ns);
}
nsresult CreateFiles(ResultConnection& aConn) {
return aConn->ExecuteSimpleSQL(
"CREATE TABLE IF NOT EXISTS Files ( "
"handle BLOB PRIMARY KEY, "
"type TEXT, "
"name BLOB NOT NULL, "
"CONSTRAINT files_are_entries "
"FOREIGN KEY (handle) "
"REFERENCES Entries (handle) "
"ON DELETE CASCADE ) "
";"_ns);
}
class KeepForeignKeysOffUntilScopeExit final {
public:
explicit KeepForeignKeysOffUntilScopeExit(const ResultConnection& aConn)
: mConn(aConn) {}
static Result<KeepForeignKeysOffUntilScopeExit, QMResult> Create(
const ResultConnection& aConn) {
QM_TRY(
QM_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns)));
KeepForeignKeysOffUntilScopeExit result(aConn);
return result;
}
~KeepForeignKeysOffUntilScopeExit() {
auto maskResult = [this]() -> Result<Ok, nsresult> {
QM_TRY(MOZ_TO_RESULT(
mConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns)));
return Ok{};
};
QM_WARNONLY_TRY(maskResult());
}
private:
ResultConnection mConn;
};
nsresult CreateRootEntry(ResultConnection& aConn, const Origin& aOrigin) {
KeepForeignKeysOffUntilScopeExit foreignKeysGuard(aConn);
const nsLiteralCString createRootQuery =
"INSERT OR IGNORE INTO Entries "
"( handle, parent ) "
"VALUES ( :handle, NULL );"_ns;
const nsLiteralCString flagRootAsDirectoryQuery =
"INSERT OR IGNORE INTO Directories "
"( handle, name ) "
"VALUES ( :handle, :name );"_ns;
QM_TRY_UNWRAP(EntryId rootId,
data::FileSystemHashSource::GenerateHash(aOrigin, kRootName));
mozStorageTransaction transaction(
aConn.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
{
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConn, createRootQuery));
QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, rootId)));
QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
}
{
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConn, flagRootAsDirectoryQuery));
QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, rootId)));
QM_TRY(MOZ_TO_RESULT(stmt.BindNameByName("name"_ns, kRootName)));
QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
}
return transaction.Commit();
}
nsresult CreateUsages(ResultConnection& aConn) {
return aConn->ExecuteSimpleSQL(
"CREATE TABLE IF NOT EXISTS Usages ( "
"usage INTEGER NOT NULL, "
"aggregated BOOLEAN DEFAULT FALSE "
") "
";"_ns);
}
Result<bool, QMResult> CheckIfEmpty(ResultConnection& aConn) {
const nsLiteralCString areThereTablesQuery =
"SELECT EXISTS ("
"SELECT 1 FROM sqlite_master "
");"_ns;
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConn, areThereTablesQuery));
return stmt.YesOrNoQuery();
};
Result<DatabaseVersion, QMResult> GetDatabaseVersion(ResultConnection& aConn) {
const nsLiteralCString getUserVersionQuery = "PRAGMA USER_VERSION;"_ns;
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConn, getUserVersionQuery));
return stmt.GetDatabaseVersion();
}
nsresult SetDatabaseVersion(ResultConnection& aConn) {
// Unfortunately bind does not work.
nsCString setUserVersionQuery = "PRAGMA USER_VERSION = "_ns;
setUserVersionQuery.AppendInt(SchemaVersion001::sVersion);
setUserVersionQuery.Append(" ;"_ns);
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConn, setUserVersionQuery));
QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
return NS_OK;
}
} // namespace
Result<DatabaseVersion, QMResult> SchemaVersion001::InitializeConnection(
ResultConnection& aConn, const Origin& aOrigin) {
QM_TRY_UNWRAP(bool isEmpty, CheckIfEmpty(aConn));
DatabaseVersion previous = 0;
if (isEmpty) {
QM_TRY(QM_TO_RESULT(SetEncoding(aConn)));
} else {
QM_TRY_UNWRAP(previous, GetDatabaseVersion(aConn));
}
if (previous < sVersion) {
QM_TRY(QM_TO_RESULT(CreateEntries(aConn)));
QM_TRY(QM_TO_RESULT(CreateDirectories(aConn)));
QM_TRY(QM_TO_RESULT(CreateFiles(aConn)));
QM_TRY(QM_TO_RESULT(CreateUsages(aConn)));
QM_TRY(QM_TO_RESULT(CreateRootEntry(aConn, aOrigin)));
QM_TRY(QM_TO_RESULT(SetDatabaseVersion(aConn)));
}
return GetDatabaseVersion(aConn);
}
} // namespace mozilla::dom::fs

Просмотреть файл

@ -0,0 +1,25 @@
/* -*- 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/. */
#ifndef DOM_FS_PARENT_DATAMODEL_SCHEMAVERSION001_H_
#define DOM_FS_PARENT_DATAMODEL_SCHEMAVERSION001_H_
#include "mozilla/dom/FileSystemTypes.h"
#include "mozilla/dom/quota/ForwardDecls.h"
#include "ResultConnection.h"
namespace mozilla::dom::fs {
struct SchemaVersion001 {
static Result<DatabaseVersion, QMResult> InitializeConnection(
ResultConnection& aConn, const Origin& aOrigin);
static const DatabaseVersion sVersion = 1;
};
} // namespace mozilla::dom::fs
#endif // DOM_FS_PARENT_DATAMODEL_SCHEMAVERSION001_H_

Просмотреть файл

@ -9,7 +9,9 @@ EXPORTS.mozilla.dom += [
]
UNIFIED_SOURCES += [
"FileSystemDatabaseManagerVersion001.cpp",
"FileSystemDataManager.cpp",
"SchemaVersion001.cpp",
]
LOCAL_INCLUDES += [

Просмотреть файл

@ -19,6 +19,13 @@ UNIFIED_SOURCES += [
"FileSystemManagerParent.cpp",
"FileSystemManagerParentFactory.cpp",
"FileSystemQuotaClient.cpp",
"GetDirectoryForOrigin.cpp",
"ResultStatement.cpp",
]
LOCAL_INCLUDES += [
"/dom/fs/include",
"/dom/fs/parent/datamodel",
]
FINAL_LIBRARY = "xul"

Просмотреть файл

@ -15,10 +15,11 @@ class nsTArray;
namespace mozilla::dom::fs {
using ContentType = nsString;
using DatabaseVersion = int32_t;
using EntryId = nsCString;
using Name = nsString;
using Origin = nsCString;
using PageNumber = uint32_t;
using PageNumber = int32_t;
using Path = nsTArray<Name>;
using TimeStamp = int64_t;
using Usage = int64_t;

Просмотреть файл

@ -0,0 +1,122 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
exported_symbols.smokeTest = async function smokeTest() {
const step = async aIter => {
return aIter.next().catch(err => {
/* console.log(err.message); */
});
};
const storage = navigator.storage;
const subdirectoryNames = new Set(["Documents", "Downloads", "Music"]);
const allowCreate = { create: true };
{
let root = await storage.getDirectory();
Assert.ok(root, "Can we access the root directory?");
let it = root.values();
Assert.ok(!!it, "Does root have values iterator?");
let elem = await step(it);
Assert.ok(!elem, "Is root directory empty?");
for (let dirName of subdirectoryNames) {
try {
await root.getDirectoryHandle(dirName, allowCreate);
} catch (err) {
Assert.equal(
Cr.NS_ERROR_NOT_IMPLEMENTED,
err.result,
"Iterator not implemented yet"
);
}
// TODO: Implement iterator
// Assert.ok(true, "Was it possible to add subdirectory " + dirName + "?");
}
}
{
let root = await storage.getDirectory();
Assert.ok(root, "Can we refresh the root directory?");
let it = root.values();
Assert.ok(!!it, "Does root have values iterator?");
let hasElements = false;
let hangGuard = 0;
for (let elem = await step(it); elem; elem = await step(it)) {
Assert.ok(!!elem, "Is element not non-empty?");
Assert.equal("directory", elem.kind, "Is found item a directory?");
Assert.ok(
elem.name.length >= 1 && elem.name.match("^[A-Za-z]{1,64}"),
"Are names of the elements strings?"
);
Assert.ok(subdirectoryNames.has(elem.name), "Is name among known names?");
hasElements = true;
++hangGuard;
if (hangGuard == 10) {
break; // Exit if there is a hang
}
}
Assert.ok(!hasElements, "Iterator not implemented yet");
// TODO: Implement iterator
// Assert.ok(hasElements, "Is values container now non-empty?");
// Assert.equal(3, hangGuard, "Do we only have three elements?");
{
it = root.values();
Assert.ok(!!it, "Does root have values iterator?");
let elem = await step(it);
Assert.ok(!elem, "Iterator not yet implemented");
// TODO: Implement iterator
// Assert.ok(!!elem, "Is element not non-empty?");
// await elem.getDirectoryHandle("Trash", allowCreate);
// let subit = elem.values();
// let subdir = await step(subit);
// Assert.ok(!!subdir, "Is element not non-empty?");
// Assert.equal("directory", subdir.kind, "Is found item a directory?");
// Assert.equal("Trash", subdir.name, "Is found item a directory?");
}
const wipeEverything = { recursive: true };
for (let dirName of subdirectoryNames) {
try {
await root.removeEntry(dirName, wipeEverything);
} catch (err) {
Assert.equal(
Cr.NS_ERROR_NOT_IMPLEMENTED,
err.result,
"Iterator not implemented yet"
);
}
// TODO: Implement iterator
// Assert.ok(
// true,
// "Was it possible to remove subdirectory " + dirName + "?"
// );
}
}
{
let root = await storage.getDirectory();
Assert.ok(root, "Can we refresh the root directory?");
let it = root.values();
Assert.ok(!!it, "Does root have values iterator?");
let elem = await step(it);
Assert.ok(!elem, "Is root directory empty?");
}
};
for (const [key, value] of Object.entries(exported_symbols)) {
Object.defineProperty(value, "name", {
value: key,
writable: false,
});
}

Просмотреть файл

@ -78,6 +78,11 @@ class MockFileSystemRequestHandler : public FileSystemRequestHandler {
const FileSystemChildMetadata& aEntry, bool aRecursive,
RefPtr<Promise> aPromise),
(override));
MOCK_METHOD(void, Resolve,
(RefPtr<FileSystemManager> & aManager,
const FileSystemEntryPair& aEndpoints, RefPtr<Promise> aPromise),
(override));
};
class WaitablePromiseListener {

Просмотреть файл

@ -13,18 +13,27 @@
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/ErrorNames.h"
namespace mozilla::dom::fs::test::gtest::detail {
constexpr nsresult ToNSError(nsresult aError) { return aError; }
inline nsresult ToNSError(const QMResult& aError) { return aError.NSResult(); }
} // namespace mozilla::dom::fs::test::gtest::detail
#define ASSERT_NSEQ(lhs, rhs) \
ASSERT_STREQ(GetStaticErrorName((lhs)), GetStaticErrorName((rhs)))
#define TEST_TRY_UNWRAP_META(tempVar, target, expr) \
auto MOZ_REMOVE_PAREN(tempVar) = (expr); \
ASSERT_TRUE(MOZ_REMOVE_PAREN(tempVar).isOk()); \
#define TEST_TRY_UNWRAP_META(tempVar, target, expr) \
auto MOZ_REMOVE_PAREN(tempVar) = (expr); \
ASSERT_TRUE(MOZ_REMOVE_PAREN(tempVar).isOk()) \
<< GetStaticErrorName(mozilla::dom::fs::test::gtest::detail::ToNSError( \
MOZ_REMOVE_PAREN(tempVar).unwrapErr())); \
MOZ_REMOVE_PAREN(target) = MOZ_REMOVE_PAREN(tempVar).unwrap();
#define TEST_TRY_UNWRAP_ERR_META(tempVar, target, expr) \
auto MOZ_REMOVE_PAREN(tempVar) = (expr); \
ASSERT_TRUE(MOZ_REMOVE_PAREN(tempVar).isErr()); \
MOZ_REMOVE_PAREN(target) = MOZ_REMOVE_PAREN(tempVar).unwrapErr().NSResult();
#define TEST_TRY_UNWRAP_ERR_META(tempVar, target, expr) \
auto MOZ_REMOVE_PAREN(tempVar) = (expr); \
ASSERT_TRUE(MOZ_REMOVE_PAREN(tempVar).isErr()); \
MOZ_REMOVE_PAREN(target) = mozilla::dom::fs::test::gtest::detail::ToNSError( \
MOZ_REMOVE_PAREN(tempVar).unwrapErr());
#define TEST_TRY_UNWRAP(target, expr) \
TEST_TRY_UNWRAP_META(MOZ_UNIQUE_VAR(testVar), target, expr)

Просмотреть файл

@ -19,6 +19,8 @@
#include "mozilla/UniquePtr.h"
#include "nsIGlobalObject.h"
using ::testing::_;
namespace mozilla::dom::fs::test {
class TestFileSystemDirectoryHandle : public ::testing::Test {

Просмотреть файл

@ -169,7 +169,7 @@ TEST(TestFileSystemHashSource, encodeGeneratedHash)
// Always the same length
ASSERT_EQ(kExpectedLength, result.Length());
// Reused buffer should be different
// Encoded versions should differ
ASSERT_STRNE(asWide(expected).c_str(), asWide(result).c_str());
// Padding length should have been stripped

Просмотреть файл

@ -10,6 +10,8 @@
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/quota/QuotaManagerService.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozIStorageService.h"
#include "mozStorageCID.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsIQuotaCallbacks.h"
@ -19,7 +21,10 @@ namespace mozilla::dom::fs::test {
namespace {
constexpr auto kTestOriginName = "http:://example.com"_ns;
quota::OriginMetadata GetTestOriginMetadata() {
return quota::OriginMetadata{""_ns, "example.com"_ns, "http://example.com"_ns,
quota::PERSISTENCE_TYPE_DEFAULT};
}
} // namespace
@ -46,6 +51,12 @@ class TestFileSystemDataManager : public ::testing::Test {
};
static void SetUpTestCase() {
// The first initialization of storage service must be done on the main
// thread.
nsCOMPtr<mozIStorageService> storageService =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
ASSERT_TRUE(storageService);
nsIObserver* observer = quota::QuotaManager::GetObserver();
ASSERT_TRUE(observer);
@ -131,7 +142,7 @@ TEST_F(TestFileSystemDataManager, GetOrCreateFileSystemDataManager) {
bool done = false;
data::FileSystemDataManager::GetOrCreateFileSystemDataManager(
Origin(kTestOriginName))
GetTestOriginMetadata())
->Then(
GetCurrentSerialEventTarget(), __func__,
[](Registered<data::FileSystemDataManager> registeredDataManager) {
@ -176,7 +187,7 @@ TEST_F(TestFileSystemDataManager,
bool done1 = false;
data::FileSystemDataManager::GetOrCreateFileSystemDataManager(
Origin(kTestOriginName))
GetTestOriginMetadata())
->Then(
GetCurrentSerialEventTarget(), __func__,
[&rdm1, &done1](Registered<data::FileSystemDataManager>
@ -192,7 +203,7 @@ TEST_F(TestFileSystemDataManager,
bool done2 = false;
data::FileSystemDataManager::GetOrCreateFileSystemDataManager(
Origin(kTestOriginName))
GetTestOriginMetadata())
->Then(
GetCurrentSerialEventTarget(), __func__,
[&rdm2, &done2](Registered<data::FileSystemDataManager>
@ -253,7 +264,7 @@ TEST_F(TestFileSystemDataManager,
bool done = false;
data::FileSystemDataManager::GetOrCreateFileSystemDataManager(
Origin(kTestOriginName))
GetTestOriginMetadata())
->Then(
GetCurrentSerialEventTarget(), __func__,
[&rdm, &done](Registered<data::FileSystemDataManager>
@ -277,7 +288,7 @@ TEST_F(TestFileSystemDataManager,
bool done = false;
data::FileSystemDataManager::GetOrCreateFileSystemDataManager(
Origin(kTestOriginName))
GetTestOriginMetadata())
->Then(
GetCurrentSerialEventTarget(), __func__,
[](Registered<data::FileSystemDataManager>

Просмотреть файл

@ -0,0 +1,306 @@
/* -*- 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 "ErrorList.h"
#include "FileSystemDataManager.h"
#include "FileSystemDatabaseManagerVersion001.h"
#include "FileSystemHashSource.h"
#include "gtest/gtest.h"
#include "mozilla/dom/FileSystemTypes.h"
#include "mozilla/dom/PFileSystemManager.h"
#include "mozilla/Array.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/Result.h"
#include "mozIStorageService.h"
#include "mozStorageCID.h"
#include "mozStorageHelper.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsContentUtils.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIFile.h"
#include "nsLiteralString.h"
#include "nsReadableUtils.h"
#include "nsStringFwd.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsTHashSet.h"
#include "ResultStatement.h"
#include "SchemaVersion001.h"
#include "TestHelpers.h"
namespace mozilla::dom::fs::test {
using data::FileSystemDatabaseManagerVersion001;
static const Origin& getTestOrigin() {
static const Origin orig = "testOrigin"_ns;
return orig;
}
static void MakeDatabaseManagerVersion001(
FileSystemDatabaseManagerVersion001*& aResult) {
TEST_TRY_UNWRAP(auto storageService,
MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
MOZ_SELECT_OVERLOAD(do_GetService),
MOZ_STORAGE_SERVICE_CONTRACTID));
const auto flags = mozIStorageService::CONNECTION_DEFAULT;
ResultConnection connection;
nsresult rv = storageService->OpenSpecialDatabase(kMozStorageMemoryStorageKey,
VoidCString(), flags,
getter_AddRefs(connection));
ASSERT_NSEQ(NS_OK, rv);
const Origin& testOrigin = getTestOrigin();
TEST_TRY_UNWRAP(
DatabaseVersion version,
SchemaVersion001::InitializeConnection(connection, testOrigin));
ASSERT_EQ(1, version);
aResult = new FileSystemDatabaseManagerVersion001(std::move(connection));
}
TEST(TestFileSystemDatabaseManagerVersion001, smokeTestCreateRemoveDirectories)
{
nsresult rv = NS_OK;
FileSystemDatabaseManagerVersion001* rdm = nullptr;
ASSERT_NO_FATAL_FAILURE(MakeDatabaseManagerVersion001(rdm));
UniquePtr<FileSystemDatabaseManagerVersion001> dm(rdm);
TEST_TRY_UNWRAP(EntryId rootId, data::GetRootHandle(getTestOrigin()));
FileSystemChildMetadata firstChildMeta(rootId, u"First"_ns);
TEST_TRY_UNWRAP_ERR(
rv, dm->GetOrCreateDirectory(firstChildMeta, /* create */ false));
ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv);
TEST_TRY_UNWRAP(EntryId firstChild,
dm->GetOrCreateDirectory(firstChildMeta, /* create */ true));
int32_t dbVersion = 0;
TEST_TRY_UNWRAP(FileSystemDirectoryListing entries,
dm->GetDirectoryEntries(rootId, dbVersion));
ASSERT_EQ(1u, entries.directories().Length());
ASSERT_EQ(0u, entries.files().Length());
const auto& firstItemRef = entries.directories()[0];
ASSERT_TRUE(u"First"_ns == firstItemRef.entryName())
<< firstItemRef.entryName();
ASSERT_EQ(firstChild, firstItemRef.entryId());
TEST_TRY_UNWRAP(EntryId firstChildClone,
dm->GetOrCreateDirectory(firstChildMeta, /* create */ true));
ASSERT_EQ(firstChild, firstChildClone);
FileSystemChildMetadata secondChildMeta(firstChild, u"Second"_ns);
TEST_TRY_UNWRAP(EntryId secondChild,
dm->GetOrCreateDirectory(secondChildMeta, /* create */ true));
FileSystemEntryPair shortPair(firstChild, secondChild);
TEST_TRY_UNWRAP(Path shortPath, dm->Resolve(shortPair));
ASSERT_EQ(1u, shortPath.Length());
ASSERT_EQ(u"Second"_ns, shortPath[0]);
FileSystemEntryPair longPair(rootId, secondChild);
TEST_TRY_UNWRAP(Path longPath, dm->Resolve(longPair));
ASSERT_EQ(2u, longPath.Length());
ASSERT_EQ(u"First"_ns, longPath[0]);
ASSERT_EQ(u"Second"_ns, longPath[1]);
FileSystemEntryPair wrongPair(secondChild, rootId);
TEST_TRY_UNWRAP_ERR(rv, dm->Resolve(wrongPair));
ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv);
PageNumber page = 0;
TEST_TRY_UNWRAP(FileSystemDirectoryListing fEntries,
dm->GetDirectoryEntries(firstChild, page));
ASSERT_EQ(1u, fEntries.directories().Length());
ASSERT_EQ(0u, fEntries.files().Length());
const auto& secItemRef = fEntries.directories()[0];
ASSERT_TRUE(u"Second"_ns == secItemRef.entryName())
<< secItemRef.entryName();
ASSERT_EQ(secondChild, secItemRef.entryId());
TEST_TRY_UNWRAP_ERR(
rv, dm->RemoveDirectory(firstChildMeta, /* recursive */ false));
ASSERT_NSEQ(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR,
rv); // Is this a good error?
TEST_TRY_UNWRAP(bool isDeleted,
dm->RemoveDirectory(firstChildMeta, /* recursive */ true));
ASSERT_TRUE(isDeleted);
FileSystemChildMetadata thirdChildMeta(secondChild, u"Second"_ns);
TEST_TRY_UNWRAP_ERR(
rv, dm->GetOrCreateDirectory(thirdChildMeta, /* create */ true));
ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Is this a good error?
dm->Close();
}
TEST(TestFileSystemDatabaseManagerVersion001, smokeTestCreateRemoveFiles)
{
nsresult rv = NS_OK;
// Create data manager
FileSystemDatabaseManagerVersion001* rdm = nullptr;
ASSERT_NO_FATAL_FAILURE(MakeDatabaseManagerVersion001(rdm));
UniquePtr<FileSystemDatabaseManagerVersion001> dm(rdm);
TEST_TRY_UNWRAP(EntryId rootId, data::GetRootHandle(getTestOrigin()));
FileSystemChildMetadata firstChildMeta(rootId, u"First"_ns);
// If creating is not allowed, getting a file from empty root fails
TEST_TRY_UNWRAP_ERR(rv,
dm->GetOrCreateFile(firstChildMeta, /* create */ false));
ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv);
// Creating a file under empty root succeeds
TEST_TRY_UNWRAP(EntryId firstChild,
dm->GetOrCreateFile(firstChildMeta, /* create */ true));
// Second time, the same file is returned
TEST_TRY_UNWRAP(EntryId firstChildClone,
dm->GetOrCreateFile(firstChildMeta, /* create */ true));
ASSERT_STREQ(firstChild.get(), firstChildClone.get());
// Directory listing returns the created file
PageNumber page = 0;
TEST_TRY_UNWRAP(FileSystemDirectoryListing entries,
dm->GetDirectoryEntries(rootId, page));
ASSERT_EQ(0u, entries.directories().Length());
ASSERT_EQ(1u, entries.files().Length());
const auto& firstItemRef = entries.files()[0];
ASSERT_TRUE(u"First"_ns == firstItemRef.entryName())
<< firstItemRef.entryName();
ASSERT_STREQ(firstChild.get(), firstItemRef.entryId().get());
// Getting the file entry as directory fails
TEST_TRY_UNWRAP_ERR(
rv, dm->GetOrCreateDirectory(firstChildMeta, /* create */ false));
ASSERT_NSEQ(NS_ERROR_DOM_TYPE_MISMATCH_ERR, rv);
// Getting or creating the file entry as directory also fails
TEST_TRY_UNWRAP_ERR(
rv, dm->GetOrCreateDirectory(firstChildMeta, /* create */ true));
ASSERT_NSEQ(NS_ERROR_DOM_TYPE_MISMATCH_ERR, rv);
// Creating a file with non existing parent hash fails
EntryId notAChildHash = "0123456789abcdef0123456789abcdef"_ns;
FileSystemChildMetadata notAChildMeta(notAChildHash, u"Dummy"_ns);
TEST_TRY_UNWRAP_ERR(rv,
dm->GetOrCreateFile(notAChildMeta, /* create */ true));
ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Is this a good error?
// We create a directory under root
FileSystemChildMetadata secondChildMeta(rootId, u"Second"_ns);
TEST_TRY_UNWRAP(EntryId secondChild,
dm->GetOrCreateDirectory(secondChildMeta, /* create */ true));
// The root should now contain the existing file and the new directory
TEST_TRY_UNWRAP(FileSystemDirectoryListing fEntries,
dm->GetDirectoryEntries(rootId, page));
ASSERT_EQ(1u, fEntries.directories().Length());
ASSERT_EQ(1u, fEntries.files().Length());
const auto& secItemRef = fEntries.directories()[0];
ASSERT_TRUE(u"Second"_ns == secItemRef.entryName())
<< secItemRef.entryName();
ASSERT_EQ(secondChild, secItemRef.entryId());
// Create a file under the new directory
FileSystemChildMetadata thirdChildMeta(secondChild, u"Third"_ns);
TEST_TRY_UNWRAP(EntryId thirdChild,
dm->GetOrCreateFile(thirdChildMeta, /* create */ true));
FileSystemEntryPair entryPair(rootId, thirdChild);
TEST_TRY_UNWRAP(Path entryPath, dm->Resolve(entryPair));
ASSERT_EQ(2u, entryPath.Length());
ASSERT_EQ(u"Second"_ns, entryPath[0]);
ASSERT_EQ(u"Third"_ns, entryPath[1]);
// If recursion is not allowed, the non-empty new directory may not be removed
TEST_TRY_UNWRAP_ERR(
rv, dm->RemoveDirectory(secondChildMeta, /* recursive */ false));
ASSERT_NSEQ(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR,
rv); // Is this a good error?
// If recursion is allowed, the new directory goes away.
TEST_TRY_UNWRAP(bool isDeleted,
dm->RemoveDirectory(secondChildMeta, /* recursive */ true));
ASSERT_TRUE(isDeleted);
// The file under the removed directory is no longer accessible.
TEST_TRY_UNWRAP_ERR(rv,
dm->GetOrCreateFile(thirdChildMeta, /* create */ true));
ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Is this a good error?
// The deletion is reflected by the root directory listing
TEST_TRY_UNWRAP(FileSystemDirectoryListing nEntries,
dm->GetDirectoryEntries(rootId, 0));
ASSERT_EQ(0u, nEntries.directories().Length());
ASSERT_EQ(1u, nEntries.files().Length());
const auto& fileItemRef = nEntries.files()[0];
ASSERT_TRUE(u"First"_ns == fileItemRef.entryName())
<< fileItemRef.entryName();
ASSERT_EQ(firstChild, fileItemRef.entryId());
// Non-empty root directory also may not be removed except recursively
FileSystemChildMetadata rootChildMeta(getTestOrigin(), u"root"_ns);
TEST_TRY_UNWRAP_ERR(
rv, dm->RemoveDirectory(rootChildMeta, /* recursive */ false));
ASSERT_NSEQ(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR,
rv); // Is this a good error?
TEST_TRY_UNWRAP(isDeleted,
dm->RemoveDirectory(rootChildMeta, /* recursive */ true));
ASSERT_TRUE(isDeleted);
// Directory listing of root directory works after it has been cleared
TEST_TRY_UNWRAP(FileSystemDirectoryListing rEntries,
dm->GetDirectoryEntries(rootId, 0));
ASSERT_EQ(0u, rEntries.directories().Length());
ASSERT_EQ(0u, rEntries.files().Length());
// Creating a file under the removed root directory fails
TEST_TRY_UNWRAP_ERR(rv,
dm->GetOrCreateFile(firstChildMeta, /* create */ true));
ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Root doesn't exist
// Creating a non-child file still fails
TEST_TRY_UNWRAP_ERR(rv,
dm->GetOrCreateFile(notAChildMeta, /* create */ true));
ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Is this a good error?
// Creating a directory under the removed root directory fails
TEST_TRY_UNWRAP_ERR(
rv, dm->GetOrCreateDirectory(firstChildMeta, /* create */ true));
ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Root doesn't exist
// Creating a non-child directory still fails
TEST_TRY_UNWRAP_ERR(
rv, dm->GetOrCreateDirectory(notAChildMeta, /* create */ true));
ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Is this a good error?
// Creating a root file fails
TEST_TRY_UNWRAP_ERR(rv,
dm->GetOrCreateFile(rootChildMeta, /* create */ true));
ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Is this a good error?
// Creating a root directory via GetOrCreateDirectory fails
TEST_TRY_UNWRAP_ERR(
rv, dm->GetOrCreateDirectory(rootChildMeta, /* create */ true));
ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Is this a good error?
dm->Close();
}
} // namespace mozilla::dom::fs::test

Просмотреть файл

@ -6,6 +6,7 @@
UNIFIED_SOURCES = [
"TestFileSystemDataManager.cpp",
"TestFileSystemDataManagerVersion001.cpp",
]
include("/ipc/chromium/chromium-config.mozbuild")
@ -13,6 +14,8 @@ include("/ipc/chromium/chromium-config.mozbuild")
FINAL_LIBRARY = "xul-gtest"
LOCAL_INCLUDES += [
"/dom/fs/include",
"/dom/fs/parent",
"/dom/fs/parent/datamodel",
"/dom/fs/test/gtest",
]

Просмотреть файл

@ -25,6 +25,7 @@ TEST_HARNESS_FILES.testing.mochitest.tests.dom.fs.test.mochitest.worker += [
TEST_HARNESS_FILES.xpcshell.dom.fs.test.common += [
"common/nsresult.js",
"common/test_basics.js",
"common/test_fileSystemDirectoryHandle.js",
]
TEST_DIRS += [

Просмотреть файл

@ -0,0 +1,14 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
add_task(async function init() {
const testSet = "dom/fs/test/common/test_fileSystemDirectoryHandle.js";
const testCases = await require_module(testSet);
Object.values(testCases).forEach(testItem => {
add_task(testItem);
});
});

Просмотреть файл

@ -6,3 +6,4 @@
head = head.js
[test_basics.js]
[test_fileSystemDirectoryHandle.js]