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

0001 /****************************************************************************************
0002  * Copyright (c) 2008-2010 Soren Harward <stharward@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 "APG::Preset"
0018 
0019 #include "Preset.h"
0020 
0021 #include "ConstraintNode.h"
0022 #include "ConstraintFactory.h"
0023 #include "ConstraintSolver.h"
0024 #include "constraints/TrackSpreader.h"
0025 
0026 #include "core/logger/Logger.h"
0027 #include "core/meta/Meta.h"
0028 #include "core/support/Components.h"
0029 #include "core/support/Debug.h"
0030 #include "core-impl/collections/support/CollectionManager.h"
0031 #include "playlist/PlaylistController.h"
0032 
0033 #include <KLocalizedString>
0034 
0035 #include <QDomElement>
0036 
0037 #include <ThreadWeaver/Queue>
0038 
0039 APG::PresetPtr
0040 APG::Preset::createFromXml( QDomElement& xmlelem )
0041 {
0042     DEBUG_BLOCK
0043 
0044     if ( xmlelem.isNull() ) {
0045         PresetPtr t( new Preset( i18n("New playlist preset") ) );
0046         return t;
0047     } else {
0048         PresetPtr t( new Preset( i18n("Unnamed playlist preset"), xmlelem ) );
0049         return t;
0050     }
0051 }
0052 
0053 APG::PresetPtr
0054 APG::Preset::createNew()
0055 {
0056     DEBUG_BLOCK
0057 
0058     PresetPtr t( new Preset( i18n("New playlist preset") ) );
0059     return t;
0060 }
0061 
0062 APG::Preset::Preset( const QString& title, QDomElement& xmlelem )
0063         : m_title( title )
0064         , m_constraintTreeRoot( nullptr )
0065 {
0066 
0067     if ( xmlelem.hasAttribute( QStringLiteral("title") ) ) {
0068         m_title = xmlelem.attribute( QStringLiteral("title") );
0069     } else {
0070         m_title = i18n("Unnamed playlist preset");
0071     }
0072     for ( int i = 0; i < xmlelem.childNodes().count(); i++ ) {
0073         QDomElement childXmlElem = xmlelem.childNodes().item( i ).toElement();
0074         if ( !childXmlElem.isNull() ) {
0075             if ( childXmlElem.tagName() == QLatin1String("constrainttree") ) {
0076                 m_constraintTreeRoot = ConstraintFactory::instance()->createGroup( childXmlElem, nullptr );
0077             } else {
0078                 error() << "unknown child: " << childXmlElem.nodeName();
0079             }
0080         }
0081     }
0082 
0083     if ( !m_constraintTreeRoot ) {
0084         m_constraintTreeRoot = ConstraintFactory::instance()->createGroup( nullptr );
0085     }
0086 }
0087 
0088 APG::Preset::Preset( const QString& title )
0089     : m_title( title )
0090 {
0091 
0092     m_constraintTreeRoot = ConstraintFactory::instance()->createGroup( nullptr );
0093 }
0094 
0095 APG::Preset::~Preset()
0096 {
0097     delete m_constraintTreeRoot;
0098 }
0099 
0100 QDomElement*
0101 APG::Preset::toXml( QDomDocument& xmldoc ) const
0102 {
0103     QDomElement e = xmldoc.createElement( QStringLiteral("generatorpreset") );
0104     e.setAttribute( QStringLiteral("title"), m_title );
0105     m_constraintTreeRoot->toXml( xmldoc, e );
0106     return new QDomElement( e );
0107 }
0108 
0109 void
0110 APG::Preset::generate( int q )
0111 {
0112     ConstraintSolver* solver = new ConstraintSolver( m_constraintTreeRoot, q );
0113     connect( solver, &ConstraintSolver::readyToRun, this, &Preset::queueSolver );
0114 }
0115 
0116 void APG::Preset::queueSolver() {
0117 
0118     /* Workaround for a design quirk of Weavers.  A Weaver will not
0119      * continuously poll queued but previously unrunnable jobs to see if they
0120      * are are runnable (and won't start them if they are), so what tends to
0121      * happen is that if a job is queued before it's ready to run, it fails the
0122      * canBeExecuted() check, and sits in the queue until something wakes up
0123      * the Weaver (eg, queueing another job).  Eventually, you get a pileup of
0124      * obsolete jobs in the queue, and those that do run properly return
0125      * obsolete results.  So to avoid that problem, we avoid queueing the job
0126      * until it's ready to run, and then the Weaver will start running it
0127      * pretty much immediately. -- sth */
0128 
0129     Q_EMIT lock( true );
0130 
0131     ConstraintSolver* s = static_cast<ConstraintSolver*>( sender() );
0132     Amarok::Logger::newProgressOperation( s, i18n("Generating a new playlist"), s->iterationCount(), s, &ConstraintSolver::requestAbort, Qt::QueuedConnection );
0133     connect( s, &APG::ConstraintSolver::done, this, &Preset::solverFinished, Qt::QueuedConnection );
0134 
0135     m_constraintTreeRoot->addChild( ConstraintTypes::TrackSpreader::createNew( m_constraintTreeRoot ), 0 ); // private mandatory constraint
0136 
0137     ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(s) );
0138 }
0139 
0140 void
0141 APG::Preset::solverFinished( ThreadWeaver::JobPointer job )
0142 {
0143     m_constraintTreeRoot->removeChild( 0 ); // remove the TrackSpreader
0144 
0145     ConstraintSolver* solver = static_cast<ConstraintSolver*>( job.data() );
0146     if ( job->success() ) {
0147         debug() << "Solver" << solver->serial() << "finished successfully";
0148         if ( !solver->satisfied() ) {
0149             Amarok::Logger::longMessage(
0150                         i18n("The playlist generator created a playlist which does not meet all "
0151                              "of your constraints.  If you are not satisfied with the results, "
0152                              "try loosening or removing some constraints and then generating a "
0153                              "new playlist.") );
0154         }
0155         The::playlistController()->insertOptioned( solver->getSolution(), Playlist::OnReplacePlaylistAction );
0156     } else {
0157         debug() << "Ignoring results from aborted Solver" << solver->serial();
0158     }
0159 
0160     Q_EMIT lock( false );
0161 }