1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQml module of the Qt Toolkit. |
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 <private/qqmltypeloader_p.h> |
41 | |
42 | #include <private/qqmldirdata_p.h> |
43 | #include <private/qqmlprofiler_p.h> |
44 | #include <private/qqmlscriptblob_p.h> |
45 | #include <private/qqmltypedata_p.h> |
46 | #include <private/qqmltypeloaderqmldircontent_p.h> |
47 | #include <private/qqmltypeloaderthread_p.h> |
48 | #include <private/qqmlsourcecoordinate_p.h> |
49 | |
50 | #include <QtQml/qqmlabstracturlinterceptor.h> |
51 | #include <QtQml/qqmlengine.h> |
52 | #include <QtQml/qqmlextensioninterface.h> |
53 | #include <QtQml/qqmlfile.h> |
54 | |
55 | #include <qtqml_tracepoints_p.h> |
56 | |
57 | #include <QtCore/qdir.h> |
58 | #include <QtCore/qdiriterator.h> |
59 | #include <QtCore/qfile.h> |
60 | #include <QtCore/qthread.h> |
61 | |
62 | #include <functional> |
63 | |
64 | // #define DATABLOB_DEBUG |
65 | #ifdef DATABLOB_DEBUG |
66 | #define ASSERT_LOADTHREAD() do { if (!m_thread->isThisThread()) qFatal("QQmlTypeLoader: Caller not in load thread"); } while (false) |
67 | #else |
68 | #define ASSERT_LOADTHREAD() |
69 | #endif |
70 | |
71 | DEFINE_BOOL_CONFIG_OPTION(disableDiskCache, QML_DISABLE_DISK_CACHE); |
72 | DEFINE_BOOL_CONFIG_OPTION(forceDiskCache, QML_FORCE_DISK_CACHE); |
73 | |
74 | QT_BEGIN_NAMESPACE |
75 | |
76 | namespace { |
77 | |
78 | template<typename LockType> |
79 | struct LockHolder |
80 | { |
81 | LockType& lock; |
82 | LockHolder(LockType *l) : lock(*l) { lock.lock(); } |
83 | ~LockHolder() { lock.unlock(); } |
84 | }; |
85 | } |
86 | |
87 | /*! |
88 | \class QQmlTypeLoader |
89 | \brief The QQmlTypeLoader class abstracts loading files and their dependencies over the network. |
90 | \internal |
91 | |
92 | The QQmlTypeLoader class is provided for the exclusive use of the QQmlTypeLoader class. |
93 | |
94 | Clients create QQmlDataBlob instances and submit them to the QQmlTypeLoader class |
95 | through the QQmlTypeLoader::load() or QQmlTypeLoader::loadWithStaticData() methods. |
96 | The loader then fetches the data over the network or from the local file system in an efficient way. |
97 | QQmlDataBlob is an abstract class, so should always be specialized. |
98 | |
99 | Once data is received, the QQmlDataBlob::dataReceived() method is invoked on the blob. The |
100 | derived class should use this callback to process the received data. Processing of the data can |
101 | result in an error being set (QQmlDataBlob::setError()), or one or more dependencies being |
102 | created (QQmlDataBlob::addDependency()). Dependencies are other QQmlDataBlob's that |
103 | are required before processing can fully complete. |
104 | |
105 | To complete processing, the QQmlDataBlob::done() callback is invoked. done() is called when |
106 | one of these three preconditions are met. |
107 | |
108 | \list 1 |
109 | \li The QQmlDataBlob has no dependencies. |
110 | \li The QQmlDataBlob has an error set. |
111 | \li All the QQmlDataBlob's dependencies are themselves "done()". |
112 | \endlist |
113 | |
114 | Thus QQmlDataBlob::done() will always eventually be called, even if the blob has an error set. |
115 | */ |
116 | |
117 | void QQmlTypeLoader::invalidate() |
118 | { |
119 | if (m_thread) { |
120 | shutdownThread(); |
121 | delete m_thread; |
122 | m_thread = nullptr; |
123 | } |
124 | |
125 | #if QT_CONFIG(qml_network) |
126 | // Need to delete the network replies after |
127 | // the loader thread is shutdown as it could be |
128 | // getting new replies while we clear them |
129 | for (NetworkReplies::Iterator iter = m_networkReplies.begin(); iter != m_networkReplies.end(); ++iter) |
130 | (*iter)->release(); |
131 | m_networkReplies.clear(); |
132 | #endif // qml_network |
133 | } |
134 | |
135 | #if QT_CONFIG(qml_debug) |
136 | void QQmlTypeLoader::setProfiler(QQmlProfiler *profiler) |
137 | { |
138 | Q_ASSERT(!m_profiler); |
139 | m_profiler.reset(other: profiler); |
140 | } |
141 | #endif |
142 | |
143 | struct PlainLoader { |
144 | void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const |
145 | { |
146 | loader->loadThread(blob); |
147 | } |
148 | void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const |
149 | { |
150 | loader->m_thread->load(b: blob); |
151 | } |
152 | void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const |
153 | { |
154 | loader->m_thread->loadAsync(b: blob); |
155 | } |
156 | }; |
157 | |
158 | struct StaticLoader { |
159 | const QByteArray &data; |
160 | StaticLoader(const QByteArray &data) : data(data) {} |
161 | |
162 | void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const |
163 | { |
164 | loader->loadWithStaticDataThread(blob, data); |
165 | } |
166 | void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const |
167 | { |
168 | loader->m_thread->loadWithStaticData(b: blob, data); |
169 | } |
170 | void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const |
171 | { |
172 | loader->m_thread->loadWithStaticDataAsync(b: blob, data); |
173 | } |
174 | }; |
175 | |
176 | struct CachedLoader { |
177 | const QV4::CompiledData::Unit *unit; |
178 | CachedLoader(const QV4::CompiledData::Unit *unit) : unit(unit) {} |
179 | |
180 | void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const |
181 | { |
182 | loader->loadWithCachedUnitThread(blob, unit); |
183 | } |
184 | void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const |
185 | { |
186 | loader->m_thread->loadWithCachedUnit(b: blob, unit); |
187 | } |
188 | void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const |
189 | { |
190 | loader->m_thread->loadWithCachedUnitAsync(b: blob, unit); |
191 | } |
192 | }; |
193 | |
194 | template<typename Loader> |
195 | void QQmlTypeLoader::doLoad(const Loader &loader, QQmlDataBlob *blob, Mode mode) |
196 | { |
197 | #ifdef DATABLOB_DEBUG |
198 | qWarning("QQmlTypeLoader::doLoad(%s): %s thread" , qPrintable(blob->urlString()), |
199 | m_thread->isThisThread()?"Compile" :"Engine" ); |
200 | #endif |
201 | blob->startLoading(); |
202 | |
203 | if (m_thread->isThisThread()) { |
204 | unlock(); |
205 | loader.loadThread(this, blob); |
206 | lock(); |
207 | } else if (mode == Asynchronous) { |
208 | blob->m_data.setIsAsync(true); |
209 | unlock(); |
210 | loader.loadAsync(this, blob); |
211 | lock(); |
212 | } else { |
213 | unlock(); |
214 | loader.load(this, blob); |
215 | lock(); |
216 | if (mode == PreferSynchronous) { |
217 | if (!blob->isCompleteOrError()) |
218 | blob->m_data.setIsAsync(true); |
219 | } else { |
220 | Q_ASSERT(mode == Synchronous); |
221 | while (!blob->isCompleteOrError()) { |
222 | unlock(); |
223 | m_thread->waitForNextMessage(); |
224 | lock(); |
225 | } |
226 | } |
227 | } |
228 | } |
229 | |
230 | /*! |
231 | Load the provided \a blob from the network or filesystem. |
232 | |
233 | The loader must be locked. |
234 | */ |
235 | void QQmlTypeLoader::load(QQmlDataBlob *blob, Mode mode) |
236 | { |
237 | doLoad(loader: PlainLoader(), blob, mode); |
238 | } |
239 | |
240 | /*! |
241 | Load the provided \a blob with \a data. The blob's URL is not used by the data loader in this case. |
242 | |
243 | The loader must be locked. |
244 | */ |
245 | void QQmlTypeLoader::loadWithStaticData(QQmlDataBlob *blob, const QByteArray &data, Mode mode) |
246 | { |
247 | doLoad(loader: StaticLoader(data), blob, mode); |
248 | } |
249 | |
250 | void QQmlTypeLoader::loadWithCachedUnit(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit, Mode mode) |
251 | { |
252 | doLoad(loader: CachedLoader(unit), blob, mode); |
253 | } |
254 | |
255 | void QQmlTypeLoader::loadWithStaticDataThread(QQmlDataBlob *blob, const QByteArray &data) |
256 | { |
257 | ASSERT_LOADTHREAD(); |
258 | |
259 | setData(blob, data); |
260 | } |
261 | |
262 | void QQmlTypeLoader::loadWithCachedUnitThread(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit) |
263 | { |
264 | ASSERT_LOADTHREAD(); |
265 | |
266 | setCachedUnit(blob, unit); |
267 | } |
268 | |
269 | void QQmlTypeLoader::loadThread(QQmlDataBlob *blob) |
270 | { |
271 | ASSERT_LOADTHREAD(); |
272 | |
273 | // Don't continue loading if we've been shutdown |
274 | if (m_thread->isShutdown()) { |
275 | QQmlError error; |
276 | error.setDescription(QLatin1String("Interrupted by shutdown" )); |
277 | blob->setError(error); |
278 | return; |
279 | } |
280 | |
281 | if (blob->m_url.isEmpty()) { |
282 | QQmlError error; |
283 | error.setDescription(QLatin1String("Invalid null URL" )); |
284 | blob->setError(error); |
285 | return; |
286 | } |
287 | |
288 | if (QQmlFile::isSynchronous(url: blob->m_url)) { |
289 | const QString fileName = QQmlFile::urlToLocalFileOrQrc(blob->m_url); |
290 | if (!QQml_isFileCaseCorrect(fileName)) { |
291 | blob->setError(QLatin1String("File name case mismatch" )); |
292 | return; |
293 | } |
294 | |
295 | blob->m_data.setProgress(0xFF); |
296 | if (blob->m_data.isAsync()) |
297 | m_thread->callDownloadProgressChanged(b: blob, p: 1.); |
298 | |
299 | setData(blob, fileName); |
300 | |
301 | } else { |
302 | #if QT_CONFIG(qml_network) |
303 | QNetworkReply *reply = m_thread->networkAccessManager()->get(request: QNetworkRequest(blob->m_url)); |
304 | QQmlTypeLoaderNetworkReplyProxy *nrp = m_thread->networkReplyProxy(); |
305 | blob->addref(); |
306 | m_networkReplies.insert(akey: reply, avalue: blob); |
307 | |
308 | if (reply->isFinished()) { |
309 | nrp->manualFinished(reply); |
310 | } else { |
311 | QObject::connect(sender: reply, SIGNAL(downloadProgress(qint64,qint64)), |
312 | receiver: nrp, SLOT(downloadProgress(qint64,qint64))); |
313 | QObject::connect(sender: reply, SIGNAL(finished()), |
314 | receiver: nrp, SLOT(finished())); |
315 | } |
316 | |
317 | #ifdef DATABLOB_DEBUG |
318 | qWarning("QQmlDataBlob: requested %s" , qPrintable(blob->urlString())); |
319 | #endif // DATABLOB_DEBUG |
320 | #endif // qml_network |
321 | } |
322 | } |
323 | |
324 | #define DATALOADER_MAXIMUM_REDIRECT_RECURSION 16 |
325 | |
326 | #ifndef TYPELOADER_MINIMUM_TRIM_THRESHOLD |
327 | #define TYPELOADER_MINIMUM_TRIM_THRESHOLD 64 |
328 | #endif |
329 | |
330 | #if QT_CONFIG(qml_network) |
331 | void QQmlTypeLoader::networkReplyFinished(QNetworkReply *reply) |
332 | { |
333 | Q_ASSERT(m_thread->isThisThread()); |
334 | |
335 | reply->deleteLater(); |
336 | |
337 | QQmlDataBlob *blob = m_networkReplies.take(akey: reply); |
338 | |
339 | Q_ASSERT(blob); |
340 | |
341 | blob->m_redirectCount++; |
342 | |
343 | if (blob->m_redirectCount < DATALOADER_MAXIMUM_REDIRECT_RECURSION) { |
344 | QVariant redirect = reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute); |
345 | if (redirect.isValid()) { |
346 | QUrl url = reply->url().resolved(relative: redirect.toUrl()); |
347 | blob->m_finalUrl = url; |
348 | blob->m_finalUrlString.clear(); |
349 | |
350 | QNetworkReply *reply = m_thread->networkAccessManager()->get(request: QNetworkRequest(url)); |
351 | QObject *nrp = m_thread->networkReplyProxy(); |
352 | QObject::connect(sender: reply, SIGNAL(finished()), receiver: nrp, SLOT(finished())); |
353 | m_networkReplies.insert(akey: reply, avalue: blob); |
354 | #ifdef DATABLOB_DEBUG |
355 | qWarning("QQmlDataBlob: redirected to %s" , qPrintable(blob->finalUrlString())); |
356 | #endif |
357 | return; |
358 | } |
359 | } |
360 | |
361 | if (reply->error()) { |
362 | blob->networkError(reply->error()); |
363 | } else { |
364 | QByteArray data = reply->readAll(); |
365 | setData(blob, data); |
366 | } |
367 | |
368 | blob->release(); |
369 | } |
370 | |
371 | void QQmlTypeLoader::networkReplyProgress(QNetworkReply *reply, |
372 | qint64 bytesReceived, qint64 bytesTotal) |
373 | { |
374 | Q_ASSERT(m_thread->isThisThread()); |
375 | |
376 | QQmlDataBlob *blob = m_networkReplies.value(akey: reply); |
377 | |
378 | Q_ASSERT(blob); |
379 | |
380 | if (bytesTotal != 0) { |
381 | quint8 progress = 0xFF * (qreal(bytesReceived) / qreal(bytesTotal)); |
382 | blob->m_data.setProgress(progress); |
383 | if (blob->m_data.isAsync()) |
384 | m_thread->callDownloadProgressChanged(b: blob, p: blob->m_data.progress()); |
385 | } |
386 | } |
387 | #endif // qml_network |
388 | |
389 | /*! |
390 | Return the QQmlEngine associated with this loader |
391 | */ |
392 | QQmlEngine *QQmlTypeLoader::engine() const |
393 | { |
394 | return m_engine; |
395 | } |
396 | |
397 | /*! \internal |
398 | Call the initializeEngine() method on \a iface. Used by QQmlImportDatabase to ensure it |
399 | gets called in the correct thread. |
400 | */ |
401 | template<class Interface> |
402 | void doInitializeEngine(Interface *iface, QQmlTypeLoaderThread *thread, QQmlEngine *engine, |
403 | const char *uri) |
404 | { |
405 | Q_ASSERT(thread->isThisThread() || engine->thread() == QThread::currentThread()); |
406 | |
407 | if (thread->isThisThread()) { |
408 | thread->initializeEngine(iface, uri); |
409 | } else { |
410 | Q_ASSERT(engine->thread() == QThread::currentThread()); |
411 | iface->initializeEngine(engine, uri); |
412 | } |
413 | } |
414 | |
415 | void QQmlTypeLoader::initializeEngine(QQmlEngineExtensionInterface *iface, const char *uri) |
416 | { |
417 | doInitializeEngine(iface, thread: m_thread, engine: engine(), uri); |
418 | } |
419 | |
420 | void QQmlTypeLoader::initializeEngine(QQmlExtensionInterface *iface, const char *uri) |
421 | { |
422 | doInitializeEngine(iface, thread: m_thread, engine: engine(), uri); |
423 | } |
424 | |
425 | void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QByteArray &data) |
426 | { |
427 | QQmlDataBlob::SourceCodeData d; |
428 | d.inlineSourceCode = QString::fromUtf8(str: data); |
429 | d.hasInlineSourceCode = true; |
430 | setData(blob, d); |
431 | } |
432 | |
433 | void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QString &fileName) |
434 | { |
435 | QQmlDataBlob::SourceCodeData d; |
436 | d.fileInfo = QFileInfo(fileName); |
437 | setData(blob, d); |
438 | } |
439 | |
440 | void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QQmlDataBlob::SourceCodeData &d) |
441 | { |
442 | Q_TRACE_SCOPE(QQmlCompiling, blob->url()); |
443 | QQmlCompilingProfiler prof(profiler(), blob); |
444 | |
445 | blob->m_inCallback = true; |
446 | |
447 | blob->dataReceived(d); |
448 | |
449 | if (!blob->isError() && !blob->isWaiting()) |
450 | blob->allDependenciesDone(); |
451 | |
452 | if (blob->status() != QQmlDataBlob::Error) |
453 | blob->m_data.setStatus(QQmlDataBlob::WaitingForDependencies); |
454 | |
455 | blob->m_inCallback = false; |
456 | |
457 | blob->tryDone(); |
458 | } |
459 | |
460 | void QQmlTypeLoader::setCachedUnit(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit) |
461 | { |
462 | Q_TRACE_SCOPE(QQmlCompiling, blob->url()); |
463 | QQmlCompilingProfiler prof(profiler(), blob); |
464 | |
465 | blob->m_inCallback = true; |
466 | |
467 | blob->initializeFromCachedUnit(unit); |
468 | |
469 | if (!blob->isError() && !blob->isWaiting()) |
470 | blob->allDependenciesDone(); |
471 | |
472 | if (blob->status() != QQmlDataBlob::Error) |
473 | blob->m_data.setStatus(QQmlDataBlob::WaitingForDependencies); |
474 | |
475 | blob->m_inCallback = false; |
476 | |
477 | blob->tryDone(); |
478 | } |
479 | |
480 | void QQmlTypeLoader::shutdownThread() |
481 | { |
482 | if (m_thread && !m_thread->isShutdown()) |
483 | m_thread->shutdown(); |
484 | } |
485 | |
486 | QQmlTypeLoader::Blob::PendingImport::PendingImport(QQmlTypeLoader::Blob *blob, const QV4::CompiledData::Import *import) |
487 | { |
488 | type = static_cast<QV4::CompiledData::Import::ImportType>(quint32(import->type)); |
489 | uri = blob->stringAt(import->uriIndex); |
490 | qualifier = blob->stringAt(import->qualifierIndex); |
491 | majorVersion = import->majorVersion; |
492 | minorVersion = import->minorVersion; |
493 | location = import->location; |
494 | } |
495 | |
496 | QQmlTypeLoader::Blob::Blob(const QUrl &url, QQmlDataBlob::Type type, QQmlTypeLoader *loader) |
497 | : QQmlDataBlob(url, type, loader), m_importCache(loader) |
498 | { |
499 | } |
500 | |
501 | QQmlTypeLoader::Blob::~Blob() |
502 | { |
503 | } |
504 | |
505 | bool QQmlTypeLoader::Blob::fetchQmldir(const QUrl &url, PendingImportPtr import, int priority, QList<QQmlError> *errors) |
506 | { |
507 | QQmlRefPointer<QQmlQmldirData> data = typeLoader()->getQmldir(url); |
508 | |
509 | data->setImport(this, std::move(import)); |
510 | data->setPriority(this, priority); |
511 | |
512 | if (data->status() == Error) { |
513 | // This qmldir must not exist - which is not an error |
514 | return true; |
515 | } else if (data->status() == Complete) { |
516 | // This data is already available |
517 | return qmldirDataAvailable(data, errors); |
518 | } |
519 | |
520 | // Wait for this data to become available |
521 | addDependency(data.data()); |
522 | return true; |
523 | } |
524 | |
525 | bool QQmlTypeLoader::Blob::updateQmldir(const QQmlRefPointer<QQmlQmldirData> &data, PendingImportPtr import, QList<QQmlError> *errors) |
526 | { |
527 | QString qmldirIdentifier = data->urlString(); |
528 | QString qmldirUrl = qmldirIdentifier.left(n: qmldirIdentifier.lastIndexOf(c: QLatin1Char('/')) + 1); |
529 | |
530 | typeLoader()->setQmldirContent(filePath: qmldirIdentifier, content: data->content()); |
531 | |
532 | if (!m_importCache.updateQmldirContent(importDb: typeLoader()->importDatabase(), uri: import->uri, prefix: import->qualifier, qmldirIdentifier, qmldirUrl, errors)) |
533 | return false; |
534 | |
535 | if (!loadImportDependencies(currentImport: import, qmldirUri: qmldirIdentifier, errors)) |
536 | return false; |
537 | |
538 | import->priority = data->priority(this); |
539 | |
540 | // Release this reference at destruction |
541 | m_qmldirs << data; |
542 | |
543 | if (!import->qualifier.isEmpty()) { |
544 | // Does this library contain any qualified scripts? |
545 | QUrl libraryUrl(qmldirUrl); |
546 | const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(filePath: qmldirIdentifier); |
547 | const auto qmldirScripts = qmldir.scripts(); |
548 | for (const QQmlDirParser::Script &script : qmldirScripts) { |
549 | QUrl scriptUrl = libraryUrl.resolved(relative: QUrl(script.fileName)); |
550 | QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(unNormalizedUrl: scriptUrl); |
551 | addDependency(blob.data()); |
552 | |
553 | scriptImported(blob, import->location, script.nameSpace, import->qualifier); |
554 | } |
555 | } |
556 | |
557 | return true; |
558 | } |
559 | |
560 | bool QQmlTypeLoader::Blob::addImport(const QV4::CompiledData::Import *import, QList<QQmlError> *errors) |
561 | { |
562 | return addImport(import: std::make_shared<PendingImport>(args: this, args&: import), errors); |
563 | } |
564 | |
565 | bool QQmlTypeLoader::Blob::addImport(QQmlTypeLoader::Blob::PendingImportPtr import, QList<QQmlError> *errors) |
566 | { |
567 | Q_ASSERT(errors); |
568 | |
569 | QQmlImportDatabase *importDatabase = typeLoader()->importDatabase(); |
570 | |
571 | if (import->type == QV4::CompiledData::Import::ImportScript) { |
572 | QUrl scriptUrl = finalUrl().resolved(relative: QUrl(import->uri)); |
573 | QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(unNormalizedUrl: scriptUrl); |
574 | addDependency(blob.data()); |
575 | |
576 | scriptImported(blob, import->location, import->qualifier, QString()); |
577 | } else if (import->type == QV4::CompiledData::Import::ImportLibrary) { |
578 | QString qmldirFilePath; |
579 | QString qmldirUrl; |
580 | |
581 | const QQmlImports::LocalQmldirResult qmldirResult = m_importCache.locateLocalQmldir( |
582 | importDatabase, uri: import->uri, vmaj: import->majorVersion, vmin: import->minorVersion, |
583 | qmldirFilePath: &qmldirFilePath, url: &qmldirUrl); |
584 | if (qmldirResult == QQmlImports::QmldirFound) { |
585 | // This is a local library import |
586 | if (!m_importCache.addLibraryImport(importDb: importDatabase, uri: import->uri, prefix: import->qualifier, vmaj: import->majorVersion, |
587 | vmin: import->minorVersion, qmldirIdentifier: qmldirFilePath, qmldirUrl, incomplete: false, errors)) |
588 | return false; |
589 | |
590 | if (!loadImportDependencies(currentImport: import, qmldirUri: qmldirFilePath, errors)) |
591 | return false; |
592 | |
593 | if (!import->qualifier.isEmpty()) { |
594 | // Does this library contain any qualified scripts? |
595 | QUrl libraryUrl(qmldirUrl); |
596 | const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(filePath: qmldirFilePath); |
597 | const auto qmldirScripts = qmldir.scripts(); |
598 | for (const QQmlDirParser::Script &script : qmldirScripts) { |
599 | QUrl scriptUrl = libraryUrl.resolved(relative: QUrl(script.fileName)); |
600 | QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(unNormalizedUrl: scriptUrl); |
601 | addDependency(blob.data()); |
602 | |
603 | scriptImported(blob, import->location, script.nameSpace, import->qualifier); |
604 | } |
605 | } |
606 | } else if ( |
607 | // Major version of module already registered: |
608 | // We believe that the registration is complete. |
609 | QQmlMetaType::typeModule(uri: import->uri, majorVersion: import->majorVersion) |
610 | |
611 | // Otherwise, try to register further module types. |
612 | || (qmldirResult != QQmlImports::QmldirInterceptedToRemote |
613 | && QQmlMetaType::qmlRegisterModuleTypes(uri: import->uri, majorVersion: import->majorVersion)) |
614 | |
615 | // Otherwise, there is no way to register any further types. |
616 | // Try with any module of that name. |
617 | || QQmlMetaType::isAnyModule(uri: import->uri)) { |
618 | |
619 | if (!m_importCache.addLibraryImport( |
620 | importDb: importDatabase, uri: import->uri, prefix: import->qualifier, vmaj: import->majorVersion, |
621 | vmin: import->minorVersion, qmldirIdentifier: QString(), qmldirUrl: QString(), incomplete: false, errors)) { |
622 | return false; |
623 | } |
624 | } else { |
625 | // We haven't yet resolved this import |
626 | m_unresolvedImports << import; |
627 | |
628 | QQmlAbstractUrlInterceptor *interceptor = typeLoader()->engine()->urlInterceptor(); |
629 | |
630 | // Query any network import paths for this library. |
631 | // Interceptor might redirect local paths. |
632 | QStringList remotePathList = importDatabase->importPathList( |
633 | type: interceptor ? QQmlImportDatabase::LocalOrRemote |
634 | : QQmlImportDatabase::Remote); |
635 | if (!remotePathList.isEmpty()) { |
636 | // Add this library and request the possible locations for it |
637 | if (!m_importCache.addLibraryImport(importDb: importDatabase, uri: import->uri, prefix: import->qualifier, vmaj: import->majorVersion, |
638 | vmin: import->minorVersion, qmldirIdentifier: QString(), qmldirUrl: QString(), incomplete: true, errors)) |
639 | return false; |
640 | |
641 | // Probe for all possible locations |
642 | int priority = 0; |
643 | const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(uri: import->uri, basePaths: remotePathList, vmaj: import->majorVersion, vmin: import->minorVersion); |
644 | for (const QString &qmldirPath : qmlDirPaths) { |
645 | if (interceptor) { |
646 | QUrl url = interceptor->intercept( |
647 | path: QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath), |
648 | type: QQmlAbstractUrlInterceptor::QmldirFile); |
649 | if (!QQmlFile::isLocalFile(url) |
650 | && !fetchQmldir(url, import, priority: ++priority, errors)) { |
651 | return false; |
652 | } |
653 | } else if (!fetchQmldir(url: QUrl(qmldirPath), import, priority: ++priority, errors)) { |
654 | return false; |
655 | } |
656 | |
657 | } |
658 | } |
659 | } |
660 | } else { |
661 | Q_ASSERT(import->type == QV4::CompiledData::Import::ImportFile); |
662 | |
663 | bool incomplete = false; |
664 | |
665 | QUrl importUrl(import->uri); |
666 | QString path = importUrl.path(); |
667 | path.append(s: QLatin1String(path.endsWith(c: QLatin1Char('/')) ? "qmldir" : "/qmldir" )); |
668 | importUrl.setPath(path); |
669 | QUrl qmldirUrl = finalUrl().resolved(relative: importUrl); |
670 | if (!QQmlImports::isLocal(url: qmldirUrl)) { |
671 | // This is a remote file; the import is currently incomplete |
672 | incomplete = true; |
673 | } |
674 | |
675 | if (!m_importCache.addFileImport(importDatabase, uri: import->uri, prefix: import->qualifier, vmaj: import->majorVersion, |
676 | vmin: import->minorVersion, incomplete, errors)) |
677 | return false; |
678 | |
679 | if (incomplete) { |
680 | if (!fetchQmldir(url: qmldirUrl, import, priority: 1, errors)) |
681 | return false; |
682 | } |
683 | } |
684 | |
685 | return true; |
686 | } |
687 | |
688 | void QQmlTypeLoader::Blob::dependencyComplete(QQmlDataBlob *blob) |
689 | { |
690 | if (blob->type() == QQmlDataBlob::QmldirFile) { |
691 | QQmlQmldirData *data = static_cast<QQmlQmldirData *>(blob); |
692 | |
693 | PendingImportPtr import = data->import(this); |
694 | |
695 | QList<QQmlError> errors; |
696 | if (!qmldirDataAvailable(data, &errors)) { |
697 | Q_ASSERT(errors.size()); |
698 | QQmlError error(errors.takeFirst()); |
699 | error.setUrl(m_importCache.baseUrl()); |
700 | error.setLine(qmlConvertSourceCoordinate<quint32, int>(n: import->location.line)); |
701 | error.setColumn(qmlConvertSourceCoordinate<quint32, int>(n: import->location.column)); |
702 | errors.prepend(t: error); // put it back on the list after filling out information. |
703 | setError(errors); |
704 | } |
705 | } |
706 | } |
707 | |
708 | bool QQmlTypeLoader::Blob::loadImportDependencies(PendingImportPtr currentImport, const QString &qmldirUri, QList<QQmlError> *errors) |
709 | { |
710 | const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(filePath: qmldirUri); |
711 | for (const QString &implicitImports: qmldir.imports()) { |
712 | auto dependencyImport = std::make_shared<PendingImport>(); |
713 | dependencyImport->uri = implicitImports; |
714 | dependencyImport->qualifier = currentImport->qualifier; |
715 | dependencyImport->majorVersion = currentImport->majorVersion; |
716 | dependencyImport->minorVersion = currentImport->minorVersion; |
717 | if (!addImport(import: dependencyImport, errors)) |
718 | return false; |
719 | } |
720 | return true; |
721 | } |
722 | |
723 | bool QQmlTypeLoader::Blob::isDebugging() const |
724 | { |
725 | return typeLoader()->engine()->handle()->debugger() != nullptr; |
726 | } |
727 | |
728 | bool QQmlTypeLoader::Blob::diskCacheEnabled() const |
729 | { |
730 | return (!disableDiskCache() && !isDebugging()) || forceDiskCache(); |
731 | } |
732 | |
733 | bool QQmlTypeLoader::Blob::qmldirDataAvailable(const QQmlRefPointer<QQmlQmldirData> &data, QList<QQmlError> *errors) |
734 | { |
735 | PendingImportPtr import = data->import(this); |
736 | data->setImport(this, nullptr); |
737 | |
738 | int priority = data->priority(this); |
739 | data->setPriority(this, 0); |
740 | |
741 | if (import) { |
742 | // Do we need to resolve this import? |
743 | const bool resolve = (import->priority == 0) || (import->priority > priority); |
744 | |
745 | if (resolve) { |
746 | // This is the (current) best resolution for this import |
747 | if (!updateQmldir(data, import, errors)) { |
748 | return false; |
749 | } |
750 | |
751 | import->priority = priority; |
752 | return true; |
753 | } |
754 | } |
755 | |
756 | return true; |
757 | } |
758 | |
759 | /*! |
760 | Constructs a new type loader that uses the given \a engine. |
761 | */ |
762 | QQmlTypeLoader::QQmlTypeLoader(QQmlEngine *engine) |
763 | : m_engine(engine) |
764 | , m_thread(new QQmlTypeLoaderThread(this)) |
765 | , m_mutex(m_thread->mutex()) |
766 | , m_typeCacheTrimThreshold(TYPELOADER_MINIMUM_TRIM_THRESHOLD) |
767 | { |
768 | } |
769 | |
770 | /*! |
771 | Destroys the type loader, first clearing the cache of any information about |
772 | loaded files. |
773 | */ |
774 | QQmlTypeLoader::~QQmlTypeLoader() |
775 | { |
776 | // Stop the loader thread before releasing resources |
777 | shutdownThread(); |
778 | |
779 | clearCache(); |
780 | |
781 | invalidate(); |
782 | } |
783 | |
784 | QQmlImportDatabase *QQmlTypeLoader::importDatabase() const |
785 | { |
786 | return &QQmlEnginePrivate::get(e: engine())->importDatabase; |
787 | } |
788 | |
789 | QUrl QQmlTypeLoader::normalize(const QUrl &unNormalizedUrl) |
790 | { |
791 | QUrl normalized(unNormalizedUrl); |
792 | if (normalized.scheme() == QLatin1String("qrc" )) |
793 | normalized.setHost(host: QString()); // map qrc:///a.qml to qrc:/a.qml |
794 | return normalized; |
795 | } |
796 | |
797 | /*! |
798 | Returns a QQmlTypeData for the specified \a url. The QQmlTypeData may be cached. |
799 | */ |
800 | QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QUrl &unNormalizedUrl, Mode mode) |
801 | { |
802 | Q_ASSERT(!unNormalizedUrl.isRelative() && |
803 | (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() || |
804 | !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl)))); |
805 | |
806 | const QUrl url = normalize(unNormalizedUrl); |
807 | |
808 | LockHolder<QQmlTypeLoader> holder(this); |
809 | |
810 | QQmlTypeData *typeData = m_typeCache.value(akey: url); |
811 | |
812 | if (!typeData) { |
813 | // Trim before adding the new type, so that we don't immediately trim it away |
814 | if (m_typeCache.size() >= m_typeCacheTrimThreshold) |
815 | trimCache(); |
816 | |
817 | typeData = new QQmlTypeData(url, this); |
818 | // TODO: if (compiledData == 0), is it safe to omit this insertion? |
819 | m_typeCache.insert(akey: url, avalue: typeData); |
820 | QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError; |
821 | if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(uri: typeData->url(), status: &error)) { |
822 | QQmlTypeLoader::loadWithCachedUnit(blob: typeData, unit: cachedUnit, mode); |
823 | } else { |
824 | typeData->setCachedUnitStatus(error); |
825 | QQmlTypeLoader::load(blob: typeData, mode); |
826 | } |
827 | } else if ((mode == PreferSynchronous || mode == Synchronous) && QQmlFile::isSynchronous(url)) { |
828 | // this was started Asynchronous, but we need to force Synchronous |
829 | // completion now (if at all possible with this type of URL). |
830 | |
831 | if (!m_thread->isThisThread()) { |
832 | // this only works when called directly from the UI thread, but not |
833 | // when recursively called on the QML thread via resolveTypes() |
834 | |
835 | while (!typeData->isCompleteOrError()) { |
836 | unlock(); |
837 | m_thread->waitForNextMessage(); |
838 | lock(); |
839 | } |
840 | } |
841 | } |
842 | |
843 | return typeData; |
844 | } |
845 | |
846 | /*! |
847 | Returns a QQmlTypeData for the given \a data with the provided base \a url. The |
848 | QQmlTypeData will not be cached. |
849 | */ |
850 | QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QByteArray &data, const QUrl &url, Mode mode) |
851 | { |
852 | LockHolder<QQmlTypeLoader> holder(this); |
853 | |
854 | QQmlTypeData *typeData = new QQmlTypeData(url, this); |
855 | QQmlTypeLoader::loadWithStaticData(blob: typeData, data, mode); |
856 | |
857 | return QQmlRefPointer<QQmlTypeData>(typeData, QQmlRefPointer<QQmlTypeData>::Adopt); |
858 | } |
859 | |
860 | /*! |
861 | Return a QQmlScriptBlob for \a url. The QQmlScriptData may be cached. |
862 | */ |
863 | QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript(const QUrl &unNormalizedUrl) |
864 | { |
865 | Q_ASSERT(!unNormalizedUrl.isRelative() && |
866 | (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() || |
867 | !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl)))); |
868 | |
869 | const QUrl url = normalize(unNormalizedUrl); |
870 | |
871 | LockHolder<QQmlTypeLoader> holder(this); |
872 | |
873 | QQmlScriptBlob *scriptBlob = m_scriptCache.value(akey: url); |
874 | |
875 | if (!scriptBlob) { |
876 | scriptBlob = new QQmlScriptBlob(url, this); |
877 | m_scriptCache.insert(akey: url, avalue: scriptBlob); |
878 | |
879 | QQmlMetaType::CachedUnitLookupError error; |
880 | if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(uri: scriptBlob->url(), status: &error)) { |
881 | QQmlTypeLoader::loadWithCachedUnit(blob: scriptBlob, unit: cachedUnit); |
882 | } else { |
883 | scriptBlob->setCachedUnitStatus(error); |
884 | QQmlTypeLoader::load(blob: scriptBlob); |
885 | } |
886 | } |
887 | |
888 | return scriptBlob; |
889 | } |
890 | |
891 | /*! |
892 | Returns a QQmlQmldirData for \a url. The QQmlQmldirData may be cached. |
893 | */ |
894 | QQmlRefPointer<QQmlQmldirData> QQmlTypeLoader::getQmldir(const QUrl &url) |
895 | { |
896 | Q_ASSERT(!url.isRelative() && |
897 | (QQmlFile::urlToLocalFileOrQrc(url).isEmpty() || |
898 | !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(url)))); |
899 | LockHolder<QQmlTypeLoader> holder(this); |
900 | |
901 | QQmlQmldirData *qmldirData = m_qmldirCache.value(akey: url); |
902 | |
903 | if (!qmldirData) { |
904 | qmldirData = new QQmlQmldirData(url, this); |
905 | m_qmldirCache.insert(akey: url, avalue: qmldirData); |
906 | QQmlTypeLoader::load(blob: qmldirData); |
907 | } |
908 | |
909 | return qmldirData; |
910 | } |
911 | |
912 | /*! |
913 | Returns the absolute filename of path via a directory cache. |
914 | Returns a empty string if the path does not exist. |
915 | |
916 | Why a directory cache? QML checks for files in many paths with |
917 | invalid directories. By caching whether a directory exists |
918 | we avoid many stats. We also cache the files' existence in the |
919 | directory, for the same reason. |
920 | */ |
921 | QString QQmlTypeLoader::absoluteFilePath(const QString &path) |
922 | { |
923 | if (path.isEmpty()) |
924 | return QString(); |
925 | if (path.at(i: 0) == QLatin1Char(':')) { |
926 | // qrc resource |
927 | QFileInfo fileInfo(path); |
928 | return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString(); |
929 | } else if (path.count() > 3 && path.at(i: 3) == QLatin1Char(':') && |
930 | path.startsWith(s: QLatin1String("qrc" ), cs: Qt::CaseInsensitive)) { |
931 | // qrc resource url |
932 | QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path)); |
933 | return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString(); |
934 | } |
935 | #if defined(Q_OS_ANDROID) |
936 | else if (path.count() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/') && |
937 | path.startsWith(QLatin1String("assets" ), Qt::CaseInsensitive)) { |
938 | // assets resource url |
939 | QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path)); |
940 | return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString(); |
941 | } else if (path.count() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') && |
942 | path.startsWith(QLatin1String("content" ), Qt::CaseInsensitive)) { |
943 | // content url |
944 | QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path)); |
945 | return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString(); |
946 | } |
947 | #endif |
948 | |
949 | int lastSlash = path.lastIndexOf(c: QLatin1Char('/')); |
950 | QString dirPath(path.left(n: lastSlash)); |
951 | |
952 | LockHolder<QQmlTypeLoader> holder(this); |
953 | if (!m_importDirCache.contains(key: dirPath)) { |
954 | bool exists = QDir(dirPath).exists(); |
955 | QCache<QString, bool> *entry = exists ? new QCache<QString, bool> : nullptr; |
956 | m_importDirCache.insert(akey: dirPath, aobject: entry); |
957 | } |
958 | QCache<QString, bool> *fileSet = m_importDirCache.object(key: dirPath); |
959 | if (!fileSet) |
960 | return QString(); |
961 | |
962 | QString absoluteFilePath; |
963 | QString fileName(path.mid(position: lastSlash+1, n: path.length()-lastSlash-1)); |
964 | |
965 | bool *value = fileSet->object(key: fileName); |
966 | if (value) { |
967 | if (*value) |
968 | absoluteFilePath = path; |
969 | } else { |
970 | bool exists = QFile::exists(fileName: path); |
971 | fileSet->insert(akey: fileName, aobject: new bool(exists)); |
972 | if (exists) |
973 | absoluteFilePath = path; |
974 | } |
975 | |
976 | if (absoluteFilePath.length() > 2 && absoluteFilePath.at(i: 0) != QLatin1Char('/') && absoluteFilePath.at(i: 1) != QLatin1Char(':')) |
977 | absoluteFilePath = QFileInfo(absoluteFilePath).absoluteFilePath(); |
978 | |
979 | return absoluteFilePath; |
980 | } |
981 | |
982 | bool QQmlTypeLoader::fileExists(const QString &path, const QString &file) |
983 | { |
984 | const QChar nullChar(QChar::Null); |
985 | if (path.isEmpty() || path.contains(c: nullChar) || file.isEmpty() || file.contains(c: nullChar)) |
986 | return false; |
987 | |
988 | Q_ASSERT(path.endsWith(QLatin1Char('/'))); |
989 | |
990 | LockHolder<QQmlTypeLoader> holder(this); |
991 | QCache<QString, bool> *fileSet = m_importDirCache.object(key: path); |
992 | if (fileSet) { |
993 | if (bool *value = fileSet->object(key: file)) |
994 | return *value; |
995 | } else if (m_importDirCache.contains(key: path)) { |
996 | // explicit nullptr in cache |
997 | return false; |
998 | } |
999 | |
1000 | auto addToCache = [&](const QFileInfo &fileInfo) { |
1001 | if (!fileSet) { |
1002 | fileSet = fileInfo.dir().exists() ? new QCache<QString, bool> : nullptr; |
1003 | m_importDirCache.insert(akey: path, aobject: fileSet); |
1004 | if (!fileSet) |
1005 | return false; |
1006 | } |
1007 | |
1008 | const bool exists = fileInfo.exists(); |
1009 | fileSet->insert(akey: file, aobject: new bool(exists)); |
1010 | return exists; |
1011 | }; |
1012 | |
1013 | if (path.at(i: 0) == QLatin1Char(':')) { |
1014 | // qrc resource |
1015 | return addToCache(QFileInfo(path + file)); |
1016 | } |
1017 | |
1018 | if (path.count() > 3 && path.at(i: 3) == QLatin1Char(':') |
1019 | && path.startsWith(s: QLatin1String("qrc" ), cs: Qt::CaseInsensitive)) { |
1020 | // qrc resource url |
1021 | return addToCache(QFileInfo(QQmlFile::urlToLocalFileOrQrc(path + file))); |
1022 | } |
1023 | |
1024 | #if defined(Q_OS_ANDROID) |
1025 | if (path.count() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/') |
1026 | && path.startsWith(QLatin1String("assets" ), Qt::CaseInsensitive)) { |
1027 | // assets resource url |
1028 | return addToCache(QFileInfo(QQmlFile::urlToLocalFileOrQrc(path + file))); |
1029 | } |
1030 | |
1031 | if (path.count() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') |
1032 | && path.startsWith(QLatin1String("content" ), Qt::CaseInsensitive)) { |
1033 | // content url |
1034 | return addToCache(QFileInfo(QQmlFile::urlToLocalFileOrQrc(path + file))); |
1035 | } |
1036 | #endif |
1037 | |
1038 | return addToCache(QFileInfo(path + file)); |
1039 | } |
1040 | |
1041 | |
1042 | /*! |
1043 | Returns true if the path is a directory via a directory cache. Cache is |
1044 | shared with absoluteFilePath(). |
1045 | */ |
1046 | bool QQmlTypeLoader::directoryExists(const QString &path) |
1047 | { |
1048 | if (path.isEmpty()) |
1049 | return false; |
1050 | |
1051 | bool isResource = path.at(i: 0) == QLatin1Char(':'); |
1052 | #if defined(Q_OS_ANDROID) |
1053 | isResource = isResource || path.startsWith(QLatin1String("assets:/" )) || path.startsWith(QLatin1String("content:/" )); |
1054 | #endif |
1055 | |
1056 | if (isResource) { |
1057 | // qrc resource |
1058 | QFileInfo fileInfo(path); |
1059 | return fileInfo.exists() && fileInfo.isDir(); |
1060 | } |
1061 | |
1062 | int length = path.length(); |
1063 | if (path.endsWith(c: QLatin1Char('/'))) |
1064 | --length; |
1065 | QString dirPath(path.left(n: length)); |
1066 | |
1067 | LockHolder<QQmlTypeLoader> holder(this); |
1068 | if (!m_importDirCache.contains(key: dirPath)) { |
1069 | bool exists = QDir(dirPath).exists(); |
1070 | QCache<QString, bool> *files = exists ? new QCache<QString, bool> : nullptr; |
1071 | m_importDirCache.insert(akey: dirPath, aobject: files); |
1072 | } |
1073 | |
1074 | QCache<QString, bool> *fileSet = m_importDirCache.object(key: dirPath); |
1075 | return fileSet != nullptr; |
1076 | } |
1077 | |
1078 | |
1079 | /*! |
1080 | Return a QQmlTypeLoaderQmldirContent for absoluteFilePath. The QQmlTypeLoaderQmldirContent may be cached. |
1081 | |
1082 | \a filePath is a local file path. |
1083 | |
1084 | It can also be a remote path for a remote directory import, but it will have been cached by now in this case. |
1085 | */ |
1086 | const QQmlTypeLoaderQmldirContent QQmlTypeLoader::qmldirContent(const QString &filePathIn) |
1087 | { |
1088 | LockHolder<QQmlTypeLoader> holder(this); |
1089 | |
1090 | QString filePath; |
1091 | |
1092 | // Try to guess if filePathIn is already a URL. This is necessarily fragile, because |
1093 | // - paths can contain ':', which might make them appear as URLs with schemes. |
1094 | // - windows drive letters appear as schemes (thus "< 2" below). |
1095 | // - a "file:" URL is equivalent to the respective file, but will be treated differently. |
1096 | // Yet, this heuristic is the best we can do until we pass more structured information here, |
1097 | // for example a QUrl also for local files. |
1098 | QUrl url(filePathIn); |
1099 | if (url.scheme().length() < 2) { |
1100 | filePath = filePathIn; |
1101 | } else { |
1102 | filePath = QQmlFile::urlToLocalFileOrQrc(url); |
1103 | if (filePath.isEmpty()) { // Can't load the remote here, but should be cached |
1104 | if (auto entry = m_importQmlDirCache.value(key: filePathIn)) |
1105 | return **entry; |
1106 | else |
1107 | return QQmlTypeLoaderQmldirContent(); |
1108 | } |
1109 | } |
1110 | |
1111 | QQmlTypeLoaderQmldirContent **val = m_importQmlDirCache.value(key: filePath); |
1112 | if (val) |
1113 | return **val; |
1114 | QQmlTypeLoaderQmldirContent *qmldir = new QQmlTypeLoaderQmldirContent; |
1115 | |
1116 | #define ERROR(description) { QQmlError e; e.setDescription(description); qmldir->setError(e); } |
1117 | #define NOT_READABLE_ERROR QString(QLatin1String("module \"$$URI$$\" definition \"%1\" not readable")) |
1118 | #define CASE_MISMATCH_ERROR QString(QLatin1String("cannot load module \"$$URI$$\": File name case mismatch for \"%1\"")) |
1119 | |
1120 | QFile file(filePath); |
1121 | if (!QQml_isFileCaseCorrect(fileName: filePath)) { |
1122 | ERROR(CASE_MISMATCH_ERROR.arg(filePath)); |
1123 | } else if (file.open(flags: QFile::ReadOnly)) { |
1124 | QByteArray data = file.readAll(); |
1125 | qmldir->setContent(location: filePath, content: QString::fromUtf8(str: data)); |
1126 | } else { |
1127 | ERROR(NOT_READABLE_ERROR.arg(filePath)); |
1128 | } |
1129 | |
1130 | #undef ERROR |
1131 | #undef NOT_READABLE_ERROR |
1132 | #undef CASE_MISMATCH_ERROR |
1133 | |
1134 | m_importQmlDirCache.insert(key: filePath, value: qmldir); |
1135 | return *qmldir; |
1136 | } |
1137 | |
1138 | void QQmlTypeLoader::setQmldirContent(const QString &url, const QString &content) |
1139 | { |
1140 | QQmlTypeLoaderQmldirContent *qmldir; |
1141 | QQmlTypeLoaderQmldirContent **val = m_importQmlDirCache.value(key: url); |
1142 | if (val) { |
1143 | qmldir = *val; |
1144 | } else { |
1145 | qmldir = new QQmlTypeLoaderQmldirContent; |
1146 | m_importQmlDirCache.insert(key: url, value: qmldir); |
1147 | } |
1148 | |
1149 | if (!qmldir->hasContent()) |
1150 | qmldir->setContent(location: url, content); |
1151 | } |
1152 | |
1153 | /*! |
1154 | Clears cached information about loaded files, including any type data, scripts |
1155 | and qmldir information. |
1156 | */ |
1157 | void QQmlTypeLoader::clearCache() |
1158 | { |
1159 | for (TypeCache::Iterator iter = m_typeCache.begin(), end = m_typeCache.end(); iter != end; ++iter) |
1160 | (*iter)->release(); |
1161 | for (ScriptCache::Iterator iter = m_scriptCache.begin(), end = m_scriptCache.end(); iter != end; ++iter) |
1162 | (*iter)->release(); |
1163 | for (QmldirCache::Iterator iter = m_qmldirCache.begin(), end = m_qmldirCache.end(); iter != end; ++iter) |
1164 | (*iter)->release(); |
1165 | |
1166 | qDeleteAll(c: m_importQmlDirCache); |
1167 | |
1168 | m_typeCache.clear(); |
1169 | m_typeCacheTrimThreshold = TYPELOADER_MINIMUM_TRIM_THRESHOLD; |
1170 | m_scriptCache.clear(); |
1171 | m_qmldirCache.clear(); |
1172 | m_importDirCache.clear(); |
1173 | m_importQmlDirCache.clear(); |
1174 | QQmlMetaType::freeUnusedTypesAndCaches(); |
1175 | } |
1176 | |
1177 | void QQmlTypeLoader::updateTypeCacheTrimThreshold() |
1178 | { |
1179 | int size = m_typeCache.size(); |
1180 | if (size > m_typeCacheTrimThreshold) |
1181 | m_typeCacheTrimThreshold = size * 2; |
1182 | if (size < m_typeCacheTrimThreshold / 2) |
1183 | m_typeCacheTrimThreshold = qMax(a: size * 2, TYPELOADER_MINIMUM_TRIM_THRESHOLD); |
1184 | } |
1185 | |
1186 | void QQmlTypeLoader::trimCache() |
1187 | { |
1188 | while (true) { |
1189 | QList<TypeCache::Iterator> unneededTypes; |
1190 | for (TypeCache::Iterator iter = m_typeCache.begin(), end = m_typeCache.end(); iter != end; ++iter) { |
1191 | QQmlTypeData *typeData = iter.value(); |
1192 | |
1193 | // typeData->m_compiledData may be set early on in the proccess of loading a file, so |
1194 | // it's important to check the general loading status of the typeData before making any |
1195 | // other decisions. |
1196 | if (typeData->count() == 1 && (typeData->isError() || typeData->isComplete()) |
1197 | && (!typeData->m_compiledData || typeData->m_compiledData->count() == 1)) { |
1198 | // There are no live objects of this type |
1199 | unneededTypes.append(t: iter); |
1200 | } |
1201 | } |
1202 | |
1203 | if (unneededTypes.isEmpty()) |
1204 | break; |
1205 | |
1206 | while (!unneededTypes.isEmpty()) { |
1207 | TypeCache::Iterator iter = unneededTypes.takeLast(); |
1208 | |
1209 | iter.value()->release(); |
1210 | m_typeCache.erase(it: iter); |
1211 | } |
1212 | } |
1213 | |
1214 | updateTypeCacheTrimThreshold(); |
1215 | |
1216 | QQmlMetaType::freeUnusedTypesAndCaches(); |
1217 | |
1218 | // TODO: release any scripts which are no longer referenced by any types |
1219 | } |
1220 | |
1221 | bool QQmlTypeLoader::isTypeLoaded(const QUrl &url) const |
1222 | { |
1223 | LockHolder<QQmlTypeLoader> holder(const_cast<QQmlTypeLoader *>(this)); |
1224 | return m_typeCache.contains(akey: url); |
1225 | } |
1226 | |
1227 | bool QQmlTypeLoader::isScriptLoaded(const QUrl &url) const |
1228 | { |
1229 | LockHolder<QQmlTypeLoader> holder(const_cast<QQmlTypeLoader *>(this)); |
1230 | return m_scriptCache.contains(akey: url); |
1231 | } |
1232 | |
1233 | QT_END_NAMESPACE |
1234 | |