Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qrandomaccessasyncfile_darwin.mm
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
9#include "qplatformdefs.h"
10
11#include <QtCore/qdir.h>
12#include <QtCore/qfile.h>
13#include <QtCore/private/qfilesystemengine_p.h>
14
16
17namespace {
18
19static bool isBarrierOperation(QIOOperation::Type type)
20{
21 return type == QIOOperation::Type::Flush || type == QIOOperation::Type::Open;
22}
23
24} // anonymous namespace
25
26// Fine to provide the definition here, because all the usages are in this file
27// only!
28template <typename Operation, typename ...Args>
29Operation *
30QRandomAccessAsyncFilePrivate::addOperation(QIOOperation::Type type, qint64 offset, Args &&...args)
31{
32 auto dataStorage = new QtPrivate::QIOOperationDataStorage(std::forward<Args>(args)...);
33 auto *priv = new QIOOperationPrivate(dataStorage);
34 priv->offset = offset;
35 priv->type = type;
36
37 Operation *op = new Operation(*priv, q_ptr);
38 auto opId = getNextId();
39 m_operations.push_back(OperationInfo(opId, op));
40 startOperationsUntilBarrier();
41
42 return op;
43}
44
45QRandomAccessAsyncFilePrivate::QRandomAccessAsyncFilePrivate()
46 : QObjectPrivate()
47{
48}
49
51 = default;
52
56
57void QRandomAccessAsyncFilePrivate::cancelAndWait(QIOOperation *op)
58{
59 auto it = std::find_if(m_operations.cbegin(), m_operations.cend(),
60 [op](const auto &opInfo) {
61 return opInfo.operation.get() == op;
62 });
63 // not found
64 if (it == m_operations.cend())
65 return;
66
67 const auto opInfo = m_operations.takeAt(std::distance(m_operations.cbegin(), it));
68
69 if (opInfo.state == OpState::Running) {
70 // cancel this operation
71 m_mutex.lock();
72 if (m_runningOps.contains(opInfo.opId)) {
73 m_opToCancel = opInfo.opId;
74 closeIoChannel(opInfo.channel);
75 m_cancellationCondition.wait(&m_mutex);
76 m_opToCancel = kInvalidOperationId; // reset
77 }
78 m_mutex.unlock();
79 } // otherwise it was not started yet
80
81 // clean up the operation
82 releaseIoChannel(opInfo.channel);
83 auto *priv = QIOOperationPrivate::get(opInfo.operation);
84 priv->setError(QIOOperation::Error::Aborted);
85
86 // we could cancel a barrier operation, so try to execute next operations
87 startOperationsUntilBarrier();
88}
89
91{
92 if (m_fileState == FileState::Closed)
93 return;
94
95 // cancel all operations
96 m_mutex.lock();
97 m_opToCancel = kAllOperationIds;
98 for (const auto &op : m_operations)
99 closeIoChannel(op.channel);
100 closeIoChannel(m_ioChannel);
101 // we're not interested in any results anymore
102 if (!m_runningOps.isEmpty() || m_ioChannel)
103 m_cancellationCondition.wait(&m_mutex);
104 m_opToCancel = kInvalidOperationId; // reset
105 m_mutex.unlock();
106
107 // clean up all operations
108 for (auto &opInfo : m_operations) {
109 releaseIoChannel(opInfo.channel);
110 auto *priv = QIOOperationPrivate::get(opInfo.operation);
111 priv->setError(QIOOperation::Error::Aborted);
112 }
113 m_operations.clear();
114
115 releaseIoChannel(m_ioChannel);
116
117 if (m_fd >= 0) {
118 ::close(m_fd);
119 m_fd = -1;
120 }
121
122 m_fileState = FileState::Closed;
123}
124
126{
127 if (m_fileState != FileState::Opened)
128 return -1;
129
130 QFileSystemMetaData metaData;
131 if (QFileSystemEngine::fillMetaData(m_fd, metaData))
132 return metaData.size();
133
134 return -1;
135}
136
138QRandomAccessAsyncFilePrivate::open(const QString &path, QIODeviceBase::OpenMode mode)
139{
140 if (m_fileState == FileState::Closed) {
141 m_filePath = path;
142 m_openMode = mode;
143 // Open is a barrier, so we won't have two open() operations running
144 // in parallel
145 m_fileState = FileState::OpenPending;
146 }
147
148 return addOperation<QIOOperation>(QIOOperation::Type::Open, 0);
149}
150
152{
153 return addOperation<QIOOperation>(QIOOperation::Type::Flush, 0);
154}
155
157{
158 QByteArray array(maxSize, Qt::Uninitialized);
159 return addOperation<QIOReadOperation>(QIOOperation::Type::Read, offset, std::move(array));
160}
161
162QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, const QByteArray &data)
163{
164 QByteArray copy = data;
165 return write(offset, std::move(copy));
166}
167
168QIOWriteOperation *QRandomAccessAsyncFilePrivate::write(qint64 offset, QByteArray &&data)
169{
170 return addOperation<QIOWriteOperation>(QIOOperation::Type::Write, offset, std::move(data));
171}
172
174QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<std::byte> buffer)
175{
176 return addOperation<QIOVectoredReadOperation>(QIOOperation::Type::Read, offset,
177 QSpan<const QSpan<std::byte>>{buffer});
178}
179
181QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan<const std::byte> buffer)
182{
183 return addOperation<QIOVectoredWriteOperation>(QIOOperation::Type::Write, offset,
184 QSpan<const QSpan<const std::byte>>{buffer});
185}
186
188QRandomAccessAsyncFilePrivate::readInto(qint64 offset, QSpan<const QSpan<std::byte>> buffers)
189{
190 // GCD implementation does not have vectored read. Spawning several read
191 // operations (each with an updated offset), is not ideal, because some
192 // of them could fail, and it wouldn't be clear what would be the return
193 // value in such case.
194 // So, we'll just execute several reads one-after-another, and complete the
195 // whole operation only when they all finish (or when an operation fails
196 // at some point).
197
198 return addOperation<QIOVectoredReadOperation>(QIOOperation::Type::Read, offset, buffers);
199}
200
202QRandomAccessAsyncFilePrivate::writeFrom(qint64 offset, QSpan<const QSpan<const std::byte>> buffers)
203{
204 return addOperation<QIOVectoredWriteOperation>(QIOOperation::Type::Write, offset, buffers);
205}
206
207dispatch_io_t QRandomAccessAsyncFilePrivate::createMainChannel(int fd)
208{
209 auto sharedThis = this;
210 return dispatch_io_create(DISPATCH_IO_RANDOM, fd,
211 dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
212 ^(int /*error*/) {
213 // Notify that the file descriptor can be closed
214 QMutexLocker locker(&sharedThis->m_mutex);
215 sharedThis->m_cancellationCondition.wakeOne();
216 });
217}
218
219dispatch_io_t QRandomAccessAsyncFilePrivate::duplicateIoChannel(OperationId opId)
220{
221 if (!m_ioChannel)
222 return nullptr;
223 // We need to create a new channel for each operation, because the only way
224 // to cancel an operation is to call dispatch_io_close() with
225 // DISPATCH_IO_STOP flag.
226 // We do not care about the callback in this case, because we have the
227 // callback from the "main" io channel to do all the proper cleanup
228 auto channel =
229 dispatch_io_create_with_io(DISPATCH_IO_RANDOM, m_ioChannel,
230 dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
231 ^(int){ /* empty callback */ });
232
233 if (channel) {
234 QMutexLocker locker(&m_mutex);
235 m_runningOps.insert(opId);
236 }
237 return channel;
238}
239
240void QRandomAccessAsyncFilePrivate::closeIoChannel(dispatch_io_t channel)
241{
242 if (channel)
243 dispatch_io_close(channel, DISPATCH_IO_STOP);
244}
245
246void QRandomAccessAsyncFilePrivate::releaseIoChannel(dispatch_io_t channel)
247{
248 if (channel) {
249 dispatch_release(channel);
250 channel = nullptr;
251 }
252}
253
254void QRandomAccessAsyncFilePrivate::handleOperationComplete(const OperationResult &opResult)
255{
256 // try to start next operations on return
257 auto onReturn = qScopeGuard([this] {
258 startOperationsUntilBarrier();
259 });
260
261 auto it = std::find_if(m_operations.cbegin(), m_operations.cend(),
262 [opId = opResult.opId](const auto &opInfo) {
263 return opInfo.opId == opId;
264 });
265 if (it == m_operations.cend())
266 return;
267 qsizetype idx = std::distance(m_operations.cbegin(), it);
268
269 const OperationInfo info = m_operations.takeAt(idx);
270 closeIoChannel(info.channel);
271 releaseIoChannel(info.channel);
272
273 if (!info.operation)
274 return;
275
276 auto convertError = [](int error, QIOOperation::Type type) {
277 if (error == 0) {
278 return QIOOperation::Error::None;
279 } else if (error == ECANCELED) {
280 return QIOOperation::Error::Aborted;
281 } else if (error == EBADF) {
282 return QIOOperation::Error::FileNotOpen;
283 } else if (error == EINVAL) {
284 switch (type) {
285 case QIOOperation::Type::Read:
286 case QIOOperation::Type::Write:
287 return QIOOperation::Error::IncorrectOffset;
288 case QIOOperation::Type::Flush:
289 return QIOOperation::Error::Flush;
290 case QIOOperation::Type::Open:
291 return QIOOperation::Error::Open;
292 case QIOOperation::Type::Unknown:
293 Q_UNREACHABLE_RETURN(QIOOperation::Error::FileNotOpen);
294 }
295 } else {
296 switch (type) {
297 case QIOOperation::Type::Read:
298 return QIOOperation::Error::Read;
299 case QIOOperation::Type::Write:
300 return QIOOperation::Error::Write;
301 case QIOOperation::Type::Flush:
302 return QIOOperation::Error::Flush;
303 case QIOOperation::Type::Open:
304 return QIOOperation::Error::Open;
305 case QIOOperation::Type::Unknown:
306 Q_UNREACHABLE_RETURN(QIOOperation::Error::FileNotOpen);
307 }
308 }
309 };
310
311 auto *priv = QIOOperationPrivate::get(info.operation);
312 switch (priv->type) {
313 case QIOOperation::Type::Read:
314 case QIOOperation::Type::Write:
315 priv->appendBytesProcessed(opResult.result);
316 // make sure that read buffers are truncated to the actual amount of
317 // bytes read
318 if (priv->type == QIOOperation::Type::Read) {
319 auto dataStorage = priv->dataStorage.get();
320 auto processed = priv->processed;
321 if (dataStorage->containsByteArray()) {
322 QByteArray &array = dataStorage->getByteArray();
323 array.truncate(processed);
324 } else if (dataStorage->containsReadSpans()) {
325 qint64 left = processed;
326 auto &readBuffers = dataStorage->getReadSpans();
327 for (auto &s : readBuffers) {
328 const qint64 spanSize = qint64(s.size_bytes());
329 const qint64 newSize = (std::min)(left, spanSize);
330 if (newSize < spanSize)
331 s.chop(spanSize - newSize);
332 left -= newSize;
333 }
334 }
335 }
336 priv->operationComplete(convertError(opResult.error, priv->type));
337 break;
338 case QIOOperation::Type::Flush: {
339 const QIOOperation::Error error = convertError(opResult.error, priv->type);
340 priv->operationComplete(error);
341 break;
342 }
343 case QIOOperation::Type::Open: {
344 const QIOOperation::Error error = convertError(opResult.error, priv->type);
345 if (opResult.result >= 0 && error == QIOOperation::Error::None) {
346 m_fd = (int)opResult.result;
347 m_ioChannel = createMainChannel(m_fd);
348 m_fileState = FileState::Opened;
349 } else {
350 m_fileState = FileState::Closed;
351 }
352 priv->operationComplete(error);
353 break;
354 }
355 case QIOOperation::Type::Unknown:
356 Q_UNREACHABLE();
357 break;
358 }
359}
360
361void QRandomAccessAsyncFilePrivate::queueCompletion(OperationId opId, int error)
362{
363 const OperationResult res = { opId, 0LL, error };
364 QMetaObject::invokeMethod(q_ptr, [this, res] {
365 handleOperationComplete(res);
366 }, Qt::QueuedConnection);
367}
368
369void QRandomAccessAsyncFilePrivate::startOperationsUntilBarrier()
370{
371 // starts all operations until barrier, or a barrier operation if it's the
372 // first one
373 bool first = true;
374 for (auto &opInfo : m_operations) {
375 const bool isBarrier = isBarrierOperation(opInfo.operation->type());
376 const bool shouldExecute = (opInfo.state == OpState::Pending) && (!isBarrier || first);
377 first = false;
378 if (shouldExecute) {
379 opInfo.state = OpState::Running;
380 switch (opInfo.operation->type()) {
381 case QIOOperation::Type::Read:
382 executeRead(opInfo);
383 break;
384 case QIOOperation::Type::Write:
385 executeWrite(opInfo);
386 break;
387 case QIOOperation::Type::Flush:
388 executeFlush(opInfo);
389 break;
390 case QIOOperation::Type::Open:
391 executeOpen(opInfo);
392 break;
393 case QIOOperation::Type::Unknown:
394 Q_UNREACHABLE();
395 break;
396 }
397 }
398 if (isBarrier)
399 break;
400 }
401}
402
403void QRandomAccessAsyncFilePrivate::executeRead(OperationInfo &opInfo)
404{
405 opInfo.channel = duplicateIoChannel(opInfo.opId);
406 if (!opInfo.channel) {
407 queueCompletion(opInfo.opId, EBADF);
408 return;
409 }
410 auto priv = QIOOperationPrivate::get(opInfo.operation);
411 auto dataStorage = priv->dataStorage.get();
412 if (dataStorage->containsByteArray()) {
413 auto &array = dataStorage->getByteArray();
414 char *bytesPtr = array.data();
415 qint64 maxSize = array.size();
416 readOneBufferHelper(opInfo.opId, opInfo.channel, priv->offset,
417 bytesPtr, maxSize,
418 0, 1, 0);
419 } else {
420 Q_ASSERT(dataStorage->containsReadSpans());
421 auto &readBuffers = dataStorage->getReadSpans();
422 const auto totalBuffers = readBuffers.size();
423 if (totalBuffers == 0) {
424 queueCompletion(opInfo.opId, 0);
425 return;
426 }
427 auto buf = readBuffers[0];
428 readOneBufferHelper(opInfo.opId, opInfo.channel, priv->offset,
429 buf.data(), buf.size(),
430 0, totalBuffers, 0);
431 }
432}
433
434void QRandomAccessAsyncFilePrivate::executeWrite(OperationInfo &opInfo)
435{
436 opInfo.channel = duplicateIoChannel(opInfo.opId);
437 if (!opInfo.channel) {
438 queueCompletion(opInfo.opId, EBADF);
439 return;
440 }
441 auto priv = QIOOperationPrivate::get(opInfo.operation);
442 auto dataStorage = priv->dataStorage.get();
443 if (dataStorage->containsByteArray()) {
444 const auto &array = dataStorage->getByteArray();
445 const char *dataPtr = array.constData();
446 const qint64 dataSize = array.size();
447
448 dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
449 // We handle the bytes on our own, so we need to specify an empty block as
450 // a destructor.
451 // dataToWrite is retained, so should be properly cleaned up. We always do
452 // it in the callback.
453 dispatch_data_t dataToWrite = dispatch_data_create(dataPtr, dataSize, queue, ^{});
454
455 writeHelper(opInfo.opId, opInfo.channel, priv->offset, dataToWrite, dataSize);
456 } else {
457 Q_ASSERT(dataStorage->containsWriteSpans());
458
459 const auto &writeBuffers = dataStorage->getWriteSpans();
460 const auto totalBuffers = writeBuffers.size();
461 if (totalBuffers == 0) {
462 queueCompletion(opInfo.opId, 0);
463 return;
464 }
465
466 dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
467 qsizetype idx = 0;
468 dispatch_data_t dataToWrite = dispatch_data_empty;
469 qint64 totalSize = 0;
470 do {
471 const std::byte *dataPtr = writeBuffers[idx].data();
472 const qint64 dataSize = writeBuffers[idx].size();
473 dispatch_data_t data = dispatch_data_create(dataPtr, dataSize, queue, ^{});
474 dataToWrite = dispatch_data_create_concat(dataToWrite, data);
475 [data release];
476 totalSize += dataSize;
477 } while (++idx < totalBuffers);
478
479 writeHelper(opInfo.opId, opInfo.channel, priv->offset, dataToWrite, totalSize);
480 }
481}
482
483void QRandomAccessAsyncFilePrivate::executeFlush(OperationInfo &opInfo)
484{
485 opInfo.channel = duplicateIoChannel(opInfo.opId);
486 if (!opInfo.channel) {
487 queueCompletion(opInfo.opId, EBADF);
488 return;
489 }
490
491 // flush() is a barrier operation, but dispatch_io_barrier does not work
492 // as documented with multiple channels :(
493 auto sharedThis = this;
494 const int fd = m_fd;
495 const OperationId opId = opInfo.opId;
496 dispatch_io_barrier(opInfo.channel, ^{
497 const int err = fsync(fd);
498
499 QMutexLocker locker(&sharedThis->m_mutex);
500 sharedThis->m_runningOps.remove(opId);
501 const auto cancelId = sharedThis->m_opToCancel;
502 if (cancelId == kAllOperationIds || cancelId == opId) {
503 if (cancelId == opId)
504 sharedThis->m_cancellationCondition.wakeOne();
505 } else {
506 auto context = sharedThis->q_ptr;
507 const OperationResult res = { opId, 0LL, err };
508 QMetaObject::invokeMethod(context, [sharedThis](const OperationResult &r) {
509 sharedThis->handleOperationComplete(r);
510 }, Qt::QueuedConnection, res);
511 }
512 });
513}
514
515// stolen from qfsfileengine_unix.cpp
516static inline int openModeToOpenFlags(QIODevice::OpenMode mode)
517{
518 int oflags = QT_OPEN_RDONLY;
519#ifdef QT_LARGEFILE_SUPPORT
520 oflags |= QT_OPEN_LARGEFILE;
521#endif
522 if ((mode & QIODevice::ReadWrite) == QIODevice::ReadWrite)
523 oflags = QT_OPEN_RDWR;
524 else if (mode & QIODevice::WriteOnly)
525 oflags = QT_OPEN_WRONLY;
526 if ((mode & QIODevice::WriteOnly)
527 && !(mode & QIODevice::ExistingOnly)) // QFSFileEnginePrivate::openModeCanCreate(mode))
528 oflags |= QT_OPEN_CREAT;
529 if (mode & QIODevice::Truncate)
530 oflags |= QT_OPEN_TRUNC;
531 if (mode & QIODevice::Append)
532 oflags |= QT_OPEN_APPEND;
533 if (mode & QIODevice::NewOnly)
534 oflags |= QT_OPEN_EXCL;
535 return oflags;
536}
537
538void QRandomAccessAsyncFilePrivate::executeOpen(OperationInfo &opInfo)
539{
540 if (m_fileState != FileState::OpenPending) {
541 queueCompletion(opInfo.opId, EINVAL);
542 return;
543 }
544
545 const QByteArray nativeName = QFile::encodeName(QDir::toNativeSeparators(m_filePath));
546
547 int openFlags = openModeToOpenFlags(m_openMode);
548 openFlags |= O_NONBLOCK;
549
550 auto sharedThis = this;
551 const OperationId opId = opInfo.opId;
552
553 // We don'd call duplicateIOChannel(), so need to update the running ops
554 // explicitly.
555 m_mutex.lock();
556 m_runningOps.insert(opId);
557 m_mutex.unlock();
558
559 dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
560 ^{
561 int err = 0;
562 const int fd = ::open(nativeName.data(), openFlags);
563 if (fd < 0)
564 err = errno;
565
566 QMutexLocker locker(&sharedThis->m_mutex);
567 sharedThis->m_runningOps.remove(opId);
568 const auto cancelId = sharedThis->m_opToCancel;
569 if (cancelId == kAllOperationIds || cancelId == opId) {
570 // open() is a barrier operation, so it's always the
571 // only executing operation.
572 // Also, the main IO channel is not created yet.
573 // So we need to notify the condition variable in
574 // any both cases.
575 Q_ASSERT(sharedThis->m_runningOps.isEmpty());
576 sharedThis->m_cancellationCondition.wakeOne();
577 } else {
578 auto context = sharedThis->q_ptr;
579 const OperationResult res = { opId, qint64(fd), err };
580 QMetaObject::invokeMethod(context, [sharedThis](const OperationResult &r) {
581 sharedThis->handleOperationComplete(r);
582 }, Qt::QueuedConnection, res);
583 }
584 });
585}
586
587void QRandomAccessAsyncFilePrivate::readOneBuffer(OperationId opId, qsizetype bufferIdx,
588 qint64 alreadyRead)
589{
590 // we need to lookup the operation again, because it could have beed removed
591 // by the user...
592
593 auto it = std::find_if(m_operations.cbegin(), m_operations.cend(),
594 [opId](const auto &opInfo) {
595 return opId == opInfo.opId;
596 });
597 if (it == m_operations.cend())
598 return;
599
600 auto op = it->operation; // QPointer could be null
601 if (!op) {
602 closeIoChannel(it->channel);
603 return;
604 }
605
606 auto *priv = QIOOperationPrivate::get(op);
607 Q_ASSERT(priv->type == QIOOperation::Type::Read);
608 Q_ASSERT(priv->dataStorage->containsReadSpans());
609
610 auto &readBuffers = priv->dataStorage->getReadSpans();
611 Q_ASSERT(readBuffers.size() > bufferIdx);
612
613 qint64 newOffset = priv->offset;
614 for (qsizetype idx = 0; idx < bufferIdx; ++idx)
615 newOffset += readBuffers[idx].size();
616
617 std::byte *bytesPtr = readBuffers[bufferIdx].data();
618 qint64 maxSize = readBuffers[bufferIdx].size();
619
620 readOneBufferHelper(opId, it->channel, newOffset, bytesPtr, maxSize, bufferIdx,
621 readBuffers.size(), alreadyRead);
622}
623
624void QRandomAccessAsyncFilePrivate::readOneBufferHelper(OperationId opId, dispatch_io_t channel,
625 qint64 offset, void *bytesPtr,
626 qint64 maxSize, qsizetype currentBufferIdx,
627 qsizetype totalBuffers, qint64 alreadyRead)
628{
629 auto sharedThis = this;
630 __block size_t readFromBuffer = 0;
631 dispatch_io_read(channel, offset, maxSize,
632 dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
633 ^(bool done, dispatch_data_t data, int error) {
634 // Handle data. If there's an error, handle as much as
635 // we can.
636 if (data) {
637 dispatch_data_apply(data, ^(dispatch_data_t /*region*/, size_t offset,
638 const void *buffer, size_t size) {
639 const char *startPtr =
640 reinterpret_cast<const char *>(buffer) + offset;
641 // NOTE: This is a copy, but looks like we
642 // cannot do better :(
643 std::memcpy((std::byte *)bytesPtr + readFromBuffer,
644 startPtr, size);
645 readFromBuffer += size;
646 return true; // Keep processing if there is more data.
647 });
648 }
649
650 QMutexLocker locker(&sharedThis->m_mutex);
651 const auto cancelId = sharedThis->m_opToCancel;
652 if (cancelId == kAllOperationIds || cancelId == opId) {
653 sharedThis->m_runningOps.remove(opId);
654 if (cancelId == opId)
655 sharedThis->m_cancellationCondition.wakeOne();
656 } else if (done) {
657 sharedThis->m_runningOps.remove(opId);
658 auto context = sharedThis->q_ptr;
659 // if error, or last buffer, or read less than expected,
660 // report operation completion
661 qint64 totalRead = qint64(readFromBuffer) + alreadyRead;
662 qsizetype nextBufferIdx = currentBufferIdx + 1;
663 if (error || nextBufferIdx == totalBuffers
664 || qint64(readFromBuffer) != maxSize) {
665 const OperationResult res = { opId, totalRead, error };
666 QMetaObject::invokeMethod(context,
667 [sharedThis](const OperationResult &r) {
668 sharedThis->handleOperationComplete(r);
669 }, Qt::QueuedConnection, res);
670 } else {
671 // else execute read for the next buffer
672 QMetaObject::invokeMethod(context,
673 [sharedThis, opId, nextBufferIdx, totalRead] {
674 sharedThis->readOneBuffer(opId, nextBufferIdx, totalRead);
675 }, Qt::QueuedConnection);
676 }
677 }
678 });
679}
680
681void QRandomAccessAsyncFilePrivate::writeHelper(OperationId opId, dispatch_io_t channel,
682 qint64 offset, dispatch_data_t dataToWrite,
683 qint64 dataSize)
684{
685 auto sharedThis = this;
686 dispatch_io_write(channel, offset, dataToWrite,
687 dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0),
688 ^(bool done, dispatch_data_t data, int error) {
689 // Either an error or complete write.
690 // If there's an error, return the amount that we have
691 // written so far
692 QMutexLocker locker(&sharedThis->m_mutex);
693 const auto cancelId = sharedThis->m_opToCancel;
694 if (cancelId == kAllOperationIds || cancelId == opId) {
695 // Operation is canceled - do nothing
696 sharedThis->m_runningOps.remove(opId);
697 if (cancelId == opId)
698 sharedThis->m_cancellationCondition.wakeOne();
699 } else if (done) {
700 sharedThis->m_runningOps.remove(opId);
701 // if no error, an attempt to access the data will
702 // crash, because it seems to have no buffer
703 // allocated (as everything was written)
704 const size_t toBeWritten =
705 (error == 0) ? 0 : dispatch_data_get_size(data);
706 const size_t written = dataSize - toBeWritten;
707 [dataToWrite release];
708
709 auto context = sharedThis->q_ptr;
710 const OperationResult res = { opId, qint64(written), error };
711 QMetaObject::invokeMethod(context,
712 [sharedThis](const OperationResult &r) {
713 sharedThis->handleOperationComplete(r);
714 }, Qt::QueuedConnection, res);
715 }
716 });
717}
718
719QRandomAccessAsyncFilePrivate::OperationId QRandomAccessAsyncFilePrivate::getNextId()
720{
721 // never return reserved values
722 static OperationId opId = kInvalidOperationId;
723 if (++opId == kAllOperationIds)
724 opId = kInvalidOperationId + 1;
725 return opId;
726}
727
728QT_END_NAMESPACE
QIOOperationPrivate(QtPrivate::QIOOperationDataStorage *storage)
QIOOperation * open(const QString &path, QIODeviceBase::OpenMode mode)
QIOReadOperation * read(qint64 offset, qint64 maxSize)
QIOVectoredWriteOperation * writeFrom(qint64 offset, QSpan< const std::byte > buffer)
QIOWriteOperation * write(qint64 offset, QByteArray &&data)
QIOWriteOperation * write(qint64 offset, const QByteArray &data)
QIOVectoredReadOperation * readInto(qint64 offset, QSpan< std::byte > buffer)
Combined button and popup list for selecting options.
static bool isBarrierOperation(QIOOperation::Type type)
static int openModeToOpenFlags(QIODevice::OpenMode mode)