Revision control
Copy as Markdown
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qrandomaccessasyncfile_p_p.h"
#include "qiooperation_p.h"
#include "qiooperation_p_p.h"
#include "qplatformdefs.h"
#include <QtCore/qdir.h>
#include <QtCore/qfile.h>
#include <QtCore/private/qfilesystemengine_p.h>
QT_BEGIN_NAMESPACE
namespace {
static bool isBarrierOperation(QIOOperation::Type type)
{
return type == QIOOperation::Type::Flush || type == QIOOperation::Type::Open;
}
} // anonymous namespace
// Fine to provide the definition here, because all the usages are in this file
// only!
template <typename Operation, typename ...Args>
Operation *
QRandomAccessAsyncFileNativeBackend::addOperation(QIOOperation::Type type, qint64 offset, Args &&...args)
{
auto dataStorage = new QtPrivate::QIOOperationDataStorage(std::forward<Args>(args)...);
auto *priv = new QIOOperationPrivate(dataStorage);
priv->offset = offset;
priv->type = type;
Operation *op = new Operation(*priv, m_owner);
auto opId = getNextId();
m_operations.push_back(OperationInfo(opId, op));
startOperationsUntilBarrier();
return op;
}
QRandomAccessAsyncFileNativeBackend::QRandomAccessAsyncFileNativeBackend(QRandomAccessAsyncFile *owner)
: QRandomAccessAsyncFileBackend(owner)
{
}
QRandomAccessAsyncFileNativeBackend::~QRandomAccessAsyncFileNativeBackend()
= default;
bool QRandomAccessAsyncFileNativeBackend::init()
{
return true;
}
void QRandomAccessAsyncFileNativeBackend::cancelAndWait(QIOOperation *op)
{
auto it = std::find_if(m_operations.cbegin(), m_operations.cend(),
[op](const auto &opInfo) {
return opInfo.operation.get() == op;
});
// not found
if (it == m_operations.cend())
return;
const auto opInfo = m_operations.takeAt(std::distance(m_operations.cbegin(), it));
if (opInfo.state == OpState::Running) {
// cancel this operation
m_mutex.lock();
if (m_runningOps.contains(opInfo.opId)) {
m_opToCancel = opInfo.opId;
closeIoChannel(opInfo.channel);
m_cancellationCondition.wait(&m_mutex);
m_opToCancel = kInvalidOperationId; // reset
}
m_mutex.unlock();
} // otherwise it was not started yet
// clean up the operation
releaseIoChannel(opInfo.channel);
auto *priv = QIOOperationPrivate::get(opInfo.operation);
priv->setError(QIOOperation::Error::Aborted);
// we could cancel a barrier operation, so try to execute next operations
startOperationsUntilBarrier();
}
void QRandomAccessAsyncFileNativeBackend::close()
{
if (m_fileState == FileState::Closed)
return;
// cancel all operations
m_mutex.lock();
m_opToCancel = kAllOperationIds;
for (const auto &op : m_operations) {
if (op.channel) {
closeIoChannel(op.channel);
}
}
closeIoChannel(m_ioChannel);
// we're not interested in any results anymore
if (!m_runningOps.isEmpty() || m_ioChannel)
m_cancellationCondition.wait(&m_mutex);
m_opToCancel = kInvalidOperationId; // reset
m_mutex.unlock();
// clean up all operations
for (auto &opInfo : m_operations) {
releaseIoChannel(opInfo.channel);
auto *priv = QIOOperationPrivate::get(opInfo.operation);
priv->setError(QIOOperation::Error::Aborted);
}
m_operations.clear();
releaseIoChannel(m_ioChannel);
if (m_fd >= 0) {
::close(m_fd);
m_fd = -1;
}
m_fileState = FileState::Closed;
}
qint64 QRandomAccessAsyncFileNativeBackend::size() const
{
if (m_fileState != FileState::Opened)
return -1;
QFileSystemMetaData metaData;
if (QFileSystemEngine::fillMetaData(m_fd, metaData))
return metaData.size();
return -1;
}
QIOOperation *
QRandomAccessAsyncFileNativeBackend::open(const QString &path, QIODeviceBase::OpenMode mode)
{
if (m_fileState == FileState::Closed) {
m_filePath = path;
m_openMode = mode;
// Open is a barrier, so we won't have two open() operations running
// in parallel
m_fileState = FileState::OpenPending;
}
return addOperation<QIOOperation>(QIOOperation::Type::Open, 0);
}
QIOOperation *QRandomAccessAsyncFileNativeBackend::flush()
{
return addOperation<QIOOperation>(QIOOperation::Type::Flush, 0);
}
QIOReadOperation *QRandomAccessAsyncFileNativeBackend::read(qint64 offset, qint64 maxSize)
{
QByteArray array(maxSize, Qt::Uninitialized);
return addOperation<QIOReadOperation>(QIOOperation::Type::Read, offset, std::move(array));
}
QIOWriteOperation *QRandomAccessAsyncFileNativeBackend::write(qint64 offset, const QByteArray &data)
{
QByteArray copy = data;
return write(offset, std::move(copy));
}
QIOWriteOperation *QRandomAccessAsyncFileNativeBackend::write(qint64 offset, QByteArray &&data)
{
return addOperation<QIOWriteOperation>(QIOOperation::Type::Write, offset, std::move(data));
}
QIOVectoredReadOperation *
QRandomAccessAsyncFileNativeBackend::readInto(qint64 offset, QSpan<std::byte> buffer)
{
return addOperation<QIOVectoredReadOperation>(QIOOperation::Type::Read, offset,
QSpan<const QSpan<std::byte>>{buffer});
}
QIOVectoredWriteOperation *
QRandomAccessAsyncFileNativeBackend::writeFrom(qint64 offset, QSpan<const std::byte> buffer)
{
return addOperation<QIOVectoredWriteOperation>(QIOOperation::Type::Write, offset,
QSpan<const QSpan<const std::byte>>{buffer});
}
QIOVectoredReadOperation *
QRandomAccessAsyncFileNativeBackend::readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers)
{
// GCD implementation does not have vectored read. Spawning several read
// operations (each with an updated offset), is not ideal, because some
// of them could fail, and it wouldn't be clear what would be the return
// value in such case.
// So, we'll just execute several reads one-after-another, and complete the
// whole operation only when they all finish (or when an operation fails
// at some point).
return addOperation<QIOVectoredReadOperation>(QIOOperation::Type::Read, offset, buffers);
}
QIOVectoredWriteOperation *
QRandomAccessAsyncFileNativeBackend::writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers)
{
return addOperation<QIOVectoredWriteOperation>(QIOOperation::Type::Write, offset, buffers);
}
void QRandomAccessAsyncFileNativeBackend::notifyIfOperationsAreCompleted()
{
QMutexLocker locker(&m_mutex);
--m_numChannelsToClose;
if (m_opToCancel == kAllOperationIds) {
if (m_numChannelsToClose == 0 && m_runningOps.isEmpty())
m_cancellationCondition.wakeOne();
}
}
dispatch_io_t QRandomAccessAsyncFileNativeBackend::createMainChannel(int fd)
{
auto sharedThis = this;
auto channel =
dispatch_io_create(DISPATCH_IO_RANDOM, fd,
dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
^(int /*error*/) {
// main I/O channel uses kInvalidOperationId
// as its identifier
sharedThis->notifyIfOperationsAreCompleted();
});
if (channel) {
QMutexLocker locker(&m_mutex);
++m_numChannelsToClose;
}
return channel;
}
dispatch_io_t QRandomAccessAsyncFileNativeBackend::duplicateIoChannel(OperationId opId)
{
if (!m_ioChannel)
return nullptr;
// We need to create a new channel for each operation, because the only way
// to cancel an operation is to call dispatch_io_close() with
// DISPATCH_IO_STOP flag.
auto sharedThis = this;
auto channel =
dispatch_io_create_with_io(DISPATCH_IO_RANDOM, m_ioChannel,
dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
^(int /*error*/){
sharedThis->notifyIfOperationsAreCompleted();
});
if (channel) {
QMutexLocker locker(&m_mutex);
m_runningOps.insert(opId);
++m_numChannelsToClose;
}
return channel;
}
void QRandomAccessAsyncFileNativeBackend::closeIoChannel(dispatch_io_t channel)
{
if (channel)
dispatch_io_close(channel, DISPATCH_IO_STOP);
}
void QRandomAccessAsyncFileNativeBackend::releaseIoChannel(dispatch_io_t channel)
{
if (channel) {
dispatch_release(channel);
channel = nullptr;
}
}
void QRandomAccessAsyncFileNativeBackend::handleOperationComplete(const OperationResult &opResult)
{
// try to start next operations on return
auto onReturn = qScopeGuard([this] {
startOperationsUntilBarrier();
});
auto it = std::find_if(m_operations.cbegin(), m_operations.cend(),
[opId = opResult.opId](const auto &opInfo) {
return opInfo.opId == opId;
});
if (it == m_operations.cend())
return;
qsizetype idx = std::distance(m_operations.cbegin(), it);
const OperationInfo info = m_operations.takeAt(idx);
closeIoChannel(info.channel);
releaseIoChannel(info.channel);
if (!info.operation)
return;
auto convertError = [](int error, QIOOperation::Type type) {
if (error == 0) {
return QIOOperation::Error::None;
} else if (error == ECANCELED) {
return QIOOperation::Error::Aborted;
} else if (error == EBADF) {
return QIOOperation::Error::FileNotOpen;
} else if (error == EINVAL) {
switch (type) {
case QIOOperation::Type::Read:
case QIOOperation::Type::Write:
return QIOOperation::Error::IncorrectOffset;
case QIOOperation::Type::Flush:
return QIOOperation::Error::Flush;
case QIOOperation::Type::Open:
return QIOOperation::Error::Open;
case QIOOperation::Type::Unknown:
Q_UNREACHABLE_RETURN(QIOOperation::Error::FileNotOpen);
}
} else {
switch (type) {
case QIOOperation::Type::Read:
return QIOOperation::Error::Read;
case QIOOperation::Type::Write:
return QIOOperation::Error::Write;
case QIOOperation::Type::Flush:
return QIOOperation::Error::Flush;
case QIOOperation::Type::Open:
return QIOOperation::Error::Open;
case QIOOperation::Type::Unknown:
Q_UNREACHABLE_RETURN(QIOOperation::Error::FileNotOpen);
}
}
};
auto *priv = QIOOperationPrivate::get(info.operation);
switch (priv->type) {
case QIOOperation::Type::Read:
case QIOOperation::Type::Write:
priv->appendBytesProcessed(opResult.result);
// make sure that read buffers are truncated to the actual amount of
// bytes read
if (priv->type == QIOOperation::Type::Read) {
auto dataStorage = priv->dataStorage.get();
auto processed = priv->processed;
if (dataStorage->containsByteArray()) {
QByteArray &array = dataStorage->getByteArray();
array.truncate(processed);
} else if (dataStorage->containsReadSpans()) {
qint64 left = processed;
auto &readBuffers = dataStorage->getReadSpans();
for (auto &s : readBuffers) {
const qint64 spanSize = qint64(s.size_bytes());
const qint64 newSize = (std::min)(left, spanSize);
if (newSize < spanSize)
s.chop(spanSize - newSize);
left -= newSize;
}
}
}
priv->operationComplete(convertError(opResult.error, priv->type));
break;
case QIOOperation::Type::Flush: {
const QIOOperation::Error error = convertError(opResult.error, priv->type);
priv->operationComplete(error);
break;
}
case QIOOperation::Type::Open: {
const QIOOperation::Error error = convertError(opResult.error, priv->type);
if (opResult.result >= 0 && error == QIOOperation::Error::None) {
m_fd = (int)opResult.result;
m_ioChannel = createMainChannel(m_fd);
m_fileState = FileState::Opened;
} else {
m_fileState = FileState::Closed;
}
priv->operationComplete(error);
break;
}
case QIOOperation::Type::Unknown:
Q_UNREACHABLE();
break;
}
}
void QRandomAccessAsyncFileNativeBackend::queueCompletion(OperationId opId, int error)
{
const OperationResult res = { opId, 0LL, error };
QMetaObject::invokeMethod(m_owner, [this, res] {
handleOperationComplete(res);
}, Qt::QueuedConnection);
}
void QRandomAccessAsyncFileNativeBackend::startOperationsUntilBarrier()
{
// starts all operations until barrier, or a barrier operation if it's the
// first one
bool first = true;
for (auto &opInfo : m_operations) {
const bool isBarrier = isBarrierOperation(opInfo.operation->type());
const bool shouldExecute = (opInfo.state == OpState::Pending) && (!isBarrier || first);
first = false;
if (shouldExecute) {
opInfo.state = OpState::Running;
switch (opInfo.operation->type()) {
case QIOOperation::Type::Read:
executeRead(opInfo);
break;
case QIOOperation::Type::Write:
executeWrite(opInfo);
break;
case QIOOperation::Type::Flush:
executeFlush(opInfo);
break;
case QIOOperation::Type::Open:
executeOpen(opInfo);
break;
case QIOOperation::Type::Unknown:
Q_UNREACHABLE();
break;
}
}
if (isBarrier)
break;
}
}
void QRandomAccessAsyncFileNativeBackend::executeRead(OperationInfo &opInfo)
{
opInfo.channel = duplicateIoChannel(opInfo.opId);
if (!opInfo.channel) {
queueCompletion(opInfo.opId, EBADF);
return;
}
auto priv = QIOOperationPrivate::get(opInfo.operation);
auto dataStorage = priv->dataStorage.get();
if (dataStorage->containsByteArray()) {
auto &array = dataStorage->getByteArray();
char *bytesPtr = array.data();
qint64 maxSize = array.size();
readOneBufferHelper(opInfo.opId, opInfo.channel, priv->offset,
bytesPtr, maxSize,
0, 1, 0);
} else {
Q_ASSERT(dataStorage->containsReadSpans());
auto &readBuffers = dataStorage->getReadSpans();
const auto totalBuffers = readBuffers.size();
if (totalBuffers == 0) {
queueCompletion(opInfo.opId, 0);
return;
}
auto buf = readBuffers[0];
readOneBufferHelper(opInfo.opId, opInfo.channel, priv->offset,
buf.data(), buf.size(),
0, totalBuffers, 0);
}
}
void QRandomAccessAsyncFileNativeBackend::executeWrite(OperationInfo &opInfo)
{
opInfo.channel = duplicateIoChannel(opInfo.opId);
if (!opInfo.channel) {
queueCompletion(opInfo.opId, EBADF);
return;
}
auto priv = QIOOperationPrivate::get(opInfo.operation);
auto dataStorage = priv->dataStorage.get();
if (dataStorage->containsByteArray()) {
const auto &array = dataStorage->getByteArray();
const char *dataPtr = array.constData();
const qint64 dataSize = array.size();
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
// We handle the bytes on our own, so we need to specify an empty block as
// a destructor.
// dataToWrite is retained, so should be properly cleaned up. We always do
// it in the callback.
dispatch_data_t dataToWrite = dispatch_data_create(dataPtr, dataSize, queue, ^{});
writeHelper(opInfo.opId, opInfo.channel, priv->offset, dataToWrite, dataSize);
} else {
Q_ASSERT(dataStorage->containsWriteSpans());
const auto &writeBuffers = dataStorage->getWriteSpans();
const auto totalBuffers = writeBuffers.size();
if (totalBuffers == 0) {
queueCompletion(opInfo.opId, 0);
return;
}
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
qsizetype idx = 0;
dispatch_data_t dataToWrite = dispatch_data_empty;
qint64 totalSize = 0;
do {
const std::byte *dataPtr = writeBuffers[idx].data();
const qint64 dataSize = writeBuffers[idx].size();
dispatch_data_t data = dispatch_data_create(dataPtr, dataSize, queue, ^{});
dataToWrite = dispatch_data_create_concat(dataToWrite, data);
[data release];
totalSize += dataSize;
} while (++idx < totalBuffers);
writeHelper(opInfo.opId, opInfo.channel, priv->offset, dataToWrite, totalSize);
}
}
void QRandomAccessAsyncFileNativeBackend::executeFlush(OperationInfo &opInfo)
{
opInfo.channel = duplicateIoChannel(opInfo.opId);
if (!opInfo.channel) {
queueCompletion(opInfo.opId, EBADF);
return;
}
// flush() is a barrier operation, but dispatch_io_barrier does not work
// as documented with multiple channels :(
auto sharedThis = this;
const int fd = m_fd;
const OperationId opId = opInfo.opId;
dispatch_io_barrier(opInfo.channel, ^{
const int err = fsync(fd);
QMutexLocker locker(&sharedThis->m_mutex);
sharedThis->m_runningOps.remove(opId);
const auto cancelId = sharedThis->m_opToCancel;
if (cancelId == kAllOperationIds || cancelId == opId) {
if (cancelId == opId) {
sharedThis->m_cancellationCondition.wakeOne();
} else { /* kAllOperationIds */
if (sharedThis->m_numChannelsToClose == 0
&& sharedThis->m_runningOps.isEmpty()) {
sharedThis->m_cancellationCondition.wakeOne();
}
}
} else {
auto context = sharedThis->m_owner;
const OperationResult res = { opId, 0LL, err };
QMetaObject::invokeMethod(context, [sharedThis](const OperationResult &r) {
sharedThis->handleOperationComplete(r);
}, Qt::QueuedConnection, res);
}
});
}
void QRandomAccessAsyncFileNativeBackend::executeOpen(OperationInfo &opInfo)
{
if (m_fileState != FileState::OpenPending) {
queueCompletion(opInfo.opId, EINVAL);
return;
}
const QByteArray nativeName = QFile::encodeName(QDir::toNativeSeparators(m_filePath));
int openFlags = QFSFileEnginePrivate::openModeToOpenFlags(m_openMode);
openFlags |= O_NONBLOCK;
auto sharedThis = this;
const OperationId opId = opInfo.opId;
// We don'd call duplicateIOChannel(), so need to update the running ops
// explicitly.
m_mutex.lock();
m_runningOps.insert(opId);
m_mutex.unlock();
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
^{
int err = 0;
const int fd = ::open(nativeName.data(), openFlags);
if (fd < 0)
err = errno;
QMutexLocker locker(&sharedThis->m_mutex);
sharedThis->m_runningOps.remove(opId);
const auto cancelId = sharedThis->m_opToCancel;
if (cancelId == kAllOperationIds || cancelId == opId) {
// open() is a barrier operation, so it's always the
// only executing operation.
// Also, the main IO channel is not created yet.
// So we need to notify the condition variable in
// both cases.
Q_ASSERT(sharedThis->m_runningOps.isEmpty());
Q_ASSERT(sharedThis->m_numChannelsToClose == 0);
sharedThis->m_cancellationCondition.wakeOne();
} else {
auto context = sharedThis->m_owner;
const OperationResult res = { opId, qint64(fd), err };
QMetaObject::invokeMethod(context,
[sharedThis](const OperationResult &r) {
sharedThis->handleOperationComplete(r);
}, Qt::QueuedConnection, res);
}
});
}
void QRandomAccessAsyncFileNativeBackend::readOneBuffer(OperationId opId, qsizetype bufferIdx,
qint64 alreadyRead)
{
// we need to lookup the operation again, because it could have beed removed
// by the user...
auto it = std::find_if(m_operations.cbegin(), m_operations.cend(),
[opId](const auto &opInfo) {
return opId == opInfo.opId;
});
if (it == m_operations.cend())
return;
auto op = it->operation; // QPointer could be null
if (!op) {
closeIoChannel(it->channel);
return;
}
auto *priv = QIOOperationPrivate::get(op);
Q_ASSERT(priv->type == QIOOperation::Type::Read);
Q_ASSERT(priv->dataStorage->containsReadSpans());
auto &readBuffers = priv->dataStorage->getReadSpans();
Q_ASSERT(readBuffers.size() > bufferIdx);
qint64 newOffset = priv->offset;
for (qsizetype idx = 0; idx < bufferIdx; ++idx)
newOffset += readBuffers[idx].size();
std::byte *bytesPtr = readBuffers[bufferIdx].data();
qint64 maxSize = readBuffers[bufferIdx].size();
readOneBufferHelper(opId, it->channel, newOffset, bytesPtr, maxSize, bufferIdx,
readBuffers.size(), alreadyRead);
}
void QRandomAccessAsyncFileNativeBackend::readOneBufferHelper(OperationId opId, dispatch_io_t channel,
qint64 offset, void *bytesPtr,
qint64 maxSize, qsizetype currentBufferIdx,
qsizetype totalBuffers, qint64 alreadyRead)
{
auto sharedThis = this;
__block size_t readFromBuffer = 0;
dispatch_io_read(channel, offset, maxSize,
dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
^(bool done, dispatch_data_t data, int error) {
// Handle data. If there's an error, handle as much as
// we can.
if (data) {
dispatch_data_apply(data, ^(dispatch_data_t /*region*/, size_t offset,
const void *buffer, size_t size) {
const char *startPtr =
reinterpret_cast<const char *>(buffer) + offset;
// NOTE: This is a copy, but looks like we
// cannot do better :(
std::memcpy((std::byte *)bytesPtr + readFromBuffer,
startPtr, size);
readFromBuffer += size;
return true; // Keep processing if there is more data.
});
}
// We're interested in handling the results only when
// the operation is done. This can mean either
// successful completion or an error (including
// cancellation).
if (!done)
return;
QMutexLocker locker(&sharedThis->m_mutex);
const auto cancelId = sharedThis->m_opToCancel;
if (cancelId == kAllOperationIds || cancelId == opId) {
sharedThis->m_runningOps.remove(opId);
if (cancelId == opId) {
sharedThis->m_cancellationCondition.wakeOne();
} else { /* kAllOperationIds */
if (sharedThis->m_numChannelsToClose == 0
&& sharedThis->m_runningOps.isEmpty()) {
sharedThis->m_cancellationCondition.wakeOne();
}
}
} else {
sharedThis->m_runningOps.remove(opId);
auto context = sharedThis->m_owner;
// if error, or last buffer, or read less than expected,
// report operation completion
qint64 totalRead = qint64(readFromBuffer) + alreadyRead;
qsizetype nextBufferIdx = currentBufferIdx + 1;
if (error || nextBufferIdx == totalBuffers
|| qint64(readFromBuffer) != maxSize) {
const OperationResult res = { opId, totalRead, error };
QMetaObject::invokeMethod(context,
[sharedThis](const OperationResult &r) {
sharedThis->handleOperationComplete(r);
}, Qt::QueuedConnection, res);
} else {
// else execute read for the next buffer
QMetaObject::invokeMethod(context,
[sharedThis, opId, nextBufferIdx, totalRead] {
sharedThis->readOneBuffer(opId, nextBufferIdx, totalRead);
}, Qt::QueuedConnection);
}
}
});
}
void QRandomAccessAsyncFileNativeBackend::writeHelper(OperationId opId, dispatch_io_t channel,
qint64 offset, dispatch_data_t dataToWrite,
qint64 dataSize)
{
auto sharedThis = this;
dispatch_io_write(channel, offset, dataToWrite,
dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
^(bool done, dispatch_data_t data, int error) {
// We're interested in handling the results only when
// the operation is done. This can mean either
// successful completion or an error (including
// cancellation). In case of an error return the
// amount that we have written so far.
if (!done)
return;
QMutexLocker locker(&sharedThis->m_mutex);
const auto cancelId = sharedThis->m_opToCancel;
if (cancelId == kAllOperationIds || cancelId == opId) {
// Operation is canceled - do nothing
sharedThis->m_runningOps.remove(opId);
if (cancelId == opId) {
sharedThis->m_cancellationCondition.wakeOne();
} else { /* kAllOperationIds */
if (sharedThis->m_numChannelsToClose == 0
&& sharedThis->m_runningOps.isEmpty()) {
sharedThis->m_cancellationCondition.wakeOne();
}
}
} else {
sharedThis->m_runningOps.remove(opId);
// if no error, an attempt to access the data will
// crash, because it seems to have no buffer
// allocated (as everything was written)
const size_t toBeWritten =
(error == 0) ? 0 : dispatch_data_get_size(data);
const size_t written = dataSize - toBeWritten;
[dataToWrite release];
auto context = sharedThis->m_owner;
const OperationResult res = { opId, qint64(written), error };
QMetaObject::invokeMethod(context,
[sharedThis](const OperationResult &r) {
sharedThis->handleOperationComplete(r);
}, Qt::QueuedConnection, res);
}
});
}
QRandomAccessAsyncFileNativeBackend::OperationId QRandomAccessAsyncFileNativeBackend::getNextId()
{
// never return reserved values
static OperationId opId = kInvalidOperationId;
if (++opId == kAllOperationIds)
opId = kInvalidOperationId + 1;
return opId;
}
QT_END_NAMESPACE