1 | // Copyright (C) 2018 The Qt Company Ltd. |
---|---|
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 "qqmlpreviewfileengine.h" |
5 | #include "qqmlpreviewservice.h" |
6 | |
7 | #include <QtCore/qlibraryinfo.h> |
8 | #include <QtCore/qthread.h> |
9 | #include <QtCore/qwaitcondition.h> |
10 | |
11 | #include <cstring> |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | static bool isRelative(const QString &path) |
16 | { |
17 | if (path.isEmpty()) |
18 | return true; |
19 | if (path.at(i: 0) == '/') |
20 | return false; |
21 | if (path.at(i: 0) == ':' && path.size() >= 2 && path.at(i: 1) == '/') |
22 | return false; |
23 | #ifdef Q_OS_WIN |
24 | if (path.length() >= 2 && path.at(1) == ':') |
25 | return false; |
26 | #endif |
27 | return true; |
28 | } |
29 | |
30 | static QString absolutePath(const QString &path) |
31 | { |
32 | return QDir::cleanPath(path: isRelative(path) ? (QDir::currentPath() + '/' + path) : path); |
33 | } |
34 | |
35 | bool isRootPath(const QString &path) |
36 | { |
37 | return QFileSystemEntry::isRootPath(path); |
38 | } |
39 | |
40 | class QQmlPreviewFileEngineIterator : public QAbstractFileEngineIterator |
41 | { |
42 | public: |
43 | QQmlPreviewFileEngineIterator(const QString &path, QDirListing::IteratorFlags filters, |
44 | const QStringList &filterNames, const QStringList &m_entries); |
45 | ~QQmlPreviewFileEngineIterator(); |
46 | |
47 | bool advance() override; |
48 | QString currentFileName() const override; |
49 | |
50 | private: |
51 | const QStringList m_entries; |
52 | int m_index; |
53 | }; |
54 | |
55 | QQmlPreviewFileEngineIterator::QQmlPreviewFileEngineIterator(const QString &path, |
56 | QDirListing::IteratorFlags filters, |
57 | const QStringList &filterNames, |
58 | const QStringList &entries) |
59 | : QAbstractFileEngineIterator(path, filters, filterNames), m_entries(entries), m_index(0) |
60 | { |
61 | } |
62 | |
63 | QQmlPreviewFileEngineIterator::~QQmlPreviewFileEngineIterator() |
64 | { |
65 | } |
66 | |
67 | bool QQmlPreviewFileEngineIterator::advance() |
68 | { |
69 | if (m_index >= m_entries.size()) |
70 | return false; |
71 | |
72 | ++m_index; |
73 | return true; |
74 | } |
75 | |
76 | QString QQmlPreviewFileEngineIterator::currentFileName() const |
77 | { |
78 | if (m_index == 0 || m_index > m_entries.size()) |
79 | return QString(); |
80 | return m_entries.at(i: m_index - 1); |
81 | } |
82 | |
83 | QQmlPreviewFileEngine::QQmlPreviewFileEngine(const QString &file, const QString &absolute, |
84 | QQmlPreviewFileLoader *loader) : |
85 | m_name(file), m_absolute(absolute), m_loader(loader) |
86 | { |
87 | load(); |
88 | } |
89 | |
90 | void QQmlPreviewFileEngine::setFileName(const QString &file) |
91 | { |
92 | m_name = file; |
93 | m_absolute = absolutePath(path: file); |
94 | m_fallback.reset(); |
95 | m_contents.close(); |
96 | m_contents.setData(QByteArray()); |
97 | m_entries.clear(); |
98 | load(); |
99 | } |
100 | |
101 | bool QQmlPreviewFileEngine::open(QIODevice::OpenMode flags, |
102 | std::optional<QFile::Permissions> permissions) |
103 | { |
104 | switch (m_result) { |
105 | case QQmlPreviewFileLoader::File: |
106 | return m_contents.open(openMode: flags); |
107 | case QQmlPreviewFileLoader::Directory: |
108 | return false; |
109 | case QQmlPreviewFileLoader::Fallback: |
110 | return m_fallback->open(openMode: flags, permissions); |
111 | default: |
112 | Q_UNREACHABLE_RETURN(false); |
113 | } |
114 | } |
115 | |
116 | bool QQmlPreviewFileEngine::close() |
117 | { |
118 | switch (m_result) { |
119 | case QQmlPreviewFileLoader::Fallback: |
120 | return m_fallback->close(); |
121 | case QQmlPreviewFileLoader::File: |
122 | m_contents.close(); |
123 | return true; |
124 | case QQmlPreviewFileLoader::Directory: |
125 | return false; |
126 | default: |
127 | Q_UNREACHABLE_RETURN(false); |
128 | } |
129 | } |
130 | |
131 | qint64 QQmlPreviewFileEngine::size() const |
132 | { |
133 | return m_fallback ? m_fallback->size() : m_contents.size(); |
134 | } |
135 | |
136 | qint64 QQmlPreviewFileEngine::pos() const |
137 | { |
138 | return m_fallback ? m_fallback->pos() : m_contents.pos(); |
139 | } |
140 | |
141 | bool QQmlPreviewFileEngine::seek(qint64 newPos) |
142 | { |
143 | return m_fallback? m_fallback->seek(pos: newPos) : m_contents.seek(off: newPos); |
144 | } |
145 | |
146 | qint64 QQmlPreviewFileEngine::read(char *data, qint64 maxlen) |
147 | { |
148 | return m_fallback ? m_fallback->read(data, maxlen) : m_contents.read(data, maxlen); |
149 | } |
150 | |
151 | QAbstractFileEngine::FileFlags QQmlPreviewFileEngine::fileFlags( |
152 | QAbstractFileEngine::FileFlags type) const |
153 | { |
154 | if (m_fallback) |
155 | return m_fallback->fileFlags(type); |
156 | |
157 | QAbstractFileEngine::FileFlags ret; |
158 | |
159 | if (type & PermsMask) { |
160 | ret |= QAbstractFileEngine::FileFlags( |
161 | ReadOwnerPerm | ReadUserPerm | ReadGroupPerm | ReadOtherPerm); |
162 | } |
163 | |
164 | if (type & TypesMask) { |
165 | if (m_result == QQmlPreviewFileLoader::Directory) |
166 | ret |= DirectoryType; |
167 | else |
168 | ret |= FileType; |
169 | } |
170 | |
171 | if (type & FlagsMask) { |
172 | ret |= ExistsFlag; |
173 | if (isRootPath(path: m_name)) |
174 | ret |= RootFlag; |
175 | } |
176 | |
177 | return ret; |
178 | } |
179 | |
180 | QString QQmlPreviewFileEngine::fileName(QAbstractFileEngine::FileName file) const |
181 | { |
182 | if (m_fallback) |
183 | return m_fallback->fileName(file); |
184 | |
185 | if (file == BaseName) { |
186 | int slashPos = m_name.lastIndexOf(c: '/'); |
187 | if (slashPos == -1) |
188 | return m_name; |
189 | return m_name.mid(position: slashPos + 1); |
190 | } else if (file == PathName || file == AbsolutePathName) { |
191 | const QString path = (file == AbsolutePathName) ? m_absolute : m_name; |
192 | const int slashPos = path.lastIndexOf(c: '/'); |
193 | if (slashPos == -1) |
194 | return QString(); |
195 | else if (slashPos == 0) |
196 | return "/"; |
197 | return path.left(n: slashPos); |
198 | } else if (file == CanonicalName || file == CanonicalPathName) { |
199 | if (file == CanonicalPathName) { |
200 | const int slashPos = m_absolute.lastIndexOf(c: '/'); |
201 | if (slashPos != -1) |
202 | return m_absolute.left(n: slashPos); |
203 | } |
204 | return m_absolute; |
205 | } |
206 | return m_name; |
207 | } |
208 | |
209 | uint QQmlPreviewFileEngine::ownerId(QAbstractFileEngine::FileOwner owner) const |
210 | { |
211 | return m_fallback ? m_fallback->ownerId(owner) : static_cast<uint>(-2); |
212 | } |
213 | |
214 | QAbstractFileEngine::IteratorUniquePtr QQmlPreviewFileEngine::beginEntryList( |
215 | const QString &path, QDirListing::IteratorFlags filters, const QStringList &filterNames) |
216 | { |
217 | return m_fallback ? m_fallback->beginEntryList(path, filters, filterNames) |
218 | : std::make_unique<QQmlPreviewFileEngineIterator>( |
219 | args: path, args&: filters, args: filterNames, args&: m_entries); |
220 | } |
221 | |
222 | QAbstractFileEngine::IteratorUniquePtr QQmlPreviewFileEngine::endEntryList() |
223 | { |
224 | return m_fallback ? m_fallback->endEntryList() : nullptr; |
225 | } |
226 | |
227 | bool QQmlPreviewFileEngine::flush() |
228 | { |
229 | return m_fallback ? m_fallback->flush() : true; |
230 | } |
231 | |
232 | bool QQmlPreviewFileEngine::syncToDisk() |
233 | { |
234 | return m_fallback ? m_fallback->syncToDisk() : false; |
235 | } |
236 | |
237 | bool QQmlPreviewFileEngine::isSequential() const |
238 | { |
239 | return m_fallback ? m_fallback->isSequential() : m_contents.isSequential(); |
240 | } |
241 | |
242 | bool QQmlPreviewFileEngine::remove() |
243 | { |
244 | return m_fallback ? m_fallback->remove() : false; |
245 | } |
246 | |
247 | bool QQmlPreviewFileEngine::copy(const QString &newName) |
248 | { |
249 | return m_fallback ? m_fallback->copy(newName) : false; |
250 | } |
251 | |
252 | bool QQmlPreviewFileEngine::rename(const QString &newName) |
253 | { |
254 | return m_fallback ? m_fallback->rename(newName) : false; |
255 | } |
256 | |
257 | bool QQmlPreviewFileEngine::renameOverwrite(const QString &newName) |
258 | { |
259 | return m_fallback ? m_fallback->renameOverwrite(newName) : false; |
260 | } |
261 | |
262 | bool QQmlPreviewFileEngine::link(const QString &newName) |
263 | { |
264 | return m_fallback ? m_fallback->link(newName) : false; |
265 | } |
266 | |
267 | bool QQmlPreviewFileEngine::mkdir(const QString &dirName, bool createParentDirectories, |
268 | std::optional<QFile::Permissions> permissions) const |
269 | { |
270 | return m_fallback ? m_fallback->mkdir(dirName, createParentDirectories, permissions) : false; |
271 | } |
272 | |
273 | bool QQmlPreviewFileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const |
274 | { |
275 | return m_fallback ? m_fallback->rmdir(dirName, recurseParentDirectories) : false; |
276 | } |
277 | |
278 | bool QQmlPreviewFileEngine::setSize(qint64 size) |
279 | { |
280 | switch (m_result) { |
281 | case QQmlPreviewFileLoader::Fallback: |
282 | return m_fallback->setSize(size); |
283 | case QQmlPreviewFileLoader::File: |
284 | if (size < 0 || size > std::numeric_limits<int>::max()) |
285 | return false; |
286 | m_contents.buffer().resize(size: static_cast<int>(size)); |
287 | return true; |
288 | case QQmlPreviewFileLoader::Directory: |
289 | return false; |
290 | default: |
291 | Q_UNREACHABLE_RETURN(false); |
292 | } |
293 | } |
294 | |
295 | bool QQmlPreviewFileEngine::caseSensitive() const |
296 | { |
297 | return m_fallback ? m_fallback->caseSensitive() : true; |
298 | } |
299 | |
300 | bool QQmlPreviewFileEngine::isRelativePath() const |
301 | { |
302 | return m_fallback ? m_fallback->isRelativePath() : isRelative(path: m_name); |
303 | } |
304 | |
305 | QStringList QQmlPreviewFileEngine::entryList(QDir::Filters filters, |
306 | const QStringList &filterNames) const |
307 | { |
308 | return m_fallback ? m_fallback->entryList(filters, filterNames) |
309 | : QAbstractFileEngine::entryList(filters, filterNames); |
310 | } |
311 | |
312 | bool QQmlPreviewFileEngine::setPermissions(uint perms) |
313 | { |
314 | return m_fallback ? m_fallback->setPermissions(perms) : false; |
315 | } |
316 | |
317 | QByteArray QQmlPreviewFileEngine::id() const |
318 | { |
319 | return m_fallback ? m_fallback->id() : QByteArray(); |
320 | } |
321 | |
322 | QString QQmlPreviewFileEngine::owner(FileOwner owner) const |
323 | { |
324 | return m_fallback ? m_fallback->owner(owner) : QString(); |
325 | } |
326 | |
327 | QDateTime QQmlPreviewFileEngine::fileTime(QFile::FileTime time) const |
328 | { |
329 | // Files we replace are always newer than the ones we had before. This makes the QML engine |
330 | // actually recompile them, rather than pick them from the cache. |
331 | return m_fallback ? m_fallback->fileTime(time) : QDateTime::currentDateTime(); |
332 | } |
333 | |
334 | int QQmlPreviewFileEngine::handle() const |
335 | { |
336 | return m_fallback ? m_fallback->handle() : -1; |
337 | } |
338 | |
339 | qint64 QQmlPreviewFileEngine::readLine(char *data, qint64 maxlen) |
340 | { |
341 | return m_fallback ? m_fallback->readLine(data, maxlen) : m_contents.readLine(data, maxlen); |
342 | } |
343 | |
344 | qint64 QQmlPreviewFileEngine::write(const char *data, qint64 len) |
345 | { |
346 | return m_fallback ? m_fallback->write(data, len) : m_contents.write(data, len); |
347 | } |
348 | |
349 | bool QQmlPreviewFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) |
350 | { |
351 | return m_fallback ? m_fallback->extension(extension, option, output) : false; |
352 | } |
353 | |
354 | bool QQmlPreviewFileEngine::supportsExtension(Extension extension) const |
355 | { |
356 | return m_fallback ? m_fallback->supportsExtension(extension) : false; |
357 | } |
358 | |
359 | void QQmlPreviewFileEngine::load() const |
360 | { |
361 | // We can get here from different threads on different instances of QQmlPreviewFileEngine. |
362 | // However, there is only one loader per QQmlPreviewFileEngineHandler and it is not thread-safe. |
363 | // Its content mutex doesn't help us here because we explicitly wait on it in load(), which |
364 | // causes it to be released. Therefore, lock the load mutex first. |
365 | // This doesn't cause any deadlocks because the only thread that wakes the loader on the content |
366 | // mutex never calls load(). It's the QML debug server thread that handles the debug protocol. |
367 | QMutexLocker loadLocker(m_loader->loadMutex()); |
368 | |
369 | m_result = m_loader->load(file: m_absolute); |
370 | switch (m_result) { |
371 | case QQmlPreviewFileLoader::File: |
372 | m_contents.setData(m_loader->contents()); |
373 | break; |
374 | case QQmlPreviewFileLoader::Directory: |
375 | m_entries = m_loader->entries(); |
376 | break; |
377 | case QQmlPreviewFileLoader::Fallback: |
378 | m_fallback = QAbstractFileEngine::create(fileName: m_name); |
379 | break; |
380 | case QQmlPreviewFileLoader::Unknown: |
381 | Q_UNREACHABLE(); |
382 | break; |
383 | } |
384 | } |
385 | |
386 | QQmlPreviewFileEngineHandler::QQmlPreviewFileEngineHandler(QQmlPreviewFileLoader *loader) |
387 | : m_loader(loader) |
388 | { |
389 | } |
390 | |
391 | std::unique_ptr<QAbstractFileEngine> QQmlPreviewFileEngineHandler::create( |
392 | const QString &fileName) const |
393 | { |
394 | using namespace Qt::StringLiterals; |
395 | static QList<QLatin1StringView> prohibitedSuffixes { |
396 | // Don't load compiled QML/JS over the network |
397 | ".qmlc"_L1, ".jsc"_L1, ".mjsc"_L1, |
398 | |
399 | // Don't load plugins over the network |
400 | ".dll"_L1, ".so"_L1, ".dylib"_L1 |
401 | }; |
402 | |
403 | for (QLatin1StringView suffix : prohibitedSuffixes) { |
404 | if (fileName.endsWith(s: suffix)) |
405 | return nullptr; |
406 | } |
407 | |
408 | if (isRootPath(path: fileName)) |
409 | return nullptr; |
410 | |
411 | QString relative = fileName; |
412 | while (relative.endsWith(c: '/')) |
413 | relative.chop(n: 1); |
414 | |
415 | if (relative.isEmpty() || relative == ":") |
416 | return nullptr; |
417 | |
418 | const QString absolute = relative.startsWith(c: ':') ? relative : absolutePath(path: relative); |
419 | |
420 | if (m_loader->isBlacklisted(file: absolute)) |
421 | return {}; |
422 | |
423 | return std::make_unique<QQmlPreviewFileEngine>(args&: relative, args: absolute, args: m_loader.data()); |
424 | } |
425 | |
426 | QT_END_NAMESPACE |
427 |
Definitions
- isRelative
- absolutePath
- isRootPath
- QQmlPreviewFileEngineIterator
- QQmlPreviewFileEngineIterator
- ~QQmlPreviewFileEngineIterator
- advance
- currentFileName
- QQmlPreviewFileEngine
- setFileName
- open
- close
- size
- pos
- seek
- read
- fileFlags
- fileName
- ownerId
- beginEntryList
- endEntryList
- flush
- syncToDisk
- isSequential
- remove
- copy
- rename
- renameOverwrite
- link
- mkdir
- rmdir
- setSize
- caseSensitive
- isRelativePath
- entryList
- setPermissions
- id
- owner
- fileTime
- handle
- readLine
- write
- extension
- supportsExtension
- load
- QQmlPreviewFileEngineHandler
Learn to use CMake with our Intro Training
Find out more