File indexing completed on 2024-05-05 04:48:30

0001 /****************************************************************************************
0002  * Copyright (c) 2008 Daniel Caleb Jones <danielcjones@gmail.com>                       *
0003  * Copyright (c) 2010, 2013 Ralf Engels <ralf-engels@gmx.de>                            *
0004  *                                                                                      *
0005  * This program is free software; you can redistribute it and/or modify it under        *
0006  * the terms of the GNU General Public License as published by the Free Software        *
0007  * Foundation; either version 2 of the License, or (at your option) version 3 or        *
0008  * any later version accepted by the membership of KDE e.V. (or its successor approved  *
0009  * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of  *
0010  * version 3 of the license.                                                            *
0011  *                                                                                      *
0012  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0013  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0014  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0015  *                                                                                      *
0016  * You should have received a copy of the GNU General Public License along with         *
0017  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0018  ****************************************************************************************/
0019 
0020 #define DEBUG_PREFIX "BiasSolver"
0021 
0022 #include "BiasSolver.h"
0023 
0024 #include "amarokconfig.h"
0025 #include "core/meta/Meta.h"
0026 #include "core/meta/support/MetaConstants.h"
0027 #include "core/support/Debug.h"
0028 #include "core/collections/QueryMaker.h"
0029 #include "core-impl/collections/support/CollectionManager.h"
0030 
0031 #include <ThreadWeaver/Thread>
0032 
0033 #include <QHash>
0034 #include <QMutexLocker>
0035 
0036 #include <cmath>
0037 
0038 /* These number are black magic. The best values can only be obtained through
0039  * exhaustive trial and error or writing another optimization program to
0040  * optimize this optimization program. They are very sensitive. Be careful */
0041 
0042 namespace Dynamic
0043 {
0044 
0045 class SolverList
0046 {
0047     public:
0048 
0049     SolverList( const Meta::TrackList &trackList,
0050                 int contextCount,
0051                 const BiasPtr &bias )
0052         : m_trackList(trackList)
0053         , m_contextCount( contextCount )
0054         , m_bias( bias )
0055     {}
0056 
0057     void appendTrack( const Meta::TrackPtr &track )
0058     {
0059         m_trackList.append( track );
0060     }
0061 
0062     void removeTrack()
0063     {
0064         m_trackList.removeLast();
0065     }
0066 
0067     SolverList &operator=( const SolverList& x )
0068     {
0069         m_trackList = x.m_trackList;
0070         m_contextCount = x.m_contextCount;
0071         m_bias = x.m_bias;
0072 
0073         return *this;
0074     }
0075 
0076     Meta::TrackList m_trackList;
0077     int m_contextCount; // the number of tracks belonging to the context
0078     BiasPtr m_bias;
0079 };
0080 
0081 
0082 
0083 BiasSolver::BiasSolver( int n, const BiasPtr &bias, const Meta::TrackList &context )
0084     : m_n( n )
0085     , m_bias( bias )
0086     , m_context( context )
0087     , m_abortRequested( false )
0088     , m_currentProgress( 0 )
0089 {
0090     debug() << "CREATING BiasSolver in thread:" << QThread::currentThreadId() << "to get"<<n<<"tracks with"<<context.count()<<"context";
0091 
0092     m_allowDuplicates = AmarokConfig::dynamicDuplicates();
0093 
0094     getTrackCollection();
0095 
0096     connect( m_bias.data(), &Dynamic::AbstractBias::resultReady,
0097              this, &BiasSolver::biasResultReady );
0098 }
0099 
0100 
0101 BiasSolver::~BiasSolver()
0102 {
0103     debug() << "DESTROYING BiasSolver in thread:" << QThread::currentThreadId();
0104     Q_EMIT endProgressOperation( this );
0105 }
0106 
0107 
0108 void
0109 BiasSolver::requestAbort()
0110 {
0111     m_abortRequested = true;
0112     Q_EMIT endProgressOperation( this );
0113 }
0114 
0115 bool
0116 BiasSolver::success() const
0117 {
0118     return !m_abortRequested;
0119 }
0120 
0121 void
0122 BiasSolver::setAutoDelete( bool autoDelete )
0123 {
0124     if( autoDelete )
0125     {
0126         if( isFinished() )
0127             deleteLater();
0128         connect( this, &BiasSolver::done, this, &BiasSolver::deleteLater );
0129     }
0130     else
0131     {
0132         disconnect( this, &BiasSolver::done, this, &BiasSolver::deleteLater );
0133     }
0134 }
0135 
0136 
0137 void
0138 BiasSolver::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread )
0139 {
0140     Q_UNUSED(self);
0141     Q_UNUSED(thread);
0142     DEBUG_BLOCK
0143 
0144     debug() << "BiasSolver::run in thread:" << QThread::currentThreadId();
0145     m_startTime = QDateTime::currentDateTime();
0146 
0147     // wait until we get the track collection
0148     {
0149         QMutexLocker locker( &m_collectionResultsMutex );
0150         if( !m_trackCollection )
0151         {
0152             debug() << "waiting for collection results";
0153             m_collectionResultsReady.wait( &m_collectionResultsMutex );
0154         }
0155         debug() << "collection has" << m_trackCollection->count()<<"uids";
0156     }
0157 
0158     debug() << "generating playlist";
0159     SolverList list( m_context, m_context.count(), m_bias );
0160     addTracks( &list );
0161     debug() << "found solution"<<list.m_trackList.count()<<"time"<< m_startTime.msecsTo( QDateTime::currentDateTime() );
0162 
0163     m_solution = list.m_trackList.mid( m_context.count() );
0164 //    setFinished( true );
0165     setStatus(Status_Success);
0166 }
0167 
0168 void
0169 BiasSolver::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
0170 {
0171     Q_EMIT started(self);
0172     ThreadWeaver::Job::defaultBegin(self, thread);
0173 }
0174 
0175 void
0176 BiasSolver::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
0177 {
0178     ThreadWeaver::Job::defaultEnd(self, thread);
0179     if (!self->success()) {
0180         Q_EMIT failed(self);
0181     }
0182     Q_EMIT done(self);
0183 }
0184 
0185 void
0186 BiasSolver::addTracks( SolverList *list )
0187 {
0188     bool firstTrack = ( list->m_trackList.count() == list->m_contextCount );
0189 
0190     if( m_abortRequested )
0191         return;
0192 
0193     updateProgress( list );
0194 
0195     if( list->m_trackList.count() >= list->m_contextCount + m_n )
0196         return; // we have all tracks
0197 
0198     TrackSet set = matchingTracks( list->m_trackList );
0199     if( !m_allowDuplicates )
0200         set = withoutDuplicate( list->m_trackList.count(), list->m_trackList, set );
0201 
0202     if( set.trackCount() == 0 )
0203         return; // no candidates
0204 
0205     // debug() << "addTracks at"<<list->m_trackList.count()<<"candidates:"<<set.trackCount()<<"time"<< m_startTime.msecsTo( QDateTime::currentDateTime() );
0206 
0207     for( int tries = 0; tries < 5 || firstTrack ; tries++ )
0208     {
0209         if( m_abortRequested )
0210             return;
0211 
0212         list->appendTrack( getRandomTrack( set ) );
0213         addTracks( list ); // add another track recursively
0214         if( list->m_trackList.count() >= list->m_contextCount + m_n )
0215             return; // we have all tracks
0216 
0217         // if time is up just try to fill the list as much as possible not cleaning up
0218         if( m_startTime.msecsTo( QDateTime::currentDateTime() ) > MAX_TIME_MS )
0219             return;
0220 
0221         list->removeTrack();
0222     }
0223 }
0224 
0225 
0226 Meta::TrackList
0227 BiasSolver::solution()
0228 {
0229     return m_solution;
0230 }
0231 
0232 
0233 Meta::TrackPtr
0234 BiasSolver::getRandomTrack( const TrackSet& subset ) const
0235 {
0236     if( subset.trackCount() == 0 )
0237         return Meta::TrackPtr();
0238 
0239     Meta::TrackPtr track;
0240 
0241     // this is really dumb, but we sometimes end up with uids that don't point to anything
0242     int giveup = 50;
0243     while( giveup-- && !track )
0244         track = trackForUid( subset.getRandomTrack() );
0245 
0246     if( !track )
0247         error() << "track is 0 in BiasSolver::getRandomTrack()";
0248 
0249     return track;
0250 }
0251 
0252 Meta::TrackPtr
0253 BiasSolver::trackForUid( const QString& uid ) const
0254 {
0255     const QUrl url( uid );
0256     Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( url );
0257 
0258     if( !track )
0259         warning() << "trackForUid returned no track for "<<uid;
0260     return track;
0261 }
0262 
0263 
0264 // ---- getting the matchingTracks ----
0265 
0266 void
0267 BiasSolver::biasResultReady( const TrackSet &set )
0268 {
0269     QMutexLocker locker( &m_biasResultsMutex );
0270     m_tracks = set;
0271     m_biasResultsReady.wakeAll();
0272 }
0273 
0274 TrackSet
0275 BiasSolver::matchingTracks( const Meta::TrackList& playlist ) const
0276 {
0277     QMutexLocker locker( &m_biasResultsMutex );
0278     m_tracks = m_bias->matchingTracks( playlist,
0279                                        m_context.count(), m_context.count() + m_n,
0280                                        m_trackCollection );
0281     if( m_tracks.isOutstanding() )
0282         m_biasResultsReady.wait( &m_biasResultsMutex );
0283 
0284     // debug() << "BiasSolver::matchingTracks returns"<<m_tracks.trackCount()<<"of"<<m_trackCollection->count()<<"tracks.";
0285 
0286     return m_tracks;
0287 }
0288 
0289 Dynamic::TrackSet
0290 BiasSolver::withoutDuplicate( int position, const Meta::TrackList& playlist,
0291                               const Dynamic::TrackSet& oldSet )
0292 {
0293     Dynamic::TrackSet result = Dynamic::TrackSet( oldSet );
0294     for( int i = 0; i < playlist.count(); i++ )
0295         if( i != position && playlist[i] )
0296             result.subtract( playlist[i] );
0297 
0298     return result;
0299 }
0300 
0301 
0302 // ---- getting the TrackCollection ----
0303 
0304 void
0305 BiasSolver::trackCollectionResultsReady( const QStringList &uids )
0306 {
0307     m_collectionUids.append( uids );
0308 }
0309 
0310 void
0311 BiasSolver::trackCollectionDone()
0312 {
0313     QMutexLocker locker( &m_collectionResultsMutex );
0314 
0315     m_trackCollection = TrackCollectionPtr( new TrackCollection( m_collectionUids ) );
0316     m_collectionUids.clear();
0317 
0318     m_collectionResultsReady.wakeAll();
0319 }
0320 
0321 void
0322 BiasSolver::getTrackCollection()
0323 {
0324     // get all the unique ids from the collection manager
0325     Collections::QueryMaker *qm = CollectionManager::instance()->queryMaker();
0326     qm->setQueryType( Collections::QueryMaker::Custom );
0327     qm->addReturnValue( Meta::valUniqueId );
0328     qm->setAutoDelete( true );
0329 
0330     connect( qm, &Collections::QueryMaker::newResultReady,
0331              this, &BiasSolver::trackCollectionResultsReady );
0332     connect( qm, &Collections::QueryMaker::queryDone,
0333              this, &BiasSolver::trackCollectionDone );
0334 
0335     qm->run();
0336 }
0337 
0338 void
0339 BiasSolver::updateProgress( const SolverList* list )
0340 {
0341     if( m_n <= 0 )
0342         return;
0343 
0344     int progress = (int)(100.0 * (double)( list->m_trackList.count() - list->m_contextCount ) / m_n );
0345 
0346     while( m_currentProgress < progress )
0347     {
0348         m_currentProgress++;
0349         Q_EMIT incrementProgress();
0350     }
0351 }
0352 
0353 
0354 }
0355