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

source code of qtdeclarative/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp