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
1818 #include <AssetRequest.h>
1919 #include <AssetUpload.h>
2020 #include <AssetUtils.h>
21+#include <BaseScriptEngine.h>
2122 #include <MappingRequest.h>
2223 #include <NodeList.h>
2324
2425 #include <RegisteredMetaTypes.h>
25−
26−#include <shared/QtHelpers.h>
27−#include "Gzip.h"
2826 #include "ScriptEngine.h"
2927 #include "ScriptEngineLogging.h"
3028
31−AssetScriptingInterface::AssetScriptingInterface(QObject* parent) : BaseAssetScriptingInterface(parent) {}
29+#include <shared/QtHelpers.h>
30+#include <Gzip.h>
31+
32+using Promise = MiniPromise::Promise;
33+
34+AssetScriptingInterface::AssetScriptingInterface(QObject* parent) : BaseAssetScriptingInterface(parent) {
35+ qCDebug(scriptengine) << "AssetScriptingInterface::AssetScriptingInterface" << parent;
36+ MiniPromise::registerMetaTypes(parent);
37+}
3238
3339 #define JS_VERIFY(cond, error) { if (!this->jsVerify(cond, error)) { return; } }
3440
3541 void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) {
36− auto handler = makeScopedHandlerObject(thisObject(), callback);
42+ auto handler = jsBindCallback(thisObject(), callback);
3743 QByteArray dataByteArray = data.toUtf8();
3844 auto upload = DependencyManager::get<AssetClient>()->createUpload(dataByteArray);
3945
4046 Promise deferred = makePromise(__FUNCTION__);
41− deferred->ready([this, handler](QString error, QVariantMap result) {
47+ deferred->ready([=](QString error, QVariantMap result) {
4248 auto url = result.value("url").toString();
4349 auto hash = result.value("hash").toString();
4450 jsCallback(handler, url, hash);
@@ -47,7 +53,7 @@ void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { · 2 unmodified lines
4753 connect(upload, &AssetUpload::finished, upload, [this, deferred](AssetUpload* upload, const QString& hash) {
4854 // we are now on the "Resource Manager" thread (and "hash" being a *reference* makes it unsafe to use directly)
4955 Q_ASSERT(QThread::currentThread() == upload->thread());
50− deferred->resolve(NoError, {
56+ deferred->resolve({
5157 { "url", "atp:" + hash },
5258 { "hash", hash },
5359 });
@@ -57,7 +63,7 @@ void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { · 3 unmodified lines
5763 }
5864
5965 void AssetScriptingInterface::setMapping(QString path, QString hash, QScriptValue callback) {
60− auto handler = makeScopedHandlerObject(thisObject(), callback);
66+ auto handler = jsBindCallback(thisObject(), callback);
6167 auto setMappingRequest = assetClient()->createSetMappingRequest(path, hash);
6268 Promise deferred = makePromise(__FUNCTION__);
6369 deferred->ready([=](QString error, QVariantMap result) {
@@ -86,7 +92,7 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb · 22 unmodified lines
8692 return;
8793 }
8894 QString hash = AssetUtils::extractAssetHash(urlString);
89− auto handler = makeScopedHandlerObject(thisObject(), callback);
95+ auto handler = jsBindCallback(thisObject(), callback);
9096 auto assetClient = DependencyManager::get<AssetClient>();
9197 auto assetRequest = assetClient->createRequest(hash);
9298
@@ -104,11 +110,11 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb · 11 unmodified lines
104110 if (request->getError() == AssetRequest::Error::NoError) {
105111 QString data = QString::fromUtf8(request->getData());
106112 // forward a thread-safe values back to our thread
107− deferred->resolve(NoError, { { "data", data } });
113+ deferred->resolve({ { "data", data } });
108114 } else {
109115 // FIXME: propagate error to scripts? (requires changing signature or inverting param order above..)
110116 //deferred->resolve(request->getErrorString(), { { "error", requet->getError() } });
111− qDebug() << "AssetScriptingInterface::downloadData ERROR: " << request->getErrorString();
117+ qCDebug(scriptengine) << "AssetScriptingInterface::downloadData ERROR: " << request->getErrorString();
112118 }
113119
114120 request->deleteLater();
@@ -118,13 +124,9 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb · 3 unmodified lines
118124 }
119125
120126 void AssetScriptingInterface::setBakingEnabled(QString path, bool enabled, QScriptValue callback) {
121− auto handler = makeScopedHandlerObject(thisObject(), callback);
122127 auto setBakingEnabledRequest = DependencyManager::get<AssetClient>()->createSetBakingEnabledRequest({ path }, enabled);
123128
124− Promise deferred = makePromise(__FUNCTION__);
125− deferred->ready([=](QString error, QVariantMap result) {
126− jsCallback(handler, error, result);
127− });
129+ Promise deferred = jsPromiseReady(makePromise(__FUNCTION__), thisObject(), callback);
128130
129131 connect(setBakingEnabledRequest, &SetBakingEnabledRequest::finished, setBakingEnabledRequest, [this, deferred](SetBakingEnabledRequest* request) {
130132 Q_ASSERT(QThread::currentThread() == request->thread());
@@ -150,13 +152,11 @@ void AssetScriptingInterface::sendFakedHandshake() { · 19 unmodified lines
150152
151153 void AssetScriptingInterface::getMapping(QString asset, QScriptValue callback) {
152154 auto path = AssetUtils::getATPUrl(asset).path();
153− auto handler = makeScopedHandlerObject(thisObject(), callback);
155+ auto handler = jsBindCallback(thisObject(), callback);
154156 JS_VERIFY(AssetUtils::isValidFilePath(path), "invalid ATP file path: " + asset + "(path:"+path+")");
155157 JS_VERIFY(callback.isFunction(), "expected second parameter to be a callback function");
156− qDebug() << ">>getMapping//getAssetInfo" << path;
157158 Promise promise = getAssetInfo(path);
158− promise->ready([this, handler](QString error, QVariantMap result) {
159− qDebug() << "//getMapping//getAssetInfo" << error << result.keys();
159+ promise->ready([=](QString error, QVariantMap result) {
160160 jsCallback(handler, error, result.value("hash").toString());
161161 });
162162 }
@@ -168,11 +168,31 @@ bool AssetScriptingInterface::jsVerify(bool condition, const QString& error) { · 5 unmodified lines
168168 if (context()) {
169169 context()->throwError(error);
170170 } 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;
172172 }
173173 return false;
174174 }
175175
176+QScriptValue 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+
186+Promise 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+
176196 void AssetScriptingInterface::jsCallback(const QScriptValue& handler,
177197 const QScriptValue& error, const QScriptValue& result) {
178198 Q_ASSERT(thread() == QThread::currentThread());
@@ -208,46 +228,36 @@ void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, · 29 unmodified lines
208228 responseType = "text";
209229 }
210230 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()));
215231 JS_VERIFY(AssetUtils::isValidHash(asset) || AssetUtils::isValidFilePath(asset),
216232 QString("Invalid ATP url '%1'").arg(url));
217233 JS_VERIFY(RESPONSE_TYPES.contains(responseType),
218234 QString("Invalid responseType: '%1' (expected: %2)").arg(responseType).arg(RESPONSE_TYPES.join(" | ")));
219235
220− Promise resolved = makePromise("resolved");
221− Promise loaded = makePromise("loaded");
236+ Promise fetched = jsPromiseReady(makePromise("fetched"), scope, callback);
237+ Promise mapped = makePromise("mapped");
222238
223− loaded->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");
239+ mapped->ready([=](QString error, QVariantMap result) {
230240 QString hash = result.value("hash").toString();
241+ QString url = result.value("url").toString();
231242 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);
233244 } else {
234245 Promise promise = loadAsset(hash, decompress, responseType);
235246 promise->mixin(result);
236− promise->ready([this, loaded, hash](QString error, QVariantMap result) {
237− qDebug() << "//getAssetInfo/loadAsset" << error << hash;
238− loaded->resolve(NoError, result);
247+ promise->ready([=](QString error, QVariantMap loadResult) {
248+ loadResult["url"] = url; // maintain mapped .url in results (vs. atp:hash returned by loadAsset)
249+ fetched->handle(error, loadResult);
239250 });
240251 }
241252 });
242253
243254 if (AssetUtils::isValidHash(asset)) {
244− resolved->resolve(NoError, { { "hash", asset } });
245− } else {
246− Promise promise = getAssetInfo(asset);
247− promise->ready([this, resolved](QString error, QVariantMap result) {
248− qDebug() << "//getAssetInfo" << error << result.value("hash") << result.value("path");
249− resolved->resolve(error, result);
255+ mapped->resolve({
256+ { "hash", asset },
257+ { "url", url },
250258 });
259+ } else {
260+ getAssetInfo(asset)->ready(mapped);
251261 }
252262 }
253263
@@ -256,128 +266,166 @@ void AssetScriptingInterface::resolveAsset(QScriptValue options, QScriptValue sc · 2 unmodified lines
256266
257267 auto url = (options.isString() ? options : options.property(URL)).toString();
258268 auto asset = AssetUtils::getATPUrl(url).path();
259− auto handler = makeScopedHandlerObject(scope, callback);
260269
261270 JS_VERIFY(AssetUtils::isValidFilePath(asset) || AssetUtils::isValidHash(asset),
262271 "expected options to be an asset URL or request options containing .url property");
263− JS_VERIFY(handler.property("callback").isFunction(), "invalid callback function");
264− getAssetInfo(asset)->ready([=](QString error, QVariantMap result) {
265− qDebug() << "//resolveAsset/getAssetInfo" << error << result.value("hash");
266− jsCallback(handler, error, result);
267− });
272+
273+ jsPromiseReady(getAssetInfo(asset), scope, callback);
268274 }
269275
270276 void AssetScriptingInterface::decompressData(QScriptValue options, QScriptValue scope, QScriptValue callback) {
271277 auto data = options.property("data");
272278 QByteArray dataByteArray = qscriptvalue_cast<QByteArray>(data);
273− auto handler = makeScopedHandlerObject(scope, callback);
274279 auto responseType = options.property("responseType").toString().toLower();
275280 if (responseType.isEmpty()) {
276281 responseType = "text";
277282 }
278− Promise promise = decompressBytes(dataByteArray);
279− promise->ready([=](QString error, QVariantMap result) {
280− if (responseType == "arraybuffer") {
281− jsCallback(handler, error, result);
282− } else {
283− Promise promise = convertBytes(result.value("data").toByteArray(), responseType);
284− promise->mixin(result);
285− promise->ready([=](QString error, QVariantMap result) {
286− jsCallback(handler, error, result);
287− });
288− }
289− });
283+ Promise completed = jsPromiseReady(makePromise(__FUNCTION__), scope, callback);
284+ Promise decompressed = decompressBytes(dataByteArray);
285+ if (responseType == "arraybuffer") {
286+ decompressed->ready(completed);
287+ } else {
288+ decompressed->ready([=](QString error, QVariantMap result) {
289+ Promise converted = convertBytes(result.value("data").toByteArray(), responseType);
290+ converted->mixin(result);
291+ converted->ready(completed);
292+ });
293+ }
290294 }
291295
292296 namespace {
293297 const int32_t DEFAULT_GZIP_COMPRESSION_LEVEL = -1;
294298 const int32_t MAX_GZIP_COMPRESSION_LEVEL = 9;
295299 }
296−
297300 void AssetScriptingInterface::compressData(QScriptValue options, QScriptValue scope, QScriptValue callback) {
298−
299− auto data = options.property("data");
300− QByteArray dataByteArray = data.isString() ?
301− data.toString().toUtf8() :
302− qscriptvalue_cast<QByteArray>(data);
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− });
301+ auto data = options.property("data").isValid() ? options.property("data") : options;
302+ QByteArray dataByteArray = data.isString() ? data.toString().toUtf8() : qscriptvalue_cast<QByteArray>(data);
303+ int level = options.property("level").isNumber() ? options.property("level").toInt32() : DEFAULT_GZIP_COMPRESSION_LEVEL;
304+ JS_VERIFY(level >= DEFAULT_GZIP_COMPRESSION_LEVEL || level <= MAX_GZIP_COMPRESSION_LEVEL, QString("invalid .level %1").arg(level));
305+ jsPromiseReady(compressBytes(dataByteArray, level), scope, callback);
312306 }
313307
314308 void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) {
315− auto compress = options.property("compress").toBool() ||
316− options.property("compressed").toBool();
317− auto handler = makeScopedHandlerObject(scope, callback);
318− auto data = options.property("data");
309+ auto compress = options.property("compress").toBool() || options.property("compressed").toBool();
310+ auto data = options.isObject() ? options.property("data") : options;
319311 auto rawPath = options.property("path").toString();
320312 auto path = AssetUtils::getATPUrl(rawPath).path();
321313
322− QByteArray dataByteArray = data.isString() ?
323− data.toString().toUtf8() :
324− qscriptvalue_cast<QByteArray>(data);
314+ QByteArray dataByteArray = data.isString() ? data.toString().toUtf8() : qscriptvalue_cast<QByteArray>(data);
325315
326316 JS_VERIFY(path.isEmpty() || AssetUtils::isValidFilePath(path),
327317 QString("expected valid ATP file path '%1' ('%2')").arg(rawPath).arg(path));
328− JS_VERIFY(handler.property("callback").isFunction(),
329− "invalid callback function");
330318 JS_VERIFY(dataByteArray.size() > 0,
331− QString("expected non-zero .data (got %1 / #%2 bytes)")
332− .arg(data.toVariant().typeName())
333− .arg(dataByteArray.size()));
319+ QString("expected non-zero .data (got %1 / #%2 bytes)").arg(data.toVariant().typeName()).arg(dataByteArray.size()));
334320
335321 // [compressed] => uploaded to server => [mapped to path]
336322 Promise prepared = makePromise("putAsset::prepared");
337323 Promise uploaded = makePromise("putAsset::uploaded");
338− Promise finished = makePromise("putAsset::finished");
324+ Promise completed = makePromise("putAsset::completed");
325+ jsPromiseReady(completed, scope, callback);
339326
340327 if (compress) {
341− qDebug() << "putAsset::compressBytes...";
342− Promise promise = compressBytes(dataByteArray, DEFAULT_GZIP_COMPRESSION_LEVEL);
343− promise->finally([=](QString error, QVariantMap result) {
344− qDebug() << "//putAsset::compressedBytes" << error << result.keys();
345− prepared->handle(error, result);
346− });
328+ Promise compress = compressBytes(dataByteArray, DEFAULT_GZIP_COMPRESSION_LEVEL);
329+ compress->ready(prepared);
347330 } else {
348− prepared->resolve(NoError, {{ "data", dataByteArray }});
331+ prepared->resolve({{ "data", dataByteArray }});
349332 }
350333
351− prepared->ready([=](QString error, QVariantMap result) {
352− qDebug() << "//putAsset::prepared" << error << result.value("data").toByteArray().size() << result.keys();
353− Promise promise = uploadBytes(result.value("data").toByteArray());
354− promise->mixin(result);
355− promise->ready([=](QString error, QVariantMap result) {
356− qDebug() << "===========//putAsset::prepared/uploadBytes" << error << result.keys();
357− uploaded->handle(error, result);
358− });
334+ prepared->fail(completed);
335+ prepared->then([=](QVariantMap result) {
336+ Promise upload = uploadBytes(result.value("data").toByteArray());
337+ upload->mixin(result);
338+ upload->ready(uploaded);
359339 });
360340
361− uploaded->ready([=](QString error, QVariantMap result) {
362− QString hash = result.value("hash").toString();
363− qDebug() << "//putAsset::uploaded" << error << hash << result.keys();
364− if (path.isEmpty()) {
365− finished->handle(error, result);
366− } else if (!AssetUtils::isValidHash(hash)) {
367− finished->reject("path mapping requested, but did not receive valid hash", result);
368− } else {
369− qDebug() << "symlinkAsset" << hash << path << QThread::currentThread();
370− Promise promise = symlinkAsset(hash, path);
371− promise->mixin(result);
372− promise->ready([=](QString error, QVariantMap result) {
373− finished->handle(error, result);
374− qDebug() << "//symlinkAsset" << hash << path << result.keys();
375− });
376− }
377− });
341+ uploaded->fail(completed);
342+ if (path.isEmpty()) {
343+ uploaded->then(completed);
344+ } else {
345+ uploaded->then([=](QVariantMap result) {
346+ QString hash = result.value("hash").toString();
347+ if (!AssetUtils::isValidHash(hash)) {
348+ completed->reject("path mapping requested, but did not receive valid hash", result);
349+ } else {
350+ Promise link = symlinkAsset(hash, path);
351+ link->mixin(result);
352+ link->ready(completed);
353+ }
354+ });
355+ }
356+}
378357
379− finished->ready([=](QString error, QVariantMap result) {
380− qDebug() << "//putAsset::finished" << error << result.keys();
381− jsCallback(handler, error, result);
382− });
358+void AssetScriptingInterface::queryCacheMeta(QScriptValue options, QScriptValue scope, QScriptValue callback) {
359+ QString url = options.isString() ? options.toString() : options.property("url").toString();
360+ JS_VERIFY(QUrl(url).isValid(), QString("Invalid URL '%1'").arg(url));
361+ jsPromiseReady(Parent::queryCacheMeta(url), scope, callback);
362+}
363+
364+void 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+
382+bool 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+
401+void 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+
411+void 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);
383431 }