summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/ios/qiosfileengineassetslibrary.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/ios/qiosfileengineassetslibrary.mm')
-rw-r--r--src/plugins/platforms/ios/qiosfileengineassetslibrary.mm320
1 files changed, 261 insertions, 59 deletions
diff --git a/src/plugins/platforms/ios/qiosfileengineassetslibrary.mm b/src/plugins/platforms/ios/qiosfileengineassetslibrary.mm
index 73bfc2a87f..44a7901160 100644
--- a/src/plugins/platforms/ios/qiosfileengineassetslibrary.mm
+++ b/src/plugins/platforms/ios/qiosfileengineassetslibrary.mm
@@ -38,6 +38,145 @@
#include <QtCore/QTimer>
#include <QtCore/private/qcoreapplication_p.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qset.h>
+#include <QtCore/qthreadstorage.h>
+
+static QThreadStorage<QString> g_iteratorCurrentUrl;
+static QThreadStorage<QPointer<QIOSAssetData> > g_assetDataCache;
+
+static const int kBufferSize = 10;
+static ALAsset *kNoAsset = 0;
+
+static void ensureAuthorizationDialogNotBlocked()
+{
+ if ([ALAssetsLibrary authorizationStatus] != ALAuthorizationStatusNotDetermined)
+ return;
+ if (static_cast<QCoreApplicationPrivate *>(QObjectPrivate::get(qApp))->in_exec)
+ return;
+
+ // Since authorization status has not been determined, the user will be asked
+ // to authorize the app. But since main has not finished, the dialog will be held
+ // back until the launch completes. To avoid a dead-lock below, we start an event
+ // loop to complete the launch.
+ QEventLoop loop;
+ QTimer::singleShot(1, &loop, &QEventLoop::quit);
+ loop.exec();
+}
+
+// -------------------------------------------------------------------------
+
+class QIOSAssetEnumerator
+{
+public:
+ QIOSAssetEnumerator(ALAssetsLibrary *assetsLibrary, ALAssetsGroupType type)
+ : m_semWriteAsset(dispatch_semaphore_create(kBufferSize))
+ , m_semReadAsset(dispatch_semaphore_create(0))
+ , m_stop(false)
+ , m_assetsLibrary([assetsLibrary retain])
+ , m_type(type)
+ , m_buffer(QVector<ALAsset *>(kBufferSize))
+ , m_readIndex(0)
+ , m_writeIndex(0)
+ , m_nextAssetReady(false)
+ {
+ ensureAuthorizationDialogNotBlocked();
+ startEnumerate();
+ }
+
+ ~QIOSAssetEnumerator()
+ {
+ m_stop = true;
+
+ // Flush and autorelease remaining assets in the buffer
+ while (hasNext())
+ next();
+
+ // Documentation states that we need to balance out calls to 'wait'
+ // and 'signal'. Since the enumeration function always will be one 'wait'
+ // ahead, we need to signal m_semProceedToNextAsset one last time.
+ dispatch_semaphore_signal(m_semWriteAsset);
+ dispatch_release(m_semReadAsset);
+ dispatch_release(m_semWriteAsset);
+
+ [m_assetsLibrary autorelease];
+ }
+
+ bool hasNext()
+ {
+ if (!m_nextAssetReady) {
+ dispatch_semaphore_wait(m_semReadAsset, DISPATCH_TIME_FOREVER);
+ m_nextAssetReady = true;
+ }
+ return m_buffer[m_readIndex] != kNoAsset;
+ }
+
+ ALAsset *next()
+ {
+ Q_ASSERT(m_nextAssetReady);
+ Q_ASSERT(m_buffer[m_readIndex]);
+
+ ALAsset *asset = [m_buffer[m_readIndex] autorelease];
+ dispatch_semaphore_signal(m_semWriteAsset);
+
+ m_readIndex = (m_readIndex + 1) % kBufferSize;
+ m_nextAssetReady = false;
+ return asset;
+ }
+
+private:
+ dispatch_semaphore_t m_semWriteAsset;
+ dispatch_semaphore_t m_semReadAsset;
+ std::atomic_bool m_stop;
+
+ ALAssetsLibrary *m_assetsLibrary;
+ ALAssetsGroupType m_type;
+ QVector<ALAsset *> m_buffer;
+ int m_readIndex;
+ int m_writeIndex;
+ bool m_nextAssetReady;
+
+ void writeAsset(ALAsset *asset)
+ {
+ dispatch_semaphore_wait(m_semWriteAsset, DISPATCH_TIME_FOREVER);
+ m_buffer[m_writeIndex] = [asset retain];
+ dispatch_semaphore_signal(m_semReadAsset);
+ m_writeIndex = (m_writeIndex + 1) % kBufferSize;
+ }
+
+ void startEnumerate()
+ {
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ [m_assetsLibrary enumerateGroupsWithTypes:m_type usingBlock:^(ALAssetsGroup *group, BOOL *stopEnumerate) {
+
+ if (!group) {
+ writeAsset(kNoAsset);
+ return;
+ }
+
+ if (m_stop) {
+ *stopEnumerate = true;
+ return;
+ }
+
+ [group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stopEnumerate) {
+ Q_UNUSED(index);
+ if (!asset || ![[asset valueForProperty:ALAssetPropertyType] isEqual:ALAssetTypePhoto])
+ return;
+
+ writeAsset(asset);
+ *stopEnumerate = m_stop;
+ }];
+ } failureBlock:^(NSError *error) {
+ NSLog(@"QIOSFileEngine: %@", error);
+ writeAsset(kNoAsset);
+ }];
+ });
+ }
+
+};
+
+// -------------------------------------------------------------------------
class QIOSAssetData : public QObject
{
@@ -47,35 +186,17 @@ public:
, m_assetUrl(assetUrl)
, m_assetLibrary(0)
{
- switch ([ALAssetsLibrary authorizationStatus]) {
- case ALAuthorizationStatusRestricted:
- case ALAuthorizationStatusDenied:
- engine->setError(QFile::PermissionsError, QLatin1String("Unauthorized access"));
- return;
- case ALAuthorizationStatusNotDetermined:
- if (!static_cast<QCoreApplicationPrivate *>(QObjectPrivate::get(qApp))->in_exec) {
- // Since authorization status has not been determined, the user will be asked
- // to authorize the app. But since main has not finished, the dialog will be held
- // back until the launch completes. To avoid a dead-lock below, we start an event
- // loop to complete the launch.
- QEventLoop loop;
- QTimer::singleShot(1, &loop, &QEventLoop::quit);
- loop.exec();
- }
- break;
- default:
- if (g_currentAssetData) {
- // It's a common pattern that QFiles pointing to the same path are created and destroyed
- // several times during a single event loop cycle. To avoid loading the same asset
- // over and over, we check if the last loaded asset has not been destroyed yet, and try to
- // reuse its data. Since QFile is (mostly) reentrant, we need to protect m_currentAssetData
- // from being modified by several threads at the same time.
- QMutexLocker lock(&g_mutex);
- if (g_currentAssetData && g_currentAssetData->m_assetUrl == assetUrl) {
- m_assetLibrary = [g_currentAssetData->m_assetLibrary retain];
- m_asset = [g_currentAssetData->m_asset retain];
- return;
- }
+ ensureAuthorizationDialogNotBlocked();
+
+ if (QIOSAssetData *assetData = g_assetDataCache.localData()) {
+ // It's a common pattern that QFiles pointing to the same path are created and destroyed
+ // several times during a single event loop cycle. To avoid loading the same asset
+ // over and over, we check if the last loaded asset has not been destroyed yet, and try to
+ // reuse its data.
+ if (assetData->m_assetUrl == assetUrl) {
+ m_assetLibrary = [assetData->m_assetLibrary retain];
+ m_asset = [assetData->m_asset retain];
+ return;
}
}
@@ -90,6 +211,26 @@ public:
NSURL *url = [NSURL URLWithString:assetUrl.toNSString()];
m_assetLibrary = [[ALAssetsLibrary alloc] init];
[m_assetLibrary assetForURL:url resultBlock:^(ALAsset *asset) {
+
+ if (!asset) {
+ // When an asset couldn't be loaded, chances are that it belongs to ALAssetsGroupPhotoStream.
+ // Such assets can be stored in the cloud and might need to be downloaded first. Unfortunately,
+ // forcing that to happen is hidden behind private APIs ([ALAsset requestDefaultRepresentation]).
+ // As a work-around, we search for it instead, since that will give us a pointer to the asset.
+ QIOSAssetEnumerator e(m_assetLibrary, ALAssetsGroupPhotoStream);
+ while (e.hasNext()) {
+ ALAsset *a = e.next();
+ QString url = QUrl::fromNSURL([a valueForProperty:ALAssetPropertyAssetURL]).toString();
+ if (url == assetUrl) {
+ asset = a;
+ break;
+ }
+ }
+ }
+
+ if (!asset)
+ engine->setError(QFile::OpenError, QLatin1String("could not open image"));
+
m_asset = [asset retain];
dispatch_semaphore_signal(semaphore);
} failureBlock:^(NSError *error) {
@@ -101,17 +242,15 @@ public:
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
- QMutexLocker lock(&g_mutex);
- g_currentAssetData = this;
+ g_assetDataCache.setLocalData(this);
}
~QIOSAssetData()
{
- QMutexLocker lock(&g_mutex);
[m_assetLibrary release];
[m_asset release];
- if (this == g_currentAssetData)
- g_currentAssetData = 0;
+ if (g_assetDataCache.localData() == this)
+ g_assetDataCache.setLocalData(0);
}
ALAsset *m_asset;
@@ -119,21 +258,67 @@ public:
private:
QString m_assetUrl;
ALAssetsLibrary *m_assetLibrary;
+};
+
+// -------------------------------------------------------------------------
- static QBasicMutex g_mutex;
- static QPointer<QIOSAssetData> g_currentAssetData;
+#ifndef QT_NO_FILESYSTEMITERATOR
+
+class QIOSFileEngineIteratorAssetsLibrary : public QAbstractFileEngineIterator
+{
+public:
+ QIOSAssetEnumerator *m_enumerator;
+
+ QIOSFileEngineIteratorAssetsLibrary(
+ QDir::Filters filters, const QStringList &nameFilters)
+ : QAbstractFileEngineIterator(filters, nameFilters)
+ , m_enumerator(new QIOSAssetEnumerator([[[ALAssetsLibrary alloc] init] autorelease], ALAssetsGroupAll))
+ {
+ }
+
+ ~QIOSFileEngineIteratorAssetsLibrary()
+ {
+ delete m_enumerator;
+ g_iteratorCurrentUrl.setLocalData(QString());
+ }
+
+ QString next() Q_DECL_OVERRIDE
+ {
+ // Cache the URL that we are about to return, since QDir will immediately create a
+ // new file engine on the file and ask if it exists. Unless we do this, we end up
+ // creating a new ALAsset just to verify its existence, which will be especially
+ // costly for assets belonging to ALAssetsGroupPhotoStream.
+ ALAsset *asset = m_enumerator->next();
+ QString url = QUrl::fromNSURL([asset valueForProperty:ALAssetPropertyAssetURL]).toString();
+ g_iteratorCurrentUrl.setLocalData(url);
+ return url;
+ }
+
+ bool hasNext() const Q_DECL_OVERRIDE
+ {
+ return m_enumerator->hasNext();
+ }
+
+ QString currentFileName() const Q_DECL_OVERRIDE
+ {
+ return g_iteratorCurrentUrl.localData();
+ }
+
+ QFileInfo currentFileInfo() const
+ {
+ return QFileInfo(currentFileName());
+ }
};
-QBasicMutex QIOSAssetData::g_mutex;
-QPointer<QIOSAssetData> QIOSAssetData::g_currentAssetData = 0;
+#endif
// -------------------------------------------------------------------------
QIOSFileEngineAssetsLibrary::QIOSFileEngineAssetsLibrary(const QString &fileName)
- : m_fileName(fileName)
- , m_offset(0)
+ : m_offset(0)
, m_data(0)
{
+ setFileName(fileName);
}
QIOSFileEngineAssetsLibrary::~QIOSFileEngineAssetsLibrary()
@@ -143,18 +328,8 @@ QIOSFileEngineAssetsLibrary::~QIOSFileEngineAssetsLibrary()
ALAsset *QIOSFileEngineAssetsLibrary::loadAsset() const
{
- if (!m_data) {
- // QUrl::fromLocalFile() will remove double slashes. Since the asset url is passed around as a file
- // name in the app (and converted to/from a file url, e.g in QFileDialog), we need to check if we still
- // have two leading slashes after the scheme, and restore the second slash if not.
- QString assetUrl = m_fileName;
- const int index = 16; // "assets-library://"
- if (assetUrl[index] != QLatin1Char('/'))
- assetUrl.insert(index, '/');
-
- m_data = new QIOSAssetData(assetUrl, const_cast<QIOSFileEngineAssetsLibrary *>(this));
- }
-
+ if (!m_data)
+ m_data = new QIOSAssetData(m_assetUrl, const_cast<QIOSFileEngineAssetsLibrary *>(this));
return m_data->m_asset;
}
@@ -179,15 +354,21 @@ bool QIOSFileEngineAssetsLibrary::close()
QAbstractFileEngine::FileFlags QIOSFileEngineAssetsLibrary::fileFlags(QAbstractFileEngine::FileFlags type) const
{
QAbstractFileEngine::FileFlags flags = 0;
- if (!loadAsset())
+ const bool isDir = (m_assetUrl == QLatin1String("assets-library://"));
+ const bool exists = isDir || m_assetUrl == g_iteratorCurrentUrl.localData() || loadAsset();
+
+ if (!exists)
return flags;
if (type & FlagsMask)
flags |= ExistsFlag;
- if (type & PermsMask)
- flags |= ReadOwnerPerm | ReadUserPerm | ReadGroupPerm | ReadOtherPerm;
+ if (type & PermsMask) {
+ ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus];
+ if (status != ALAuthorizationStatusRestricted && status != ALAuthorizationStatusDenied)
+ flags |= ReadOwnerPerm | ReadUserPerm | ReadGroupPerm | ReadOtherPerm;
+ }
if (type & TypesMask)
- flags |= FileType;
+ flags |= isDir ? DirectoryType : FileType;
return flags;
}
@@ -245,11 +426,32 @@ void QIOSFileEngineAssetsLibrary::setFileName(const QString &file)
if (m_data)
close();
m_fileName = file;
+ // QUrl::fromLocalFile() will remove double slashes. Since the asset url is
+ // passed around as a file name in the app (and converted to/from a file url, e.g
+ // in QFileDialog), we need to ensure that m_assetUrl ends up being valid.
+ int index = file.indexOf(QLatin1String("asset.JPG?"));
+ if (index == -1)
+ m_assetUrl = QLatin1String("assets-library://");
+ else
+ m_assetUrl = QLatin1String("assets-library://asset/") + file.mid(index);
}
QStringList QIOSFileEngineAssetsLibrary::entryList(QDir::Filters filters, const QStringList &filterNames) const
{
- Q_UNUSED(filters);
- Q_UNUSED(filterNames);
- return QStringList();
+ return QAbstractFileEngine::entryList(filters, filterNames);
+}
+
+#ifndef QT_NO_FILESYSTEMITERATOR
+
+QAbstractFileEngine::Iterator *QIOSFileEngineAssetsLibrary::beginEntryList(
+ QDir::Filters filters, const QStringList &filterNames)
+{
+ return new QIOSFileEngineIteratorAssetsLibrary(filters, filterNames);
}
+
+QAbstractFileEngine::Iterator *QIOSFileEngineAssetsLibrary::endEntryList()
+{
+ return 0;
+}
+
+#endif