]> git.sur5r.net Git - minitube/blob - lib/js/js.cpp
New upstream version 3.8
[minitube] / lib / js / js.cpp
1 #include "js.h"
2
3 #include "cachedhttp.h"
4
5 namespace {
6 Http &cachedHttp() {
7     static Http *h = [] {
8         CachedHttp *cachedHttp = new CachedHttp(Http::instance(), "js");
9         cachedHttp->setMaxSeconds(3600);
10         // Avoid expiring the cached js
11         cachedHttp->setMaxSize(0);
12
13         cachedHttp->getValidators().insert("application/javascript", [](const auto &reply) -> bool {
14             return !reply.body().isEmpty();
15         });
16
17         return cachedHttp;
18     }();
19     return *h;
20 }
21 } // namespace
22
23 JS &JS::instance() {
24     static thread_local JS i;
25     return i;
26 }
27
28 JS::JS(QObject *parent) : QObject(parent), engine(nullptr) {}
29
30 void JS::initialize(const QUrl &url) {
31     this->url = url;
32     initialize();
33 }
34
35 bool JS::checkError(const QJSValue &value) {
36     if (value.isError()) {
37         qWarning() << "Error" << value.toString();
38         qDebug() << value.property("stack").toString().splitRef('\n');
39         return true;
40     }
41     return false;
42 }
43
44 bool JS::isInitialized() {
45     if (ready) return true;
46     initialize();
47     return false;
48 }
49
50 JSResult &JS::callFunction(JSResult *result, const QString &name, const QJSValueList &args) {
51     if (!isInitialized()) {
52         qDebug() << "Not initialized";
53         QTimer::singleShot(1000, this,
54                            [this, result, name, args] { callFunction(result, name, args); });
55         return *result;
56     }
57
58     auto function = engine->evaluate(name);
59     if (!function.isCallable()) {
60         qWarning() << function.toString() << " is not callable";
61         QTimer::singleShot(0, result, [result, function] { result->setError(function); });
62         return *result;
63     }
64
65     auto args2 = args;
66     args2.prepend(engine->newQObject(result));
67     qDebug() << "Calling" << function.toString();
68     auto v = function.call(args2);
69     if (checkError(v)) QTimer::singleShot(0, result, [result, v] { result->setError(v); });
70
71     return *result;
72 }
73
74 void JS::initialize() {
75     if (url.isEmpty()) {
76         qDebug() << "No js url set";
77         return;
78     }
79
80     if (initializing) return;
81     initializing = true;
82     qDebug() << "Initializing";
83
84     if (engine) engine->deleteLater();
85     engine = new QQmlEngine(this);
86     engine->setNetworkAccessManagerFactory(&namFactory);
87     engine->globalObject().setProperty("global", engine->globalObject());
88
89     QJSValue timer = engine->newQObject(new JSTimer(engine));
90     engine->globalObject().setProperty("setTimeoutQt", timer.property("setTimeout"));
91     QJSValue setTimeoutWrapperFunction =
92             engine->evaluate("function setTimeout(cb, delay) {"
93                              "const args = Array.prototype.slice.call(arguments, 2);"
94                              "return setTimeoutQt(cb, delay, args);"
95                              "}");
96     checkError(setTimeoutWrapperFunction);
97     engine->globalObject().setProperty("clearTimeout", timer.property("clearTimeout"));
98
99     connect(cachedHttp().get(url), &HttpReply::finished, this, [this](auto &reply) {
100         if (!reply.isSuccessful()) {
101             emit initFailed("Cannot load JS");
102             qDebug() << "Cannot load JS";
103             initializing = false;
104             return;
105         }
106         auto value = engine->evaluate(reply.body());
107         if (!checkError(value)) {
108             qDebug() << "Initialized";
109             ready = true;
110             emit initialized();
111         }
112         initializing = false;
113     });
114 }