File indexing completed on 2024-05-19 04:49:26
0001 /**************************************************************************************** 0002 * Copyright (c) 2007-2008 Ian Monroe <ian@monroe.nu> * 0003 * Copyright (c) 2013 Matěj Laitl <matej@laitl.cz> * 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 "TrackLoader.h" 0021 0022 #include "core/playlists/PlaylistFormat.h" 0023 #include "core/support/Debug.h" 0024 #include "core-impl/meta/file/File.h" 0025 #include "core-impl/meta/proxy/MetaProxy.h" 0026 #include "core-impl/meta/multi/MultiTrack.h" 0027 #include "core-impl/playlists/types/file/PlaylistFileSupport.h" 0028 0029 #include <KIO/Job> 0030 #include <KFileItem> 0031 0032 #include <QFileInfo> 0033 #include <QTimer> 0034 0035 TrackLoader::TrackLoader( Flags flags, int timeout ) 0036 : m_status( LoadingTracks ) 0037 , m_flags( flags ) 0038 , m_timeout( timeout ) 0039 { 0040 } 0041 0042 TrackLoader::~TrackLoader() 0043 { 0044 } 0045 0046 void 0047 TrackLoader::init( const QUrl &url ) 0048 { 0049 init( QList<QUrl>() << url ); 0050 } 0051 0052 void 0053 TrackLoader::init( const QList<QUrl> &qurls ) 0054 { 0055 m_sourceUrls = qurls; 0056 QTimer::singleShot( 0, this, &TrackLoader::processNextSourceUrl ); 0057 } 0058 0059 void 0060 TrackLoader::init( const Playlists::PlaylistList &playlists ) 0061 { 0062 m_resultPlaylists = playlists; 0063 // no need to process source urls here, short-cut to result urls (just playlists) 0064 QTimer::singleShot( 0, this, &TrackLoader::processNextResultUrl ); 0065 } 0066 0067 void 0068 TrackLoader::processNextSourceUrl() 0069 { 0070 if( m_sourceUrls.isEmpty() ) 0071 { 0072 QTimer::singleShot( 0, this, &TrackLoader::processNextResultUrl ); 0073 return; 0074 } 0075 0076 QUrl sourceUrl = m_sourceUrls.takeFirst(); 0077 if( !sourceUrl.isValid() ) 0078 { 0079 error() << "Url is invalid:" << sourceUrl; 0080 QTimer::singleShot( 0, this, &TrackLoader::processNextSourceUrl ); 0081 return; 0082 } 0083 if( sourceUrl.isLocalFile() && QFileInfo( sourceUrl.toLocalFile() ).isDir() ) 0084 { 0085 // KJobs delete themselves 0086 KIO::ListJob *lister = KIO::listRecursive( sourceUrl ); 0087 connect( lister, &KIO::ListJob::result, this, &TrackLoader::processNextSourceUrl ); 0088 connect( lister, &KIO::ListJob::entries, this, &TrackLoader::directoryListResults ); 0089 return; 0090 } 0091 else 0092 m_resultUrls.append( sourceUrl ); 0093 0094 QTimer::singleShot( 0, this, &TrackLoader::processNextSourceUrl ); 0095 } 0096 0097 void 0098 TrackLoader::directoryListResults( KIO::Job *job, const KIO::UDSEntryList &list ) 0099 { 0100 //dfaure says that job->redirectionUrl().isValid() ? job->redirectionUrl() : job->url(); might be needed 0101 //but to wait until an issue is actually found, since it might take more work 0102 const QUrl dir = static_cast<KIO::SimpleJob *>( job )->url(); 0103 foreach( const KIO::UDSEntry &entry, list ) 0104 { 0105 KFileItem item( entry, dir, true, true ); 0106 QUrl url = item.url(); 0107 if( MetaFile::Track::isTrack( url ) ) 0108 { 0109 auto insertIter = std::upper_bound( m_resultUrls.begin(), m_resultUrls.end(), url, directorySensitiveLessThan ); 0110 m_resultUrls.insert( insertIter, url ); 0111 } 0112 } 0113 } 0114 0115 void 0116 TrackLoader::processNextResultUrl() 0117 { 0118 using namespace Playlists; 0119 if( !m_resultPlaylists.isEmpty() ) 0120 { 0121 PlaylistPtr playlist = m_resultPlaylists.takeFirst(); 0122 PlaylistObserver::subscribeTo( playlist ); 0123 playlist->triggerTrackLoad(); // playlist track loading is on demand. 0124 // will trigger tracksLoaded() which in turn calls processNextResultUrl(), 0125 // therefore we shouldn't call trigger processNextResultUrl() here: 0126 return; 0127 } 0128 0129 if( m_resultUrls.isEmpty() ) 0130 { 0131 mayFinish(); 0132 return; 0133 } 0134 0135 QUrl resultUrl = m_resultUrls.takeFirst(); 0136 if( isPlaylist( resultUrl ) ) 0137 { 0138 PlaylistFilePtr playlist = loadPlaylistFile( resultUrl ); 0139 if( playlist ) 0140 { 0141 PlaylistObserver::subscribeTo( PlaylistPtr::staticCast( playlist ) ); 0142 playlist->triggerTrackLoad(); // playlist track loading is on demand. 0143 // will trigger tracksLoaded() which in turn calls processNextResultUrl(), 0144 // therefore we shouldn't call trigger processNextResultUrl() here: 0145 return; 0146 } 0147 else 0148 warning() << __PRETTY_FUNCTION__ << "cannot load playlist" << resultUrl; 0149 } 0150 else if( MetaFile::Track::isTrack( resultUrl ) ) 0151 { 0152 MetaProxy::TrackPtr proxyTrack( new MetaProxy::Track( resultUrl ) ); 0153 proxyTrack->setTitle( resultUrl.fileName() ); // set temporary name 0154 Meta::TrackPtr track( proxyTrack.data() ); 0155 m_tracks << Meta::TrackPtr( track ); 0156 0157 if( m_flags.testFlag( FullMetadataRequired ) && !proxyTrack->isResolved() ) 0158 { 0159 m_unresolvedTracks.insert( track ); 0160 Observer::subscribeTo( track ); 0161 } 0162 } 0163 else 0164 warning() << __PRETTY_FUNCTION__ << resultUrl 0165 << "is neither a playlist or a track, skipping"; 0166 0167 QTimer::singleShot( 0, this, &TrackLoader::processNextResultUrl ); 0168 } 0169 0170 void 0171 TrackLoader::tracksLoaded( Playlists::PlaylistPtr playlist ) 0172 { 0173 // this method needs to be thread-safe! 0174 0175 // some playlists used to Q_EMIT tracksLoaded() in ->tracks(), prevent infinite 0176 // recursion by unsubscribing early 0177 PlaylistObserver::unsubscribeFrom( playlist ); 0178 0179 // accessing m_tracks is thread-safe as nothing else is happening in this class in 0180 // the main thread while we are waiting for tracksLoaded() to trigger: 0181 Meta::TrackList tracks = playlist->tracks(); 0182 if( m_flags.testFlag( FullMetadataRequired ) ) 0183 { 0184 foreach( const Meta::TrackPtr &track, tracks ) 0185 { 0186 MetaProxy::TrackPtr proxyTrack = MetaProxy::TrackPtr::dynamicCast( track ); 0187 if( !proxyTrack ) 0188 { 0189 debug() << __PRETTY_FUNCTION__ << "strange, playlist" << playlist->name() 0190 << "doesn't use MetaProxy::Tracks"; 0191 continue; 0192 } 0193 if( !proxyTrack->isResolved() ) 0194 { 0195 m_unresolvedTracks.insert( track ); 0196 Observer::subscribeTo( track ); 0197 } 0198 } 0199 } 0200 0201 static const QSet<QString> remoteProtocols = QSet<QString>() 0202 << "http" << "https" << "mms" << "smb"; // consider unifying with CollectionManager::trackForUrl() 0203 if( m_flags.testFlag( RemotePlaylistsAreStreams ) && tracks.count() > 1 0204 && remoteProtocols.contains( playlist->uidUrl().scheme() ) ) 0205 { 0206 m_tracks << Meta::TrackPtr( new Meta::MultiTrack( playlist ) ); 0207 } 0208 else 0209 m_tracks << tracks; 0210 0211 // this also ensures that processNextResultUrl() will resume in the main thread 0212 QTimer::singleShot( 0, this, &TrackLoader::processNextResultUrl ); 0213 } 0214 0215 void 0216 TrackLoader::metadataChanged( const Meta::TrackPtr &track ) 0217 { 0218 // first metadataChanged() from a MetaProxy::Track means that it has found the real track 0219 bool isEmpty; 0220 { 0221 QMutexLocker locker( &m_unresolvedTracksMutex ); 0222 m_unresolvedTracks.remove( track ); 0223 isEmpty = m_unresolvedTracks.isEmpty(); 0224 } 0225 0226 Observer::unsubscribeFrom( track ); 0227 if( m_status == MayFinish && isEmpty ) 0228 QTimer::singleShot( 0, this, &TrackLoader::finish ); 0229 } 0230 0231 void 0232 TrackLoader::mayFinish() 0233 { 0234 m_status = MayFinish; 0235 bool isEmpty; 0236 { 0237 QMutexLocker locker( &m_unresolvedTracksMutex ); 0238 isEmpty = m_unresolvedTracks.isEmpty(); 0239 } 0240 if( isEmpty ) 0241 { 0242 finish(); 0243 return; 0244 } 0245 0246 // we must wait for tracks to resolve, but with a timeout 0247 QTimer::singleShot( m_timeout, this, &TrackLoader::finish ); 0248 } 0249 0250 void 0251 TrackLoader::finish() 0252 { 0253 // prevent double Q_EMIT of finished(), race between singleshot QTimers from mayFinish() 0254 // and metadataChanged() 0255 if( m_status != MayFinish ) 0256 return; 0257 0258 m_status = Finished; 0259 Q_EMIT finished( m_tracks ); 0260 deleteLater(); 0261 } 0262 0263 bool 0264 TrackLoader::directorySensitiveLessThan( const QUrl &left, const QUrl &right ) 0265 { 0266 QString leftDir = left.adjusted(QUrl::RemoveFilename).path(); 0267 QString rightDir = right.adjusted(QUrl::RemoveFilename).path(); 0268 0269 // filter out tracks from same directories: 0270 if( leftDir == rightDir ) 0271 return QString::localeAwareCompare( left.fileName(), right.fileName() ) < 0; 0272 0273 // left is "/a/b/c/", right is "/a/b/" 0274 if( leftDir.startsWith( rightDir ) ) 0275 return true; // we sort directories above files 0276 // left is "/a/b/", right is "/a/b/c/" 0277 if( rightDir.startsWith( leftDir ) ) 0278 return false; 0279 0280 return QString::localeAwareCompare( leftDir, rightDir ) < 0; 0281 }