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

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