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