1 /****************************************************************************************
2 * Copyright (c) 2007 Bart Cerneels <bart.cerneels@kde.org> *
3 * *
4 * This program is free software; you can redistribute it and/or modify it under *
5 * the terms of the GNU General Public License as published by the Free Software *
6 * Foundation; either version 2 of the License, or (at your option) any later *
7 * version. *
8 * *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY *
10 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
11 * PARTICULAR PURPOSE. See the GNU General Public License for more details. *
12 * *
13 * You should have received a copy of the GNU General Public License along with *
14 * this program. If not, see <http://www.gnu.org/licenses/>. *
15 ****************************************************************************************/
16
17 #include "PlaylistFileProvider.h"
18 #include "App.h"
19 #include "core-impl/playlists/types/file/PlaylistFileSupport.h"
20 #include "core/support/Amarok.h"
21 #include "core/support/Debug.h"
22 #include "core/support/Components.h"
23 #include "core/logger/Logger.h"
24 #include "core-impl/playlists/types/file/asx/ASXPlaylist.h"
25 #include "core-impl/playlists/types/file/m3u/M3UPlaylist.h"
26 #include "core-impl/playlists/types/file/pls/PLSPlaylist.h"
27 #include "core-impl/playlists/types/file/xspf/XSPFPlaylist.h"
28 #include "playlist/PlaylistModelStack.h"
29 #include "playlistmanager/PlaylistManager.h"
30
31 #include <QAction>
32 #include <QDir>
33 #include <QInputDialog>
34 #include <QLabel>
35 #include <QString>
36 #include <QTimer>
37 #include <QUrl>
38
39 #include <KIO/Global>
40 #include <KLocalizedString>
41
42 using Playlist::ModelStack;
43
44 namespace Playlists {
45
PlaylistFileProvider()46 PlaylistFileProvider::PlaylistFileProvider()
47 : UserPlaylistProvider()
48 , m_playlistsLoaded( false )
49 , m_saveLaterTimer( 0 )
50 {
51 //playlists are lazy loaded but we can count how many we'll load already
52 QStringList keys = loadedPlaylistsConfig().keyList();
53 foreach( const QString &key, keys )
54 {
55 QUrl url( key );
56 //Don't load these from the config file, they are read from the directory anyway
57 if( KIO::upUrl(url).matches( QUrl::fromUserInput(Amarok::saveLocation(QStringLiteral("playlists"))), QUrl::StripTrailingSlash ) )
58 continue;
59 m_urlsToLoad << url;
60 }
61 //also add all files in the $KDEHOME/share/apps/amarok/playlists
62 QDir playlistDir = QDir( Amarok::saveLocation( QStringLiteral("playlists") ), QLatin1String(""),
63 QDir::Name,
64 QDir::Files | QDir::Readable );
65 foreach( const QString &file, playlistDir.entryList() )
66 {
67 QUrl url( playlistDir.path() );
68 url = url.adjusted(QUrl::StripTrailingSlash);
69 url.setPath(url.path() + QLatin1Char('/') + ( file ));
70 if( Playlists::isPlaylist( url ) )
71 m_urlsToLoad << url;
72 }
73 }
74
~PlaylistFileProvider()75 PlaylistFileProvider::~PlaylistFileProvider()
76 {
77 DEBUG_BLOCK
78 //remove all, well add them again soon
79 loadedPlaylistsConfig().deleteGroup();
80 //Write loaded playlists to config file
81 foreach( Playlists::PlaylistFilePtr playlistFile, m_playlists )
82 {
83 QUrl url = playlistFile->uidUrl();
84 //only save files NOT in "playlists", those are automatically loaded.
85 if( KIO::upUrl(url).matches( QUrl::fromUserInput(Amarok::saveLocation( QStringLiteral("playlists") )), QUrl::StripTrailingSlash ) )
86 continue;
87
88 //debug() << "storing to rc-file: " << url.url();
89
90 loadedPlaylistsConfig().writeEntry( url.url(), playlistFile->groups() );
91 }
92 loadedPlaylistsConfig().sync();
93 }
94
95 QString
prettyName() const96 PlaylistFileProvider::prettyName() const
97 {
98 return i18n( "Playlist Files on Disk" );
99 }
100
icon() const101 QIcon PlaylistFileProvider::icon() const
102 {
103 return QIcon::fromTheme( "folder-documents" );
104 }
105
106 int
playlistCount() const107 PlaylistFileProvider::playlistCount() const
108 {
109 return m_playlists.count() + m_urlsToLoad.count();
110 }
111
112 Playlists::PlaylistList
playlists()113 PlaylistFileProvider::playlists()
114 {
115 Playlists::PlaylistList playlists;
116
117 if( !m_playlistsLoaded )
118 {
119 //trigger a lazy load the playlists
120 QTimer::singleShot(0, this, &PlaylistFileProvider::loadPlaylists );
121 return playlists;
122 }
123
124 foreach( const Playlists::PlaylistFilePtr &playlistFile, m_playlists )
125 {
126 Playlists::PlaylistPtr playlist = Playlists::PlaylistPtr::dynamicCast( playlistFile );
127 if( !playlist.isNull() )
128 playlists << playlist;
129 }
130 return playlists;
131 }
132
133 Playlists::PlaylistPtr
save(const Meta::TrackList & tracks,const QString & name)134 PlaylistFileProvider::save( const Meta::TrackList &tracks, const QString &name )
135 {
136 DEBUG_BLOCK
137
138 QString filename = name.isEmpty() ? QDateTime::currentDateTime().toString( QStringLiteral("ddd MMMM d yy hh-mm")) : name;
139 filename.replace( QLatin1Char('/'), QLatin1Char('-') );
140 filename.replace( QLatin1Char('\\'), QLatin1Char('-') );
141
142 Playlists::PlaylistFormat format = Playlists::getFormat( QUrl::fromUserInput(filename) );
143 if( format == Playlists::Unknown ) // maybe the name just had a dot in it. We just add .xspf
144 {
145 format = Playlists::XSPF;
146 filename.append( QLatin1String( ".xspf" ) );
147 }
148
149 QUrl path( Amarok::saveLocation( QStringLiteral("playlists") ) );
150 path = path.adjusted(QUrl::StripTrailingSlash);
151 path.setPath(path.path() + QLatin1Char('/') + ( Amarok::vfatPath( filename ) ));
152 if( QFileInfo( path.toLocalFile() ).exists() )
153 {
154 //TODO:request overwrite
155 return Playlists::PlaylistPtr();
156 }
157
158 Playlists::PlaylistFile *playlistFile = 0;
159 switch( format )
160 {
161 case Playlists::ASX:
162 playlistFile = new Playlists::ASXPlaylist( path, this );
163 break;
164 case Playlists::PLS:
165 playlistFile = new Playlists::PLSPlaylist( path, this );
166 break;
167 case Playlists::M3U:
168 playlistFile = new Playlists::M3UPlaylist( path, this );
169 break;
170 case Playlists::XSPF:
171 playlistFile = new Playlists::XSPFPlaylist( path, this );
172 break;
173 case Playlists::XML:
174 case Playlists::RAM:
175 case Playlists::SMIL:
176 case Playlists::Unknown:
177 // this should not happen since we set the format to XSPF above.
178 return Playlists::PlaylistPtr();
179 }
180 playlistFile->setName( filename );
181 playlistFile->addTracks( tracks );
182 playlistFile->save( true );
183
184 Playlists::PlaylistFilePtr playlistPtr( playlistFile );
185 m_playlists << playlistPtr;
186 //just in case there wasn't one loaded before.
187 m_playlistsLoaded = true;
188
189 Playlists::PlaylistPtr playlist( playlistFile );
190 Q_EMIT playlistAdded( playlist );
191 return playlist;
192 }
193
194 bool
import(const QUrl & path)195 PlaylistFileProvider::import( const QUrl &path )
196 {
197 DEBUG_BLOCK
198 if( !path.isValid() )
199 {
200 error() << "path is not valid!";
201 return false;
202 }
203
204 foreach( Playlists::PlaylistFilePtr playlistFile, m_playlists )
205 {
206 if( !playlistFile )
207 {
208 error() << "Could not cast down.";
209 error() << "m_playlists got corrupted! " << __FILE__ << ":" << __LINE__;
210 continue;
211 }
212 if( playlistFile->uidUrl() == path )
213 {
214 debug() << "Playlist " << path.path() << " was already imported";
215 return false;
216 }
217 }
218
219 debug() << "Importing playlist file " << path;
220 if( path == QUrl::fromLocalFile(Amarok::defaultPlaylistPath()) )
221 {
222 error() << "trying to load saved session playlist at %s" << path.path();
223 return false;
224 }
225
226 Playlists::PlaylistFilePtr playlistFile = Playlists::loadPlaylistFile( path, this );
227 if( !playlistFile )
228 return false;
229
230 m_playlists << playlistFile;
231 //just in case there wasn't one loaded before.
232 m_playlistsLoaded = true;
233
234 Q_EMIT playlistAdded( PlaylistPtr( playlistFile.data() ) );
235 return true;
236 }
237
238 void
renamePlaylist(PlaylistPtr playlist,const QString & newName)239 PlaylistFileProvider::renamePlaylist(PlaylistPtr playlist, const QString &newName )
240 {
241 DEBUG_BLOCK
242 playlist->setName( newName );
243 }
244
245 bool
deletePlaylists(const Playlists::PlaylistList & playlists)246 PlaylistFileProvider::deletePlaylists( const Playlists::PlaylistList &playlists )
247 {
248 Playlists::PlaylistFileList playlistFiles;
249 foreach( Playlists::PlaylistPtr playlist, playlists )
250 {
251 Playlists::PlaylistFilePtr playlistFile =
252 Playlists::PlaylistFilePtr::dynamicCast( playlist );
253 if( !playlistFile.isNull() )
254 playlistFiles << playlistFile;
255 }
256 return deletePlaylistFiles( playlistFiles );
257 }
258
259 bool
deletePlaylistFiles(Playlists::PlaylistFileList playlistFiles)260 PlaylistFileProvider::deletePlaylistFiles( Playlists::PlaylistFileList playlistFiles )
261 {
262 foreach( Playlists::PlaylistFilePtr playlistFile, playlistFiles )
263 {
264 m_playlists.removeAll( playlistFile );
265 loadedPlaylistsConfig().deleteEntry( playlistFile->uidUrl().url() );
266 QFile::remove( playlistFile->uidUrl().path() );
267 Q_EMIT playlistRemoved( Playlists::PlaylistPtr::dynamicCast( playlistFile ) );
268 }
269 loadedPlaylistsConfig().sync();
270
271 return true;
272 }
273
274 void
loadPlaylists()275 PlaylistFileProvider::loadPlaylists()
276 {
277 if( m_urlsToLoad.isEmpty() )
278 return;
279
280 //arbitrary number of playlists to load during one mainloop run: 5
281 for( int i = 0; i < qMin( m_urlsToLoad.count(), 5 ); i++ )
282 {
283 QUrl url = m_urlsToLoad.takeFirst();
284 QString groups = loadedPlaylistsConfig().readEntry( url.url() );
285 Playlists::PlaylistFilePtr playlist = Playlists::loadPlaylistFile( url, this );
286 if( !playlist )
287 {
288 Amarok::Logger::longMessage(
289 i18n("The playlist file \"%1\" could not be loaded.", url.fileName() ),
290 Amarok::Logger::Error
291 );
292 continue;
293 }
294
295 if( !groups.isEmpty() && playlist->isWritable() )
296 playlist->setGroups( groups.split( QLatin1Char(','), QString::SkipEmptyParts ) );
297
298 m_playlists << playlist;
299 Q_EMIT playlistAdded( PlaylistPtr( playlist.data() ) );
300 }
301
302 //give the mainloop time to run
303 if( !m_urlsToLoad.isEmpty() )
304 QTimer::singleShot( 0, this, &PlaylistFileProvider::loadPlaylists );
305 }
306
307 void
saveLater(Playlists::PlaylistFilePtr playlist)308 PlaylistFileProvider::saveLater( Playlists::PlaylistFilePtr playlist )
309 {
310 //WARNING: this assumes the playlistfile uses it's m_url for uidUrl
311 if( playlist->uidUrl().isEmpty() )
312 return;
313
314 if( !m_saveLaterPlaylists.contains( playlist ) )
315 m_saveLaterPlaylists << playlist;
316
317 if( !m_saveLaterTimer )
318 {
319 m_saveLaterTimer = new QTimer( this );
320 m_saveLaterTimer->setSingleShot( true );
321 m_saveLaterTimer->setInterval( 0 );
322 connect( m_saveLaterTimer, &QTimer::timeout, this, &PlaylistFileProvider::slotSaveLater );
323 }
324
325 m_saveLaterTimer->start();
326 }
327
328 void
slotSaveLater()329 PlaylistFileProvider::slotSaveLater() //SLOT
330 {
331 foreach( Playlists::PlaylistFilePtr playlist, m_saveLaterPlaylists )
332 {
333 playlist->save( true ); //TODO: read relative type when loading
334 }
335
336 m_saveLaterPlaylists.clear();
337 }
338
339 KConfigGroup
loadedPlaylistsConfig() const340 PlaylistFileProvider::loadedPlaylistsConfig() const
341 {
342 return Amarok::config( QStringLiteral("Loaded Playlist Files") );
343 }
344
345 } //namespace Playlists
346
347