1
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
3
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
4
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
6
* Tomahawk is free software: you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation, either version 3 of the License, or
9
* (at your option) any later version.
11
* Tomahawk is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
20
#include "ScriptResolver.h"
23
#include <QtNetwork/QNetworkAccessManager>
24
#include <QtNetwork/QNetworkProxy>
29
#include "SourceList.h"
31
#include "utils/TomahawkUtilsGui.h"
32
#include "utils/Logger.h"
38
ScriptResolver::ScriptResolver( const QString& exe )
39
: Tomahawk::ExternalResolverGui( exe )
44
, m_configSent( false )
46
, m_error( Tomahawk::ExternalResolver::NoError )
48
tLog() << Q_FUNC_INFO << "Created script resolver:" << exe;
49
connect( &m_proc, SIGNAL( readyReadStandardError() ), SLOT( readStderr() ) );
50
connect( &m_proc, SIGNAL( readyReadStandardOutput() ), SLOT( readStdout() ) );
51
connect( &m_proc, SIGNAL( finished( int, QProcess::ExitStatus ) ), SLOT( cmdExited( int, QProcess::ExitStatus ) ) );
55
if ( !TomahawkUtils::nam() )
58
// set the name to the binary, if we launch properly we'll get the name the resolver reports
59
m_name = QFileInfo( filePath() ).baseName();
61
// set the icon, if we launch properly we'll get the icon the resolver reports
62
m_icon = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultResolver, TomahawkUtils::Original, QSize( 128, 128 ) );
66
ScriptResolver::~ScriptResolver()
68
disconnect( &m_proc, SIGNAL( finished( int, QProcess::ExitStatus ) ), this, SLOT( cmdExited( int, QProcess::ExitStatus ) ) );
72
msg[ "_msgtype" ] = "quit";
75
bool finished = m_proc.state() != QProcess::Running || m_proc.waitForFinished( 2500 ); // might call handleMsg
77
Tomahawk::Pipeline::instance()->removeResolver( this );
79
if ( !finished || m_proc.state() == QProcess::Running )
81
qDebug() << "External resolver didn't exit after waiting 2s for it to die, killing forcefully";
89
if ( !m_configWidget.isNull() )
90
delete m_configWidget.data();
94
Tomahawk::ExternalResolver*
95
ScriptResolver::factory( const QString& exe )
97
ExternalResolver* res = 0;
99
const QFileInfo fi( exe );
100
if ( fi.suffix() != "js" && fi.suffix() != "script" )
102
res = new ScriptResolver( exe );
103
tLog() << Q_FUNC_INFO << exe << "Loaded.";
111
ScriptResolver::start()
115
Tomahawk::Pipeline::instance()->addResolver( this );
116
else if ( !m_configSent )
118
// else, we've sent our config msg so are waiting for the resolver to react
123
ScriptResolver::sendConfig()
125
// Send a configutaion message with any information the resolver might need
126
// For now, only the proxy information is sent
128
m.insert( "_msgtype", "config" );
132
TomahawkUtils::NetworkProxyFactory* factory = dynamic_cast<TomahawkUtils::NetworkProxyFactory*>( TomahawkUtils::nam()->proxyFactory() );
133
QNetworkProxy proxy = factory->proxy();
134
QString proxyType = ( proxy.type() == QNetworkProxy::Socks5Proxy ? "socks5" : "none" );
135
m.insert( "proxytype", proxyType );
136
m.insert( "proxyhost", proxy.hostName() );
137
m.insert( "proxyport", proxy.port() );
138
m.insert( "proxyuser", proxy.user() );
139
m.insert( "proxypass", proxy.password() );
143
foreach ( const QString& host, factory->noProxyHosts() )
145
m.insert( "noproxyhosts", hosts );
147
QByteArray data = m_serializer.serialize( m );
153
ScriptResolver::reload()
160
ScriptResolver::running() const
167
ScriptResolver::sendMessage( const QVariantMap& map )
169
QByteArray data = m_serializer.serialize( map );
175
ScriptResolver::readStderr()
177
tLog() << "SCRIPT_STDERR" << filePath() << m_proc.readAllStandardError();
181
ScriptResolver::ErrorState
182
ScriptResolver::error() const
189
ScriptResolver::readStdout()
191
if ( m_msgsize == 0 )
193
if ( m_proc.bytesAvailable() < 4 )
197
m_proc.read( (char*) &len_nbo, 4 );
198
m_msgsize = qFromBigEndian( len_nbo );
203
m_msg.append( m_proc.read( m_msgsize - m_msg.length() ) );
206
if ( m_msgsize == (quint32) m_msg.length() )
212
if ( m_proc.bytesAvailable() )
213
QTimer::singleShot( 0, this, SLOT( readStdout() ) );
219
ScriptResolver::sendMsg( const QByteArray& msg )
221
// qDebug() << Q_FUNC_INFO << m_ready << msg << msg.length();
222
if ( !m_proc.isOpen() )
226
qToBigEndian( msg.length(), (uchar*) &len );
227
m_proc.write( (const char*) &len, 4 );
233
ScriptResolver::handleMsg( const QByteArray& msg )
235
// qDebug() << Q_FUNC_INFO << msg.size() << QString::fromAscii( msg );
237
// Might be called from waitForFinished() in ~ScriptResolver, no database in that case, abort.
242
QVariant v = m_parser.parse( msg, &ok );
243
if ( !ok || v.type() != QVariant::Map )
248
QVariantMap m = v.toMap();
249
QString msgtype = m.value( "_msgtype" ).toString();
251
if ( msgtype == "settings" )
256
else if ( msgtype == "confwidget" )
258
setupConfWidget( m );
261
else if ( msgtype == "results" )
263
const QString qid = m.value( "qid" ).toString();
264
QList< Tomahawk::result_ptr > results;
265
const QVariantList reslist = m.value( "results" ).toList();
267
foreach( const QVariant& rv, reslist )
269
QVariantMap m = rv.toMap();
270
tDebug( LOGVERBOSE ) << "Found result:" << m;
271
if ( m.value( "artist" ).toString().trimmed().isEmpty() || m.value( "track" ).toString().trimmed().isEmpty() )
274
Tomahawk::result_ptr rp = Tomahawk::Result::get( m.value( "url" ).toString() );
275
Tomahawk::artist_ptr ap = Tomahawk::Artist::get( m.value( "artist" ).toString(), false );
277
rp->setAlbum( Tomahawk::Album::get( ap, m.value( "album" ).toString(), false ) );
278
rp->setAlbumPos( m.value( "albumpos" ).toUInt() );
279
rp->setTrack( m.value( "track" ).toString() );
280
rp->setDuration( m.value( "duration" ).toUInt() );
281
rp->setBitrate( m.value( "bitrate" ).toUInt() );
282
rp->setSize( m.value( "size" ).toUInt() );
283
rp->setRID( uuid() );
284
rp->setFriendlySource( m_name );
285
rp->setPurchaseUrl( m.value( "purchaseUrl" ).toString() );
286
rp->setLinkUrl( m.value( "linkUrl" ).toString() );
287
rp->setYear( m.value( "year").toUInt() );
288
rp->setDiscNumber( m.value( "discnumber" ).toUInt() );
290
rp->setMimetype( m.value( "mimetype" ).toString() );
291
if ( rp->mimetype().isEmpty() )
293
rp->setMimetype( TomahawkUtils::extensionToMimetype( m.value( "extension" ).toString() ) );
294
Q_ASSERT( !rp->mimetype().isEmpty() );
297
rp->setResolvedBy( this );
301
Tomahawk::Pipeline::instance()->reportResults( qid, results );
305
// Unknown message, give up for custom implementations
306
emit customMessage( msgtype, m );
312
ScriptResolver::cmdExited( int code, QProcess::ExitStatus status )
315
tLog() << Q_FUNC_INFO << "SCRIPT EXITED, code" << code << "status" << status << filePath();
316
Tomahawk::Pipeline::instance()->removeResolver( this );
318
m_error = ExternalResolver::FailedToLoad;
323
tLog() << "*** Script resolver stopped ";
329
if ( m_num_restarts < 10 )
332
tLog() << "*** Restart num" << m_num_restarts;
338
tLog() << "*** Reached max restarts, not restarting.";
344
ScriptResolver::resolve( const Tomahawk::query_ptr& query )
347
m.insert( "_msgtype", "rq" );
349
if ( query->isFullTextQuery() )
351
m.insert( "fulltext", query->fullTextQuery() );
352
m.insert( "artist", query->artist() );
353
m.insert( "track", query->fullTextQuery() );
354
m.insert( "qid", query->id() );
358
m.insert( "artist", query->artist() );
359
m.insert( "track", query->track() );
360
m.insert( "qid", query->id() );
362
if ( !query->resultHint().isEmpty() )
363
m.insert( "resultHint", query->resultHint() );
366
const QByteArray msg = m_serializer.serialize( QVariant( m ) );
372
ScriptResolver::doSetup( const QVariantMap& m )
374
// qDebug() << Q_FUNC_INFO << m;
376
m_name = m.value( "name" ).toString();
377
m_weight = m.value( "weight", 0 ).toUInt();
378
m_timeout = m.value( "timeout", 5 ).toUInt() * 1000;
379
bool compressed = m.value( "compressed", "false" ).toString() == "true";
381
QByteArray icoData = m.value( "icon" ).toByteArray();
383
icoData = qUncompress( QByteArray::fromBase64( icoData ) );
385
icoData = QByteArray::fromBase64( icoData );
387
ico.loadFromData( icoData );
389
bool success = false;
392
m_icon = ico.scaled( m_icon.size(), Qt::IgnoreAspectRatio );
395
// see if the resolver sent an icon path to not break the old (unofficial) api.
396
// TODO: remove this and publish a definitive api
399
QString iconPath = QFileInfo( filePath() ).path() + "/" + m.value( "icon" ).toString();
400
success = m_icon.load( iconPath );
403
qDebug() << "SCRIPT" << filePath() << "READY," << "name" << m_name << "weight" << m_weight << "timeout" << m_timeout << "icon received" << success;
406
m_configSent = false;
410
Tomahawk::Pipeline::instance()->addResolver( this );
417
ScriptResolver::setupConfWidget( const QVariantMap& m )
419
bool compressed = m.value( "compressed", "false" ).toString() == "true";
420
qDebug() << "Resolver has a preferences widget! compressed?" << compressed;
422
QByteArray uiData = m[ "widget" ].toByteArray();
424
uiData = qUncompress( QByteArray::fromBase64( uiData ) );
426
uiData = QByteArray::fromBase64( uiData );
428
if ( m.contains( "images" ) )
429
uiData = fixDataImagePaths( uiData, compressed, m[ "images" ].toMap() );
430
m_configWidget = QWeakPointer< QWidget >( widgetFromData( uiData, 0 ) );
437
ScriptResolver::startProcess()
439
if ( !QFile::exists( filePath() ) )
440
m_error = Tomahawk::ExternalResolver::FileNotFound;
443
m_error = Tomahawk::ExternalResolver::NoError;
446
const QFileInfo fi( filePath() );
449
// have to enclose in quotes if path contains spaces...
450
const QString runPath = QString( "\"%1\"" ).arg( filePath() );
452
QFile file( filePath() );
453
file.setPermissions( file.permissions() | QFile::ExeOwner | QFile::ExeGroup | QFile::ExeOther );
456
if ( fi.suffix().toLower() != "exe" )
458
DWORD dwSize = MAX_PATH;
460
wchar_t path[MAX_PATH] = { 0 };
461
wchar_t *ext = (wchar_t *) ("." + fi.suffix()).utf16();
463
HRESULT hr = AssocQueryStringW(
472
if ( ! FAILED( hr ) )
474
interpreter = QString( "\"%1\"" ).arg(QString::fromUtf16((const ushort *) path));
479
if ( interpreter.isEmpty() )
482
const QFileInfo info( filePath() );
483
m_proc.setWorkingDirectory( info.absolutePath() );
484
tLog() << "Setting working dir:" << info.absolutePath();
486
m_proc.start( runPath );
489
m_proc.start( interpreter, QStringList() << filePath() );
496
ScriptResolver::saveConfig()
498
Q_ASSERT( !m_configWidget.isNull() );
501
m.insert( "_msgtype", "setpref" );
502
QVariant widgets = configMsgFromWidget( m_configWidget.data() );
503
m.insert( "widgets", widgets );
504
QByteArray data = m_serializer.serialize( m );
511
ScriptResolver::setIcon( const QPixmap& icon )
518
ScriptResolver::configUI() const
520
if ( m_configWidget.isNull() )
523
return m_configWidget.data();
528
ScriptResolver::stop()
531
Tomahawk::Pipeline::instance()->removeResolver( this );