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