2 * Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 #include "ScanResultProcessor.h"
22 #include "meta/MetaConstants.h"
23 #include "meta/MetaUtility.h"
24 #include "mountpointmanager.h"
25 #include "sqlcollection.h"
31 ScanResultProcessor::ScanResultProcessor( SqlCollection
*collection
)
32 : m_collection( collection
)
37 ScanResultProcessor::~ScanResultProcessor()
43 ScanResultProcessor::processScanResult( const QMap
<QString
, QHash
<QString
, QString
> > &scanResult
)
46 m_collection
->dbUpdater()->createTemporaryTables();
47 m_collection
->dbUpdater()->prepareTemporaryTables();
48 QList
<QHash
<QString
, QString
> > dirData
;
49 bool firstTrack
= true;
51 QHash
<QString
,QString
> track
;
52 debug() << "Processing " << scanResult
.size() << " tracks";
53 foreach( track
, scanResult
)
57 KUrl
url( track
.value( Field::URL
) );
58 dir
= url
.directory();
60 debug() << "first dir " << url
.directory();
63 KUrl
url( track
.value( Field::URL
) );
64 debug() << "current dir" << url
.directory();
65 if( url
.directory() == dir
)
67 dirData
.append( track
);
71 processDirectory( dirData
);
73 dir
= url
.directory();
76 m_collection
->dbUpdater()->copyToPermanentTables();
77 m_collection
->dbUpdater()->removeTemporaryTables();
81 ScanResultProcessor::processDirectory( const QList
<QHash
<QString
, QString
> > &data
)
84 //using the following heuristics:
85 //if more than one album is in the dir, use the artist of each track as albumartist
86 //if more than 60 files are in the dir, use the artist of each track as albumartist
87 //if all tracks have the same artist, use it as albumartist
88 //try to find the albumartist A: tracks must have the artist A or A feat. B (and variants)
89 //if no albumartist could be found, it's a compilation
90 QSet
<QString
> artists
;
92 bool multipleAlbums
= false;
94 album
= data
[0].value( Field::ALBUM
);
95 QHash
<QString
,QString
> row
;
98 artists
.insert( row
.value( Field::ARTIST
) );
99 if( row
.value( Field::ALBUM
) != album
)
100 multipleAlbums
= true;
102 if( multipleAlbums
|| data
.count() > 60 || artists
.size() == 1 )
104 QHash
<QString
, QString
> row
;
107 int artist
= artistId( row
.value( Field::ARTIST
) );
108 addTrack( row
, artist
);
113 QString albumArtist
= findAlbumArtist( artists
);
114 //an empty string means that no albumartist was found
115 int artist
= albumArtist
.isEmpty() ? 0 : artistId( albumArtist
);
116 QHash
<QString
, QString
> row
;
119 addTrack( row
, artist
);
125 ScanResultProcessor::findAlbumArtist( const QSet
<QString
> &artists
) const
127 QMap
<QString
, int> artistCount
;
128 foreach( QString artist
, artists
)
130 //this needs to be improved
131 if( artist
.contains( "featuring" ) )
133 QStringList trackArtists
= artist
.split( "featuring" );
134 //always use the first artist
135 QString tmp
= trackArtists
[0].simplified();
138 //TODO error handling
142 if( artistCount
.contains( tmp
) )
144 artistCount
.insert( tmp
, artistCount
.value( tmp
) + 1 );
148 artistCount
.insert( tmp
, 1 );
152 else if( artist
.contains( "feat." ) )
154 //FIXME code duplication, refactor!
155 QStringList trackArtists
= artist
.split( "feat." );
156 //always use the first artist
157 QString tmp
= trackArtists
[0].simplified();
160 //TODO error handling
164 if( artistCount
.contains( tmp
) )
166 artistCount
.insert( tmp
, artistCount
.value( tmp
) + 1 );
170 artistCount
.insert( tmp
, 1 );
176 if( artistCount
.contains( artist
) )
178 artistCount
.insert( artist
, artistCount
.value( artist
) + 1 );
182 artistCount
.insert( artist
, 1 );
188 foreach( QString key
, artistCount
.keys() )
190 if( artistCount
.value( key
) > count
)
193 count
= artistCount
.value( key
);
200 ScanResultProcessor::addTrack( const QHash
<QString
, QString
> &trackData
, int albumArtistId
)
203 int album
= albumId( trackData
.value( Field::ALBUM
), albumArtistId
);
204 int artist
= artistId( trackData
.value( Field::ARTIST
) );
205 int genre
= genreId( trackData
.value( Field::GENRE
) );
206 int composer
= composerId( trackData
.value( Field::COMPOSER
) );
207 int year
= yearId( trackData
.value( Field::YEAR
) );
208 int url
= urlId( trackData
.value( Field::URL
) );
210 QString insert
= "INSERT INTO tracks_temp(url,artist,album,genre,composer,year,title,comment,"
211 "tracknumber,discnumber,bitrate,length,samplerate,filesize,filetype,bpm,"
212 "createdate,modifydate) VALUES ( %1,%2,%3,%4,%5,%6,'%7','%8',%9"; //goes up to tracknumber
213 insert
= insert
.arg( url
).arg( artist
).arg( album
).arg( genre
).arg( composer
).arg( year
);
214 insert
= insert
.arg( m_collection
->escape( trackData
[ Field::TITLE
] ), m_collection
->escape( trackData
[ Field::COMMENT
] ) );
215 insert
= insert
.arg( trackData
[Field::TRACKNUMBER
].toInt() );
217 QString insert2
= ",%1,%2,%3,%4,%5,%6,%7,%8,%9);";
218 insert2
= insert2
.arg( trackData
[Field::BITRATE
].toInt() ).arg( trackData
[Field::LENGTH
].toInt() );
219 insert2
= insert2
.arg( trackData
[Field::SAMPLERATE
].toInt() ).arg( trackData
[Field::FILESIZE
].toInt() );
220 insert2
= insert2
.arg( "0", "0", "0", "0" ); //filetype,bpm, createdate, modifydate not implemented yet
223 m_collection
->insert( insert
, "tracks_temp" );
227 ScanResultProcessor::artistId( const QString
&artist
)
229 if( m_artists
.contains( artist
) )
230 return m_artists
.value( artist
);
231 QString query
= QString( "SELECT id FROM artists_temp WHERE name = '%1';" ).arg( m_collection
->escape( artist
) );
232 QStringList res
= m_collection
->query( query
);
235 QString insert
= QString( "INSERT INTO artists_temp( name ) VALUES ('%1');" ).arg( m_collection
->escape( artist
) );
236 int id
= m_collection
->insert( insert
, "artists_temp" );
237 m_artists
.insert( artist
, id
);
242 int id
= res
[0].toInt();
243 m_artists
.insert( artist
, id
);
249 ScanResultProcessor::genreId( const QString
&genre
)
251 if( m_genre
.contains( genre
) )
252 return m_genre
.value( genre
);
253 QString query
= QString( "SELECT id FROM genres_temp WHERE name = '%1';" ).arg( m_collection
->escape( genre
) );
254 QStringList res
= m_collection
->query( query
);
257 QString insert
= QString( "INSERT INTO genres_temp( name ) VALUES ('%1');" ).arg( m_collection
->escape( genre
) );
258 int id
= m_collection
->insert( insert
, "genres_temp" );
259 m_genre
.insert( genre
, id
);
264 int id
= res
[0].toInt();
265 m_genre
.insert( genre
, id
);
271 ScanResultProcessor::composerId( const QString
&composer
)
273 if( m_composer
.contains( composer
) )
274 return m_composer
.value( composer
);
275 QString query
= QString( "SELECT id FROM composers_temp WHERE name = '%1';" ).arg( m_collection
->escape( composer
) );
276 QStringList res
= m_collection
->query( query
);
279 QString insert
= QString( "INSERT INTO composers_temp( name ) VALUES ('%1');" ).arg( m_collection
->escape( composer
) );
280 int id
= m_collection
->insert( insert
, "composers_temp" );
281 m_composer
.insert( composer
, id
);
286 int id
= res
[0].toInt();
287 m_composer
.insert( composer
, id
);
293 ScanResultProcessor::yearId( const QString
&year
)
295 if( m_year
.contains( year
) )
296 return m_year
.value( year
);
297 QString query
= QString( "SELECT id FROM years_temp WHERE name = '%1';" ).arg( m_collection
->escape( year
) );
298 QStringList res
= m_collection
->query( query
);
301 QString insert
= QString( "INSERT INTO years_temp( name ) VALUES ('%1');" ).arg( m_collection
->escape( year
) );
302 int id
= m_collection
->insert( insert
, "years_temp" );
303 m_year
.insert( year
, id
);
308 int id
= res
[0].toInt();
309 m_year
.insert( year
, id
);
315 ScanResultProcessor::albumId( const QString
&album
, int artistId
)
317 //artistId == 0 means no albumartist
318 QPair
<QString
, int> key( album
, artistId
);
319 if( m_albums
.contains( key
) )
320 return m_albums
.value( key
);
325 query
= QString( "SELECT id FROM albums_temp WHERE artist IS NULL AND name = '%1';" )
326 .arg( m_collection
->escape( album
) );
330 query
= QString( "SELECT id FROM albums_temp WHERE artist = %1 AND name = '%2';" )
331 .arg( QString::number( artistId
), m_collection
->escape( album
) );
333 QStringList res
= m_collection
->query( query
);
336 QString insert
= QString( "INSERT INTO albums_temp(artist, name) VALUES( %1, '%2' );" )
337 .arg( artistId
? QString::number( artistId
) : "NULL" )
338 .arg( m_collection
->escape( album
) );
339 int id
= m_collection
->insert( insert
, "albums_temp" );
340 m_albums
.insert( key
, id
);
345 int id
= res
[0].toInt();
346 m_albums
.insert( key
, id
);
352 ScanResultProcessor::urlId( const QString
&url
)
354 int deviceId
= MountPointManager::instance()->getIdForUrl( url
);
355 QString rpath
= MountPointManager::instance()->getRelativePath( deviceId
, url
);
356 //don't bother caching the data, we only call this method for each url once
357 QString query
= QString( "SELECT id FROM urls_temp WHERE deviceid = %1 AND rpath = '%2';" )
358 .arg( QString::number( deviceId
), m_collection
->escape( rpath
) );
359 QStringList res
= m_collection
->query( query
);
362 QString insert
= QString( "INSERT INTO urls_temp(deviceid, rpath) VALUES ( %1, '%2' );" )
363 .arg( QString::number( deviceId
), m_collection
->escape( rpath
) );
364 return m_collection
->insert( insert
, "urls_temp" );
368 return res
[0].toInt();