blob: 646ecf0f00e87313962642953703a87e04024a6f [file] [log] [blame]
/*
* Copyright (C) 2021 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "FileSystemSyncAccessHandle.h"
#include "BufferSource.h"
#include "FileSystemFileHandle.h"
#include "JSDOMPromiseDeferred.h"
#include "WorkerGlobalScope.h"
#include "WorkerThread.h"
#include <wtf/CompletionHandler.h>
namespace WebCore {
Ref<FileSystemSyncAccessHandle> FileSystemSyncAccessHandle::create(ScriptExecutionContext& context, FileSystemFileHandle& source, FileSystemSyncAccessHandleIdentifier identifier, FileHandle&& file)
{
auto handle = adoptRef(*new FileSystemSyncAccessHandle(context, source, identifier, WTFMove(file)));
handle->suspendIfNeeded();
return handle;
}
FileSystemSyncAccessHandle::FileSystemSyncAccessHandle(ScriptExecutionContext& context, FileSystemFileHandle& source, FileSystemSyncAccessHandleIdentifier identifier, FileHandle&& file)
: ActiveDOMObject(&context)
, m_source(source)
, m_identifier(identifier)
, m_file(WTFMove(file))
{
ASSERT(m_file);
m_source->registerSyncAccessHandle(m_identifier, *this);
}
FileSystemSyncAccessHandle::~FileSystemSyncAccessHandle()
{
ASSERT(isClosingOrClosed());
m_source->unregisterSyncAccessHandle(m_identifier);
if (m_closeResult)
return;
// Task replies may be scheduled to WorkerRunLoop but do not run.
auto pendingPromises = std::exchange(m_pendingPromises, { });
for (auto& promise : pendingPromises) {
std::visit([](auto& promise) {
promise.reject(Exception { UnknownError, "AccessHandle is about to be destroyed"_s });
}, promise);
}
closeBackend(CloseMode::Sync);
}
bool FileSystemSyncAccessHandle::isClosingOrClosed() const
{
return m_closeResult || !m_closeCallbacks.isEmpty();
}
void FileSystemSyncAccessHandle::truncate(unsigned long long size, DOMPromiseDeferred<void>&& promise)
{
if (isClosingOrClosed())
return promise.reject(Exception { InvalidStateError, "AccessHandle is closing or closed"_s });
auto* scope = downcast<WorkerGlobalScope>(scriptExecutionContext());
if (!scope)
return promise.reject(Exception { InvalidStateError, "Context is invalid"_s });
m_pendingPromises.append(WTFMove(promise));
WorkerGlobalScope::postFileSystemStorageTask([weakThis = WeakPtr { *this }, file = m_file.handle(), size, workerThread = Ref { scope->thread() }]() mutable {
workerThread->runLoop().postTask([weakThis = WTFMove(weakThis), success = FileSystem::truncateFile(file, size)](auto&) mutable {
if (weakThis)
weakThis->completePromise(success ? ExceptionOr<void> { } : Exception { UnknownError });
});
});
}
void FileSystemSyncAccessHandle::getSize(DOMPromiseDeferred<IDLUnsignedLongLong>&& promise)
{
if (isClosingOrClosed())
return promise.reject(Exception { InvalidStateError, "AccessHandle is closing or closed"_s });
auto* scope = downcast<WorkerGlobalScope>(scriptExecutionContext());
if (!scope)
return promise.reject(Exception { InvalidStateError, "Context is invalid"_s });
m_pendingPromises.append(WTFMove(promise));
WorkerGlobalScope::postFileSystemStorageTask([weakThis = WeakPtr { *this }, file = m_file.handle(), workerThread = Ref { scope->thread() }]() mutable {
workerThread->runLoop().postTask([weakThis = WTFMove(weakThis), success = FileSystem::fileSize(file)](auto&) mutable {
if (weakThis)
weakThis->completePromise(success ? ExceptionOr<uint64_t> { success.value() } : Exception { UnknownError });
});
});
}
void FileSystemSyncAccessHandle::flush(DOMPromiseDeferred<void>&& promise)
{
if (isClosingOrClosed())
return promise.reject(Exception { InvalidStateError, "AccessHandle is closing or closed"_s });
auto* scope = downcast<WorkerGlobalScope>(scriptExecutionContext());
if (!scope)
return promise.reject(Exception { InvalidStateError, "Context is invalid"_s });
m_pendingPromises.append(WTFMove(promise));
WorkerGlobalScope::postFileSystemStorageTask([weakThis = WeakPtr { *this }, file = m_file.handle(), workerThread = Ref { scope->thread() }]() mutable {
workerThread->runLoop().postTask([weakThis = WTFMove(weakThis), success = FileSystem::flushFile(file)](auto&) mutable {
if (weakThis)
weakThis->completePromise(success ? ExceptionOr<void> { } : Exception { UnknownError });
});
});
}
void FileSystemSyncAccessHandle::close(DOMPromiseDeferred<void>&& promise)
{
closeInternal([weakThis = WeakPtr { *this }, promise = WTFMove(promise)](auto result) mutable {
promise.settle(WTFMove(result));
});
}
void FileSystemSyncAccessHandle::closeInternal(CloseCallback&& callback)
{
if (m_closeResult)
return callback(ExceptionOr<void> { *m_closeResult });
auto isClosing = !m_closeCallbacks.isEmpty();
m_closeCallbacks.append(WTFMove(callback));
if (isClosing)
return;
ASSERT(m_file);
closeFile();
}
void FileSystemSyncAccessHandle::closeFile()
{
if (!m_file)
return;
auto* scope = downcast<WorkerGlobalScope>(scriptExecutionContext());
ASSERT(scope);
WorkerGlobalScope::postFileSystemStorageTask([weakThis = WeakPtr { *this }, file = std::exchange(m_file, { }), workerThread = Ref { scope->thread() }]() mutable {
workerThread->runLoop().postTask([weakThis = WTFMove(weakThis)](auto&) mutable {
if (weakThis)
weakThis->didCloseFile();
});
});
}
void FileSystemSyncAccessHandle::didCloseFile()
{
closeBackend(CloseMode::Async);
}
void FileSystemSyncAccessHandle::closeBackend(CloseMode mode)
{
if (m_closeResult)
return;
if (mode == CloseMode::Async) {
m_source->closeSyncAccessHandle(m_identifier, [this, protectedThis = Ref { *this }](auto result) mutable {
didCloseBackend(WTFMove(result));
});
return;
}
m_source->closeSyncAccessHandle(m_identifier, [](auto) { });
didCloseBackend({ });
}
void FileSystemSyncAccessHandle::didCloseBackend(ExceptionOr<void>&& result)
{
if (m_closeResult)
return;
m_closeResult = WTFMove(result);
auto callbacks = std::exchange(m_closeCallbacks, { });
for (auto& callback : callbacks)
callback(ExceptionOr<void> { *m_closeResult });
}
ExceptionOr<unsigned long long> FileSystemSyncAccessHandle::read(BufferSource&& buffer, FileSystemSyncAccessHandle::FilesystemReadWriteOptions options)
{
ASSERT(!isMainThread());
if (isClosingOrClosed())
return Exception { InvalidStateError, "AccessHandle is closing or closed"_s };
if (!m_pendingPromises.isEmpty())
return Exception { InvalidStateError, "Access handle has unfinished operation"_s };
int result = FileSystem::seekFile(m_file.handle(), options.at, FileSystem::FileSeekOrigin::Beginning);
if (result == -1)
return Exception { InvalidStateError, "Failed to read at offset"_s };
result = FileSystem::readFromFile(m_file.handle(), buffer.mutableData(), buffer.length());
if (result == -1)
return Exception { InvalidStateError, "Failed to read from file"_s };
return result;
}
ExceptionOr<unsigned long long> FileSystemSyncAccessHandle::write(BufferSource&& buffer, FileSystemSyncAccessHandle::FilesystemReadWriteOptions options)
{
ASSERT(!isMainThread());
if (isClosingOrClosed())
return Exception { InvalidStateError, "AccessHandle is closing or closed"_s };
if (!m_pendingPromises.isEmpty())
return Exception { InvalidStateError, "Access handle has unfinished operation"_s };
int result = FileSystem::seekFile(m_file.handle(), options.at, FileSystem::FileSeekOrigin::Beginning);
if (result == -1)
return Exception { InvalidStateError, "Failed to write at offset"_s };
result = FileSystem::writeToFile(m_file.handle(), buffer.data(), buffer.length());
if (result == -1)
return Exception { InvalidStateError, "Failed to write to file"_s };
return result;
}
void FileSystemSyncAccessHandle::completePromise(Result&& result)
{
if (m_pendingPromises.isEmpty())
return;
auto pendingPromise = m_pendingPromises.takeFirst();
WTF::switchOn(WTFMove(result), [&pendingPromise](ExceptionOr<void>&& result) {
auto* promise = std::get_if<DOMPromiseDeferred<void>>(&pendingPromise);
ASSERT(promise);
promise->settle(WTFMove(result));
}, [&pendingPromise](ExceptionOr<uint64_t>&& result) {
auto* promise = std::get_if<DOMPromiseDeferred<IDLUnsignedLongLong>>(&pendingPromise);
ASSERT(promise);
promise->settle(WTFMove(result));
});
}
const char* FileSystemSyncAccessHandle::activeDOMObjectName() const
{
return "FileSystemSyncAccessHandle";
}
void FileSystemSyncAccessHandle::stop()
{
closeInternal([](auto) { });
}
void FileSystemSyncAccessHandle::invalidate()
{
closeFile();
// Invalidation is initiated by backend.
didCloseBackend({ });
}
} // namespace WebCore