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 }