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