File indexing completed on 2024-05-19 04:49:59
0001 /**************************************************************************************** 0002 * Copyright (c) 2007 Bart Cerneels <bart.cerneels@kde.org> * 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 #include "PlaylistFileProvider.h" 0018 #include "App.h" 0019 #include "core-impl/playlists/types/file/PlaylistFileSupport.h" 0020 #include "core/support/Amarok.h" 0021 #include "core/support/Debug.h" 0022 #include "core/support/Components.h" 0023 #include "core/logger/Logger.h" 0024 #include "core-impl/playlists/types/file/asx/ASXPlaylist.h" 0025 #include "core-impl/playlists/types/file/m3u/M3UPlaylist.h" 0026 #include "core-impl/playlists/types/file/pls/PLSPlaylist.h" 0027 #include "core-impl/playlists/types/file/xspf/XSPFPlaylist.h" 0028 #include "playlist/PlaylistModelStack.h" 0029 #include "playlistmanager/PlaylistManager.h" 0030 0031 #include <QAction> 0032 #include <QDir> 0033 #include <QInputDialog> 0034 #include <QLabel> 0035 #include <QString> 0036 #include <QTimer> 0037 #include <QUrl> 0038 0039 #include <KIO/Global> 0040 #include <KLocalizedString> 0041 0042 using Playlist::ModelStack; 0043 0044 namespace Playlists { 0045 0046 PlaylistFileProvider::PlaylistFileProvider() 0047 : UserPlaylistProvider() 0048 , m_playlistsLoaded( false ) 0049 , m_saveLaterTimer( nullptr ) 0050 { 0051 //playlists are lazy loaded but we can count how many we'll load already 0052 QStringList keys = loadedPlaylistsConfig().keyList(); 0053 foreach( const QString &key, keys ) 0054 { 0055 QUrl url( key ); 0056 //Don't load these from the config file, they are read from the directory anyway 0057 if( KIO::upUrl(url).matches( QUrl::fromUserInput(Amarok::saveLocation(QStringLiteral("playlists"))), QUrl::StripTrailingSlash ) ) 0058 continue; 0059 m_urlsToLoad << url; 0060 } 0061 //also add all files in the $KDEHOME/share/apps/amarok/playlists 0062 QDir playlistDir = QDir( Amarok::saveLocation( QStringLiteral("playlists") ), QLatin1String(""), 0063 QDir::Name, 0064 QDir::Files | QDir::Readable ); 0065 foreach( const QString &file, playlistDir.entryList() ) 0066 { 0067 QUrl url( playlistDir.path() ); 0068 url = url.adjusted(QUrl::StripTrailingSlash); 0069 url.setPath(url.path() + QLatin1Char('/') + ( file )); 0070 if( Playlists::isPlaylist( url ) ) 0071 m_urlsToLoad << url; 0072 } 0073 } 0074 0075 PlaylistFileProvider::~PlaylistFileProvider() 0076 { 0077 DEBUG_BLOCK 0078 //remove all, well add them again soon 0079 loadedPlaylistsConfig().deleteGroup(); 0080 //Write loaded playlists to config file 0081 foreach( Playlists::PlaylistFilePtr playlistFile, m_playlists ) 0082 { 0083 QUrl url = playlistFile->uidUrl(); 0084 //only save files NOT in "playlists", those are automatically loaded. 0085 if( KIO::upUrl(url).matches( QUrl::fromUserInput(Amarok::saveLocation( QStringLiteral("playlists") )), QUrl::StripTrailingSlash ) ) 0086 continue; 0087 0088 //debug() << "storing to rc-file: " << url.url(); 0089 0090 loadedPlaylistsConfig().writeEntry( url.url(), playlistFile->groups() ); 0091 } 0092 loadedPlaylistsConfig().sync(); 0093 } 0094 0095 QString 0096 PlaylistFileProvider::prettyName() const 0097 { 0098 return i18n( "Playlist Files on Disk" ); 0099 } 0100 0101 QIcon PlaylistFileProvider::icon() const 0102 { 0103 return QIcon::fromTheme( "folder-documents" ); 0104 } 0105 0106 int 0107 PlaylistFileProvider::playlistCount() const 0108 { 0109 return m_playlists.count() + m_urlsToLoad.count(); 0110 } 0111 0112 Playlists::PlaylistList 0113 PlaylistFileProvider::playlists() 0114 { 0115 Playlists::PlaylistList playlists; 0116 0117 if( !m_playlistsLoaded ) 0118 { 0119 //trigger a lazy load the playlists 0120 QTimer::singleShot(0, this, &PlaylistFileProvider::loadPlaylists ); 0121 return playlists; 0122 } 0123 0124 foreach( const Playlists::PlaylistFilePtr &playlistFile, m_playlists ) 0125 { 0126 Playlists::PlaylistPtr playlist = Playlists::PlaylistPtr::dynamicCast( playlistFile ); 0127 if( !playlist.isNull() ) 0128 playlists << playlist; 0129 } 0130 return playlists; 0131 } 0132 0133 Playlists::PlaylistPtr 0134 PlaylistFileProvider::save( const Meta::TrackList &tracks, const QString &name ) 0135 { 0136 DEBUG_BLOCK 0137 0138 QString filename = name.isEmpty() ? QDateTime::currentDateTime().toString( QStringLiteral("ddd MMMM d yy hh-mm")) : name; 0139 filename.replace( QLatin1Char('/'), QLatin1Char('-') ); 0140 filename.replace( QLatin1Char('\\'), QLatin1Char('-') ); 0141 0142 Playlists::PlaylistFormat format = Playlists::getFormat( QUrl::fromUserInput(filename) ); 0143 if( format == Playlists::Unknown ) // maybe the name just had a dot in it. We just add .xspf 0144 { 0145 format = Playlists::XSPF; 0146 filename.append( QLatin1String( ".xspf" ) ); 0147 } 0148 0149 QUrl path( Amarok::saveLocation( QStringLiteral("playlists") ) ); 0150 path = path.adjusted(QUrl::StripTrailingSlash); 0151 path.setPath(path.path() + QLatin1Char('/') + ( Amarok::vfatPath( filename ) )); 0152 if( QFileInfo( path.toLocalFile() ).exists() ) 0153 { 0154 //TODO:request overwrite 0155 return Playlists::PlaylistPtr(); 0156 } 0157 0158 Playlists::PlaylistFile *playlistFile = nullptr; 0159 switch( format ) 0160 { 0161 case Playlists::ASX: 0162 playlistFile = new Playlists::ASXPlaylist( path, this ); 0163 break; 0164 case Playlists::PLS: 0165 playlistFile = new Playlists::PLSPlaylist( path, this ); 0166 break; 0167 case Playlists::M3U: 0168 playlistFile = new Playlists::M3UPlaylist( path, this ); 0169 break; 0170 case Playlists::XSPF: 0171 playlistFile = new Playlists::XSPFPlaylist( path, this ); 0172 break; 0173 case Playlists::XML: 0174 case Playlists::RAM: 0175 case Playlists::SMIL: 0176 case Playlists::Unknown: 0177 // this should not happen since we set the format to XSPF above. 0178 return Playlists::PlaylistPtr(); 0179 } 0180 playlistFile->setName( filename ); 0181 playlistFile->addTracks( tracks ); 0182 playlistFile->save( true ); 0183 0184 Playlists::PlaylistFilePtr playlistPtr( playlistFile ); 0185 m_playlists << playlistPtr; 0186 //just in case there wasn't one loaded before. 0187 m_playlistsLoaded = true; 0188 0189 Playlists::PlaylistPtr playlist( playlistFile ); 0190 Q_EMIT playlistAdded( playlist ); 0191 return playlist; 0192 } 0193 0194 bool 0195 PlaylistFileProvider::import( const QUrl &path ) 0196 { 0197 DEBUG_BLOCK 0198 if( !path.isValid() ) 0199 { 0200 error() << "path is not valid!"; 0201 return false; 0202 } 0203 0204 foreach( Playlists::PlaylistFilePtr playlistFile, m_playlists ) 0205 { 0206 if( !playlistFile ) 0207 { 0208 error() << "Could not cast down."; 0209 error() << "m_playlists got corrupted! " << __FILE__ << ":" << __LINE__; 0210 continue; 0211 } 0212 if( playlistFile->uidUrl() == path ) 0213 { 0214 debug() << "Playlist " << path.path() << " was already imported"; 0215 return false; 0216 } 0217 } 0218 0219 debug() << "Importing playlist file " << path; 0220 if( path == QUrl::fromLocalFile(Amarok::defaultPlaylistPath()) ) 0221 { 0222 error() << "trying to load saved session playlist at %s" << path.path(); 0223 return false; 0224 } 0225 0226 Playlists::PlaylistFilePtr playlistFile = Playlists::loadPlaylistFile( path, this ); 0227 if( !playlistFile ) 0228 return false; 0229 0230 m_playlists << playlistFile; 0231 //just in case there wasn't one loaded before. 0232 m_playlistsLoaded = true; 0233 0234 Q_EMIT playlistAdded( PlaylistPtr( playlistFile.data() ) ); 0235 return true; 0236 } 0237 0238 void 0239 PlaylistFileProvider::renamePlaylist(PlaylistPtr playlist, const QString &newName ) 0240 { 0241 DEBUG_BLOCK 0242 playlist->setName( newName ); 0243 } 0244 0245 bool 0246 PlaylistFileProvider::deletePlaylists( const Playlists::PlaylistList &playlists ) 0247 { 0248 Playlists::PlaylistFileList playlistFiles; 0249 foreach( Playlists::PlaylistPtr playlist, playlists ) 0250 { 0251 Playlists::PlaylistFilePtr playlistFile = 0252 Playlists::PlaylistFilePtr::dynamicCast( playlist ); 0253 if( !playlistFile.isNull() ) 0254 playlistFiles << playlistFile; 0255 } 0256 return deletePlaylistFiles( playlistFiles ); 0257 } 0258 0259 bool 0260 PlaylistFileProvider::deletePlaylistFiles( Playlists::PlaylistFileList playlistFiles ) 0261 { 0262 foreach( Playlists::PlaylistFilePtr playlistFile, playlistFiles ) 0263 { 0264 m_playlists.removeAll( playlistFile ); 0265 loadedPlaylistsConfig().deleteEntry( playlistFile->uidUrl().url() ); 0266 QFile::remove( playlistFile->uidUrl().path() ); 0267 Q_EMIT playlistRemoved( Playlists::PlaylistPtr::dynamicCast( playlistFile ) ); 0268 } 0269 loadedPlaylistsConfig().sync(); 0270 0271 return true; 0272 } 0273 0274 void 0275 PlaylistFileProvider::loadPlaylists() 0276 { 0277 if( m_urlsToLoad.isEmpty() ) 0278 return; 0279 0280 //arbitrary number of playlists to load during one mainloop run: 5 0281 for( int i = 0; i < qMin( m_urlsToLoad.count(), 5 ); i++ ) 0282 { 0283 QUrl url = m_urlsToLoad.takeFirst(); 0284 QString groups = loadedPlaylistsConfig().readEntry( url.url() ); 0285 Playlists::PlaylistFilePtr playlist = Playlists::loadPlaylistFile( url, this ); 0286 if( !playlist ) 0287 { 0288 Amarok::Logger::longMessage( 0289 i18n("The playlist file \"%1\" could not be loaded.", url.fileName() ), 0290 Amarok::Logger::Error 0291 ); 0292 continue; 0293 } 0294 0295 if( !groups.isEmpty() && playlist->isWritable() ) 0296 playlist->setGroups( groups.split( QLatin1Char(','), Qt::SkipEmptyParts ) ); 0297 0298 m_playlists << playlist; 0299 Q_EMIT playlistAdded( PlaylistPtr( playlist.data() ) ); 0300 } 0301 0302 //give the mainloop time to run 0303 if( !m_urlsToLoad.isEmpty() ) 0304 QTimer::singleShot( 0, this, &PlaylistFileProvider::loadPlaylists ); 0305 } 0306 0307 void 0308 PlaylistFileProvider::saveLater( Playlists::PlaylistFilePtr playlist ) 0309 { 0310 //WARNING: this assumes the playlistfile uses it's m_url for uidUrl 0311 if( playlist->uidUrl().isEmpty() ) 0312 return; 0313 0314 if( !m_saveLaterPlaylists.contains( playlist ) ) 0315 m_saveLaterPlaylists << playlist; 0316 0317 if( !m_saveLaterTimer ) 0318 { 0319 m_saveLaterTimer = new QTimer( this ); 0320 m_saveLaterTimer->setSingleShot( true ); 0321 m_saveLaterTimer->setInterval( 0 ); 0322 connect( m_saveLaterTimer, &QTimer::timeout, this, &PlaylistFileProvider::slotSaveLater ); 0323 } 0324 0325 m_saveLaterTimer->start(); 0326 } 0327 0328 void 0329 PlaylistFileProvider::slotSaveLater() //SLOT 0330 { 0331 foreach( Playlists::PlaylistFilePtr playlist, m_saveLaterPlaylists ) 0332 { 0333 playlist->save( true ); //TODO: read relative type when loading 0334 } 0335 0336 m_saveLaterPlaylists.clear(); 0337 } 0338 0339 KConfigGroup 0340 PlaylistFileProvider::loadedPlaylistsConfig() const 0341 { 0342 return Amarok::config( QStringLiteral("Loaded Playlist Files") ); 0343 } 0344 0345 } //namespace Playlists 0346