зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
e3a468fafb
Коммит
bbf9109b68
|
@ -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]
|
||||
|
|
Загрузка…
Ссылка в новой задаче