diff options
Diffstat (limited to 'dom/fs/child/FileSystemDirectoryIteratorFactory.cpp')
-rw-r--r-- | dom/fs/child/FileSystemDirectoryIteratorFactory.cpp | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/dom/fs/child/FileSystemDirectoryIteratorFactory.cpp b/dom/fs/child/FileSystemDirectoryIteratorFactory.cpp new file mode 100644 index 0000000000..26b605f5b6 --- /dev/null +++ b/dom/fs/child/FileSystemDirectoryIteratorFactory.cpp @@ -0,0 +1,237 @@ +/* -*- 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 "FileSystemDirectoryIteratorFactory.h" + +#include "FileSystemEntryMetadataArray.h" +#include "fs/FileSystemRequestHandler.h" +#include "jsapi.h" +#include "mozilla/dom/FileSystemDirectoryHandle.h" +#include "mozilla/dom/FileSystemDirectoryIterator.h" +#include "mozilla/dom/FileSystemFileHandle.h" +#include "mozilla/dom/FileSystemHandle.h" +#include "mozilla/dom/FileSystemLog.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/IterableIterator.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseNativeHandler.h" + +namespace mozilla::dom::fs { + +namespace { + +template <IterableIteratorBase::IteratorType Type> +struct ValueResolver; + +template <> +struct ValueResolver<IterableIteratorBase::Keys> { + void operator()(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + const FileSystemEntryMetadata& aValue, + const RefPtr<Promise>& aPromise) { + aPromise->MaybeResolve(aValue.entryName()); + } +}; + +template <> +struct ValueResolver<IterableIteratorBase::Values> { + void operator()(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + const FileSystemEntryMetadata& aValue, + const RefPtr<Promise>& aPromise) { + RefPtr<FileSystemHandle> handle; + + if (aValue.directory()) { + handle = new FileSystemDirectoryHandle(aGlobal, aManager, aValue); + } else { + handle = new FileSystemFileHandle(aGlobal, aManager, aValue); + } + + aPromise->MaybeResolve(std::move(handle)); + } +}; + +template <> +struct ValueResolver<IterableIteratorBase::Entries> { + void operator()(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + const FileSystemEntryMetadata& aValue, + const RefPtr<Promise>& aPromise) { + RefPtr<FileSystemHandle> handle; + + if (aValue.directory()) { + handle = new FileSystemDirectoryHandle(aGlobal, aManager, aValue); + } else { + handle = new FileSystemFileHandle(aGlobal, aManager, aValue); + } + + iterator_utils::ResolvePromiseWithKeyAndValue(aPromise, aValue.entryName(), + handle); + } +}; + +// TODO: PageSize could be compile-time shared between content and parent +template <class ValueResolver, size_t PageSize = 1024u> +class DoubleBufferQueueImpl + : public mozilla::dom::FileSystemDirectoryIterator::Impl { + static_assert(PageSize > 0u); + + public: + using DataType = FileSystemEntryMetadata; + explicit DoubleBufferQueueImpl(const FileSystemEntryMetadata& aMetadata) + : mEntryId(aMetadata.entryId()), + mData(), + mWithinPageEnd(0u), + mWithinPageIndex(0u), + mCurrentPageIsLastPage(true), + mPageNumber(0u) {} + + // XXX This doesn't have to be public + void ResolveValue(nsIGlobalObject* aGlobal, + RefPtr<FileSystemManager>& aManager, + const Maybe<DataType>& aValue, RefPtr<Promise> aPromise) { + MOZ_ASSERT(aPromise); + MOZ_ASSERT(aPromise.get()); + + if (!aValue) { + iterator_utils::ResolvePromiseForFinished(aPromise); + return; + } + + ValueResolver{}(aGlobal, aManager, *aValue, aPromise); + } + + already_AddRefed<Promise> Next(nsIGlobalObject* aGlobal, + RefPtr<FileSystemManager>& aManager, + ErrorResult& aError) override { + RefPtr<Promise> promise = Promise::Create(aGlobal, aError); + if (aError.Failed()) { + return nullptr; + } + + next(aGlobal, aManager, promise, aError); + if (aError.Failed()) { + return nullptr; + } + + return promise.forget(); + } + + ~DoubleBufferQueueImpl() = default; + + protected: + void next(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager, + RefPtr<Promise> aResult, ErrorResult& aError) { + LOG_VERBOSE(("next")); + MOZ_ASSERT(aResult); + + Maybe<DataType> rawValue; + + // TODO: Would it be better to prefetch the items before + // we hit the end of a page? + // How likely it is that it would that lead to wasted fetch operations? + if (0u == mWithinPageIndex) { + RefPtr<Promise> promise = Promise::Create(aGlobal, aError); + if (aError.Failed()) { + return; + } + + auto newPage = MakeRefPtr<FileSystemEntryMetadataArray>(); + + RefPtr<DomPromiseListener> listener = new DomPromiseListener( + [global = nsCOMPtr<nsIGlobalObject>(aGlobal), + manager = RefPtr<FileSystemManager>(aManager), newPage, aResult, + this](JSContext* aCx, JS::Handle<JS::Value> aValue) mutable { + MOZ_ASSERT(0u == mWithinPageIndex); + MOZ_ASSERT(newPage->Length() <= PageSize); + + const size_t startPos = mCurrentPageIsLastPage ? 0u : PageSize; + if (mData.Length() < 2 * PageSize) { + mData.InsertElementsAt(startPos, newPage->Elements(), + newPage->Length()); + } else { + mData.ReplaceElementsAt(startPos, newPage->Length(), + newPage->Elements(), newPage->Length()); + } + MOZ_ASSERT(mData.Length() <= 2 * PageSize); + mWithinPageEnd = newPage->Length(); + + Maybe<DataType> value; + if (0 != newPage->Length()) { + nextInternal(value); + } + + ResolveValue(global, manager, value, aResult); + }, + [aResult](nsresult aRv) { aResult->MaybeReject(aRv); }); + promise->AppendNativeHandler(listener); + + FileSystemRequestHandler{}.GetEntries(aManager, mEntryId, mPageNumber, + promise, newPage, aError); + if (aError.Failed()) { + return; + } + + ++mPageNumber; + return; + } + + nextInternal(rawValue); + + ResolveValue(aGlobal, aManager, rawValue, aResult); + } + + bool nextInternal(Maybe<DataType>& aNext) { + if (mWithinPageIndex >= mWithinPageEnd) { + return false; + } + + const size_t previous = + mWithinPageIndex + (mCurrentPageIsLastPage ? 0 : PageSize); + MOZ_ASSERT(2u * PageSize > previous); + MOZ_ASSERT(previous < mData.Length()); + + ++mWithinPageIndex; + + if (mWithinPageIndex == PageSize) { + // Page end reached + mWithinPageIndex = 0u; + mCurrentPageIsLastPage = !mCurrentPageIsLastPage; + } + + aNext = Some(mData[previous]); + return true; + } + + const EntryId mEntryId; + + nsTArray<DataType> mData; // TODO: Fixed size above one page? + + size_t mWithinPageEnd = 0u; + size_t mWithinPageIndex = 0u; + bool mCurrentPageIsLastPage = true; // In the beginning, first page is free + PageNumber mPageNumber = 0u; +}; + +template <IterableIteratorBase::IteratorType Type> +using UnderlyingQueue = DoubleBufferQueueImpl<ValueResolver<Type>>; + +} // namespace + +UniquePtr<mozilla::dom::FileSystemDirectoryIterator::Impl> +FileSystemDirectoryIteratorFactory::Create( + const FileSystemEntryMetadata& aMetadata, + IterableIteratorBase::IteratorType aType) { + if (IterableIteratorBase::Entries == aType) { + return MakeUnique<UnderlyingQueue<IterableIteratorBase::Entries>>( + aMetadata); + } + + if (IterableIteratorBase::Values == aType) { + return MakeUnique<UnderlyingQueue<IterableIteratorBase::Values>>(aMetadata); + } + + return MakeUnique<UnderlyingQueue<IterableIteratorBase::Keys>>(aMetadata); +} + +} // namespace mozilla::dom::fs |