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

0001 /****************************************************************************************
0002  * Copyright (c) 2009 Edward Toroshchin <edward.hades@gmail.com>                        *
0003  * Copyright (c) 2010 Nanno Langstraat <langstr@gmail.com>                              *
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 #include "FavoredRandomTrackNavigator.h"
0021 
0022 #include "playlist/PlaylistModelStack.h"
0023 
0024 #include "amarokconfig.h"
0025 #include "core/meta/Meta.h"
0026 #include "core/meta/Statistics.h"
0027 #include "core/support/Debug.h"
0028 
0029 #include <QRandomGenerator>
0030 
0031 
0032 Playlist::FavoredRandomTrackNavigator::FavoredRandomTrackNavigator()
0033 {
0034     loadFromSourceModel();
0035 }
0036 
0037 void
0038 Playlist::FavoredRandomTrackNavigator::planOne()
0039 {
0040     DEBUG_BLOCK
0041 
0042     if ( m_plannedItems.isEmpty() && !allItemsList().isEmpty() )
0043     {
0044         int avoidRecentlyPlayedSize = AVOID_RECENTLY_PLAYED_MAX;    // Start with being very picky.
0045 
0046         // Don't over-constrain ourself:
0047         //   - Keep enough headroom to be unpredictable.
0048         //   - Make sure that 'chooseRandomItem()' doesn't need to find a needle in a haystack.
0049         avoidRecentlyPlayedSize = qMin( avoidRecentlyPlayedSize, allItemsList().size() / 2 );
0050 
0051         QSet<quint64> avoidSet = getRecentHistory( avoidRecentlyPlayedSize );
0052 
0053         QList<qreal> weights = rowWeights( avoidSet );
0054 
0055         // Choose a weighed random row.
0056         if( !weights.isEmpty() )
0057         {
0058             qreal totalWeight = 0.0;
0059             foreach ( qreal weight, weights )
0060                 totalWeight += weight;
0061 
0062             qreal randomCumulWeight = ( QRandomGenerator::global()->generate() / qreal( RAND_MAX ) ) * totalWeight;
0063 
0064             int row = 0;
0065             qreal rowCumulWeight = weights[ row ];
0066             while ( randomCumulWeight > rowCumulWeight + 0.0000000001 )
0067                 rowCumulWeight += weights[ ++row ];
0068 
0069             m_plannedItems.append( m_model->idAt( row ) );
0070         }
0071     }
0072 }
0073 
0074 QList<qreal>
0075 Playlist::FavoredRandomTrackNavigator::rowWeights(const QSet<quint64> &avoidSet )
0076 {
0077     QList<qreal> weights;
0078 
0079     int favorType = AmarokConfig::favorTracks();
0080     int rowCount = m_model->qaim()->rowCount();
0081 
0082     for( int row = 0; row < rowCount; row++ )
0083     {
0084         qreal weight = 0.0;
0085         Meta::StatisticsPtr statistics = m_model->trackAt( row )->statistics();
0086 
0087         if ( !avoidSet.contains( m_model->idAt( row ) ) )
0088         {
0089             switch( favorType )
0090             {
0091                 case AmarokConfig::EnumFavorTracks::HigherScores:
0092                 {
0093                     int score = statistics->score();
0094                     weight = score ? score : 50.0;    // "Unknown" weight: in the middle, 50%
0095                     break;
0096                 }
0097 
0098                 case AmarokConfig::EnumFavorTracks::HigherRatings:
0099                 {
0100                     int rating = statistics->rating();
0101                     weight = rating ? rating : 5.0;
0102                     break;
0103                 }
0104 
0105                 case AmarokConfig::EnumFavorTracks::LessRecentlyPlayed:
0106                 {
0107                     QDateTime lastPlayed = statistics->lastPlayed();
0108                     if ( lastPlayed.isValid() )
0109                     {
0110                         weight = lastPlayed.secsTo( QDateTime::currentDateTime() );
0111                         if ( weight < 0 )    // If 'lastPlayed()' is nonsense, or the system clock has been set back:
0112                             weight = 1 * 60 * 60;    // "Nonsense" weight: 1 hour.
0113                     }
0114                     else
0115                         weight = 365 * 24 * 60 * 60;    // "Never" weight: 1 year.
0116                     break;
0117                 }
0118             }
0119         }
0120 
0121         weights.append( weight );
0122     }
0123 
0124     return weights;
0125 }
0126 
0127 QSet<quint64>
0128 Playlist::FavoredRandomTrackNavigator::getRecentHistory( int size )
0129 {
0130     QList<quint64> allHistory = historyItems();
0131     QSet<quint64> recentHistory;
0132 
0133     if ( size > 0 ) {    // If '== 0', we even need to consider playing the same item again.
0134         recentHistory.insert( currentItem() );    // Might be '0'
0135         size--;
0136     }
0137 
0138     for ( int i = allHistory.size() - 1; ( i >= 0 ) && ( i >= allHistory.size() - size ); i-- )
0139         recentHistory.insert( allHistory.at( i ) );
0140 
0141     return recentHistory;
0142 }