diff --git a/data/js/tomahawk.js b/data/js/tomahawk.js new file mode 100644 index 000000000..278ced0c6 --- /dev/null +++ b/data/js/tomahawk.js @@ -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> 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>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)); + +} \ No newline at end of file diff --git a/resources.qrc b/resources.qrc index 4afb8151f..763a4d08e 100644 --- a/resources.qrc +++ b/resources.qrc @@ -97,5 +97,6 @@ ./data/sql/dbmigrate-22_to_23.sql ./data/sql/dbmigrate-23_to_24.sql ./data/sql/dbmigrate-24_to_25.sql + ./data/js/tomahawk.js diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index a9dad2dcf..7c7fbe68e 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -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://" ); } diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index 78508728a..4ff0e0151 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -24,6 +24,9 @@ #include "sourcelist.h" #include "utils/tomahawkutils.h" +#include + +#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( "" ); + + // 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(); +} + diff --git a/src/resolvers/qtscriptresolver.h b/src/resolvers/qtscriptresolver.h index cf148990d..a988b1e9e 100644 --- a/src/resolvers/qtscriptresolver.h +++ b/src/resolvers/qtscriptresolver.h @@ -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