- Reverted database view back to HEAD as otherwise it would only show songs which...
[xbmc:xbmc-antiquated.git] / xbmc / MusicDatabase.cpp
1 /*
2  *      Copyright (C) 2005-2008 Team XBMC
3  *      http://www.xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "stdafx.h"
23 #include "MusicDatabase.h"
24 #include "FileSystem/cddb.h"
25 #include "FileSystem/DirectoryCache.h"
26 #include "FileSystem/MusicDatabaseDirectory/DirectoryNode.h"
27 #include "FileSystem/MusicDatabaseDirectory/QueryParams.h"
28 #include "FileSystem/MusicDatabaseDirectory.h"
29 #include "GUIDialogMusicScan.h"
30 #include "DetectDVDType.h"
31 #include "utils/GUIInfoManager.h"
32 #include "MusicInfoTag.h"
33 #include "ScraperSettings.h"
34 #include "Util.h"
35 #include "Artist.h"
36 #include "Album.h"
37 #include "Song.h"
38 #include "GUIWindowManager.h"
39 #include "GUIDialogOK.h"
40 #include "GUIDialogProgress.h"
41 #include "GUIDialogYesNo.h"
42 #include "GUIDialogSelect.h"
43 #include "FileSystem/File.h"
44 #include "Settings.h"
45 #include "FileItem.h"
46 #include "Application.h"
47 #include "karaoke/karaokelyricsfactory.h"
48
49 using namespace std;
50 using namespace AUTOPTR;
51 using namespace XFILE;
52 using namespace DIRECTORY;
53 using namespace MUSICDATABASEDIRECTORY;
54 using namespace MEDIA_DETECT;
55
56 #define MUSIC_DATABASE_OLD_VERSION 1.6f
57 #define MUSIC_DATABASE_VERSION        11
58 #define MUSIC_DATABASE_NAME "MyMusic7.db"
59 #define RECENTLY_ADDED_LIMIT  25
60 #define RECENTLY_PLAYED_LIMIT 25
61 #define MIN_FULL_SEARCH_LENGTH 3
62
63 using namespace CDDB;
64
65 CMusicDatabase::CMusicDatabase(void)
66 {
67   m_preV2version=MUSIC_DATABASE_OLD_VERSION;
68   m_version=MUSIC_DATABASE_VERSION;
69   m_strDatabaseFile=MUSIC_DATABASE_NAME;
70   m_iSongsBeforeCommit = 0;
71 }
72
73 CMusicDatabase::~CMusicDatabase(void)
74 {
75   EmptyCache();
76 }
77
78 bool CMusicDatabase::CreateTables()
79 {
80   try
81   {
82     CDatabase::CreateTables();
83
84     CLog::Log(LOGINFO, "create artist table");
85     m_pDS->exec("CREATE TABLE artist ( idArtist integer primary key, strArtist text)\n");
86     CLog::Log(LOGINFO, "create album table");
87     m_pDS->exec("CREATE TABLE album ( idAlbum integer primary key, strAlbum text, idArtist integer, strExtraArtists text, idGenre integer, strExtraGenres text, iYear integer, idThumb integer)\n");
88     CLog::Log(LOGINFO, "create genre table");
89     m_pDS->exec("CREATE TABLE genre ( idGenre integer primary key, strGenre text)\n");
90     CLog::Log(LOGINFO, "create path table");
91     m_pDS->exec("CREATE TABLE path ( idPath integer primary key, strPath text, strHash text)\n");
92     CLog::Log(LOGINFO, "create song table");
93     m_pDS->exec("CREATE TABLE song ( idSong integer primary key, idAlbum integer, idPath integer, idArtist integer, strExtraArtists text, idGenre integer, strExtraGenres text, strTitle text, iTrack integer, iDuration integer, iYear integer, dwFileNameCRC text, strFileName text, strMusicBrainzTrackID text, strMusicBrainzArtistID text, strMusicBrainzAlbumID text, strMusicBrainzAlbumArtistID text, strMusicBrainzTRMID text, iTimesPlayed integer, iStartOffset integer, iEndOffset integer, idThumb integer, lastplayed text default NULL, rating char default '0', comment text)\n");
94     CLog::Log(LOGINFO, "create albuminfo table");
95     m_pDS->exec("CREATE TABLE albuminfo ( idAlbumInfo integer primary key, idAlbum integer, iYear integer, idGenre integer, strExtraGenres text, strMoods text, strStyles text, strThemes text, strReview text, strImage text, strLabel text, strType text, iRating integer)\n");
96     CLog::Log(LOGINFO, "create albuminfosong table");
97     m_pDS->exec("CREATE TABLE albuminfosong ( idAlbumInfoSong integer primary key, idAlbumInfo integer, iTrack integer, strTitle text, iDuration integer)\n");
98     CLog::Log(LOGINFO, "create thumb table");
99     m_pDS->exec("CREATE TABLE thumb (idThumb integer primary key, strThumb text)\n");
100     CLog::Log(LOGINFO, "create artistnfo table");
101     m_pDS->exec("CREATE TABLE artistinfo ( idArtistInfo integer primary key, idArtist integer, strBorn text, strFormed text, strGenres text, strMoods text, strStyles text, strInstruments text, strBiography text, strDied text, strDisbanded text, strYearsActive text, strImage text)\n");
102     CLog::Log(LOGINFO, "create content table");
103     m_pDS->exec("CREATE TABLE content (strPath text, strScraperPath text, strContent text, strSettings text)\n");
104     CLog::Log(LOGINFO, "create discography table");
105     m_pDS->exec("CREATE TABLE discography (idArtist integer, strAlbum text, strYear text)\n");
106
107     CLog::Log(LOGINFO, "create exartistsong table");
108     m_pDS->exec("CREATE TABLE exartistsong ( idSong integer, iPosition integer, idArtist integer)\n");
109     CLog::Log(LOGINFO, "create extragenresong table");
110     m_pDS->exec("CREATE TABLE exgenresong ( idSong integer, iPosition integer, idGenre integer)\n");
111     CLog::Log(LOGINFO, "create exartistalbum table");
112     m_pDS->exec("CREATE TABLE exartistalbum ( idAlbum integer, iPosition integer, idArtist integer)\n");
113     CLog::Log(LOGINFO, "create exgenrealbum table");
114     m_pDS->exec("CREATE TABLE exgenrealbum ( idAlbum integer, iPosition integer, idGenre integer)\n");
115
116     CLog::Log(LOGINFO, "create karaokedata table");
117     m_pDS->exec("CREATE TABLE karaokedata ( iKaraNumber integer, idSong integer, iKaraDelay integer, strKaraEncoding text, "
118                 "strKaralyrics text, strKaraLyrFileCRC text )\n");
119
120     // Indexes
121     CLog::Log(LOGINFO, "create exartistsong index");
122     m_pDS->exec("CREATE INDEX idxExtraArtistSong ON exartistsong(idSong)");
123     m_pDS->exec("CREATE INDEX idxExtraArtistSong2 ON exartistsong(idArtist)");
124     CLog::Log(LOGINFO, "create exgenresong index");
125     m_pDS->exec("CREATE INDEX idxExtraGenreSong ON exgenresong(idSong)");
126     m_pDS->exec("CREATE INDEX idxExtraGenreSong2 ON exgenresong(idGenre)");
127     CLog::Log(LOGINFO, "create exartistalbum index");
128     m_pDS->exec("CREATE INDEX idxExtraArtistAlbum ON exartistalbum(idAlbum)");
129     m_pDS->exec("CREATE INDEX idxExtraArtistAlbum2 ON exartistalbum(idArtist)");
130     CLog::Log(LOGINFO, "create exgenrealbum index");
131     m_pDS->exec("CREATE INDEX idxExtraGenreAlbum ON exgenrealbum(idAlbum)");
132     m_pDS->exec("CREATE INDEX idxExtraGenreAlbum2 ON exgenrealbum(idGenre)");
133
134     CLog::Log(LOGINFO, "create album index");
135     m_pDS->exec("CREATE INDEX idxAlbum ON album(strAlbum)");
136     CLog::Log(LOGINFO, "create genre index");
137     m_pDS->exec("CREATE INDEX idxGenre ON genre(strGenre)");
138     CLog::Log(LOGINFO, "create artist index");
139     m_pDS->exec("CREATE INDEX idxArtist ON artist(strArtist)");
140     CLog::Log(LOGINFO, "create path index");
141     m_pDS->exec("CREATE INDEX idxPath ON path(strPath)");
142     CLog::Log(LOGINFO, "create song index");
143     m_pDS->exec("CREATE INDEX idxSong ON song(strTitle)");
144     CLog::Log(LOGINFO, "create song index1");
145     m_pDS->exec("CREATE INDEX idxSong1 ON song(iTimesPlayed)");
146     CLog::Log(LOGINFO, "create song index2");
147     m_pDS->exec("CREATE INDEX idxSong2 ON song(lastplayed)");
148     CLog::Log(LOGINFO, "create thumb index");
149     m_pDS->exec("CREATE INDEX idxThumb ON thumb(strThumb)");
150     //m_pDS->exec("CREATE INDEX idxSong ON song(dwFileNameCRC)");
151     CLog::Log(LOGINFO, "create artistinfo index");
152     m_pDS->exec("CREATE INDEX idxArtistInfo on artistinfo(idArtist)");
153     CLog::Log(LOGINFO, "create albuminfo index");
154     m_pDS->exec("CREATE INDEX idxAlbumInfo on albuminfo(idAlbum)");
155
156     CLog::Log(LOGINFO, "create karaokedata index");
157     m_pDS->exec("CREATE INDEX idxKaraNumber on karaokedata(iKaraNumber)");
158     m_pDS->exec("CREATE INDEX idxKarSong on karaokedata(idSong)");
159
160     // Trigger
161     CLog::Log(LOGINFO, "create albuminfo trigger");
162     m_pDS->exec("CREATE TRIGGER tgrAlbumInfo AFTER delete ON albuminfo FOR EACH ROW BEGIN delete from albuminfosong where albuminfosong.idAlbumInfo=old.idAlbumInfo; END");
163
164     // views
165     CLog::Log(LOGINFO, "create song view");
166     m_pDS->exec("create view songview as select idSong, song.strExtraArtists as strExtraArtists, song.strExtraGenres as strExtraGenres, strTitle, iTrack, iDuration, song.iYear as iYear, dwFileNameCRC, strFileName, strMusicBrainzTrackID, strMusicBrainzArtistID, strMusicBrainzAlbumID, strMusicBrainzAlbumArtistID, strMusicBrainzTRMID, iTimesPlayed, iStartOffset, iEndOffset, lastplayed, rating, comment, song.idAlbum as idAlbum, strAlbum, strPath, song.idArtist as idArtist, strArtist, song.idGenre as idGenre, strGenre, strThumb from song join album on song.idAlbum=album.idAlbum join path on song.idPath=path.idPath join artist on song.idArtist=artist.idArtist join genre on song.idGenre=genre.idGenre join thumb on song.idThumb=thumb.idThumb");
167     CLog::Log(LOGINFO, "create album view");
168     m_pDS->exec("create view albumview as select album.idAlbum as idAlbum, strAlbum, strExtraArtists, "
169         "album.idArtist as idArtist, album.strExtraGenres as strExtraGenres, album.idGenre as idGenre, "
170         "strArtist, strGenre, album.iYear as iYear, strThumb, idAlbumInfo, strMoods, strStyles, strThemes, "
171         "strReview, strLabel, strType, strImage, iRating from album "
172         "left outer join artist on album.idArtist=artist.idArtist "
173         "left outer join genre on album.idGenre=genre.idGenre "
174         "left outer join thumb on album.idThumb=thumb.idThumb "
175         "left outer join albuminfo on album.idAlbum=albumInfo.idAlbum");
176
177     // Add 'Karaoke' genre
178     AddGenre( "Karaoke" );
179   }
180   catch (...)
181   {
182     CLog::Log(LOGERROR, "musicbase::unable to create tables:%u",
183               GetLastError());
184     return false;
185   }
186
187   return true;
188 }
189
190 void CMusicDatabase::AddSong(const CSong& song, bool bCheck)
191 {
192   CStdString strSQL;
193   try
194   {
195     // We need at least the title
196     if (song.strTitle.IsEmpty())
197       return;
198
199     CStdString strPath, strFileName;
200     CUtil::Split(song.strFileName, strPath, strFileName);
201     ASSERT(CUtil::HasSlashAtEnd(strPath));
202
203     if (NULL == m_pDB.get()) return ;
204     if (NULL == m_pDS.get()) return ;
205
206     // split our (possibly) multiple artist string into individual artists
207     CStdStringArray vecArtists; CStdString extraArtists;
208     SplitString(song.strArtist, vecArtists, extraArtists);
209
210     // do the same with our albumartist
211     CStdStringArray vecAlbumArtists; CStdString extraAlbumArtists;
212     SplitString(song.strAlbumArtist, vecAlbumArtists, extraAlbumArtists);
213
214     // and the same for our genres
215     CStdStringArray vecGenres; CStdString extraGenres;
216     SplitString(song.strGenre, vecGenres, extraGenres);
217
218     // add the primary artist/genre
219     // SplitString returns >= 1 so no worries referencing the first item here
220     long lArtistId = AddArtist(vecArtists[0]);
221     long lGenreId = AddGenre(vecGenres[0]);
222     // and also the primary album artist (if applicable)
223     long lAlbumArtistId = -1;
224     if (!vecAlbumArtists[0].IsEmpty())
225       lAlbumArtistId = AddArtist(vecAlbumArtists[0]);
226
227     long lPathId = AddPath(strPath);
228     long lThumbId = AddThumb(song.strThumb);
229     long lAlbumId;
230     if (lAlbumArtistId > -1)  // have an album artist
231       lAlbumId = AddAlbum(song.strAlbum, lAlbumArtistId, extraAlbumArtists, song.strAlbumArtist, lThumbId, lGenreId, extraGenres, song.iYear);
232     else
233       lAlbumId = AddAlbum(song.strAlbum, lArtistId, extraArtists, song.strArtist, lThumbId, lGenreId, extraGenres, song.iYear);
234
235     DWORD crc = ComputeCRC(song.strFileName);
236
237     bool bInsert = true;
238     int lSongId = -1;
239     bool bHasKaraoke = CKaraokeLyricsFactory::HasLyrics( song.strFileName );
240
241     // If this is karaoke song, change the genre to 'Karaoke' (and add it if it's not there)
242     if ( bHasKaraoke && g_advancedSettings.m_karaokeChangeGenreForKaraokeSongs )
243       lGenreId = AddGenre( "Karaoke" );
244
245     if (bCheck)
246     {
247       strSQL=FormatSQL("select * from song where idAlbum=%i and dwFileNameCRC='%ul' and strTitle='%s'",
248                     lAlbumId, crc, song.strTitle.c_str());
249       if (!m_pDS->query(strSQL.c_str())) return ;
250       if (m_pDS->num_rows() != 0)
251       {
252         lSongId = m_pDS->fv("idSong").get_asLong();
253         bInsert = false;
254       }
255       m_pDS->close();
256     }
257     if (bInsert)
258     {
259       CStdString strSQL1;
260
261       strSQL=FormatSQL("insert into song (idSong,idAlbum,idPath,idArtist,strExtraArtists,idGenre,strExtraGenres,strTitle,iTrack,iDuration,iYear,dwFileNameCRC,strFileName,strMusicBrainzTrackID,strMusicBrainzArtistID,strMusicBrainzAlbumID,strMusicBrainzAlbumArtistID,strMusicBrainzTRMID,iTimesPlayed,iStartOffset,iEndOffset,idThumb,lastplayed,rating,comment) values (NULL,%i,%i,%i,'%s',%i,'%s','%s',%i,%i,%i,'%ul','%s','%s','%s','%s','%s','%s'",
262                     lAlbumId, lPathId, lArtistId, extraArtists.c_str(), lGenreId, extraGenres.c_str(),
263                     song.strTitle.c_str(),
264                     song.iTrack, song.iDuration, song.iYear,
265                     crc, strFileName.c_str(),
266                     song.strMusicBrainzTrackID.c_str(),
267                     song.strMusicBrainzArtistID.c_str(),
268                     song.strMusicBrainzAlbumID.c_str(),
269                     song.strMusicBrainzAlbumArtistID.c_str(),
270                     song.strMusicBrainzTRMID.c_str());
271
272       if (song.lastPlayed.GetLength())
273         strSQL1=FormatSQL(",%i,%i,%i,%i,'%s','%c','%s')",
274                       song.iTimesPlayed, song.iStartOffset, song.iEndOffset, lThumbId, song.lastPlayed.c_str(), song.rating, song.strComment.c_str());
275       else
276         strSQL1=FormatSQL(",%i,%i,%i,%i,NULL,'%c','%s')",
277                       song.iTimesPlayed, song.iStartOffset, song.iEndOffset, lThumbId, song.rating, song.strComment.c_str());
278       strSQL+=strSQL1;
279
280       m_pDS->exec(strSQL.c_str());
281       lSongId = (long)sqlite3_last_insert_rowid(m_pDB->getHandle());
282     }
283
284     // add extra artists and genres
285     AddExtraSongArtists(vecArtists, lSongId, bCheck);
286     if (lAlbumArtistId > -1)
287       AddExtraAlbumArtists(vecAlbumArtists, lAlbumId);
288     else
289       AddExtraAlbumArtists(vecArtists, lAlbumId);
290     AddExtraGenres(vecGenres, lSongId, lAlbumId, bCheck);
291
292     // Add karaoke information (if any)
293     if ( bHasKaraoke )
294     {
295       // song argument is const :(
296       CSong mysong = song;
297       mysong.idSong = lSongId;
298       AddKaraokeData( mysong );
299     }
300
301     // increment the number of songs we've added since the last commit, and check if we should commit
302     if (m_iSongsBeforeCommit++ > NUM_SONGS_BEFORE_COMMIT)
303     {
304       CommitTransaction();
305       m_iSongsBeforeCommit=0;
306       BeginTransaction();
307     }
308   }
309   catch (...)
310   {
311     CLog::Log(LOGERROR, "musicdatabase:unable to addsong (%s)", strSQL.c_str());
312   }
313 }
314
315 long CMusicDatabase::AddAlbum(const CStdString& strAlbum1, long lArtistId, const CStdString &extraArtists, const CStdString &strArtist1, long idThumb, long idGenre, const CStdString &extraGenres, long year)
316 {
317   CStdString strSQL;
318   try
319   {
320     CStdString strAlbum=strAlbum1;
321     strAlbum.TrimLeft(" ");
322     strAlbum.TrimRight(" ");
323
324     CStdString strArtist=strArtist1;
325
326     if (strAlbum.IsEmpty())
327     {
328       // album tag is empty, fake an album
329       // with no path and artist and add this
330       // instead of an empty string
331       strAlbum=g_localizeStrings.Get(13205); // Unknown
332       lArtistId=-1;
333       strArtist.Empty();
334       idThumb=AddThumb("");
335       idGenre=-1;
336       year=0;
337     }
338
339     if (NULL == m_pDB.get()) return -1;
340     if (NULL == m_pDS.get()) return -1;
341
342     map <CStdString, CAlbumCache>::const_iterator it;
343
344     it = m_albumCache.find(strAlbum + strArtist);
345     if (it != m_albumCache.end())
346       return it->second.idAlbum;
347
348     strSQL=FormatSQL("select * from album where idArtist=%i and strAlbum like '%s'", lArtistId, strAlbum.c_str());
349     m_pDS->query(strSQL.c_str());
350
351     if (m_pDS->num_rows() == 0)
352     {
353       m_pDS->close();
354       // doesnt exists, add it
355       strSQL=FormatSQL("insert into album (idAlbum, strAlbum, idArtist, strExtraArtists, idGenre, strExtraGenres, iYear, idThumb) values( NULL, '%s', %i, '%s', %i, '%s', %i, %i)", strAlbum.c_str(), lArtistId, extraArtists.c_str(), idGenre, extraGenres.c_str(), year, idThumb);
356       m_pDS->exec(strSQL.c_str());
357
358       CAlbumCache album;
359       album.idAlbum = (long)sqlite3_last_insert_rowid(m_pDB->getHandle());
360       album.strAlbum = strAlbum;
361       album.idArtist = lArtistId;
362       album.strArtist = strArtist;
363       m_albumCache.insert(pair<CStdString, CAlbumCache>(album.strAlbum + album.strArtist, album));
364       return album.idAlbum;
365     }
366     else
367     {
368       // exists in our database and not scanned during this scan, so we should update it as the details
369       // may have changed (there's a reason we're rescanning, afterall!)
370       CAlbumCache album;
371       album.idAlbum = m_pDS->fv("idAlbum").get_asLong();
372       album.strAlbum = strAlbum;
373       album.idArtist = lArtistId;
374       album.strArtist = strArtist;
375       m_albumCache.insert(pair<CStdString, CAlbumCache>(album.strAlbum + album.strArtist, album));
376       m_pDS->close();
377       strSQL=FormatSQL("update album set strExtraArtists='%s', idGenre=%i, strExtraGenres='%s', iYear=%i, idThumb=%i where idAlbum=%i", extraArtists.c_str(), idGenre, extraGenres.c_str(), year, idThumb, album.idAlbum);
378       m_pDS->exec(strSQL.c_str());
379       // and clear the exartistalbum and exgenrealbum tables - these are updated in AddSong()
380       strSQL=FormatSQL("delete from exartistalbum where idAlbum=%i", album.idAlbum);
381       m_pDS->exec(strSQL.c_str());
382       strSQL=FormatSQL("delete from exgenrealbum where idAlbum=%i", album.idAlbum);
383       m_pDS->exec(strSQL.c_str());
384       return album.idAlbum;
385     }
386   }
387   catch (...)
388   {
389     CLog::Log(LOGERROR, "%s failed with query (%s)", __FUNCTION__, strSQL.c_str());
390   }
391
392   return -1;
393 }
394
395 long CMusicDatabase::AddGenre(const CStdString& strGenre1)
396 {
397   CStdString strSQL;
398   try
399   {
400     CStdString strGenre = strGenre1;
401     strGenre.TrimLeft(" ");
402     strGenre.TrimRight(" ");
403
404     if (strGenre.IsEmpty())
405       strGenre=g_localizeStrings.Get(13205); // Unknown
406
407     if (NULL == m_pDB.get()) return -1;
408     if (NULL == m_pDS.get()) return -1;
409     map <CStdString, int>::const_iterator it;
410
411     it = m_genreCache.find(strGenre);
412     if (it != m_genreCache.end())
413       return it->second;
414
415
416     strSQL=FormatSQL("select * from genre where strGenre like '%s'", strGenre.c_str());
417     m_pDS->query(strSQL.c_str());
418     if (m_pDS->num_rows() == 0)
419     {
420       m_pDS->close();
421       // doesnt exists, add it
422       strSQL=FormatSQL("insert into genre (idGenre, strGenre) values( NULL, '%s' )", strGenre.c_str());
423       m_pDS->exec(strSQL.c_str());
424
425       int idGenre = (int)sqlite3_last_insert_rowid(m_pDB->getHandle());
426       m_genreCache.insert(pair<CStdString, int>(strGenre1, idGenre));
427       return idGenre;
428     }
429     else
430     {
431       int idGenre = m_pDS->fv("idGenre").get_asLong();
432       m_genreCache.insert(pair<CStdString, int>(strGenre1, idGenre));
433       m_pDS->close();
434       return idGenre;
435     }
436   }
437   catch (...)
438   {
439     CLog::Log(LOGERROR, "musicdatabase:unable to addgenre (%s)", strSQL.c_str());
440   }
441
442   return -1;
443 }
444
445 long CMusicDatabase::AddArtist(const CStdString& strArtist1)
446 {
447   CStdString strSQL;
448   try
449   {
450     CStdString strArtist = strArtist1;
451     strArtist.TrimLeft(" ");
452     strArtist.TrimRight(" ");
453
454     if (strArtist.IsEmpty())
455       strArtist=g_localizeStrings.Get(13205); // Unknown
456
457     if (NULL == m_pDB.get()) return -1;
458     if (NULL == m_pDS.get()) return -1;
459
460     map <CStdString, int>::const_iterator it;
461
462     it = m_artistCache.find(strArtist);
463     if (it != m_artistCache.end())
464       return it->second;//.idArtist;
465
466     strSQL=FormatSQL("select * from artist where strArtist like '%s'", strArtist.c_str());
467     m_pDS->query(strSQL.c_str());
468
469     if (m_pDS->num_rows() == 0)
470     {
471       m_pDS->close();
472       // doesnt exists, add it
473       strSQL=FormatSQL("insert into artist (idArtist, strArtist) values( NULL, '%s' )", strArtist.c_str());
474       m_pDS->exec(strSQL.c_str());
475       int idArtist = (long)sqlite3_last_insert_rowid(m_pDB->getHandle());
476       m_artistCache.insert(pair<CStdString, int>(strArtist1, idArtist));
477       return idArtist;
478     }
479     else
480     {
481       int idArtist = (long)m_pDS->fv("idArtist").get_asLong();
482       m_artistCache.insert(pair<CStdString, int>(strArtist1, idArtist));
483       m_pDS->close();
484       return idArtist;
485     }
486   }
487   catch (...)
488   {
489     CLog::Log(LOGERROR, "musicdatabase:unable to addartist (%s)", strSQL.c_str());
490   }
491
492   return -1;
493 }
494
495 void CMusicDatabase::AddExtraSongArtists(const CStdStringArray &vecArtists, long lSongId, bool bCheck)
496 {
497   try
498   {
499     // add each of the artists in the vector of artists
500     for (int i = 1; i < (int)vecArtists.size(); i++)
501     {
502       long lArtistId = AddArtist(vecArtists[i]);
503       if (lArtistId >= 0)
504       { // added successfully, we must now add entries to the exartistsong table
505         CStdString strSQL;
506         // first link the artist with the song
507         bool bInsert = true;
508         if (bCheck)
509         {
510           strSQL=FormatSQL("select * from exartistsong where idSong=%i and idArtist=%i",
511                         lSongId, lArtistId);
512           if (!m_pDS->query(strSQL.c_str())) return ;
513           if (m_pDS->num_rows() != 0)
514             bInsert = false; // already exists
515           m_pDS->close();
516         }
517         if (bInsert)
518         {
519           strSQL=FormatSQL("insert into exartistsong (idSong,iPosition,idArtist) values(%i,%i,%i)",
520                         lSongId, i, lArtistId);
521
522           m_pDS->exec(strSQL.c_str());
523         }
524       }
525     }
526   }
527   catch (...)
528   {
529     CLog::Log(LOGERROR, "%s(%lu) failed", __FUNCTION__, lSongId);
530   }
531 }
532
533 void CMusicDatabase::AddExtraAlbumArtists(const CStdStringArray &vecArtists, long lAlbumId)
534 {
535   try
536   {
537     // add each of the artists in the vector of artists
538     for (int i = 1; i < (int)vecArtists.size(); i++)
539     {
540       long lArtistId = AddArtist(vecArtists[i]);
541       if (lArtistId >= 0)
542       { // added successfully, we must now add entries to the exartistalbum table
543         CStdString strSQL;
544         bool bInsert = true;
545         // always check artists (as this routine is called whenever a song is added)
546         strSQL=FormatSQL("select * from exartistalbum where idAlbum=%i and idArtist=%i",
547                       lAlbumId, lArtistId);
548         if (!m_pDS->query(strSQL.c_str())) return ;
549         if (m_pDS->num_rows() != 0)
550           bInsert = false; // already exists
551         m_pDS->close();
552         if (bInsert)
553         {
554           strSQL=FormatSQL("insert into exartistalbum (idAlbum,iPosition,idArtist) values(%i,%i,%i)",
555                         lAlbumId, i, lArtistId);
556
557           m_pDS->exec(strSQL.c_str());
558         }
559       }
560     }
561   }
562   catch (...)
563   {
564     CLog::Log(LOGERROR, "%s(%lu) failed", __FUNCTION__, lAlbumId);
565   }
566 }
567
568 void CMusicDatabase::AddExtraGenres(const CStdStringArray &vecGenres, long lSongId, long lAlbumId, bool bCheck)
569 {
570   try
571   {
572     // add each of the genres in the vector
573     for (int i = 1; i < (int)vecGenres.size(); i++)
574     {
575       long lGenreId = AddGenre(vecGenres[i]);
576       if (lGenreId >= 0)
577       { // added successfully!
578         CStdString strSQL;
579         // first link the genre with the song
580         bool bInsert = true;
581         if (lSongId)
582         {
583           if (bCheck)
584           {
585             strSQL=FormatSQL("select * from exgenresong where idSong=%i and idGenre=%i",
586                           lSongId, lGenreId);
587             if (!m_pDS->query(strSQL.c_str())) return ;
588             if (m_pDS->num_rows() != 0)
589               bInsert = false; // already exists
590             m_pDS->close();
591           }
592           if (bInsert)
593           {
594             strSQL=FormatSQL("insert into exgenresong (idSong,iPosition,idGenre) values(%i,%i,%i)",
595                           lSongId, i, lGenreId);
596
597             m_pDS->exec(strSQL.c_str());
598           }
599         }
600         // now link the genre with the album - we always check these as there's usually
601         // more than one song per album with the same extra genres
602         if (lAlbumId)
603         {
604           strSQL=FormatSQL("select * from exgenrealbum where idAlbum=%i and idGenre=%i",
605                         lAlbumId, lGenreId);
606           if (!m_pDS->query(strSQL.c_str())) return ;
607           if (m_pDS->num_rows() == 0)
608           { // insert
609             m_pDS->close();
610             strSQL=FormatSQL("insert into exgenrealbum (idAlbum,iPosition,idGenre) values(%i,%i,%i)",
611                           lAlbumId, i, lGenreId);
612
613             m_pDS->exec(strSQL.c_str());
614           }
615         }
616       }
617     }
618   }
619   catch (...)
620   {
621     CLog::Log(LOGERROR, "%s(%lu,%lu) failed", __FUNCTION__, lSongId, lAlbumId);
622   }
623 }
624
625 long CMusicDatabase::AddPath(const CStdString& strPath)
626 {
627   CStdString strSQL;
628   try
629   {
630     ASSERT(CUtil::HasSlashAtEnd(strPath));
631
632     if (NULL == m_pDB.get()) return -1;
633     if (NULL == m_pDS.get()) return -1;
634
635     map <CStdString, int>::const_iterator it;
636
637     it = m_pathCache.find(strPath);
638     if (it != m_pathCache.end())
639       return it->second;
640
641     strSQL=FormatSQL( "select * from path where strPath like '%s'", strPath.c_str());
642     m_pDS->query(strSQL.c_str());
643     if (m_pDS->num_rows() == 0)
644     {
645       m_pDS->close();
646       // doesnt exists, add it
647       strSQL=FormatSQL("insert into path (idPath, strPath) values( NULL, '%s' )", strPath.c_str());
648       m_pDS->exec(strSQL.c_str());
649
650       int idPath = (long)sqlite3_last_insert_rowid(m_pDB->getHandle());
651       m_pathCache.insert(pair<CStdString, int>(strPath, idPath));
652       return idPath;
653     }
654     else
655     {
656       int idPath = m_pDS->fv("idPath").get_asLong();
657       m_pathCache.insert(pair<CStdString, int>(strPath, idPath));
658       m_pDS->close();
659       return idPath;
660     }
661   }
662   catch (...)
663   {
664     CLog::Log(LOGERROR, "musicdatabase:unable to addpath (%s)", strSQL.c_str());
665   }
666
667   return -1;
668 }
669
670 CSong CMusicDatabase::GetSongFromDataset(bool bWithMusicDbPath/*=false*/)
671 {
672   CSong song;
673   song.idSong = m_pDS->fv(song_idSong).get_asLong();
674   // get the full artist string
675   song.strArtist = m_pDS->fv(song_strArtist).get_asString();
676   song.strArtist += m_pDS->fv(song_strExtraArtists).get_asString();
677   // and the full genre string
678   song.strGenre = m_pDS->fv(song_strGenre).get_asString();
679   song.strGenre += m_pDS->fv(song_strExtraGenres).get_asString();
680   // and the rest...
681   song.strAlbum = m_pDS->fv(song_strAlbum).get_asString();
682   song.iTrack = m_pDS->fv(song_iTrack).get_asLong() ;
683   song.iDuration = m_pDS->fv(song_iDuration).get_asLong() ;
684   song.iYear = m_pDS->fv(song_iYear).get_asLong() ;
685   song.strTitle = m_pDS->fv(song_strTitle).get_asString();
686   song.iTimesPlayed = m_pDS->fv(song_iTimesPlayed).get_asLong();
687   song.lastPlayed = m_pDS->fv(song_lastplayed).get_asString();
688   song.iStartOffset = m_pDS->fv(song_iStartOffset).get_asLong();
689   song.iEndOffset = m_pDS->fv(song_iEndOffset).get_asLong();
690   song.strMusicBrainzTrackID = m_pDS->fv(song_strMusicBrainzTrackID).get_asString();
691   song.strMusicBrainzArtistID = m_pDS->fv(song_strMusicBrainzArtistID).get_asString();
692   song.strMusicBrainzAlbumID = m_pDS->fv(song_strMusicBrainzAlbumID).get_asString();
693   song.strMusicBrainzAlbumArtistID = m_pDS->fv(song_strMusicBrainzAlbumArtistID).get_asString();
694   song.strMusicBrainzTRMID = m_pDS->fv(song_strMusicBrainzTRMID).get_asString();
695   song.rating = m_pDS->fv(song_rating).get_asChar();
696   song.strComment = m_pDS->fv(song_comment).get_asString();
697   song.strThumb = m_pDS->fv(song_strThumb).get_asString();
698
699   // Get karaoke data if available
700   std::auto_ptr<dbiplus::Dataset> pKDS;
701   pKDS.reset( m_pDB->CreateDataset() );
702
703   if ( pKDS.get() )
704   {
705     CStdString strSQL=FormatSQL("SELECT * FROM karaokedata where idSong=%ld", song.idSong);
706
707     if ( pKDS->query(strSQL.c_str() ) && pKDS->num_rows() > 0 )
708     {
709       song.iKaraokeNumber = pKDS->fv("iKaraNumber").get_asLong();
710       song.strKaraokeLyrEncoding = pKDS->fv("strKaraEncoding").get_asString();
711       song.iKaraokeDelay = pKDS->fv("iKaraDelay").get_asLong();
712     }
713
714     pKDS->close();
715   }
716
717   if (song.strThumb == "NONE")
718     song.strThumb.Empty();
719   // Get filename with full path
720   if (!bWithMusicDbPath)
721     CUtil::AddFileToFolder(m_pDS->fv(song_strPath).get_asString(), m_pDS->fv(song_strFileName).get_asString(), song.strFileName);
722   else
723   {
724     CStdString strFileName=m_pDS->fv(song_strFileName).get_asString();
725     CStdString strExt=CUtil::GetExtension(strFileName);
726     song.strFileName.Format("musicdb://3/%ld/%ld%s", m_pDS->fv(song_idAlbum).get_asLong(), m_pDS->fv(song_idSong).get_asLong(), strExt.c_str());
727   }
728
729   return song;
730 }
731
732 void CMusicDatabase::GetFileItemFromDataset(CFileItem* item, const CStdString& strMusicDBbasePath)
733 {
734   // get the full artist string
735   CStdString strArtist=m_pDS->fv(song_strArtist).get_asString();
736   strArtist += m_pDS->fv(song_strExtraArtists).get_asString();
737   item->GetMusicInfoTag()->SetArtist(strArtist);
738   // and the full genre string
739   CStdString strGenre = m_pDS->fv(song_strGenre).get_asString();
740   strGenre += m_pDS->fv(song_strExtraGenres).get_asString();
741   item->GetMusicInfoTag()->SetGenre(strGenre);
742   // and the rest...
743   item->GetMusicInfoTag()->SetAlbum(m_pDS->fv(song_strAlbum).get_asString());
744   item->GetMusicInfoTag()->SetTrackAndDiskNumber(m_pDS->fv(song_iTrack).get_asLong());
745   item->GetMusicInfoTag()->SetDuration(m_pDS->fv(song_iDuration).get_asLong());
746   item->GetMusicInfoTag()->SetDatabaseId(m_pDS->fv(song_idSong).get_asLong());
747   SYSTEMTIME stTime;
748   stTime.wYear = (WORD)m_pDS->fv(song_iYear).get_asLong();
749   item->GetMusicInfoTag()->SetReleaseDate(stTime);
750   item->GetMusicInfoTag()->SetTitle(m_pDS->fv(song_strTitle).get_asString());
751   item->SetLabel(m_pDS->fv(song_strTitle).get_asString());
752   //song.iTimesPlayed = m_pDS->fv(song_iTimesPlayed).get_asLong();
753   item->m_lStartOffset = m_pDS->fv(song_iStartOffset).get_asLong();
754   item->m_lEndOffset = m_pDS->fv(song_iEndOffset).get_asLong();
755   item->GetMusicInfoTag()->SetMusicBrainzTrackID(m_pDS->fv(song_strMusicBrainzTrackID).get_asString());
756   item->GetMusicInfoTag()->SetMusicBrainzArtistID(m_pDS->fv(song_strMusicBrainzArtistID).get_asString());
757   item->GetMusicInfoTag()->SetMusicBrainzAlbumID(m_pDS->fv(song_strMusicBrainzAlbumID).get_asString());
758   item->GetMusicInfoTag()->SetMusicBrainzAlbumArtistID(m_pDS->fv(song_strMusicBrainzAlbumArtistID).get_asString());
759   item->GetMusicInfoTag()->SetMusicBrainzTRMID(m_pDS->fv(song_strMusicBrainzTRMID).get_asString());
760   item->GetMusicInfoTag()->SetRating(m_pDS->fv(song_rating).get_asChar());
761   item->GetMusicInfoTag()->SetComment(m_pDS->fv(song_comment).get_asString());
762   CStdString strRealPath;
763   CUtil::AddFileToFolder(m_pDS->fv(song_strPath).get_asString(), m_pDS->fv(song_strFileName).get_asString(), strRealPath);
764   item->GetMusicInfoTag()->SetURL(strRealPath);
765   item->GetMusicInfoTag()->SetLoaded(true);
766   CStdString strThumb=m_pDS->fv(song_strThumb).get_asString();
767   if (strThumb != "NONE")
768     item->SetThumbnailImage(strThumb);
769   // Get filename with full path
770   if (strMusicDBbasePath.IsEmpty())
771   {
772     item->m_strPath = strRealPath;
773   }
774   else
775   {
776     CStdString strFileName=m_pDS->fv(song_strFileName).get_asString();
777     CStdString strExt=CUtil::GetExtension(strFileName);
778     item->m_strPath.Format("%s%ld%s", strMusicDBbasePath.c_str(), m_pDS->fv(song_idSong).get_asLong(), strExt.c_str());
779   }
780 }
781
782 CAlbum CMusicDatabase::GetAlbumFromDataset(dbiplus::Dataset* pDS, bool imageURL /* = false*/)
783 {
784   CAlbum album;
785   album.idAlbum = pDS->fv(album_idAlbum).get_asLong();
786   album.strAlbum = pDS->fv(album_strAlbum).get_asString();
787   album.strArtist = pDS->fv(album_strArtist).get_asString();
788   album.strArtist += pDS->fv(album_strExtraArtists).get_asString();
789   // workaround... the fake "Unknown" album usually has a NULL artist.
790   // since it can contain songs from lots of different artists, lets set
791   // it to "Various Artists" instead
792   if (pDS->fv("artist.idArtist").get_asLong() == -1)
793     album.strArtist = g_localizeStrings.Get(340);
794   album.strGenre = pDS->fv(album_strGenre).get_asString();
795   album.strGenre += pDS->fv(album_strExtraGenres).get_asString();
796   album.iYear = pDS->fv(album_iYear).get_asLong();
797   if (imageURL)
798     album.thumbURL.ParseString(pDS->fv(album_strThumbURL).get_asString());
799   else
800   {
801     CStdString strThumb = pDS->fv(album_strThumb).get_asString();
802     if (strThumb != "NONE")
803       album.thumbURL.ParseString(strThumb);
804   }
805   album.iRating = pDS->fv(album_iRating).get_asLong();
806   album.iYear = pDS->fv(album_iYear).get_asLong();
807   album.strReview = pDS->fv(album_strReview).get_asString();
808   album.strStyles = pDS->fv(album_strStyles).get_asString();
809   album.strMoods = pDS->fv(album_strMoods).get_asString();
810   album.strThemes = pDS->fv(album_strThemes).get_asString();
811   album.strLabel = pDS->fv(album_strLabel).get_asString();
812   album.strType = pDS->fv(album_strType).get_asString();
813   return album;
814 }
815
816 CArtist CMusicDatabase::GetArtistFromDataset(dbiplus::Dataset* pDS, bool needThumb)
817 {
818   CArtist artist;
819   artist.idArtist = pDS->fv(artist_idArtist).get_asLong();
820   artist.strArtist = pDS->fv("artist.strArtist").get_asString();
821   artist.strGenre = pDS->fv(artist_strGenres).get_asString();
822   artist.strBiography = pDS->fv(artist_strBiography).get_asString();
823   artist.strStyles = pDS->fv(artist_strStyles).get_asString();
824   artist.strMoods = pDS->fv(artist_strMoods).get_asString();
825   artist.strBorn = pDS->fv(artist_strBorn).get_asString();
826   artist.strFormed = pDS->fv(artist_strFormed).get_asString();
827   artist.strDied = pDS->fv(artist_strDied).get_asString();
828   artist.strDisbanded = pDS->fv(artist_strDisbanded).get_asString();
829   artist.strYearsActive = pDS->fv(artist_strYearsActive).get_asString();
830   artist.strInstruments = pDS->fv(artist_strInstruments).get_asString();
831
832   if (needThumb)
833     artist.thumbURL.ParseString(pDS->fv(artist_strImage).get_asString());
834
835   return artist;
836 }
837
838 bool CMusicDatabase::GetSongByFileName(const CStdString& strFileName, CSong& song)
839 {
840   try
841   {
842     song.Clear();
843     CURL url(strFileName);
844
845     if (url.GetProtocol()=="musicdb")
846     {
847       CStdString strFile = CUtil::GetFileName(strFileName);
848       CUtil::RemoveExtension(strFile);
849       return GetSongById(atol(strFile.c_str()), song);
850     }
851
852     CStdString strPath;
853     CUtil::GetDirectory(strFileName, strPath);
854     CUtil::AddSlashAtEnd(strPath);
855
856     if (NULL == m_pDB.get()) return false;
857     if (NULL == m_pDS.get()) return false;
858
859     DWORD crc = ComputeCRC(strFileName);
860
861     CStdString strSQL=FormatSQL("select * from songview "
862                                 "where dwFileNameCRC='%ul' and strPath like'%s'"
863                                 , crc,
864                                 strPath.c_str());
865
866     if (!m_pDS->query(strSQL.c_str())) return false;
867     int iRowsFound = m_pDS->num_rows();
868     if (iRowsFound == 0)
869     {
870       m_pDS->close();
871       return false;
872     }
873     song = GetSongFromDataset();
874     m_pDS->close(); // cleanup recordset data
875     return true;
876   }
877   catch (...)
878   {
879     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strFileName.c_str());
880   }
881
882   return false;
883 }
884
885 long CMusicDatabase::GetAlbumIdByPath(const CStdString& strPath)
886 {
887   try
888   {
889     CStdString strSQL=FormatSQL("select distinct idAlbum from song join path on song.idPath = path.idPath where path.strPath like '%s'", strPath.c_str());
890     m_pDS->query(strSQL.c_str());
891     if (m_pDS->eof())
892       return -1;
893
894     int idAlbum = m_pDS->fv(0).get_asLong();
895     m_pDS->close();
896
897     return idAlbum;
898   }
899   catch (...)
900   {
901     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strPath.c_str());
902   }
903
904   return false;
905 }
906
907 long CMusicDatabase::GetSongByArtistAndAlbumAndTitle(const CStdString& strArtist, const CStdString& strAlbum, const CStdString& strTitle)
908 {
909   try
910   {
911     CStdString strSQL=FormatSQL("select idsong from songview "
912                                 "where strArtist like '%s' and strAlbum like '%s' and "
913                                 "strTitle like '%s'",strArtist.c_str(),strAlbum.c_str(),strTitle.c_str());
914
915     if (!m_pDS->query(strSQL.c_str())) return false;
916     int iRowsFound = m_pDS->num_rows();
917     if (iRowsFound == 0)
918     {
919       m_pDS->close();
920       return -1;
921     }
922     long lResult = m_pDS->fv(0).get_asLong();
923     m_pDS->close(); // cleanup recordset data
924     return lResult;
925   }
926   catch (...)
927   {
928     CLog::Log(LOGERROR, "%s (%s,%s,%s) failed", __FUNCTION__, strArtist.c_str(),strAlbum.c_str(),strTitle.c_str());
929   }
930
931   return -1;
932 }
933
934 bool CMusicDatabase::GetSongById(long idSong, CSong& song)
935 {
936   try
937   {
938     song.Clear();
939
940     if (NULL == m_pDB.get()) return false;
941     if (NULL == m_pDS.get()) return false;
942
943     CStdString strSQL=FormatSQL("select * from songview "
944                                 "where idSong=%ld"
945                                 , idSong);
946
947     if (!m_pDS->query(strSQL.c_str())) return false;
948     int iRowsFound = m_pDS->num_rows();
949     if (iRowsFound == 0)
950     {
951       m_pDS->close();
952       return false;
953     }
954     song = GetSongFromDataset();
955     m_pDS->close(); // cleanup recordset data
956     return true;
957   }
958   catch (...)
959   {
960     CLog::Log(LOGERROR, "%s(%ld) failed", __FUNCTION__, idSong);
961   }
962
963   return false;
964 }
965
966 bool CMusicDatabase::SearchArtists(const CStdString& search, CFileItemList &artists)
967 {
968   try
969   {
970     if (NULL == m_pDB.get()) return false;
971     if (NULL == m_pDS.get()) return false;
972
973     // Exclude "Various Artists"
974     long lVariousArtistId = AddArtist(g_localizeStrings.Get(340));
975
976     CStdString strSQL;
977     if (search.GetLength() >= MIN_FULL_SEARCH_LENGTH)
978       strSQL=FormatSQL("select * from artist "
979                                 "where (strArtist like '%s%%' or strArtist like '%% %s%%') and idArtist <> %i "
980                                 , search.c_str(), search.c_str(), lVariousArtistId );
981     else
982       strSQL=FormatSQL("select * from artist "
983                                 "where strArtist like '%s%%' and idArtist <> %i "
984                                 , search.c_str(), lVariousArtistId );
985
986     if (!m_pDS->query(strSQL.c_str())) return false;
987     if (m_pDS->num_rows() == 0)
988     {
989       m_pDS->close();
990       return false;
991     }
992
993     CStdString artistLabel(g_localizeStrings.Get(557)); // Artist
994     while (!m_pDS->eof())
995     {
996       CStdString path;
997       path.Format("musicdb://2/%ld/", m_pDS->fv(0).get_asLong());
998       CFileItemPtr pItem(new CFileItem(path, true));
999       CStdString label;
1000       label.Format("[%s] %s", artistLabel.c_str(), m_pDS->fv(1).get_asString());
1001       pItem->SetLabel(label);
1002       label.Format("A %s", m_pDS->fv(1).get_asString()); // sort label is stored in the title tag
1003       pItem->GetMusicInfoTag()->SetTitle(label);
1004       pItem->SetCachedArtistThumb();
1005       artists.Add(pItem);
1006       m_pDS->next();
1007     }
1008
1009     m_pDS->close(); // cleanup recordset data
1010     return true;
1011   }
1012   catch (...)
1013   {
1014     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1015   }
1016
1017   return false;
1018 }
1019
1020 bool CMusicDatabase::GetArbitraryQuery(const CStdString& strQuery, const CStdString& strOpenRecordSet, const CStdString& strCloseRecordSet,
1021                                        const CStdString& strOpenRecord, const CStdString& strCloseRecord, const CStdString& strOpenField,
1022                                        const CStdString& strCloseField, CStdString& strResult)
1023 {
1024   try
1025   {
1026     strResult = "";
1027     if (NULL == m_pDB.get()) return false;
1028     if (NULL == m_pDS.get()) return false;
1029     CStdString strSQL=FormatSQL(strQuery);
1030     if (!m_pDS->query(strSQL.c_str()))
1031     {
1032       strResult = m_pDB->getErrorMsg();
1033       return false;
1034     }
1035     strResult=strOpenRecordSet;
1036     while (!m_pDS->eof())
1037     {
1038       strResult += strOpenRecord;
1039       for (int i=0; i<m_pDS->fieldCount(); i++)
1040       {
1041         strResult += strOpenField;
1042         strResult += m_pDS->fv(i).get_asString();
1043         strResult += strCloseField;
1044       }
1045       strResult += strCloseRecord;
1046       m_pDS->next();
1047     }
1048     strResult += strCloseRecordSet;
1049     m_pDS->close();
1050     return true;
1051   }
1052   catch (...)
1053   {
1054     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strQuery.c_str());
1055   }
1056   try
1057   {
1058     if (NULL == m_pDB.get()) return false;
1059       strResult = m_pDB->getErrorMsg();
1060   }
1061   catch (...)
1062   {
1063
1064   }
1065
1066   return false;
1067 }
1068
1069 bool CMusicDatabase::GetAlbumInfo(long idAlbum, CAlbum &info, VECSONGS* songs)
1070 {
1071   try
1072   {
1073     if (idAlbum == -1)
1074       return false; // not in the database
1075
1076     CStdString strSQL=FormatSQL("select * from albumview where idAlbum = %ld", idAlbum);
1077
1078     if (!m_pDS2->query(strSQL.c_str())) return false;
1079     int iRowsFound = m_pDS2->num_rows();
1080     if (iRowsFound != 0)
1081     {
1082       info = GetAlbumFromDataset(m_pDS2.get(), true); // true to grab the thumburl rather than the thumb
1083       long idAlbumInfo = m_pDS2->fv(album_idAlbumInfo).get_asLong();
1084       m_pDS2->close(); // cleanup recordset data
1085
1086       if (songs)
1087         GetAlbumInfoSongs(idAlbumInfo, *songs);
1088
1089       return true;
1090     }
1091     m_pDS2->close();
1092     return false;
1093   }
1094   catch (...)
1095   {
1096     CLog::Log(LOGERROR, "%s(%ld) failed", __FUNCTION__, idAlbum);
1097   }
1098
1099   return false;
1100 }
1101
1102 bool CMusicDatabase::HasAlbumInfo(long idAlbum)
1103 {
1104   try
1105   {
1106     if (idAlbum == -1)
1107       return false; // not in the database
1108
1109     CStdString strSQL=FormatSQL("select * from albuminfo where idAlbum = %ld", idAlbum);
1110
1111     if (!m_pDS2->query(strSQL.c_str())) return false;
1112     int iRowsFound = m_pDS2->num_rows();
1113     m_pDS2->close();
1114     return iRowsFound > 0;
1115   }
1116   catch (...)
1117   {
1118     CLog::Log(LOGERROR, "%s(%ld) failed", __FUNCTION__, idAlbum);
1119   }
1120
1121   return false;
1122 }
1123
1124 bool CMusicDatabase::DeleteAlbumInfo(long idAlbum)
1125 {
1126   try
1127   {
1128     if (idAlbum == -1)
1129       return false; // not in the database
1130
1131     CStdString strSQL = FormatSQL("delete from albuminfo where idAlbum=%u",idAlbum);
1132
1133     if (!m_pDS2->exec(strSQL.c_str()))
1134       return false;
1135
1136     return true;
1137   }
1138   catch (...)
1139   {
1140     CLog::Log(LOGERROR, "%s - (%ld) failed", __FUNCTION__, idAlbum);
1141   }
1142
1143   return false;
1144 }
1145
1146 bool CMusicDatabase::GetArtistInfo(long idArtist, CArtist &info, bool needAll)
1147 {
1148   try
1149   {
1150     if (idArtist == -1)
1151       return false; // not in the database
1152
1153     CStdString strSQL=FormatSQL("select * from artistinfo "
1154                                 "join artist on artist.idartist=artistinfo.idArtist "
1155                                 "where artistinfo.idArtist = %ld"
1156                                 , idArtist);
1157
1158     if (!m_pDS2->query(strSQL.c_str())) return false;
1159     int iRowsFound = m_pDS2->num_rows();
1160     if (iRowsFound != 0)
1161     {
1162       info = GetArtistFromDataset(m_pDS2.get(),needAll);
1163       if (needAll)
1164       {
1165         strSQL=FormatSQL("select * from discography where idArtist=%i",idArtist);
1166         m_pDS2->query(strSQL.c_str());
1167         while (!m_pDS2->eof())
1168         {
1169           info.discography.push_back(make_pair(m_pDS2->fv("strAlbum").get_asString(),m_pDS2->fv("strYear").get_asString()));
1170           m_pDS2->next();
1171         }
1172       }
1173       m_pDS2->close(); // cleanup recordset data
1174       return true;
1175     }
1176     m_pDS2->close();
1177     return false;
1178   }
1179   catch (...)
1180   {
1181     CLog::Log(LOGERROR, "%s - (%ld) failed", __FUNCTION__, idArtist);
1182   }
1183
1184   return false;
1185 }
1186
1187 bool CMusicDatabase::DeleteArtistInfo(long idArtist)
1188 {
1189   try
1190   {
1191     if (idArtist == -1)
1192       return false; // not in the database
1193
1194     CStdString strSQL = FormatSQL("delete from artistinfo where idartist=%u",idArtist);
1195
1196     if (!m_pDS2->exec(strSQL.c_str()))
1197       return false;
1198
1199     return true;
1200   }
1201   catch (...)
1202   {
1203     CLog::Log(LOGERROR, "%s - (%ld) failed", __FUNCTION__, idArtist);
1204   }
1205
1206   return false;
1207 }
1208
1209 bool CMusicDatabase::GetAlbumInfoSongs(long idAlbumInfo, VECSONGS& songs)
1210 {
1211   try
1212   {
1213     CStdString strSQL=FormatSQL("select * from albuminfosong "
1214                                 "where idAlbumInfo=%i "
1215                                 "order by iTrack", idAlbumInfo);
1216
1217     if (!m_pDS2->query(strSQL.c_str())) return false;
1218     int iRowsFound = m_pDS2->num_rows();
1219     if (iRowsFound == 0) return false;
1220     while (!m_pDS2->eof())
1221     {
1222       CSong song;
1223       song.iTrack = m_pDS2->fv("iTrack").get_asLong();
1224       song.strTitle = m_pDS2->fv("strTitle").get_asString();
1225       song.iDuration = m_pDS2->fv("iDuration").get_asLong();
1226
1227       songs.push_back(song);
1228       m_pDS2->next();
1229     }
1230
1231     m_pDS2->close(); // cleanup recordset data
1232
1233     return true;
1234   }
1235   catch (...)
1236   {
1237     CLog::Log(LOGERROR, "%s(%lu) failed", __FUNCTION__, idAlbumInfo);
1238   }
1239
1240   return false;
1241 }
1242
1243 bool CMusicDatabase::GetTop100(const CStdString& strBaseDir, CFileItemList& items)
1244 {
1245   try
1246   {
1247     if (NULL == m_pDB.get()) return false;
1248     if (NULL == m_pDS.get()) return false;
1249
1250     CStdString strSQL="select * from songview "
1251                       "where iTimesPlayed>0 "
1252                       "order by iTimesPlayed desc "
1253                       "limit 100";
1254
1255     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
1256     if (!m_pDS->query(strSQL.c_str())) return false;
1257     int iRowsFound = m_pDS->num_rows();
1258     if (iRowsFound == 0)
1259     {
1260       m_pDS->close();
1261       return false;
1262     }
1263     items.Reserve(iRowsFound);
1264     while (!m_pDS->eof())
1265     {
1266       CFileItemPtr item(new CFileItem);
1267       GetFileItemFromDataset(item.get(), strBaseDir);
1268       items.Add(item);
1269       m_pDS->next();
1270     }
1271
1272     m_pDS->close(); // cleanup recordset data
1273     return true;
1274   }
1275   catch (...)
1276   {
1277     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1278   }
1279
1280   return false;
1281 }
1282
1283 bool CMusicDatabase::GetTop100Albums(VECALBUMS& albums)
1284 {
1285   try
1286   {
1287     albums.erase(albums.begin(), albums.end());
1288     if (NULL == m_pDB.get()) return false;
1289     if (NULL == m_pDS.get()) return false;
1290
1291     // NOTE: The song.idAlbum is needed for the group by, as for some reason group by albumview.idAlbum doesn't work
1292     //       consistently - possibly an SQLite bug, as it works fine in SQLiteSpy (v3.3.17)
1293     CStdString strSQL = "select albumview.*, sum(song.iTimesPlayed) as total, song.idAlbum from song "
1294                     "join albumview on albumview.idAlbum=song.idAlbum "
1295                     "where song.iTimesPlayed>0 "
1296                     "group by song.idAlbum "
1297                     "order by total desc "
1298                     "limit 100 ";
1299
1300     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
1301     if (!m_pDS->query(strSQL.c_str())) return false;
1302     int iRowsFound = m_pDS->num_rows();
1303     if (iRowsFound == 0)
1304     {
1305       m_pDS->close();
1306       return false;
1307     }
1308     while (!m_pDS->eof())
1309     {
1310       albums.push_back(GetAlbumFromDataset(m_pDS.get()));
1311       m_pDS->next();
1312     }
1313
1314     m_pDS->close(); // cleanup recordset data
1315     return true;
1316   }
1317   catch (...)
1318   {
1319     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1320   }
1321
1322   return false;
1323 }
1324
1325 bool CMusicDatabase::GetTop100AlbumSongs(const CStdString& strBaseDir, CFileItemList& items)
1326 {
1327   try
1328   {
1329     if (NULL == m_pDB.get()) return false;
1330     if (NULL == m_pDS.get()) return false;
1331
1332     CStdString strSQL;
1333     strSQL.Format("select * from songview join albumview on (songview.idAlbum = albumview.idAlbum) where albumview.idalbum in (select song.idAlbum from song where song.iTimesPlayed>0 group by idalbum order by sum(song.iTimesPlayed) desc limit 100) order by albumview.idalbum in (select song.idAlbum from song where song.iTimesPlayed>0 group by idalbum order by sum(song.iTimesPlayed) desc limit 100)");
1334     CLog::Log(LOGDEBUG,"GetTop100AlbumSongs() query: %s", strSQL.c_str());
1335     if (!m_pDS->query(strSQL.c_str())) return false;
1336
1337     int iRowsFound = m_pDS->num_rows();
1338     if (iRowsFound == 0)
1339     {
1340       m_pDS->close();
1341       return false;
1342     }
1343
1344     // get data from returned rows
1345     items.Reserve(iRowsFound);
1346     while (!m_pDS->eof())
1347     {
1348       CFileItemPtr item(new CFileItem);
1349       GetFileItemFromDataset(item.get(), strBaseDir);
1350       items.Add(item);
1351       m_pDS->next();
1352     }
1353
1354     // cleanup
1355     m_pDS->close();
1356     return true;
1357   }
1358   catch (...)
1359   {
1360     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1361   }
1362   return false;
1363 }
1364
1365 bool CMusicDatabase::GetRecentlyPlayedAlbums(VECALBUMS& albums)
1366 {
1367   try
1368   {
1369     albums.erase(albums.begin(), albums.end());
1370     if (NULL == m_pDB.get()) return false;
1371     if (NULL == m_pDS.get()) return false;
1372
1373     CStdString strSQL;
1374     strSQL.Format("select distinct albumview.* from song join albumview on albumview.idAlbum=song.idAlbum where song.lastplayed NOT NULL order by song.lastplayed desc limit %i", RECENTLY_PLAYED_LIMIT);
1375     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
1376     if (!m_pDS->query(strSQL.c_str())) return false;
1377     int iRowsFound = m_pDS->num_rows();
1378     if (iRowsFound == 0)
1379     {
1380       m_pDS->close();
1381       return false;
1382     }
1383     while (!m_pDS->eof())
1384     {
1385       albums.push_back(GetAlbumFromDataset(m_pDS.get()));
1386       m_pDS->next();
1387     }
1388
1389     m_pDS->close(); // cleanup recordset data
1390     return true;
1391   }
1392   catch (...)
1393   {
1394     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1395   }
1396
1397   return false;
1398 }
1399
1400 bool CMusicDatabase::GetRecentlyPlayedAlbumSongs(const CStdString& strBaseDir, CFileItemList& items)
1401 {
1402   try
1403   {
1404     if (NULL == m_pDB.get()) return false;
1405     if (NULL == m_pDS.get()) return false;
1406
1407     CStdString strSQL;
1408     strSQL.Format("select * from songview join albumview on (songview.idAlbum = albumview.idAlbum) where albumview.idalbum in (select distinct albumview.idalbum from albumview join song on albumview.idAlbum=song.idAlbum where song.lastplayed NOT NULL order by song.lastplayed desc limit %i)", RECENTLY_ADDED_LIMIT);
1409     CLog::Log(LOGDEBUG,"GetRecentlyPlayedAlbumSongs() query: %s", strSQL.c_str());
1410     if (!m_pDS->query(strSQL.c_str())) return false;
1411
1412     int iRowsFound = m_pDS->num_rows();
1413     if (iRowsFound == 0)
1414     {
1415       m_pDS->close();
1416       return false;
1417     }
1418
1419     // get data from returned rows
1420     items.Reserve(iRowsFound);
1421     while (!m_pDS->eof())
1422     {
1423       CFileItemPtr item(new CFileItem);
1424       GetFileItemFromDataset(item.get(), strBaseDir);
1425       items.Add(item);
1426       m_pDS->next();
1427     }
1428
1429     // cleanup
1430     m_pDS->close();
1431     return true;
1432   }
1433   catch (...)
1434   {
1435     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1436   }
1437   return false;
1438 }
1439
1440 bool CMusicDatabase::GetRecentlyAddedAlbums(VECALBUMS& albums)
1441 {
1442   try
1443   {
1444     albums.erase(albums.begin(), albums.end());
1445     if (NULL == m_pDB.get()) return false;
1446     if (NULL == m_pDS.get()) return false;
1447
1448     CStdString strSQL;
1449     strSQL.Format("select * from albumview order by idAlbum desc limit %i", RECENTLY_ADDED_LIMIT);
1450
1451     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
1452     if (!m_pDS->query(strSQL.c_str())) return false;
1453     int iRowsFound = m_pDS->num_rows();
1454     if (iRowsFound == 0)
1455     {
1456       m_pDS->close();
1457       return false;
1458     }
1459
1460     while (!m_pDS->eof())
1461     {
1462       albums.push_back(GetAlbumFromDataset(m_pDS.get()));
1463       m_pDS->next();
1464     }
1465
1466     m_pDS->close(); // cleanup recordset data
1467     return true;
1468   }
1469   catch (...)
1470   {
1471     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1472   }
1473
1474   return false;
1475 }
1476
1477 bool CMusicDatabase::GetRecentlyAddedAlbumSongs(const CStdString& strBaseDir, CFileItemList& items)
1478 {
1479   try
1480   {
1481     if (NULL == m_pDB.get()) return false;
1482     if (NULL == m_pDS.get()) return false;
1483
1484     CStdString strSQL;
1485     strSQL.Format("select songview.* from albumview join songview on (songview.idAlbum = albumview.idAlbum) where albumview.idalbum in ( select idAlbum from albumview order by idAlbum desc limit %i)", RECENTLY_ADDED_LIMIT);
1486     CLog::Log(LOGDEBUG,"GetRecentlyAddedAlbumSongs() query: %s", strSQL.c_str());
1487     if (!m_pDS->query(strSQL.c_str())) return false;
1488
1489     int iRowsFound = m_pDS->num_rows();
1490     if (iRowsFound == 0)
1491     {
1492       m_pDS->close();
1493       return false;
1494     }
1495
1496     // get data from returned rows
1497     items.Reserve(iRowsFound);
1498     while (!m_pDS->eof())
1499     {
1500       CFileItemPtr item(new CFileItem);
1501       GetFileItemFromDataset(item.get(), strBaseDir);
1502       items.Add(item);
1503       m_pDS->next();
1504     }
1505
1506     // cleanup
1507     m_pDS->close();
1508     return true;
1509   }
1510   catch (...)
1511   {
1512     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1513   }
1514   return false;
1515 }
1516
1517 bool CMusicDatabase::IncrTop100CounterByFileName(const CStdString& strFileName)
1518 {
1519   try
1520   {
1521     if (NULL == m_pDB.get()) return false;
1522     if (NULL == m_pDS.get()) return false;
1523
1524     long songID = GetSongIDFromPath(strFileName);
1525
1526     m_pDS->close();
1527
1528     CStdString sql=FormatSQL("UPDATE song SET iTimesPlayed=iTimesPlayed+1, lastplayed=CURRENT_TIMESTAMP where idSong=%ld", songID);
1529     m_pDS->exec(sql.c_str());
1530     return true;
1531   }
1532   catch (...)
1533   {
1534     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strFileName.c_str());
1535   }
1536
1537   return false;
1538 }
1539
1540 bool CMusicDatabase::GetSongsByPath(const CStdString& strPath, CSongMap& songs, bool bAppendToMap)
1541 {
1542   try
1543   {
1544     ASSERT(CUtil::HasSlashAtEnd(strPath));
1545
1546     if (!bAppendToMap)
1547       songs.Clear();
1548
1549     if (NULL == m_pDB.get()) return false;
1550     if (NULL == m_pDS.get()) return false;
1551
1552     CStdString strSQL=FormatSQL("select * from songview where strPath like '%s'", strPath.c_str() );
1553     if (!m_pDS->query(strSQL.c_str())) return false;
1554     int iRowsFound = m_pDS->num_rows();
1555     if (iRowsFound == 0)
1556     {
1557       m_pDS->close();
1558       return false;
1559     }
1560     while (!m_pDS->eof())
1561     {
1562       CSong song = GetSongFromDataset();
1563       songs.Add(song.strFileName, song);
1564       m_pDS->next();
1565     }
1566
1567     m_pDS->close(); // cleanup recordset data
1568     return true;
1569   }
1570   catch (...)
1571   {
1572     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strPath.c_str());
1573   }
1574
1575   return false;
1576 }
1577
1578 void CMusicDatabase::EmptyCache()
1579 {
1580   m_artistCache.erase(m_artistCache.begin(), m_artistCache.end());
1581   m_genreCache.erase(m_genreCache.begin(), m_genreCache.end());
1582   m_pathCache.erase(m_pathCache.begin(), m_pathCache.end());
1583   m_albumCache.erase(m_albumCache.begin(), m_albumCache.end());
1584   m_thumbCache.erase(m_thumbCache.begin(), m_thumbCache.end());
1585 }
1586
1587 bool CMusicDatabase::Search(const CStdString& search, CFileItemList &items)
1588 {
1589   DWORD time = timeGetTime();
1590   // first grab all the artists that match
1591   SearchArtists(search, items);
1592   CLog::Log(LOGDEBUG, "%s Artist search in %u ms",
1593             __FUNCTION__, timeGetTime() - time); time = timeGetTime();
1594
1595   // then albums that match
1596   SearchAlbums(search, items);
1597   CLog::Log(LOGDEBUG, "%s Album search in %u ms",
1598             __FUNCTION__, timeGetTime() - time); time = timeGetTime();
1599
1600   // and finally songs
1601   SearchSongs(search, items);
1602   CLog::Log(LOGDEBUG, "%s Songs search in %u ms",
1603             __FUNCTION__, timeGetTime() - time); time = timeGetTime();
1604   return true;
1605 }
1606
1607 bool CMusicDatabase::SearchSongs(const CStdString& search, CFileItemList &items)
1608 {
1609   try
1610   {
1611     if (NULL == m_pDB.get()) return false;
1612     if (NULL == m_pDS.get()) return false;
1613
1614     CStdString strSQL;
1615     if (search.GetLength() >= MIN_FULL_SEARCH_LENGTH)
1616       strSQL=FormatSQL("select * from songview where strTitle like '%s%%' or strTitle like '%% %s%%' limit 1000", search.c_str(), search.c_str());
1617     else
1618       strSQL=FormatSQL("select * from songview where strTitle like '%s%%' limit 1000", search.c_str());
1619
1620     if (!m_pDS->query(strSQL.c_str())) return false;
1621     if (m_pDS->num_rows() == 0) return false;
1622
1623     CStdString songLabel = g_localizeStrings.Get(179); // Song
1624     while (!m_pDS->eof())
1625     {
1626       CFileItemPtr item(new CFileItem);
1627       GetFileItemFromDataset(item.get(), "musicdb://4/");
1628       items.Add(item);
1629       m_pDS->next();
1630     }
1631
1632     m_pDS->close();
1633     return true;
1634   }
1635   catch (...)
1636   {
1637     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1638   }
1639
1640   return false;
1641 }
1642
1643 bool CMusicDatabase::SearchAlbums(const CStdString& search, CFileItemList &albums)
1644 {
1645   try
1646   {
1647     if (NULL == m_pDB.get()) return false;
1648     if (NULL == m_pDS.get()) return false;
1649
1650     CStdString strSQL;
1651     if (search.GetLength() >= MIN_FULL_SEARCH_LENGTH)
1652       strSQL=FormatSQL("select * from albumview where strAlbum like '%s%%' or strAlbum like '%% %s%%'", search.c_str(), search.c_str());
1653     else
1654       strSQL=FormatSQL("select * from albumview where strAlbum like '%s%%'", search.c_str());
1655
1656     if (!m_pDS->query(strSQL.c_str())) return false;
1657
1658     CStdString albumLabel(g_localizeStrings.Get(558)); // Album
1659     while (!m_pDS->eof())
1660     {
1661       CAlbum album = GetAlbumFromDataset(m_pDS.get());
1662       CStdString path;
1663       path.Format("musicdb://3/%ld/", album.idAlbum);
1664       CFileItemPtr pItem(new CFileItem(path, album));
1665       CStdString label;
1666       label.Format("[%s] %s", albumLabel.c_str(), album.strAlbum);
1667       pItem->SetLabel(label);
1668       label.Format("B %s", album.strAlbum); // sort label is stored in the title tag
1669       pItem->GetMusicInfoTag()->SetTitle(label);
1670       albums.Add(pItem);
1671       m_pDS->next();
1672     }
1673     m_pDS->close(); // cleanup recordset data
1674     return true;
1675   }
1676   catch (...)
1677   {
1678     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1679   }
1680   return false;
1681 }
1682
1683 long CMusicDatabase::SetAlbumInfo(long idAlbum, const CAlbum& album, const VECSONGS& songs, bool bTransaction)
1684 {
1685   CStdString strSQL;
1686   try
1687   {
1688     if (NULL == m_pDB.get()) return -1;
1689     if (NULL == m_pDS.get()) return -1;
1690
1691     if (bTransaction)
1692       BeginTransaction();
1693
1694     // and also the multiple genre string into single genres.
1695     CStdStringArray vecGenres; CStdString extraGenres;
1696     SplitString(album.strGenre, vecGenres, extraGenres);
1697     long lGenreId = AddGenre(vecGenres[0]);
1698
1699     // delete any album info we may have
1700     strSQL=FormatSQL("delete from albuminfo where idAlbum=%i", idAlbum);
1701     m_pDS->exec(strSQL.c_str());
1702
1703     // insert the albuminfo
1704     strSQL=FormatSQL("insert into albuminfo (idAlbumInfo,idAlbum,idGenre,strExtraGenres,strMoods,strStyles,strThemes,strReview,strImage,strLabel,strType,iRating,iYear) values(NULL,%i,%i,'%s','%s','%s','%s','%s','%s','%s','%s',%i,%i)",
1705                   idAlbum, lGenreId, extraGenres.c_str(),
1706                   album.strMoods.c_str(),
1707                   album.strStyles.c_str(),
1708                   album.strThemes.c_str(),
1709                   album.strReview.c_str(),
1710                   album.thumbURL.m_xml.c_str(),
1711                   album.strLabel.c_str(),
1712                   album.strType.c_str(),
1713                   album.iRating,
1714                   album.iYear);
1715     m_pDS->exec(strSQL.c_str());
1716     long idAlbumInfo = (long)sqlite3_last_insert_rowid(m_pDB->getHandle());
1717
1718     if (SetAlbumInfoSongs(idAlbumInfo, songs))
1719     {
1720       if (bTransaction)
1721         CommitTransaction();
1722     }
1723     else
1724     {
1725       if (bTransaction) // icky
1726         RollbackTransaction();
1727       idAlbumInfo = -1;
1728     }
1729
1730     return idAlbumInfo;
1731   }
1732   catch (...)
1733   {
1734     CLog::Log(LOGERROR, "%s failed with query (%s)", __FUNCTION__, strSQL.c_str());
1735   }
1736
1737   if (bTransaction)
1738     RollbackTransaction();
1739
1740   return -1;
1741 }
1742
1743 long CMusicDatabase::SetArtistInfo(long idArtist, const CArtist& artist)
1744 {
1745   CStdString strSQL;
1746   try
1747   {
1748     if (NULL == m_pDB.get()) return -1;
1749     if (NULL == m_pDS.get()) return -1;
1750
1751     // delete any artist info we may have
1752     strSQL=FormatSQL("delete from artistinfo where idArtist=%i", idArtist);
1753     m_pDS->exec(strSQL.c_str());
1754     strSQL=FormatSQL("delete from discography where idArtist=%i", idArtist);
1755     m_pDS->exec(strSQL.c_str());
1756
1757     // insert the artistinfo
1758     strSQL=FormatSQL("insert into artistinfo (idArtistInfo,idArtist,strBorn,strFormed,strGenres,strMoods,strStyles,strInstruments,strBiography,strDied,strDisbanded,strYearsActive,strImage) values(NULL,%i,'%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')",
1759                   idArtist, artist.strBorn.c_str(),
1760                   artist.strFormed.c_str(),
1761                   artist.strGenre.c_str(),
1762                   artist.strMoods.c_str(),
1763                   artist.strStyles.c_str(),
1764                   artist.strInstruments.c_str(),
1765                   artist.strBiography.c_str(),
1766                   artist.strDied.c_str(),
1767                   artist.strDisbanded.c_str(),
1768                   artist.strYearsActive.c_str(),
1769                   artist.thumbURL.m_xml.c_str());
1770     m_pDS->exec(strSQL.c_str());
1771     long idArtistInfo = (long)sqlite3_last_insert_rowid(m_pDB->getHandle());
1772     for (unsigned int i=0;i<artist.discography.size();++i)
1773     {
1774       strSQL=FormatSQL("insert into discography (idArtist,strAlbum,strYear) values (%i,'%s','%s')",idArtist,artist.discography[i].first.c_str(),artist.discography[i].second.c_str());
1775       m_pDS->exec(strSQL.c_str());
1776     }
1777
1778     return idArtistInfo;
1779   }
1780   catch (...)
1781   {
1782     CLog::Log(LOGERROR, "%s -  failed with query (%s)", __FUNCTION__, strSQL.c_str());
1783   }
1784
1785
1786   return -1;
1787 }
1788
1789 bool CMusicDatabase::SetAlbumInfoSongs(long idAlbumInfo, const VECSONGS& songs)
1790 {
1791   CStdString strSQL;
1792   try
1793   {
1794     if (NULL == m_pDB.get()) return false;
1795     if (NULL == m_pDS.get()) return false;
1796
1797     strSQL=FormatSQL("delete from albuminfosong where idAlbumInfo=%i", idAlbumInfo);
1798     m_pDS->exec(strSQL.c_str());
1799
1800     for (int i = 0; i < (int)songs.size(); i++)
1801     {
1802       CSong song = songs[i];
1803       strSQL=FormatSQL("insert into albuminfosong (idAlbumInfoSong,idAlbumInfo,iTrack,strTitle,iDuration) values(NULL,%i,%i,'%s',%i)",
1804                     idAlbumInfo,
1805                     song.iTrack,
1806                     song.strTitle.c_str(),
1807                     song.iDuration);
1808       m_pDS->exec(strSQL.c_str());
1809     }
1810     return true;
1811   }
1812   catch (...)
1813   {
1814     CLog::Log(LOGERROR, "%s failed with query (%s)", __FUNCTION__, strSQL.c_str());
1815   }
1816
1817   return false;
1818 }
1819
1820 bool CMusicDatabase::CleanupSongsByIds(const CStdString &strSongIds)
1821 {
1822   try
1823   {
1824     if (NULL == m_pDB.get()) return false;
1825     if (NULL == m_pDS.get()) return false;
1826     // ok, now find all idSong's
1827     CStdString strSQL=FormatSQL("select * from song join path on song.idPath = path.idPath where song.idSong in %s", strSongIds.c_str());
1828     if (!m_pDS->query(strSQL.c_str())) return false;
1829     int iRowsFound = m_pDS->num_rows();
1830     if (iRowsFound == 0)
1831     {
1832       m_pDS->close();
1833       return true;
1834     }
1835     CStdString strSongsToDelete = "(";
1836     while (!m_pDS->eof())
1837     { // get the full song path
1838       CStdString strFileName;
1839       CUtil::AddFileToFolder(m_pDS->fv("path.strPath").get_asString(), m_pDS->fv("song.strFileName").get_asString(), strFileName);
1840
1841       //  Special case for streams inside an ogg file. (oggstream)
1842       //  The last dir in the path is the ogg file that
1843       //  contains the stream, so test if its there
1844       CStdString strExtension=CUtil::GetExtension(strFileName);
1845       if (strExtension==".oggstream" || strExtension==".nsfstream")
1846       {
1847         CStdString strFileAndPath=strFileName;
1848         CUtil::GetDirectory(strFileAndPath, strFileName);
1849         // we are dropping back to a file, so remove the slash at end
1850         CUtil::RemoveSlashAtEnd(strFileName);
1851       }
1852
1853       if (!CFile::Exists(strFileName))
1854       { // file no longer exists, so add to deletion list
1855         strSongsToDelete += m_pDS->fv("song.idSong").get_asString() + ",";
1856       }
1857       m_pDS->next();
1858     }
1859     m_pDS->close();
1860     strSongsToDelete.TrimRight(",");
1861     strSongsToDelete += ")";
1862     // ok, now delete these songs + all references to them from the exartistsong and exgenresong tables
1863     strSQL = "delete from song where idSong in " + strSongsToDelete;
1864     m_pDS->exec(strSQL.c_str());
1865     strSQL = "delete from exartistsong where idSong in " + strSongsToDelete;
1866     m_pDS->exec(strSQL.c_str());
1867     strSQL = "delete from exgenresong where idSong in " + strSongsToDelete;
1868     m_pDS->exec(strSQL.c_str());
1869     strSQL = "delete from karaokedata where idSong in " + strSongsToDelete;
1870     m_pDS->exec(strSQL.c_str());
1871     m_pDS->close();
1872     return true;
1873   }
1874   catch (...)
1875   {
1876     CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupSongsFromPaths()");
1877   }
1878   return false;
1879 }
1880
1881
1882 bool CMusicDatabase::CleanupSongs()
1883 {
1884   try
1885   {
1886     // run through all songs and get all unique path ids
1887     int iLIMIT = 1000;
1888     for (int i=0;;i+=iLIMIT)
1889     {
1890       CStdString strSQL=FormatSQL("select song.idsong from song order by song.idsong limit %i offset %i",iLIMIT,i);
1891       if (!m_pDS->query(strSQL.c_str())) return false;
1892       int iRowsFound = m_pDS->num_rows();
1893       // keep going until no rows are left!
1894       if (iRowsFound == 0)
1895       {
1896         m_pDS->close();
1897         return true;
1898       }
1899       CStdString strSongIds = "(";
1900       while (!m_pDS->eof())
1901       {
1902         strSongIds += m_pDS->fv("song.idSong").get_asString() + ",";
1903         m_pDS->next();
1904       }
1905       m_pDS->close();
1906       strSongIds.TrimRight(",");
1907       strSongIds += ")";
1908       CLog::Log(LOGDEBUG,"Checking songs from song ID list: %s",strSongIds.c_str());
1909       if (!CleanupSongsByIds(strSongIds)) return false;
1910     }
1911     return true;
1912   }
1913   catch(...)
1914   {
1915     CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupSongs()");
1916   }
1917   return false;
1918 }
1919
1920 bool CMusicDatabase::CleanupAlbums()
1921 {
1922   try
1923   {
1924     // This must be run AFTER songs have been cleaned up
1925     // delete albums with no reference to songs
1926     CStdString strSQL = "select * from album where album.idAlbum not in (select idAlbum from song)";
1927     if (!m_pDS->query(strSQL.c_str())) return false;
1928     int iRowsFound = m_pDS->num_rows();
1929     if (iRowsFound == 0)
1930     {
1931       m_pDS->close();
1932       return true;
1933     }
1934     CStdString strAlbumIds = "(";
1935     while (!m_pDS->eof())
1936     {
1937       strAlbumIds += m_pDS->fv("album.idAlbum").get_asString() + ",";
1938       m_pDS->next();
1939     }
1940     m_pDS->close();
1941
1942     strAlbumIds.TrimRight(",");
1943     strAlbumIds += ")";
1944     // ok, now we can delete them and the references in the exartistalbum, exgenrealbum and albuminfo tables
1945     strSQL = "delete from album where idAlbum in " + strAlbumIds;
1946     m_pDS->exec(strSQL.c_str());
1947     strSQL = "delete from albuminfo where idAlbum in " + strAlbumIds;
1948     m_pDS->exec(strSQL.c_str());
1949     strSQL = "delete from exartistalbum where idAlbum in " + strAlbumIds;
1950     m_pDS->exec(strSQL.c_str());
1951     strSQL = "delete from exgenrealbum where idAlbum in " + strAlbumIds;
1952     m_pDS->exec(strSQL.c_str());
1953     return true;
1954   }
1955   catch (...)
1956   {
1957     CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupAlbums()");
1958   }
1959   return false;
1960 }
1961
1962 bool CMusicDatabase::CleanupPaths()
1963 {
1964   try
1965   {
1966     // needs to be done AFTER the songs and albums have been cleaned up.
1967     // we can happily delete any path that has no reference to a song
1968     // but we must keep all paths that have been scanned that may contain songs in subpaths
1969     CStdString sql = "select strPath from path where idPath in (select idPath from song)";
1970     if (!m_pDS->query(sql.c_str())) return false;
1971     int iRowsFound = m_pDS->num_rows();
1972     if (iRowsFound == 0)
1973     {
1974       m_pDS->close();
1975       m_pDS->exec("delete from path");
1976       return true;
1977     }
1978     sql = "delete from path where ";
1979     while (!m_pDS->eof())
1980     {
1981       sql += FormatSQL("strPath not like substr('%s',0,length(strPath)) and ", m_pDS->fv("strPath").get_asString().c_str());
1982       m_pDS->next();
1983     }
1984     m_pDS->close();
1985     sql = sql.Left(sql.GetLength() - 4);
1986     m_pDS->exec(sql.c_str());
1987     return true;
1988   }
1989   catch (...)
1990   {
1991     CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupPaths() or was aborted");
1992   }
1993   return false;
1994 }
1995
1996 bool CMusicDatabase::CleanupThumbs()
1997 {
1998   try
1999   {
2000     // needs to be done AFTER the songs have been cleaned up.
2001     // we can happily delete any thumb that has no reference to a song
2002     CStdString strSQL = "select * from thumb where idThumb not in (select idThumb from song) and idThumb not in (select idThumb from album)";
2003     if (!m_pDS->query(strSQL.c_str())) return false;
2004     int iRowsFound = m_pDS->num_rows();
2005     if (iRowsFound == 0)
2006     {
2007       m_pDS->close();
2008       return true;
2009     }
2010     // get albums dir
2011     CStdString strThumbsDir = g_settings.GetMusicThumbFolder();
2012     while (!m_pDS->eof())
2013     {
2014       CStdString strThumb = m_pDS->fv("strThumb").get_asString();
2015       if (strThumb.Left(strThumbsDir.size()) == strThumbsDir)
2016       { // only delete cached thumbs
2017         ::DeleteFile(strThumb.c_str());
2018       }
2019       m_pDS->next();
2020     }
2021     // clear the thumb cache
2022     //CUtil::ThumbCacheClear();
2023     //g_directoryCache.ClearMusicThumbCache();
2024     // now we can delete
2025     m_pDS->close();
2026     strSQL = "delete from thumb where idThumb not in (select idThumb from song) and idThumb not in (select idThumb from album)";
2027     m_pDS->exec(strSQL.c_str());
2028     return true;
2029   }
2030   catch (...)
2031   {
2032     CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupThumbs() or was aborted");
2033   }
2034   return false;
2035 }
2036
2037 bool CMusicDatabase::CleanupArtists()
2038 {
2039   try
2040   {
2041     // (nested queries by Bobbin007)
2042     // must be executed AFTER the song, exartistsong, album and exartistalbum tables are cleaned.
2043     // don't delete the "Various Artists" string
2044     CStdString strVariousArtists = g_localizeStrings.Get(340);
2045     long lVariousArtistsId = AddArtist(strVariousArtists);
2046     CStdString strSQL = "delete from artist where idArtist not in (select idArtist from song)";
2047     strSQL += " and idArtist not in (select idArtist from exartistsong)";
2048     strSQL += " and idArtist not in (select idArtist from album)";
2049     strSQL += " and idArtist not in (select idArtist from exartistalbum)";
2050     CStdString strSQL2;
2051     strSQL2.Format(" and idArtist<>%i", lVariousArtistsId);
2052     strSQL += strSQL2;
2053     m_pDS->exec(strSQL.c_str());
2054     return true;
2055   }
2056   catch (...)
2057   {
2058     CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupArtists() or was aborted");
2059   }
2060   return false;
2061 }
2062
2063 bool CMusicDatabase::CleanupGenres()
2064 {
2065   try
2066   {
2067     // Cleanup orphaned genres (ie those that don't belong to a song or an albuminfo entry)
2068     // (nested queries by Bobbin007)
2069     // Must be executed AFTER the song, exgenresong, albuminfo and exgenrealbum tables have been cleaned.
2070     CStdString strSQL = "delete from genre where idGenre not in (select idGenre from song) and";
2071     strSQL += " idGenre not in (select idGenre from exgenresong) and";
2072     strSQL += " idGenre not in (select idGenre from albuminfo) and";
2073     strSQL += " idGenre not in (select idGenre from exgenrealbum)";
2074     m_pDS->exec(strSQL.c_str());
2075     return true;
2076   }
2077   catch (...)
2078   {
2079     CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupGenres() or was aborted");
2080   }
2081   return false;
2082 }
2083
2084 bool CMusicDatabase::CleanupOrphanedItems()
2085 {
2086   // paths aren't cleaned up here - they're cleaned up in RemoveSongsFromPath()
2087   if (NULL == m_pDB.get()) return false;
2088   if (NULL == m_pDS.get()) return false;
2089   if (!CleanupAlbums()) return false;
2090   if (!CleanupArtists()) return false;
2091   if (!CleanupGenres()) return false;
2092   if (!CleanupThumbs()) return false;
2093   return true;
2094 }
2095
2096 int CMusicDatabase::Cleanup(CGUIDialogProgress *pDlgProgress)
2097 {
2098   if (NULL == m_pDB.get()) return ERROR_DATABASE;
2099   if (NULL == m_pDS.get()) return ERROR_DATABASE;
2100   // first cleanup any songs with invalid paths
2101   pDlgProgress->SetHeading(700);
2102   pDlgProgress->SetLine(0, "");
2103   pDlgProgress->SetLine(1, 318);
2104   pDlgProgress->SetLine(2, 330);
2105   pDlgProgress->SetPercentage(0);
2106   pDlgProgress->StartModal();
2107   pDlgProgress->ShowProgressBar(true);
2108
2109   if (!CleanupSongs())
2110   {
2111     RollbackTransaction();
2112     return ERROR_REORG_SONGS;
2113   }
2114   // then the albums that are not linked to a song or to albuminfo, or whose path is removed
2115   pDlgProgress->SetLine(1, 326);
2116   pDlgProgress->SetPercentage(20);
2117   pDlgProgress->Progress();
2118   if (!CleanupAlbums())
2119   {
2120     RollbackTransaction();
2121     return ERROR_REORG_ALBUM;
2122   }
2123   // now the paths
2124   pDlgProgress->SetLine(1, 324);
2125   pDlgProgress->SetPercentage(40);
2126   pDlgProgress->Progress();
2127   if (!CleanupPaths() || !CleanupThumbs())
2128   {
2129     RollbackTransaction();
2130     return ERROR_REORG_PATH;
2131   }
2132   // and finally artists + genres
2133   pDlgProgress->SetLine(1, 320);
2134   pDlgProgress->SetPercentage(60);
2135   pDlgProgress->Progress();
2136   if (!CleanupArtists())
2137   {
2138     RollbackTransaction();
2139     return ERROR_REORG_ARTIST;
2140   }
2141   pDlgProgress->SetLine(1, 322);
2142   pDlgProgress->SetPercentage(80);
2143   pDlgProgress->Progress();
2144   if (!CleanupGenres())
2145   {
2146     RollbackTransaction();
2147     return ERROR_REORG_GENRE;
2148   }
2149   // commit transaction
2150   pDlgProgress->SetLine(1, 328);
2151   pDlgProgress->SetPercentage(90);
2152   pDlgProgress->Progress();
2153   if (!CommitTransaction())
2154   {
2155     RollbackTransaction();
2156     return ERROR_WRITING_CHANGES;
2157   }
2158   // and compress the database
2159   pDlgProgress->SetLine(1, 331);
2160   pDlgProgress->SetPercentage(100);
2161   pDlgProgress->Progress();
2162   if (!Compress(false))
2163   {
2164     return ERROR_COMPRESSING;
2165   }
2166   return ERROR_OK;
2167 }
2168
2169 void CMusicDatabase::DeleteAlbumInfo()
2170 {
2171   // open our database
2172   Open();
2173   if (NULL == m_pDB.get()) return ;
2174   if (NULL == m_pDS.get()) return ;
2175
2176   // If we are scanning for music info in the background,
2177   // other writing access to the database is prohibited.
2178   CGUIDialogMusicScan* dlgMusicScan = (CGUIDialogMusicScan*)m_gWindowManager.GetWindow(WINDOW_DIALOG_MUSIC_SCAN);
2179   if (dlgMusicScan->IsDialogRunning())
2180   {
2181     CGUIDialogOK::ShowAndGetInput(189, 14057, 0, 0);
2182     return;
2183   }
2184
2185   CStdString strSQL="select * from albuminfo,album,artist where and albuminfo.idAlbum=album.idAlbum and album.idArtist=artist.idArtist order by album.strAlbum";
2186   if (!m_pDS->query(strSQL.c_str())) return ;
2187   int iRowsFound = m_pDS->num_rows();
2188   if (iRowsFound == 0)
2189   {
2190     m_pDS->close();
2191     CGUIDialogOK::ShowAndGetInput(313, 425, 0, 0);
2192   }
2193   vector<CAlbumCache> vecAlbums;
2194   while (!m_pDS->eof())
2195   {
2196     CAlbumCache album;
2197     album.idAlbum = m_pDS->fv("album.idAlbum").get_asLong() ;
2198     album.strAlbum = m_pDS->fv("album.strAlbum").get_asString();
2199     album.strArtist = m_pDS->fv("artist.strArtist").get_asString();
2200     album.strArtist += m_pDS->fv("album.strExtraArtists").get_asString();
2201     vecAlbums.push_back(album);
2202     m_pDS->next();
2203   }
2204   m_pDS->close();
2205
2206   // Show a selectdialog that the user can select the albuminfo to delete
2207   CGUIDialogSelect *pDlg = (CGUIDialogSelect*)m_gWindowManager.GetWindow(WINDOW_DIALOG_SELECT);
2208   if (pDlg)
2209   {
2210     pDlg->SetHeading(g_localizeStrings.Get(181).c_str());
2211     pDlg->Reset();
2212     for (int i = 0; i < (int)vecAlbums.size(); ++i)
2213     {
2214       CMusicDatabase::CAlbumCache& album = vecAlbums[i];
2215       pDlg->Add(album.strAlbum + " - " + album.strArtist);
2216     }
2217     pDlg->DoModal();
2218
2219     // and wait till user selects one
2220     int iSelectedAlbum = pDlg->GetSelectedLabel();
2221     if (iSelectedAlbum < 0)
2222     {
2223       vecAlbums.erase(vecAlbums.begin(), vecAlbums.end());
2224       return ;
2225     }
2226
2227     CAlbumCache& album = vecAlbums[iSelectedAlbum];
2228     strSQL=FormatSQL("delete from albuminfo where albuminfo.idAlbum=%i", album.idAlbum);
2229     if (!m_pDS->exec(strSQL.c_str())) return ;
2230
2231     vecAlbums.erase(vecAlbums.begin(), vecAlbums.end());
2232   }
2233 }
2234
2235 bool CMusicDatabase::LookupCDDBInfo(bool bRequery/*=false*/)
2236 {
2237   if (!g_guiSettings.GetBool("musicfiles.usecddb"))
2238     return false;
2239
2240   // check network connectivity
2241   if (!g_guiSettings.GetBool("network.enableinternet") || !g_application.getNetwork().IsAvailable())
2242     return false;
2243
2244   // Get information for the inserted disc
2245   CCdInfo* pCdInfo = CDetectDVDMedia::GetCdInfo();
2246   if (pCdInfo == NULL)
2247     return false;
2248
2249   // If the disc has no tracks, we are finished here.
2250   int nTracks = pCdInfo->GetTrackCount();
2251   if (nTracks <= 0)
2252     return false;
2253
2254   //  Delete old info if any
2255   if (bRequery)
2256   {
2257     CStdString strFile;
2258     strFile.Format("%s\\%x.cddb", g_settings.GetCDDBFolder().c_str(), pCdInfo->GetCddbDiscId());
2259     ::DeleteFile(strFile.c_str());
2260   }
2261
2262   // Prepare cddb
2263   Xcddb cddb;
2264   cddb.setCacheDir(g_settings.GetCDDBFolder());
2265
2266   // Do we have to look for cddb information
2267   if (pCdInfo->HasCDDBInfo() && !cddb.isCDCached(pCdInfo))
2268   {
2269     CGUIDialogProgress* pDialogProgress = (CGUIDialogProgress*)m_gWindowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
2270     CGUIDialogSelect *pDlgSelect = (CGUIDialogSelect*)m_gWindowManager.GetWindow(WINDOW_DIALOG_SELECT);
2271
2272     if (!pDialogProgress) return false;
2273     if (!pDlgSelect) return false;
2274
2275     // Show progress dialog if we have to connect to freedb.org
2276     pDialogProgress->SetHeading(255); //CDDB
2277     pDialogProgress->SetLine(0, ""); // Querying freedb for CDDB info
2278     pDialogProgress->SetLine(1, 256);
2279     pDialogProgress->SetLine(2, "");
2280     pDialogProgress->ShowProgressBar(false);
2281     pDialogProgress->StartModal();
2282
2283     // get cddb information
2284     if (!cddb.queryCDinfo(pCdInfo))
2285     {
2286       pDialogProgress->Close();
2287       int lasterror = cddb.getLastError();
2288
2289       // Have we found more then on match in cddb for this disc,...
2290       if (lasterror == E_WAIT_FOR_INPUT)
2291       {
2292         // ...yes, show the matches found in a select dialog
2293         // and let the user choose an entry.
2294         pDlgSelect->Reset();
2295         pDlgSelect->SetHeading(255);
2296         int i = 1;
2297         while (1)
2298         {
2299           CStdString strTitle = cddb.getInexactTitle(i);
2300           if (strTitle == "") break;
2301
2302           CStdString strArtist = cddb.getInexactArtist(i);
2303           if (!strArtist.IsEmpty())
2304             strTitle += " - " + strArtist;
2305
2306           pDlgSelect->Add(strTitle);
2307           i++;
2308         }
2309         pDlgSelect->DoModal();
2310
2311         // Has the user selected a match...
2312         int iSelectedCD = pDlgSelect->GetSelectedLabel();
2313         if (iSelectedCD >= 0)
2314         {
2315           // ...query cddb for the inexact match
2316           if (!cddb.queryCDinfo(pCdInfo, 1 + iSelectedCD))
2317             pCdInfo->SetNoCDDBInfo();
2318         }
2319         else
2320           pCdInfo->SetNoCDDBInfo();
2321
2322         pDialogProgress->Close();
2323       }
2324       else if (lasterror == E_NO_MATCH_FOUND)
2325       {
2326         pCdInfo->SetNoCDDBInfo();
2327         pDialogProgress->Close();
2328       }
2329       else
2330       {
2331         pCdInfo->SetNoCDDBInfo();
2332         pDialogProgress->Close();
2333         // ..no, an error occured, display it to the user
2334         CGUIDialogOK *pDialogOK = (CGUIDialogOK *)m_gWindowManager.GetWindow(WINDOW_DIALOG_OK);
2335         if (pDialogOK)
2336         {
2337           CStdString strErrorText;
2338           strErrorText.Format("[%d] %s", cddb.getLastError(), cddb.getLastErrorText());
2339
2340           pDialogOK->SetHeading(255);
2341           pDialogOK->SetLine(0, 257); //ERROR
2342           pDialogOK->SetLine(1, strErrorText.c_str() );
2343           pDialogOK->SetLine(2, "");
2344           pDialogOK->DoModal();
2345         }
2346       }
2347     } // if ( !cddb.queryCDinfo( pCdInfo ) )
2348     pDialogProgress->Close();
2349   } // if (pCdInfo->HasCDDBInfo() && g_stSettings.m_bUseCDDB)
2350
2351   // Filling the file items with cddb info happens in CMusicInfoTagLoaderCDDA
2352
2353   return pCdInfo->HasCDDBInfo();
2354 }
2355
2356 void CMusicDatabase::DeleteCDDBInfo()
2357 {
2358   WIN32_FIND_DATA wfd;
2359   memset(&wfd, 0, sizeof(wfd));
2360
2361   CStdString strCDDBFileMask;
2362   strCDDBFileMask.Format("%s\\*.cddb", g_settings.GetCDDBFolder().c_str());
2363
2364   map<ULONG, CStdString> mapCDDBIds;
2365
2366   CAutoPtrFind hFind( FindFirstFile(strCDDBFileMask.c_str(), &wfd));
2367   if (!hFind.isValid())
2368   {
2369     CGUIDialogOK::ShowAndGetInput(313, 426, 0, 0);
2370     return ;
2371   }
2372
2373   // Show a selectdialog that the user can select the albuminfo to delete
2374   CGUIDialogSelect *pDlg = (CGUIDialogSelect*)m_gWindowManager.GetWindow(WINDOW_DIALOG_SELECT);
2375   if (pDlg)
2376   {
2377     pDlg->SetHeading(g_localizeStrings.Get(181).c_str());
2378     pDlg->Reset();
2379     CStdString strDir = g_settings.GetCDDBFolder();
2380     do
2381     {
2382       if ( !(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
2383       {
2384         CStdString strFile = wfd.cFileName;
2385         strFile.Delete(strFile.size() - 5, 5);
2386         ULONG lDiscId = strtoul(strFile.c_str(), NULL, 16);
2387         Xcddb cddb;
2388         cddb.setCacheDir(strDir);
2389
2390         if (!cddb.queryCache(lDiscId))
2391           continue;
2392
2393         CStdString strDiskTitle, strDiskArtist;
2394         cddb.getDiskTitle(strDiskTitle);
2395         cddb.getDiskArtist(strDiskArtist);
2396
2397         CStdString str;
2398         if (strDiskArtist.IsEmpty())
2399           str = strDiskTitle;
2400         else
2401           str = strDiskTitle + " - " + strDiskArtist;
2402
2403         pDlg->Add(str);
2404         mapCDDBIds.insert(pair<ULONG, CStdString>(lDiscId, str));
2405       }
2406     }
2407     while (FindNextFile(hFind, &wfd));
2408
2409     pDlg->Sort();
2410     pDlg->DoModal();
2411
2412     // and wait till user selects one
2413     int iSelectedAlbum = pDlg->GetSelectedLabel();
2414     if (iSelectedAlbum < 0)
2415     {
2416       mapCDDBIds.erase(mapCDDBIds.begin(), mapCDDBIds.end());
2417       return ;
2418     }
2419
2420     CStdString strSelectedAlbum = pDlg->GetSelectedLabelText();
2421     map<ULONG, CStdString>::iterator it;
2422     for (it = mapCDDBIds.begin();it != mapCDDBIds.end();it++)
2423     {
2424       if (it->second == strSelectedAlbum)
2425       {
2426         CStdString strFile;
2427         strFile.Format("%s\\%x.cddb", g_settings.GetCDDBFolder().c_str(), it->first );
2428         ::DeleteFile(strFile.c_str());
2429         break;
2430       }
2431     }
2432     mapCDDBIds.erase(mapCDDBIds.begin(), mapCDDBIds.end());
2433   }
2434 }
2435
2436 void CMusicDatabase::Clean()
2437 {
2438   // If we are scanning for music info in the background,
2439   // other writing access to the database is prohibited.
2440   CGUIDialogMusicScan* dlgMusicScan = (CGUIDialogMusicScan*)m_gWindowManager.GetWindow(WINDOW_DIALOG_MUSIC_SCAN);
2441   if (dlgMusicScan->IsDialogRunning())
2442   {
2443     CGUIDialogOK::ShowAndGetInput(189, 14057, 0, 0);
2444     return;
2445   }
2446
2447   if (CGUIDialogYesNo::ShowAndGetInput(313, 333, 0, 0))
2448   {
2449     CGUIDialogProgress* dlgProgress = (CGUIDialogProgress*)m_gWindowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
2450     if (dlgProgress)
2451     {
2452       CMusicDatabase musicdatabase;
2453       if (musicdatabase.Open())
2454       {
2455         int iReturnString = musicdatabase.Cleanup(dlgProgress);
2456         musicdatabase.Close();
2457
2458         if (iReturnString != ERROR_OK)
2459         {
2460           CGUIDialogOK::ShowAndGetInput(313, iReturnString, 0, 0);
2461         }
2462       }
2463       dlgProgress->Close();
2464     }
2465   }
2466 }
2467
2468 bool CMusicDatabase::GetGenresNav(const CStdString& strBaseDir, CFileItemList& items)
2469 {
2470   try
2471   {
2472     if (NULL == m_pDB.get()) return false;
2473     if (NULL == m_pDS.get()) return false;
2474
2475     // get primary genres for songs
2476     CStdString strSQL="select * from genre "
2477                       "where (idGenre IN ("
2478                         "select song.idGenre from song) "
2479                       "or idGenre IN ("
2480                         "select exgenresong.idGenre from exgenresong)) ";
2481
2482     // block null strings
2483     strSQL += " and genre.strGenre != \"\"";
2484
2485     // run query
2486     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
2487     if (!m_pDS->query(strSQL.c_str())) return false;
2488     int iRowsFound = m_pDS->num_rows();
2489     if (iRowsFound == 0)
2490     {
2491       m_pDS->close();
2492       return false;
2493     }
2494
2495     // get data from returned rows
2496     while (!m_pDS->eof())
2497     {
2498       CFileItemPtr pItem(new CFileItem(m_pDS->fv("strGenre").get_asString()));
2499       pItem->GetMusicInfoTag()->SetGenre(m_pDS->fv("strGenre").get_asString());
2500       CStdString strDir;
2501       strDir.Format("%ld/", m_pDS->fv("idGenre").get_asLong());
2502       pItem->m_strPath=strBaseDir + strDir;
2503       pItem->m_bIsFolder=true;
2504       items.Add(pItem);
2505
2506       m_pDS->next();
2507     }
2508
2509     // cleanup
2510     m_pDS->close();
2511
2512     return true;
2513   }
2514   catch (...)
2515   {
2516     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2517   }
2518   return false;
2519 }
2520
2521 bool CMusicDatabase::GetYearsNav(const CStdString& strBaseDir, CFileItemList& items)
2522 {
2523   try
2524   {
2525     if (NULL == m_pDB.get()) return false;
2526     if (NULL == m_pDS.get()) return false;
2527
2528     // get years from album list
2529     CStdString strSQL="select distinct iYear from album where iYear <> 0";
2530
2531     // run query
2532     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
2533     if (!m_pDS->query(strSQL.c_str())) return false;
2534     int iRowsFound = m_pDS->num_rows();
2535     if (iRowsFound == 0)
2536     {
2537       m_pDS->close();
2538       return false;
2539     }
2540
2541     // get data from returned rows
2542     while (!m_pDS->eof())
2543     {
2544       CFileItemPtr pItem(new CFileItem(m_pDS->fv("iYear").get_asString()));
2545       SYSTEMTIME stTime;
2546       stTime.wYear = (WORD)m_pDS->fv("iYear").get_asLong();
2547       pItem->GetMusicInfoTag()->SetReleaseDate(stTime);
2548       CStdString strDir;
2549       strDir.Format("%ld/", m_pDS->fv("iYear").get_asLong());
2550       pItem->m_strPath=strBaseDir + strDir;
2551       pItem->m_bIsFolder=true;
2552       items.Add(pItem);
2553
2554       m_pDS->next();
2555     }
2556
2557     // cleanup
2558     m_pDS->close();
2559
2560     return true;
2561   }
2562   catch (...)
2563   {
2564     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2565   }
2566   return false;
2567 }
2568
2569 bool CMusicDatabase::GetAlbumsByYear(const CStdString& strBaseDir, CFileItemList& items, long year)
2570 {
2571   CStdString where = FormatSQL("where iYear=%ld", year);
2572
2573   return GetAlbumsByWhere(strBaseDir, where, "", items);
2574 }
2575
2576 bool CMusicDatabase::GetArtistsNav(const CStdString& strBaseDir, CFileItemList& items, long idGenre, bool albumArtistsOnly)
2577 {
2578   if (NULL == m_pDB.get()) return false;
2579   if (NULL == m_pDS.get()) return false;
2580   try
2581   {
2582     if (NULL == m_pDB.get()) return false;
2583     if (NULL == m_pDS.get()) return false;
2584
2585     DWORD time = timeGetTime();
2586
2587     CStdString strSQL = "select * from artist where (idArtist IN ";
2588
2589     if (idGenre==-1)
2590     {
2591       if (!albumArtistsOnly)  // show all artists in this case (ie those linked to a song)
2592         strSQL +=         "("
2593                           "select song.idArtist from song" // All primary artists linked to a song
2594                           ") "
2595                         "or idArtist IN "
2596                           "("
2597                           "select exartistsong.idArtist from exartistsong" // All extra artists linked to a song
2598                           ") "
2599                         "or idArtist IN ";
2600
2601       // and always show any artists linked to an album (may be different from above due to album artist tag)
2602       strSQL +=          "("
2603                           "select album.idArtist from album" // All primary artists linked to an album
2604                           ") "
2605                         "or idArtist IN "
2606                           "("
2607                           "select exartistalbum.idArtist from exartistalbum "; // All extra artists linked to an album
2608       if (albumArtistsOnly)
2609         strSQL +=         "join album on album.idAlbum = exartistalbum.idAlbum " // if we're hiding compilation artists,
2610                           "where album.strExtraArtists != ''";                      // then exclude those where that have no extra artists
2611       strSQL +=           ")"
2612                         ") ";
2613     }
2614     else
2615     { // same statements as above, but limit to the specified genre
2616       // in this case we show the whole lot always - there is no limitation to just album artists
2617       strSQL+=FormatSQL("("
2618                         "select song.idArtist from song " // All primary artists linked to primary genres
2619                         "where song.idGenre=%ld"
2620                         ") "
2621                       "or idArtist IN "
2622                         "("
2623                         "select song.idArtist from song " // All primary artists linked to extra genres
2624                           "join exgenresong on song.idSong=exgenresong.idSong "
2625                         "where exgenresong.idGenre=%ld"
2626                         ")"
2627                       "or idArtist IN "
2628                         "("
2629                         "select exartistsong.idArtist from exartistsong " // All extra artists linked to extra genres
2630                           "join song on exartistsong.idSong=song.idSong "
2631                           "join exgenresong on song.idSong=exgenresong.idSong "
2632                         "where exgenresong.idGenre=%ld"
2633                         ") "
2634                       "or idArtist IN "
2635                         "("
2636                         "select exartistsong.idArtist from exartistsong " // All extra artists linked to primary genres
2637                           "join song on exartistsong.idSong=song.idSong "
2638                         "where song.idGenre=%ld"
2639                         ") "
2640                       "or idArtist IN "
2641                       , idGenre, idGenre, idGenre, idGenre);
2642       // and add any artists linked to an album (may be different from above due to album artist tag)
2643       strSQL += FormatSQL("("
2644                           "select album.idArtist from album " // All primary album artists linked to primary genres
2645                           "where album.idGenre=%ld"
2646                           ") "
2647                         "or idArtist IN "
2648                           "("
2649                           "select album.idArtist from album " // All primary album artists linked to extra genres
2650                             "join exgenrealbum on album.idAlbum=exgenrealbum.idAlbum "
2651                           "where exgenrealbum.idGenre=%ld"
2652                           ")"
2653                         "or idArtist IN "
2654                           "("
2655                           "select exartistalbum.idArtist from exartistalbum " // All extra album artists linked to extra genres
2656                             "join album on exartistalbum.idAlbum=album.idAlbum "
2657                             "join exgenrealbum on album.idAlbum=exgenrealbum.idAlbum "
2658                           "where exgenrealbum.idGenre=%ld"
2659                           ") "
2660                         "or idArtist IN "
2661                           "("
2662                           "select exartistalbum.idArtist from exartistalbum " // All extra album artists linked to primary genres
2663                             "join album on exartistalbum.idAlbum=album.idAlbum "
2664                           "where album.idGenre=%ld"
2665                           ") "
2666                         ")", idGenre, idGenre, idGenre, idGenre);
2667     }
2668
2669     // remove the null string
2670     strSQL += " and artist.strArtist != \"\"";
2671     // and the various artist entry if applicable
2672     if (!albumArtistsOnly || idGenre > -1)
2673     {
2674       CStdString strVariousArtists = g_localizeStrings.Get(340);
2675       long lVariousArtistsId = AddArtist(strVariousArtists);
2676       strSQL+=FormatSQL(" and artist.idArtist<>%i", lVariousArtistsId);
2677     }
2678
2679     // run query
2680     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
2681     if (!m_pDS->query(strSQL.c_str())) return false;
2682     int iRowsFound = m_pDS->num_rows();
2683     if (iRowsFound == 0)
2684     {
2685       m_pDS->close();
2686       return false;
2687     }
2688
2689     items.Reserve(iRowsFound);
2690
2691     // get data from returned rows
2692     while (!m_pDS->eof())
2693     {
2694       CStdString strArtist = m_pDS->fv("strArtist").get_asString();
2695       CFileItemPtr pItem(new CFileItem(strArtist));
2696       pItem->GetMusicInfoTag()->SetArtist(strArtist);
2697       CStdString strDir;
2698       long idArtist = m_pDS->fv("idArtist").get_asLong();
2699       strDir.Format("%ld/", idArtist);
2700       pItem->m_strPath=strBaseDir + strDir;
2701       pItem->m_bIsFolder=true;
2702       if (CFile::Exists(pItem->GetCachedArtistThumb()))
2703         pItem->SetThumbnailImage(pItem->GetCachedArtistThumb());
2704       else
2705         pItem->SetThumbnailImage("DefaultArtistBig.png");
2706
2707       CArtist artist;
2708       GetArtistInfo(idArtist,artist,false);
2709       pItem->SetProperty("instrument",artist.strInstruments);
2710       pItem->SetProperty("style",artist.strStyles);
2711       pItem->SetProperty("mood",artist.strMoods);
2712       pItem->SetProperty("born",artist.strBorn);
2713       pItem->SetProperty("formed",artist.strFormed);
2714       pItem->SetProperty("description",artist.strBiography);
2715       pItem->SetProperty("genre",artist.strGenre);
2716       pItem->SetProperty("died",artist.strDied);
2717       pItem->SetProperty("disbanded",artist.strDisbanded);
2718       pItem->SetProperty("yearsactive",artist.strYearsActive);
2719       items.Add(pItem);
2720
2721       m_pDS->next();
2722     }
2723     CLog::Log(LOGDEBUG,"Time to retrieve artists from dataset = %u", timeGetTime() - time);
2724
2725     // cleanup
2726     m_pDS->close();
2727
2728     return true;
2729   }
2730   catch (...)
2731   {
2732     m_pDS->close();
2733     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2734   }
2735   return false;
2736 }
2737
2738 bool CMusicDatabase::GetAlbumFromSong(long idSong, CAlbum &album)
2739 {
2740   try
2741   {
2742     if (NULL == m_pDB.get()) return false;
2743     if (NULL == m_pDS.get()) return false;
2744
2745     CStdString strSQL = FormatSQL("select albumview.* from song join albumview on song.idAlbum = albumview.idAlbum where song.idSong='%ld'", idSong);
2746     if (!m_pDS->query(strSQL.c_str())) return false;
2747     int iRowsFound = m_pDS->num_rows();
2748     if (iRowsFound != 1)
2749     {
2750       m_pDS->close();
2751       return false;
2752     }
2753
2754     album = GetAlbumFromDataset(m_pDS.get());
2755
2756     m_pDS->close();
2757     return true;
2758
2759   }
2760   catch (...)
2761   {
2762     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2763   }
2764   return false;
2765 }
2766
2767 // This function won't be required if/when the fileitem tag has idSong information
2768 bool CMusicDatabase::GetAlbumFromSong(const CSong &song, CAlbum &album)
2769 {
2770   try
2771   {
2772     if (NULL == m_pDB.get()) return false;
2773     if (NULL == m_pDS.get()) return false;
2774
2775     if (song.idSong != -1) return GetAlbumFromSong(song.idSong, album);
2776
2777     CStdString path, file;
2778     CUtil::Split(song.strFileName, path, file);
2779
2780     CStdString strSQL = FormatSQL("select albumview.* from song join albumview on song.idAlbum = albumview.idAlbum join path on song.idPath = path.idPath where song.strFileName like '%s' and path.strPath like '%s'", file.c_str(), path.c_str());
2781     if (!m_pDS->query(strSQL.c_str())) return false;
2782     int iRowsFound = m_pDS->num_rows();
2783     if (iRowsFound != 1)
2784     {
2785       m_pDS->close();
2786       return false;
2787     }
2788
2789     album = GetAlbumFromDataset(m_pDS.get());
2790
2791     m_pDS->close();
2792     return true;
2793   }
2794   catch (...)
2795   {
2796     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2797   }
2798   return false;
2799 }
2800
2801 bool CMusicDatabase::GetAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, long idGenre, long idArtist)
2802 {
2803   // where clause
2804   CStdString strWhere;
2805   if (idGenre!=-1)
2806   {
2807     strWhere+=FormatSQL("where (idAlbum IN "
2808                           "("
2809                           "select song.idAlbum from song " // All albums where the primary genre fits
2810                           "where song.idGenre=%ld"
2811                           ") "
2812                         "or idAlbum IN "
2813                           "("
2814                           "select song.idAlbum from song " // All albums where extra genres fits
2815                             "join exgenresong on song.idSong=exgenresong.idSong "
2816                           "where exgenresong.idGenre=%ld"
2817                           ")"
2818                         ") "
2819                         , idGenre, idGenre);
2820   }
2821
2822   if (idArtist!=-1)
2823   {
2824     if (strWhere.IsEmpty())
2825       strWhere += "where ";
2826     else
2827       strWhere += "and ";
2828
2829     strWhere +=FormatSQL("(idAlbum IN "
2830                             "("
2831                               "select song.idAlbum from song "  // All albums where the primary artist fits
2832                               "where song.idArtist=%ld"
2833                             ")"
2834                           " or idAlbum IN "
2835                             "("
2836                               "select song.idAlbum from song "  // All albums where extra artists fit
2837                                 "join exartistsong on song.idSong=exartistsong.idSong "
2838                               "where exartistsong.idArtist=%ld"
2839                             ")"
2840                           " or idAlbum IN "
2841                             "("
2842                               "select album.idAlbum from album " // All albums where primary album artist fits
2843                               "where album.idArtist=%ld"
2844                             ")"
2845                           " or idAlbum IN "
2846                             "("
2847                               "select exartistalbum.idAlbum from exartistalbum " // All albums where extra album artists fit
2848                               "where exartistalbum.idArtist=%ld"
2849                             ")"
2850                           ") "
2851                           , idArtist, idArtist, idArtist, idArtist);
2852   }
2853
2854   return GetAlbumsByWhere(strBaseDir, strWhere, "", items);
2855 }
2856
2857 bool CMusicDatabase::GetAlbumsByWhere(const CStdString &baseDir, const CStdString &where, const CStdString &order, CFileItemList &items)
2858 {
2859   if (NULL == m_pDB.get()) return false;
2860   if (NULL == m_pDS.get()) return false;
2861
2862   try
2863   {
2864     CStdString sql = "select * from albumview ";
2865
2866     // block null album names
2867     if (where.IsEmpty())
2868       sql += "where ";
2869     else
2870       sql += where + " and ";
2871     sql += "albumview.strAlbum != \"\" " + order;
2872
2873     // run query
2874     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, sql.c_str());
2875     if (!m_pDS->query(sql.c_str())) return false;
2876     int iRowsFound = m_pDS->num_rows();
2877     if (iRowsFound == 0)
2878     {
2879       m_pDS->close();
2880       return false;
2881     }
2882
2883     items.Reserve(iRowsFound);
2884
2885     // get data from returned rows
2886     while (!m_pDS->eof())
2887     {
2888       try
2889       {
2890         CStdString strDir;
2891         long idAlbum = m_pDS->fv("idAlbum").get_asLong();
2892         strDir.Format("%s%ld/", baseDir.c_str(), idAlbum);
2893         CFileItemPtr pItem(new CFileItem(strDir, GetAlbumFromDataset(m_pDS.get())));