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