1 /****************************************************************************************
2 * Copyright (c) 2012 Matěj Laitl <matej@laitl.cz> *
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 "IpodParseTracksJob.h"
18
19 #include "IpodCollection.h"
20 #include "IpodMeta.h"
21 #include "IpodPlaylistProvider.h"
22 #include "core/logger/Logger.h"
23 #include "core/support/Components.h"
24 #include "core/support/Debug.h"
25 #include "core-impl/meta/file/File.h"
26
27 #include <QAction>
28 #include <QDir>
29 #include <QFileInfo>
30
31 #include <gpod/itdb.h>
32
IpodParseTracksJob(IpodCollection * collection)33 IpodParseTracksJob::IpodParseTracksJob( IpodCollection *collection )
34 : QObject()
35 , ThreadWeaver::Job()
36 , m_coll( collection )
37 , m_aborted( false )
38 {
39 }
40
abort()41 void IpodParseTracksJob::abort()
42 {
43 m_aborted = true;
44 }
45
46 void
run(ThreadWeaver::JobPointer self,ThreadWeaver::Thread * thread)47 IpodParseTracksJob::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread)
48 {
49 Q_UNUSED(self);
50 Q_UNUSED(thread);
51 DEBUG_BLOCK
52 Itdb_iTunesDB *itdb = m_coll->m_itdb;
53 if( !itdb )
54 return; // paranoia
55
56 guint32 trackNumber = itdb_tracks_number( itdb );
57 QString operationText = i18nc( "operation when iPod is connected", "Reading iPod tracks" );
58 Amarok::Logger::newProgressOperation( this, operationText, trackNumber,
59 this, &IpodParseTracksJob::abort );
60
61 Meta::TrackList staleTracks;
62 QSet<QString> knownPaths;
63
64 for( GList *tracklist = itdb->tracks; tracklist; tracklist = tracklist->next )
65 {
66 if( m_aborted )
67 break;
68
69 Itdb_Track *ipodTrack = (Itdb_Track *) tracklist->data;
70 if( !ipodTrack )
71 continue; // paranoia
72 // IpodCollection::addTrack() locks and unlocks the m_itdbMutex mutex
73 Meta::TrackPtr proxyTrack = m_coll->addTrack( new IpodMeta::Track( ipodTrack ) );
74 if( proxyTrack )
75 {
76 QString canonPath = QFileInfo( proxyTrack->playableUrl().toLocalFile() ).canonicalFilePath();
77 if( !proxyTrack->isPlayable() )
78 staleTracks.append( proxyTrack );
79 else if( !canonPath.isEmpty() ) // nonexistent files return empty canonical path
80 knownPaths.insert( canonPath );
81 }
82
83 incrementProgress();
84 }
85
86 parsePlaylists( staleTracks, knownPaths );
87 Q_EMIT endProgressOperation( this );
88 }
89
90 void
defaultBegin(const ThreadWeaver::JobPointer & self,ThreadWeaver::Thread * thread)91 IpodParseTracksJob::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
92 {
93 Q_EMIT started(self);
94 ThreadWeaver::Job::defaultBegin(self, thread);
95 }
96
97 void
defaultEnd(const ThreadWeaver::JobPointer & self,ThreadWeaver::Thread * thread)98 IpodParseTracksJob::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread)
99 {
100 ThreadWeaver::Job::defaultEnd(self, thread);
101 if (!self->success()) {
102 Q_EMIT failed(self);
103 }
104 Q_EMIT done(self);
105 }
106
107 void
parsePlaylists(const Meta::TrackList & staleTracks,const QSet<QString> & knownPaths)108 IpodParseTracksJob::parsePlaylists( const Meta::TrackList &staleTracks,
109 const QSet<QString> &knownPaths )
110 {
111 IpodPlaylistProvider *prov = m_coll->m_playlistProvider;
112 if( !prov || m_aborted )
113 return;
114
115 if( !staleTracks.isEmpty() )
116 {
117 prov->m_stalePlaylist = Playlists::PlaylistPtr( new IpodPlaylist( staleTracks,
118 i18nc( "iPod playlist name", "Stale tracks" ), m_coll, IpodPlaylist::Stale ) );
119 prov->m_playlists << prov->m_stalePlaylist; // we don't subscribe to this playlist, no need to update database
120 Q_EMIT prov->playlistAdded( prov->m_stalePlaylist );
121 }
122
123 Meta::TrackList orphanedTracks = findOrphanedTracks( knownPaths );
124 if( !orphanedTracks.isEmpty() )
125 {
126 prov->m_orphanedPlaylist = Playlists::PlaylistPtr( new IpodPlaylist( orphanedTracks,
127 i18nc( "iPod playlist name", "Orphaned tracks" ), m_coll, IpodPlaylist::Orphaned ) );
128 prov->m_playlists << prov->m_orphanedPlaylist; // we don't subscribe to this playlist, no need to update database
129 Q_EMIT prov->playlistAdded( prov->m_orphanedPlaylist );
130 }
131
132 if( !m_coll->m_itdb || m_aborted )
133 return;
134 for( GList *playlists = m_coll->m_itdb->playlists; playlists; playlists = playlists->next )
135 {
136 Itdb_Playlist *playlist = (Itdb_Playlist *) playlists->data;
137 if( !playlist || itdb_playlist_is_mpl( playlist ) )
138 continue; // skip null (?) or master playlists
139 Playlists::PlaylistPtr playlistPtr( new IpodPlaylist( playlist, m_coll ) );
140 prov->m_playlists << playlistPtr;
141 prov->subscribeTo( playlistPtr );
142 Q_EMIT prov->playlistAdded( playlistPtr );
143 }
144
145 if( !m_aborted && ( prov->m_stalePlaylist || prov->m_orphanedPlaylist ) )
146 {
147 QString text = i18n( "Stale and/or orphaned tracks detected on %1. You can resolve "
148 "the situation using the <b>%2</b> collection action. You can also view the tracks "
149 "under the Saved Playlists tab.", m_coll->prettyName(),
150 m_coll->m_consolidateAction->text() );
151 Amarok::Logger::longMessage( text );
152 }
153 }
154
findOrphanedTracks(const QSet<QString> & knownPaths)155 Meta::TrackList IpodParseTracksJob::findOrphanedTracks(const QSet< QString >& knownPaths)
156 {
157 gchar *musicDirChar = itdb_get_music_dir( QFile::encodeName( m_coll->mountPoint() ) );
158 QString musicDirPath = QFile::decodeName( musicDirChar );
159 g_free( musicDirChar );
160 musicDirChar = 0;
161
162 QStringList trackPatterns;
163 foreach( const QString &suffix, m_coll->supportedFormats() )
164 {
165 trackPatterns << QString( "*.%1" ).arg( suffix );
166 }
167
168 Meta::TrackList orphanedTracks;
169 QDir musicDir( musicDirPath );
170 foreach( QString subdir, musicDir.entryList( QStringList( "F??" ), QDir::Dirs | QDir::NoDotAndDotDot ) )
171 {
172 if( m_aborted )
173 return Meta::TrackList();
174 subdir = musicDir.absoluteFilePath( subdir ); // make the path absolute
175 foreach( const QFileInfo &info, QDir( subdir ).entryInfoList( trackPatterns ) )
176 {
177 QString canonPath = info.canonicalFilePath();
178 if( knownPaths.contains( canonPath ) )
179 continue; // already in iTunes database
180 Meta::TrackPtr track( new MetaFile::Track( QUrl::fromLocalFile( canonPath ) ) );
181 orphanedTracks << track;
182 }
183 }
184 return orphanedTracks;
185 }
186