1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-08-05 21:57:41 +02:00

Implement Tomahawk.ajax native implementation on behalf of NativeScriptJobs

This commit is contained in:
Dominik Schmidt
2015-11-19 05:39:07 +01:00
parent 3db8e91b51
commit 17d71b413f
6 changed files with 108 additions and 77 deletions

View File

@@ -430,6 +430,23 @@ Tomahawk.nativeAsyncRequestDone = function (reqId, xhr) {
delete Tomahawk.asyncRequestCallbacks[reqId]; delete Tomahawk.asyncRequestCallbacks[reqId];
}; };
/**
* This method is externalized from Tomahawk.asyncRequest, so that other clients
* (like tomahawk-android) can inject their own logic that determines whether or not to do a request
* natively.
*
* @returns boolean indicating whether or not to do a request with the given parameters natively
*/
var shouldDoNativeRequest = function (options) {
var extraHeaders = options.headers;
return (extraHeaders && (extraHeaders.hasOwnProperty("Referer")
|| extraHeaders.hasOwnProperty("referer")
|| extraHeaders.hasOwnProperty("User-Agent")));
};
/** /**
* Possible options: * Possible options:
* - method: The HTTP request method (default: GET) * - method: The HTTP request method (default: GET)
@@ -439,59 +456,45 @@ Tomahawk.nativeAsyncRequestDone = function (reqId, xhr) {
* - data: body data included in POST requests * - data: body data included in POST requests
* - needCookieHeader: boolean indicating whether or not the request needs to be able to get the * - needCookieHeader: boolean indicating whether or not the request needs to be able to get the
* "Set-Cookie" response header * "Set-Cookie" response header
* - headers: headers set on the request
*/ */
Tomahawk.asyncRequest = function (url, callback, extraHeaders, options) { var doRequest = function(options) {
// unpack options if (shouldDoNativeRequest(options)) {
var opt = options || {}; return Tomahawk.NativeScriptJobManager.invoke('httpRequest', options).then(function(xhr) {
var method = opt.method || 'GET'; xhr.responseHeaders = xhr.responseHeaders || {};
xhr.getAllResponseHeaders = function() {
if (shouldDoNativeRequest(url, callback, extraHeaders, options)) { return this.responseHeaders;
// Assign a request Id to the callback so we can use it when we are
// returning from the native call.
var reqId = Tomahawk.asyncRequestIdCounter;
Tomahawk.asyncRequestIdCounter++;
Tomahawk.asyncRequestCallbacks[reqId] = {
callback: callback,
errorHandler: opt.errorHandler
}; };
Tomahawk.nativeAsyncRequest(reqId, url, extraHeaders, options); xhr.getResponseHeader = function (header) {
return this.responseHeaders[header];
};
return xhr;
});
} else { } else {
return new RSVP.Promise(function(resolve, reject) {
var xmlHttpRequest = new XMLHttpRequest(); var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.open(method, url, true, opt.username, opt.password); xmlHttpRequest.open(options.method, options.url, true, options.username, options.password);
if (extraHeaders) { if (options.headers) {
for (var headerName in extraHeaders) { for (var headerName in options.headers) {
xmlHttpRequest.setRequestHeader(headerName, extraHeaders[headerName]); xmlHttpRequest.setRequestHeader(headerName, options.headers[headerName]);
} }
} }
xmlHttpRequest.onreadystatechange = function () { xmlHttpRequest.onreadystatechange = function () {
if (xmlHttpRequest.readyState == 4 if (xmlHttpRequest.readyState == 4
&& httpSuccessStatuses.indexOf(xmlHttpRequest.status) != -1) { && httpSuccessStatuses.indexOf(xmlHttpRequest.status) != -1) {
callback.call(window, xmlHttpRequest); resolve(xmlHttpRequest);
} else if (xmlHttpRequest.readyState === 4) { } else if (xmlHttpRequest.readyState === 4) {
Tomahawk.log("Failed to do " + method + " request: to: " + url); Tomahawk.log("Failed to do " + options.method + " request: to: " + options.url);
Tomahawk.log("Status Code was: " + xmlHttpRequest.status); Tomahawk.log("Status Code was: " + xmlHttpRequest.status);
if (opt.hasOwnProperty('errorHandler')) { reject(xmlHttpRequest);
opt.errorHandler.call(window, xmlHttpRequest);
}
} }
}; };
xmlHttpRequest.send(opt.data || null); xmlHttpRequest.send(options.data || null);
});
} }
}; };
/**
* This method is externalized from Tomahawk.asyncRequest, so that other clients
* (like tomahawk-android) can inject their own logic that determines whether or not to do a request
* natively.
*
* @returns boolean indicating whether or not to do a request with the given parameters natively
*/
var shouldDoNativeRequest = function (url, callback, extraHeaders, options) {
return (extraHeaders && (extraHeaders.hasOwnProperty("Referer")
|| extraHeaders.hasOwnProperty("referer")
|| extraHeaders.hasOwnProperty("User-Agent")));
};
Tomahawk.ajax = function (url, settings) { Tomahawk.ajax = function (url, settings) {
if (typeof url === "object") { if (typeof url === "object") {
settings = url; settings = url;
@@ -549,10 +552,7 @@ Tomahawk.ajax = function (url, settings) {
} }
} }
return new RSVP.Promise(function (resolve, reject) { return doRequest(settings).then(function (xhr) {
settings.errorHandler = reject;
Tomahawk.asyncRequest(settings.url, resolve, settings.headers, settings);
}).then(function (xhr) {
if (settings.rawResponse) { if (settings.rawResponse) {
return xhr; return xhr;
} }
@@ -876,12 +876,17 @@ Tomahawk.PluginManager = {
} }
}; };
var encodeParamsToNativeFunctions = function(param) {
return param;
};
Tomahawk.NativeScriptJobManager = { Tomahawk.NativeScriptJobManager = {
idCounter: 0, idCounter: 0,
deferreds: {}, deferreds: {},
invoke: function (methodName, params) { invoke: function (methodName, params) {
var requestId = this.idCounter++; var requestId = this.idCounter++;
Tomahawk.invokeNativeScriptJob(requestId, methodName, JSON.stringify(params)); Tomahawk.invokeNativeScriptJob(requestId, methodName, encodeParamsToNativeFunctions(params));
this.deferreds[requestId] = RSVP.defer(); this.deferreds[requestId] = RSVP.defer();
return this.deferreds[requestId].promise; return this.deferreds[requestId].promise;
}, },
@@ -891,6 +896,7 @@ Tomahawk.NativeScriptJobManager = {
Tomahawk.log("Deferred object with the given requestId is not present!"); Tomahawk.log("Deferred object with the given requestId is not present!");
} }
deferred.resolve(result); deferred.resolve(result);
delete this.deferreds[requestId];
} }
}; };

View File

@@ -170,6 +170,22 @@ JSAccount::syncInvoke( const scriptobject_ptr& scriptObject, const QString& meth
return evaluateJavaScriptWithResult( eval ); return evaluateJavaScriptWithResult( eval );
} }
void
JSAccount::reportNativeScriptJobResult( int resultId, const QVariantMap& result )
{
QString eval = QString(
"Tomahawk.NativeScriptJobManager.reportNativeScriptJobResult("
"%1," // requestId
"%2" // results
");"
).arg( resultId )
.arg( serializeQVariantMap( result ) );
// Remove when new scripting api turned out to work reliably
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << eval;
evaluateJavaScript( eval );
}
QVariant QVariant
JSAccount::evaluateJavaScriptInternal( const QString& scriptSource ) JSAccount::evaluateJavaScriptInternal( const QString& scriptSource )

View File

@@ -71,6 +71,8 @@ public:
static QString serializeQVariantMap(const QVariantMap& map); static QString serializeQVariantMap(const QVariantMap& map);
void reportNativeScriptJobResult( int resultId, const QVariantMap& result ) override;
private: private:
/** /**
* Wrap the pure evaluateJavaScript call in here, while the threadings guards are in public methods * Wrap the pure evaluateJavaScript call in here, while the threadings guards are in public methods

View File

@@ -47,6 +47,8 @@
#include <QMap> #include <QMap>
#include <QWebFrame> #include <QWebFrame>
#include <QLocale> #include <QLocale>
#include <QNetworkReply>
#include <taglib/asffile.h> #include <taglib/asffile.h>
#include <taglib/flacfile.h> #include <taglib/flacfile.h>
#include <taglib/id3v2framefactory.h> #include <taglib/id3v2framefactory.h>
@@ -627,12 +629,24 @@ JSResolverHelper::nativeRetrieveMetadata( int metadataId, const QString& url,
} }
} }
void
JSResolverHelper::invokeNativeScriptJob( int requestId, const QString& methodName, const QVariantMap& params )
{
if ( methodName == "httpRequest" ) {
nativeAsyncRequest( requestId, params );
} else {
// TODO: make promise reject instead
Q_ASSERT_X(false, "invokeNativeScriptJob", "NativeScriptJob methodName was not found");
}
}
void void
JSResolverHelper::nativeAsyncRequest( const int requestId, const QString& url, JSResolverHelper::nativeAsyncRequest( const int requestId, const QVariantMap& options )
const QVariantMap& headers,
const QVariantMap& options )
{ {
QString url = options[ "url" ].toString();
QVariantMap headers = options[ "headers" ].toMap();
QNetworkRequest req( url ); QNetworkRequest req( url );
foreach ( const QString& key, headers.keys() ) foreach ( const QString& key, headers.keys() )
{ {
@@ -688,17 +702,16 @@ JSResolverHelper::nativeAsyncRequestDone( int requestId, NetworkReply* reply )
map["status"] = reply->reply()->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt(); map["status"] = reply->reply()->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
map["statusText"] = QString("%1 %2").arg( map["status"].toString() ) map["statusText"] = QString("%1 %2").arg( map["status"].toString() )
.arg( reply->reply()->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString() ); .arg( reply->reply()->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString() );
if (reply->reply()->hasRawHeader( "Content-Type" ))
map["contentType"] = reply->reply()->rawHeader( "Content-Type" );
bool ok = false;
QString json = QString::fromUtf8( TomahawkUtils::toJson( map, &ok ) );
Q_ASSERT( ok );
QString javascript = QString( "Tomahawk.nativeAsyncRequestDone( %1, %2 );" ) QVariantMap responseHeaders;
.arg( QString::number( requestId ) ) foreach( const QNetworkReply::RawHeaderPair& pair, reply->reply()->rawHeaderPairs() )
.arg( json ); {
m_resolver->d_func()->scriptAccount->evaluateJavaScript( javascript ); responseHeaders[ pair.first ] = pair.second;
}
map["responseHeaders"] = responseHeaders;
m_resolver->d_func()->scriptAccount->reportNativeScriptJobResult( requestId, map );
} }

View File

@@ -86,20 +86,9 @@ public:
int sizehint, int sizehint,
const QVariantMap& options ); const QVariantMap& options );
/** Q_INVOKABLE void invokeNativeScriptJob( int requestId,
* Native handler for asynchronous HTTP requests. const QString& methodName,
* const QVariantMap& params );
* This handler shall only be used if we cannot achieve the request with
* XMLHttpRequest as that would be more efficient.
* Use cases are:
* * Referer header: Stripped on MacOS and the specification says it
* should be stripped
*
* INTERNAL USE ONLY!
*/
Q_INVOKABLE void nativeAsyncRequest( int requestId, const QString& url,
const QVariantMap& headers,
const QVariantMap& options );
/** /**
* Lucene++ indices for JS resolvers * Lucene++ indices for JS resolvers
@@ -145,6 +134,10 @@ private:
bool indexDataFromVariant( const QVariantMap& map, struct Tomahawk::IndexData& indexData ); bool indexDataFromVariant( const QVariantMap& map, struct Tomahawk::IndexData& indexData );
QVariantList searchInFuzzyIndex( const Tomahawk::query_ptr& query ); QVariantList searchInFuzzyIndex( const Tomahawk::query_ptr& query );
// native script jobs
void nativeAsyncRequest( int requestId, const QVariantMap& options );
QVariantMap m_resolverConfig; QVariantMap m_resolverConfig;
JSResolver* m_resolver; JSResolver* m_resolver;
QString m_scriptPath; QString m_scriptPath;

View File

@@ -65,13 +65,14 @@ public:
ScriptJob* invoke( const scriptobject_ptr& scriptObject, const QString& methodName, const QVariantMap& arguments ); ScriptJob* invoke( const scriptobject_ptr& scriptObject, const QString& methodName, const QVariantMap& arguments );
virtual QVariant syncInvoke( const scriptobject_ptr& scriptObject, const QString& methodName, const QVariantMap& arguments ) = 0; virtual QVariant syncInvoke( const scriptobject_ptr& scriptObject, const QString& methodName, const QVariantMap& arguments ) = 0;
virtual void startJob( ScriptJob* scriptJob ) = 0; virtual void startJob( ScriptJob* scriptJob ) = 0;
void reportScriptJobResult( const QVariantMap& result ); void reportScriptJobResult( const QVariantMap& result );
void registerScriptPlugin( const QString& type, const QString& objectId ); void registerScriptPlugin( const QString& type, const QString& objectId );
void unregisterScriptPlugin( const QString& type, const QString& objectId ); void unregisterScriptPlugin( const QString& type, const QString& objectId );
virtual void reportNativeScriptJobResult( int resultId, const QVariantMap& result ) = 0;
virtual void scriptPluginFactory( const QString& type, const scriptobject_ptr& object ); virtual void scriptPluginFactory( const QString& type, const scriptobject_ptr& object );
QList< Tomahawk::result_ptr > parseResultVariantList( const QVariantList& reslist ); QList< Tomahawk::result_ptr > parseResultVariantList( const QVariantList& reslist );