File indexing completed on 2024-05-19 04:49:42

0001 /****************************************************************************************
0002  * Copyright (c) 2008 Daniel Jones <danielcjones@gmail.com>                             *
0003  * Copyright (c) 2009 Leo Franchi <lfranchi@kde.org>                                    *
0004  * Copyright (c) 2010,2011 Ralf Engels <ralf-engels@gmx.de>                                  *
0005  *                                                                                      *
0006  * This program is free software; you can redistribute it and/or modify it under        *
0007  * the terms of the GNU General Public License as published by the Free Software        *
0008  * Foundation; either version 2 of the License, or (at your option) version 3 or        *
0009  * any later version accepted by the membership of KDE e.V. (or its successor approved  *
0010  * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of  *
0011  * version 3 of the license.                                                            *
0012  *                                                                                      *
0013  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0014  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0015  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0016  *                                                                                      *
0017  * You should have received a copy of the GNU General Public License along with         *
0018  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0019  ****************************************************************************************/
0020 
0021 #define DEBUG_PREFIX "TagMatchBias"
0022 
0023 #include "TagMatchBias.h"
0024 
0025 #include "core/collections/Collection.h"
0026 #include "core/collections/QueryMaker.h"
0027 #include "core/meta/Meta.h"
0028 #include "core/support/Debug.h"
0029 #include "core-impl/collections/support/CollectionManager.h"
0030 #include "dynamic/TrackSet.h"
0031 
0032 #include <QDateTime>
0033 #include <QCheckBox>
0034 #include <QHBoxLayout>
0035 #include <QLabel>
0036 #include <QTimer>
0037 #include <QXmlStreamReader>
0038 #include <QXmlStreamWriter>
0039 
0040 QString
0041 Dynamic::TagMatchBiasFactory::i18nName() const
0042 { return i18nc("Name of the \"TagMatch\" bias", "Match meta tag"); }
0043 
0044 QString
0045 Dynamic::TagMatchBiasFactory::name() const
0046 { return Dynamic::TagMatchBias::sName(); }
0047 
0048 QString
0049 Dynamic::TagMatchBiasFactory::i18nDescription() const
0050 { return i18nc("Description of the \"TagMatch\" bias",
0051                    "The \"TagMatch\" bias adds tracks that\n"
0052                    "fulfill a specific condition."); }
0053 
0054 Dynamic::BiasPtr
0055 Dynamic::TagMatchBiasFactory::createBias()
0056 { return Dynamic::BiasPtr( new Dynamic::TagMatchBias() ); }
0057 
0058 
0059 // ----- SimpleMatchBias --------
0060 
0061 Dynamic::SimpleMatchBias::SimpleMatchBias()
0062     : m_invert( false )
0063 { }
0064 
0065 void
0066 Dynamic::SimpleMatchBias::fromXml( QXmlStreamReader *reader )
0067 {
0068     m_invert = reader->attributes().value( QStringLiteral("invert") ).toString().toInt();
0069 }
0070 
0071 void
0072 Dynamic::SimpleMatchBias::toXml( QXmlStreamWriter *writer ) const
0073 {
0074     if( m_invert )
0075         writer->writeAttribute(QStringLiteral("invert"), QStringLiteral("1"));
0076 }
0077 
0078 bool
0079 Dynamic::SimpleMatchBias::isInvert() const
0080 {
0081     return m_invert;
0082 }
0083 
0084 void
0085 Dynamic::SimpleMatchBias::setInvert( bool value )
0086 {
0087     DEBUG_BLOCK;
0088     if( value == m_invert )
0089         return;
0090 
0091     m_invert = value;
0092     // setting "invert" does not invalidate the search results
0093     Q_EMIT changed( BiasPtr(this) );
0094 }
0095 
0096 
0097 Dynamic::TrackSet
0098 Dynamic::SimpleMatchBias::matchingTracks( const Meta::TrackList& playlist,
0099                                           int contextCount, int finalCount,
0100                                           const Dynamic::TrackCollectionPtr &universe ) const
0101 {
0102     Q_UNUSED( playlist );
0103     Q_UNUSED( contextCount );
0104     Q_UNUSED( finalCount );
0105 
0106     if( tracksValid() )
0107         return m_tracks;
0108 
0109     m_tracks = Dynamic::TrackSet( universe, m_invert );
0110 
0111     QTimer::singleShot(0,
0112                        const_cast<SimpleMatchBias*>(this),
0113                        &SimpleMatchBias::newQuery); // create the new query from my parent thread
0114 
0115     return Dynamic::TrackSet();
0116 }
0117 
0118 void
0119 Dynamic::SimpleMatchBias::updateReady( const QStringList &uids )
0120 {
0121     if( m_invert )
0122         m_tracks.subtract( uids );
0123     else
0124         m_tracks.unite( uids );
0125 }
0126 
0127 void
0128 Dynamic::SimpleMatchBias::updateFinished()
0129 {
0130     m_tracksTime = QDateTime::currentDateTime();
0131     m_qm.reset();
0132     debug() << "SimpleMatchBias::"<<name()<<"updateFinished"<<m_tracks.trackCount();
0133     Q_EMIT resultReady( m_tracks );
0134 }
0135 
0136 bool
0137 Dynamic::SimpleMatchBias::tracksValid() const
0138 {
0139     return m_tracksTime.isValid() &&
0140         m_tracksTime.secsTo(QDateTime::currentDateTime()) < 60 * 3;
0141 }
0142 
0143 bool
0144 Dynamic::SimpleMatchBias::trackMatches( int position,
0145                                         const Meta::TrackList& playlist,
0146                                         int contextCount ) const
0147 {
0148     Q_UNUSED( contextCount );
0149     if( tracksValid() )
0150         return m_tracks.contains( playlist.at(position) );
0151     return true; // we should have already received the tracks before some-one calls trackMatches
0152 }
0153 
0154 
0155 void
0156 Dynamic::SimpleMatchBias::invalidate()
0157 {
0158     m_tracksTime = QDateTime();
0159     m_tracks = TrackSet();
0160     // TODO: need to finish a running query
0161     m_qm.reset();
0162 }
0163 
0164 // ---------- TagMatchBias --------
0165 
0166 
0167 Dynamic::TagMatchBiasWidget::TagMatchBiasWidget( Dynamic::TagMatchBias* bias,
0168                                                            QWidget* parent )
0169     : QWidget( parent )
0170     , m_bias( bias )
0171 {
0172     QVBoxLayout *layout = new QVBoxLayout( this );
0173 
0174     QHBoxLayout *invertLayout = new QHBoxLayout();
0175     m_invertBox = new QCheckBox();
0176     QLabel *label = new QLabel( i18n("Invert condition") );
0177     label->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
0178     label->setBuddy( m_invertBox );
0179     invertLayout->addWidget( m_invertBox, 0 );
0180     invertLayout->addWidget( label, 1 );
0181     layout->addLayout(invertLayout);
0182 
0183     m_queryWidget = new MetaQueryWidget();
0184     layout->addWidget( m_queryWidget );
0185 
0186     syncControlsToBias();
0187 
0188     connect( m_invertBox, &QCheckBox::toggled,
0189              this, &TagMatchBiasWidget::syncBiasToControls );
0190     connect( m_queryWidget, &MetaQueryWidget::changed,
0191              this, &TagMatchBiasWidget::syncBiasToControls );
0192 }
0193 
0194 void
0195 Dynamic::TagMatchBiasWidget::syncControlsToBias()
0196 {
0197     m_queryWidget->setFilter( m_bias->filter() );
0198     m_invertBox->setChecked( m_bias->isInvert() );
0199 }
0200 
0201 void
0202 Dynamic::TagMatchBiasWidget::syncBiasToControls()
0203 {
0204     m_bias->setFilter( m_queryWidget->filter() );
0205     m_bias->setInvert( m_invertBox->isChecked() );
0206 }
0207 
0208 
0209 
0210 // ----- TagMatchBias --------
0211 
0212 Dynamic::TagMatchBias::TagMatchBias()
0213     : SimpleMatchBias()
0214 { }
0215 
0216 void
0217 Dynamic::TagMatchBias::fromXml( QXmlStreamReader *reader )
0218 {
0219     SimpleMatchBias::fromXml( reader );
0220     while (!reader->atEnd()) {
0221         reader->readNext();
0222 
0223         if( reader->isStartElement() )
0224         {
0225             QStringRef name = reader->name();
0226             if( name == "field" )
0227                 m_filter.setField( Meta::fieldForPlaylistName( reader->readElementText(QXmlStreamReader::SkipChildElements) ) );
0228             else if( name == "numValue" )
0229                 m_filter.numValue = reader->readElementText(QXmlStreamReader::SkipChildElements).toUInt();
0230             else if( name == "numValue2" )
0231                 m_filter.numValue2 = reader->readElementText(QXmlStreamReader::SkipChildElements).toUInt();
0232             else if( name == "value" )
0233                 m_filter.value = reader->readElementText(QXmlStreamReader::SkipChildElements);
0234             else if( name == "condition" )
0235                 m_filter.condition = conditionForName( reader->readElementText(QXmlStreamReader::SkipChildElements) );
0236             else
0237             {
0238                 debug()<<"Unexpected xml start element"<<reader->name()<<"in input";
0239                 reader->skipCurrentElement();
0240             }
0241         }
0242         else if( reader->isEndElement() )
0243         {
0244             break;
0245         }
0246     }
0247 }
0248 
0249 void
0250 Dynamic::TagMatchBias::toXml( QXmlStreamWriter *writer ) const
0251 {
0252     SimpleMatchBias::toXml( writer );
0253     writer->writeTextElement( QStringLiteral("field"), Meta::playlistNameForField( m_filter.field() ) );
0254 
0255     if( m_filter.isNumeric() )
0256     {
0257         writer->writeTextElement( QStringLiteral("numValue"),  QString::number( m_filter.numValue ) );
0258         writer->writeTextElement( QStringLiteral("numValue2"), QString::number( m_filter.numValue2 ) );
0259     }
0260     else
0261     {
0262         writer->writeTextElement( QStringLiteral("value"), m_filter.value );
0263     }
0264 
0265     writer->writeTextElement( QStringLiteral("condition"), nameForCondition( m_filter.condition ) );
0266 }
0267 
0268 QString
0269 Dynamic::TagMatchBias::sName()
0270 {
0271     return QStringLiteral( "tagMatchBias" );
0272 }
0273 
0274 QString
0275 Dynamic::TagMatchBias::name() const
0276 {
0277     return Dynamic::TagMatchBias::sName();
0278 }
0279 
0280 QString
0281 Dynamic::TagMatchBias::toString() const
0282 {
0283     if( isInvert() )
0284         return i18nc("Inverted condition in tag match bias",
0285                      "Not %1", m_filter.toString() );
0286     else
0287         return m_filter.toString();
0288 }
0289 
0290 QWidget*
0291 Dynamic::TagMatchBias::widget( QWidget* parent )
0292 {
0293     return new Dynamic::TagMatchBiasWidget( this, parent );
0294 }
0295 
0296 bool
0297 Dynamic::TagMatchBias::trackMatches( int position,
0298                                      const Meta::TrackList& playlist,
0299                                      int contextCount ) const
0300 {
0301     Q_UNUSED( contextCount );
0302     if( tracksValid() )
0303         return m_tracks.contains( playlist.at(position) );
0304     else
0305         return matches( playlist.at(position) );
0306 }
0307 
0308 MetaQueryWidget::Filter
0309 Dynamic::TagMatchBias::filter() const
0310 {
0311     return m_filter;
0312 }
0313 
0314 void
0315 Dynamic::TagMatchBias::setFilter( const MetaQueryWidget::Filter &filter)
0316 {
0317     DEBUG_BLOCK;
0318     m_filter = filter;
0319     invalidate();
0320     Q_EMIT changed( BiasPtr(this) );
0321 }
0322 
0323 void
0324 Dynamic::TagMatchBias::newQuery()
0325 {
0326     DEBUG_BLOCK;
0327 
0328     // ok, I need a new query maker
0329     m_qm.reset( CollectionManager::instance()->queryMaker() );
0330 
0331     // -- set the querymaker
0332     if( m_filter.isDate() )
0333     {
0334         switch( m_filter.condition )
0335         {
0336         case MetaQueryWidget::LessThan:
0337         case MetaQueryWidget::Equals:
0338         case MetaQueryWidget::GreaterThan:
0339             m_qm->addNumberFilter( m_filter.field(), m_filter.numValue,
0340                                 (Collections::QueryMaker::NumberComparison)m_filter.condition );
0341             break;
0342         case MetaQueryWidget::Between:
0343             m_qm->beginAnd();
0344             m_qm->addNumberFilter( m_filter.field(), qMin(m_filter.numValue, m_filter.numValue2)-1,
0345                                 Collections::QueryMaker::GreaterThan );
0346             m_qm->addNumberFilter( m_filter.field(), qMax(m_filter.numValue, m_filter.numValue2)+1,
0347                                 Collections::QueryMaker::LessThan );
0348             m_qm->endAndOr();
0349             break;
0350         case MetaQueryWidget::OlderThan:
0351             m_qm->addNumberFilter( m_filter.field(), QDateTime::currentDateTimeUtc().toSecsSinceEpoch() - m_filter.numValue,
0352                                 Collections::QueryMaker::LessThan );
0353             break;
0354         default:
0355             ;
0356         }
0357     }
0358     else if( m_filter.isNumeric() )
0359     {
0360         switch( m_filter.condition )
0361         {
0362         case MetaQueryWidget::LessThan:
0363         case MetaQueryWidget::Equals:
0364         case MetaQueryWidget::GreaterThan:
0365             m_qm->addNumberFilter( m_filter.field(), m_filter.numValue,
0366                                 (Collections::QueryMaker::NumberComparison)m_filter.condition );
0367             break;
0368         case MetaQueryWidget::Between:
0369             m_qm->beginAnd();
0370             m_qm->addNumberFilter( m_filter.field(), qMin(m_filter.numValue, m_filter.numValue2)-1,
0371                                 Collections::QueryMaker::GreaterThan );
0372             m_qm->addNumberFilter( m_filter.field(), qMax(m_filter.numValue, m_filter.numValue2)+1,
0373                                 Collections::QueryMaker::LessThan );
0374             m_qm->endAndOr();
0375             break;
0376         default:
0377             ;
0378         }
0379     }
0380     else
0381     {
0382         switch( m_filter.condition )
0383         {
0384         case MetaQueryWidget::Equals:
0385             m_qm->addFilter( m_filter.field(), m_filter.value, true, true );
0386             break;
0387         case MetaQueryWidget::Contains:
0388             if( m_filter.field() == 0 )
0389             {
0390                 // simple search
0391                 // TODO: split different words and make separate searches
0392                 m_qm->beginOr();
0393                 m_qm->addFilter( Meta::valArtist,  m_filter.value );
0394                 m_qm->addFilter( Meta::valTitle,   m_filter.value );
0395                 m_qm->addFilter( Meta::valAlbum,   m_filter.value );
0396                 m_qm->addFilter( Meta::valGenre,   m_filter.value );
0397                 m_qm->addFilter( Meta::valUrl,     m_filter.value );
0398                 m_qm->addFilter( Meta::valComment, m_filter.value );
0399                 m_qm->addFilter( Meta::valLabel,   m_filter.value );
0400                 m_qm->endAndOr();
0401             }
0402             else
0403             {
0404                 m_qm->addFilter( m_filter.field(), m_filter.value );
0405             }
0406             break;
0407         default:
0408             ;
0409         }
0410     }
0411 
0412     m_qm->setQueryType( Collections::QueryMaker::Custom );
0413     m_qm->addReturnValue( Meta::valUniqueId );
0414 
0415     connect( m_qm.data(), &Collections::QueryMaker::newResultReady,
0416              this, &TagMatchBias::updateReady, Qt::QueuedConnection );
0417     connect( m_qm.data(), &Collections::QueryMaker::queryDone,
0418              this, &TagMatchBias::updateFinished, Qt::QueuedConnection );
0419     m_qm.data()->run();
0420 }
0421 
0422 QString
0423 Dynamic::TagMatchBias::nameForCondition( MetaQueryWidget::FilterCondition cond )
0424 {
0425     switch( cond )
0426     {
0427     case MetaQueryWidget::Equals:      return QStringLiteral("equals");
0428     case MetaQueryWidget::GreaterThan: return QStringLiteral("greater");
0429     case MetaQueryWidget::LessThan:    return QStringLiteral("less");
0430     case MetaQueryWidget::Between:     return QStringLiteral("between");
0431     case MetaQueryWidget::OlderThan:   return QStringLiteral("older");
0432     case MetaQueryWidget::Contains:    return QStringLiteral("contains");
0433     default:
0434         ;// the other conditions are only for the advanced playlist generator
0435     }
0436     return QString();
0437 }
0438 
0439 MetaQueryWidget::FilterCondition
0440 Dynamic::TagMatchBias::conditionForName( const QString &name )
0441 {
0442     if( name == QLatin1String("equals") )        return MetaQueryWidget::Equals;
0443     else if( name == QLatin1String("greater") )  return MetaQueryWidget::GreaterThan;
0444     else if( name == QLatin1String("less") )     return MetaQueryWidget::LessThan;
0445     else if( name == QLatin1String("between") )  return MetaQueryWidget::Between;
0446     else if( name == QLatin1String("older") )    return MetaQueryWidget::OlderThan;
0447     else if( name == QLatin1String("contains") ) return MetaQueryWidget::Contains;
0448     else return MetaQueryWidget::Equals;
0449 }
0450 
0451 bool
0452 Dynamic::TagMatchBias::matches( const Meta::TrackPtr &track ) const
0453 {
0454     QVariant value = Meta::valueForField( m_filter.field(), track );
0455 
0456     bool result = false;
0457     if( m_filter.isDate() )
0458     {
0459         switch( m_filter.condition )
0460         {
0461         case MetaQueryWidget::LessThan:
0462             result = value.toLongLong() < m_filter.numValue;
0463             break;
0464         case MetaQueryWidget::Equals:
0465             result = value.toLongLong() == m_filter.numValue;
0466             break;
0467         case MetaQueryWidget::GreaterThan:
0468             result = value.toLongLong() > m_filter.numValue;
0469             break;
0470         case MetaQueryWidget::Between:
0471             result = value.toLongLong() > m_filter.numValue &&
0472                 value.toLongLong() < m_filter.numValue2;
0473             break;
0474         case MetaQueryWidget::OlderThan:
0475             result = value.toLongLong() < m_filter.numValue + QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
0476             break;
0477         default:
0478             ;
0479         }
0480     }
0481     else if( m_filter.isNumeric() )
0482     {
0483         switch( m_filter.condition )
0484         {
0485         case MetaQueryWidget::LessThan:
0486             result = value.toLongLong() < m_filter.numValue;
0487             break;
0488         case MetaQueryWidget::Equals:
0489             result = value.toLongLong() == m_filter.numValue;
0490             break;
0491         case MetaQueryWidget::GreaterThan:
0492             result = value.toLongLong() > m_filter.numValue;
0493             break;
0494         case MetaQueryWidget::Between:
0495             result = value.toLongLong() > m_filter.numValue &&
0496                 value.toLongLong() < m_filter.numValue2;
0497             break;
0498         default:
0499             ;
0500         }
0501     }
0502     else
0503     {
0504         switch( m_filter.condition )
0505         {
0506         case MetaQueryWidget::Equals:
0507             result = value.toString() == m_filter.value;
0508             break;
0509         case MetaQueryWidget::Contains:
0510             result = value.toString().contains( m_filter.value, Qt::CaseInsensitive );
0511             break;
0512         default:
0513             ;
0514         }
0515     }
0516     if( m_invert )
0517         return !result;
0518     else
0519         return result;
0520 }
0521 
0522