File indexing completed on 2025-02-23 04:28:33

0001 /****************************************************************************************
0002  * Copyright (c) 2013 Anmol Ahuja <darthcodus@gmail.com>                                *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #define DEBUG_PREFIX "ScriptableBias"
0018 
0019 #include "ScriptableBiasExporter.h"
0020 
0021 #include "core/support/Debug.h"
0022 #include "core/meta/Meta.h"
0023 #include "core-impl/collections/support/CollectionManager.h"
0024 
0025 #include <QApplication>
0026 #include <QCoreApplication>
0027 #include <QJSEngine>
0028 #include <QQmlEngine>
0029 #include <QThread>
0030 #include <QXmlStreamReader>
0031 #include <QXmlStreamWriter>
0032 
0033 using namespace AmarokScript;
0034 
0035 ScriptableBiasFactoryWrapper::ScriptableBiasFactoryWrapper(QJSEngine *engine)
0036 : QObject( engine )
0037 , m_engine( engine )
0038 {
0039 }
0040 
0041 QJSValue
0042 ScriptableBiasFactoryWrapper::biasCtor()
0043 {
0044     return m_engine->newQObject( new ScriptableBiasFactory( m_engine ) );
0045 }
0046 
0047 QJSValue
0048 ScriptableBiasFactoryWrapper::groupBiasCtor()
0049 {
0050     return m_engine->newQObject( new ScriptableBiasFactory( m_engine, true ) );
0051 }
0052 
0053 ScriptableBiasFactoryWrapper* ScriptableBiasFactory::s_wrapper = nullptr;
0054 
0055 void
0056 ScriptableBiasFactory::init( QJSEngine *engine )
0057 {
0058     TrackSetExporter::init( engine );
0059     if (s_wrapper == nullptr)
0060         s_wrapper = new ScriptableBiasFactoryWrapper( engine );
0061     QJSValue scriptObj = engine->newQObject( s_wrapper );
0062     engine->globalObject().setProperty( QStringLiteral("BiasFactory"),  scriptObj.property("biasCtor") );
0063     engine->globalObject().setProperty( QStringLiteral("GroupBiasFactory"), scriptObj.property("groupBiasCtor") );
0064 }
0065 
0066 ScriptableBiasFactory::ScriptableBiasFactory( QJSEngine *engine, bool groupBias )
0067 : QObject( engine )
0068 , m_groupBias( groupBias )
0069 , m_engine( engine )
0070 , m_enabled( false )
0071 {}
0072 
0073 ScriptableBiasFactory::~ScriptableBiasFactory()
0074 {
0075     Dynamic::BiasFactory::instance()->removeBiasFactory( this );
0076 }
0077 
0078 Dynamic::BiasPtr
0079 ScriptableBiasFactory::createBias()
0080 {
0081     ScriptableBias *bias;
0082     //if( m_groupBias )
0083     //    return new ScriptableGroupBias( this );
0084     //else
0085     bias = new ScriptableBias( this );
0086     Dynamic::BiasPtr biasPtr = Dynamic::BiasPtr( bias );
0087     QJSValue biasObject = bias->scriptObject();
0088     if( m_initFunction.isCallable() )
0089         m_initFunction.callWithInstance( biasObject, QJSValueList() << biasObject );
0090 
0091     return biasPtr;
0092 }
0093 
0094 QJSEngine*
0095 ScriptableBiasFactory::engine() const
0096 {
0097     return m_engine;
0098 }
0099 
0100 void
0101 ScriptableBiasFactory::setEnabled( bool enabled )
0102 {
0103     if( enabled )
0104     {
0105         if( !m_enabled )
0106             Dynamic::BiasFactory::instance()->registerNewBiasFactory( this );
0107     }
0108     else
0109         Dynamic::BiasFactory::instance()->removeBiasFactory( this );
0110     m_enabled = enabled;
0111 }
0112 
0113 bool
0114 ScriptableBiasFactory::enabled() const
0115 {
0116     return m_enabled;
0117 }
0118 
0119 void
0120 ScriptableBiasFactory::setName( const QString &name )
0121 {
0122     m_name = name;
0123 }
0124 
0125 QString
0126 ScriptableBiasFactory::name() const
0127 {
0128     return m_name;
0129 }
0130 
0131 QString
0132 ScriptableBiasFactory::i18nName() const
0133 {
0134     return m_i18nName;
0135 }
0136 
0137 QString
0138 ScriptableBiasFactory::i18nDescription() const
0139 {
0140     return m_description;
0141 }
0142 
0143 QJSValue
0144 ScriptableBiasFactory::initFunction() const
0145 {
0146     return m_initFunction;
0147 }
0148 
0149 void
0150 ScriptableBiasFactory::setInitFunction( const QJSValue &value )
0151 {
0152     m_initFunction = value;
0153 }
0154 
0155 void
0156 ScriptableBiasFactory::setI18nDescription( const QString &description )
0157 {
0158     m_description = description;
0159 }
0160 
0161 void
0162 ScriptableBiasFactory::setI18nName( const QString &i18nName )
0163 {
0164     m_i18nName = i18nName;
0165 }
0166 
0167 QJSValue
0168 ScriptableBiasFactory::widgetFunction() const
0169 {
0170     return m_widgetFunction;
0171 }
0172 
0173 void
0174 ScriptableBiasFactory::setWidgetFunction( const QJSValue &value )
0175 {
0176     // throw exception?
0177     //if( !value.isFunction() )
0178     m_widgetFunction = value;
0179 }
0180 
0181 void
0182 ScriptableBiasFactory::setFromXmlFunction( const QJSValue &value )
0183 {
0184     m_fromXmlFunction = value;
0185 }
0186 
0187 void
0188 ScriptableBiasFactory::setToXmlFunction( const QJSValue &value )
0189 {
0190     m_toXmlFunction = value;
0191 }
0192 
0193 QJSValue
0194 ScriptableBiasFactory::fromXmlFunction() const
0195 {
0196     return m_fromXmlFunction;
0197 }
0198 
0199 QJSValue
0200 ScriptableBiasFactory::toXmlFunction() const
0201 {
0202     return m_toXmlFunction;
0203 }
0204 
0205 QJSValue
0206 ScriptableBiasFactory::matchingTracksFunction() const
0207 {
0208     return m_matchingTracksFunction;
0209 }
0210 
0211 void
0212 ScriptableBiasFactory::setMatchingTracksFunction( const QJSValue &value )
0213 {
0214     m_matchingTracksFunction = value;
0215 }
0216 
0217 void
0218 ScriptableBiasFactory::setTrackMatchesFunction( const QJSValue &value )
0219 {
0220     m_trackMatchesFunction = value;
0221 }
0222 
0223 QJSValue
0224 ScriptableBiasFactory::trackMatchesFunction() const
0225 {
0226     return m_trackMatchesFunction;
0227 }
0228 
0229 void
0230 ScriptableBiasFactory::setToStringFunction( const QJSValue &value )
0231 {
0232     m_toStringFunction = value;
0233 }
0234 
0235 QJSValue
0236 ScriptableBiasFactory::toStringFunction() const
0237 {
0238     return m_toStringFunction;
0239 }
0240 
0241 /*********************************************************************************
0242 // ScriptableBias
0243 **********************************************************************************/
0244 void
0245 ScriptableBias::toXml( QXmlStreamWriter *writer ) const
0246 {
0247     if( m_scriptBias->toXmlFunction().isCallable() )
0248         m_scriptBias->fromXmlFunction().callWithInstance( m_biasObject,
0249                                               QJSValueList() << m_engine->toScriptValue<QXmlStreamWriter*>( writer ) );
0250     else
0251         Dynamic::AbstractBias::toXml( writer );
0252 }
0253 
0254 void
0255 ScriptableBias::fromXml( QXmlStreamReader *reader )
0256 {
0257     if( m_scriptBias->fromXmlFunction().isCallable() )
0258         m_scriptBias->fromXmlFunction().callWithInstance( m_biasObject,
0259                                               QJSValueList() << m_engine->toScriptValue<QXmlStreamReader*>( reader ) );
0260     else
0261         Dynamic::AbstractBias::fromXml( reader );
0262 }
0263 
0264 QWidget*
0265 ScriptableBias::widget( QWidget *parent )
0266 {
0267     QWidget *widget = dynamic_cast<QWidget*>( m_scriptBias->widgetFunction().callWithInstance( m_biasObject,
0268                                                                                   QJSValueList() << m_scriptBias->engine()->newQObject( parent ) ).toQObject() );
0269     if( widget )
0270         return widget;
0271     return Dynamic::AbstractBias::widget( parent );
0272 }
0273 
0274 void
0275 ScriptableBias::invalidate()
0276 {
0277     Dynamic::AbstractBias::invalidate();
0278 }
0279 
0280 Dynamic::TrackSet
0281 ScriptableBias::matchingTracks(const Meta::TrackList &playlist, int contextCount, int finalCount, const Dynamic::TrackCollectionPtr &universe ) const
0282 {
0283     DEBUG_BLOCK
0284     if( QThread::currentThread() == QCoreApplication::instance()->thread() )
0285         return slotMatchingTracks( playlist, contextCount, finalCount, universe );
0286 
0287     Dynamic::TrackSet retVal;
0288     Q_ASSERT( QMetaObject::invokeMethod( const_cast<ScriptableBias*>( this ), "slotMatchingTracks", Qt::BlockingQueuedConnection,
0289                                          Q_RETURN_ARG( Dynamic::TrackSet, retVal),
0290                                          Q_ARG( Meta::TrackList, playlist ),
0291                                          Q_ARG( int, contextCount ),
0292                                          Q_ARG( int, finalCount ),
0293                                          Q_ARG( Dynamic::TrackCollectionPtr, universe )
0294                                        ) );
0295     debug() << "Returning trackSet, trackCount " << retVal.trackCount() << ", isOutstanding " << retVal.isOutstanding();
0296     return retVal;
0297 }
0298 
0299 Dynamic::TrackSet
0300 ScriptableBias::slotMatchingTracks( const Meta::TrackList &playlist, int contextCount, int finalCount, const Dynamic::TrackCollectionPtr &universe ) const
0301 {
0302     Q_ASSERT( QThread::currentThread() == QCoreApplication::instance()->thread() );
0303     if( m_scriptBias->matchingTracksFunction().isCallable() )
0304     {
0305         QJSValue trackSetVal = m_scriptBias->matchingTracksFunction().callWithInstance( m_biasObject,
0306                                                                                 QJSValueList() << m_engine->toScriptValue<Meta::TrackList>( playlist )
0307                                                                                                    << contextCount
0308                                                                                                    << finalCount
0309                                                                                                    << m_engine->toScriptValue<QStringList>( universe->uids() ) );
0310         TrackSetExporter *trackSetExporter = dynamic_cast<TrackSetExporter*>( trackSetVal.toQObject() );
0311         if( trackSetExporter )
0312             return Dynamic::TrackSet( *trackSetExporter );
0313     }
0314     debug() << "Invalid trackSet received";
0315     return Dynamic::TrackSet( universe, false );
0316 }
0317 
0318 QString
0319 ScriptableBias::name() const
0320 {
0321     QString name;
0322     if( m_scriptBias )
0323         name = m_scriptBias->name();
0324     return name.isEmpty() ? Dynamic::AbstractBias::name() : name;
0325 }
0326 
0327 void
0328 ScriptableBias::ready( const Dynamic::TrackSet &trackSet )
0329 {
0330     debug() << "Received trackset, count: " << trackSet.trackCount() << "Is outstanding:" << trackSet.isOutstanding();
0331     Q_EMIT resultReady( trackSet );
0332 }
0333 
0334 void
0335 ScriptableBias::paintOperator( QPainter *painter, const QRect &rect, Dynamic::AbstractBias *bias )
0336 {
0337     Dynamic::AbstractBias::paintOperator( painter, rect, bias );
0338 }
0339 
0340 void
0341 ScriptableBias::replace(const Dynamic::BiasPtr &newBias )
0342 {
0343     Dynamic::AbstractBias::replace( newBias );
0344 }
0345 
0346 QString
0347 ScriptableBias::toString() const
0348 {
0349     return m_scriptBias->toStringFunction().call( QJSValueList() << m_biasObject ).toString();
0350 }
0351 
0352 bool
0353 ScriptableBias::trackMatches( int position, const Meta::TrackList& playlist, int contextCount ) const
0354 {
0355     if( m_scriptBias->trackMatchesFunction().isCallable() )
0356         return m_scriptBias->trackMatchesFunction().callWithInstance( m_biasObject,
0357                                                           QJSValueList() << position
0358                                                                              << m_engine->toScriptValue<Meta::TrackList>( playlist )
0359                                                                              << contextCount
0360                                                         ).toBool();
0361     return true;
0362 }
0363 
0364 ScriptableBias::ScriptableBias( ScriptableBiasFactory *biasProto )
0365 : m_scriptBias( biasProto )
0366 , m_engine( biasProto->engine() )
0367 {
0368     m_biasObject = m_engine->newQObject( this );
0369     QQmlEngine::setObjectOwnership( this, QQmlEngine::CppOwnership );
0370     connect( m_engine, &QObject::destroyed, this, &ScriptableBias::removeBias );
0371 }
0372 
0373 ScriptableBias::~ScriptableBias()
0374 {}
0375 
0376 void
0377 ScriptableBias::removeBias()
0378 {
0379     replace( Dynamic::BiasPtr( new Dynamic::ReplacementBias( name() ) ) );
0380 }
0381 
0382 /////////////////////////////////////////////////////////////////////////////////////////
0383 // TrackSetExporterWrapper
0384 /////////////////////////////////////////////////////////////////////////////////////////
0385 
0386 TrackSetExporterWrapper *TrackSetExporter::s_wrapper = nullptr;
0387 TrackSetExporterWrapper::TrackSetExporterWrapper( QJSEngine* engine )
0388 : QObject( engine )
0389 , m_engine (engine )
0390  {
0391 }
0392 QJSValue
0393 TrackSetExporterWrapper::trackSetConstructor( QJSValue arg0, QJSValue arg1 )
0394 {
0395     DEBUG_BLOCK
0396 
0397     TrackSetExporter *trackSetExporter = nullptr;
0398     // Called with 1 argument
0399     if ( !arg0.isUndefined() && arg1.isUndefined() )
0400     {
0401         TrackSetExporter *trackSetPrototype = dynamic_cast<TrackSetExporter*>( arg0.toQObject() );
0402         if( trackSetPrototype ) {
0403             trackSetExporter = new TrackSetExporter( Dynamic::TrackSet( *trackSetPrototype ) );
0404         }
0405     // Called with 2 arguments
0406     } else if ( !arg0.isUndefined() && arg1.isBool() ) {
0407         bool isFull = arg1.toBool();
0408         QStringList uidList;
0409         Meta::TrackList trackList;
0410         if( arg0.toVariant().canConvert<QStringList>() )
0411         {
0412             uidList = arg0.toVariant().toStringList();
0413             Q_ASSERT( !arg0.toVariant().canConvert<Meta::TrackList>() );
0414             trackSetExporter =  new TrackSetExporter( Dynamic::TrackSet( Dynamic::TrackCollectionPtr( new Dynamic::TrackCollection( uidList ) ), isFull ) );
0415         }
0416         else if( arg0.toVariant().canConvert<Meta::TrackList>() )
0417         {
0418             debug() << "In Meta::Tracklist TrackSet ctor";
0419             trackList = qjsvalue_cast<Meta::TrackList>( arg0 );
0420             foreach( const Meta::TrackPtr &track, trackList )
0421             {
0422                 if( track )
0423                     uidList << track->uidUrl();
0424             }
0425             trackSetExporter = new TrackSetExporter( Dynamic::TrackSet( Dynamic::TrackCollectionPtr( new Dynamic::TrackCollection( uidList ) ), isFull  ) );
0426         }
0427     }
0428     if( trackSetExporter == nullptr )
0429     {
0430         /* TODO - Use commented code once QT versions >= 5.12
0431         m_engine->throwError( QJSValue::SyntaxError, QStringLiteral("Invalid arguments for TrackSet!") );
0432         return QJSValue(QJSValue::UndefinedValue);
0433         */
0434         return m_engine->evaluate("throw new TypeError('Invalid arguments for TrackSet!')");
0435     }
0436 
0437     const QJSValue trackSetObject = m_engine->newQObject( trackSetExporter );
0438     return trackSetObject;
0439 }
0440 
0441 /////////////////////////////////////////////////////////////////////////////////////////
0442 // TrackSetExporter
0443 /////////////////////////////////////////////////////////////////////////////////////////
0444 
0445 void
0446 TrackSetExporter::init( QJSEngine *engine )
0447 {
0448     qRegisterMetaType<Dynamic::TrackSet>();
0449     QMetaType::registerConverter<Dynamic::TrackSet, QJSValue>( [=] (Dynamic::TrackSet trackSet) { return toScriptValue( engine, trackSet ); } );
0450     QMetaType::registerConverter<QJSValue, Dynamic::TrackSet>( [] (QJSValue jsValue) {
0451         Dynamic::TrackSet trackSet;
0452         fromScriptValue( jsValue, trackSet );
0453         return trackSet;
0454     } );
0455     if (s_wrapper == nullptr)
0456         s_wrapper = new TrackSetExporterWrapper( engine );
0457     engine->globalObject().setProperty( QStringLiteral("TrackSet"), engine->newQObject( s_wrapper ).property( "trackSetConstructor" ) );
0458 }
0459 
0460 void
0461 TrackSetExporter::fromScriptValue( const QJSValue &obj, Dynamic::TrackSet &trackSet )
0462 {
0463     DEBUG_BLOCK
0464     TrackSetExporter *trackSetProto = dynamic_cast<TrackSetExporter*>( obj.toQObject() );
0465     if( !trackSetProto )
0466         trackSet = Dynamic::TrackSet( Dynamic::TrackCollectionPtr( new Dynamic::TrackCollection( QStringList() ) ), false );
0467     else
0468         trackSet = *trackSetProto;
0469 }
0470 
0471 QJSValue
0472 TrackSetExporter::toScriptValue( QJSEngine *engine, const Dynamic::TrackSet &trackSet )
0473 {
0474     DEBUG_BLOCK
0475     TrackSetExporter *trackProto = new TrackSetExporter( trackSet );
0476     QJSValue val = engine->newQObject( trackProto );
0477     QQmlEngine::setObjectOwnership( trackProto, QQmlEngine::JavaScriptOwnership);
0478     return val;
0479 }
0480 
0481 bool
0482 TrackSetExporter::containsUid( const QString &uid ) const
0483 {
0484     return Dynamic::TrackSet::contains( uid );
0485 }
0486 
0487 
0488 void
0489 TrackSetExporter::reset( bool value )
0490 {
0491     Dynamic::TrackSet::reset( value );
0492 }
0493 
0494 void
0495 TrackSetExporter::intersectTrackSet( const Dynamic::TrackSet &trackSet)
0496 {
0497     Dynamic::TrackSet::intersect( trackSet );
0498 }
0499 
0500 void
0501 TrackSetExporter::intersectUids( const QStringList &uids )
0502 {
0503     Dynamic::TrackSet::intersect( uids );
0504 }
0505 
0506 void
0507 TrackSetExporter::subtractTrack( const Meta::TrackPtr &track )
0508 {
0509     Dynamic::TrackSet::subtract( track );
0510 }
0511 
0512 void
0513 TrackSetExporter::subtractTrackSet( const Dynamic::TrackSet &trackSet )
0514 {
0515     Dynamic::TrackSet::subtract( trackSet );
0516 }
0517 
0518 void
0519 TrackSetExporter::subtractUids( const QStringList &uids )
0520 {
0521     Dynamic::TrackSet::subtract( uids );
0522 }
0523 
0524 void
0525 TrackSetExporter::uniteTrack( const Meta::TrackPtr &track )
0526 {
0527     Dynamic::TrackSet::unite( track );
0528 }
0529 
0530 void
0531 TrackSetExporter::uniteTrackSet( const Dynamic::TrackSet &trackSet )
0532 {
0533     Dynamic::TrackSet::unite( trackSet );
0534 }
0535 
0536 void
0537 TrackSetExporter::uniteUids( const QStringList &uids )
0538 {
0539     Dynamic::TrackSet::unite( uids );
0540 }
0541 
0542 Meta::TrackPtr
0543 TrackSetExporter::getRandomTrack() const
0544 {
0545     return CollectionManager::instance()->trackForUrl( QUrl( Dynamic::TrackSet::getRandomTrack() ) );
0546 }
0547 
0548 bool
0549 TrackSetExporter::containsTrack( const Meta::TrackPtr &track ) const
0550 {
0551     return Dynamic::TrackSet::contains( track );
0552 }
0553 
0554 // private
0555 TrackSetExporter::TrackSetExporter( const Dynamic::TrackSet &trackSet )
0556 : QObject( nullptr )
0557 , TrackSet( trackSet )
0558 {}