1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-03-25 10:19:41 +01:00

Merge branch 'js-resolver'

This commit is contained in:
Dominik Schmidt 2011-07-05 13:17:23 +02:00
commit 7c9c046b17
5 changed files with 590 additions and 15 deletions

300
data/js/tomahawk.js Normal file
View File

@ -0,0 +1,300 @@
// if run in phantomjs add fake Tomahawk environment
if(window.Tomahawk === undefined)
{
alert("PHANTOMJS ENVIRONMENT");
var Tomahawk = {
fakeEnv: function()
{
return true;
},
resolverData: function()
{
return {
scriptPath: function()
{
return "/home/tomahawk/resolver.js";
}
};
},
log: function( message )
{
console.log( message );
}
};
}
Tomahawk.resolver = {
scriptPath: Tomahawk.resolverData().scriptPath
};
Tomahawk.timestamp = function() {
return Math.round( new Date()/1000 );
}
Tomahawk.dumpResult = function( result ) {
var results = result.results;
Tomahawk.log("Dumping " + results.length + " results for query " + result.qid + "...");
for(var i=0; i<results.length;i++)
{
var result = results[i];
Tomahawk.log( result.artist + " - " + result.track + " | " + result.url );
}
Tomahawk.log("Done.");
}
// javascript part of Tomahawk-Object API
Tomahawk.extend = function(object, members) {
var F = function() {};
F.prototype = object;
var newObject = new F;
for(var key in members)
{
newObject[key] = members[key];
}
return newObject;
}
// Resolver BaseObject, inherit it to implement your own resolver
var TomahawkResolver = {
init: function()
{
},
scriptPath: function()
{
return Tomahawk.resolverData().scriptPath;
},
getConfigUi: function()
{
return {};
},
getUserConfig: function()
{
var configJson = window.localStorage[ this.scriptPath() ];
if( configJson === undefined )
configJson = "{}";
var config = JSON.parse( configJson );
return config;
},
saveUserConfig: function()
{
var config = Tomahawk.resolverData().config;
var configJson = JSON.stringify( config );
window.localStorage[ this.scriptPath() ] = configJson;
},
resolve: function( qid, artist, album, title )
{
return {
qid: qid
};
},
search: function( qid, searchString )
{
return this.resolve( qid, "", "", searchString );
}
};
/**** begin example implementation of a resolver ****/
// implement the resolver
/*
* var DemoResolver = Tomahawk.extend(TomahawkResolver,
* {
* getSettings: function()
* {
* return {
* name: "Demo Resolver",
* weigth: 95,
* timeout: 5,
* limit: 10
};
},
resolve: function( qid, artist, album, track )
{
return {
qid: qid,
results: [
{
artist: "Mokele",
album: "You Yourself are Me Myself and I am in Love",
track: "Hiding In Your Insides (php)",
source: "Mokele.co.uk",
url: "http://play.mokele.co.uk/music/Hiding%20In%20Your%20Insides.mp3",
bitrate: 160,
duration: 248,
size: 4971780,
score: 1.0,
extension: "mp3",
mimetype: "audio/mpeg"
}
]
};
}
}
);
// register the resolver
Tomahawk.resolver.instance = DemoResolver;*/
/**** end example implementation of a resolver ****/
// help functions
Tomahawk.valueForSubNode = function(node, tag)
{
if(node === undefined)
throw new Error("Tomahawk.valueForSubnode: node is undefined!");
var element = node.getElementsByTagName(tag)[0];
if( element === undefined )
return undefined;
return element.textContent;
};
Tomahawk.syncRequest = function(url)
{
var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.open('GET', url, false);
xmlHttpRequest.send(null);
return xmlHttpRequest.responseText;
}
/**
*
* Secure Hash Algorithm (SHA256)
* http://www.webtoolkit.info/
*
* Original code by Angel Marin, Paul Johnston.
*
**/
Tomahawk.sha256=function(s){
var chrsz = 8;
var hexcase = 0;
function safe_add (x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
function S (X, n) { return ( X >>> n ) | (X << (32 - n)); }
function R (X, n) { return ( X >>> n ); }
function Ch(x, y, z) { return ((x & y) ^ ((~x) & z)); }
function Maj(x, y, z) { return ((x & y) ^ (x & z) ^ (y & z)); }
function Sigma0256(x) { return (S(x, 2) ^ S(x, 13) ^ S(x, 22)); }
function Sigma1256(x) { return (S(x, 6) ^ S(x, 11) ^ S(x, 25)); }
function Gamma0256(x) { return (S(x, 7) ^ S(x, 18) ^ R(x, 3)); }
function Gamma1256(x) { return (S(x, 17) ^ S(x, 19) ^ R(x, 10)); }
function core_sha256 (m, l) {
var K = new Array(0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0xFC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x6CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2);
var HASH = new Array(0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19);
var W = new Array(64);
var a, b, c, d, e, f, g, h, i, j;
var T1, T2;
m[l >> 5] |= 0x80 << (24 - l % 32);
m[((l + 64 >> 9) << 4) + 15] = l;
for ( var i = 0; i<m.length; i+=16 ) {
a = HASH[0];
b = HASH[1];
c = HASH[2];
d = HASH[3];
e = HASH[4];
f = HASH[5];
g = HASH[6];
h = HASH[7];
for ( var j = 0; j<64; j++) {
if (j < 16) W[j] = m[j + i];
else W[j] = safe_add(safe_add(safe_add(Gamma1256(W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
T1 = safe_add(safe_add(safe_add(safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
T2 = safe_add(Sigma0256(a), Maj(a, b, c));
h = g;
g = f;
f = e;
e = safe_add(d, T1);
d = c;
c = b;
b = a;
a = safe_add(T1, T2);
}
HASH[0] = safe_add(a, HASH[0]);
HASH[1] = safe_add(b, HASH[1]);
HASH[2] = safe_add(c, HASH[2]);
HASH[3] = safe_add(d, HASH[3]);
HASH[4] = safe_add(e, HASH[4]);
HASH[5] = safe_add(f, HASH[5]);
HASH[6] = safe_add(g, HASH[6]);
HASH[7] = safe_add(h, HASH[7]);
}
return HASH;
}
function str2binb (str) {
var bin = Array();
var mask = (1 << chrsz) - 1;
for(var i = 0; i < str.length * chrsz; i += chrsz) {
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32);
}
return bin;
}
function Utf8Encode(string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
}
function binb2hex (binarray) {
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for(var i = 0; i < binarray.length * 4; i++) {
str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
}
return str;
}
s = Utf8Encode(s);
return binb2hex(core_sha256(str2binb(s), s.length * chrsz));
}

View File

@ -97,5 +97,6 @@
<file>./data/sql/dbmigrate-22_to_23.sql</file>
<file>./data/sql/dbmigrate-23_to_24.sql</file>
<file>./data/sql/dbmigrate-24_to_25.sql</file>
<file>./data/js/tomahawk.js</file>
</qresource>
</RCC>

View File

@ -586,7 +586,7 @@ AudioEngine::setCurrentTrack( const Tomahawk::result_ptr& result )
bool
AudioEngine::isHttpResult( const QString& url ) const
{
return url.startsWith( "http://" );
return url.startsWith( "http://" ) || url.startsWith( "https://" );
}

View File

@ -24,6 +24,9 @@
#include "sourcelist.h"
#include "utils/tomahawkutils.h"
#include <QMetaProperty>
#define RESOLVER_LEGACY_CODE "var resolver = Tomahawk.resolver.instance ? Tomahawk.resolver.instance : TomahawkResolver;"
QtScriptResolverHelper::QtScriptResolverHelper( const QString& scriptPath, QObject* parent )
: QObject( parent )
@ -32,8 +35,8 @@ QtScriptResolverHelper::QtScriptResolverHelper( const QString& scriptPath, QObje
}
QString
QtScriptResolverHelper::readFile( const QString& fileName )
QByteArray
QtScriptResolverHelper::readRaw( const QString& fileName )
{
QString path = QFileInfo( m_scriptPath ).absolutePath();
// remove directories
@ -43,13 +46,15 @@ QtScriptResolverHelper::readFile( const QString& fileName )
QFile file( absoluteFilePath );
if ( !file.exists() )
{
return QString();
Q_ASSERT(false);
return QByteArray();
}
file.open( QIODevice::ReadOnly );
return file.readAll();
}
QString
QtScriptResolverHelper::compress( const QString& data )
{
@ -58,11 +63,48 @@ QtScriptResolverHelper::compress( const QString& data )
}
QString
QtScriptResolverHelper::readCompressed(const QString& fileName)
{
return compress( readRaw( fileName ) );
}
QString
QtScriptResolverHelper::readBase64(const QString& fileName)
{
return readRaw( fileName ).toBase64();
}
QVariantMap
QtScriptResolverHelper::resolverData()
{
QVariantMap resolver;
resolver["config"] = m_resolverConfig;
resolver["scriptPath"] = m_scriptPath;
return resolver;
}
void QtScriptResolverHelper::log(const QString& message)
{
qDebug() << m_scriptPath << ":" << message;
}
void
QtScriptResolverHelper::setResolverConfig( QVariantMap config )
{
m_resolverConfig = config;
}
QtScriptResolver::QtScriptResolver( const QString& scriptPath )
: Tomahawk::ExternalResolver( scriptPath )
, m_ready( false )
, m_stopped( false )
, m_resolverHelper( new QtScriptResolverHelper( scriptPath, this ) )
{
qDebug() << Q_FUNC_INFO << scriptPath;
@ -76,15 +118,36 @@ QtScriptResolver::QtScriptResolver( const QString& scriptPath )
}
m_engine->mainFrame()->setHtml( "<html><body></body></html>" );
// add c++ part of tomahawk javascript library
m_engine->mainFrame()->addToJavaScriptWindowObject( "Tomahawk", m_resolverHelper );
// add rest of it
m_engine->setScriptPath( "tomahawk.js" );
QFile jslib( RESPATH "js/tomahawk.js" );
jslib.open( QIODevice::ReadOnly );
m_engine->mainFrame()->evaluateJavaScript( jslib.readAll() );
jslib.close();
// add resolver
m_engine->setScriptPath( scriptPath );
m_engine->mainFrame()->evaluateJavaScript( scriptFile.readAll() );
m_engine->mainFrame()->addToJavaScriptWindowObject( "Tomahawk", new QtScriptResolverHelper( scriptPath, this ) );
scriptFile.close();
QVariantMap m = m_engine->mainFrame()->evaluateJavaScript( "getSettings();" ).toMap();
// init resolver
resolverInit();
QVariantMap m = resolverSettings();
m_name = m.value( "name" ).toString();
m_weight = m.value( "weight", 0 ).toUInt();
m_timeout = m.value( "timeout", 25 ).toUInt() * 1000;
// load config widget and apply settings
loadUi();
QVariantMap config = resolverUserConfig();
fillDataInWidgets( config );
qDebug() << Q_FUNC_INFO << m_name << m_weight << m_timeout;
m_ready = true;
@ -114,7 +177,7 @@ QtScriptResolver::resolve( const Tomahawk::query_ptr& query )
if ( !query->isFullTextQuery() )
{
eval = QString( "resolve( '%1', '%2', '%3', '%4' );" )
eval = QString( RESOLVER_LEGACY_CODE "resolver.resolve( '%1', '%2', '%3', '%4' );" )
.arg( query->id().replace( "'", "\\'" ) )
.arg( query->artist().replace( "'", "\\'" ) )
.arg( query->album().replace( "'", "\\'" ) )
@ -122,11 +185,14 @@ QtScriptResolver::resolve( const Tomahawk::query_ptr& query )
}
else
{
eval = QString( "resolve( '%1', '%2', '%3', '%4' );" )
eval = QString( "if(Tomahawk.resolver.instance !== undefined) {"
" resolver.search( '%1', '%2' );"
"} else {"
" resolve( '%1', '', '', '%2' );"
"}"
)
.arg( query->id().replace( "'", "\\'" ) )
.arg( query->fullTextQuery().replace( "'", "\\'" ) )
.arg( QString() )
.arg( QString() );
.arg( query->fullTextQuery().replace( "'", "\\'" ) );
}
QList< Tomahawk::result_ptr > results;
@ -187,3 +253,180 @@ QtScriptResolver::stop()
m_stopped = true;
emit finished();
}
void
QtScriptResolver::loadUi()
{
qDebug() << Q_FUNC_INFO;
QVariantMap m = m_engine->mainFrame()->evaluateJavaScript( RESOLVER_LEGACY_CODE "resolver.getConfigUi();" ).toMap();
m_dataWidgets = m["fields"].toList();
bool compressed = m.value( "compressed", "false" ).toBool();
qDebug() << "Resolver has a preferences widget! compressed?" << compressed << m;
QByteArray uiData = m[ "widget" ].toByteArray();
if( compressed )
uiData = qUncompress( QByteArray::fromBase64( uiData ) );
else
uiData = QByteArray::fromBase64( uiData );
QVariantMap images;
foreach(const QVariant& item, m[ "images" ].toList())
{
QString key = item.toMap().keys().first();
QVariant value = item.toMap().value(key);
images[key] = value;
}
if( m.contains( "images" ) )
uiData = fixDataImagePaths( uiData, compressed, images );
m_configWidget = QWeakPointer< QWidget >( widgetFromData( uiData, 0 ) );
emit changed();
}
QWidget*
QtScriptResolver::configUI() const
{
if( m_configWidget.isNull() )
return 0;
else
return m_configWidget.data();
}
void
QtScriptResolver::saveConfig()
{
QVariant saveData = loadDataFromWidgets();
qDebug() << Q_FUNC_INFO << saveData;
m_resolverHelper->setResolverConfig( saveData.toMap() );
m_engine->mainFrame()->evaluateJavaScript( RESOLVER_LEGACY_CODE "resolver.saveUserConfig();" );
}
QWidget*
QtScriptResolver::findWidget(QWidget* widget, const QString& objectName)
{
if( !widget || !widget->isWidgetType() )
return 0;
if( widget->objectName() == objectName )
return widget;
foreach( QObject* child, widget->children() )
{
QWidget* found = findWidget(qobject_cast< QWidget* >( child ), objectName);
if( found )
return found;
}
return 0;
}
QVariant
QtScriptResolver::widgetData(QWidget* widget, const QString& property)
{
for( int i = 0; i < widget->metaObject()->propertyCount(); i++ )
{
if( widget->metaObject()->property( i ).name() == property )
{
return widget->property( property.toLatin1() );
}
}
return QVariant();
}
void
QtScriptResolver::setWidgetData(const QVariant& value, QWidget* widget, const QString& property)
{
for( int i = 0; i < widget->metaObject()->propertyCount(); i++ )
{
if( widget->metaObject()->property( i ).name() == property )
{
widget->metaObject()->property( i ).write( widget, value);
return;
}
}
}
QVariantMap
QtScriptResolver::loadDataFromWidgets()
{
QVariantMap saveData;
foreach(const QVariant& dataWidget, m_dataWidgets)
{
QVariantMap data = dataWidget.toMap();
QString widgetName = data["widget"].toString();
QWidget* widget= findWidget( m_configWidget.data(), widgetName );
QString value = widgetData( widget, data["property"].toString() ).toString();
saveData[ data["name"].toString() ] = value;
}
qDebug() << saveData;
return saveData;
}
void
QtScriptResolver::fillDataInWidgets( const QVariantMap& data )
{
qDebug() << Q_FUNC_INFO << data;
foreach(const QVariant& dataWidget, m_dataWidgets)
{
QString widgetName = dataWidget.toMap()["widget"].toString();
QWidget* widget= findWidget( m_configWidget.data(), widgetName );
if( !widget )
{
qDebug() << Q_FUNC_INFO << "widget specified in resolver was not found:" << widgetName;
Q_ASSERT(false);
return;
}
QString propertyName = dataWidget.toMap()["property"].toString();
QString name = dataWidget.toMap()["name"].toString();
setWidgetData( data[ name ], widget, propertyName );
}
}
QVariantMap
QtScriptResolver::resolverSettings()
{
return m_engine->mainFrame()->evaluateJavaScript( RESOLVER_LEGACY_CODE "if(resolver.settings) resolver.settings; else getSettings(); " ).toMap();
}
QVariantMap
QtScriptResolver::resolverUserConfig()
{
return m_engine->mainFrame()->evaluateJavaScript( RESOLVER_LEGACY_CODE "resolver.getUserConfig();" ).toMap();
}
QVariantMap
QtScriptResolver::resolverInit()
{
return m_engine->mainFrame()->evaluateJavaScript( RESOLVER_LEGACY_CODE "resolver.init();" ).toMap();
}

View File

@ -40,13 +40,22 @@ Q_OBJECT
public:
QtScriptResolverHelper( const QString& scriptPath, QObject* parent );
void setResolverConfig( QVariantMap config );
public slots:
QString readFile( const QString& fileName );
QByteArray readRaw( const QString& fileName );
QString readBase64( const QString& fileName );
QString readCompressed( const QString& fileName );
QString compress( const QString& data );
QVariantMap resolverData();
void log( const QString& message);
bool fakeEnv() { return false; }
private:
QString m_scriptPath;
QVariantMap m_resolverConfig;
};
class ScriptEngine : public QWebPage
@ -65,6 +74,11 @@ public:
settings()->setAttribute( QWebSettings::LocalStorageDatabaseEnabled, true );
}
void setScriptPath( const QString& scriptPath )
{
m_scriptPath = scriptPath;
}
public slots:
bool shouldInterruptJavaScript()
{
@ -73,10 +87,11 @@ public slots:
protected:
virtual void javaScriptConsoleMessage( const QString & message, int lineNumber, const QString & sourceID )
{ qDebug() << "JAVASCRIPT ERROR:" << message << lineNumber << sourceID; }
{ qDebug() << "JAVASCRIPT:" << m_scriptPath << message << lineNumber << sourceID; Q_ASSERT(false);}
private:
QtScriptResolver* m_parent;
QString m_scriptPath;
};
@ -92,8 +107,8 @@ public:
virtual unsigned int weight() const { return m_weight; }
virtual unsigned int timeout() const { return m_timeout; }
virtual QWidget* configUI() const { return 0; } // TODO support properly for qtscript resolvers too!
virtual void saveConfig() {}
virtual QWidget* configUI() const;
virtual void saveConfig();
public slots:
virtual void resolve( const Tomahawk::query_ptr& query );
@ -103,12 +118,28 @@ signals:
void finished();
private:
virtual void loadUi();
QWidget* findWidget( QWidget* widget, const QString& objectName );
void setWidgetData( const QVariant& value, QWidget* widget, const QString& property );
QVariant widgetData( QWidget* widget, const QString& property );
QVariantMap loadDataFromWidgets();
void fillDataInWidgets( const QVariantMap& data );
// encapsulate javascript calls
QVariantMap resolverSettings();
QVariantMap resolverUserConfig();
QVariantMap resolverInit();
ScriptEngine* m_engine;
QString m_name;
unsigned int m_weight, m_timeout;
bool m_ready, m_stopped;
QtScriptResolverHelper* m_resolverHelper;
QWeakPointer< QWidget > m_configWidget;
QList< QVariant > m_dataWidgets;
};
#endif // QTSCRIPTRESOLVER_H