History

CR feedback and code cleanup

humbletim committed 8 years ago

Commit 3a735c11 parent 395cc6614 files · +652 −365Browse files

Showing the changes to libraries/script-engine/src/AssetScriptingInterface.cpp (1 of 14 files). Show all

Unified · Split

libraries/script-engine/src/AssetScriptingInterface.cpp+175−127
@@ -18,27 +18,33 @@ · 17 unmodified lines
18#include <AssetRequest.h>18#include <AssetRequest.h>
19#include <AssetUpload.h>19#include <AssetUpload.h>
20#include <AssetUtils.h>20#include <AssetUtils.h>
21#include <BaseScriptEngine.h>
21#include <MappingRequest.h>22#include <MappingRequest.h>
22#include <NodeList.h>23#include <NodeList.h>
23 24
24#include <RegisteredMetaTypes.h>25#include <RegisteredMetaTypes.h>
25
26#include <shared/QtHelpers.h>
27#include "Gzip.h"
28#include "ScriptEngine.h"26#include "ScriptEngine.h"
29#include "ScriptEngineLogging.h"27#include "ScriptEngineLogging.h"
30 28
31AssetScriptingInterface::AssetScriptingInterface(QObject* parent) : BaseAssetScriptingInterface(parent) {}29#include <shared/QtHelpers.h>
30#include <Gzip.h>
31
32using Promise = MiniPromise::Promise;
33
34AssetScriptingInterface::AssetScriptingInterface(QObject* parent) : BaseAssetScriptingInterface(parent) {
35 qCDebug(scriptengine) << "AssetScriptingInterface::AssetScriptingInterface" << parent;
36 MiniPromise::registerMetaTypes(parent);
37}
32 38
33#define JS_VERIFY(cond, error) { if (!this->jsVerify(cond, error)) { return; } }39#define JS_VERIFY(cond, error) { if (!this->jsVerify(cond, error)) { return; } }
34 40
35void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) {41void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) {
36 auto handler = makeScopedHandlerObject(thisObject(), callback);42 auto handler = jsBindCallback(thisObject(), callback);
37 QByteArray dataByteArray = data.toUtf8();43 QByteArray dataByteArray = data.toUtf8();
38 auto upload = DependencyManager::get<AssetClient>()->createUpload(dataByteArray);44 auto upload = DependencyManager::get<AssetClient>()->createUpload(dataByteArray);
39 45
40 Promise deferred = makePromise(__FUNCTION__);46 Promise deferred = makePromise(__FUNCTION__);
41 deferred->ready([this, handler](QString error, QVariantMap result) {47 deferred->ready([=](QString error, QVariantMap result) {
42 auto url = result.value("url").toString();48 auto url = result.value("url").toString();
43 auto hash = result.value("hash").toString();49 auto hash = result.value("hash").toString();
44 jsCallback(handler, url, hash);50 jsCallback(handler, url, hash);
@@ -47,7 +53,7 @@ void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { · 2 unmodified lines
47 connect(upload, &AssetUpload::finished, upload, [this, deferred](AssetUpload* upload, const QString& hash) {53 connect(upload, &AssetUpload::finished, upload, [this, deferred](AssetUpload* upload, const QString& hash) {
48 // we are now on the "Resource Manager" thread (and "hash" being a *reference* makes it unsafe to use directly)54 // we are now on the "Resource Manager" thread (and "hash" being a *reference* makes it unsafe to use directly)
49 Q_ASSERT(QThread::currentThread() == upload->thread());55 Q_ASSERT(QThread::currentThread() == upload->thread());
50 deferred->resolve(NoError, {56 deferred->resolve({
51 { "url", "atp:" + hash },57 { "url", "atp:" + hash },
52 { "hash", hash },58 { "hash", hash },
53 });59 });
@@ -57,7 +63,7 @@ void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { · 3 unmodified lines
57}63}
58 64
59void AssetScriptingInterface::setMapping(QString path, QString hash, QScriptValue callback) {65void AssetScriptingInterface::setMapping(QString path, QString hash, QScriptValue callback) {
60 auto handler = makeScopedHandlerObject(thisObject(), callback);66 auto handler = jsBindCallback(thisObject(), callback);
61 auto setMappingRequest = assetClient()->createSetMappingRequest(path, hash);67 auto setMappingRequest = assetClient()->createSetMappingRequest(path, hash);
62 Promise deferred = makePromise(__FUNCTION__);68 Promise deferred = makePromise(__FUNCTION__);
63 deferred->ready([=](QString error, QVariantMap result) {69 deferred->ready([=](QString error, QVariantMap result) {
@@ -86,7 +92,7 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb · 22 unmodified lines
86 return;92 return;
87 }93 }
88 QString hash = AssetUtils::extractAssetHash(urlString);94 QString hash = AssetUtils::extractAssetHash(urlString);
89 auto handler = makeScopedHandlerObject(thisObject(), callback);95 auto handler = jsBindCallback(thisObject(), callback);
90 auto assetClient = DependencyManager::get<AssetClient>();96 auto assetClient = DependencyManager::get<AssetClient>();
91 auto assetRequest = assetClient->createRequest(hash);97 auto assetRequest = assetClient->createRequest(hash);
92 98
@@ -104,11 +110,11 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb · 11 unmodified lines
104 if (request->getError() == AssetRequest::Error::NoError) {110 if (request->getError() == AssetRequest::Error::NoError) {
105 QString data = QString::fromUtf8(request->getData());111 QString data = QString::fromUtf8(request->getData());
106 // forward a thread-safe values back to our thread112 // forward a thread-safe values back to our thread
107 deferred->resolve(NoError, { { "data", data } });113 deferred->resolve({ { "data", data } });
108 } else {114 } else {
109 // FIXME: propagate error to scripts? (requires changing signature or inverting param order above..)115 // FIXME: propagate error to scripts? (requires changing signature or inverting param order above..)
110 //deferred->resolve(request->getErrorString(), { { "error", requet->getError() } });116 //deferred->resolve(request->getErrorString(), { { "error", requet->getError() } });
111 qDebug() << "AssetScriptingInterface::downloadData ERROR: " << request->getErrorString();117 qCDebug(scriptengine) << "AssetScriptingInterface::downloadData ERROR: " << request->getErrorString();
112 }118 }
113 119
114 request->deleteLater();120 request->deleteLater();
@@ -118,13 +124,9 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb · 3 unmodified lines
118}124}
119 125
120void AssetScriptingInterface::setBakingEnabled(QString path, bool enabled, QScriptValue callback) {126void AssetScriptingInterface::setBakingEnabled(QString path, bool enabled, QScriptValue callback) {
121 auto handler = makeScopedHandlerObject(thisObject(), callback);
122 auto setBakingEnabledRequest = DependencyManager::get<AssetClient>()->createSetBakingEnabledRequest({ path }, enabled);127 auto setBakingEnabledRequest = DependencyManager::get<AssetClient>()->createSetBakingEnabledRequest({ path }, enabled);
123 128
124 Promise deferred = makePromise(__FUNCTION__);129 Promise deferred = jsPromiseReady(makePromise(__FUNCTION__), thisObject(), callback);
125 deferred->ready([=](QString error, QVariantMap result) {
126 jsCallback(handler, error, result);
127 });
128 130
129 connect(setBakingEnabledRequest, &SetBakingEnabledRequest::finished, setBakingEnabledRequest, [this, deferred](SetBakingEnabledRequest* request) {131 connect(setBakingEnabledRequest, &SetBakingEnabledRequest::finished, setBakingEnabledRequest, [this, deferred](SetBakingEnabledRequest* request) {
130 Q_ASSERT(QThread::currentThread() == request->thread());132 Q_ASSERT(QThread::currentThread() == request->thread());
@@ -150,13 +152,11 @@ void AssetScriptingInterface::sendFakedHandshake() { · 19 unmodified lines
150 152
151void AssetScriptingInterface::getMapping(QString asset, QScriptValue callback) {153void AssetScriptingInterface::getMapping(QString asset, QScriptValue callback) {
152 auto path = AssetUtils::getATPUrl(asset).path();154 auto path = AssetUtils::getATPUrl(asset).path();
153 auto handler = makeScopedHandlerObject(thisObject(), callback);155 auto handler = jsBindCallback(thisObject(), callback);
154 JS_VERIFY(AssetUtils::isValidFilePath(path), "invalid ATP file path: " + asset + "(path:"+path+")");156 JS_VERIFY(AssetUtils::isValidFilePath(path), "invalid ATP file path: " + asset + "(path:"+path+")");
155 JS_VERIFY(callback.isFunction(), "expected second parameter to be a callback function");157 JS_VERIFY(callback.isFunction(), "expected second parameter to be a callback function");
156 qDebug() << ">>getMapping//getAssetInfo" << path;
157 Promise promise = getAssetInfo(path);158 Promise promise = getAssetInfo(path);
158 promise->ready([this, handler](QString error, QVariantMap result) {159 promise->ready([=](QString error, QVariantMap result) {
159 qDebug() << "//getMapping//getAssetInfo" << error << result.keys();
160 jsCallback(handler, error, result.value("hash").toString());160 jsCallback(handler, error, result.value("hash").toString());
161 });161 });
162}162}
@@ -168,11 +168,31 @@ bool AssetScriptingInterface::jsVerify(bool condition, const QString& error) { · 5 unmodified lines
168 if (context()) {168 if (context()) {
169 context()->throwError(error);169 context()->throwError(error);
170 } else {170 } else {
171 qDebug() << "WARNING -- jsVerify failed outside of a valid JS context: " + error;171 qCDebug(scriptengine) << "WARNING -- jsVerify failed outside of a valid JS context: " + error;
172 }172 }
173 return false;173 return false;
174}174}
175 175
176QScriptValue AssetScriptingInterface::jsBindCallback(QScriptValue scope, QScriptValue callback) {
177 QScriptValue handler = ::makeScopedHandlerObject(scope, callback);
178 QScriptValue value = handler.property("callback");
179 if (!jsVerify(handler.isObject() && value.isFunction(),
180 QString("jsBindCallback -- .callback is not a function (%1)").arg(value.toVariant().typeName()))) {
181 return QScriptValue();
182 }
183 return handler;
184}
185
186Promise AssetScriptingInterface::jsPromiseReady(Promise promise, QScriptValue scope, QScriptValue callback) {
187 auto handler = jsBindCallback(scope, callback);
188 if (!jsVerify(handler.isValid(), "jsPromiseReady -- invalid callback handler")) {
189 return nullptr;
190 }
191 return promise->ready([this, handler](QString error, QVariantMap result) {
192 jsCallback(handler, error, result);
193 });
194}
195
176void AssetScriptingInterface::jsCallback(const QScriptValue& handler,196void AssetScriptingInterface::jsCallback(const QScriptValue& handler,
177 const QScriptValue& error, const QScriptValue& result) {197 const QScriptValue& error, const QScriptValue& result) {
178 Q_ASSERT(thread() == QThread::currentThread());198 Q_ASSERT(thread() == QThread::currentThread());
@@ -208,46 +228,36 @@ void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, · 29 unmodified lines
208 responseType = "text";228 responseType = "text";
209 }229 }
210 auto asset = AssetUtils::getATPUrl(url).path();230 auto asset = AssetUtils::getATPUrl(url).path();
211 auto handler = makeScopedHandlerObject(scope, callback);
212
213 JS_VERIFY(handler.property("callback").isFunction(),
214 QString("Invalid callback function (%1)").arg(handler.property("callback").toVariant().typeName()));
215 JS_VERIFY(AssetUtils::isValidHash(asset) || AssetUtils::isValidFilePath(asset),231 JS_VERIFY(AssetUtils::isValidHash(asset) || AssetUtils::isValidFilePath(asset),
216 QString("Invalid ATP url '%1'").arg(url));232 QString("Invalid ATP url '%1'").arg(url));
217 JS_VERIFY(RESPONSE_TYPES.contains(responseType),233 JS_VERIFY(RESPONSE_TYPES.contains(responseType),
218 QString("Invalid responseType: '%1' (expected: %2)").arg(responseType).arg(RESPONSE_TYPES.join(" | ")));234 QString("Invalid responseType: '%1' (expected: %2)").arg(responseType).arg(RESPONSE_TYPES.join(" | ")));
219 235
220 Promise resolved = makePromise("resolved");236 Promise fetched = jsPromiseReady(makePromise("fetched"), scope, callback);
221 Promise loaded = makePromise("loaded");237 Promise mapped = makePromise("mapped");
222 238
223 loaded->ready([=](QString error, QVariantMap result) {239 mapped->ready([=](QString error, QVariantMap result) {
224 qDebug() << "//loaded" << error;
225 jsCallback(handler, error, result);
226 });
227
228 resolved->ready([=](QString error, QVariantMap result) {
229 qDebug() << "//resolved" << result.value("hash");
230 QString hash = result.value("hash").toString();240 QString hash = result.value("hash").toString();
241 QString url = result.value("url").toString();
231 if (!error.isEmpty() || !AssetUtils::isValidHash(hash)) {242 if (!error.isEmpty() || !AssetUtils::isValidHash(hash)) {
232 loaded->reject(error.isEmpty() ? "internal hash error: " + hash : error, result);243 fetched->reject(error.isEmpty() ? "internal hash error: " + hash : error, result);
233 } else {244 } else {
234 Promise promise = loadAsset(hash, decompress, responseType);245 Promise promise = loadAsset(hash, decompress, responseType);
235 promise->mixin(result);246 promise->mixin(result);
236 promise->ready([this, loaded, hash](QString error, QVariantMap result) {247 promise->ready([=](QString error, QVariantMap loadResult) {
237 qDebug() << "//getAssetInfo/loadAsset" << error << hash;248 loadResult["url"] = url; // maintain mapped .url in results (vs. atp:hash returned by loadAsset)
238 loaded->resolve(NoError, result);249 fetched->handle(error, loadResult);
239 });250 });
240 }251 }
241 });252 });
242 253
243 if (AssetUtils::isValidHash(asset)) {254 if (AssetUtils::isValidHash(asset)) {
244 resolved->resolve(NoError, { { "hash", asset } });255 mapped->resolve({
245 } else {256 { "hash", asset },
246 Promise promise = getAssetInfo(asset);257 { "url", url },
247 promise->ready([this, resolved](QString error, QVariantMap result) {
248 qDebug() << "//getAssetInfo" << error << result.value("hash") << result.value("path");
249 resolved->resolve(error, result);
250 });258 });
259 } else {
260 getAssetInfo(asset)->ready(mapped);
251 }261 }
252}262}
253 263
@@ -256,128 +266,166 @@ void AssetScriptingInterface::resolveAsset(QScriptValue options, QScriptValue sc · 2 unmodified lines
256 266
257 auto url = (options.isString() ? options : options.property(URL)).toString();267 auto url = (options.isString() ? options : options.property(URL)).toString();
258 auto asset = AssetUtils::getATPUrl(url).path();268 auto asset = AssetUtils::getATPUrl(url).path();
259 auto handler = makeScopedHandlerObject(scope, callback);
260 269
261 JS_VERIFY(AssetUtils::isValidFilePath(asset) || AssetUtils::isValidHash(asset),270 JS_VERIFY(AssetUtils::isValidFilePath(asset) || AssetUtils::isValidHash(asset),
262 "expected options to be an asset URL or request options containing .url property");271 "expected options to be an asset URL or request options containing .url property");
263 JS_VERIFY(handler.property("callback").isFunction(), "invalid callback function");272
264 getAssetInfo(asset)->ready([=](QString error, QVariantMap result) {273 jsPromiseReady(getAssetInfo(asset), scope, callback);
265 qDebug() << "//resolveAsset/getAssetInfo" << error << result.value("hash");
266 jsCallback(handler, error, result);
267 });
268}274}
269 275
270void AssetScriptingInterface::decompressData(QScriptValue options, QScriptValue scope, QScriptValue callback) {276void AssetScriptingInterface::decompressData(QScriptValue options, QScriptValue scope, QScriptValue callback) {
271 auto data = options.property("data");277 auto data = options.property("data");
272 QByteArray dataByteArray = qscriptvalue_cast<QByteArray>(data);278 QByteArray dataByteArray = qscriptvalue_cast<QByteArray>(data);
273 auto handler = makeScopedHandlerObject(scope, callback);
274 auto responseType = options.property("responseType").toString().toLower();279 auto responseType = options.property("responseType").toString().toLower();
275 if (responseType.isEmpty()) {280 if (responseType.isEmpty()) {
276 responseType = "text";281 responseType = "text";
277 }282 }
278 Promise promise = decompressBytes(dataByteArray);283 Promise completed = jsPromiseReady(makePromise(__FUNCTION__), scope, callback);
279 promise->ready([=](QString error, QVariantMap result) {284 Promise decompressed = decompressBytes(dataByteArray);
280 if (responseType == "arraybuffer") {285 if (responseType == "arraybuffer") {
281 jsCallback(handler, error, result);286 decompressed->ready(completed);
282 } else {287 } else {
283 Promise promise = convertBytes(result.value("data").toByteArray(), responseType);288 decompressed->ready([=](QString error, QVariantMap result) {
284 promise->mixin(result);289 Promise converted = convertBytes(result.value("data").toByteArray(), responseType);
285 promise->ready([=](QString error, QVariantMap result) {290 converted->mixin(result);
286 jsCallback(handler, error, result);291 converted->ready(completed);
287 });292 });
288 }293 }
289 });
290}294}
291 295
292namespace {296namespace {
293 const int32_t DEFAULT_GZIP_COMPRESSION_LEVEL = -1;297 const int32_t DEFAULT_GZIP_COMPRESSION_LEVEL = -1;
294 const int32_t MAX_GZIP_COMPRESSION_LEVEL = 9;298 const int32_t MAX_GZIP_COMPRESSION_LEVEL = 9;
295}299}
296
297void AssetScriptingInterface::compressData(QScriptValue options, QScriptValue scope, QScriptValue callback) {300void AssetScriptingInterface::compressData(QScriptValue options, QScriptValue scope, QScriptValue callback) {
298 301 auto data = options.property("data").isValid() ? options.property("data") : options;
299 auto data = options.property("data");302 QByteArray dataByteArray = data.isString() ? data.toString().toUtf8() : qscriptvalue_cast<QByteArray>(data);
300 QByteArray dataByteArray = data.isString() ?303 int level = options.property("level").isNumber() ? options.property("level").toInt32() : DEFAULT_GZIP_COMPRESSION_LEVEL;
301 data.toString().toUtf8() :304 JS_VERIFY(level >= DEFAULT_GZIP_COMPRESSION_LEVEL || level <= MAX_GZIP_COMPRESSION_LEVEL, QString("invalid .level %1").arg(level));
302 qscriptvalue_cast<QByteArray>(data);305 jsPromiseReady(compressBytes(dataByteArray, level), scope, callback);
303 auto handler = makeScopedHandlerObject(scope, callback);
304 auto level = options.property("level").toInt32();
305 if (level < DEFAULT_GZIP_COMPRESSION_LEVEL || level > MAX_GZIP_COMPRESSION_LEVEL) {
306 level = DEFAULT_GZIP_COMPRESSION_LEVEL;
307 }
308 Promise promise = compressBytes(dataByteArray, level);
309 promise->ready([=](QString error, QVariantMap result) {
310 jsCallback(handler, error, result);
311 });
312}306}
313 307
314void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) {308void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) {
315 auto compress = options.property("compress").toBool() ||309 auto compress = options.property("compress").toBool() || options.property("compressed").toBool();
316 options.property("compressed").toBool();310 auto data = options.isObject() ? options.property("data") : options;
317 auto handler = makeScopedHandlerObject(scope, callback);
318 auto data = options.property("data");
319 auto rawPath = options.property("path").toString();311 auto rawPath = options.property("path").toString();
320 auto path = AssetUtils::getATPUrl(rawPath).path();312 auto path = AssetUtils::getATPUrl(rawPath).path();
321 313
322 QByteArray dataByteArray = data.isString() ?314 QByteArray dataByteArray = data.isString() ? data.toString().toUtf8() : qscriptvalue_cast<QByteArray>(data);
323 data.toString().toUtf8() :
324 qscriptvalue_cast<QByteArray>(data);
325 315
326 JS_VERIFY(path.isEmpty() || AssetUtils::isValidFilePath(path),316 JS_VERIFY(path.isEmpty() || AssetUtils::isValidFilePath(path),
327 QString("expected valid ATP file path '%1' ('%2')").arg(rawPath).arg(path));317 QString("expected valid ATP file path '%1' ('%2')").arg(rawPath).arg(path));
328 JS_VERIFY(handler.property("callback").isFunction(),
329 "invalid callback function");
330 JS_VERIFY(dataByteArray.size() > 0,318 JS_VERIFY(dataByteArray.size() > 0,
331 QString("expected non-zero .data (got %1 / #%2 bytes)")319 QString("expected non-zero .data (got %1 / #%2 bytes)").arg(data.toVariant().typeName()).arg(dataByteArray.size()));
332 .arg(data.toVariant().typeName())
333 .arg(dataByteArray.size()));
334 320
335 // [compressed] => uploaded to server => [mapped to path]321 // [compressed] => uploaded to server => [mapped to path]
336 Promise prepared = makePromise("putAsset::prepared");322 Promise prepared = makePromise("putAsset::prepared");
337 Promise uploaded = makePromise("putAsset::uploaded");323 Promise uploaded = makePromise("putAsset::uploaded");
338 Promise finished = makePromise("putAsset::finished");324 Promise completed = makePromise("putAsset::completed");
325 jsPromiseReady(completed, scope, callback);
339 326
340 if (compress) {327 if (compress) {
341 qDebug() << "putAsset::compressBytes...";328 Promise compress = compressBytes(dataByteArray, DEFAULT_GZIP_COMPRESSION_LEVEL);
342 Promise promise = compressBytes(dataByteArray, DEFAULT_GZIP_COMPRESSION_LEVEL);329 compress->ready(prepared);
343 promise->finally([=](QString error, QVariantMap result) {
344 qDebug() << "//putAsset::compressedBytes" << error << result.keys();
345 prepared->handle(error, result);
346 });
347 } else {330 } else {
348 prepared->resolve(NoError, {{ "data", dataByteArray }});331 prepared->resolve({{ "data", dataByteArray }});
349 }332 }
350 333
351 prepared->ready([=](QString error, QVariantMap result) {334 prepared->fail(completed);
352 qDebug() << "//putAsset::prepared" << error << result.value("data").toByteArray().size() << result.keys();335 prepared->then([=](QVariantMap result) {
353 Promise promise = uploadBytes(result.value("data").toByteArray());336 Promise upload = uploadBytes(result.value("data").toByteArray());
354 promise->mixin(result);337 upload->mixin(result);
355 promise->ready([=](QString error, QVariantMap result) {338 upload->ready(uploaded);
356 qDebug() << "===========//putAsset::prepared/uploadBytes" << error << result.keys();
357 uploaded->handle(error, result);
358 });
359 });339 });
360 340
361 uploaded->ready([=](QString error, QVariantMap result) {341 uploaded->fail(completed);
362 QString hash = result.value("hash").toString();342 if (path.isEmpty()) {
363 qDebug() << "//putAsset::uploaded" << error << hash << result.keys();343 uploaded->then(completed);
364 if (path.isEmpty()) {344 } else {
365 finished->handle(error, result);345 uploaded->then([=](QVariantMap result) {
366 } else if (!AssetUtils::isValidHash(hash)) {346 QString hash = result.value("hash").toString();
367 finished->reject("path mapping requested, but did not receive valid hash", result);347 if (!AssetUtils::isValidHash(hash)) {
368 } else {348 completed->reject("path mapping requested, but did not receive valid hash", result);
369 qDebug() << "symlinkAsset" << hash << path << QThread::currentThread();349 } else {
370 Promise promise = symlinkAsset(hash, path);350 Promise link = symlinkAsset(hash, path);
371 promise->mixin(result);351 link->mixin(result);
372 promise->ready([=](QString error, QVariantMap result) {352 link->ready(completed);
373 finished->handle(error, result);353 }
374 qDebug() << "//symlinkAsset" << hash << path << result.keys();354 });
375 });355 }
376 }356}
377 });
378 357
379 finished->ready([=](QString error, QVariantMap result) {358void AssetScriptingInterface::queryCacheMeta(QScriptValue options, QScriptValue scope, QScriptValue callback) {
380 qDebug() << "//putAsset::finished" << error << result.keys();359 QString url = options.isString() ? options.toString() : options.property("url").toString();
381 jsCallback(handler, error, result);360 JS_VERIFY(QUrl(url).isValid(), QString("Invalid URL '%1'").arg(url));
382 });361 jsPromiseReady(Parent::queryCacheMeta(url), scope, callback);
362}
363
364void AssetScriptingInterface::loadFromCache(QScriptValue options, QScriptValue scope, QScriptValue callback) {
365 QString url, responseType;
366 bool decompress = false;
367 if (options.isString()) {
368 url = options.toString();
369 responseType = "text";
370 } else {
371 url = options.property("url").toString();
372 responseType = options.property("responseType").isValid() ? options.property("responseType").toString() : "text";
373 decompress = options.property("decompress").toBool() || options.property("compressed").toBool();
374 }
375 JS_VERIFY(QUrl(url).isValid(), QString("Invalid URL '%1'").arg(url));
376 JS_VERIFY(RESPONSE_TYPES.contains(responseType),
377 QString("Invalid responseType: '%1' (expected: %2)").arg(responseType).arg(RESPONSE_TYPES.join(" | ")));
378
379 jsPromiseReady(Parent::loadFromCache(url, decompress, responseType), scope, callback);
380}
381
382bool AssetScriptingInterface::canWriteCacheValue(const QUrl& url) {
383 auto scriptEngine = qobject_cast<ScriptEngine*>(engine());
384 if (!scriptEngine) {
385 qCDebug(scriptengine) << __FUNCTION__ << "invalid script engine" << url;
386 return false;
387 }
388 // allow cache writes only from Client, EntityServer and Agent scripts
389 bool isAllowedContext = (
390 scriptEngine->isClientScript() ||
391 scriptEngine->isAgentScript() ||
392 scriptEngine->isEntityServerScript()
393 );
394 if (!isAllowedContext) {
395 qCDebug(scriptengine) << __FUNCTION__ << "invalid context" << scriptEngine->getContext() << url;
396 return false;
397 }
398 return true;
399}
400
401void AssetScriptingInterface::saveToCache(QScriptValue options, QScriptValue scope, QScriptValue callback) {
402 JS_VERIFY(options.isObject(), QString("expected options object as first parameter not: %1").arg(options.toVariant().typeName()));
403
404 QString url = options.property("url").toString();
405 QByteArray data = qscriptvalue_cast<QByteArray>(options.property("data"));
406 QVariantMap headers = qscriptvalue_cast<QVariantMap>(options.property("headers"));
407
408 saveToCache(url, data, headers, scope, callback);
409}
410
411void AssetScriptingInterface::saveToCache(const QUrl& rawURL, const QByteArray& data, const QVariantMap& metadata, QScriptValue scope, QScriptValue callback) {
412 QUrl url = rawURL;
413 if (url.path().isEmpty() && !data.isEmpty()) {
414 // generate a valid ATP URL from the data -- appending any existing fragment or querystring values
415 auto atpURL = AssetUtils::getATPUrl(hashDataHex(data));
416 atpURL.setQuery(url.query());
417 atpURL.setFragment(url.fragment());
418 qCDebug(scriptengine) << "autogenerated ATP URL" << url << "=>" << atpURL;
419 url = atpURL;
420 }
421 auto hash = AssetUtils::extractAssetHash(url.toDisplayString());
422
423 JS_VERIFY(url.isValid(), QString("Invalid URL '%1'").arg(url.toString()));
424 JS_VERIFY(canWriteCacheValue(url), "Invalid cache write URL: " + url.toString());
425 JS_VERIFY(url.scheme() == "atp" || url.scheme() == "cache", "only 'atp' and 'cache' URL schemes supported");
426 JS_VERIFY(hash.isEmpty() || hash == hashDataHex(data), QString("invalid checksum hash for atp:HASH style URL (%1 != %2)").arg(hash, hashDataHex(data)));
427
428 qCDebug(scriptengine) << "saveToCache" << url.toDisplayString() << data << hash << metadata;
429
430 jsPromiseReady(Parent::saveToCache(url, data, metadata), scope, callback);
383}431}