Bug 1789116: Implement move() for OriginPrivateFileSystems r=dom-storage-reviewers,jari,emilio

Depends on D155352

Differential Revision: https://phabricator.services.mozilla.com/D156371
This commit is contained in:
Randell Jesup 2022-09-10 02:15:03 +00:00
Родитель ad2a0a8fc0
Коммит 5eedaaf373
15 изменённых файлов: 624 добавлений и 78 удалений

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

@ -13,6 +13,7 @@
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/FileSystemHandleBinding.h"
#include "mozilla/dom/FileSystemManager.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/StorageManager.h"
#include "xpcpublic.h"
@ -112,6 +113,71 @@ already_AddRefed<Promise> FileSystemHandle::IsSameEntry(
return promise.forget();
}
already_AddRefed<Promise> FileSystemHandle::Move(const nsAString& aName,
ErrorResult& aError) {
LOG(("Move %s to %s", NS_ConvertUTF16toUTF8(mMetadata.entryName()).get(),
NS_ConvertUTF16toUTF8(aName).get()));
fs::EntryId parent; // empty means same directory
return Move(parent, aName, aError);
}
already_AddRefed<Promise> FileSystemHandle::Move(
FileSystemDirectoryHandle& aParent, ErrorResult& aError) {
LOG(("Move %s to %s/%s", NS_ConvertUTF16toUTF8(mMetadata.entryName()).get(),
NS_ConvertUTF16toUTF8(aParent.mMetadata.entryName()).get(),
NS_ConvertUTF16toUTF8(mMetadata.entryName()).get()));
return Move(aParent, mMetadata.entryName(), aError);
}
already_AddRefed<Promise> FileSystemHandle::Move(
FileSystemDirectoryHandle& aParent, const nsAString& aName,
ErrorResult& aError) {
LOG(("Move %s to %s/%s", NS_ConvertUTF16toUTF8(mMetadata.entryName()).get(),
NS_ConvertUTF16toUTF8(aParent.mMetadata.entryName()).get(),
NS_ConvertUTF16toUTF8(aName).get()));
return Move(aParent.mMetadata.entryId(), aName, aError);
}
already_AddRefed<Promise> FileSystemHandle::Move(const fs::EntryId& aParentId,
const nsAString& aName,
ErrorResult& aError) {
RefPtr<Promise> promise = Promise::Create(GetParentObject(), aError);
if (aError.Failed()) {
return nullptr;
}
fs::Name name(aName);
if (!aParentId.IsEmpty()) {
fs::FileSystemChildMetadata newMetadata;
newMetadata.parentId() = aParentId;
newMetadata.childName() = aName;
mRequestHandler->MoveEntry(mManager, this, mMetadata, newMetadata, promise);
} else {
mRequestHandler->RenameEntry(mManager, this, mMetadata, name, promise);
}
// Other handles to this will be broken, and the spec is ok with this, but we
// need to update our EntryId and name
promise->AddCallbacksWithCycleCollectedArgs(
[name](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
FileSystemHandle* aHandle) {
// XXX Fix entryId!
LOG(("Changing FileSystemHandle name from %s to %s",
NS_ConvertUTF16toUTF8(aHandle->mMetadata.entryName()).get(),
NS_ConvertUTF16toUTF8(name).get()));
aHandle->mMetadata.entryName() = name;
},
[](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
FileSystemHandle* aHandle) {
LOG(("reject of move for %s",
NS_ConvertUTF16toUTF8(aHandle->mMetadata.entryName()).get()));
},
RefPtr(this));
return promise.forget();
}
// [Serializable] implementation
// static

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

@ -73,6 +73,21 @@ class FileSystemHandle : public nsISupports, public nsWrapperCache {
virtual bool WriteStructuredClone(JSContext* aCx,
JSStructuredCloneWriter* aWriter) const;
already_AddRefed<Promise> Move(const nsAString& aName, ErrorResult& aError);
already_AddRefed<Promise> Move(FileSystemDirectoryHandle& aParent,
ErrorResult& aError);
already_AddRefed<Promise> Move(FileSystemDirectoryHandle& aParent,
const nsAString& aName, ErrorResult& aError);
already_AddRefed<Promise> Move(const fs::EntryId& aParentId,
const nsAString& aName, ErrorResult& aError);
void UpdateMetadata(const fs::FileSystemEntryMetadata& aMetadata) {
mMetadata = aMetadata;
}
protected:
virtual ~FileSystemHandle() = default;
@ -88,7 +103,8 @@ class FileSystemHandle : public nsISupports, public nsWrapperCache {
RefPtr<FileSystemManager> mManager;
const fs::FileSystemEntryMetadata mMetadata;
// move() can change names/directories
fs::FileSystemEntryMetadata mMetadata;
const UniquePtr<fs::FileSystemRequestHandler> mRequestHandler;
};

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

@ -134,14 +134,59 @@ void ResolveCallback(
MOZ_ASSERT(FileSystemRemoveEntryResponse::Tnsresult == aResponse.type());
const auto& status = aResponse.get_nsresult();
if (NS_ERROR_FILE_ACCESS_DENIED == status) {
aPromise->MaybeRejectWithNotAllowedError("Permission denied");
} else if (NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR == status) {
aPromise->MaybeRejectWithInvalidModificationError("Disallowed by system");
} else if (NS_FAILED(status)) {
aPromise->MaybeRejectWithUnknownError("Unknown failure");
} else {
aPromise->MaybeResolveWithUndefined();
switch (status) {
case NS_ERROR_FILE_ACCESS_DENIED:
aPromise->MaybeRejectWithNotAllowedError("Permission denied");
break;
case NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR:
aPromise->MaybeRejectWithInvalidModificationError("Disallowed by system");
break;
default:
if (NS_FAILED(status)) {
aPromise->MaybeRejectWithUnknownError("Unknown failure");
} else {
aPromise->MaybeResolveWithUndefined();
}
break;
}
}
template <>
void ResolveCallback(
FileSystemMoveEntryResponse&& aResponse,
RefPtr<Promise> aPromise) { // NOLINT(performance-unnecessary-value-param)
MOZ_ASSERT(aPromise);
QM_TRY(OkIf(Promise::PromiseState::Pending == aPromise->State()), QM_VOID);
MOZ_ASSERT(FileSystemMoveEntryResponse::Tnsresult == aResponse.type());
const auto& status = aResponse.get_nsresult();
switch (status) {
case NS_OK:
aPromise->MaybeResolveWithUndefined();
break;
case NS_ERROR_FILE_ACCESS_DENIED:
aPromise->MaybeRejectWithNotAllowedError("Permission denied");
break;
case NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR:
aPromise->MaybeRejectWithInvalidModificationError("Disallowed by system");
break;
case NS_ERROR_DOM_NOT_FOUND_ERR:
aPromise->MaybeRejectWithNotFoundError("Entry not found");
break;
case NS_ERROR_DOM_INVALID_MODIFICATION_ERR:
aPromise->MaybeRejectWithInvalidModificationError("Invalid modification");
break;
case NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR:
aPromise->MaybeRejectWithNoModificationAllowedError(
"No modification allowed");
break;
default:
if (NS_FAILED(status)) {
aPromise->MaybeRejectWithUnknownError("Unknown failure");
} else {
aPromise->MaybeResolveWithUndefined();
}
break;
}
}
@ -383,6 +428,63 @@ void FileSystemRequestHandler::RemoveEntry(
std::move(onReject));
}
void FileSystemRequestHandler::MoveEntry(
RefPtr<FileSystemManager>& aManager, FileSystemHandle* aHandle,
const FileSystemEntryMetadata& aEntry,
const FileSystemChildMetadata& aNewEntry,
RefPtr<Promise> aPromise) { // NOLINT(performance-unnecessary-value-param)
MOZ_ASSERT(aPromise);
LOG(("MoveEntry"));
// reject invalid names: empty, path separators, current & parent directories
if (!IsValidName(aNewEntry.childName())) {
aPromise->MaybeRejectWithTypeError("Invalid name");
return;
}
FileSystemMoveEntryRequest request(aEntry, aNewEntry);
RefPtr<FileSystemHandle> handle(aHandle);
auto&& onResolve =
SelectResolveCallback<FileSystemMoveEntryResponse, void>(aPromise);
auto&& onReject = GetRejectCallback(aPromise);
QM_TRY(OkIf(aManager && aManager->Actor()), QM_VOID, [aPromise](const auto&) {
aPromise->MaybeRejectWithUnknownError("Invalid actor");
});
aManager->Actor()->SendMoveEntry(request, std::move(onResolve),
std::move(onReject));
}
void FileSystemRequestHandler::RenameEntry(
RefPtr<FileSystemManager>& aManager, FileSystemHandle* aHandle,
const FileSystemEntryMetadata& aEntry, const Name& aName,
RefPtr<Promise> aPromise) { // NOLINT(performance-unnecessary-value-param)
MOZ_ASSERT(!aEntry.entryId().IsEmpty());
MOZ_ASSERT(aPromise);
LOG(("RenameEntry"));
// reject invalid names: empty, path separators, current & parent directories
if (!IsValidName(aName)) {
aPromise->MaybeRejectWithTypeError("Invalid name");
return;
}
FileSystemRenameEntryRequest request(aEntry, aName);
auto&& onResolve =
SelectResolveCallback<FileSystemMoveEntryResponse, void>(aPromise);
auto&& onReject = GetRejectCallback(aPromise);
QM_TRY(OkIf(aManager && aManager->Actor()), QM_VOID, [aPromise](const auto&) {
aPromise->MaybeRejectWithUnknownError("Invalid actor");
});
aManager->Actor()->SendRenameEntry(request, std::move(onResolve),
std::move(onReject));
}
void FileSystemRequestHandler::Resolve(
RefPtr<FileSystemManager>& aManager,
// NOLINTNEXTLINE(performance-unnecessary-value-param)

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

@ -51,6 +51,17 @@ class FileSystemRequestHandler {
const FileSystemChildMetadata& aEntry,
bool aRecursive, RefPtr<Promise> aPromise);
virtual void MoveEntry(RefPtr<FileSystemManager>& aManager,
FileSystemHandle* aHandle,
const FileSystemEntryMetadata& aEntry,
const FileSystemChildMetadata& aNewEntry,
RefPtr<Promise> aPromise);
virtual void RenameEntry(RefPtr<FileSystemManager>& aManager,
FileSystemHandle* aHandle,
const FileSystemEntryMetadata& aEntry,
const Name& aName, RefPtr<Promise> aPromise);
virtual void Resolve(RefPtr<FileSystemManager>& aManager,
const FileSystemEntryPair& aEndpoints,
RefPtr<Promise> aPromise);

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

@ -214,6 +214,75 @@ IPCResult FileSystemManagerParent::RecvRemoveEntry(
return IPC_OK();
}
IPCResult FileSystemManagerParent::RecvMoveEntry(
FileSystemMoveEntryRequest&& aRequest, MoveEntryResolver&& aResolver) {
LOG(("MoveEntry %s to %s",
NS_ConvertUTF16toUTF8(aRequest.handle().entryName()).get(),
NS_ConvertUTF16toUTF8(aRequest.destHandle().childName()).get()));
MOZ_ASSERT(!aRequest.handle().entryId().IsEmpty());
MOZ_ASSERT(!aRequest.destHandle().parentId().IsEmpty());
MOZ_ASSERT(mDataManager);
auto reportError = [&aResolver](const QMResult& aRv) {
FileSystemMoveEntryResponse response(ToNSResult(aRv));
aResolver(response);
};
QM_TRY_UNWRAP(EntryId parentId,
mDataManager->MutableDatabaseManagerPtr()->GetParentEntryId(
aRequest.handle().entryId()),
IPC_OK(), reportError);
FileSystemChildMetadata sourceHandle;
sourceHandle.parentId() = parentId;
sourceHandle.childName() = aRequest.handle().entryName();
QM_TRY_UNWRAP(bool moved,
mDataManager->MutableDatabaseManagerPtr()->MoveEntry(
sourceHandle, aRequest.destHandle()),
IPC_OK(), reportError);
fs::FileSystemMoveEntryResponse response(moved ? NS_OK : NS_ERROR_FAILURE);
aResolver(response);
return IPC_OK();
}
IPCResult FileSystemManagerParent::RecvRenameEntry(
FileSystemRenameEntryRequest&& aRequest, MoveEntryResolver&& aResolver) {
// if destHandle's parentId is empty, then we're renaming in the same
// directory
LOG(("RenameEntry %s to %s",
NS_ConvertUTF16toUTF8(aRequest.handle().entryName()).get(),
NS_ConvertUTF16toUTF8(aRequest.name()).get()));
MOZ_ASSERT(!aRequest.handle().entryId().IsEmpty());
MOZ_ASSERT(mDataManager);
auto reportError = [&aResolver](const QMResult& aRv) {
FileSystemMoveEntryResponse response(ToNSResult(aRv));
aResolver(response);
};
QM_TRY_UNWRAP(EntryId parentId,
mDataManager->MutableDatabaseManagerPtr()->GetParentEntryId(
aRequest.handle().entryId()),
IPC_OK(), reportError);
FileSystemChildMetadata sourceHandle;
sourceHandle.parentId() = parentId;
sourceHandle.childName() = aRequest.handle().entryName();
FileSystemChildMetadata newHandle;
newHandle.parentId() = parentId;
newHandle.childName() = aRequest.name();
QM_TRY_UNWRAP(bool moved,
mDataManager->MutableDatabaseManagerPtr()->MoveEntry(
sourceHandle, newHandle),
IPC_OK(), reportError);
fs::FileSystemMoveEntryResponse response(moved ? NS_OK : NS_ERROR_FAILURE);
aResolver(response);
return IPC_OK();
}
IPCResult FileSystemManagerParent::RecvCloseFile(
FileSystemGetFileRequest&& aRequest) {
AssertIsOnIOTarget();

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

@ -57,6 +57,12 @@ class FileSystemManagerParent : public PFileSystemManagerParent {
mozilla::ipc::IPCResult RecvRemoveEntry(
FileSystemRemoveEntryRequest&& aRequest, RemoveEntryResolver&& aResolver);
mozilla::ipc::IPCResult RecvMoveEntry(FileSystemMoveEntryRequest&& aRequest,
MoveEntryResolver&& aResolver);
mozilla::ipc::IPCResult RecvRenameEntry(
FileSystemRenameEntryRequest&& aRequest, MoveEntryResolver&& aResolver);
mozilla::ipc::IPCResult RecvCloseFile(FileSystemGetFileRequest&& aRequest);
mozilla::ipc::IPCResult RecvGetAccessHandle(

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

@ -25,6 +25,7 @@ class Result;
namespace dom::fs {
class FileSystemChildMetadata;
class FileSystemEntryMetadata;
class FileSystemDirectoryListing;
class FileSystemEntryPair;
@ -41,6 +42,16 @@ class FileSystemDatabaseManager {
*/
virtual Result<int64_t, QMResult> GetUsage() const = 0;
/**
* @brief Returns directory identifier for the parent of
* a given entry, or error.
*
* @param aEntry EntryId of an existing file or directory
* @return Result<EntryId, QMResult> Directory identifier or error
*/
virtual Result<EntryId, QMResult> GetParentEntryId(
const EntryId& aEntry) const = 0;
/**
* @brief Returns directory identifier, optionally creating it if it doesn't
* exist
@ -90,6 +101,18 @@ class FileSystemDatabaseManager {
virtual Result<bool, QMResult> RemoveFile(
const FileSystemChildMetadata& aHandle) = 0;
/**
* @brief Move/Rename a file/directory
*
* @param aHandle Source directory or file
* @param aNewDesignation Destination directory and filename
* @return Result<bool, QMResult> False if file didn't exist, otherwise true
* or error
*/
virtual Result<bool, QMResult> MoveEntry(
const FileSystemChildMetadata& aHandle,
const FileSystemChildMetadata& aNewDesignation) = 0;
/**
* @brief Tries to connect a parent directory to a file system item with a
* path, excluding the parent directory

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

@ -26,10 +26,11 @@ namespace {
Result<bool, QMResult> ApplyEntryExistsQuery(
const FileSystemConnection& aConnection, const nsACString& aQuery,
const EntryId& aEntryId) {
const FileSystemChildMetadata& aHandle) {
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConnection, aQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId())));
QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, aHandle.childName())));
return stmt.YesOrNoQuery();
}
@ -82,17 +83,18 @@ nsresult AggregateUsages(FileSystemConnection& mConnection) {
}
Result<bool, QMResult> DoesDirectoryExist(
const FileSystemConnection& mConnection, const EntryId& aEntryId) {
MOZ_ASSERT(!aEntryId.IsEmpty());
const FileSystemConnection& mConnection,
const FileSystemChildMetadata& aHandle) {
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
const nsCString existsQuery =
"SELECT EXISTS "
"(SELECT 1 FROM Directories "
"WHERE handle = :handle )"
"(SELECT 1 FROM Directories JOIN Entries USING (handle) "
"WHERE Directories.name = :name AND Entries.parent = :parent )"
";"_ns;
QM_TRY_UNWRAP(bool exists,
ApplyEntryExistsQuery(mConnection, existsQuery, aEntryId));
ApplyEntryExistsQuery(mConnection, existsQuery, aHandle));
return exists;
}
@ -136,17 +138,17 @@ Result<Path, QMResult> ResolveReversedPath(
}
Result<bool, QMResult> DoesFileExist(const FileSystemConnection& mConnection,
const EntryId& aEntryId) {
MOZ_ASSERT(!aEntryId.IsEmpty());
const FileSystemChildMetadata& aHandle) {
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
const nsCString existsQuery =
"SELECT EXISTS "
"(SELECT 1 FROM Files "
"WHERE handle = :handle ) "
"(SELECT 1 FROM Files JOIN Entries USING (handle) "
"WHERE Files.name = :name AND Entries.parent = :parent )"
";"_ns;
QM_TRY_UNWRAP(bool exists,
ApplyEntryExistsQuery(mConnection, existsQuery, aEntryId));
ApplyEntryExistsQuery(mConnection, existsQuery, aHandle));
return exists;
}
@ -210,6 +212,66 @@ nsresult GetEntries(const FileSystemConnection& aConnection,
return NS_OK;
}
Result<EntryId, QMResult> GetUniqueEntryId(
const FileSystemConnection& aConnection,
const FileSystemChildMetadata& aHandle) {
const nsCString existsQuery =
"SELECT EXISTS "
"(SELECT 1 FROM Entries "
"WHERE handle = :handle )"
";"_ns;
FileSystemChildMetadata generatorInput = aHandle;
const size_t maxRounds = 1024u;
for (size_t hangGuard = 0u; hangGuard < maxRounds; ++hangGuard) {
QM_TRY_UNWRAP(EntryId entryId, fs::data::GetEntryHandle(generatorInput));
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConnection, existsQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
QM_TRY_UNWRAP(bool alreadyInUse, stmt.YesOrNoQuery());
if (!alreadyInUse) {
return entryId;
}
generatorInput.parentId() = entryId;
}
return Err(QMResult(NS_ERROR_UNEXPECTED));
}
Result<EntryId, QMResult> FindEntryId(const FileSystemConnection& aConnection,
const FileSystemChildMetadata& aHandle,
bool isFile) {
const nsCString aDirectoryQuery =
"SELECT Entries.handle FROM Directories JOIN Entries USING (handle) "
"WHERE Directories.name = :name AND Entries.parent = :parent "
";"_ns;
const nsCString aFileQuery =
"SELECT Entries.handle FROM Files JOIN Entries USING (handle) "
"WHERE Files.name = :name AND Entries.parent = :parent "
";"_ns;
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConnection,
isFile ? aFileQuery : aDirectoryQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId())));
QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, aHandle.childName())));
QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
if (!moreResults) {
return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
}
QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u));
return entryId;
}
} // namespace
Result<Usage, QMResult> FileSystemDatabaseManagerVersion001::GetUsage() const {
@ -244,6 +306,26 @@ nsresult FileSystemDatabaseManagerVersion001::UpdateUsage(int64_t aDelta) {
return NS_OK;
}
Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetParentEntryId(
const EntryId& aEntry) const {
MOZ_ASSERT(!aEntry.IsEmpty());
const nsCString parentQuery =
"SELECT parent FROM Entries "
"WHERE handle = :entryId;"_ns;
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(mConnection, parentQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntry)));
QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
if (!moreResults) {
return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
}
QM_TRY_UNWRAP(EntryId parentId, stmt.GetEntryIdByColumn(/* Column */ 0u));
return parentId;
}
Result<EntryId, QMResult>
FileSystemDatabaseManagerVersion001::GetOrCreateDirectory(
const FileSystemChildMetadata& aHandle, bool aCreate) {
@ -256,11 +338,8 @@ FileSystemDatabaseManagerVersion001::GetOrCreateDirectory(
}
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));
QM_TRY_UNWRAP(exists, DoesFileExist(mConnection, aHandle));
// By spec, we don't allow a file and a directory
// to have the same name and parent
@ -268,11 +347,11 @@ FileSystemDatabaseManagerVersion001::GetOrCreateDirectory(
return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR));
}
QM_TRY_UNWRAP(exists, DoesDirectoryExist(mConnection, entryId));
QM_TRY_UNWRAP(exists, DoesDirectoryExist(mConnection, aHandle));
// exists as directory
if (exists) {
return entryId;
return FindEntryId(mConnection, aHandle, false);
}
if (!aCreate) {
@ -293,6 +372,9 @@ FileSystemDatabaseManagerVersion001::GetOrCreateDirectory(
"( :handle, :name ) "
";"_ns;
QM_TRY_UNWRAP(EntryId entryId, GetUniqueEntryId(mConnection, aHandle));
MOZ_ASSERT(!entryId.IsEmpty());
mozStorageTransaction transaction(
mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
{
@ -317,7 +399,7 @@ FileSystemDatabaseManagerVersion001::GetOrCreateDirectory(
QM_TRY(QM_TO_RESULT(transaction.Commit()));
QM_TRY_UNWRAP(DebugOnly<bool> doesItExistNow,
DoesDirectoryExist(mConnection, entryId));
DoesDirectoryExist(mConnection, aHandle));
MOZ_ASSERT(doesItExistNow);
return entryId;
@ -334,10 +416,7 @@ Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetOrCreateFile(
}
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));
QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(mConnection, aHandle));
// By spec, we don't allow a file and a directory
// to have the same name and parent
@ -345,10 +424,10 @@ Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetOrCreateFile(
return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR));
}
QM_TRY_UNWRAP(exists, DoesFileExist(mConnection, entryId));
QM_TRY_UNWRAP(exists, DoesFileExist(mConnection, aHandle));
if (exists) {
return entryId;
return FindEntryId(mConnection, aHandle, true);
}
if (!aCreate) {
@ -369,7 +448,9 @@ Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetOrCreateFile(
"( :handle, :name ) "
";"_ns;
// TODO: This needs a scope quard
QM_TRY_UNWRAP(EntryId entryId, GetUniqueEntryId(mConnection, aHandle));
MOZ_ASSERT(!entryId.IsEmpty());
mozStorageTransaction transaction(
mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
{
@ -465,15 +546,15 @@ Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveDirectory(
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));
QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(mConnection, aHandle));
if (!exists) {
return false;
}
// At this point, entry exists and is a directory.
QM_TRY_UNWRAP(EntryId entryId, FindEntryId(mConnection, aHandle, false));
MOZ_ASSERT(!entryId.IsEmpty());
QM_TRY_UNWRAP(bool isEmpty, IsDirectoryEmpty(mConnection, entryId));
@ -547,11 +628,8 @@ Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveFile(
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));
QM_TRY_UNWRAP(bool exists, DoesFileExist(mConnection, aHandle));
if (!exists) {
return false;
@ -563,6 +641,9 @@ Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveFile(
"WHERE handle = :handle "
";"_ns;
QM_TRY_UNWRAP(EntryId entryId, FindEntryId(mConnection, aHandle, true));
MOZ_ASSERT(!entryId.IsEmpty());
mozStorageTransaction transaction(
mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
@ -583,6 +664,97 @@ Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveFile(
return true;
}
Result<bool, QMResult> FileSystemDatabaseManagerVersion001::MoveEntry(
const FileSystemChildMetadata& aHandle,
const FileSystemChildMetadata& aNewDesignation) {
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
if (aHandle.childName().IsEmpty() || aNewDesignation.childName().IsEmpty()) {
return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
}
DebugOnly<Name> name = aHandle.childName();
MOZ_ASSERT(!name.inspect().IsVoid());
// Verify the source exists
bool isFile = false;
QM_TRY_UNWRAP(bool exists, DoesFileExist(mConnection, aHandle));
if (!exists) {
QM_TRY_UNWRAP(exists, DoesDirectoryExist(mConnection, aHandle));
if (!exists) {
return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
}
} else {
isFile = true;
}
QM_TRY_UNWRAP(EntryId entryId, FindEntryId(mConnection, aHandle, isFile));
#if 0
// Enable once file lock code lands with the SyncAccessHandle patch
// At this point, entry exists
if (isFile && !CanUseFile(entryId, AccessMode::EXCLUSIVE)) {
LOG(("Trying to move in-use file"));
return Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR));
}
#endif
const nsLiteralCString updateEntryParentQuery =
"UPDATE Entries "
"SET parent = :parent "
"WHERE handle = :handle "
";"_ns;
const nsLiteralCString updateFileNameQuery =
"UPDATE Files "
"SET name = :name "
"WHERE handle = :handle "
";"_ns;
const nsLiteralCString updateDirectoryNameQuery =
"UPDATE Directories "
"SET name = :name "
"WHERE handle = :handle "
";"_ns;
mozStorageTransaction transaction(
mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
{
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(mConnection, updateEntryParentQuery));
QM_TRY(QM_TO_RESULT(
stmt.BindEntryIdByName("parent"_ns, aNewDesignation.parentId())));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
QM_TRY(QM_TO_RESULT(stmt.Execute()));
}
if (isFile) {
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(mConnection, updateFileNameQuery));
QM_TRY(QM_TO_RESULT(
stmt.BindNameByName("name"_ns, aNewDesignation.childName())));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
QM_TRY(QM_TO_RESULT(stmt.Execute()));
} else {
QM_TRY_UNWRAP(
ResultStatement stmt,
ResultStatement::Create(mConnection, updateDirectoryNameQuery));
QM_TRY(QM_TO_RESULT(
stmt.BindNameByName("name"_ns, aNewDesignation.childName())));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
QM_TRY(QM_TO_RESULT(stmt.Execute()));
}
const auto usageDelta =
static_cast<int64_t>(aNewDesignation.childName().Length()) -
static_cast<int64_t>(aHandle.childName().Length());
QM_TRY(QM_TO_RESULT(UpdateUsage(usageDelta)));
QM_TRY(QM_TO_RESULT(transaction.Commit()));
return true;
}
Result<Path, QMResult> FileSystemDatabaseManagerVersion001::Resolve(
const FileSystemEntryPair& aEndpoints) const {
QM_TRY_UNWRAP(Path path, ResolveReversedPath(mConnection, aEndpoints));

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

@ -40,6 +40,9 @@ class FileSystemDatabaseManagerVersion001 : public FileSystemDatabaseManager {
virtual Result<int64_t, QMResult> GetUsage() const override;
virtual Result<EntryId, QMResult> GetParentEntryId(
const EntryId& aEntry) const override;
virtual Result<EntryId, QMResult> GetOrCreateDirectory(
const FileSystemChildMetadata& aHandle, bool aCreate) override;
@ -54,6 +57,10 @@ class FileSystemDatabaseManagerVersion001 : public FileSystemDatabaseManager {
virtual Result<FileSystemDirectoryListing, QMResult> GetDirectoryEntries(
const EntryId& aParent, PageNumber aPage) const override;
virtual Result<bool, QMResult> MoveEntry(
const FileSystemChildMetadata& aHandle,
const FileSystemChildMetadata& aNewDesignation) override;
virtual Result<bool, QMResult> RemoveDirectory(
const FileSystemChildMetadata& aHandle, bool aRecursive) override;

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

@ -181,6 +181,34 @@ union FileSystemRemoveEntryResponse
void_t;
};
/**
* Identifies a file/directory to be moved and the new name, and the
* destination directory
*/
struct FileSystemMoveEntryRequest
{
FileSystemEntryMetadata handle;
FileSystemChildMetadata destHandle;
};
/**
* Identifies a file/directory to be renamed and the new name
*/
struct FileSystemRenameEntryRequest
{
FileSystemEntryMetadata handle;
Name name;
};
/**
* Contains an error or the new entryId
*/
union FileSystemMoveEntryResponse
{
nsresult;
void_t;
};
struct FileSystemQuotaRequest
{
FileSystemChildMetadata handle;
@ -320,6 +348,23 @@ async protocol PFileSystemManager
async RemoveEntry(FileSystemRemoveEntryRequest request)
returns(FileSystemRemoveEntryResponse response);
/**
* Initiates an asynchronous request to move a directory or file with a
* given name to a given destination and new name.
*
* @returns error information
*/
async MoveEntry(FileSystemMoveEntryRequest request)
returns(FileSystemMoveEntryResponse response);
/**
* Initiates an asynchronous request to rename a directory or file
*
* @returns error information
*/
async RenameEntry(FileSystemRenameEntryRequest request)
returns(FileSystemMoveEntryResponse response);
/**
* TODO: documentation
* So we can implement exclusive access

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

@ -96,6 +96,19 @@ class MockFileSystemRequestHandler : public FileSystemRequestHandler {
RefPtr<Promise> aPromise),
(override));
MOCK_METHOD(void, MoveEntry,
(RefPtr<FileSystemManager> & aManager, FileSystemHandle* aHandle,
const FileSystemEntryMetadata& aEntry,
const FileSystemChildMetadata& aNewEntry,
RefPtr<Promise> aPromise),
(override));
MOCK_METHOD(void, RenameEntry,
(RefPtr<FileSystemManager> & aManager, FileSystemHandle* aHandle,
const FileSystemEntryMetadata& aEntry, const Name& aName,
RefPtr<Promise> aPromise),
(override));
MOCK_METHOD(void, Resolve,
(RefPtr<FileSystemManager> & aManager,
const FileSystemEntryPair& aEndpoints, RefPtr<Promise> aPromise),

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

@ -13,6 +13,14 @@ interface FileSystemHandle {
readonly attribute FileSystemHandleKind kind;
readonly attribute USVString name;
/* https://whatpr.org/fs/10.html#api-filesystemhandle */
[NewObject]
Promise<void> move(USVString name);
[NewObject]
Promise<void> move(FileSystemDirectoryHandle parent);
[NewObject]
Promise<void> move(FileSystemDirectoryHandle parent, USVString name);
[NewObject]
Promise<boolean> isSameEntry(FileSystemHandle other);
};

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

@ -1,60 +1,60 @@
[FileSystemFileHandle-move.https.any.worker.html]
[move(name) to rename a file]
expected: FAIL
expected: PASS
[move(name) to rename a file the same name]
expected: FAIL
expected: PASS
[move("") to rename a file fails]
expected: FAIL
expected: PASS
[move(name) can be called multiple times]
expected: FAIL
expected: PASS
[move(name) with a name with a trailing period should fail]
expected: FAIL
[move(name) with a name with invalid characters should fail]
expected: FAIL
expected: PASS
[move(name) while the file has an open writable fails]
expected: FAIL
expected: PASS
[move(name) while the destination file has an open writable fails]
expected: FAIL
[move(dir, name) to rename a file]
expected: FAIL
expected: PASS
[move(dir, name) to rename a file the same name]
expected: FAIL
expected: PASS
[move(dir) to move a file to a new directory]
expected: FAIL
expected: PASS
[move(dir, "") to move a file to a new directory]
expected: FAIL
[move(dir, name) to move a file to a new directory]
expected: FAIL
expected: PASS
[move(dir) can be called multiple times]
expected: FAIL
expected: PASS
[move(dir, "") can be called multiple times]
expected: FAIL
[move(dir, name) can be called multiple times]
expected: FAIL
expected: PASS
[move(dir, name) with a name with invalid characters should fail]
expected: FAIL
expected: PASS
[move(dir) while the file has an open writable fails]
expected: FAIL
expected: PASS
[move(dir, name) while the file has an open writable fails]
expected: FAIL
expected: PASS
[move(dir) while the destination file has an open writable fails]
expected: FAIL
@ -65,61 +65,61 @@
[FileSystemFileHandle-move.https.any.html]
[move(name) to rename a file]
expected: FAIL
expected: PASS
[move(name) to rename a file the same name]
expected: FAIL
expected: PASS
[move("") to rename a file fails]
expected: FAIL
expected: PASS
[move(name) can be called multiple times]
expected: FAIL
expected: PASS
[move(name) with a name with a trailing period should fail]
expected: FAIL
[move(name) with a name with invalid characters should fail]
expected: FAIL
expected: PASS
[move(name) while the file has an open writable fails]
expected: FAIL
expected: PASS
[move(name) while the destination file has an open writable fails]
expected: FAIL
[move(dir, name) to rename a file]
expected: FAIL
expected: PASS
[move(dir, name) to rename a file the same name]
expected: FAIL
expected: PASS
[move(dir) to move a file to a new directory]
expected: FAIL
expected: PASS
[move(dir, "") to move a file to a new directory]
expected: FAIL
[move(dir, name) to move a file to a new directory]
expected: FAIL
expected: PASS
[move(dir) can be called multiple times]
expected: FAIL
expected: PASS
[move(dir, "") can be called multiple times]
expected: FAIL
[move(dir, name) can be called multiple times]
expected: FAIL
expected: PASS
[move(dir, name) with a name with invalid characters should fail]
expected: FAIL
expected: PASS
[move(dir) while the file has an open writable fails]
expected: FAIL
expected: PASS
[move(dir, name) while the file has an open writable fails]
expected: FAIL
expected: PASS
[move(dir) while the destination file has an open writable fails]
expected: FAIL

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

@ -34,10 +34,11 @@ async function getDirectoryEntryCount(handle) {
async function getSortedDirectoryEntries(handle) {
let result = [];
for await (let entry of handle.values()) {
if (entry.kind === 'directory')
if (entry.kind === 'directory') {
result.push(entry.name + '/');
else
} else {
result.push(entry.name);
}
}
result.sort();
return result;

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

@ -11,6 +11,14 @@ directory_test(async (t, root) => {
assert_equals(await getFileSize(handle), 3);
}, 'move(name) to rename a file');
directory_test(async (t, root) => {
const handle = await createFileWithContents(t, 'file-before', 'foo', root);
await handle.move('file-after');
const newhandle = await root.getFileHandle('file-after');
assert_equals(await getFileContents(newhandle), 'foo');
assert_equals(await getFileSize(newhandle), 3);
}, 'get a handle to a moved file');
directory_test(async (t, root) => {
const handle = await createFileWithContents(t, 'file-before', 'foo', root);
await handle.move('file-before');
@ -55,7 +63,7 @@ directory_test(async (t, root) => {
directory_test(async (t, root) => {
const handle = await createFileWithContents(t, 'file-before', 'foo', root);
await promise_rejects_js(t, TypeError, handle.move('#$23423@352^*3243'));
await promise_rejects_js(t, TypeError, handle.move('test/test'));
assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']);
assert_equals(await getFileContents(handle), 'foo');
@ -238,8 +246,7 @@ directory_test(async (t, root) => {
directory_test(async (t, root) => {
const handle = await createFileWithContents(t, 'file-before', 'foo', root);
await promise_rejects_js(
t, TypeError, handle.move(root, '#$23423@352^*3243'));
await promise_rejects_js(t, TypeError, handle.move(root, '..'));
assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']);
assert_equals(await getFileContents(handle), 'foo');