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