8#import <AssetsLibrary/AssetsLibrary.h>
10#include <QtCore/QTimer>
11#include <QtCore/private/qcoreapplication_p.h>
12#include <QtCore/qurl.h>
13#include <QtCore/qset.h>
14#include <QtCore/qthreadstorage.h>
15#include <QtCore/qfileselector.h>
16#include <QtCore/qpointer.h>
20using namespace Qt::StringLiterals;
30 if ([ALAssetsLibrary authorizationStatus] != ALAuthorizationStatusNotDetermined)
33 if (
static_cast<QCoreApplicationPrivate *>(QObjectPrivate::get(qApp))->in_exec)
36 if ([NSThread isMainThread]) {
43 QTimer::singleShot(1, &loop, &QEventLoop::quit);
46 NSLog(@
"QIOSFileEngine: unable to show assets authorization dialog from non-gui thread before QApplication is executing.");
86 dispatch_semaphore_signal(m_semWriteAsset);
87 dispatch_release(m_semReadAsset);
88 dispatch_release(m_semWriteAsset);
90 [m_assetsLibrary autorelease];
95 if (!m_nextAssetReady) {
96 dispatch_semaphore_wait(m_semReadAsset, DISPATCH_TIME_FOREVER);
97 m_nextAssetReady =
true;
99 return m_buffer[m_readIndex] != kNoAsset;
104 Q_ASSERT(m_nextAssetReady);
105 Q_ASSERT(m_buffer[m_readIndex]);
107 ALAsset *asset = [m_buffer[m_readIndex] autorelease];
108 dispatch_semaphore_signal(m_semWriteAsset);
111 m_nextAssetReady =
false;
116 dispatch_semaphore_t m_semWriteAsset;
117 dispatch_semaphore_t m_semReadAsset;
118 std::atomic_bool m_stop;
120 ALAssetsLibrary *m_assetsLibrary;
121 ALAssetsGroupType m_type;
122 QVector<ALAsset *> m_buffer;
125 bool m_nextAssetReady;
127 void writeAsset(ALAsset *asset)
129 dispatch_semaphore_wait(m_semWriteAsset, DISPATCH_TIME_FOREVER);
130 m_buffer[m_writeIndex] = [asset retain];
131 dispatch_semaphore_signal(m_semReadAsset);
135 void startEnumerate()
137 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
138 [m_assetsLibrary enumerateGroupsWithTypes:m_type usingBlock:^(ALAssetsGroup *group, BOOL *stopEnumerate) {
141 writeAsset(kNoAsset);
146 *stopEnumerate =
true;
150 [group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stopEnumerate) {
152 if (!asset || ![[asset valueForProperty:ALAssetPropertyType] isEqual:ALAssetTypePhoto])
156 *stopEnumerate = m_stop;
158 } failureBlock:^(NSError *error) {
159 NSLog(@
"QIOSFileEngine: %@", error);
160 writeAsset(kNoAsset);
180 if (
QIOSAssetData *assetData = g_assetDataCache.localData()) {
185 if (assetData->m_assetUrl == assetUrl) {
186 m_assetLibrary = [assetData->m_assetLibrary retain];
187 m_asset = [assetData->m_asset retain];
197 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
199 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
200 NSURL *url = [NSURL URLWithString:assetUrl.toNSString()];
201 m_assetLibrary = [[ALAssetsLibrary alloc] init];
202 [m_assetLibrary assetForURL:url resultBlock:^(ALAsset *asset) {
209 QIOSAssetEnumerator e(m_assetLibrary, ALAssetsGroupPhotoStream);
210 while (e.hasNext()) {
211 ALAsset *a = e.next();
212 QString url = QUrl::fromNSURL([a valueForProperty:ALAssetPropertyAssetURL]).toString();
213 if (url == assetUrl) {
221 engine->setError(QFile::OpenError,
"could not open image"_L1);
223 m_asset = [asset retain];
224 dispatch_semaphore_signal(semaphore);
225 } failureBlock:^(NSError *error) {
226 engine->setError(QFile::OpenError, QString::fromNSString(error.localizedDescription));
227 dispatch_semaphore_signal(semaphore);
231 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
232 dispatch_release(semaphore);
239 [m_assetLibrary release];
249 ALAssetsLibrary *m_assetLibrary;
254#ifndef QT_NO_FILESYSTEMITERATOR
261 QIOSFileEngineIteratorAssetsLibrary(
262 const QString &path, QDirListing::IteratorFlags filters,
const QStringList &nameFilters)
268 ~QIOSFileEngineIteratorAssetsLibrary()
274 bool advance() override
283 ALAsset *asset = m_enumerator->next();
284 QString url = QUrl::fromNSURL([asset valueForProperty:ALAssetPropertyAssetURL]).toString();
289 QString currentFileName()
const override
291 return g_iteratorCurrentUrl.localData();
294 QFileInfo currentFileInfo()
const override
296 return QFileInfo(currentFileName());
308 setFileName(fileName);
319 m_data =
new QIOSAssetData(m_assetUrl,
const_cast<QIOSFileEngineAssetsLibrary *>(
this));
320 return m_data->m_asset;
324 std::optional<QFile::Permissions> permissions)
326 Q_UNUSED(permissions);
328 if (openMode & (QIODevice::WriteOnly | QIODevice::Text))
338 m_data->deleteLater();
346 QAbstractFileEngine::FileFlags flags;
347 const bool isDir = (m_assetUrl ==
"assets-library://"_L1);
349 static const QFileSelector fileSelector;
350 static const auto selectors = fileSelector.allSelectors();
351 if (m_assetUrl.startsWith(
"assets-library://"_L1)) {
352 for (
const auto &selector : selectors) {
353 if (m_assetUrl.endsWith(selector))
359 const bool exists = isDir || m_assetUrl == g_iteratorCurrentUrl.localData() || loadAsset();
364 if (type & FlagsMask)
366 if (type & PermsMask) {
367 ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus];
368 if (status != ALAuthorizationStatusRestricted && status != ALAuthorizationStatusDenied)
369 flags |= ReadOwnerPerm | ReadUserPerm | ReadGroupPerm | ReadOtherPerm;
371 if (type & TypesMask)
372 flags |= isDir ? DirectoryType : FileType;
379 if (ALAsset *asset = loadAsset())
380 return [[asset defaultRepresentation] size];
386 ALAsset *asset = loadAsset();
390 qint64 bytesRead = qMin(maxlen, size() - m_offset);
394 NSError *error =
nullptr;
395 [[asset defaultRepresentation] getBytes:(uint8_t *)data fromOffset:m_offset length:bytesRead error:&error];
398 setError(QFile::ReadError, QString::fromNSString(error.localizedDescription));
402 m_offset += bytesRead;
433 qsizetype index = file.indexOf(
"/asset"_L1);
435 m_assetUrl =
"assets-library://"_L1;
437 m_assetUrl =
"assets-library:/"_L1 + file.mid(index);
440#ifndef QT_NO_FILESYSTEMITERATOR
444 const QString &path, QDirListing::IteratorFlags filters,
const QStringList &filterNames)
446 return std::make_unique<QIOSFileEngineIteratorAssetsLibrary>(path, filters, filterNames);
QIOSAssetData(const QString &assetUrl, QIOSFileEngineAssetsLibrary *engine)
QIOSAssetEnumerator(ALAssetsLibrary *assetsLibrary, ALAssetsGroupType type)
~QIOSFileEngineAssetsLibrary()
QString fileName(FileName file) const override
Return the file engine's current file name in the format specified by file.
qint64 pos() const override
Returns the current file position.
void setFileName(const QString &file) override
Sets the file engine's file name to file.
bool seek(qint64 pos) override
Sets the file position to the given offset.
FileFlags fileFlags(FileFlags type) const override
This function should return the set of OR'd flags that are true for the file engine's file,...
qint64 read(char *data, qint64 maxlen) override
Reads a number of characters from the file into data.
qint64 size() const override
Returns the size of the file.
QIOSFileEngineAssetsLibrary(const QString &fileName)
IteratorUniquePtr beginEntryList(const QString &path, QDirListing::IteratorFlags filters, const QStringList &filterNames) override
Returns a QAbstractFileEngine::IteratorUniquePtr, that can be used to iterate over the entries in pat...
bool close() override
Closes the file, returning true if successful; otherwise returns false.
QIOSAssetEnumerator * m_enumerator
static const int kBufferSize
static QThreadStorage< QPointer< QIOSAssetData > > g_assetDataCache
static bool ensureAuthorizationDialogNotBlocked()
static ALAsset * kNoAsset
static QThreadStorage< QString > g_iteratorCurrentUrl