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