1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2019 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/qqmldatablob_p.h> |
41 | #include <private/qqmlglobal_p.h> |
42 | #include <private/qqmlprofiler_p.h> |
43 | #include <private/qqmltypeloader_p.h> |
44 | #include <private/qqmltypeloaderthread_p.h> |
45 | |
46 | #include <QtQml/qqmlengine.h> |
47 | |
48 | #ifdef DATABLOB_DEBUG |
49 | #define ASSERT_CALLBACK() do { if (!m_typeLoader || !m_typeLoader->m_thread->isThisThread()) qFatal("QQmlDataBlob: An API call was made outside a callback"); } while (false) |
50 | #else |
51 | #define ASSERT_CALLBACK() |
52 | #endif |
53 | |
54 | DEFINE_BOOL_CONFIG_OPTION(dumpErrors, QML_DUMP_ERRORS); |
55 | |
56 | QT_BEGIN_NAMESPACE |
57 | |
58 | /*! |
59 | \class QQmlDataBlob |
60 | \brief The QQmlDataBlob encapsulates a data request that can be issued to a QQmlTypeLoader. |
61 | \internal |
62 | |
63 | QQmlDataBlob's are loaded by a QQmlTypeLoader. The user creates the QQmlDataBlob |
64 | and then calls QQmlTypeLoader::load() or QQmlTypeLoader::loadWithStaticData() to load it. |
65 | The QQmlTypeLoader invokes callbacks on the QQmlDataBlob as data becomes available. |
66 | */ |
67 | |
68 | /*! |
69 | \enum QQmlDataBlob::Status |
70 | |
71 | This enum describes the status of the data blob. |
72 | |
73 | \list |
74 | \li Null The blob has not yet been loaded by a QQmlTypeLoader |
75 | \li Loading The blob is loading network data. The QQmlDataBlob::setData() callback has not yet been |
76 | invoked or has not yet returned. |
77 | \li WaitingForDependencies The blob is waiting for dependencies to be done before continuing. |
78 | This status only occurs after the QQmlDataBlob::setData() callback has been made, and when the |
79 | blob has outstanding dependencies. |
80 | \li Complete The blob's data has been loaded and all dependencies are done. |
81 | \li Error An error has been set on this blob. |
82 | \endlist |
83 | */ |
84 | |
85 | /*! |
86 | \enum QQmlDataBlob::Type |
87 | |
88 | This enum describes the type of the data blob. |
89 | |
90 | \list |
91 | \li QmlFile This is a QQmlTypeData |
92 | \li JavaScriptFile This is a QQmlScriptData |
93 | \li QmldirFile This is a QQmlQmldirData |
94 | \endlist |
95 | */ |
96 | |
97 | /*! |
98 | Create a new QQmlDataBlob for \a url and of the provided \a type. |
99 | */ |
100 | QQmlDataBlob::QQmlDataBlob(const QUrl &url, Type type, QQmlTypeLoader *manager) |
101 | : m_typeLoader(manager), m_type(type), m_url(url), m_finalUrl(url), m_redirectCount(0), |
102 | m_inCallback(false), m_isDone(false) |
103 | { |
104 | //Set here because we need to get the engine from the manager |
105 | if (m_typeLoader->engine() && m_typeLoader->engine()->urlInterceptor()) |
106 | m_url = m_typeLoader->engine()->urlInterceptor()->intercept(m_url, |
107 | (QQmlAbstractUrlInterceptor::DataType)m_type); |
108 | } |
109 | |
110 | /*! \internal */ |
111 | QQmlDataBlob::~QQmlDataBlob() |
112 | { |
113 | Q_ASSERT(m_waitingOnMe.isEmpty()); |
114 | |
115 | cancelAllWaitingFor(); |
116 | } |
117 | |
118 | /*! |
119 | Must be called before loading can occur. |
120 | */ |
121 | void QQmlDataBlob::startLoading() |
122 | { |
123 | Q_ASSERT(status() == QQmlDataBlob::Null); |
124 | m_data.setStatus(QQmlDataBlob::Loading); |
125 | } |
126 | |
127 | /*! |
128 | Returns the type provided to the constructor. |
129 | */ |
130 | QQmlDataBlob::Type QQmlDataBlob::type() const |
131 | { |
132 | return m_type; |
133 | } |
134 | |
135 | /*! |
136 | Returns the blob's status. |
137 | */ |
138 | QQmlDataBlob::Status QQmlDataBlob::status() const |
139 | { |
140 | return m_data.status(); |
141 | } |
142 | |
143 | /*! |
144 | Returns true if the status is Null. |
145 | */ |
146 | bool QQmlDataBlob::isNull() const |
147 | { |
148 | return status() == Null; |
149 | } |
150 | |
151 | /*! |
152 | Returns true if the status is Loading. |
153 | */ |
154 | bool QQmlDataBlob::isLoading() const |
155 | { |
156 | return status() == Loading; |
157 | } |
158 | |
159 | /*! |
160 | Returns true if the status is WaitingForDependencies. |
161 | */ |
162 | bool QQmlDataBlob::isWaiting() const |
163 | { |
164 | return status() == WaitingForDependencies || |
165 | status() == ResolvingDependencies; |
166 | } |
167 | |
168 | /*! |
169 | Returns true if the status is Complete. |
170 | */ |
171 | bool QQmlDataBlob::isComplete() const |
172 | { |
173 | return status() == Complete; |
174 | } |
175 | |
176 | /*! |
177 | Returns true if the status is Error. |
178 | */ |
179 | bool QQmlDataBlob::isError() const |
180 | { |
181 | return status() == Error; |
182 | } |
183 | |
184 | /*! |
185 | Returns true if the status is Complete or Error. |
186 | */ |
187 | bool QQmlDataBlob::isCompleteOrError() const |
188 | { |
189 | Status s = status(); |
190 | return s == Error || s == Complete; |
191 | } |
192 | |
193 | /*! |
194 | Returns the data download progress from 0 to 1. |
195 | */ |
196 | qreal QQmlDataBlob::progress() const |
197 | { |
198 | quint8 p = m_data.progress(); |
199 | if (p == 0xFF) return 1.; |
200 | else return qreal(p) / qreal(0xFF); |
201 | } |
202 | |
203 | /*! |
204 | Returns the physical url of the data. Initially this is the same as |
205 | finalUrl(), but if a URL interceptor is set, it will work on this URL |
206 | and leave finalUrl() alone. |
207 | |
208 | \sa finalUrl() |
209 | */ |
210 | QUrl QQmlDataBlob::url() const |
211 | { |
212 | return m_url; |
213 | } |
214 | |
215 | QString QQmlDataBlob::urlString() const |
216 | { |
217 | if (m_urlString.isEmpty()) |
218 | m_urlString = m_url.toString(); |
219 | |
220 | return m_urlString; |
221 | } |
222 | |
223 | /*! |
224 | Returns the logical URL to be used for resolving further URLs referred to in |
225 | the code. |
226 | |
227 | This is the blob url passed to the constructor. If a URL interceptor rewrites |
228 | the URL, this one stays the same. If a network redirect happens while fetching |
229 | the data, this url is updated to reflect the new location. Therefore, if both |
230 | an interception and a redirection happen, the final url will indirectly |
231 | incorporate the result of the interception, potentially breaking further |
232 | lookups. |
233 | |
234 | \sa url() |
235 | */ |
236 | QUrl QQmlDataBlob::finalUrl() const |
237 | { |
238 | return m_finalUrl; |
239 | } |
240 | |
241 | /*! |
242 | Returns the finalUrl() as a string. |
243 | */ |
244 | QString QQmlDataBlob::finalUrlString() const |
245 | { |
246 | if (m_finalUrlString.isEmpty()) |
247 | m_finalUrlString = m_finalUrl.toString(); |
248 | |
249 | return m_finalUrlString; |
250 | } |
251 | |
252 | /*! |
253 | Return the errors on this blob. |
254 | |
255 | May only be called from the load thread, or after the blob isCompleteOrError(). |
256 | */ |
257 | QList<QQmlError> QQmlDataBlob::errors() const |
258 | { |
259 | Q_ASSERT(isCompleteOrError() || (m_typeLoader && m_typeLoader->m_thread->isThisThread())); |
260 | return m_errors; |
261 | } |
262 | |
263 | /*! |
264 | Mark this blob as having \a errors. |
265 | |
266 | All outstanding dependencies will be cancelled. Requests to add new dependencies |
267 | will be ignored. Entry into the Error state is irreversable. |
268 | |
269 | The setError() method may only be called from within a QQmlDataBlob callback. |
270 | */ |
271 | void QQmlDataBlob::setError(const QQmlError &errors) |
272 | { |
273 | ASSERT_CALLBACK(); |
274 | |
275 | QList<QQmlError> l; |
276 | l << errors; |
277 | setError(l); |
278 | } |
279 | |
280 | /*! |
281 | \overload |
282 | */ |
283 | void QQmlDataBlob::setError(const QList<QQmlError> &errors) |
284 | { |
285 | ASSERT_CALLBACK(); |
286 | |
287 | Q_ASSERT(status() != Error); |
288 | Q_ASSERT(m_errors.isEmpty()); |
289 | |
290 | m_errors = errors; // Must be set before the m_data fence |
291 | m_data.setStatus(Error); |
292 | |
293 | if (dumpErrors()) { |
294 | qWarning().nospace() << "Errors for " << urlString(); |
295 | for (int ii = 0; ii < errors.count(); ++ii) |
296 | qWarning().nospace() << " " << qPrintable(errors.at(ii).toString()); |
297 | } |
298 | cancelAllWaitingFor(); |
299 | |
300 | if (!m_inCallback) |
301 | tryDone(); |
302 | } |
303 | |
304 | void QQmlDataBlob::setError(const QQmlJS::DiagnosticMessage &error) |
305 | { |
306 | QQmlError e; |
307 | e.setColumn(error.column); |
308 | e.setLine(error.line); |
309 | e.setDescription(error.message); |
310 | e.setUrl(url()); |
311 | setError(e); |
312 | } |
313 | |
314 | void QQmlDataBlob::setError(const QVector<QQmlJS::DiagnosticMessage> &errors) |
315 | { |
316 | QList<QQmlError> finalErrors; |
317 | finalErrors.reserve(errors.count()); |
318 | for (const auto &error : errors) { |
319 | QQmlError e; |
320 | e.setColumn(error.column); |
321 | e.setLine(error.line); |
322 | e.setDescription(error.message); |
323 | e.setUrl(url()); |
324 | finalErrors << e; |
325 | } |
326 | setError(finalErrors); |
327 | } |
328 | |
329 | void QQmlDataBlob::setError(const QString &description) |
330 | { |
331 | QQmlError e; |
332 | e.setDescription(description); |
333 | e.setUrl(url()); |
334 | setError(e); |
335 | } |
336 | |
337 | /*! |
338 | Wait for \a blob to become complete or to error. If \a blob is already |
339 | complete or in error, or this blob is already complete, this has no effect. |
340 | |
341 | The setError() method may only be called from within a QQmlDataBlob callback. |
342 | */ |
343 | void QQmlDataBlob::addDependency(QQmlDataBlob *blob) |
344 | { |
345 | ASSERT_CALLBACK(); |
346 | |
347 | Q_ASSERT(status() != Null); |
348 | |
349 | if (!blob || |
350 | blob->status() == Error || blob->status() == Complete || |
351 | status() == Error || status() == Complete || m_isDone) |
352 | return; |
353 | |
354 | for (auto existingDep: qAsConst(m_waitingFor)) |
355 | if (existingDep.data() == blob) |
356 | return; |
357 | |
358 | m_data.setStatus(WaitingForDependencies); |
359 | |
360 | m_waitingFor.append(blob); |
361 | blob->m_waitingOnMe.append(this); |
362 | } |
363 | |
364 | /*! |
365 | \fn void QQmlDataBlob::dataReceived(const Data &data) |
366 | |
367 | Invoked when data for the blob is received. Implementors should use this callback |
368 | to determine a blob's dependencies. Within this callback you may call setError() |
369 | or addDependency(). |
370 | */ |
371 | |
372 | /*! |
373 | Invoked once data has either been received or a network error occurred, and all |
374 | dependencies are complete. |
375 | |
376 | You can set an error in this method, but you cannot add new dependencies. Implementors |
377 | should use this callback to finalize processing of data. |
378 | |
379 | The default implementation does nothing. |
380 | |
381 | XXX Rename processData() or some such to avoid confusion between done() (processing thread) |
382 | and completed() (main thread) |
383 | */ |
384 | void QQmlDataBlob::done() |
385 | { |
386 | } |
387 | |
388 | #if QT_CONFIG(qml_network) |
389 | /*! |
390 | Invoked if there is a network error while fetching this blob. |
391 | |
392 | The default implementation sets an appropriate QQmlError. |
393 | */ |
394 | void QQmlDataBlob::networkError(QNetworkReply::NetworkError networkError) |
395 | { |
396 | Q_UNUSED(networkError); |
397 | |
398 | QQmlError error; |
399 | error.setUrl(m_url); |
400 | |
401 | const char *errorString = nullptr; |
402 | switch (networkError) { |
403 | default: |
404 | errorString = "Network error" ; |
405 | break; |
406 | case QNetworkReply::ConnectionRefusedError: |
407 | errorString = "Connection refused" ; |
408 | break; |
409 | case QNetworkReply::RemoteHostClosedError: |
410 | errorString = "Remote host closed the connection" ; |
411 | break; |
412 | case QNetworkReply::HostNotFoundError: |
413 | errorString = "Host not found" ; |
414 | break; |
415 | case QNetworkReply::TimeoutError: |
416 | errorString = "Timeout" ; |
417 | break; |
418 | case QNetworkReply::ProxyConnectionRefusedError: |
419 | case QNetworkReply::ProxyConnectionClosedError: |
420 | case QNetworkReply::ProxyNotFoundError: |
421 | case QNetworkReply::ProxyTimeoutError: |
422 | case QNetworkReply::ProxyAuthenticationRequiredError: |
423 | case QNetworkReply::UnknownProxyError: |
424 | errorString = "Proxy error" ; |
425 | break; |
426 | case QNetworkReply::ContentAccessDenied: |
427 | errorString = "Access denied" ; |
428 | break; |
429 | case QNetworkReply::ContentNotFoundError: |
430 | errorString = "File not found" ; |
431 | break; |
432 | case QNetworkReply::AuthenticationRequiredError: |
433 | errorString = "Authentication required" ; |
434 | break; |
435 | }; |
436 | |
437 | error.setDescription(QLatin1String(errorString)); |
438 | |
439 | setError(error); |
440 | } |
441 | #endif // qml_network |
442 | |
443 | /*! |
444 | Called if \a blob, which was previously waited for, has an error. |
445 | |
446 | The default implementation does nothing. |
447 | */ |
448 | void QQmlDataBlob::dependencyError(QQmlDataBlob *blob) |
449 | { |
450 | Q_UNUSED(blob); |
451 | } |
452 | |
453 | /*! |
454 | Called if \a blob, which was previously waited for, has completed. |
455 | |
456 | The default implementation does nothing. |
457 | */ |
458 | void QQmlDataBlob::dependencyComplete(QQmlDataBlob *blob) |
459 | { |
460 | Q_UNUSED(blob); |
461 | } |
462 | |
463 | /*! |
464 | Called when all blobs waited for have completed. This occurs regardless of |
465 | whether they are in error, or complete state. |
466 | |
467 | The default implementation does nothing. |
468 | */ |
469 | void QQmlDataBlob::allDependenciesDone() |
470 | { |
471 | m_data.setStatus(QQmlDataBlob::ResolvingDependencies); |
472 | } |
473 | |
474 | /*! |
475 | Called when the download progress of this blob changes. \a progress goes |
476 | from 0 to 1. |
477 | |
478 | This callback is only invoked if an asynchronous load for this blob is |
479 | made. An asynchronous load is one in which the Asynchronous mode is |
480 | specified explicitly, or one that is implicitly delayed due to a network |
481 | operation. |
482 | |
483 | The default implementation does nothing. |
484 | */ |
485 | void QQmlDataBlob::downloadProgressChanged(qreal progress) |
486 | { |
487 | Q_UNUSED(progress); |
488 | } |
489 | |
490 | /*! |
491 | Invoked on the main thread sometime after done() was called on the load thread. |
492 | |
493 | You cannot modify the blobs state at all in this callback and cannot depend on the |
494 | order or timeliness of these callbacks. Implementors should use this callback to notify |
495 | dependencies on the main thread that the blob is done and not a lot else. |
496 | |
497 | This callback is only invoked if an asynchronous load for this blob is |
498 | made. An asynchronous load is one in which the Asynchronous mode is |
499 | specified explicitly, or one that is implicitly delayed due to a network |
500 | operation. |
501 | |
502 | The default implementation does nothing. |
503 | */ |
504 | void QQmlDataBlob::completed() |
505 | { |
506 | } |
507 | |
508 | |
509 | void QQmlDataBlob::tryDone() |
510 | { |
511 | if (status() != Loading && m_waitingFor.isEmpty() && !m_isDone) { |
512 | m_isDone = true; |
513 | addref(); |
514 | |
515 | #ifdef DATABLOB_DEBUG |
516 | qWarning("QQmlDataBlob::done() %s" , qPrintable(urlString())); |
517 | #endif |
518 | done(); |
519 | |
520 | if (status() != Error) |
521 | m_data.setStatus(Complete); |
522 | |
523 | notifyAllWaitingOnMe(); |
524 | |
525 | // Locking is not required here, as anyone expecting callbacks must |
526 | // already be protected against the blob being completed (as set above); |
527 | #ifdef DATABLOB_DEBUG |
528 | qWarning("QQmlDataBlob: Dispatching completed" ); |
529 | #endif |
530 | m_typeLoader->m_thread->callCompleted(this); |
531 | |
532 | release(); |
533 | } |
534 | } |
535 | |
536 | void QQmlDataBlob::cancelAllWaitingFor() |
537 | { |
538 | while (m_waitingFor.count()) { |
539 | QQmlRefPointer<QQmlDataBlob> blob = m_waitingFor.takeLast(); |
540 | |
541 | Q_ASSERT(blob->m_waitingOnMe.contains(this)); |
542 | |
543 | blob->m_waitingOnMe.removeOne(this); |
544 | } |
545 | } |
546 | |
547 | void QQmlDataBlob::notifyAllWaitingOnMe() |
548 | { |
549 | while (m_waitingOnMe.count()) { |
550 | QQmlDataBlob *blob = m_waitingOnMe.takeLast(); |
551 | |
552 | Q_ASSERT(std::any_of(blob->m_waitingFor.constBegin(), blob->m_waitingFor.constEnd(), |
553 | [this](const QQmlRefPointer<QQmlDataBlob> &waiting) { return waiting.data() == this; })); |
554 | |
555 | blob->notifyComplete(this); |
556 | } |
557 | } |
558 | |
559 | void QQmlDataBlob::notifyComplete(QQmlDataBlob *blob) |
560 | { |
561 | Q_ASSERT(blob->status() == Error || blob->status() == Complete); |
562 | QQmlCompilingProfiler prof(typeLoader()->profiler(), blob); |
563 | |
564 | m_inCallback = true; |
565 | |
566 | QQmlRefPointer<QQmlDataBlob> blobRef; |
567 | for (int i = 0; i < m_waitingFor.count(); ++i) { |
568 | if (m_waitingFor.at(i).data() == blob) { |
569 | blobRef = m_waitingFor.takeAt(i); |
570 | break; |
571 | } |
572 | } |
573 | Q_ASSERT(blobRef); |
574 | |
575 | if (blob->status() == Error) { |
576 | dependencyError(blob); |
577 | } else if (blob->status() == Complete) { |
578 | dependencyComplete(blob); |
579 | } |
580 | |
581 | if (!isError() && m_waitingFor.isEmpty()) |
582 | allDependenciesDone(); |
583 | |
584 | m_inCallback = false; |
585 | |
586 | tryDone(); |
587 | } |
588 | |
589 | QString QQmlDataBlob::SourceCodeData::readAll(QString *error) const |
590 | { |
591 | error->clear(); |
592 | if (hasInlineSourceCode) |
593 | return inlineSourceCode; |
594 | |
595 | QFile f(fileInfo.absoluteFilePath()); |
596 | if (!f.open(QIODevice::ReadOnly)) { |
597 | *error = f.errorString(); |
598 | return QString(); |
599 | } |
600 | |
601 | const qint64 fileSize = fileInfo.size(); |
602 | |
603 | if (uchar *mappedData = f.map(0, fileSize)) { |
604 | QString source = QString::fromUtf8(reinterpret_cast<const char *>(mappedData), fileSize); |
605 | f.unmap(mappedData); |
606 | return source; |
607 | } |
608 | |
609 | QByteArray data(fileSize, Qt::Uninitialized); |
610 | if (f.read(data.data(), data.length()) != data.length()) { |
611 | *error = f.errorString(); |
612 | return QString(); |
613 | } |
614 | return QString::fromUtf8(data); |
615 | } |
616 | |
617 | QDateTime QQmlDataBlob::SourceCodeData::sourceTimeStamp() const |
618 | { |
619 | if (hasInlineSourceCode) |
620 | return QDateTime(); |
621 | |
622 | return fileInfo.lastModified(); |
623 | } |
624 | |
625 | bool QQmlDataBlob::SourceCodeData::exists() const |
626 | { |
627 | if (hasInlineSourceCode) |
628 | return true; |
629 | return fileInfo.exists(); |
630 | } |
631 | |
632 | bool QQmlDataBlob::SourceCodeData::isEmpty() const |
633 | { |
634 | if (hasInlineSourceCode) |
635 | return inlineSourceCode.isEmpty(); |
636 | return fileInfo.size() == 0; |
637 | } |
638 | |
639 | QT_END_NAMESPACE |
640 | |