From f1f07aef715ffea26b18f3c8b041cfd295fea8ce Mon Sep 17 00:00:00 2001 From: Randell Jesup Date: Sat, 10 Sep 2022 17:18:40 +0000 Subject: [PATCH] Bug 1789116: Implement move() for OriginPrivateFileSystems r=dom-storage-reviewers,jari,emilio Depends on D155352 Differential Revision: https://phabricator.services.mozilla.com/D156371 --- dom/fs/api/FileSystemHandle.cpp | 66 +++++ dom/fs/api/FileSystemHandle.h | 18 +- dom/fs/child/FileSystemRequestHandler.cpp | 118 ++++++++- dom/fs/include/fs/FileSystemRequestHandler.h | 11 + dom/fs/parent/FileSystemManagerParent.cpp | 69 +++++ dom/fs/parent/FileSystemManagerParent.h | 6 + .../datamodel/FileSystemDatabaseManager.h | 23 ++ .../FileSystemDatabaseManagerVersion001.cpp | 240 +++++++++++++++--- .../FileSystemDatabaseManagerVersion001.h | 7 + dom/fs/shared/PFileSystemManager.ipdl | 45 ++++ dom/fs/test/gtest/FileSystemMocks.h | 13 + dom/webidl/FileSystemHandle.webidl | 8 + .../tests/fs/resources/test-helpers.js | 5 +- .../script-tests/FileSystemFileHandle-move.js | 13 +- 14 files changed, 594 insertions(+), 48 deletions(-) diff --git a/dom/fs/api/FileSystemHandle.cpp b/dom/fs/api/FileSystemHandle.cpp index c752bdc61452..a408471d9b71 100644 --- a/dom/fs/api/FileSystemHandle.cpp +++ b/dom/fs/api/FileSystemHandle.cpp @@ -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 FileSystemHandle::IsSameEntry( return promise.forget(); } +already_AddRefed 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 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 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 FileSystemHandle::Move(const fs::EntryId& aParentId, + const nsAString& aName, + ErrorResult& aError) { + RefPtr 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 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 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 diff --git a/dom/fs/api/FileSystemHandle.h b/dom/fs/api/FileSystemHandle.h index 514e8ffa7a29..7c4967ea46d9 100644 --- a/dom/fs/api/FileSystemHandle.h +++ b/dom/fs/api/FileSystemHandle.h @@ -73,6 +73,21 @@ class FileSystemHandle : public nsISupports, public nsWrapperCache { virtual bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter) const; + already_AddRefed Move(const nsAString& aName, ErrorResult& aError); + + already_AddRefed Move(FileSystemDirectoryHandle& aParent, + ErrorResult& aError); + + already_AddRefed Move(FileSystemDirectoryHandle& aParent, + const nsAString& aName, ErrorResult& aError); + + already_AddRefed 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 mManager; - const fs::FileSystemEntryMetadata mMetadata; + // move() can change names/directories + fs::FileSystemEntryMetadata mMetadata; const UniquePtr mRequestHandler; }; diff --git a/dom/fs/child/FileSystemRequestHandler.cpp b/dom/fs/child/FileSystemRequestHandler.cpp index 4269262b3627..9b483a86d9e6 100644 --- a/dom/fs/child/FileSystemRequestHandler.cpp +++ b/dom/fs/child/FileSystemRequestHandler.cpp @@ -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 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& aManager, FileSystemHandle* aHandle, + const FileSystemEntryMetadata& aEntry, + const FileSystemChildMetadata& aNewEntry, + RefPtr 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 handle(aHandle); + auto&& onResolve = + SelectResolveCallback(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& aManager, FileSystemHandle* aHandle, + const FileSystemEntryMetadata& aEntry, const Name& aName, + RefPtr 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(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& aManager, // NOLINTNEXTLINE(performance-unnecessary-value-param) diff --git a/dom/fs/include/fs/FileSystemRequestHandler.h b/dom/fs/include/fs/FileSystemRequestHandler.h index 56fc1aef55e7..81f94b71faf5 100644 --- a/dom/fs/include/fs/FileSystemRequestHandler.h +++ b/dom/fs/include/fs/FileSystemRequestHandler.h @@ -51,6 +51,17 @@ class FileSystemRequestHandler { const FileSystemChildMetadata& aEntry, bool aRecursive, RefPtr aPromise); + virtual void MoveEntry(RefPtr& aManager, + FileSystemHandle* aHandle, + const FileSystemEntryMetadata& aEntry, + const FileSystemChildMetadata& aNewEntry, + RefPtr aPromise); + + virtual void RenameEntry(RefPtr& aManager, + FileSystemHandle* aHandle, + const FileSystemEntryMetadata& aEntry, + const Name& aName, RefPtr aPromise); + virtual void Resolve(RefPtr& aManager, const FileSystemEntryPair& aEndpoints, RefPtr aPromise); diff --git a/dom/fs/parent/FileSystemManagerParent.cpp b/dom/fs/parent/FileSystemManagerParent.cpp index 782c8c7360da..5b145eaadfc2 100644 --- a/dom/fs/parent/FileSystemManagerParent.cpp +++ b/dom/fs/parent/FileSystemManagerParent.cpp @@ -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(); diff --git a/dom/fs/parent/FileSystemManagerParent.h b/dom/fs/parent/FileSystemManagerParent.h index 83bdc82f68c6..0a6d3704e272 100644 --- a/dom/fs/parent/FileSystemManagerParent.h +++ b/dom/fs/parent/FileSystemManagerParent.h @@ -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( diff --git a/dom/fs/parent/datamodel/FileSystemDatabaseManager.h b/dom/fs/parent/datamodel/FileSystemDatabaseManager.h index 440f3e5c0a7a..e430057a6aba 100644 --- a/dom/fs/parent/datamodel/FileSystemDatabaseManager.h +++ b/dom/fs/parent/datamodel/FileSystemDatabaseManager.h @@ -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 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 Directory identifier or error + */ + virtual Result 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 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 False if file didn't exist, otherwise true + * or error + */ + virtual Result 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 diff --git a/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.cpp b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.cpp index 849b140c1b98..c6227dcf9dc6 100644 --- a/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.cpp +++ b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.cpp @@ -26,10 +26,11 @@ namespace { Result 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 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 ResolveReversedPath( } Result 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 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 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 FileSystemDatabaseManagerVersion001::GetUsage() const { @@ -244,6 +306,26 @@ nsresult FileSystemDatabaseManagerVersion001::UpdateUsage(int64_t aDelta) { return NS_OK; } +Result 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 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 doesItExistNow, - DoesDirectoryExist(mConnection, entryId)); + DoesDirectoryExist(mConnection, aHandle)); MOZ_ASSERT(doesItExistNow); return entryId; @@ -334,10 +416,7 @@ Result 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 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 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 FileSystemDatabaseManagerVersion001::RemoveDirectory( DebugOnly 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 FileSystemDatabaseManagerVersion001::RemoveFile( DebugOnly 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 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 FileSystemDatabaseManagerVersion001::RemoveFile( return true; } +Result 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 = 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(aNewDesignation.childName().Length()) - + static_cast(aHandle.childName().Length()); + QM_TRY(QM_TO_RESULT(UpdateUsage(usageDelta))); + + QM_TRY(QM_TO_RESULT(transaction.Commit())); + + return true; +} + Result FileSystemDatabaseManagerVersion001::Resolve( const FileSystemEntryPair& aEndpoints) const { QM_TRY_UNWRAP(Path path, ResolveReversedPath(mConnection, aEndpoints)); diff --git a/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.h b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.h index 5ea511cde178..9bf5b03e9729 100644 --- a/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.h +++ b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.h @@ -40,6 +40,9 @@ class FileSystemDatabaseManagerVersion001 : public FileSystemDatabaseManager { virtual Result GetUsage() const override; + virtual Result GetParentEntryId( + const EntryId& aEntry) const override; + virtual Result GetOrCreateDirectory( const FileSystemChildMetadata& aHandle, bool aCreate) override; @@ -54,6 +57,10 @@ class FileSystemDatabaseManagerVersion001 : public FileSystemDatabaseManager { virtual Result GetDirectoryEntries( const EntryId& aParent, PageNumber aPage) const override; + virtual Result MoveEntry( + const FileSystemChildMetadata& aHandle, + const FileSystemChildMetadata& aNewDesignation) override; + virtual Result RemoveDirectory( const FileSystemChildMetadata& aHandle, bool aRecursive) override; diff --git a/dom/fs/shared/PFileSystemManager.ipdl b/dom/fs/shared/PFileSystemManager.ipdl index 1044f063a18f..f61378100411 100644 --- a/dom/fs/shared/PFileSystemManager.ipdl +++ b/dom/fs/shared/PFileSystemManager.ipdl @@ -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 diff --git a/dom/fs/test/gtest/FileSystemMocks.h b/dom/fs/test/gtest/FileSystemMocks.h index b41b4dd0dfe1..8f47a782c9c3 100644 --- a/dom/fs/test/gtest/FileSystemMocks.h +++ b/dom/fs/test/gtest/FileSystemMocks.h @@ -96,6 +96,19 @@ class MockFileSystemRequestHandler : public FileSystemRequestHandler { RefPtr aPromise), (override)); + MOCK_METHOD(void, MoveEntry, + (RefPtr & aManager, FileSystemHandle* aHandle, + const FileSystemEntryMetadata& aEntry, + const FileSystemChildMetadata& aNewEntry, + RefPtr aPromise), + (override)); + + MOCK_METHOD(void, RenameEntry, + (RefPtr & aManager, FileSystemHandle* aHandle, + const FileSystemEntryMetadata& aEntry, const Name& aName, + RefPtr aPromise), + (override)); + MOCK_METHOD(void, Resolve, (RefPtr & aManager, const FileSystemEntryPair& aEndpoints, RefPtr aPromise), diff --git a/dom/webidl/FileSystemHandle.webidl b/dom/webidl/FileSystemHandle.webidl index cbcc8205dd27..e86cbf9394a1 100644 --- a/dom/webidl/FileSystemHandle.webidl +++ b/dom/webidl/FileSystemHandle.webidl @@ -13,6 +13,14 @@ interface FileSystemHandle { readonly attribute FileSystemHandleKind kind; readonly attribute USVString name; + /* https://whatpr.org/fs/10.html#api-filesystemhandle */ + [NewObject] + Promise move(USVString name); + [NewObject] + Promise move(FileSystemDirectoryHandle parent); + [NewObject] + Promise move(FileSystemDirectoryHandle parent, USVString name); + [NewObject] Promise isSameEntry(FileSystemHandle other); }; diff --git a/testing/web-platform/tests/fs/resources/test-helpers.js b/testing/web-platform/tests/fs/resources/test-helpers.js index 27469349cdea..91834e0677a3 100644 --- a/testing/web-platform/tests/fs/resources/test-helpers.js +++ b/testing/web-platform/tests/fs/resources/test-helpers.js @@ -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; diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js index a3be9f49afd8..5b2d25faf345 100644 --- a/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js +++ b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js @@ -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');