1 /****************************************************************************************
2 * Copyright (c) 2011 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 "PlaylistFile.h"
18
19 #include "core/support/Debug.h"
20 #include "core-impl/playlists/types/file/PlaylistFileLoaderJob.h"
21 #include "playlistmanager/file/PlaylistFileProvider.h"
22 #include "playlistmanager/PlaylistManager.h"
23
24 #include <QUrl>
25 #include <QDir>
26
27 #include <ThreadWeaver/Queue>
28
29 using namespace Playlists;
30
PlaylistFile(const QUrl & url,PlaylistProvider * provider)31 PlaylistFile::PlaylistFile( const QUrl &url, PlaylistProvider *provider )
32 : Playlist()
33 , m_provider( provider )
34 , m_url( url )
35 , m_tracksLoaded( false )
36 , m_name( m_url.fileName() )
37 , m_relativePaths( false )
38 , m_loadingDone( 0 )
39 {
40 }
41
42 void
saveLater()43 PlaylistFile::saveLater()
44 {
45 PlaylistFileProvider *fileProvider = qobject_cast<PlaylistFileProvider *>( m_provider );
46 if( !fileProvider )
47 return;
48
49 fileProvider->saveLater( PlaylistFilePtr( this ) );
50 }
51
52 void
triggerTrackLoad()53 PlaylistFile::triggerTrackLoad()
54 {
55 if( m_tracksLoaded )
56 {
57 notifyObserversTracksLoaded();
58 return;
59 }
60 PlaylistFileLoaderJob *worker = new PlaylistFileLoaderJob( PlaylistFilePtr( this ) );
61 ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(worker) );
62 if ( !isLoadingAsync() )
63 m_loadingDone.acquire(); // after loading is finished worker will release semapore
64 }
65
66 bool
isWritable() const67 PlaylistFile::isWritable() const
68 {
69 if( m_url.isEmpty() )
70 return false;
71
72 return QFileInfo( m_url.path() ).isWritable();
73 }
74
75 int
trackCount() const76 PlaylistFile::trackCount() const
77 {
78 if( m_tracksLoaded )
79 return m_tracks.count();
80 else
81 return -1;
82 }
83
84 void
addTrack(const Meta::TrackPtr & track,int position)85 PlaylistFile::addTrack( const Meta::TrackPtr &track, int position )
86 {
87 if( !track ) // playlists might contain invalid tracks. see BUG: 303056
88 return;
89
90 int trackPos = position < 0 ? m_tracks.count() : position;
91 if( trackPos > m_tracks.count() )
92 trackPos = m_tracks.count();
93 m_tracks.insert( trackPos, track );
94 // set in case no track was in the playlist before
95 m_tracksLoaded = true;
96
97 notifyObserversTrackAdded( track, trackPos );
98
99 if( !m_url.isEmpty() )
100 saveLater();
101 }
102
103 void
removeTrack(int position)104 PlaylistFile::removeTrack( int position )
105 {
106 if( position < 0 || position >= m_tracks.count() )
107 return;
108
109 m_tracks.removeAt( position );
110
111 notifyObserversTrackRemoved( position );
112
113 if( !m_url.isEmpty() )
114 saveLater();
115 }
116
117 bool
save(bool relative)118 PlaylistFile::save( bool relative )
119 {
120 m_relativePaths = relative;
121 QMutexLocker locker( &m_saveLock );
122
123 //if the location is a directory append the name of this playlist.
124 if( m_url.fileName().isNull() )
125 { m_url = m_url.adjusted(QUrl::RemoveFilename);
126 m_url.setPath(m_url.path() + name());
127 }
128 QFile file( m_url.path() );
129
130 if( !file.open( QIODevice::WriteOnly ) )
131 {
132 warning() << QStringLiteral( "Cannot write playlist (%1)." ).arg( file.fileName() )
133 << file.errorString();
134 return false;
135 }
136
137 savePlaylist( file );
138 file.close();
139 return true;
140 }
141
142 void
setName(const QString & name)143 PlaylistFile::setName( const QString &name )
144 {
145 //can't save to a new file if we don't know where.
146 if( !m_url.isEmpty() && !name.isEmpty() )
147 {
148 QString exten = QStringLiteral( ".%1" ).arg(extension());
149 m_url = m_url.adjusted(QUrl::RemoveFilename);
150 m_url.setPath(m_url.path() + name + ( name.endsWith( exten, Qt::CaseInsensitive ) ? QLatin1String("") : exten ));
151 }
152 }
153
154 void
addProxyTrack(const Meta::TrackPtr & proxyTrack)155 PlaylistFile::addProxyTrack( const Meta::TrackPtr &proxyTrack )
156 {
157 m_tracks << proxyTrack;
158 notifyObserversTrackAdded( m_tracks.last(), m_tracks.size() - 1 );
159 }
160
161 QUrl
getAbsolutePath(const QUrl & url)162 PlaylistFile::getAbsolutePath( const QUrl &url )
163 {
164 QUrl absUrl = url;
165
166 if( url.scheme().isEmpty() )
167 absUrl.setScheme( QStringLiteral( "file" ) );
168
169 if( !absUrl.isLocalFile() )
170 return url;
171
172 if( !url.path().startsWith( QLatin1Char('/') ) )
173 {
174 m_relativePaths = true;
175 // example: url = QUrl( "file://../tunes/tune.ogg" )
176 absUrl = m_url.adjusted(QUrl::RemoveFilename); // file:///playlists/
177 absUrl = absUrl.adjusted(QUrl::StripTrailingSlash);
178 absUrl.setPath( absUrl.path() + QLatin1Char('/') + url.path() );
179 absUrl.setPath( QDir::cleanPath(absUrl.path()) ); // file:///playlists/tunes/tune.ogg
180 }
181 return absUrl;
182 }
183
184 QString
trackLocation(const Meta::TrackPtr & track) const185 PlaylistFile::trackLocation( const Meta::TrackPtr &track ) const
186 {
187 QUrl path = track->playableUrl();
188 if( path.isEmpty() )
189 return track->uidUrl();
190
191 if( !m_relativePaths || m_url.isEmpty() || !path.isLocalFile() || !m_url.isLocalFile() )
192 return path.toEncoded();
193
194 QDir playlistDir( m_url.adjusted(QUrl::RemoveFilename).path() );
195 return QUrl::toPercentEncoding( playlistDir.relativeFilePath( path.path() ), "/" );
196 }
197