Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qandroidassetsfileenginehandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "androidjnimain.h"
6
7#include <optional>
8
9#include <QCoreApplication>
10#include <QList>
11#include <QtCore/QJniEnvironment>
12#include <QtCore/QJniObject>
13
15
16using namespace Qt::StringLiterals;
17
18static const auto assetsPrefix = "assets:"_L1;
19const static int prefixSize = 7;
20
22{
23 if (file.startsWith(assetsPrefix))
25 file.replace("//"_L1, "/"_L1);
26 if (file.startsWith(u'/'))
27 file.remove(0, 1);
28 if (file.endsWith(u'/'))
29 file.chop(1);
30 return file;
31}
32
34{
35 path = assetsPrefix + u'/' + path;
36 path.replace("//"_L1, "/"_L1);
37 return path;
38}
39
40struct AssetItem {
41 enum class Type {
42 File,
43 Folder,
45 };
46 AssetItem() = default;
47 AssetItem (const QString &rawName)
48 : name(rawName)
49 {
50 if (name.endsWith(u'/')) {
51 type = Type::Folder;
52 name.chop(1);
53 }
54 }
55 Type type = Type::File;
58};
59
61
63{
64public:
66 {
67 QMutexLocker lock(&m_assetsCacheMutex);
68 QSharedPointer<FolderIterator> *folder = m_assetsCache.object(path);
69 if (!folder) {
71 if ((*folder)->empty() || !m_assetsCache.insert(path, folder)) {
73 delete folder;
74 return res;
75 }
76 }
77 return clone ? QSharedPointer<FolderIterator>{new FolderIterator{*(*folder)}} : *folder;
78 }
79
80 static AssetItem::Type fileType(const QString &filePath)
81 {
82 if (filePath.isEmpty())
83 return AssetItem::Type::Folder;
84 const QStringList paths = filePath.split(u'/');
85 QString fullPath;
86 AssetItem::Type res = AssetItem::Type::Invalid;
87 for (const auto &path: paths) {
88 auto folder = fromCache(fullPath, false);
89 auto it = std::lower_bound(folder->begin(), folder->end(), AssetItem{path}, [](const AssetItem &val, const AssetItem &assetItem) {
90 return val.name < assetItem.name;
91 });
92 if (it == folder->end() || it->name != path)
93 return AssetItem::Type::Invalid;
94 if (!fullPath.isEmpty())
95 fullPath.append(u'/');
96 fullPath += path;
97 res = it->type;
98 }
99 return res;
100 }
101
104 , m_index(-1)
105 , m_path(other.m_path)
106 {}
107
109 : m_path(path)
110 {
111 // Note that empty dirs in the assets dir before the build are not going to be
112 // included in the final apk, so no empty folders should expected to be listed.
113 QJniObject files = QJniObject::callStaticObjectMethod(QtAndroid::applicationClass(),
114 "listAssetContent",
115 "(Landroid/content/res/AssetManager;Ljava/lang/String;)[Ljava/lang/String;",
116 QtAndroid::assets(), QJniObject::fromString(path).object());
117 if (files.isValid()) {
118 QJniEnvironment env;
119 jobjectArray jFiles = files.object<jobjectArray>();
120 const jint nFiles = env->GetArrayLength(jFiles);
121 for (int i = 0; i < nFiles; ++i) {
122 AssetItem item{QJniObject::fromLocalRef(env->GetObjectArrayElement(jFiles, i)).toString()};
123 insert(std::upper_bound(begin(), end(), item, [](const auto &a, const auto &b){
124 return a.name < b.name;
125 }), item);
126 }
127 }
128 m_path = assetsPrefix + u'/' + m_path + u'/';
129 m_path.replace("//"_L1, "/"_L1);
130 }
131
133 {
134 if (m_index < 0 || m_index >= size())
135 return {};
136 return at(m_index).name;
137 }
139 {
140 if (m_index < 0 || m_index >= size())
141 return {};
142 return m_path + at(m_index).name;
143 }
144
145 bool hasNext() const
146 {
147 return !empty() && m_index + 1 < size();
148 }
149
150 std::optional<std::pair<QString, AssetItem>> next()
151 {
152 if (!hasNext())
153 return {};
154 ++m_index;
155 return std::pair<QString, AssetItem>(currentFileName(), at(m_index));
156 }
157
158private:
159 int m_index = -1;
160 QString m_path;
162 static QMutex m_assetsCacheMutex;
163};
164
165QCache<QString, QSharedPointer<FolderIterator>> FolderIterator::m_assetsCache(std::max(50, qEnvironmentVariableIntValue("QT_ANDROID_MAX_ASSETS_CACHE_SIZE")));
166Q_CONSTINIT QMutex FolderIterator::m_assetsCacheMutex;
167
169{
170public:
173 const QString &path)
175 {
176 m_currentIterator = FolderIterator::fromCache(cleanedAssetPath(path), true);
177 }
178
179 QFileInfo currentFileInfo() const override
180 {
181 return QFileInfo(currentFilePath());
182 }
183
184 QString currentFileName() const override
185 {
186 if (!m_currentIterator)
187 return {};
188 return m_currentIterator->currentFileName();
189 }
190
191 QString currentFilePath() const override
192 {
193 if (!m_currentIterator)
194 return {};
195 return m_currentIterator->currentFilePath();
196 }
197
198 bool hasNext() const override
199 {
200 if (!m_currentIterator)
201 return false;
202 return m_currentIterator->hasNext();
203 }
204
205 QString next() override
206 {
207 if (!m_currentIterator)
208 return {};
209 auto res = m_currentIterator->next();
210 if (!res)
211 return {};
212 return res->first;
213 }
214
215private:
216 QSharedPointer<FolderIterator> m_currentIterator;
217};
218
220{
221public:
222 explicit AndroidAbstractFileEngine(AAssetManager *assetManager, const QString &fileName)
223 : m_assetManager(assetManager)
224 {
226 }
227
229 {
230 close();
231 }
232
233 bool open(QIODevice::OpenMode openMode, std::optional<QFile::Permissions> permissions) override
234 {
235 Q_UNUSED(permissions);
236
237 if (!m_assetInfo || m_assetInfo->type != AssetItem::Type::File || (openMode & QIODevice::WriteOnly))
238 return false;
239 close();
240 m_assetFile = AAssetManager_open(m_assetManager, m_fileName.toUtf8(), AASSET_MODE_BUFFER);
241 return m_assetFile;
242 }
243
244 bool close() override
245 {
246 if (m_assetFile) {
247 AAsset_close(m_assetFile);
248 m_assetFile = 0;
249 return true;
250 }
251 return false;
252 }
253
254 qint64 size() const override
255 {
256 if (m_assetInfo)
257 return m_assetInfo->size;
258 return -1;
259 }
260
261 qint64 pos() const override
262 {
263 if (m_assetFile)
264 return AAsset_seek(m_assetFile, 0, SEEK_CUR);
265 return -1;
266 }
267
268 bool seek(qint64 pos) override
269 {
270 if (m_assetFile)
271 return pos == AAsset_seek(m_assetFile, pos, SEEK_SET);
272 return false;
273 }
274
275 qint64 read(char *data, qint64 maxlen) override
276 {
277 if (m_assetFile)
278 return AAsset_read(m_assetFile, data, maxlen);
279 return -1;
280 }
281
282 bool caseSensitive() const override
283 {
284 return true;
285 }
286
287 FileFlags fileFlags(FileFlags type = FileInfoAll) const override
288 {
290 FileFlags flags;
291 if (m_assetInfo) {
292 if (m_assetInfo->type == AssetItem::Type::File)
293 flags = FileType | commonFlags;
294 else if (m_assetInfo->type == AssetItem::Type::Folder)
295 flags = DirectoryType | commonFlags;
296 }
297 return type & flags;
298 }
299
301 {
303 switch (file) {
304 case DefaultName:
305 case AbsoluteName:
306 case CanonicalName:
307 return prefixedPath(m_fileName);
308 case BaseName:
309 if ((pos = m_fileName.lastIndexOf(u'/')) != -1)
310 return m_fileName.mid(pos + 1);
311 else
312 return m_fileName;
313 case PathName:
314 case AbsolutePathName:
316 if ((pos = m_fileName.lastIndexOf(u'/')) != -1)
317 return prefixedPath(m_fileName.left(pos));
318 else
319 return prefixedPath(m_fileName);
320 default:
321 return QString();
322 }
323 }
324
325 void setFileName(const QString &file) override
326 {
327 if (m_fileName == cleanedAssetPath(file))
328 return;
329 close();
330 m_fileName = cleanedAssetPath(file);
331
332 {
333 QMutexLocker lock(&m_assetsInfoCacheMutex);
334 QSharedPointer<AssetItem> *assetInfoPtr = m_assetsInfoCache.object(m_fileName);
335 if (assetInfoPtr) {
336 m_assetInfo = *assetInfoPtr;
337 return;
338 }
339 }
340
342
343 m_assetInfo = *newAssetInfoPtr;
344 m_assetInfo->name = m_fileName;
345 m_assetInfo->type = AssetItem::Type::Invalid;
346
347 m_assetFile = AAssetManager_open(m_assetManager, m_fileName.toUtf8(), AASSET_MODE_BUFFER);
348
349 if (m_assetFile) {
350 m_assetInfo->type = AssetItem::Type::File;
351 m_assetInfo->size = AAsset_getLength(m_assetFile);
352 } else {
353 auto *assetDir = AAssetManager_openDir(m_assetManager, m_fileName.toUtf8());
354 if (assetDir) {
355 if (AAssetDir_getNextFileName(assetDir)
356 || (!FolderIterator::fromCache(m_fileName, false)->empty())) {
357 // If AAssetDir_getNextFileName is not valid, it still can be a directory that
358 // contains only other directories (no files). FolderIterator will not be called
359 // on the directory containing files so it should not be too time consuming now.
360 m_assetInfo->type = AssetItem::Type::Folder;
361 }
362 AAssetDir_close(assetDir);
363 }
364 }
365
366 QMutexLocker lock(&m_assetsInfoCacheMutex);
367 m_assetsInfoCache.insert(m_fileName, newAssetInfoPtr);
368 }
369
370 Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override
371 {
372 if (m_assetInfo && m_assetInfo->type == AssetItem::Type::Folder)
373 return new AndroidAbstractFileEngineIterator(filters, filterNames, m_fileName);
374 return nullptr;
375 }
376
377private:
378 AAsset *m_assetFile = nullptr;
379 AAssetManager *m_assetManager = nullptr;
380 // initialize with a name that can't be used as a file name
381 QString m_fileName = "."_L1;
382 QSharedPointer<AssetItem> m_assetInfo;
383
384 static QCache<QString, QSharedPointer<AssetItem>> m_assetsInfoCache;
385 static QMutex m_assetsInfoCacheMutex;
386};
387
388QCache<QString, QSharedPointer<AssetItem>> AndroidAbstractFileEngine::m_assetsInfoCache(std::max(200, qEnvironmentVariableIntValue("QT_ANDROID_MAX_FILEINFO_ASSETS_CACHE_SIZE")));
389Q_CONSTINIT QMutex AndroidAbstractFileEngine::m_assetsInfoCacheMutex;
390
392{
393 m_assetManager = QtAndroid::assetManager();
394}
395
397{
398 if (fileName.isEmpty())
399 return nullptr;
400
401 if (!fileName.startsWith(assetsPrefix))
402 return nullptr;
403
405 path.replace("//"_L1, "/"_L1);
406 if (path.startsWith(u'/'))
407 path.remove(0, 1);
408 if (path.endsWith(u'/'))
409 path.chop(1);
410 return new AndroidAbstractFileEngine(m_assetManager, path);
411}
412
QString next() override
This pure virtual function advances the iterator to the next directory entry, and returns the file pa...
AndroidAbstractFileEngineIterator(QDir::Filters filters, const QStringList &nameFilters, const QString &path)
QString currentFileName() const override
This pure virtual function returns the name of the current directory entry, excluding the path.
QString currentFilePath() const override
Returns the path to the current directory entry.
QFileInfo currentFileInfo() const override
The virtual function returns a QFileInfo for the current directory entry.
bool hasNext() const override
This pure virtual function returns true if there is at least one more entry in the current directory ...
void setFileName(const QString &file) override
Sets the file engine's file name to file.
FileFlags fileFlags(FileFlags type=FileInfoAll) const override
This function should return the set of OR'd flags that are true for the file engine's file,...
qint64 size() const override
Returns the size of the file.
qint64 pos() const override
Returns the current file position.
bool caseSensitive() const override
Should return true if the underlying file system is case-sensitive; otherwise return false.
bool seek(qint64 pos) override
Sets the file position to the given offset.
qint64 read(char *data, qint64 maxlen) override
Reads a number of characters from the file into data.
bool close() override
Closes the file, returning true if successful; otherwise returns false.
bool open(QIODevice::OpenMode openMode, std::optional< QFile::Permissions > permissions) override
Opens the file in the specified mode.
QString fileName(FileName file=DefaultName) const override
Return the file engine's current file name in the format specified by file.
Iterator * beginEntryList(QDir::Filters filters, const QStringList &filterNames) override
Returns an instance of a QAbstractFileEngineIterator using filters for entry filtering and filterName...
AndroidAbstractFileEngine(AAssetManager *assetManager, const QString &fileName)
QAbstractFileEngine * create(const QString &fileName) const override
Creates a file engine for file fileName.
static QSharedPointer< FolderIterator > fromCache(const QString &path, bool clone)
std::optional< std::pair< QString, AssetItem > > next()
FolderIterator(const QString &path)
static AssetItem::Type fileType(const QString &filePath)
FolderIterator(const FolderIterator &other)
The QAbstractFileEngineIterator class provides an iterator interface for custom file engines.
QDir::Filters filters() const
Returns the entry filters for this iterator.
QStringList nameFilters() const
Returns the name filters for this iterator.
\inmodule QtCore \reentrant
FileName
These values are used to request a file name in a particular format.
T * object(const Key &key) const noexcept
Definition qcache.h:209
bool insert(const Key &key, T *object, qsizetype cost=1)
Definition qcache.h:184
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
bool remove()
Removes the file specified by fileName().
Definition qfile.cpp:419
\inmodule QtCore
\inmodule QtCore
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
iterator insert(qsizetype i, parameter_type t)
Definition qlist.h:471
bool empty() const noexcept
Definition qlist.h:682
iterator end()
Definition qlist.h:609
iterator begin()
Definition qlist.h:608
\inmodule QtCore
Definition qmutex.h:317
\inmodule QtCore
Definition qmutex.h:285
iterator end()
Definition qset.h:140
\inmodule QtCore
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
qsizetype lastIndexOf(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
Definition qstring.h:279
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3794
QStringList split(const QString &sep, Qt::SplitBehavior behavior=Qt::KeepEmptyParts, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Splits the string into substrings wherever sep occurs, and returns the list of those strings.
Definition qstring.cpp:7956
QString mid(qsizetype position, qsizetype n=-1) const
Returns a string that contains n characters of this string, starting at the specified position index.
Definition qstring.cpp:5204
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
QString & append(QChar c)
Definition qstring.cpp:3227
QString left(qsizetype n) const
Returns a substring that contains the n leftmost characters of the string.
Definition qstring.cpp:5161
QByteArray toUtf8() const &
Definition qstring.h:563
QSet< QString >::iterator it
Combined button and popup list for selecting options.
jobject assets()
jclass applicationClass()
AAssetManager * assetManager()
static QString prefixedPath(QString path)
static const int prefixSize
static const auto assetsPrefix
static QString cleanedAssetPath(QString file)
GLboolean GLboolean GLboolean b
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum type
GLsizei const GLuint * paths
GLbitfield flags
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint name
GLuint res
GLuint GLfloat * val
GLsizei const GLchar *const * path
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:70
long long qint64
Definition qtypes.h:55
QFile file
[0]
QReadWriteLock lock
[0]
QSharedPointer< T > other(t)
[5]
QStringList files
[8]
const QStringList filters({"Image files (*.png *.xpm *.jpg)", "Text files (*.txt)", "Any files (*)" })
[6]
QGraphicsItem * item
QAction * at
AssetItem()=default
AssetItem(const QString &rawName)
Definition moc.h:24