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 | |
49 | QT_BEGIN_NAMESPACE |
50 | |
51 | static 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 | |
66 | static QString absolutePath(const QString &path) |
67 | { |
68 | return QDir::cleanPath(path: isRelative(path) ? (QDir::currentPath() + '/' + path) : path); |
69 | } |
70 | |
71 | bool isRootPath(const QString &path) |
72 | { |
73 | return QFileSystemEntry::isRootPath(path); |
74 | } |
75 | |
76 | class QQmlPreviewFileEngineIterator : public QAbstractFileEngineIterator |
77 | { |
78 | public: |
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 | |
87 | private: |
88 | const QStringList m_entries; |
89 | int m_index; |
90 | }; |
91 | |
92 | QQmlPreviewFileEngineIterator::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 | |
99 | QQmlPreviewFileEngineIterator::~QQmlPreviewFileEngineIterator() |
100 | { |
101 | } |
102 | |
103 | QString QQmlPreviewFileEngineIterator::next() |
104 | { |
105 | if (!hasNext()) |
106 | return QString(); |
107 | ++m_index; |
108 | return currentFilePath(); |
109 | } |
110 | |
111 | bool QQmlPreviewFileEngineIterator::hasNext() const |
112 | { |
113 | return m_index < m_entries.size(); |
114 | } |
115 | |
116 | QString 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 | |
123 | QQmlPreviewFileEngine::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 | |
130 | void 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 | |
141 | bool 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 | |
156 | bool 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 | |
172 | qint64 QQmlPreviewFileEngine::size() const |
173 | { |
174 | return m_fallback ? m_fallback->size() : m_contents.size(); |
175 | } |
176 | |
177 | qint64 QQmlPreviewFileEngine::pos() const |
178 | { |
179 | return m_fallback ? m_fallback->pos() : m_contents.pos(); |
180 | } |
181 | |
182 | bool QQmlPreviewFileEngine::seek(qint64 newPos) |
183 | { |
184 | return m_fallback? m_fallback->seek(pos: newPos) : m_contents.seek(off: newPos); |
185 | } |
186 | |
187 | qint64 QQmlPreviewFileEngine::read(char *data, qint64 maxlen) |
188 | { |
189 | return m_fallback ? m_fallback->read(data, maxlen) : m_contents.read(data, maxlen); |
190 | } |
191 | |
192 | QAbstractFileEngine::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 | |
221 | QString 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 | |
250 | uint QQmlPreviewFileEngine::ownerId(QAbstractFileEngine::FileOwner owner) const |
251 | { |
252 | return m_fallback ? m_fallback->ownerId(owner) : static_cast<uint>(-2); |
253 | } |
254 | |
255 | QAbstractFileEngine::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 | |
262 | QAbstractFileEngine::Iterator *QQmlPreviewFileEngine::endEntryList() |
263 | { |
264 | return m_fallback ? m_fallback->endEntryList() : nullptr; |
265 | } |
266 | |
267 | bool QQmlPreviewFileEngine::flush() |
268 | { |
269 | return m_fallback ? m_fallback->flush() : true; |
270 | } |
271 | |
272 | bool QQmlPreviewFileEngine::syncToDisk() |
273 | { |
274 | return m_fallback ? m_fallback->syncToDisk() : false; |
275 | } |
276 | |
277 | bool QQmlPreviewFileEngine::isSequential() const |
278 | { |
279 | return m_fallback ? m_fallback->isSequential() : m_contents.isSequential(); |
280 | } |
281 | |
282 | bool QQmlPreviewFileEngine::remove() |
283 | { |
284 | return m_fallback ? m_fallback->remove() : false; |
285 | } |
286 | |
287 | bool QQmlPreviewFileEngine::copy(const QString &newName) |
288 | { |
289 | return m_fallback ? m_fallback->copy(newName) : false; |
290 | } |
291 | |
292 | bool QQmlPreviewFileEngine::rename(const QString &newName) |
293 | { |
294 | return m_fallback ? m_fallback->rename(newName) : false; |
295 | } |
296 | |
297 | bool QQmlPreviewFileEngine::renameOverwrite(const QString &newName) |
298 | { |
299 | return m_fallback ? m_fallback->renameOverwrite(newName) : false; |
300 | } |
301 | |
302 | bool QQmlPreviewFileEngine::link(const QString &newName) |
303 | { |
304 | return m_fallback ? m_fallback->link(newName) : false; |
305 | } |
306 | |
307 | bool QQmlPreviewFileEngine::mkdir(const QString &dirName, bool createParentDirectories) const |
308 | { |
309 | return m_fallback ? m_fallback->mkdir(dirName, createParentDirectories) : false; |
310 | } |
311 | |
312 | bool QQmlPreviewFileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const |
313 | { |
314 | return m_fallback ? m_fallback->rmdir(dirName, recurseParentDirectories) : false; |
315 | } |
316 | |
317 | bool 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 | |
335 | bool QQmlPreviewFileEngine::caseSensitive() const |
336 | { |
337 | return m_fallback ? m_fallback->caseSensitive() : true; |
338 | } |
339 | |
340 | bool QQmlPreviewFileEngine::isRelativePath() const |
341 | { |
342 | return m_fallback ? m_fallback->isRelativePath() : isRelative(path: m_name); |
343 | } |
344 | |
345 | QStringList 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 | |
352 | bool QQmlPreviewFileEngine::setPermissions(uint perms) |
353 | { |
354 | return m_fallback ? m_fallback->setPermissions(perms) : false; |
355 | } |
356 | |
357 | QByteArray QQmlPreviewFileEngine::id() const |
358 | { |
359 | return m_fallback ? m_fallback->id() : QByteArray(); |
360 | } |
361 | |
362 | QString QQmlPreviewFileEngine::owner(FileOwner owner) const |
363 | { |
364 | return m_fallback ? m_fallback->owner(owner) : QString(); |
365 | } |
366 | |
367 | QDateTime 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 | |
374 | int QQmlPreviewFileEngine::handle() const |
375 | { |
376 | return m_fallback ? m_fallback->handle() : -1; |
377 | } |
378 | |
379 | qint64 QQmlPreviewFileEngine::readLine(char *data, qint64 maxlen) |
380 | { |
381 | return m_fallback ? m_fallback->readLine(data, maxlen) : m_contents.readLine(data, maxlen); |
382 | } |
383 | |
384 | qint64 QQmlPreviewFileEngine::write(const char *data, qint64 len) |
385 | { |
386 | return m_fallback ? m_fallback->write(data, len) : m_contents.write(data, len); |
387 | } |
388 | |
389 | bool QQmlPreviewFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) |
390 | { |
391 | return m_fallback ? m_fallback->extension(extension, option, output) : false; |
392 | } |
393 | |
394 | bool QQmlPreviewFileEngine::supportsExtension(Extension extension) const |
395 | { |
396 | return m_fallback ? m_fallback->supportsExtension(extension) : false; |
397 | } |
398 | |
399 | void 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 | |
426 | QQmlPreviewFileEngineHandler::QQmlPreviewFileEngineHandler(QQmlPreviewFileLoader *loader) |
427 | : m_loader(loader) |
428 | { |
429 | } |
430 | |
431 | QAbstractFileEngine *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 | |
451 | QT_END_NAMESPACE |
452 | |