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
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// Qt-Security score:critical reason:file-handling
4
7
8#include <optional>
9
10#include <QCoreApplication>
11#include <QList>
12#include <QtCore/QJniEnvironment>
13#include <QtCore/QJniObject>
14
15QT_BEGIN_NAMESPACE
16
17using namespace Qt::StringLiterals;
18
19static const auto assetsPrefix = "assets:"_L1;
20const static int prefixSize = 7;
21
22static inline QString cleanedAssetPath(QString file)
23{
24 if (file.startsWith(assetsPrefix))
25 file.remove(0, prefixSize);
26 file.replace("//"_L1, "/"_L1);
27 if (file.startsWith(u'/'))
28 file.remove(0, 1);
29 if (file.endsWith(u'/'))
30 file.chop(1);
31 return file;
32}
33
34static inline QString prefixedPath(QString path)
35{
36 path = assetsPrefix + u'/' + path;
37 path.replace("//"_L1, "/"_L1);
38 return path;
39}
40
41struct AssetItem {
42 enum class Type {
46 };
47 AssetItem() = default;
48 AssetItem (const QString &rawName)
49 : name(rawName)
50 {
51 if (name.endsWith(u'/')) {
53 name.chop(1);
54 }
55 }
57 QString name;
59};
60
61using AssetItemList = QList<AssetItem>;
62
64{
65public:
66 static QSharedPointer<FolderIterator> fromCache(const QString &path, bool clone)
67 {
68 QMutexLocker lock(&m_assetsCacheMutex);
69 QSharedPointer<FolderIterator> *folder = m_assetsCache.object(path);
70 if (!folder) {
71 folder = new QSharedPointer<FolderIterator>{new FolderIterator{path}};
72 if ((*folder)->empty() || !m_assetsCache.insert(path, folder)) {
73 QSharedPointer<FolderIterator> res = *folder;
74 delete folder;
75 return res;
76 }
77 }
78 return clone ? QSharedPointer<FolderIterator>{new FolderIterator{*(*folder)}} : *folder;
79 }
80
81 static AssetItem::Type fileType(const QString &filePath)
82 {
83 if (filePath.isEmpty())
85 const QStringList paths = filePath.split(u'/');
86 QString fullPath;
88 for (const auto &path: paths) {
89 auto folder = fromCache(fullPath, false);
90 auto it = std::lower_bound(folder->begin(), folder->end(), AssetItem{path}, [](const AssetItem &val, const AssetItem &assetItem) {
91 return val.name < assetItem.name;
92 });
93 if (it == folder->end() || it->name != path)
94 return AssetItem::Type::Invalid;
95 if (!fullPath.isEmpty())
96 fullPath.append(u'/');
97 fullPath += path;
98 res = it->type;
99 }
100 return res;
101 }
102
105 , m_index(-1)
107 {}
108
109 FolderIterator(const QString &path)
110 : m_path(path)
111 {
112 // Note that empty dirs in the assets dir before the build are not going to be
113 // included in the final apk, so no empty folders should expected to be listed.
114 QJniObject files = QJniObject::callStaticObjectMethod(QtAndroid::applicationClass(),
115 "listAssetContent",
116 "(Landroid/content/res/AssetManager;Ljava/lang/String;)[Ljava/lang/String;",
117 QtAndroid::assets(), QJniObject::fromString(path).object());
118 if (files.isValid()) {
119 QJniEnvironment env;
120 jobjectArray jFiles = files.object<jobjectArray>();
121 const jint nFiles = env->GetArrayLength(jFiles);
122 for (int i = 0; i < nFiles; ++i) {
123 AssetItem item{QJniObject::fromLocalRef(env->GetObjectArrayElement(jFiles, i)).toString()};
124 insert(std::upper_bound(begin(), end(), item, [](const auto &a, const auto &b){
125 return a.name < b.name;
126 }), item);
127 }
128 }
129 m_path = assetsPrefix + u'/' + m_path + u'/';
130 m_path.replace("//"_L1, "/"_L1);
131 }
132
133 QString currentFileName() const
134 {
135 if (m_index < 0 || m_index >= size())
136 return {};
137 return at(m_index).name;
138 }
139 QString currentFilePath() const
140 {
141 if (m_index < 0 || m_index >= size())
142 return {};
143 return m_path + at(m_index).name;
144 }
145
146 bool advance()
147 {
148 if (!empty() && m_index + 1 < size()) {
149 ++m_index;
150 return true;
151 }
152 return false;
153 }
154
155private:
156 int m_index = -1;
157 QString m_path;
158 static QCache<QString, QSharedPointer<FolderIterator>> m_assetsCache;
159 static QMutex m_assetsCacheMutex;
160};
161
162QCache<QString, QSharedPointer<FolderIterator>> FolderIterator::m_assetsCache(std::max(50, qEnvironmentVariableIntValue("QT_ANDROID_MAX_ASSETS_CACHE_SIZE")));
163Q_CONSTINIT QMutex FolderIterator::m_assetsCacheMutex;
164
166{
167public:
168 AndroidAbstractFileEngineIterator(QDirListing::IteratorFlags filters,
169 const QStringList &nameFilters,
170 const QString &path)
172 {
173 m_currentIterator = FolderIterator::fromCache(cleanedAssetPath(path), true);
174 }
175
176 QFileInfo currentFileInfo() const override
177 {
178 return QFileInfo(currentFilePath());
179 }
180
181 QString currentFileName() const override
182 {
183 if (!m_currentIterator)
184 return {};
185 return m_currentIterator->currentFileName();
186 }
187
188 QString currentFilePath() const override
189 {
190 if (!m_currentIterator)
191 return {};
192 return m_currentIterator->currentFilePath();
193 }
194
196 {
197 return m_currentIterator ? m_currentIterator->advance() : false;
198 }
199
200private:
201 QSharedPointer<FolderIterator> m_currentIterator;
202};
203
205{
206public:
207 explicit AndroidAbstractFileEngine(AAssetManager *assetManager, const QString &fileName)
209 {
210 setFileName(fileName);
211 }
212
217
218 bool open(QIODevice::OpenMode openMode, std::optional<QFile::Permissions> permissions) override
219 {
220 Q_UNUSED(permissions);
221
222 if (!m_assetInfo || m_assetInfo->type != AssetItem::Type::File || (openMode & QIODevice::WriteOnly))
223 return false;
224 close();
225 m_assetFile = AAssetManager_open(m_assetManager, m_fileName.toUtf8(), AASSET_MODE_BUFFER);
226 return m_assetFile;
227 }
228
230 {
231 if (m_assetFile) {
232 AAsset_close(m_assetFile);
233 m_assetFile = 0;
234 return true;
235 }
236 return false;
237 }
238
239 qint64 size() const override
240 {
241 if (m_assetInfo)
242 return m_assetInfo->size;
243 return -1;
244 }
245
246 qint64 pos() const override
247 {
248 if (m_assetFile)
249 return AAsset_seek(m_assetFile, 0, SEEK_CUR);
250 return -1;
251 }
252
253 bool seek(qint64 pos) override
254 {
255 if (m_assetFile)
256 return pos == AAsset_seek(m_assetFile, pos, SEEK_SET);
257 return false;
258 }
259
260 qint64 read(char *data, qint64 maxlen) override
261 {
262 if (m_assetFile)
263 return AAsset_read(m_assetFile, data, maxlen);
264 return -1;
265 }
266
268 {
269 return true;
270 }
271
272 FileFlags fileFlags(FileFlags type = FileInfoAll) const override
273 {
274 FileFlags commonFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm|ExistsFlag);
275 FileFlags flags;
276 if (m_assetInfo) {
277 if (m_assetInfo->type == AssetItem::Type::File)
278 flags = FileType | commonFlags;
279 else if (m_assetInfo->type == AssetItem::Type::Folder)
280 flags = DirectoryType | commonFlags;
281 }
282 return type & flags;
283 }
284
285 QString fileName(FileName file = DefaultName) const override
286 {
287 qsizetype pos;
288 switch (file) {
289 case DefaultName:
290 case AbsoluteName:
291 case CanonicalName:
292 return prefixedPath(m_fileName);
293 case BaseName:
294 if ((pos = m_fileName.lastIndexOf(u'/')) != -1)
295 return m_fileName.mid(pos + 1);
296 else
297 return m_fileName;
298 case PathName:
299 case AbsolutePathName:
300 case CanonicalPathName:
301 if ((pos = m_fileName.lastIndexOf(u'/')) != -1)
302 return prefixedPath(m_fileName.left(pos));
303 else
304 return prefixedPath(m_fileName);
305 default:
306 return QString();
307 }
308 }
309
310 void setFileName(const QString &file) override
311 {
312 if (m_fileName == cleanedAssetPath(file))
313 return;
314 close();
315 m_fileName = cleanedAssetPath(file);
316
317 {
318 QMutexLocker lock(&m_assetsInfoCacheMutex);
319 QSharedPointer<AssetItem> *assetInfoPtr = m_assetsInfoCache.object(m_fileName);
320 if (assetInfoPtr) {
321 m_assetInfo = *assetInfoPtr;
322 return;
323 }
324 }
325
326 QSharedPointer<AssetItem> *newAssetInfoPtr = new QSharedPointer<AssetItem>(new AssetItem);
327
328 m_assetInfo = *newAssetInfoPtr;
329 m_assetInfo->name = m_fileName;
330 m_assetInfo->type = AssetItem::Type::Invalid;
331
332 m_assetFile = AAssetManager_open(m_assetManager, m_fileName.toUtf8(), AASSET_MODE_BUFFER);
333
334 if (m_assetFile) {
335 m_assetInfo->type = AssetItem::Type::File;
336 m_assetInfo->size = AAsset_getLength(m_assetFile);
337 } else {
338 auto *assetDir = AAssetManager_openDir(m_assetManager, m_fileName.toUtf8());
339 if (assetDir) {
340 if (AAssetDir_getNextFileName(assetDir)
341 || (!FolderIterator::fromCache(m_fileName, false)->empty())) {
342 // If AAssetDir_getNextFileName is not valid, it still can be a directory that
343 // contains only other directories (no files). FolderIterator will not be called
344 // on the directory containing files so it should not be too time consuming now.
345 m_assetInfo->type = AssetItem::Type::Folder;
346 }
347 AAssetDir_close(assetDir);
348 }
349 }
350
351 QMutexLocker lock(&m_assetsInfoCacheMutex);
352 m_assetsInfoCache.insert(m_fileName, newAssetInfoPtr);
353 }
354
355 IteratorUniquePtr beginEntryList(const QString &, QDirListing::IteratorFlags filters,
356 const QStringList &filterNames) override
357 {
358 // AndroidAbstractFileEngineIterator use `m_fileName` as the path
359 if (m_assetInfo && m_assetInfo->type == AssetItem::Type::Folder)
360 return std::make_unique<AndroidAbstractFileEngineIterator>(filters, filterNames, m_fileName);
361 return nullptr;
362 }
363
364private:
365 AAsset *m_assetFile = nullptr;
366 AAssetManager *m_assetManager = nullptr;
367 // initialize with a name that can't be used as a file name
368 QString m_fileName = "."_L1;
369 QSharedPointer<AssetItem> m_assetInfo;
370
371 static QCache<QString, QSharedPointer<AssetItem>> m_assetsInfoCache;
372 static QMutex m_assetsInfoCacheMutex;
373};
374
375QCache<QString, QSharedPointer<AssetItem>> AndroidAbstractFileEngine::m_assetsInfoCache(std::max(200, qEnvironmentVariableIntValue("QT_ANDROID_MAX_FILEINFO_ASSETS_CACHE_SIZE")));
376Q_CONSTINIT QMutex AndroidAbstractFileEngine::m_assetsInfoCacheMutex;
377
378AndroidAssetsFileEngineHandler::AndroidAssetsFileEngineHandler()
379{
380 m_assetManager = QtAndroid::assetManager();
381}
382
384AndroidAssetsFileEngineHandler::create(const QString &fileName) const
385{
386 if (fileName.isEmpty())
387 return {};
388
389 if (!fileName.startsWith(assetsPrefix))
390 return {};
391
392 QString path = fileName.mid(prefixSize);
393 path.replace("//"_L1, "/"_L1);
394 if (path.startsWith(u'/'))
395 path.remove(0, 1);
396 if (path.endsWith(u'/'))
397 path.chop(1);
398 return std::make_unique<AndroidAbstractFileEngine>(m_assetManager, path);
399}
400
401QT_END_NAMESPACE
bool advance() override
This pure virtual function advances the iterator to the next directory entry; if the operation was su...
AndroidAbstractFileEngineIterator(QDirListing::IteratorFlags 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.
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.
IteratorUniquePtr beginEntryList(const QString &, QDirListing::IteratorFlags filters, const QStringList &filterNames) override
Returns a QAbstractFileEngine::IteratorUniquePtr, that can be used to iterate over the entries in pat...
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.
AndroidAbstractFileEngine(AAssetManager *assetManager, const QString &fileName)
std::unique_ptr< QAbstractFileEngine > create(const QString &fileName) const override
If this file handler can handle fileName, this method creates a file engine and returns it wrapped in...
static QSharedPointer< FolderIterator > fromCache(const QString &path, bool clone)
FolderIterator(const QString &path)
static AssetItem::Type fileType(const QString &filePath)
FolderIterator(const FolderIterator &other)
static QString prefixedPath(QString path)
static const auto assetsPrefix
static const int prefixSize
static QString cleanedAssetPath(QString file)
AssetItem()=default
AssetItem(const QString &rawName)