DVDCodecs: Amlogic: Handle conditions in which amcodec should be opened during Open()
[vuplus_xbmc] / xbmc / music / MusicDatabase.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "network/Network.h"
22 #include "threads/SystemClock.h"
23 #include "system.h"
24 #include "MusicDatabase.h"
25 #include "network/cddb.h"
26 #include "filesystem/DirectoryCache.h"
27 #include "filesystem/MusicDatabaseDirectory/DirectoryNode.h"
28 #include "filesystem/MusicDatabaseDirectory/QueryParams.h"
29 #include "filesystem/MusicDatabaseDirectory.h"
30 #include "filesystem/SpecialProtocol.h"
31 #include "GUIInfoManager.h"
32 #include "music/tags/MusicInfoTag.h"
33 #include "addons/AddonManager.h"
34 #include "addons/Scraper.h"
35 #include "addons/Addon.h"
36 #include "utils/URIUtils.h"
37 #include "Artist.h"
38 #include "Album.h"
39 #include "Song.h"
40 #include "guilib/GUIWindowManager.h"
41 #include "dialogs/GUIDialogOK.h"
42 #include "dialogs/GUIDialogProgress.h"
43 #include "dialogs/GUIDialogYesNo.h"
44 #include "dialogs/GUIDialogSelect.h"
45 #include "filesystem/File.h"
46 #include "profiles/ProfilesManager.h"
47 #include "settings/AdvancedSettings.h"
48 #include "FileItem.h"
49 #include "Application.h"
50 #ifdef HAS_KARAOKE
51 #include "karaoke/karaokelyricsfactory.h"
52 #endif
53 #include "storage/MediaManager.h"
54 #include "settings/MediaSettings.h"
55 #include "settings/Settings.h"
56 #include "utils/StringUtils.h"
57 #include "guilib/LocalizeStrings.h"
58 #include "utils/LegacyPathTranslation.h"
59 #include "utils/log.h"
60 #include "utils/TimeUtils.h"
61 #include "TextureCache.h"
62 #include "addons/AddonInstaller.h"
63 #include "utils/AutoPtrHandle.h"
64 #include "interfaces/AnnouncementManager.h"
65 #include "dbwrappers/dataset.h"
66 #include "utils/XMLUtils.h"
67 #include "URL.h"
68 #include "playlists/SmartPlayList.h"
69
70 using namespace std;
71 using namespace AUTOPTR;
72 using namespace XFILE;
73 using namespace MUSICDATABASEDIRECTORY;
74 using ADDON::AddonPtr;
75
76 #define RECENTLY_PLAYED_LIMIT 25
77 #define MIN_FULL_SEARCH_LENGTH 3
78
79 #ifdef HAS_DVD_DRIVE
80 using namespace CDDB;
81 #endif
82
83 static void AnnounceRemove(const std::string& content, int id)
84 {
85   CVariant data;
86   data["type"] = content;
87   data["id"] = id;
88   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnRemove", data);
89 }
90
91 static void AnnounceUpdate(const std::string& content, int id)
92 {
93   CVariant data;
94   data["type"] = content;
95   data["id"] = id;
96   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnUpdate", data);
97 }
98
99 CMusicDatabase::CMusicDatabase(void)
100 {
101 }
102
103 CMusicDatabase::~CMusicDatabase(void)
104 {
105   EmptyCache();
106 }
107
108 bool CMusicDatabase::Open()
109 {
110   return CDatabase::Open(g_advancedSettings.m_databaseMusic);
111 }
112
113 void CMusicDatabase::CreateTables()
114 {
115   CLog::Log(LOGINFO, "create artist table");
116   m_pDS->exec("CREATE TABLE artist ( idArtist integer primary key, "
117               " strArtist varchar(256), strMusicBrainzArtistID text, "
118               " strBorn text, strFormed text, strGenres text, strMoods text, "
119               " strStyles text, strInstruments text, strBiography text, "
120               " strDied text, strDisbanded text, strYearsActive text, "
121               " strImage text, strFanart text, "
122               " lastScraped varchar(20) default NULL, "
123               " dateAdded varchar (20) default NULL)");
124   CLog::Log(LOGINFO, "create album table");
125   m_pDS->exec("CREATE TABLE album (idAlbum integer primary key, "
126               " strAlbum varchar(256), strMusicBrainzAlbumID text, "
127               " strArtists text, strGenres text, "
128               " iYear integer, idThumb integer, "
129               " bCompilation integer not null default '0', "
130               " strMoods text, strStyles text, strThemes text, "
131               " strReview text, strImage text, strLabel text, "
132               " strType text, "
133               " iRating integer, "
134               " lastScraped varchar(20) default NULL, "
135               " dateAdded varchar (20) default NULL)");
136   CLog::Log(LOGINFO, "create album_artist table");
137   m_pDS->exec("CREATE TABLE album_artist (idArtist integer, idAlbum integer, strJoinPhrase text, boolFeatured integer, iOrder integer, strArtist text)");
138   CLog::Log(LOGINFO, "create album_genre table");
139   m_pDS->exec("CREATE TABLE album_genre (idGenre integer, idAlbum integer, iOrder integer)");
140
141   CLog::Log(LOGINFO, "create genre table");
142   m_pDS->exec("CREATE TABLE genre (idGenre integer primary key, strGenre varchar(256))");
143   CLog::Log(LOGINFO, "create path table");
144   m_pDS->exec("CREATE TABLE path (idPath integer primary key, strPath varchar(512), strHash text)");
145   CLog::Log(LOGINFO, "create song table");
146   m_pDS->exec("CREATE TABLE song (idSong integer primary key, "
147               " idAlbum integer, idPath integer, "
148               " strArtists text, strGenres text, strTitle varchar(512), "
149               " iTrack integer, iDuration integer, iYear integer, "
150               " dwFileNameCRC text, "
151               " strFileName text, strMusicBrainzTrackID text, "
152               " iTimesPlayed integer, iStartOffset integer, iEndOffset integer, "
153               " idThumb integer, "
154               " lastplayed varchar(20) default NULL, "
155               " rating char default '0', comment text)");
156   CLog::Log(LOGINFO, "create song_artist table");
157   m_pDS->exec("CREATE TABLE song_artist (idArtist integer, idSong integer, strJoinPhrase text, boolFeatured integer, iOrder integer, strArtist text)");
158   CLog::Log(LOGINFO, "create song_genre table");
159   m_pDS->exec("CREATE TABLE song_genre (idGenre integer, idSong integer, iOrder integer)");
160
161   CLog::Log(LOGINFO, "create albuminfosong table");
162   m_pDS->exec("CREATE TABLE albuminfosong (idAlbumInfoSong integer primary key, idAlbumInfo integer, iTrack integer, strTitle text, iDuration integer)");
163
164   CLog::Log(LOGINFO, "create content table");
165   m_pDS->exec("CREATE TABLE content (strPath text, strScraperPath text, strContent text, strSettings text)");
166   CLog::Log(LOGINFO, "create discography table");
167   m_pDS->exec("CREATE TABLE discography (idArtist integer, strAlbum text, strYear text)");
168
169   CLog::Log(LOGINFO, "create karaokedata table");
170   m_pDS->exec("CREATE TABLE karaokedata (iKaraNumber integer, idSong integer, iKaraDelay integer, strKaraEncoding text, "
171               "strKaralyrics text, strKaraLyrFileCRC text)");
172
173   CLog::Log(LOGINFO, "create art table");
174   m_pDS->exec("CREATE TABLE art(art_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, type TEXT, url TEXT)");
175
176   // Add 'Karaoke' genre
177   AddGenre( "Karaoke" );
178 }
179
180 void CMusicDatabase::CreateAnalytics()
181 {
182   CLog::Log(LOGINFO, "%s - creating indices", __FUNCTION__);
183   m_pDS->exec("CREATE INDEX idxAlbum ON album(strAlbum(255))");
184   m_pDS->exec("CREATE INDEX idxAlbum_1 ON album(bCompilation)");
185   m_pDS->exec("CREATE UNIQUE INDEX idxAlbum_2 ON album(strMusicBrainzAlbumID(36))");
186
187   m_pDS->exec("CREATE UNIQUE INDEX idxAlbumArtist_1 ON album_artist ( idAlbum, idArtist )");
188   m_pDS->exec("CREATE UNIQUE INDEX idxAlbumArtist_2 ON album_artist ( idArtist, idAlbum )");
189   m_pDS->exec("CREATE INDEX idxAlbumArtist_3 ON album_artist ( boolFeatured )");
190
191   m_pDS->exec("CREATE UNIQUE INDEX idxAlbumGenre_1 ON album_genre ( idAlbum, idGenre )");
192   m_pDS->exec("CREATE UNIQUE INDEX idxAlbumGenre_2 ON album_genre ( idGenre, idAlbum )");
193
194   m_pDS->exec("CREATE INDEX idxGenre ON genre(strGenre(255))");
195
196   m_pDS->exec("CREATE INDEX idxArtist ON artist(strArtist(255))");
197   m_pDS->exec("CREATE UNIQUE INDEX idxArtist1 ON artist(strMusicBrainzArtistID(36))");
198
199   m_pDS->exec("CREATE INDEX idxPath ON path(strPath(255))");
200
201   m_pDS->exec("CREATE INDEX idxSong ON song(strTitle(255))");
202   m_pDS->exec("CREATE INDEX idxSong1 ON song(iTimesPlayed)");
203   m_pDS->exec("CREATE INDEX idxSong2 ON song(lastplayed)");
204   m_pDS->exec("CREATE INDEX idxSong3 ON song(idAlbum)");
205   m_pDS->exec("CREATE INDEX idxSong6 ON song( idPath, strFileName(255) )");
206   m_pDS->exec("CREATE UNIQUE INDEX idxSong7 ON song( idAlbum, strMusicBrainzTrackID(36) )");
207
208   m_pDS->exec("CREATE UNIQUE INDEX idxSongArtist_1 ON song_artist ( idSong, idArtist )");
209   m_pDS->exec("CREATE UNIQUE INDEX idxSongArtist_2 ON song_artist ( idArtist, idSong )");
210   m_pDS->exec("CREATE INDEX idxSongArtist_3 ON song_artist ( boolFeatured )");
211
212   m_pDS->exec("CREATE UNIQUE INDEX idxSongGenre_1 ON song_genre ( idSong, idGenre )");
213   m_pDS->exec("CREATE UNIQUE INDEX idxSongGenre_2 ON song_genre ( idGenre, idSong )");
214
215   m_pDS->exec("CREATE INDEX idxAlbumInfoSong_1 ON albuminfosong ( idAlbumInfo )");
216
217   m_pDS->exec("CREATE INDEX idxKaraNumber on karaokedata(iKaraNumber)");
218   m_pDS->exec("CREATE INDEX idxKarSong on karaokedata(idSong)");
219
220   m_pDS->exec("CREATE INDEX idxDiscography_1 ON discography ( idArtist )");
221
222   m_pDS->exec("CREATE INDEX ix_art ON art(media_id, media_type(20), type(20))");
223
224   CLog::Log(LOGINFO, "create triggers");
225   m_pDS->exec("CREATE TRIGGER tgrDeleteAlbum AFTER delete ON album FOR EACH ROW BEGIN"
226               "  DELETE FROM song WHERE song.idAlbum = old.idAlbum;"
227               "  DELETE FROM album_artist WHERE album_artist.idAlbum = old.idAlbum;"
228               "  DELETE FROM album_genre WHERE album_genre.idAlbum = old.idAlbum;"
229               "  DELETE FROM albuminfosong WHERE albuminfosong.idAlbumInfo=old.idAlbum;"
230               "  DELETE FROM art WHERE media_id=old.idAlbum AND media_type='album';"
231               " END");
232   m_pDS->exec("CREATE TRIGGER tgrDeleteArtist AFTER delete ON artist FOR EACH ROW BEGIN"
233               "  DELETE FROM album_artist WHERE album_artist.idArtist = old.idArtist;"
234               "  DELETE FROM song_artist WHERE song_artist.idArtist = old.idArtist;"
235               "  DELETE FROM discography WHERE discography.idArtist = old.idArtist;"
236               "  DELETE FROM art WHERE media_id=old.idArtist AND media_type='artist';"
237               " END");
238   m_pDS->exec("CREATE TRIGGER tgrDeleteSong AFTER delete ON song FOR EACH ROW BEGIN"
239               "  DELETE FROM song_artist WHERE song_artist.idSong = old.idSong;"
240               "  DELETE FROM song_genre WHERE song_genre.idSong = old.idSong;"
241               "  DELETE FROM karaokedata WHERE karaokedata.idSong = old.idSong;"
242               "  DELETE FROM art WHERE media_id=old.idSong AND media_type='song';"
243               " END");
244
245   // we create views last to ensure all indexes are rolled in
246   CreateViews();
247 }
248
249 void CMusicDatabase::CreateViews()
250 {
251   CLog::Log(LOGINFO, "create song view");
252   m_pDS->exec("CREATE VIEW songview AS SELECT "
253               "        song.idSong AS idSong, "
254               "        song.strArtists AS strArtists,"
255               "        song.strGenres AS strGenres,"
256               "        strTitle, "
257               "        iTrack, iDuration, "
258               "        song.iYear AS iYear, "
259               "        dwFileNameCRC, "
260               "        strFileName, "
261               "        strMusicBrainzTrackID, "
262               "        iTimesPlayed, iStartOffset, iEndOffset, "
263               "        lastplayed, rating, comment, "
264               "        song.idAlbum AS idAlbum, "
265               "        strAlbum, "
266               "        strPath, "
267               "        iKaraNumber, iKaraDelay, strKaraEncoding,"
268               "        album.bCompilation AS bCompilation,"
269               "        album.strArtists AS strAlbumArtists "
270               "FROM song"
271               "  JOIN album ON"
272               "    song.idAlbum=album.idAlbum"
273               "  JOIN path ON"
274               "    song.idPath=path.idPath"
275               "  LEFT OUTER JOIN karaokedata ON"
276               "    song.idSong=karaokedata.idSong");
277
278   CLog::Log(LOGINFO, "create album view");
279   m_pDS->exec("CREATE VIEW albumview AS SELECT "
280               "        album.idAlbum AS idAlbum, "
281               "        strAlbum, "
282               "        strMusicBrainzAlbumID, "
283               "        album.strArtists AS strArtists, "
284               "        album.strGenres AS strGenres, "
285               "        album.iYear AS iYear, "
286               "        album.strMoods AS strMoods, "
287               "        album.strStyles AS strStyles, "
288               "        strThemes, "
289               "        strReview, "
290               "        strLabel, "
291               "        strType, "
292               "        album.strImage as strImage, "
293               "        iRating, "
294               "        bCompilation, "
295               "        MIN(song.iTimesPlayed) AS iTimesPlayed "
296               "FROM album"
297               " LEFT OUTER JOIN song ON"
298               "   album.idAlbum=song.idAlbum "
299               "GROUP BY album.idAlbum");
300
301   CLog::Log(LOGINFO, "create artist view");
302   m_pDS->exec("CREATE VIEW artistview AS SELECT"
303               "  idArtist, strArtist, "
304               "  strMusicBrainzArtistID, "
305               "  strBorn, strFormed, strGenres,"
306               "  strMoods, strStyles, strInstruments, "
307               "  strBiography, strDied, strDisbanded, "
308               "  strYearsActive, strImage, strFanart "
309               "FROM artist");
310
311   CLog::Log(LOGINFO, "create albumartist view");
312   m_pDS->exec("CREATE VIEW albumartistview AS SELECT"
313               "  album_artist.idAlbum AS idAlbum, "
314               "  album_artist.idArtist AS idArtist, "
315               "  artist.strArtist AS strArtist, "
316               "  artist.strMusicBrainzArtistID AS strMusicBrainzArtistID, "
317               "  album_artist.boolFeatured AS boolFeatured, "
318               "  album_artist.strJoinPhrase AS strJoinPhrase, "
319               "  album_artist.iOrder AS iOrder "
320               "FROM album_artist "
321               "JOIN artist ON "
322               "     album_artist.idArtist = artist.idArtist");
323
324   CLog::Log(LOGINFO, "create songartist view");
325   m_pDS->exec("CREATE VIEW songartistview AS SELECT"
326               "  song_artist.idSong AS idSong, "
327               "  song_artist.idArtist AS idArtist, "
328               "  artist.strArtist AS strArtist, "
329               "  artist.strMusicBrainzArtistID AS strMusicBrainzArtistID, "
330               "  song_artist.boolFeatured AS boolFeatured, "
331               "  song_artist.strJoinPhrase AS strJoinPhrase, "
332               "  song_artist.iOrder AS iOrder "
333               "FROM song_artist "
334               "JOIN artist ON "
335               "     song_artist.idArtist = artist.idArtist");
336 }
337
338 int CMusicDatabase::AddAlbumInfoSong(int idAlbum, const CSong& song)
339 {
340   CStdString strSQL = PrepareSQL("SELECT idAlbumInfoSong FROM albuminfosong WHERE idAlbumInfo = %i and iTrack = %i", idAlbum, song.iTrack);
341   int idAlbumInfoSong = (int)strtol(GetSingleValue(strSQL).c_str(), NULL, 10);
342   if (idAlbumInfoSong > 0)
343   {
344     strSQL = PrepareSQL("UPDATE albuminfosong SET strTitle = '%s', iDuration = %i WHERE idAlbumInfoSong = %i", song.strTitle.c_str(), song.iDuration, idAlbumInfoSong);
345     return ExecuteQuery(strSQL);
346   }
347   else
348   {
349     strSQL = PrepareSQL("INSERT INTO albuminfosong (idAlbumInfoSong,idAlbumInfo,iTrack,strTitle,iDuration) VALUES (NULL,%i,%i,'%s',%i)",
350                         idAlbum,
351                         song.iTrack,
352                         song.strTitle.c_str(),
353                         song.iDuration);
354     return ExecuteQuery(strSQL);
355   }
356 }
357
358 std::string GetArtistString(const VECARTISTCREDITS &credits)
359 {
360   std::string artistString;
361   for (VECARTISTCREDITS::const_iterator i = credits.begin(); i != credits.end(); ++i)
362     artistString += i->GetArtist() + i->GetJoinPhrase();
363   return artistString;
364 }
365
366 bool CMusicDatabase::AddAlbum(CAlbum& album)
367 {
368   BeginTransaction();
369
370   album.idAlbum = AddAlbum(album.strAlbum,
371                            album.strMusicBrainzAlbumID,
372                            GetArtistString(album.artistCredits),
373                            album.GetGenreString(),
374                            album.iYear,
375                            album.bCompilation);
376
377   // Add the album artists
378   for (VECARTISTCREDITS::iterator artistCredit = album.artistCredits.begin(); artistCredit != album.artistCredits.end(); ++artistCredit)
379   {
380     artistCredit->idArtist = AddArtist(artistCredit->GetArtist(), artistCredit->GetMusicBrainzArtistID());
381     AddAlbumArtist(artistCredit->idArtist,
382                    album.idAlbum,
383                    artistCredit->GetArtist(),
384                    artistCredit->GetJoinPhrase(),
385                    artistCredit == album.artistCredits.begin() ? false : true,
386                    std::distance(album.artistCredits.begin(), artistCredit));
387   }
388
389   for (VECSONGS::iterator song = album.songs.begin(); song != album.songs.end(); ++song)
390   {
391     song->idAlbum = album.idAlbum;
392     song->idSong = AddSong(song->idAlbum,
393                            song->strTitle, song->strMusicBrainzTrackID,
394                            song->strFileName, song->strComment,
395                            song->strThumb,
396                            GetArtistString(song->artistCredits), song->genre,
397                            song->iTrack, song->iDuration, song->iYear,
398                            song->iTimesPlayed, song->iStartOffset,
399                            song->iEndOffset,
400                            song->lastPlayed,
401                            song->rating,
402                            song->iKaraokeNumber);
403     for (VECARTISTCREDITS::iterator artistCredit = song->artistCredits.begin(); artistCredit != song->artistCredits.end(); ++artistCredit)
404     {
405       artistCredit->idArtist = AddArtist(artistCredit->GetArtist(),
406                                          artistCredit->GetMusicBrainzArtistID());
407       AddSongArtist(artistCredit->idArtist,
408                     song->idSong,
409                     artistCredit->GetArtist(),
410                     artistCredit->GetJoinPhrase(), // we don't have song artist breakdowns from scrapers, yet
411                     artistCredit == song->artistCredits.begin() ? false : true,
412                     std::distance(song->artistCredits.begin(), artistCredit));
413     }
414   }
415   for (VECSONGS::const_iterator infoSong = album.infoSongs.begin(); infoSong != album.infoSongs.end(); ++infoSong)
416     AddAlbumInfoSong(album.idAlbum, *infoSong);
417
418   for (std::map<std::string, std::string>::const_iterator albumArt = album.art.begin();
419                                                           albumArt != album.art.end();
420                                                         ++albumArt)
421     SetArtForItem(album.idAlbum, "album", albumArt->first, albumArt->second);
422
423   CommitTransaction();
424   return true;
425 }
426
427 bool CMusicDatabase::UpdateAlbum(CAlbum& album)
428 {
429   BeginTransaction();
430
431   UpdateAlbum(album.idAlbum,
432               album.strAlbum, album.strMusicBrainzAlbumID,
433               GetArtistString(album.artistCredits), album.GetGenreString(),
434               StringUtils::Join(album.moods, g_advancedSettings.m_musicItemSeparator).c_str(),
435               StringUtils::Join(album.styles, g_advancedSettings.m_musicItemSeparator).c_str(),
436               StringUtils::Join(album.themes, g_advancedSettings.m_musicItemSeparator).c_str(),
437               album.strReview,
438               album.thumbURL.m_xml.c_str(),
439               album.strLabel, album.strType,
440               album.iRating, album.iYear, album.bCompilation);
441
442   // Add the album artists
443   DeleteAlbumArtistsByAlbum(album.idAlbum);
444   for (VECARTISTCREDITS::iterator artistCredit = album.artistCredits.begin(); artistCredit != album.artistCredits.end(); ++artistCredit)
445   {
446     artistCredit->idArtist = AddArtist(artistCredit->GetArtist(),
447                                        artistCredit->GetMusicBrainzArtistID());
448     AddAlbumArtist(artistCredit->idArtist,
449                    album.idAlbum,
450                    artistCredit->GetArtist(),
451                    artistCredit->GetJoinPhrase(),
452                    artistCredit == album.artistCredits.begin() ? false : true,
453                    std::distance(album.artistCredits.begin(), artistCredit));
454   }
455
456   for (VECSONGS::iterator song = album.songs.begin(); song != album.songs.end(); ++song)
457   {
458     UpdateSong(song->idSong,
459                song->strTitle,
460                song->strMusicBrainzTrackID,
461                song->strFileName,
462                song->strComment,
463                song->strThumb,
464                GetArtistString(song->artistCredits),
465                song->genre,
466                song->iTrack,
467                song->iDuration,
468                song->iYear,
469                song->iTimesPlayed,
470                song->iStartOffset,
471                song->iEndOffset,
472                song->lastPlayed,
473                song->rating,
474                song->iKaraokeNumber);
475     DeleteSongArtistsBySong(song->idSong);
476     for (VECARTISTCREDITS::iterator artistCredit = song->artistCredits.begin(); artistCredit != song->artistCredits.end(); ++artistCredit)
477     {
478       artistCredit->idArtist = AddArtist(artistCredit->GetArtist(),
479                                          artistCredit->GetMusicBrainzArtistID());
480       AddSongArtist(artistCredit->idArtist,
481                     song->idSong,
482                     artistCredit->GetArtist(),
483                     artistCredit->GetJoinPhrase(),
484                     artistCredit == song->artistCredits.begin() ? false : true,
485                     std::distance(song->artistCredits.begin(), artistCredit));
486     }
487   }
488   for (VECSONGS::const_iterator infoSong = album.infoSongs.begin(); infoSong != album.infoSongs.end(); ++infoSong)
489     AddAlbumInfoSong(album.idAlbum, *infoSong);
490
491   if (!album.art.empty())
492     SetArtForItem(album.idAlbum, "album", album.art);
493
494   CommitTransaction();
495   return true;
496 }
497
498 int CMusicDatabase::AddSong(const int idAlbum,
499                             const CStdString& strTitle, const CStdString& strMusicBrainzTrackID,
500                             const CStdString& strPathAndFileName, const CStdString& strComment, const CStdString& strThumb,
501                             const std::string &artistString, const std::vector<std::string>& genres,
502                             int iTrack, int iDuration, int iYear,
503                             const int iTimesPlayed, int iStartOffset, int iEndOffset,
504                             const CDateTime& dtLastPlayed, char rating, int iKaraokeNumber)
505 {
506   int idSong = -1;
507   CStdString strSQL;
508   try
509   {
510     // We need at least the title
511     if (strTitle.empty())
512       return -1;
513
514     if (NULL == m_pDB.get()) return -1;
515     if (NULL == m_pDS.get()) return -1;
516
517     CStdString strPath, strFileName;
518     URIUtils::Split(strPathAndFileName, strPath, strFileName);
519     int idPath = AddPath(strPath);
520     DWORD crc = ComputeCRC(strFileName);
521
522     bool bHasKaraoke = false;
523 #ifdef HAS_KARAOKE
524     bHasKaraoke = CKaraokeLyricsFactory::HasLyrics(strPathAndFileName);
525 #endif
526
527     if (!strMusicBrainzTrackID.empty())
528       strSQL = PrepareSQL("SELECT * FROM song WHERE idAlbum = %i AND strMusicBrainzTrackID = '%s'",
529                           idAlbum,
530                           strMusicBrainzTrackID.c_str());
531     else
532       strSQL = PrepareSQL("SELECT * FROM song WHERE idAlbum=%i AND dwFileNameCRC='%ul' AND strTitle='%s' AND strMusicBrainzTrackID IS NULL",
533                           idAlbum,
534                           crc,
535                           strTitle.c_str());
536
537     if (!m_pDS->query(strSQL.c_str()))
538       return -1;
539
540     if (m_pDS->num_rows() == 0)
541     {
542       m_pDS->close();
543       strSQL=PrepareSQL("INSERT INTO song (idSong,idAlbum,idPath,strArtists,strGenres,strTitle,iTrack,iDuration,iYear,dwFileNameCRC,strFileName,strMusicBrainzTrackID,iTimesPlayed,iStartOffset,iEndOffset,lastplayed,rating,comment) values (NULL, %i, %i, '%s', '%s', '%s', %i, %i, %i, '%ul', '%s'",
544                     idAlbum,
545                     idPath,
546                     artistString.c_str(),
547                     StringUtils::Join(genres, g_advancedSettings.m_musicItemSeparator).c_str(),
548                     strTitle.c_str(),
549                     iTrack, iDuration, iYear,
550                     crc, strFileName.c_str());
551
552       if (strMusicBrainzTrackID.empty())
553         strSQL += PrepareSQL(",NULL");
554       else
555         strSQL += PrepareSQL(",'%s'", strMusicBrainzTrackID.c_str());
556
557       if (dtLastPlayed.IsValid())
558         strSQL += PrepareSQL(",%i,%i,%i,'%s','%c','%s')",
559                       iTimesPlayed, iStartOffset, iEndOffset, dtLastPlayed.GetAsDBDateTime().c_str(), rating, strComment.c_str());
560       else
561         strSQL += PrepareSQL(",%i,%i,%i,NULL,'%c','%s')",
562                       iTimesPlayed, iStartOffset, iEndOffset, rating, strComment.c_str());
563       m_pDS->exec(strSQL.c_str());
564       idSong = (int)m_pDS->lastinsertid();
565     }
566     else
567     {
568       idSong = m_pDS->fv("idSong").get_asInt();
569       m_pDS->close();
570       UpdateSong(idSong, strTitle, strMusicBrainzTrackID, strPathAndFileName, strComment, strThumb, artistString, genres, iTrack, iDuration, iYear, iTimesPlayed, iStartOffset, iEndOffset, dtLastPlayed, rating,  iKaraokeNumber);
571     }
572
573     if (!strThumb.empty())
574       SetArtForItem(idSong, "song", "thumb", strThumb);
575
576     unsigned int index = 0;
577     // If this is karaoke song, change the genre to 'Karaoke' (and add it if it's not there)
578     if ( bHasKaraoke && g_advancedSettings.m_karaokeChangeGenreForKaraokeSongs )
579     {
580       int idGenre = AddGenre("Karaoke");
581       AddSongGenre(idGenre, idSong, index);
582       AddAlbumGenre(idGenre, idAlbum, index++);
583     }
584     for (vector<string>::const_iterator i = genres.begin(); i != genres.end(); ++i)
585     {
586       // index will be wrong for albums, but ordering is not all that relevant
587       // for genres anyway
588       int idGenre = AddGenre(*i);
589       AddSongGenre(idGenre, idSong, index);
590       AddAlbumGenre(idGenre, idAlbum, index++);
591     }
592
593     // Add karaoke information (if any)
594     if (bHasKaraoke)
595       AddKaraokeData(idSong, iKaraokeNumber, crc);
596
597     AnnounceUpdate("song", idSong);
598   }
599   catch (...)
600   {
601     CLog::Log(LOGERROR, "musicdatabase:unable to addsong (%s)", strSQL.c_str());
602   }
603   return idSong;
604 }
605
606 bool CMusicDatabase::GetSong(int idSong, CSong& song)
607 {
608   try
609   {
610     song.Clear();
611
612     if (NULL == m_pDB.get()) return false;
613     if (NULL == m_pDS.get()) return false;
614
615     CStdString strSQL=PrepareSQL("SELECT songview.*,songartistview.* FROM songview "
616                                  " JOIN songartistview ON songview.idSong = songartistview.idSong "
617                                  " WHERE songview.idSong = %i", idSong);
618
619     if (!m_pDS->query(strSQL.c_str())) return false;
620     int iRowsFound = m_pDS->num_rows();
621     if (iRowsFound == 0)
622     {
623       m_pDS->close();
624       return false;
625     }
626
627     int songArtistOffset = song_enumCount;
628
629     set<int> artistcredits;
630     song = GetSongFromDataset(m_pDS.get()->get_sql_record());
631     while (!m_pDS->eof())
632     {
633       const dbiplus::sql_record* const record = m_pDS.get()->get_sql_record();
634
635       int idSongArtist = record->at(songArtistOffset + artistCredit_idArtist).get_asInt();
636       if (artistcredits.find(idSongArtist) == artistcredits.end())
637       {
638         song.artistCredits.push_back(GetArtistCreditFromDataset(record, songArtistOffset));
639         artistcredits.insert(idSongArtist);
640       }
641
642       m_pDS->next();
643     }
644     m_pDS->close(); // cleanup recordset data
645     return true;
646   }
647   catch (...)
648   {
649     CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idSong);
650   }
651
652   return false;
653 }
654
655 int CMusicDatabase::UpdateSong(int idSong, const CSong &song)
656 {
657   return UpdateSong(idSong,
658                     song.strTitle,
659                     song.strMusicBrainzTrackID,
660                     song.strFileName,
661                     song.strComment,
662                     song.strThumb,
663                     StringUtils::Join(song.artist, g_advancedSettings.m_musicItemSeparator), // NOTE: Don't call this function internally!!!
664                     song.genre,
665                     song.iTrack,
666                     song.iDuration,
667                     song.iYear,
668                     song.iTimesPlayed,
669                     song.iStartOffset,
670                     song.iEndOffset,
671                     song.lastPlayed,
672                     song.rating,
673                     song.iKaraokeNumber);
674 }
675
676 int CMusicDatabase::UpdateSong(int idSong,
677                                const CStdString& strTitle, const CStdString& strMusicBrainzTrackID,
678                                const CStdString& strPathAndFileName, const CStdString& strComment, const CStdString& strThumb,
679                                const std::string& artistString, const std::vector<std::string>& genres,
680                                int iTrack, int iDuration, int iYear,
681                                int iTimesPlayed, int iStartOffset, int iEndOffset,
682                                const CDateTime& dtLastPlayed, char rating, int iKaraokeNumber)
683 {
684   if (idSong < 0)
685     return -1;
686
687   CStdString strSQL;
688   CStdString strPath, strFileName;
689   URIUtils::Split(strPathAndFileName, strPath, strFileName);
690   int idPath = AddPath(strPath);
691   DWORD crc = ComputeCRC(strFileName);
692
693   strSQL = PrepareSQL("UPDATE song SET idPath = %i, strArtists = '%s', strGenres = '%s', strTitle = '%s', iTrack = %i, iDuration = %i, iYear = %i, dwFileNameCRC = '%ul', strFileName = '%s'",
694                       idPath,
695                       artistString.c_str(),
696                       StringUtils::Join(genres, g_advancedSettings.m_musicItemSeparator).c_str(),
697                       strTitle.c_str(),
698                       iTrack, iDuration, iYear,
699                       crc, strFileName.c_str());
700   if (strMusicBrainzTrackID.empty())
701     strSQL += PrepareSQL(", strMusicBrainzTrackID = NULL");
702   else
703     strSQL += PrepareSQL(", strMusicBrainzTrackID = '%s'", strMusicBrainzTrackID.c_str());
704
705   if (dtLastPlayed.IsValid())
706     strSQL += PrepareSQL(", iTimesPlayed = %i, iStartOffset = %i, iEndOffset = %i, lastplayed = '%s', rating = '%c', comment = '%s'",
707                          iTimesPlayed, iStartOffset, iEndOffset, dtLastPlayed.GetAsDBDateTime().c_str(), rating, strComment.c_str());
708   else
709     strSQL += PrepareSQL(", iTimesPlayed = %i, iStartOffset = %i, iEndOffset = %i, lastplayed = NULL, rating = '%c', comment = '%s'",
710                          iTimesPlayed, iStartOffset, iEndOffset, rating, strComment.c_str());
711   strSQL += PrepareSQL(" WHERE idSong = %i", idSong);
712
713   bool status = ExecuteQuery(strSQL);
714   if (status)
715     AnnounceUpdate("song", idSong);
716   return idSong;
717 }
718
719 int CMusicDatabase::AddAlbum(const CStdString& strAlbum, const CStdString& strMusicBrainzAlbumID,
720                              const CStdString& strArtist, const CStdString& strGenre, int year, bool bCompilation)
721 {
722   CStdString strSQL;
723   try
724   {
725     if (NULL == m_pDB.get()) return -1;
726     if (NULL == m_pDS.get()) return -1;
727
728     if (!strMusicBrainzAlbumID.empty())
729       strSQL = PrepareSQL("SELECT * FROM album WHERE strMusicBrainzAlbumID = '%s'",
730                         strMusicBrainzAlbumID.c_str());
731     else
732       strSQL = PrepareSQL("SELECT * FROM album WHERE strArtists LIKE '%s' AND strAlbum LIKE '%s' AND strMusicBrainzAlbumID IS NULL",
733                           strArtist.c_str(),
734                           strAlbum.c_str());
735     m_pDS->query(strSQL.c_str());
736
737     if (m_pDS->num_rows() == 0)
738     {
739       m_pDS->close();
740       // doesnt exists, add it
741       if (strMusicBrainzAlbumID.empty())
742         strSQL=PrepareSQL("insert into album (idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, strGenres, iYear, bCompilation) values( NULL, '%s', NULL, '%s', '%s', %i, %i)",
743                           strAlbum.c_str(),
744                           strArtist.c_str(),
745                           strGenre.c_str(),
746                           year,
747                           bCompilation);
748       else
749         strSQL=PrepareSQL("insert into album (idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, strGenres, iYear, bCompilation) values( NULL, '%s', '%s', '%s', '%s', %i, %i)",
750                           strAlbum.c_str(),
751                           strMusicBrainzAlbumID.c_str(),
752                           strArtist.c_str(),
753                           strGenre.c_str(),
754                           year,
755                           bCompilation);
756       m_pDS->exec(strSQL.c_str());
757
758       return (int)m_pDS->lastinsertid();
759     }
760     else
761     {
762       /* Exists in our database and being re-scanned from tags, so we should update it as the details
763          may have changed.
764
765          Note that for multi-folder albums this will mean the last folder scanned will have the information
766          stored for it.  Most values here should be the same across all songs anyway, but it does mean
767          that if there's any inconsistencies then only the last folders information will be taken.
768
769          We make sure we clear out the link tables (album artists, album genres) and we reset
770          the last scraped time to make sure that online metadata is re-fetched. */
771       int idAlbum = m_pDS->fv("idAlbum").get_asInt();
772       m_pDS->close();
773       if (strMusicBrainzAlbumID.empty())
774         strSQL=PrepareSQL("UPDATE album SET strGenres = '%s', iYear=%i, bCompilation=%i, lastScraped = NULL WHERE idAlbum=%i",
775                           strGenre.c_str(),
776                           year,
777                           bCompilation,
778                           idAlbum);
779       else
780         strSQL=PrepareSQL("UPDATE album SET strAlbum = '%s', strArtists = '%s', strGenres = '%s', iYear=%i, bCompilation=%i, lastScraped = NULL WHERE idAlbum=%i",
781                           strAlbum.c_str(),
782                           strArtist.c_str(),
783                           strGenre.c_str(),
784                           year,
785                           bCompilation,
786                           idAlbum);
787       m_pDS->exec(strSQL.c_str());
788       DeleteAlbumArtistsByAlbum(idAlbum);
789       DeleteAlbumGenresByAlbum(idAlbum);
790       return idAlbum;
791     }
792   }
793   catch (...)
794   {
795     CLog::Log(LOGERROR, "%s failed with query (%s)", __FUNCTION__, strSQL.c_str());
796   }
797
798   return -1;
799 }
800
801 int  CMusicDatabase::UpdateAlbum(int idAlbum,
802                                  const CStdString& strAlbum, const CStdString& strMusicBrainzAlbumID,
803                                  const CStdString& strArtist, const CStdString& strGenre,
804                                  const CStdString& strMoods, const CStdString& strStyles,
805                                  const CStdString& strThemes, const CStdString& strReview,
806                                  const CStdString& strImage, const CStdString& strLabel,
807                                  const CStdString& strType,
808                                  int iRating, int iYear, bool bCompilation)
809 {
810   if (idAlbum < 0)
811     return -1;
812
813   CStdString strSQL;
814   strSQL = PrepareSQL("UPDATE album SET "
815                       " strAlbum = '%s', strArtists = '%s', strGenres = '%s', "
816                       " strMoods = '%s', strStyles = '%s', strThemes = '%s', "
817                       " strReview = '%s', strImage = '%s', strLabel = '%s', "
818                       " strType = '%s',"
819                       " iYear = %i, bCompilation = %i, lastScraped = '%s'",
820                       strAlbum.c_str(), strArtist.c_str(), strGenre.c_str(),
821                       strMoods.c_str(), strStyles.c_str(), strThemes.c_str(),
822                       strReview.c_str(), strImage.c_str(), strLabel.c_str(),
823                       strType.c_str(),
824                       iYear, bCompilation,
825                       CDateTime::GetCurrentDateTime().GetAsDBDateTime().c_str());
826   if (strMusicBrainzAlbumID.empty())
827     strSQL += PrepareSQL(", strMusicBrainzAlbumID = NULL");
828   else
829     strSQL += PrepareSQL(", strMusicBrainzAlbumID = '%s'", strMusicBrainzAlbumID.c_str());
830
831   strSQL += PrepareSQL(" WHERE idAlbum = %i", idAlbum);
832
833   bool status = ExecuteQuery(strSQL);
834   if (status)
835     AnnounceUpdate("album", idAlbum);
836   return idAlbum;
837 }
838
839 bool CMusicDatabase::GetAlbum(int idAlbum, CAlbum& album, bool getSongs /* = true */)
840 {
841   try
842   {
843     if (NULL == m_pDB.get()) return false;
844     if (NULL == m_pDS.get()) return false;
845
846     if (idAlbum == -1)
847       return false; // not in the database
848
849     CStdString sql;
850     if (getSongs)
851     {
852       sql = PrepareSQL("SELECT albumview.*,albumartistview.*,songview.*,songartistview.*,albuminfosong.* "
853                        "  FROM albumview "
854                        "  JOIN albumartistview ON albumview.idAlbum = albumartistview.idAlbum "
855                        "  JOIN songview ON albumview.idAlbum = songview.idAlbum "
856                        "  JOIN songartistview ON songview.idSong = songartistview.idSong "
857                        "  LEFT JOIN albuminfosong ON albumview.idAlbum = albuminfosong.idAlbumInfo "
858                        "  WHERE albumview.idAlbum = %ld "
859                        "  ORDER BY albumartistview.iOrder, songview.iTrack, songartistview.iOrder", idAlbum);
860     }
861     else
862     {
863       sql = PrepareSQL("SELECT albumview.*,albumartistview.* "
864                        "  FROM albumview "
865                        "  JOIN albumartistview ON albumview.idAlbum = albumartistview.idAlbum "
866                        "  WHERE albumview.idAlbum = %ld "
867                        "  ORDER BY albumartistview.iOrder", idAlbum);
868     }
869
870     CLog::Log(LOGDEBUG, "%s", sql.c_str());
871     if (!m_pDS->query(sql.c_str())) return false;
872     if (m_pDS->num_rows() == 0)
873     {
874       m_pDS->close();
875       return false;
876     }
877
878     int albumArtistOffset = album_enumCount;
879     int songOffset = albumArtistOffset + artistCredit_enumCount;
880     int songArtistOffset = songOffset + song_enumCount;
881     int infoSongOffset = songArtistOffset + artistCredit_enumCount;
882
883     set<int> artistcredits;
884     set<int> songs;
885     set<pair<int, int> > songartistcredits;
886     set<int> infosongs;
887     album = GetAlbumFromDataset(m_pDS.get()->get_sql_record(), 0, true); // true to grab and parse the imageURL
888     while (!m_pDS->eof())
889     {
890       const dbiplus::sql_record* const record = m_pDS->get_sql_record();
891
892       // Because rows repeat in the joined query (cartesian join) we may see each
893       // entity (album artist, song, song artist) multiple times in the result set.
894       // Since there should never be a song with the same artist twice, or an album
895       // with the same song (by id) listed twice, we key on the entity ID and only
896       // create an entity for the first occurence of each entity in the data set.
897       int idAlbumArtist = record->at(albumArtistOffset + artistCredit_idArtist).get_asInt();
898       if (artistcredits.find(idAlbumArtist) == artistcredits.end())
899       {
900         album.artistCredits.push_back(GetArtistCreditFromDataset(record, albumArtistOffset));
901         artistcredits.insert(idAlbumArtist);
902       }
903
904       if (getSongs)
905       {
906         int idSong = record->at(songOffset + song_idSong).get_asInt();
907         if (songs.find(idSong) == songs.end())
908         {
909           album.songs.push_back(GetSongFromDataset(record, songOffset));
910           songs.insert(idSong);
911         }
912
913         int idSongArtistSong = record->at(songArtistOffset + artistCredit_idEntity).get_asInt();
914         int idSongArtistArtist = record->at(songArtistOffset + artistCredit_idArtist).get_asInt();
915         if (songartistcredits.find(make_pair(idSongArtistSong, idSongArtistArtist)) == songartistcredits.end())
916         {
917           for (VECSONGS::iterator si = album.songs.begin(); si != album.songs.end(); ++si)
918             if (si->idSong == idSongArtistSong)
919               si->artistCredits.push_back(GetArtistCreditFromDataset(record, songArtistOffset));
920           songartistcredits.insert(make_pair(idSongArtistSong, idSongArtistArtist));
921         }
922
923         int idAlbumInfoSong = m_pDS.get()->get_sql_record()->at(infoSongOffset + albumInfoSong_idAlbumInfoSong).get_asInt();
924         if (infosongs.find(idAlbumInfoSong) == infosongs.end())
925         {
926           album.infoSongs.push_back(GetAlbumInfoSongFromDataset(record, infoSongOffset));
927           infosongs.insert(idAlbumInfoSong);
928         }
929       }
930       m_pDS->next();
931     }
932     m_pDS->close(); // cleanup recordset data
933     return true;
934   }
935   catch (...)
936   {
937     CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idAlbum);
938   }
939
940   return false;
941 }
942
943 bool CMusicDatabase::ClearAlbumLastScrapedTime(int idAlbum)
944 {
945   CStdString strSQL = PrepareSQL("UPDATE album SET lastScraped = NULL WHERE idAlbum = %i", idAlbum);
946   return ExecuteQuery(strSQL);
947 }
948
949 bool CMusicDatabase::HasAlbumBeenScraped(int idAlbum)
950 {
951   CStdString strSQL = PrepareSQL("SELECT idAlbum FROM album WHERE idAlbum = %i AND lastScraped IS NULL", idAlbum);
952   return GetSingleValue(strSQL).empty();
953 }
954
955 int CMusicDatabase::AddGenre(const CStdString& strGenre1)
956 {
957   CStdString strSQL;
958   try
959   {
960     CStdString strGenre = strGenre1;
961     StringUtils::Trim(strGenre);
962
963     if (strGenre.empty())
964       strGenre=g_localizeStrings.Get(13205); // Unknown
965
966     if (NULL == m_pDB.get()) return -1;
967     if (NULL == m_pDS.get()) return -1;
968     map <CStdString, int>::const_iterator it;
969
970     it = m_genreCache.find(strGenre);
971     if (it != m_genreCache.end())
972       return it->second;
973
974
975     strSQL=PrepareSQL("select * from genre where strGenre like '%s'", strGenre.c_str());
976     m_pDS->query(strSQL.c_str());
977     if (m_pDS->num_rows() == 0)
978     {
979       m_pDS->close();
980       // doesnt exists, add it
981       strSQL=PrepareSQL("insert into genre (idGenre, strGenre) values( NULL, '%s' )", strGenre.c_str());
982       m_pDS->exec(strSQL.c_str());
983
984       int idGenre = (int)m_pDS->lastinsertid();
985       m_genreCache.insert(pair<CStdString, int>(strGenre1, idGenre));
986       return idGenre;
987     }
988     else
989     {
990       int idGenre = m_pDS->fv("idGenre").get_asInt();
991       m_genreCache.insert(pair<CStdString, int>(strGenre1, idGenre));
992       m_pDS->close();
993       return idGenre;
994     }
995   }
996   catch (...)
997   {
998     CLog::Log(LOGERROR, "musicdatabase:unable to addgenre (%s)", strSQL.c_str());
999   }
1000
1001   return -1;
1002 }
1003
1004 bool CMusicDatabase::UpdateArtist(const CArtist& artist)
1005 {
1006   UpdateArtist(artist.idArtist,
1007                artist.strArtist, artist.strMusicBrainzArtistID,
1008                artist.strBorn, artist.strFormed,
1009                StringUtils::Join(artist.genre, g_advancedSettings.m_musicItemSeparator),
1010                StringUtils::Join(artist.moods, g_advancedSettings.m_musicItemSeparator),
1011                StringUtils::Join(artist.styles, g_advancedSettings.m_musicItemSeparator),
1012                StringUtils::Join(artist.instruments, g_advancedSettings.m_musicItemSeparator),
1013                artist.strBiography, artist.strDied,
1014                artist.strDisbanded,
1015                StringUtils::Join(artist.yearsActive, g_advancedSettings.m_musicItemSeparator).c_str(),
1016                artist.thumbURL.m_xml.c_str(),
1017                artist.fanart.m_xml.c_str());
1018
1019   DeleteArtistDiscography(artist.idArtist);
1020   for (std::vector<std::pair<CStdString,CStdString> >::const_iterator disc = artist.discography.begin();
1021                                                                       disc != artist.discography.end();
1022                                                                     ++disc)
1023   {
1024     AddArtistDiscography(artist.idArtist, disc->first, disc->second);
1025   }
1026
1027   return true;
1028 }
1029
1030 int CMusicDatabase::AddArtist(const CStdString& strArtist, const CStdString& strMusicBrainzArtistID)
1031 {
1032   CStdString strSQL;
1033   try
1034   {
1035     if (NULL == m_pDB.get()) return -1;
1036     if (NULL == m_pDS.get()) return -1;
1037
1038     // 1) MusicBrainz
1039     if (!strMusicBrainzArtistID.empty())
1040     {
1041       // 1.a) Match on a MusicBrainz ID
1042       strSQL = PrepareSQL("SELECT * FROM artist WHERE strMusicBrainzArtistID = '%s'",
1043                           strMusicBrainzArtistID.c_str());
1044       m_pDS->query(strSQL.c_str());
1045       if (m_pDS->num_rows() > 0)
1046       {
1047         int idArtist = (int)m_pDS->fv("idArtist").get_asInt();
1048         m_pDS->close();
1049         return idArtist;
1050       }
1051       m_pDS->close();
1052
1053
1054       // 1.b) No match on MusicBrainz ID. Look for a previously added artist with no MusicBrainz ID
1055       //     and update that if it exists.
1056       strSQL = PrepareSQL("SELECT * FROM artist WHERE strArtist LIKE '%s' AND strMusicBrainzArtistID IS NULL", strArtist.c_str());
1057       m_pDS->query(strSQL.c_str());
1058       if (m_pDS->num_rows() > 0)
1059       {
1060         int idArtist = (int)m_pDS->fv("idArtist").get_asInt();
1061         m_pDS->close();
1062         // 1.b.a) We found an artist by name but with no MusicBrainz ID set, update it and assume it is our artist
1063         strSQL = PrepareSQL("UPDATE artist SET strArtist = '%s', strMusicBrainzArtistID = '%s' WHERE idArtist = %i",
1064                             strArtist.c_str(),
1065                             strMusicBrainzArtistID.c_str(),
1066                             idArtist);
1067         m_pDS->exec(strSQL.c_str());
1068         return idArtist;
1069       }
1070
1071     // 2) No MusicBrainz - search for any artist (MB ID or non) with the same name.
1072     //    With MusicBrainz IDs this could return multiple artists and is non-determinstic
1073     //    Always pick the first artist ID returned by the DB to return.
1074     }
1075     else
1076     {
1077       strSQL = PrepareSQL("SELECT * FROM artist WHERE strArtist LIKE '%s'",
1078                           strArtist.c_str());
1079
1080       m_pDS->query(strSQL.c_str());
1081       if (m_pDS->num_rows() > 0)
1082       {
1083         int idArtist = (int)m_pDS->fv("idArtist").get_asInt();
1084         m_pDS->close();
1085         return idArtist;
1086       }
1087       m_pDS->close();
1088     }
1089
1090     // 3) No artist exists at all - add it
1091     if (strMusicBrainzArtistID.empty())
1092       strSQL = PrepareSQL("INSERT INTO artist (idArtist, strArtist, strMusicBrainzArtistID) VALUES( NULL, '%s', NULL )",
1093                           strArtist.c_str());
1094     else
1095       strSQL = PrepareSQL("INSERT INTO artist (idArtist, strArtist, strMusicBrainzArtistID) VALUES( NULL, '%s', '%s' )",
1096                           strArtist.c_str(),
1097                           strMusicBrainzArtistID.c_str());
1098
1099     m_pDS->exec(strSQL.c_str());
1100     int idArtist = (int)m_pDS->lastinsertid();
1101     return idArtist;
1102   }
1103   catch (...)
1104   {
1105     CLog::Log(LOGERROR, "musicdatabase:unable to addartist (%s)", strSQL.c_str());
1106   }
1107
1108   return -1;
1109 }
1110
1111 int  CMusicDatabase::UpdateArtist(int idArtist,
1112                                   const CStdString& strArtist, const CStdString& strMusicBrainzArtistID,
1113                                   const CStdString& strBorn, const CStdString& strFormed,
1114                                   const CStdString& strGenres, const CStdString& strMoods,
1115                                   const CStdString& strStyles, const CStdString& strInstruments,
1116                                   const CStdString& strBiography, const CStdString& strDied,
1117                                   const CStdString& strDisbanded, const CStdString& strYearsActive,
1118                                   const CStdString& strImage, const CStdString& strFanart)
1119 {
1120   CScraperUrl thumbURL;
1121   CFanart fanart;
1122   std::vector<std::pair<CStdString,CStdString> > discography;
1123   if (idArtist < 0)
1124     return -1;
1125
1126   CStdString strSQL;
1127   strSQL = PrepareSQL("UPDATE artist SET "
1128                       " strArtist = '%s', "
1129                       " strBorn = '%s', strFormed = '%s', strGenres = '%s', "
1130                       " strMoods = '%s', strStyles = '%s', strInstruments = '%s', "
1131                       " strBiography = '%s', strDied = '%s', strDisbanded = '%s', "
1132                       " strYearsActive = '%s', strImage = '%s', strFanart = '%s', "
1133                       " lastScraped = '%s'",
1134                       strArtist.c_str(), /* strMusicBrainzArtistID.c_str(), */
1135                       strBorn.c_str(), strFormed.c_str(), strGenres.c_str(),
1136                       strMoods.c_str(), strStyles.c_str(), strInstruments.c_str(),
1137                       strBiography.c_str(), strDied.c_str(), strDisbanded.c_str(),
1138                       strYearsActive.c_str(), strImage.c_str(), strFanart.c_str(),
1139                       CDateTime::GetCurrentDateTime().GetAsDBDateTime().c_str());
1140   if (strMusicBrainzArtistID.empty())
1141     strSQL += PrepareSQL(", strMusicBrainzArtistID = NULL");
1142   else
1143     strSQL += PrepareSQL(", strMusicBrainzArtistID = '%s'", strMusicBrainzArtistID.c_str());
1144
1145   strSQL += PrepareSQL(" WHERE idArtist = %i", idArtist);
1146
1147   bool status = ExecuteQuery(strSQL);
1148   if (status)
1149     AnnounceUpdate("artist", idArtist);
1150   return idArtist;
1151 }
1152
1153 bool CMusicDatabase::GetArtist(int idArtist, CArtist &artist, bool fetchAll /* = false */)
1154 {
1155   try
1156   {
1157     if (NULL == m_pDB.get()) return false;
1158     if (NULL == m_pDS.get()) return false;
1159
1160     if (idArtist == -1)
1161       return false; // not in the database
1162
1163     CStdString strSQL;
1164     if (fetchAll)
1165       strSQL = PrepareSQL("SELECT * FROM artistview LEFT JOIN discography ON artistview.idArtist = discography.idArtist WHERE artistview.idArtist = %i", idArtist);
1166     else
1167       strSQL = PrepareSQL("SELECT * FROM artistview WHERE artistview.idArtist = %i", idArtist);
1168
1169     if (!m_pDS->query(strSQL.c_str())) return false;
1170     if (m_pDS->num_rows() == 0)
1171     {
1172       m_pDS->close();
1173       return false;
1174     }
1175
1176     int discographyOffset = artist_enumCount;
1177
1178     artist.discography.clear();
1179     artist = GetArtistFromDataset(m_pDS.get()->get_sql_record(), 0, fetchAll);
1180     if (fetchAll)
1181     {
1182       while (!m_pDS->eof())
1183       {
1184         const dbiplus::sql_record* const record = m_pDS.get()->get_sql_record();
1185
1186         artist.discography.push_back(make_pair(record->at(discographyOffset + 1).get_asString(), record->at(discographyOffset + 2).get_asString()));
1187         m_pDS->next();
1188       }
1189     }
1190     m_pDS->close(); // cleanup recordset data
1191     return true;
1192   }
1193   catch (...)
1194   {
1195     CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idArtist);
1196   }
1197
1198   return false;
1199 }
1200
1201 bool CMusicDatabase::HasArtistBeenScraped(int idArtist)
1202 {
1203   CStdString strSQL = PrepareSQL("SELECT idArtist FROM artist WHERE idArtist = %i AND lastScraped IS NULL", idArtist);
1204   return GetSingleValue(strSQL).empty();
1205 }
1206
1207 bool CMusicDatabase::ClearArtistLastScrapedTime(int idArtist)
1208 {
1209   CStdString strSQL = PrepareSQL("UPDATE artist SET lastScraped = NULL WHERE idArtist = %i", idArtist);
1210   return ExecuteQuery(strSQL);
1211 }
1212
1213 int CMusicDatabase::AddArtistDiscography(int idArtist, const CStdString& strAlbum, const CStdString& strYear)
1214 {
1215   CStdString strSQL=PrepareSQL("INSERT INTO discography (idArtist, strAlbum, strYear) values(%i, '%s', '%s')",
1216                                idArtist,
1217                                strAlbum.c_str(),
1218                                strYear.c_str());
1219   return ExecuteQuery(strSQL);
1220 }
1221
1222 bool CMusicDatabase::DeleteArtistDiscography(int idArtist)
1223 {
1224   CStdString strSQL = PrepareSQL("DELETE FROM discography WHERE idArtist = %i", idArtist);
1225   return ExecuteQuery(strSQL);
1226 }
1227
1228 bool CMusicDatabase::AddSongArtist(int idArtist, int idSong, std::string strArtist, std::string joinPhrase, bool featured, int iOrder)
1229 {
1230   CStdString strSQL;
1231   strSQL=PrepareSQL("replace into song_artist (idArtist, idSong, strArtist, strJoinPhrase, boolFeatured, iOrder) values(%i,%i,'%s','%s',%i,%i)",
1232                     idArtist, idSong, strArtist.c_str(), joinPhrase.c_str(), featured == true ? 1 : 0, iOrder);
1233   return ExecuteQuery(strSQL);
1234 };
1235
1236 bool CMusicDatabase::DeleteSongArtistsBySong(int idSong)
1237 {
1238   return ExecuteQuery(PrepareSQL("DELETE FROM song_artist WHERE idSong = %i", idSong));
1239 }
1240
1241 bool CMusicDatabase::AddAlbumArtist(int idArtist, int idAlbum, std::string strArtist, std::string joinPhrase, bool featured, int iOrder)
1242 {
1243   CStdString strSQL;
1244   strSQL=PrepareSQL("replace into album_artist (idArtist, idAlbum, strArtist, strJoinPhrase, boolFeatured, iOrder) values(%i,%i,'%s','%s',%i,%i)",
1245                     idArtist, idAlbum, strArtist.c_str(), joinPhrase.c_str(), featured == true ? 1 : 0, iOrder);
1246   return ExecuteQuery(strSQL);
1247 };
1248
1249 bool CMusicDatabase::DeleteAlbumArtistsByAlbum(int idAlbum)
1250 {
1251   return ExecuteQuery(PrepareSQL("DELETE FROM album_artist WHERE idAlbum = %i", idAlbum));
1252 }
1253
1254 bool CMusicDatabase::AddSongGenre(int idGenre, int idSong, int iOrder)
1255 {
1256   if (idGenre == -1 || idSong == -1)
1257     return true;
1258
1259   CStdString strSQL;
1260   strSQL=PrepareSQL("replace into song_genre (idGenre, idSong, iOrder) values(%i,%i,%i)",
1261                     idGenre, idSong, iOrder);
1262   return ExecuteQuery(strSQL);
1263 };
1264
1265 bool CMusicDatabase::DeleteSongGenresBySong(int idSong)
1266 {
1267   return ExecuteQuery(PrepareSQL("DELETE FROM song_genre WHERE idSong = %i", idSong));
1268 }
1269
1270 bool CMusicDatabase::AddAlbumGenre(int idGenre, int idAlbum, int iOrder)
1271 {
1272   if (idGenre == -1 || idAlbum == -1)
1273     return true;
1274   
1275   CStdString strSQL;
1276   strSQL=PrepareSQL("replace into album_genre (idGenre, idAlbum, iOrder) values(%i,%i,%i)",
1277                     idGenre, idAlbum, iOrder);
1278   return ExecuteQuery(strSQL);
1279 };
1280
1281 bool CMusicDatabase::DeleteAlbumGenresByAlbum(int idAlbum)
1282 {
1283   return ExecuteQuery(PrepareSQL("DELETE FROM album_genre WHERE idAlbum = %i", idAlbum));
1284 }
1285
1286 bool CMusicDatabase::GetAlbumsByArtist(int idArtist, bool includeFeatured, std::vector<int> &albums)
1287 {
1288   try 
1289   {
1290     CStdString strSQL, strPrepSQL;
1291
1292     strPrepSQL = "select idAlbum from album_artist where idArtist=%i";
1293     if (includeFeatured == false)
1294       strPrepSQL += " AND boolFeatured = 0";
1295     
1296     strSQL=PrepareSQL(strPrepSQL, idArtist);
1297     if (!m_pDS->query(strSQL.c_str())) 
1298       return false;
1299     if (m_pDS->num_rows() == 0)
1300     {
1301       m_pDS->close();
1302       return false;
1303     }
1304     
1305     while (!m_pDS->eof())
1306     {
1307       albums.push_back(m_pDS->fv("idAlbum").get_asInt());
1308       m_pDS->next();
1309     }
1310     m_pDS->close();
1311     return true;
1312   }
1313   catch (...)
1314   {
1315     CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idArtist);
1316   }
1317   return false;
1318 }
1319
1320 bool CMusicDatabase::GetArtistsByAlbum(int idAlbum, bool includeFeatured, std::vector<int> &artists)
1321 {
1322   try 
1323   {
1324     CStdString strSQL, strPrepSQL;
1325
1326     strPrepSQL = "select idArtist from album_artist where idAlbum=%i";
1327     if (includeFeatured == false)
1328       strPrepSQL += " AND boolFeatured = 0";
1329
1330     strSQL=PrepareSQL(strPrepSQL, idAlbum);
1331     if (!m_pDS->query(strSQL.c_str())) 
1332       return false;
1333     if (m_pDS->num_rows() == 0)
1334     {
1335       m_pDS->close();
1336       return false;
1337     }
1338
1339     while (!m_pDS->eof())
1340     {
1341       artists.push_back(m_pDS->fv("idArtist").get_asInt());
1342       m_pDS->next();
1343     }
1344     m_pDS->close();
1345     return true;
1346   }
1347   catch (...)
1348   {
1349     CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idAlbum);
1350   }
1351   return false;
1352 }
1353
1354 bool CMusicDatabase::GetSongsByArtist(int idArtist, bool includeFeatured, std::vector<int> &songs)
1355 {
1356   try 
1357   {
1358     CStdString strSQL, strPrepSQL;
1359     
1360     strPrepSQL = "select idSong from song_artist where idArtist=%i";
1361     if (includeFeatured == false)
1362       strPrepSQL += " AND boolFeatured = 0";
1363
1364     strSQL=PrepareSQL(strPrepSQL, idArtist);
1365     if (!m_pDS->query(strSQL.c_str())) 
1366       return false;
1367     if (m_pDS->num_rows() == 0)
1368     {
1369       m_pDS->close();
1370       return false;
1371     }
1372     
1373     while (!m_pDS->eof())
1374     {
1375       songs.push_back(m_pDS->fv("idSong").get_asInt());
1376       m_pDS->next();
1377     }
1378     m_pDS->close();
1379     return true;
1380   }
1381   catch (...)
1382   {
1383     CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idArtist);
1384   }
1385   return false;
1386 };
1387
1388 bool CMusicDatabase::GetArtistsBySong(int idSong, bool includeFeatured, std::vector<int> &artists)
1389 {
1390   try 
1391   {
1392     CStdString strSQL, strPrepSQL;
1393     
1394     strPrepSQL = "select idArtist from song_artist where idSong=%i";
1395     if (includeFeatured == false)
1396       strPrepSQL += " AND boolFeatured = 0";
1397     
1398     strSQL=PrepareSQL(strPrepSQL, idSong);
1399     if (!m_pDS->query(strSQL.c_str())) 
1400       return false;
1401     if (m_pDS->num_rows() == 0)
1402     {
1403       m_pDS->close();
1404       return false;
1405     }
1406
1407     while (!m_pDS->eof())
1408     {
1409       artists.push_back(m_pDS->fv("idArtist").get_asInt());
1410       m_pDS->next();
1411     }
1412     m_pDS->close();
1413     return true;
1414   }
1415   catch (...)
1416   {
1417     CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idSong);
1418   }
1419   return false;
1420 }
1421
1422 bool CMusicDatabase::GetGenresByAlbum(int idAlbum, std::vector<int>& genres)
1423 {
1424   try
1425   {
1426     CStdString strSQL = PrepareSQL("select idGenre from album_genre where idAlbum = %i ORDER BY iOrder ASC", idAlbum);
1427     if (!m_pDS->query(strSQL.c_str()))
1428       return false;
1429     if (m_pDS->num_rows() == 0)
1430     {
1431       m_pDS->close();
1432       return true;
1433     }
1434
1435     while (!m_pDS->eof())
1436     {
1437       genres.push_back(m_pDS->fv("idGenre").get_asInt());
1438       m_pDS->next();
1439     }
1440     m_pDS->close();
1441
1442     return true;
1443   }
1444   catch (...)
1445   {
1446     CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idAlbum);
1447   }
1448   return false;
1449 }
1450
1451 bool CMusicDatabase::GetGenresBySong(int idSong, std::vector<int>& genres)
1452 {
1453   try
1454   {
1455     CStdString strSQL = PrepareSQL("select idGenre from song_genre where idSong = %i ORDER BY iOrder ASC", idSong);
1456     if (!m_pDS->query(strSQL.c_str()))
1457       return false;
1458     if (m_pDS->num_rows() == 0)
1459     {
1460       m_pDS->close();
1461       return true;
1462     }
1463
1464     while (!m_pDS->eof())
1465     {
1466       genres.push_back(m_pDS->fv("idGenre").get_asInt());
1467       m_pDS->next();
1468     }
1469     m_pDS->close();
1470
1471     return true;
1472   }
1473   catch (...)
1474   {
1475     CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idSong);
1476   }
1477   return false;
1478 }
1479
1480 int CMusicDatabase::AddPath(const CStdString& strPath1)
1481 {
1482   CStdString strSQL;
1483   try
1484   {
1485     CStdString strPath(strPath1);
1486     if (!URIUtils::HasSlashAtEnd(strPath))
1487       URIUtils::AddSlashAtEnd(strPath);
1488
1489     if (NULL == m_pDB.get()) return -1;
1490     if (NULL == m_pDS.get()) return -1;
1491
1492     map <CStdString, int>::const_iterator it;
1493
1494     it = m_pathCache.find(strPath);
1495     if (it != m_pathCache.end())
1496       return it->second;
1497
1498     strSQL=PrepareSQL( "select * from path where strPath='%s'", strPath.c_str());
1499     m_pDS->query(strSQL.c_str());
1500     if (m_pDS->num_rows() == 0)
1501     {
1502       m_pDS->close();
1503       // doesnt exists, add it
1504       strSQL=PrepareSQL("insert into path (idPath, strPath) values( NULL, '%s' )", strPath.c_str());
1505       m_pDS->exec(strSQL.c_str());
1506
1507       int idPath = (int)m_pDS->lastinsertid();
1508       m_pathCache.insert(pair<CStdString, int>(strPath, idPath));
1509       return idPath;
1510     }
1511     else
1512     {
1513       int idPath = m_pDS->fv("idPath").get_asInt();
1514       m_pathCache.insert(pair<CStdString, int>(strPath, idPath));
1515       m_pDS->close();
1516       return idPath;
1517     }
1518   }
1519   catch (...)
1520   {
1521     CLog::Log(LOGERROR, "musicdatabase:unable to addpath (%s)", strSQL.c_str());
1522   }
1523
1524   return -1;
1525 }
1526
1527 CSong CMusicDatabase::GetSongFromDataset()
1528 {
1529   return GetSongFromDataset(m_pDS->get_sql_record());
1530 }
1531
1532 CSong CMusicDatabase::GetSongFromDataset(const dbiplus::sql_record* const record, int offset /* = 0 */)
1533 {
1534   CSong song;
1535   song.idSong = record->at(offset + song_idSong).get_asInt();
1536   // get the full artist string
1537   song.artist = StringUtils::Split(record->at(offset + song_strArtists).get_asString(), g_advancedSettings.m_musicItemSeparator);
1538   // and the full genre string
1539   song.genre = StringUtils::Split(record->at(offset + song_strGenres).get_asString(), g_advancedSettings.m_musicItemSeparator);
1540   // and the rest...
1541   song.strAlbum = record->at(offset + song_strAlbum).get_asString();
1542   song.idAlbum = record->at(offset + song_idAlbum).get_asInt();
1543   song.iTrack = record->at(offset + song_iTrack).get_asInt() ;
1544   song.iDuration = record->at(offset + song_iDuration).get_asInt() ;
1545   song.iYear = record->at(offset + song_iYear).get_asInt() ;
1546   song.strTitle = record->at(offset + song_strTitle).get_asString();
1547   song.iTimesPlayed = record->at(offset + song_iTimesPlayed).get_asInt();
1548   song.lastPlayed.SetFromDBDateTime(record->at(offset + song_lastplayed).get_asString());
1549   song.iStartOffset = record->at(offset + song_iStartOffset).get_asInt();
1550   song.iEndOffset = record->at(offset + song_iEndOffset).get_asInt();
1551   song.strMusicBrainzTrackID = record->at(offset + song_strMusicBrainzTrackID).get_asString();
1552   song.rating = record->at(offset + song_rating).get_asChar();
1553   song.strComment = record->at(offset + song_comment).get_asString();
1554   song.iKaraokeNumber = record->at(offset + song_iKarNumber).get_asInt();
1555   song.strKaraokeLyrEncoding = record->at(offset + song_strKarEncoding).get_asString();
1556   song.iKaraokeDelay = record->at(offset + song_iKarDelay).get_asInt();
1557   song.bCompilation = record->at(offset + song_bCompilation).get_asInt() == 1;
1558   song.albumArtist = StringUtils::Split(record->at(offset + song_strAlbumArtists).get_asString(), g_advancedSettings.m_musicItemSeparator);
1559
1560   // Get filename with full path
1561   song.strFileName = URIUtils::AddFileToFolder(record->at(offset + song_strPath).get_asString(), record->at(offset + song_strFileName).get_asString());
1562   return song;
1563 }
1564
1565 void CMusicDatabase::GetFileItemFromDataset(CFileItem* item, const CMusicDbUrl &baseUrl)
1566 {
1567   GetFileItemFromDataset(m_pDS->get_sql_record(), item, baseUrl);
1568 }
1569
1570 void CMusicDatabase::GetFileItemFromDataset(const dbiplus::sql_record* const record, CFileItem* item, const CMusicDbUrl &baseUrl)
1571 {
1572   // get the full artist string
1573   item->GetMusicInfoTag()->SetArtist(StringUtils::Split(record->at(song_strArtists).get_asString(), g_advancedSettings.m_musicItemSeparator));
1574   // and the full genre string
1575   item->GetMusicInfoTag()->SetGenre(record->at(song_strGenres).get_asString());
1576   // and the rest...
1577   item->GetMusicInfoTag()->SetAlbum(record->at(song_strAlbum).get_asString());
1578   item->GetMusicInfoTag()->SetAlbumId(record->at(song_idAlbum).get_asInt());
1579   item->GetMusicInfoTag()->SetTrackAndDiskNumber(record->at(song_iTrack).get_asInt());
1580   item->GetMusicInfoTag()->SetDuration(record->at(song_iDuration).get_asInt());
1581   item->GetMusicInfoTag()->SetDatabaseId(record->at(song_idSong).get_asInt(), "song");
1582   SYSTEMTIME stTime;
1583   stTime.wYear = (WORD)record->at(song_iYear).get_asInt();
1584   item->GetMusicInfoTag()->SetReleaseDate(stTime);
1585   item->GetMusicInfoTag()->SetTitle(record->at(song_strTitle).get_asString());
1586   item->SetLabel(record->at(song_strTitle).get_asString());
1587   item->m_lStartOffset = record->at(song_iStartOffset).get_asInt();
1588   item->SetProperty("item_start", item->m_lStartOffset);
1589   item->m_lEndOffset = record->at(song_iEndOffset).get_asInt();
1590   item->GetMusicInfoTag()->SetMusicBrainzTrackID(record->at(song_strMusicBrainzTrackID).get_asString());
1591   item->GetMusicInfoTag()->SetRating(record->at(song_rating).get_asChar());
1592   item->GetMusicInfoTag()->SetComment(record->at(song_comment).get_asString());
1593   item->GetMusicInfoTag()->SetPlayCount(record->at(song_iTimesPlayed).get_asInt());
1594   item->GetMusicInfoTag()->SetLastPlayed(record->at(song_lastplayed).get_asString());
1595   CStdString strRealPath = URIUtils::AddFileToFolder(record->at(song_strPath).get_asString(), record->at(song_strFileName).get_asString());
1596   item->GetMusicInfoTag()->SetURL(strRealPath);
1597   item->GetMusicInfoTag()->SetCompilation(record->at(song_bCompilation).get_asInt() == 1);
1598   item->GetMusicInfoTag()->SetAlbumArtist(record->at(song_strAlbumArtists).get_asString());
1599   item->GetMusicInfoTag()->SetLoaded(true);
1600   // Get filename with full path
1601   if (!baseUrl.IsValid())
1602     item->SetPath(strRealPath);
1603   else
1604   {
1605     CMusicDbUrl itemUrl = baseUrl;
1606     CStdString strFileName = record->at(song_strFileName).get_asString();
1607     CStdString strExt = URIUtils::GetExtension(strFileName);
1608     CStdString path = StringUtils::Format("%ld%s", record->at(song_idSong).get_asInt(), strExt.c_str());
1609     itemUrl.AppendPath(path);
1610     item->SetPath(itemUrl.ToString());
1611   }
1612 }
1613
1614 CAlbum CMusicDatabase::GetAlbumFromDataset(dbiplus::Dataset* pDS, int offset /* = 0 */, bool imageURL /* = false*/)
1615 {
1616   return GetAlbumFromDataset(pDS->get_sql_record(), offset, imageURL);
1617 }
1618
1619 CAlbum CMusicDatabase::GetAlbumFromDataset(const dbiplus::sql_record* const record, int offset /* = 0 */, bool imageURL /* = false*/)
1620 {
1621   CAlbum album;
1622   album.idAlbum = record->at(offset + album_idAlbum).get_asInt();
1623   album.strAlbum = record->at(offset + album_strAlbum).get_asString();
1624   if (album.strAlbum.empty())
1625     album.strAlbum = g_localizeStrings.Get(1050);
1626   album.strMusicBrainzAlbumID = record->at(offset + album_strMusicBrainzAlbumID).get_asString();
1627   album.artist = StringUtils::Split(record->at(offset + album_strArtists).get_asString(), g_advancedSettings.m_musicItemSeparator);
1628   album.genre = StringUtils::Split(record->at(offset + album_strGenres).get_asString(), g_advancedSettings.m_musicItemSeparator);
1629   album.iYear = record->at(offset + album_iYear).get_asInt();
1630   if (imageURL)
1631     album.thumbURL.ParseString(record->at(offset + album_strThumbURL).get_asString());
1632   album.iRating = record->at(offset + album_iRating).get_asInt();
1633   album.iYear = record->at(offset + album_iYear).get_asInt();
1634   album.strReview = record->at(offset + album_strReview).get_asString();
1635   album.styles = StringUtils::Split(record->at(offset + album_strStyles).get_asString(), g_advancedSettings.m_musicItemSeparator);
1636   album.moods = StringUtils::Split(record->at(offset + album_strMoods).get_asString(), g_advancedSettings.m_musicItemSeparator);
1637   album.themes = StringUtils::Split(record->at(offset + album_strThemes).get_asString(), g_advancedSettings.m_musicItemSeparator);
1638   album.strLabel = record->at(offset + album_strLabel).get_asString();
1639   album.strType = record->at(offset + album_strType).get_asString();
1640   album.bCompilation = record->at(offset + album_bCompilation).get_asInt() == 1;
1641   album.iTimesPlayed = record->at(offset + album_iTimesPlayed).get_asInt();
1642   return album;
1643 }
1644
1645 CArtistCredit CMusicDatabase::GetArtistCreditFromDataset(const dbiplus::sql_record* const record, int offset /* = 0 */)
1646 {
1647   CArtistCredit artistCredit;
1648   artistCredit.idArtist = record->at(offset + artistCredit_idArtist).get_asInt();
1649   artistCredit.m_strArtist = record->at(offset + artistCredit_strArtist).get_asString();
1650   artistCredit.m_strMusicBrainzArtistID = record->at(offset + artistCredit_strMusicBrainzArtistID).get_asString();
1651   artistCredit.m_boolFeatured = record->at(offset + artistCredit_bFeatured).get_asBool();
1652   artistCredit.m_strJoinPhrase = record->at(offset + artistCredit_strJoinPhrase).get_asString();
1653   return artistCredit;
1654 }
1655
1656 CArtist CMusicDatabase::GetArtistFromDataset(dbiplus::Dataset* pDS, int offset /* = 0 */, bool needThumb /* = true */)
1657 {
1658   return GetArtistFromDataset(pDS->get_sql_record(), offset, needThumb);
1659 }
1660
1661 CArtist CMusicDatabase::GetArtistFromDataset(const dbiplus::sql_record* const record, int offset /* = 0 */, bool needThumb /* = true */)
1662 {
1663   CArtist artist;
1664   artist.idArtist = record->at(offset + artist_idArtist).get_asInt();
1665   artist.strArtist = record->at(offset + artist_strArtist).get_asString();
1666   artist.strMusicBrainzArtistID = record->at(offset + artist_strMusicBrainzArtistID).get_asString();
1667   artist.genre = StringUtils::Split(record->at(offset + artist_strGenres).get_asString(), g_advancedSettings.m_musicItemSeparator);
1668   artist.strBiography = record->at(offset + artist_strBiography).get_asString();
1669   artist.styles = StringUtils::Split(record->at(offset + artist_strStyles).get_asString(), g_advancedSettings.m_musicItemSeparator);
1670   artist.moods = StringUtils::Split(record->at(offset + artist_strMoods).get_asString(), g_advancedSettings.m_musicItemSeparator);
1671   artist.strBorn = record->at(offset + artist_strBorn).get_asString();
1672   artist.strFormed = record->at(offset + artist_strFormed).get_asString();
1673   artist.strDied = record->at(offset + artist_strDied).get_asString();
1674   artist.strDisbanded = record->at(offset + artist_strDisbanded).get_asString();
1675   artist.yearsActive = StringUtils::Split(record->at(offset + artist_strYearsActive).get_asString(), g_advancedSettings.m_musicItemSeparator);
1676   artist.instruments = StringUtils::Split(record->at(offset + artist_strInstruments).get_asString(), g_advancedSettings.m_musicItemSeparator);
1677
1678   if (needThumb)
1679   {
1680     artist.fanart.m_xml = record->at(artist_strFanart).get_asString();
1681     artist.fanart.Unpack();
1682     artist.thumbURL.ParseString(record->at(artist_strImage).get_asString());
1683   }
1684
1685   return artist;
1686 }
1687
1688 CSong CMusicDatabase::GetAlbumInfoSongFromDataset(const dbiplus::sql_record* const record, int offset /* = 0 */)
1689 {
1690   CSong song;
1691   song.iTrack = record->at(offset + albumInfoSong_iTrack).get_asInt();
1692   song.iDuration = record->at(offset + albumInfoSong_iDuration).get_asInt();
1693   song.strTitle = record->at(offset + albumInfoSong_strTitle).get_asString();
1694   return song;
1695 }
1696
1697 bool CMusicDatabase::GetSongByFileName(const CStdString& strFileName, CSong& song, int startOffset)
1698 {
1699   song.Clear();
1700   CURL url(strFileName);
1701
1702   if (url.GetProtocol()=="musicdb")
1703   {
1704     CStdString strFile = URIUtils::GetFileName(strFileName);
1705     URIUtils::RemoveExtension(strFile);
1706     return GetSong(atol(strFile.c_str()), song);
1707   }
1708
1709   CStdString strPath = URIUtils::GetDirectory(strFileName);
1710   URIUtils::AddSlashAtEnd(strPath);
1711
1712   if (NULL == m_pDB.get()) return false;
1713   if (NULL == m_pDS.get()) return false;
1714
1715   DWORD crc = ComputeCRC(strFileName);
1716
1717   CStdString strSQL = PrepareSQL("select idSong from songview "
1718                                  "where dwFileNameCRC='%ul' and strPath='%s'",
1719                                  crc, strPath.c_str());
1720   if (startOffset)
1721     strSQL += PrepareSQL(" AND iStartOffset=%i", startOffset);
1722
1723   int idSong = (int)strtol(GetSingleValue(strSQL).c_str(), NULL, 10);
1724   if (idSong > 0)
1725     return GetSong(idSong, song);
1726
1727   return false;
1728 }
1729
1730 int CMusicDatabase::GetAlbumIdByPath(const CStdString& strPath)
1731 {
1732   try
1733   {
1734     CStdString strSQL=PrepareSQL("select distinct idAlbum from song join path on song.idPath = path.idPath where path.strPath='%s'", strPath.c_str());
1735     m_pDS->query(strSQL.c_str());
1736     if (m_pDS->eof())
1737       return -1;
1738
1739     int idAlbum = m_pDS->fv(0).get_asInt();
1740     m_pDS->close();
1741
1742     return idAlbum;
1743   }
1744   catch (...)
1745   {
1746     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strPath.c_str());
1747   }
1748
1749   return false;
1750 }
1751
1752 int CMusicDatabase::GetSongByArtistAndAlbumAndTitle(const CStdString& strArtist, const CStdString& strAlbum, const CStdString& strTitle)
1753 {
1754   try
1755   {
1756     CStdString strSQL=PrepareSQL("select idSong from songview "
1757                                 "where strArtists like '%s' and strAlbum like '%s' and "
1758                                 "strTitle like '%s'",strArtist.c_str(),strAlbum.c_str(),strTitle.c_str());
1759
1760     if (!m_pDS->query(strSQL.c_str())) return false;
1761     int iRowsFound = m_pDS->num_rows();
1762     if (iRowsFound == 0)
1763     {
1764       m_pDS->close();
1765       return -1;
1766     }
1767     int lResult = m_pDS->fv(0).get_asInt();
1768     m_pDS->close(); // cleanup recordset data
1769     return lResult;
1770   }
1771   catch (...)
1772   {
1773     CLog::Log(LOGERROR, "%s (%s,%s,%s) failed", __FUNCTION__, strArtist.c_str(),strAlbum.c_str(),strTitle.c_str());
1774   }
1775
1776   return -1;
1777 }
1778
1779 bool CMusicDatabase::SearchArtists(const CStdString& search, CFileItemList &artists)
1780 {
1781   try
1782   {
1783     if (NULL == m_pDB.get()) return false;
1784     if (NULL == m_pDS.get()) return false;
1785
1786     CStdString strVariousArtists = g_localizeStrings.Get(340).c_str();
1787     CStdString strSQL;
1788     if (search.size() >= MIN_FULL_SEARCH_LENGTH)
1789       strSQL=PrepareSQL("select * from artist "
1790                                 "where (strArtist like '%s%%' or strArtist like '%% %s%%') and strArtist <> '%s' "
1791                                 , search.c_str(), search.c_str(), strVariousArtists.c_str() );
1792     else
1793       strSQL=PrepareSQL("select * from artist "
1794                                 "where strArtist like '%s%%' and strArtist <> '%s' "
1795                                 , search.c_str(), strVariousArtists.c_str() );
1796
1797     if (!m_pDS->query(strSQL.c_str())) return false;
1798     if (m_pDS->num_rows() == 0)
1799     {
1800       m_pDS->close();
1801       return false;
1802     }
1803
1804     CStdString artistLabel(g_localizeStrings.Get(557)); // Artist
1805     while (!m_pDS->eof())
1806     {
1807       CStdString path = StringUtils::Format("musicdb://artists/%ld/", m_pDS->fv(0).get_asInt());
1808       CFileItemPtr pItem(new CFileItem(path, true));
1809       CStdString label = StringUtils::Format("[%s] %s", artistLabel.c_str(), m_pDS->fv(1).get_asString().c_str());
1810       pItem->SetLabel(label);
1811       label = StringUtils::Format("A %s", m_pDS->fv(1).get_asString().c_str()); // sort label is stored in the title tag
1812       pItem->GetMusicInfoTag()->SetTitle(label);
1813       pItem->GetMusicInfoTag()->SetDatabaseId(m_pDS->fv(0).get_asInt(), "artist");
1814       artists.Add(pItem);
1815       m_pDS->next();
1816     }
1817
1818     m_pDS->close(); // cleanup recordset data
1819     return true;
1820   }
1821   catch (...)
1822   {
1823     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1824   }
1825
1826   return false;
1827 }
1828
1829 bool CMusicDatabase::GetTop100(const CStdString& strBaseDir, CFileItemList& items)
1830 {
1831   try
1832   {
1833     if (NULL == m_pDB.get()) return false;
1834     if (NULL == m_pDS.get()) return false;
1835
1836     CMusicDbUrl baseUrl;
1837     if (!strBaseDir.empty() && !baseUrl.FromString(strBaseDir))
1838       return false;
1839
1840     CStdString strSQL="select * from songview "
1841                       "where iTimesPlayed>0 "
1842                       "order by iTimesPlayed desc "
1843                       "limit 100";
1844
1845     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
1846     if (!m_pDS->query(strSQL.c_str())) return false;
1847     int iRowsFound = m_pDS->num_rows();
1848     if (iRowsFound == 0)
1849     {
1850       m_pDS->close();
1851       return true;
1852     }
1853     items.Reserve(iRowsFound);
1854     while (!m_pDS->eof())
1855     {
1856       CFileItemPtr item(new CFileItem);
1857       GetFileItemFromDataset(item.get(), baseUrl);
1858       items.Add(item);
1859       m_pDS->next();
1860     }
1861
1862     m_pDS->close(); // cleanup recordset data
1863     return true;
1864   }
1865   catch (...)
1866   {
1867     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1868   }
1869
1870   return false;
1871 }
1872
1873 bool CMusicDatabase::GetTop100Albums(VECALBUMS& albums)
1874 {
1875   try
1876   {
1877     albums.erase(albums.begin(), albums.end());
1878     if (NULL == m_pDB.get()) return false;
1879     if (NULL == m_pDS.get()) return false;
1880
1881     // NOTE: The song.idAlbum is needed for the group by, as for some reason group by albumview.idAlbum doesn't work
1882     //       consistently - possibly an SQLite bug, as it works fine in SQLiteSpy (v3.3.17)
1883     CStdString strSQL = "select albumview.* from albumview "
1884                     "where albumview.iTimesPlayed>0 and albumview.strAlbum != '' "
1885                     "order by albumview.iTimesPlayed desc "
1886                     "limit 100 ";
1887
1888     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
1889     if (!m_pDS->query(strSQL.c_str())) return false;
1890     int iRowsFound = m_pDS->num_rows();
1891     if (iRowsFound == 0)
1892     {
1893       m_pDS->close();
1894       return true;
1895     }
1896     while (!m_pDS->eof())
1897     {
1898       albums.push_back(GetAlbumFromDataset(m_pDS.get()));
1899       m_pDS->next();
1900     }
1901
1902     m_pDS->close(); // cleanup recordset data
1903     return true;
1904   }
1905   catch (...)
1906   {
1907     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1908   }
1909
1910   return false;
1911 }
1912
1913 bool CMusicDatabase::GetTop100AlbumSongs(const CStdString& strBaseDir, CFileItemList& items)
1914 {
1915   try
1916   {
1917     if (NULL == m_pDB.get()) return false;
1918     if (NULL == m_pDS.get()) return false;
1919
1920     CMusicDbUrl baseUrl;
1921     if (!strBaseDir.empty() && baseUrl.FromString(strBaseDir))
1922       return false;
1923
1924     CStdString strSQL = StringUtils::Format("select * from songview join albumview on (songview.idAlbum = albumview.idAlbum) where albumview.idAlbum in (select song.idAlbum from song where song.iTimesPlayed>0 group by idAlbum order by sum(song.iTimesPlayed) desc limit 100) order by albumview.idAlbum in (select song.idAlbum from song where song.iTimesPlayed>0 group by idAlbum order by sum(song.iTimesPlayed) desc limit 100)");
1925     CLog::Log(LOGDEBUG,"GetTop100AlbumSongs() query: %s", strSQL.c_str());
1926     if (!m_pDS->query(strSQL.c_str())) return false;
1927
1928     int iRowsFound = m_pDS->num_rows();
1929     if (iRowsFound == 0)
1930     {
1931       m_pDS->close();
1932       return true;
1933     }
1934
1935     // get data from returned rows
1936     items.Reserve(iRowsFound);
1937     while (!m_pDS->eof())
1938     {
1939       CFileItemPtr item(new CFileItem);
1940       GetFileItemFromDataset(item.get(), baseUrl);
1941       items.Add(item);
1942       m_pDS->next();
1943     }
1944
1945     // cleanup
1946     m_pDS->close();
1947     return true;
1948   }
1949   catch (...)
1950   {
1951     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1952   }
1953   return false;
1954 }
1955
1956 bool CMusicDatabase::GetRecentlyPlayedAlbums(VECALBUMS& albums)
1957 {
1958   try
1959   {
1960     albums.erase(albums.begin(), albums.end());
1961     if (NULL == m_pDB.get()) return false;
1962     if (NULL == m_pDS.get()) return false;
1963
1964     CStdString strSQL = StringUtils::Format("select distinct albumview.* from song join albumview on albumview.idAlbum=song.idAlbum where song.lastplayed IS NOT NULL order by song.lastplayed desc limit %i", RECENTLY_PLAYED_LIMIT);
1965     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
1966     if (!m_pDS->query(strSQL.c_str())) return false;
1967     int iRowsFound = m_pDS->num_rows();
1968     if (iRowsFound == 0)
1969     {
1970       m_pDS->close();
1971       return true;
1972     }
1973     while (!m_pDS->eof())
1974     {
1975       albums.push_back(GetAlbumFromDataset(m_pDS.get()));
1976       m_pDS->next();
1977     }
1978
1979     m_pDS->close(); // cleanup recordset data
1980     return true;
1981   }
1982   catch (...)
1983   {
1984     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1985   }
1986
1987   return false;
1988 }
1989
1990 bool CMusicDatabase::GetRecentlyPlayedAlbumSongs(const CStdString& strBaseDir, CFileItemList& items)
1991 {
1992   try
1993   {
1994     if (NULL == m_pDB.get()) return false;
1995     if (NULL == m_pDS.get()) return false;
1996
1997     CMusicDbUrl baseUrl;
1998     if (!strBaseDir.empty() && !baseUrl.FromString(strBaseDir))
1999       return false;
2000
2001     CStdString strSQL = StringUtils::Format("select * from songview join albumview on (songview.idAlbum = albumview.idAlbum) where albumview.idAlbum in (select distinct albumview.idAlbum from albumview join song on albumview.idAlbum=song.idAlbum where song.lastplayed IS NOT NULL order by song.lastplayed desc limit %i)", g_advancedSettings.m_iMusicLibraryRecentlyAddedItems);
2002     CLog::Log(LOGDEBUG,"GetRecentlyPlayedAlbumSongs() query: %s", strSQL.c_str());
2003     if (!m_pDS->query(strSQL.c_str())) return false;
2004
2005     int iRowsFound = m_pDS->num_rows();
2006     if (iRowsFound == 0)
2007     {
2008       m_pDS->close();
2009       return true;
2010     }
2011
2012     // get data from returned rows
2013     items.Reserve(iRowsFound);
2014     while (!m_pDS->eof())
2015     {
2016       CFileItemPtr item(new CFileItem);
2017       GetFileItemFromDataset(item.get(), baseUrl);
2018       items.Add(item);
2019       m_pDS->next();
2020     }
2021
2022     // cleanup
2023     m_pDS->close();
2024     return true;
2025   }
2026   catch (...)
2027   {
2028     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2029   }
2030   return false;
2031 }
2032
2033 bool CMusicDatabase::GetRecentlyAddedAlbums(VECALBUMS& albums, unsigned int limit)
2034 {
2035   try
2036   {
2037     albums.erase(albums.begin(), albums.end());
2038     if (NULL == m_pDB.get()) return false;
2039     if (NULL == m_pDS.get()) return false;
2040
2041     CStdString strSQL = StringUtils::Format("select * from albumview where strAlbum != '' order by idAlbum desc limit %u", limit ? limit : g_advancedSettings.m_iMusicLibraryRecentlyAddedItems);
2042
2043     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
2044     if (!m_pDS->query(strSQL.c_str())) return false;
2045     int iRowsFound = m_pDS->num_rows();
2046     if (iRowsFound == 0)
2047     {
2048       m_pDS->close();
2049       return true;
2050     }
2051
2052     while (!m_pDS->eof())
2053     {
2054       albums.push_back(GetAlbumFromDataset(m_pDS.get()));
2055       m_pDS->next();
2056     }
2057
2058     m_pDS->close(); // cleanup recordset data
2059     return true;
2060   }
2061   catch (...)
2062   {
2063     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2064   }
2065
2066   return false;
2067 }
2068
2069 bool CMusicDatabase::GetRecentlyAddedAlbumSongs(const CStdString& strBaseDir, CFileItemList& items, unsigned int limit)
2070 {
2071   try
2072   {
2073     if (NULL == m_pDB.get()) return false;
2074     if (NULL == m_pDS.get()) return false;
2075
2076     CMusicDbUrl baseUrl;
2077     if (!strBaseDir.empty() && !baseUrl.FromString(strBaseDir))
2078       return false;
2079
2080     CStdString strSQL;
2081     strSQL = PrepareSQL("SELECT songview.* FROM (SELECT idAlbum FROM albumview ORDER BY idAlbum DESC LIMIT %u) AS recentalbums JOIN songview ON songview.idAlbum=recentalbums.idAlbum", limit ? limit : g_advancedSettings.m_iMusicLibraryRecentlyAddedItems);
2082     CLog::Log(LOGDEBUG,"GetRecentlyAddedAlbumSongs() query: %s", strSQL.c_str());
2083     if (!m_pDS->query(strSQL.c_str())) return false;
2084
2085     int iRowsFound = m_pDS->num_rows();
2086     if (iRowsFound == 0)
2087     {
2088       m_pDS->close();
2089       return true;
2090     }
2091
2092     // get data from returned rows
2093     items.Reserve(iRowsFound);
2094     while (!m_pDS->eof())
2095     {
2096       CFileItemPtr item(new CFileItem);
2097       GetFileItemFromDataset(item.get(), baseUrl);
2098       items.Add(item);
2099       m_pDS->next();
2100     }
2101
2102     // cleanup
2103     m_pDS->close();
2104     return true;
2105   }
2106   catch (...)
2107   {
2108     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2109   }
2110   return false;
2111 }
2112
2113 void CMusicDatabase::IncrementPlayCount(const CFileItem& item)
2114 {
2115   try
2116   {
2117     if (NULL == m_pDB.get()) return;
2118     if (NULL == m_pDS.get()) return;
2119
2120     int idSong = GetSongIDFromPath(item.GetPath());
2121
2122     CStdString sql=PrepareSQL("UPDATE song SET iTimesPlayed=iTimesPlayed+1, lastplayed=CURRENT_TIMESTAMP where idSong=%i", idSong);
2123     m_pDS->exec(sql.c_str());
2124   }
2125   catch (...)
2126   {
2127     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, item.GetPath().c_str());
2128   }
2129 }
2130
2131 bool CMusicDatabase::GetSongsByPath(const CStdString& strPath1, MAPSONGS& songs, bool bAppendToMap)
2132 {
2133   CStdString strPath(strPath1);
2134   try
2135   {
2136     if (!URIUtils::HasSlashAtEnd(strPath))
2137       URIUtils::AddSlashAtEnd(strPath);
2138
2139     if (!bAppendToMap)
2140       songs.clear();
2141
2142     if (NULL == m_pDB.get()) return false;
2143     if (NULL == m_pDS.get()) return false;
2144
2145     CStdString strSQL=PrepareSQL("select * from songview where strPath='%s'", strPath.c_str() );
2146     if (!m_pDS->query(strSQL.c_str())) return false;
2147     int iRowsFound = m_pDS->num_rows();
2148     if (iRowsFound == 0)
2149     {
2150       m_pDS->close();
2151       return false;
2152     }
2153     while (!m_pDS->eof())
2154     {
2155       CSong song = GetSongFromDataset();
2156       songs.insert(make_pair(song.strFileName, song));
2157       m_pDS->next();
2158     }
2159
2160     m_pDS->close(); // cleanup recordset data
2161     return true;
2162   }
2163   catch (...)
2164   {
2165     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strPath.c_str());
2166   }
2167
2168   return false;
2169 }
2170
2171 void CMusicDatabase::EmptyCache()
2172 {
2173   m_artistCache.erase(m_artistCache.begin(), m_artistCache.end());
2174   m_genreCache.erase(m_genreCache.begin(), m_genreCache.end());
2175   m_pathCache.erase(m_pathCache.begin(), m_pathCache.end());
2176   m_albumCache.erase(m_albumCache.begin(), m_albumCache.end());
2177   m_thumbCache.erase(m_thumbCache.begin(), m_thumbCache.end());
2178 }
2179
2180 bool CMusicDatabase::Search(const CStdString& search, CFileItemList &items)
2181 {
2182   unsigned int time = XbmcThreads::SystemClockMillis();
2183   // first grab all the artists that match
2184   SearchArtists(search, items);
2185   CLog::Log(LOGDEBUG, "%s Artist search in %i ms",
2186             __FUNCTION__, XbmcThreads::SystemClockMillis() - time); time = XbmcThreads::SystemClockMillis();
2187
2188   // then albums that match
2189   SearchAlbums(search, items);
2190   CLog::Log(LOGDEBUG, "%s Album search in %i ms",
2191             __FUNCTION__, XbmcThreads::SystemClockMillis() - time); time = XbmcThreads::SystemClockMillis();
2192
2193   // and finally songs
2194   SearchSongs(search, items);
2195   CLog::Log(LOGDEBUG, "%s Songs search in %i ms",
2196             __FUNCTION__, XbmcThreads::SystemClockMillis() - time); time = XbmcThreads::SystemClockMillis();
2197   return true;
2198 }
2199
2200 bool CMusicDatabase::SearchSongs(const CStdString& search, CFileItemList &items)
2201 {
2202   try
2203   {
2204     if (NULL == m_pDB.get()) return false;
2205     if (NULL == m_pDS.get()) return false;
2206
2207     CMusicDbUrl baseUrl;
2208     if (!baseUrl.FromString("musicdb://songs/"))
2209       return false;
2210
2211     CStdString strSQL;
2212     if (search.size() >= MIN_FULL_SEARCH_LENGTH)
2213       strSQL=PrepareSQL("select * from songview where strTitle like '%s%%' or strTitle like '%% %s%%' limit 1000", search.c_str(), search.c_str());
2214     else
2215       strSQL=PrepareSQL("select * from songview where strTitle like '%s%%' limit 1000", search.c_str());
2216
2217     if (!m_pDS->query(strSQL.c_str())) return false;
2218     if (m_pDS->num_rows() == 0) return false;
2219
2220     CStdString songLabel = g_localizeStrings.Get(179); // Song
2221     while (!m_pDS->eof())
2222     {
2223       CFileItemPtr item(new CFileItem);
2224       GetFileItemFromDataset(item.get(), baseUrl);
2225       items.Add(item);
2226       m_pDS->next();
2227     }
2228
2229     m_pDS->close();
2230     return true;
2231   }
2232   catch (...)
2233   {
2234     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2235   }
2236
2237   return false;
2238 }
2239
2240 bool CMusicDatabase::SearchAlbums(const CStdString& search, CFileItemList &albums)
2241 {
2242   try
2243   {
2244     if (NULL == m_pDB.get()) return false;
2245     if (NULL == m_pDS.get()) return false;
2246
2247     CStdString strSQL;
2248     if (search.size() >= MIN_FULL_SEARCH_LENGTH)
2249       strSQL=PrepareSQL("select * from albumview where strAlbum like '%s%%' or strAlbum like '%% %s%%'", search.c_str(), search.c_str());
2250     else
2251       strSQL=PrepareSQL("select * from albumview where strAlbum like '%s%%'", search.c_str());
2252
2253     if (!m_pDS->query(strSQL.c_str())) return false;
2254
2255     CStdString albumLabel(g_localizeStrings.Get(558)); // Album
2256     while (!m_pDS->eof())
2257     {
2258       CAlbum album = GetAlbumFromDataset(m_pDS.get());
2259       CStdString path = StringUtils::Format("musicdb://albums/%ld/", album.idAlbum);
2260       CFileItemPtr pItem(new CFileItem(path, album));
2261       CStdString label = StringUtils::Format("[%s] %s", albumLabel.c_str(), album.strAlbum.c_str());
2262       pItem->SetLabel(label);
2263       label = StringUtils::Format("B %s", album.strAlbum.c_str()); // sort label is stored in the title tag
2264       pItem->GetMusicInfoTag()->SetTitle(label);
2265       albums.Add(pItem);
2266       m_pDS->next();
2267     }
2268     m_pDS->close(); // cleanup recordset data
2269     return true;
2270   }
2271   catch (...)
2272   {
2273     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2274   }
2275   return false;
2276 }
2277
2278 bool CMusicDatabase::CleanupSongsByIds(const CStdString &strSongIds)
2279 {
2280   try
2281   {
2282     if (NULL == m_pDB.get()) return false;
2283     if (NULL == m_pDS.get()) return false;
2284     // ok, now find all idSong's
2285     CStdString strSQL=PrepareSQL("select * from song join path on song.idPath = path.idPath where song.idSong in %s", strSongIds.c_str());
2286     if (!m_pDS->query(strSQL.c_str())) return false;
2287     int iRowsFound = m_pDS->num_rows();
2288     if (iRowsFound == 0)
2289     {
2290       m_pDS->close();
2291       return true;
2292     }
2293     vector<std::string> songsToDelete;
2294     while (!m_pDS->eof())
2295     { // get the full song path
2296       CStdString strFileName = URIUtils::AddFileToFolder(m_pDS->fv("path.strPath").get_asString(), m_pDS->fv("song.strFileName").get_asString());
2297
2298       //  Special case for streams inside an ogg file. (oggstream)
2299       //  The last dir in the path is the ogg file that
2300       //  contains the stream, so test if its there
2301       if (URIUtils::HasExtension(strFileName, ".oggstream|.nsfstream"))
2302       {
2303         strFileName = URIUtils::GetDirectory(strFileName);
2304         // we are dropping back to a file, so remove the slash at end
2305         URIUtils::RemoveSlashAtEnd(strFileName);
2306       }
2307
2308       if (!CFile::Exists(strFileName))
2309       { // file no longer exists, so add to deletion list
2310         songsToDelete.push_back(m_pDS->fv("song.idSong").get_asString());
2311       }
2312       m_pDS->next();
2313     }
2314     m_pDS->close();
2315
2316     if (!songsToDelete.empty())
2317     {
2318       std::string strSongsToDelete = "(" + StringUtils::Join(songsToDelete, ",") + ")";
2319       // ok, now delete these songs + all references to them from the linked tables
2320       strSQL = "delete from song where idSong in " + strSongsToDelete;
2321       m_pDS->exec(strSQL.c_str());
2322       m_pDS->close();
2323     }
2324     return true;
2325   }
2326   catch (...)
2327   {
2328     CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupSongsFromPaths()");
2329   }
2330   return false;
2331 }
2332
2333 bool CMusicDatabase::CleanupSongs()
2334 {
2335   try
2336   {
2337     // run through all songs and get all unique path ids
2338     int iLIMIT = 1000;
2339     for (int i=0;;i+=iLIMIT)
2340     {
2341       CStdString strSQL=PrepareSQL("select song.idSong from song order by song.idSong limit %i offset %i",iLIMIT,i);
2342       if (!m_pDS->query(strSQL.c_str())) return false;
2343       int iRowsFound = m_pDS->num_rows();
2344       // keep going until no rows are left!
2345       if (iRowsFound == 0)
2346       {
2347         m_pDS->close();
2348         return true;
2349       }
2350
2351       std::vector<std::string> songIds;
2352       while (!m_pDS->eof())
2353       {
2354         songIds.push_back(m_pDS->fv("song.idSong").get_asString());
2355         m_pDS->next();
2356       }
2357       m_pDS->close();
2358       std::string strSongIds = "(" + StringUtils::Join(songIds, ",") + ")";
2359       CLog::Log(LOGDEBUG,"Checking songs from song ID list: %s",strSongIds.c_str());
2360       if (!CleanupSongsByIds(strSongIds)) return false;
2361     }
2362     return true;
2363   }
2364   catch(...)
2365   {
2366     CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupSongs()");
2367   }
2368   return false;
2369 }
2370
2371 bool CMusicDatabase::CleanupAlbums()
2372 {
2373   try
2374   {
2375     // This must be run AFTER songs have been cleaned up
2376     // delete albums with no reference to songs
2377     CStdString strSQL = "select * from album where album.idAlbum not in (select idAlbum from song)";
2378     if (!m_pDS->query(strSQL.c_str())) return false;
2379     int iRowsFound = m_pDS->num_rows();
2380     if (iRowsFound == 0)
2381     {
2382       m_pDS->close();
2383       return true;
2384     }
2385
2386     std::vector<std::string> albumIds;
2387     while (!m_pDS->eof())
2388     {
2389       albumIds.push_back(m_pDS->fv("album.idAlbum").get_asString());
2390       m_pDS->next();
2391     }
2392     m_pDS->close();
2393
2394     std::string strAlbumIds = "(" + StringUtils::Join(albumIds, ",") + ")";
2395     // ok, now we can delete them and the references in the linked tables
2396     strSQL = "delete from album where idAlbum in " + strAlbumIds;
2397     m_pDS->exec(strSQL.c_str());
2398     return true;
2399   }
2400   catch (...)
2401   {
2402     CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupAlbums()");
2403   }
2404   return false;
2405 }
2406
2407 bool CMusicDatabase::CleanupPaths()
2408 {
2409   try
2410   {
2411     // needs to be done AFTER the songs and albums have been cleaned up.
2412     // we can happily delete any path that has no reference to a song
2413     // but we must keep all paths that have been scanned that may contain songs in subpaths
2414
2415     // first create a temporary table of song paths
2416     m_pDS->exec("CREATE TEMPORARY TABLE songpaths (idPath integer, strPath varchar(512))\n");
2417     m_pDS->exec("INSERT INTO songpaths select idPath,strPath from path where idPath in (select idPath from song)\n");
2418
2419     // grab all paths that aren't immediately connected with a song
2420     CStdString sql = "select * from path where idPath not in (select idPath from song)";
2421     if (!m_pDS->query(sql.c_str())) return false;
2422     int iRowsFound = m_pDS->num_rows();
2423     if (iRowsFound == 0)
2424     {
2425       m_pDS->close();
2426       return true;
2427     }
2428     // and construct a list to delete
2429     std::vector<std::string> pathIds;
2430     while (!m_pDS->eof())
2431     {
2432       // anything that isn't a parent path of a song path is to be deleted
2433       CStdString path = m_pDS->fv("strPath").get_asString();
2434       CStdString sql = PrepareSQL("select count(idPath) from songpaths where SUBSTR(strPath,1,%i)='%s'", StringUtils::utf8_strlen(path.c_str()), path.c_str());
2435       if (m_pDS2->query(sql.c_str()) && m_pDS2->num_rows() == 1 && m_pDS2->fv(0).get_asInt() == 0)
2436         pathIds.push_back(m_pDS->fv("idPath").get_asString()); // nothing found, so delete
2437       m_pDS2->close();
2438       m_pDS->next();
2439     }
2440     m_pDS->close();
2441
2442     if (!pathIds.empty())
2443     {
2444       // do the deletion, and drop our temp table
2445       std::string deleteSQL = "DELETE FROM path WHERE idPath IN (" + StringUtils::Join(pathIds, ",") + ")";
2446       m_pDS->exec(deleteSQL.c_str());
2447     }
2448     m_pDS->exec("drop table songpaths");
2449     return true;
2450   }
2451   catch (...)
2452   {
2453     CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupPaths() or was aborted");
2454   }
2455   return false;
2456 }
2457
2458 bool CMusicDatabase::InsideScannedPath(const CStdString& path)
2459 {
2460   CStdString sql = PrepareSQL("select idPath from path where SUBSTR(strPath,1,%i)='%s' LIMIT 1", path.size(), path.c_str());
2461   return !GetSingleValue(sql).empty();
2462 }
2463
2464 bool CMusicDatabase::CleanupArtists()
2465 {
2466   try
2467   {
2468     // (nested queries by Bobbin007)
2469     // must be executed AFTER the song, album and their artist link tables are cleaned.
2470     // don't delete the "Various Artists" string
2471
2472     // Create temp table to avoid 1442 trigger hell on mysql
2473     m_pDS->exec("CREATE TEMPORARY TABLE tmp_delartists (idArtist integer)");
2474     m_pDS->exec("INSERT INTO tmp_delartists select idArtist from song_artist");
2475     m_pDS->exec("INSERT INTO tmp_delartists select idArtist from album_artist");
2476     m_pDS->exec("delete from artist where idArtist not in (select idArtist from tmp_delartists)");
2477     m_pDS->exec("DROP TABLE tmp_delartists");
2478     return true;
2479   }
2480   catch (...)
2481   {
2482     CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupArtists() or was aborted");
2483   }
2484   return false;
2485 }
2486
2487 bool CMusicDatabase::CleanupGenres()
2488 {
2489   try
2490   {
2491     // Cleanup orphaned genres (ie those that don't belong to a song or an album entry)
2492     // (nested queries by Bobbin007)
2493     // Must be executed AFTER the song, song_genre, album and album_genre tables have been cleaned.
2494     CStdString strSQL = "delete from genre where idGenre not in (select idGenre from song_genre) and";
2495     strSQL += " idGenre not in (select idGenre from album_genre)";
2496     m_pDS->exec(strSQL.c_str());
2497     return true;
2498   }
2499   catch (...)
2500   {
2501     CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupGenres() or was aborted");
2502   }
2503   return false;
2504 }
2505
2506 bool CMusicDatabase::CleanupOrphanedItems()
2507 {
2508   // paths aren't cleaned up here - they're cleaned up in RemoveSongsFromPath()
2509   if (NULL == m_pDB.get()) return false;
2510   if (NULL == m_pDS.get()) return false;
2511   if (!CleanupAlbums()) return false;
2512   if (!CleanupArtists()) return false;
2513   if (!CleanupGenres()) return false;
2514   return true;
2515 }
2516
2517 int CMusicDatabase::Cleanup(CGUIDialogProgress *pDlgProgress)
2518 {
2519   if (NULL == m_pDB.get()) return ERROR_DATABASE;
2520   if (NULL == m_pDS.get()) return ERROR_DATABASE;
2521
2522   int ret = ERROR_OK;
2523   unsigned int time = XbmcThreads::SystemClockMillis();
2524   CLog::Log(LOGNOTICE, "%s: Starting musicdatabase cleanup ..", __FUNCTION__);
2525   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnCleanStarted");
2526
2527   // first cleanup any songs with invalid paths
2528   if (pDlgProgress)
2529   {
2530     pDlgProgress->SetHeading(700);
2531     pDlgProgress->SetLine(0, "");
2532     pDlgProgress->SetLine(1, 318);
2533     pDlgProgress->SetLine(2, 330);
2534     pDlgProgress->SetPercentage(0);
2535     pDlgProgress->StartModal();
2536     pDlgProgress->ShowProgressBar(true);
2537   }
2538   if (!CleanupSongs())
2539   {
2540     ret = ERROR_REORG_SONGS;
2541     goto error;
2542   }
2543   // then the albums that are not linked to a song or to album, or whose path is removed
2544   if (pDlgProgress)
2545   {
2546     pDlgProgress->SetLine(1, 326);
2547     pDlgProgress->SetPercentage(20);
2548     pDlgProgress->Progress();
2549   }
2550   if (!CleanupAlbums())
2551   {
2552     ret = ERROR_REORG_ALBUM;
2553     goto error;
2554   }
2555   // now the paths
2556   if (pDlgProgress)
2557   {
2558     pDlgProgress->SetLine(1, 324);
2559     pDlgProgress->SetPercentage(40);
2560     pDlgProgress->Progress();
2561   }
2562   if (!CleanupPaths())
2563   {
2564     ret = ERROR_REORG_PATH;
2565     goto error;
2566   }
2567   // and finally artists + genres
2568   if (pDlgProgress)
2569   {
2570     pDlgProgress->SetLine(1, 320);
2571     pDlgProgress->SetPercentage(60);
2572     pDlgProgress->Progress();
2573   }
2574   if (!CleanupArtists())
2575   {
2576     ret = ERROR_REORG_ARTIST;
2577     goto error;
2578   }
2579   if (pDlgProgress)
2580   {
2581     pDlgProgress->SetLine(1, 322);
2582     pDlgProgress->SetPercentage(80);
2583     pDlgProgress->Progress();
2584   }
2585   if (!CleanupGenres())
2586   {
2587     ret = ERROR_REORG_GENRE;
2588     goto error;
2589   }
2590   // commit transaction
2591   if (pDlgProgress)
2592   {
2593     pDlgProgress->SetLine(1, 328);
2594     pDlgProgress->SetPercentage(90);
2595     pDlgProgress->Progress();
2596   }
2597   if (!CommitTransaction())
2598   {
2599     ret = ERROR_WRITING_CHANGES;
2600     goto error;
2601   }
2602   // and compress the database
2603   if (pDlgProgress)
2604   {
2605     pDlgProgress->SetLine(1, 331);
2606     pDlgProgress->SetPercentage(100);
2607     pDlgProgress->Progress();
2608   }
2609   time = XbmcThreads::SystemClockMillis() - time;
2610   CLog::Log(LOGNOTICE, "%s: Cleaning musicdatabase done. Operation took %s", __FUNCTION__, StringUtils::SecondsToTimeString(time / 1000).c_str());
2611   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnCleanFinished");
2612
2613   if (!Compress(false))
2614   {
2615     return ERROR_COMPRESSING;
2616   }
2617   return ERROR_OK;
2618
2619 error:
2620   RollbackTransaction();
2621   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnCleanFinished");
2622   return ret;
2623 }
2624
2625 bool CMusicDatabase::LookupCDDBInfo(bool bRequery/*=false*/)
2626 {
2627 #ifdef HAS_DVD_DRIVE
2628   if (!CSettings::Get().GetBool("audiocds.usecddb"))
2629     return false;
2630
2631   // check network connectivity
2632   if (!g_application.getNetwork().IsAvailable())
2633     return false;
2634
2635   // Get information for the inserted disc
2636   CCdInfo* pCdInfo = g_mediaManager.GetCdInfo();
2637   if (pCdInfo == NULL)
2638     return false;
2639
2640   // If the disc has no tracks, we are finished here.
2641   int nTracks = pCdInfo->GetTrackCount();
2642   if (nTracks <= 0)
2643     return false;
2644
2645   //  Delete old info if any
2646   if (bRequery)
2647   {
2648     CStdString strFile = StringUtils::Format("%x.cddb", pCdInfo->GetCddbDiscId());
2649     CFile::Delete(URIUtils::AddFileToFolder(CProfilesManager::Get().GetCDDBFolder(), strFile));
2650   }
2651
2652   // Prepare cddb
2653   Xcddb cddb;
2654   cddb.setCacheDir(CProfilesManager::Get().GetCDDBFolder());
2655
2656   // Do we have to look for cddb information
2657   if (pCdInfo->HasCDDBInfo() && !cddb.isCDCached(pCdInfo))
2658   {
2659     CGUIDialogProgress* pDialogProgress = (CGUIDialogProgress*)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
2660     CGUIDialogSelect *pDlgSelect = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
2661
2662     if (!pDialogProgress) return false;
2663     if (!pDlgSelect) return false;
2664
2665     // Show progress dialog if we have to connect to freedb.org
2666     pDialogProgress->SetHeading(255); //CDDB
2667     pDialogProgress->SetLine(0, ""); // Querying freedb for CDDB info
2668     pDialogProgress->SetLine(1, 256);
2669     pDialogProgress->SetLine(2, "");
2670     pDialogProgress->ShowProgressBar(false);
2671     pDialogProgress->StartModal();
2672
2673     // get cddb information
2674     if (!cddb.queryCDinfo(pCdInfo))
2675     {
2676       pDialogProgress->Close();
2677       int lasterror = cddb.getLastError();
2678
2679       // Have we found more then on match in cddb for this disc,...
2680       if (lasterror == E_WAIT_FOR_INPUT)
2681       {
2682         // ...yes, show the matches found in a select dialog
2683         // and let the user choose an entry.
2684         pDlgSelect->Reset();
2685         pDlgSelect->SetHeading(255);
2686         int i = 1;
2687         while (1)
2688         {
2689           CStdString strTitle = cddb.getInexactTitle(i);
2690           if (strTitle == "") break;
2691
2692           CStdString strArtist = cddb.getInexactArtist(i);
2693           if (!strArtist.empty())
2694             strTitle += " - " + strArtist;
2695
2696           pDlgSelect->Add(strTitle);
2697           i++;
2698         }
2699         pDlgSelect->DoModal();
2700
2701         // Has the user selected a match...
2702         int iSelectedCD = pDlgSelect->GetSelectedLabel();
2703         if (iSelectedCD >= 0)
2704         {
2705           // ...query cddb for the inexact match
2706           if (!cddb.queryCDinfo(pCdInfo, 1 + iSelectedCD))
2707             pCdInfo->SetNoCDDBInfo();
2708         }
2709         else
2710           pCdInfo->SetNoCDDBInfo();
2711       }
2712       else if (lasterror == E_NO_MATCH_FOUND)
2713       {
2714         pCdInfo->SetNoCDDBInfo();
2715       }
2716       else
2717       {
2718         pCdInfo->SetNoCDDBInfo();
2719         // ..no, an error occured, display it to the user
2720         CStdString strErrorText = StringUtils::Format("[%d] %s", cddb.getLastError(), cddb.getLastErrorText());
2721         CGUIDialogOK::ShowAndGetInput(255, 257, strErrorText, 0);
2722       }
2723     } // if ( !cddb.queryCDinfo( pCdInfo ) )
2724     else
2725       pDialogProgress->Close();
2726   }
2727
2728   // Filling the file items with cddb info happens in CMusicInfoTagLoaderCDDA
2729
2730   return pCdInfo->HasCDDBInfo();
2731 #else
2732   return false;
2733 #endif
2734 }
2735
2736 void CMusicDatabase::DeleteCDDBInfo()
2737 {
2738 #ifdef HAS_DVD_DRIVE
2739   CFileItemList items;
2740   if (!CDirectory::GetDirectory(CProfilesManager::Get().GetCDDBFolder(), items, ".cddb", DIR_FLAG_NO_FILE_DIRS))
2741   {
2742     CGUIDialogOK::ShowAndGetInput(313, 426, 0, 0);
2743     return ;
2744   }
2745   // Show a selectdialog that the user can select the album to delete
2746   CGUIDialogSelect *pDlg = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
2747   if (pDlg)
2748   {
2749     pDlg->SetHeading(g_localizeStrings.Get(181).c_str());
2750     pDlg->Reset();
2751
2752     map<ULONG, CStdString> mapCDDBIds;
2753     for (int i = 0; i < items.Size(); ++i)
2754     {
2755       if (items[i]->m_bIsFolder)
2756         continue;
2757
2758       CStdString strFile = URIUtils::GetFileName(items[i]->GetPath());
2759       strFile.erase(strFile.size() - 5, 5);
2760       ULONG lDiscId = strtoul(strFile.c_str(), NULL, 16);
2761       Xcddb cddb;
2762       cddb.setCacheDir(CProfilesManager::Get().GetCDDBFolder());
2763
2764       if (!cddb.queryCache(lDiscId))
2765         continue;
2766
2767       CStdString strDiskTitle, strDiskArtist;
2768       cddb.getDiskTitle(strDiskTitle);
2769       cddb.getDiskArtist(strDiskArtist);
2770
2771       CStdString str;
2772       if (strDiskArtist.empty())
2773         str = strDiskTitle;
2774       else
2775         str = strDiskTitle + " - " + strDiskArtist;
2776
2777       pDlg->Add(str);
2778       mapCDDBIds.insert(pair<ULONG, CStdString>(lDiscId, str));
2779     }
2780
2781     pDlg->Sort();
2782     pDlg->DoModal();
2783
2784     // and wait till user selects one
2785     int iSelectedAlbum = pDlg->GetSelectedLabel();
2786     if (iSelectedAlbum < 0)
2787     {
2788       mapCDDBIds.erase(mapCDDBIds.begin(), mapCDDBIds.end());
2789       return ;
2790     }
2791
2792     CStdString strSelectedAlbum = pDlg->GetSelectedLabelText();
2793     map<ULONG, CStdString>::iterator it;
2794     for (it = mapCDDBIds.begin();it != mapCDDBIds.end();it++)
2795     {
2796       if (it->second == strSelectedAlbum)
2797       {
2798         CStdString strFile = StringUtils::Format("%x.cddb", it->first);
2799         CFile::Delete(URIUtils::AddFileToFolder(CProfilesManager::Get().GetCDDBFolder(), strFile));
2800         break;
2801       }
2802     }
2803     mapCDDBIds.erase(mapCDDBIds.begin(), mapCDDBIds.end());
2804   }
2805 #endif
2806 }
2807
2808 void CMusicDatabase::Clean()
2809 {
2810   // If we are scanning for music info in the background,
2811   // other writing access to the database is prohibited.
2812   if (g_application.IsMusicScanning())
2813   {
2814     CGUIDialogOK::ShowAndGetInput(189, 14057, 0, 0);
2815     return;
2816   }
2817
2818   if (CGUIDialogYesNo::ShowAndGetInput(313, 333, 0, 0))
2819   {
2820     CGUIDialogProgress* dlgProgress = (CGUIDialogProgress*)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
2821     if (dlgProgress)
2822     {
2823       CMusicDatabase musicdatabase;
2824       if (musicdatabase.Open())
2825       {
2826         int iReturnString = musicdatabase.Cleanup(dlgProgress);
2827         musicdatabase.Close();
2828
2829         if (iReturnString != ERROR_OK)
2830         {
2831           CGUIDialogOK::ShowAndGetInput(313, iReturnString, 0, 0);
2832         }
2833       }
2834       dlgProgress->Close();
2835     }
2836   }
2837 }
2838
2839 bool CMusicDatabase::GetGenresNav(const CStdString& strBaseDir, CFileItemList& items, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
2840 {
2841   try
2842   {
2843     if (NULL == m_pDB.get()) return false;
2844     if (NULL == m_pDS.get()) return false;
2845
2846     // get primary genres for songs - could be simplified to just SELECT * FROM genre?
2847     CStdString strSQL = "SELECT %s FROM genre ";
2848
2849     Filter extFilter = filter;
2850     CMusicDbUrl musicUrl;
2851     SortDescription sorting;
2852     if (!musicUrl.FromString(strBaseDir) || !GetFilter(musicUrl, extFilter, sorting))
2853       return false;
2854
2855     // if there are extra WHERE conditions we might need access
2856     // to songview or albumview for these conditions
2857     if (extFilter.where.size() > 0)
2858     {
2859       if (extFilter.where.find("artistview") != string::npos)
2860         extFilter.AppendJoin("JOIN song_genre ON song_genre.idGenre = genre.idGenre JOIN songview ON songview.idSong = song_genre.idSong "
2861                              "JOIN song_artist ON song_artist.idSong = songview.idSong JOIN artistview ON artistview.idArtist = song_artist.idArtist");
2862       else if (extFilter.where.find("songview") != string::npos)
2863         extFilter.AppendJoin("JOIN song_genre ON song_genre.idGenre = genre.idGenre JOIN songview ON songview.idSong = song_genre.idSong");
2864       else if (extFilter.where.find("albumview") != string::npos)
2865         extFilter.AppendJoin("JOIN album_genre ON album_genre.idGenre = genre.idGenre JOIN albumview ON albumview.idAlbum = album_genre.idAlbum");
2866
2867       extFilter.AppendGroup("genre.idGenre");
2868     }
2869     extFilter.AppendWhere("genre.strGenre != ''");
2870
2871     if (countOnly)
2872     {
2873       extFilter.fields = "COUNT(DISTINCT genre.idGenre)";
2874       extFilter.group.clear();
2875       extFilter.order.clear();
2876     }
2877
2878     CStdString strSQLExtra;
2879     if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra))
2880       return false;
2881
2882     strSQL = PrepareSQL(strSQL.c_str(), !extFilter.fields.empty() && extFilter.fields.compare("*") != 0 ? extFilter.fields.c_str() : "genre.*") + strSQLExtra;
2883
2884     // run query
2885     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
2886
2887     if (!m_pDS->query(strSQL.c_str()))
2888       return false;
2889     int iRowsFound = m_pDS->num_rows();
2890     if (iRowsFound == 0)
2891     {
2892       m_pDS->close();
2893       return true;
2894     }
2895
2896     if (countOnly)
2897     {
2898       CFileItemPtr pItem(new CFileItem());
2899       pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
2900       items.Add(pItem);
2901
2902       m_pDS->close();
2903       return true;
2904     }
2905
2906     // get data from returned rows
2907     while (!m_pDS->eof())
2908     {
2909       CFileItemPtr pItem(new CFileItem(m_pDS->fv("genre.strGenre").get_asString()));
2910       pItem->GetMusicInfoTag()->SetGenre(m_pDS->fv("genre.strGenre").get_asString());
2911       pItem->GetMusicInfoTag()->SetDatabaseId(m_pDS->fv("genre.idGenre").get_asInt(), "genre");
2912
2913       CMusicDbUrl itemUrl = musicUrl;
2914       CStdString strDir = StringUtils::Format("%ld/", m_pDS->fv("genre.idGenre").get_asInt());
2915       itemUrl.AppendPath(strDir);
2916       pItem->SetPath(itemUrl.ToString());
2917
2918       pItem->m_bIsFolder = true;
2919       items.Add(pItem);
2920
2921       m_pDS->next();
2922     }
2923
2924     // cleanup
2925     m_pDS->close();
2926
2927     return true;
2928   }
2929   catch (...)
2930   {
2931     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2932   }
2933   return false;
2934 }
2935
2936 bool CMusicDatabase::GetYearsNav(const CStdString& strBaseDir, CFileItemList& items, const Filter &filter /* = Filter() */)
2937 {
2938   try
2939   {
2940     if (NULL == m_pDB.get()) return false;
2941     if (NULL == m_pDS.get()) return false;
2942
2943     Filter extFilter = filter;
2944     CMusicDbUrl musicUrl;
2945     SortDescription sorting;
2946     if (!musicUrl.FromString(strBaseDir) || !GetFilter(musicUrl, extFilter, sorting))
2947       return false;
2948
2949     // get years from album list
2950     CStdString strSQL = "SELECT DISTINCT albumview.iYear FROM albumview ";
2951     extFilter.AppendWhere("albumview.iYear <> 0");
2952
2953     if (!BuildSQL(strSQL, extFilter, strSQL))
2954       return false;
2955
2956     // run query
2957     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
2958     if (!m_pDS->query(strSQL.c_str()))
2959       return false;
2960     int iRowsFound = m_pDS->num_rows();
2961     if (iRowsFound == 0)
2962     {
2963       m_pDS->close();
2964       return true;
2965     }
2966
2967     // get data from returned rows
2968     while (!m_pDS->eof())
2969     {
2970       CFileItemPtr pItem(new CFileItem(m_pDS->fv(0).get_asString()));
2971       SYSTEMTIME stTime;
2972       stTime.wYear = (WORD)m_pDS->fv(0).get_asInt();
2973       pItem->GetMusicInfoTag()->SetReleaseDate(stTime);
2974
2975       CMusicDbUrl itemUrl = musicUrl;
2976       CStdString strDir = StringUtils::Format("%ld/", m_pDS->fv(0).get_asInt());
2977       itemUrl.AppendPath(strDir);
2978       pItem->SetPath(itemUrl.ToString());
2979
2980       pItem->m_bIsFolder = true;
2981       items.Add(pItem);
2982
2983       m_pDS->next();
2984     }
2985
2986     // cleanup
2987     m_pDS->close();
2988
2989     return true;
2990   }
2991   catch (...)
2992   {
2993     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2994   }
2995   return false;
2996 }
2997
2998 bool CMusicDatabase::GetAlbumsByYear(const CStdString& strBaseDir, CFileItemList& items, int year)
2999 {
3000   CMusicDbUrl musicUrl;
3001   if (!musicUrl.FromString(strBaseDir))
3002     return false;
3003
3004   musicUrl.AddOption("year", year);
3005   
3006   Filter filter;
3007   return GetAlbumsByWhere(musicUrl.ToString(), filter, items);
3008 }
3009
3010 bool CMusicDatabase::GetCommonNav(const CStdString &strBaseDir, const CStdString &table, const CStdString &labelField, CFileItemList &items, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
3011 {
3012   if (NULL == m_pDB.get()) return false;
3013   if (NULL == m_pDS.get()) return false;
3014
3015   if (table.empty() || labelField.empty())
3016     return false;
3017   
3018   try
3019   {
3020     Filter extFilter = filter;
3021     CStdString strSQL = "SELECT %s FROM " + table + " ";
3022     extFilter.AppendGroup(labelField);
3023     extFilter.AppendWhere(labelField + " != ''");
3024     
3025     if (countOnly)
3026     {
3027       extFilter.fields = "COUNT(DISTINCT " + labelField + ")";
3028       extFilter.group.clear();
3029       extFilter.order.clear();
3030     }
3031     
3032     CMusicDbUrl musicUrl;
3033     if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, musicUrl))
3034       return false;
3035     
3036     strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : labelField.c_str());
3037     
3038     // run query
3039     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
3040     if (!m_pDS->query(strSQL.c_str()))
3041       return false;
3042     
3043     int iRowsFound = m_pDS->num_rows();
3044     if (iRowsFound <= 0)
3045     {
3046       m_pDS->close();
3047       return false;
3048     }
3049     
3050     if (countOnly)
3051     {
3052       CFileItemPtr pItem(new CFileItem());
3053       pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
3054       items.Add(pItem);
3055       
3056       m_pDS->close();
3057       return true;
3058     }
3059     
3060     // get data from returned rows
3061     while (!m_pDS->eof())
3062     {
3063       string labelValue = m_pDS->fv(labelField).get_asString();
3064       CFileItemPtr pItem(new CFileItem(labelValue));
3065       
3066       CMusicDbUrl itemUrl = musicUrl;
3067       CStdString strDir = StringUtils::Format("%s/", labelValue.c_str());
3068       itemUrl.AppendPath(strDir);
3069       pItem->SetPath(itemUrl.ToString());
3070       
3071       pItem->m_bIsFolder = true;
3072       items.Add(pItem);
3073       
3074       m_pDS->next();
3075     }
3076     
3077     // cleanup
3078     m_pDS->close();
3079     
3080     return true;
3081   }
3082   catch (...)
3083   {
3084     m_pDS->close();
3085     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
3086   }
3087   
3088   return false;
3089 }
3090
3091 bool CMusicDatabase::GetAlbumTypesNav(const CStdString &strBaseDir, CFileItemList &items, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
3092 {
3093   return GetCommonNav(strBaseDir, "albumview", "albumview.strType", items, filter, countOnly);
3094 }
3095
3096 bool CMusicDatabase::GetMusicLabelsNav(const CStdString &strBaseDir, CFileItemList &items, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
3097 {
3098   return GetCommonNav(strBaseDir, "albumview", "albumview.strLabel", items, filter, countOnly);
3099 }
3100
3101 bool CMusicDatabase::GetArtistsNav(const CStdString& strBaseDir, CFileItemList& items, bool albumArtistsOnly /* = false */, int idGenre /* = -1 */, int idAlbum /* = -1 */, int idSong /* = -1 */, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */, bool countOnly /* = false */)
3102 {
3103   if (NULL == m_pDB.get()) return false;
3104   if (NULL == m_pDS.get()) return false;
3105   try
3106   {
3107     unsigned int time = XbmcThreads::SystemClockMillis();
3108
3109     CMusicDbUrl musicUrl;
3110     if (!musicUrl.FromString(strBaseDir))
3111       return false;
3112
3113     if (idGenre > 0)
3114       musicUrl.AddOption("genreid", idGenre);
3115     else if (idAlbum > 0)
3116       musicUrl.AddOption("albumid", idAlbum);
3117     else if (idSong > 0)
3118       musicUrl.AddOption("songid", idSong);
3119
3120     musicUrl.AddOption("albumartistsonly", albumArtistsOnly);
3121
3122     bool result = GetArtistsByWhere(musicUrl.ToString(), filter, items, sortDescription, countOnly);
3123     CLog::Log(LOGDEBUG,"Time to retrieve artists from dataset = %i", XbmcThreads::SystemClockMillis() - time);
3124
3125     return result;
3126   }
3127   catch (...)
3128   {
3129     m_pDS->close();
3130     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
3131   }
3132   return false;
3133 }
3134
3135 bool CMusicDatabase::GetArtistsByWhere(const CStdString& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */, bool countOnly /* = false */)
3136 {
3137   if (NULL == m_pDB.get()) return false;
3138   if (NULL == m_pDS.get()) return false;
3139
3140   try
3141   {
3142     int total = -1;
3143
3144     CStdString strSQL = "SELECT %s FROM artistview ";
3145
3146     Filter extFilter = filter;
3147     CMusicDbUrl musicUrl;
3148     SortDescription sorting = sortDescription;
3149     if (!musicUrl.FromString(strBaseDir) || !GetFilter(musicUrl, extFilter, sorting))
3150       return false;
3151
3152     // if there are extra WHERE conditions we might need access
3153     // to songview or albumview for these conditions
3154     if (extFilter.where.size() > 0)
3155     {
3156       bool extended = false;
3157       if (extFilter.where.find("songview") != string::npos)
3158       {
3159         extended = true;
3160         extFilter.AppendJoin("JOIN song_artist ON song_artist.idArtist = artistview.idArtist JOIN songview ON songview.idSong = song_artist.idSong");
3161       }
3162       else if (extFilter.where.find("albumview") != string::npos)
3163       {
3164         extended = true;
3165         extFilter.AppendJoin("JOIN album_artist ON album_artist.idArtist = artistview.idArtist JOIN albumview ON albumview.idAlbum = album_artist.idAlbum");
3166       }
3167
3168       if (extended)
3169         extFilter.AppendGroup("artistview.idArtist");
3170     }
3171     
3172     if (countOnly)
3173     {
3174       extFilter.fields = "COUNT(DISTINCT artistview.idArtist)";
3175       extFilter.group.clear();
3176       extFilter.order.clear();
3177     }
3178
3179     CStdString strSQLExtra;
3180     if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra))
3181       return false;
3182
3183     // Apply the limiting directly here if there's no special sorting but limiting
3184     if (extFilter.limit.empty() &&
3185         sortDescription.sortBy == SortByNone &&
3186        (sortDescription.limitStart > 0 || sortDescription.limitEnd > 0))
3187     {
3188       total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
3189       strSQLExtra += DatabaseUtils::BuildLimitClause(sortDescription.limitEnd, sortDescription.limitStart);
3190     }
3191
3192     strSQL = PrepareSQL(strSQL.c_str(), !extFilter.fields.empty() && extFilter.fields.compare("*") != 0 ? extFilter.fields.c_str() : "artistview.*") + strSQLExtra;
3193
3194     // run query
3195     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
3196     if (!m_pDS->query(strSQL.c_str())) return false;
3197     int iRowsFound = m_pDS->num_rows();
3198     if (iRowsFound == 0)
3199     {
3200       m_pDS->close();
3201       return true;
3202     }
3203
3204     if (countOnly)
3205     {
3206       CFileItemPtr pItem(new CFileItem());
3207       pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
3208       items.Add(pItem);
3209
3210       m_pDS->close();
3211       return true;
3212     }
3213
3214     // store the total value of items as a property
3215     if (total < iRowsFound)
3216       total = iRowsFound;
3217     items.SetProperty("total", total);
3218     
3219     DatabaseResults results;
3220     results.reserve(iRowsFound);
3221     if (!SortUtils::SortFromDataset(sortDescription, MediaTypeArtist, m_pDS, results))
3222       return false;
3223
3224     // get data from returned rows
3225     items.Reserve(results.size());
3226     const dbiplus::query_data &data = m_pDS->get_result_set().records;
3227     for (DatabaseResults::const_iterator it = results.begin(); it != results.end(); it++)
3228     {
3229       unsigned int targetRow = (unsigned int)it->at(FieldRow).asInteger();
3230       const dbiplus::sql_record* const record = data.at(targetRow);
3231       
3232       try
3233       {
3234         CArtist artist = GetArtistFromDataset(record, false);
3235         CFileItemPtr pItem(new CFileItem(artist));
3236
3237         CMusicDbUrl itemUrl = musicUrl;
3238         CStdString path = StringUtils::Format("%ld/", artist.idArtist);
3239         itemUrl.AppendPath(path);
3240         pItem->SetPath(itemUrl.ToString());
3241
3242         pItem->GetMusicInfoTag()->SetDatabaseId(artist.idArtist, "artist");
3243         pItem->SetIconImage("DefaultArtist.png");
3244
3245         SetPropertiesFromArtist(*pItem, artist);
3246         items.Add(pItem);
3247       }
3248       catch (...)
3249       {
3250         m_pDS->close();
3251         CLog::Log(LOGERROR, "%s - out of memory getting listing (got %i)", __FUNCTION__, items.Size());
3252       }
3253     }
3254
3255     // cleanup
3256     m_pDS->close();
3257
3258     return true;
3259   }
3260   catch (...)
3261   {
3262     m_pDS->close();
3263     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
3264   }
3265   return false;
3266 }
3267
3268 bool CMusicDatabase::GetAlbumFromSong(int idSong, CAlbum &album)
3269 {
3270   try
3271   {
3272     if (NULL == m_pDB.get()) return false;
3273     if (NULL == m_pDS.get()) return false;
3274
3275     CStdString strSQL = PrepareSQL("select albumview.* from song join albumview on song.idAlbum = albumview.idAlbum where song.idSong='%i'", idSong);
3276     if (!m_pDS->query(strSQL.c_str())) return false;
3277     int iRowsFound = m_pDS->num_rows();
3278     if (iRowsFound != 1)
3279     {
3280       m_pDS->close();
3281       return false;
3282     }
3283
3284     album = GetAlbumFromDataset(m_pDS.get());
3285
3286     m_pDS->close();
3287     return true;
3288
3289   }
3290   catch (...)
3291   {
3292     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
3293   }
3294   return false;
3295 }
3296
3297 bool CMusicDatabase::GetAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre /* = -1 */, int idArtist /* = -1 */, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */, bool countOnly /* = false */)
3298 {
3299   CMusicDbUrl musicUrl;
3300   if (!musicUrl.FromString(strBaseDir))
3301     return false;
3302
3303   // where clause
3304   if (idGenre > 0)
3305     musicUrl.AddOption("genreid", idGenre);
3306
3307   if (idArtist > 0)
3308     musicUrl.AddOption("artistid", idArtist);
3309
3310   return GetAlbumsByWhere(musicUrl.ToString(), filter, items, sortDescription, countOnly);
3311 }
3312
3313 bool CMusicDatabase::GetAlbumsByWhere(const CStdString &baseDir, const Filter &filter, CFileItemList &items, const SortDescription &sortDescription /* = SortDescription() */, bool countOnly /* = false */)
3314 {
3315   if (m_pDB.get() == NULL || m_pDS.get() == NULL)
3316     return false;
3317
3318   try
3319   {
3320     int total = -1;
3321
3322     CStdString strSQL = "SELECT %s FROM albumview ";
3323
3324     Filter extFilter = filter;
3325     CMusicDbUrl musicUrl;
3326     SortDescription sorting = sortDescription;
3327     if (!musicUrl.FromString(baseDir) || !GetFilter(musicUrl, extFilter, sorting))
3328       return false;
3329
3330     // if there are extra WHERE conditions we might need access
3331     // to songview for these conditions
3332     if (extFilter.where.find("songview") != string::npos)
3333     {
3334       extFilter.AppendJoin("JOIN songview ON songview.idAlbum = albumview.idAlbum");
3335       extFilter.AppendGroup("albumview.idAlbum");
3336     }
3337
3338     CStdString strSQLExtra;
3339     if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra))
3340       return false;
3341
3342     // Apply the limiting directly here if there's no special sorting but limiting
3343     if (extFilter.limit.empty() &&
3344         sortDescription.sortBy == SortByNone &&
3345        (sortDescription.limitStart > 0 || sortDescription.limitEnd > 0))
3346     {
3347       total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
3348       strSQLExtra += DatabaseUtils::BuildLimitClause(sortDescription.limitEnd, sortDescription.limitStart);
3349     }
3350
3351     strSQL = PrepareSQL(strSQL, !filter.fields.empty() && filter.fields.compare("*") != 0 ? filter.fields.c_str() : "albumview.*") + strSQLExtra;
3352
3353     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
3354     // run query
3355     unsigned int time = XbmcThreads::SystemClockMillis();
3356     if (!m_pDS->query(strSQL.c_str()))
3357       return false;
3358     CLog::Log(LOGDEBUG, "%s - query took %i ms",
3359               __FUNCTION__, XbmcThreads::SystemClockMillis() - time); time = XbmcThreads::SystemClockMillis();
3360
3361     int iRowsFound = m_pDS->num_rows();
3362     if (iRowsFound <= 0)
3363     {
3364       m_pDS->close();
3365       return true;
3366     }
3367
3368     // store the total value of items as a property
3369     if (total < iRowsFound)
3370       total = iRowsFound;
3371     items.SetProperty("total", total);
3372
3373     if (countOnly)
3374     {
3375       CFileItemPtr pItem(new CFileItem());
3376       pItem->SetProperty("total", total);
3377       items.Add(pItem);
3378
3379       m_pDS->close();
3380       return true;
3381     }
3382     
3383     DatabaseResults results;
3384     results.reserve(iRowsFound);
3385     if (!SortUtils::SortFromDataset(sortDescription, MediaTypeAlbum, m_pDS, results))
3386       return false;
3387
3388     // get data from returned rows
3389     items.Reserve(results.size());
3390     const dbiplus::query_data &data = m_pDS->get_result_set().records;
3391     for (DatabaseResults::const_iterator it = results.begin(); it != results.end(); it++)
3392     {
3393       unsigned int targetRow = (unsigned int)it->at(FieldRow).asInteger();
3394       const dbiplus::sql_record* const record = data.at(targetRow);
3395       
3396       try
3397       {
3398         CMusicDbUrl itemUrl = musicUrl;
3399         CStdString path = StringUtils::Format("%ld/", record->at(album_idAlbum).get_asInt());
3400         itemUrl.AppendPath(path);
3401
3402         CFileItemPtr pItem(new CFileItem(itemUrl.ToString(), GetAlbumFromDataset(record)));
3403         pItem->SetIconImage("DefaultAlbumCover.png");
3404         items.Add(pItem);
3405       }
3406       catch (...)
3407       {
3408         m_pDS->close();
3409         CLog::Log(LOGERROR, "%s - out of memory getting listing (got %i)", __FUNCTION__, items.Size());
3410       }
3411     }
3412
3413     // cleanup
3414     m_pDS->close();
3415     return true;
3416   }
3417   catch (...)
3418   {
3419     m_pDS->close();
3420     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, filter.where.c_str());
3421   }
3422   return false;
3423 }
3424
3425 bool CMusicDatabase::GetSongsByWhere(const CStdString &baseDir, const Filter &filter, CFileItemList &items, const SortDescription &sortDescription /* = SortDescription() */)
3426 {
3427   if (m_pDB.get() == NULL || m_pDS.get() == NULL)
3428     return false;
3429
3430   try
3431   {
3432     unsigned int time = XbmcThreads::SystemClockMillis();
3433     int total = -1;
3434
3435     CStdString strSQL = "SELECT %s FROM songview ";
3436
3437     Filter extFilter = filter;
3438     CMusicDbUrl musicUrl;
3439     SortDescription sorting = sortDescription;
3440     if (!musicUrl.FromString(baseDir) || !GetFilter(musicUrl, extFilter, sorting))
3441       return false;
3442
3443     // if there are extra WHERE conditions we might need access
3444     // to songview for these conditions
3445     if (extFilter.where.find("albumview") != string::npos)
3446     {
3447       extFilter.AppendJoin("JOIN albumview ON albumview.idAlbum = songview.idAlbum");
3448       extFilter.AppendGroup("songview.idSong");
3449     }
3450
3451     CStdString strSQLExtra;
3452     if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra))
3453       return false;
3454
3455     // Apply the limiting directly here if there's no special sorting but limiting
3456     if (extFilter.limit.empty() &&
3457         sortDescription.sortBy == SortByNone &&
3458        (sortDescription.limitStart > 0 || sortDescription.limitEnd > 0))
3459     {
3460       total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
3461       strSQLExtra += DatabaseUtils::BuildLimitClause(sortDescription.limitEnd, sortDescription.limitStart);
3462     }
3463
3464     strSQL = PrepareSQL(strSQL, !filter.fields.empty() && filter.fields.compare("*") != 0 ? filter.fields.c_str() : "songview.*") + strSQLExtra;
3465
3466     CLog::Log(LOGDEBUG, "%s query = %s", __FUNCTION__, strSQL.c_str());
3467     // run query
3468     if (!m_pDS->query(strSQL.c_str()))
3469       return false;
3470
3471     int iRowsFound = m_pDS->num_rows();
3472     if (iRowsFound == 0)
3473     {
3474       m_pDS->close();
3475       return true;
3476     }
3477
3478     // store the total value of items as a property
3479     if (total < iRowsFound)
3480       total = iRowsFound;
3481     items.SetProperty("total", total);
3482     
3483     DatabaseResults results;
3484     results.reserve(iRowsFound);
3485     if (!SortUtils::SortFromDataset(sortDescription, MediaTypeSong, m_pDS, results))
3486       return false;
3487
3488     // get data from returned rows
3489     items.Reserve(results.size());
3490     const dbiplus::query_data &data = m_pDS->get_result_set().records;
3491     int count = 0;
3492     for (DatabaseResults::const_iterator it = results.begin(); it != results.end(); it++)
3493     {
3494       unsigned int targetRow = (unsigned int)it->at(FieldRow).asInteger();
3495       const dbiplus::sql_record* const record = data.at(targetRow);
3496       
3497       try
3498       {
3499         CFileItemPtr item(new CFileItem);
3500         GetFileItemFromDataset(record, item.get(), musicUrl);
3501         // HACK for sorting by database returned order
3502         item->m_iprogramCount = ++count;
3503         items.Add(item);
3504       }
3505       catch (...)
3506       {
3507         m_pDS->close();
3508         CLog::Log(LOGERROR, "%s: out of memory loading query: %s", __FUNCTION__, filter.where.c_str());
3509         return (items.Size() > 0);
3510       }
3511     }
3512
3513     // cleanup
3514     m_pDS->close();
3515     CLog::Log(LOGDEBUG, "%s(%s) - took %d ms", __FUNCTION__, filter.where.c_str(), XbmcThreads::SystemClockMillis() - time);
3516     return true;
3517   }
3518   catch (...)
3519   {
3520     // cleanup
3521     m_pDS->close();
3522     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, filter.where.c_str());
3523   }
3524   return false;
3525 }
3526
3527 bool CMusicDatabase::GetSongsByYear(const CStdString& baseDir, CFileItemList& items, int year)
3528 {
3529   CMusicDbUrl musicUrl;
3530   if (!musicUrl.FromString(baseDir))
3531     return false;
3532
3533   musicUrl.AddOption("year", year);
3534   
3535   Filter filter;
3536   return GetSongsByWhere(baseDir, filter, items);
3537 }
3538
3539 bool CMusicDatabase::GetSongsNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre, int idArtist, int idAlbum, const SortDescription &sortDescription /* = SortDescription() */)
3540 {
3541   CMusicDbUrl musicUrl;
3542   if (!musicUrl.FromString(strBaseDir))
3543     return false;
3544
3545   if (idAlbum > 0)
3546     musicUrl.AddOption("albumid", idAlbum);
3547
3548   if (idGenre > 0)
3549     musicUrl.AddOption("genreid", idGenre);
3550
3551   if (idArtist > 0)
3552     musicUrl.AddOption("artistid", idArtist);
3553
3554   Filter filter;
3555   return GetSongsByWhere(musicUrl.ToString(), filter, items, sortDescription);
3556 }
3557
3558 void CMusicDatabase::UpdateTables(int version)
3559 {
3560   if (version < 19)
3561   {
3562     int len = g_advancedSettings.m_musicItemSeparator.size() + 1;
3563     CStdString sql = PrepareSQL("UPDATE song SET strExtraArtists=SUBSTR(strExtraArtists,%i), strExtraGenres=SUBSTR(strExtraGenres,%i)", len, len);
3564     m_pDS->exec(sql.c_str());
3565     sql = PrepareSQL("UPDATE album SET strExtraArtists=SUBSTR(strExtraArtists,%i), strExtraGenres=SUBSTR(strExtraGenres,%i)", len, len);
3566     m_pDS->exec(sql.c_str());
3567   }
3568
3569   if (version < 21)
3570   {
3571     m_pDS->exec("CREATE TABLE album_artist ( idArtist integer, idAlbum integer, boolFeatured integer, iOrder integer )\n");
3572     m_pDS->exec("INSERT INTO album_artist (idArtist, idAlbum, boolFeatured, iOrder) SELECT idArtist, idAlbum, 1, iPosition FROM exartistalbum");
3573     m_pDS->exec("REPLACE INTO album_artist (idArtist, idAlbum, boolFeatured, iOrder) SELECT idArtist, idAlbum, 0, 0 FROM album");
3574
3575     CStdString strSQL;
3576     strSQL=PrepareSQL("SELECT album.idAlbum AS idAlbum, strExtraArtists,"
3577                       "  album.idArtist AS idArtist, strArtist FROM album "
3578                       "  LEFT OUTER JOIN artist ON album.idArtist=artist.idArtist");
3579     m_pDS->query(strSQL.c_str());
3580
3581     VECALBUMS albums;
3582     while (!m_pDS->eof())
3583     {
3584       CAlbum album;
3585       album.idAlbum = m_pDS->fv("idAlbum").get_asInt();
3586       album.artist.push_back(m_pDS->fv("strArtist").get_asString());
3587       if (!m_pDS->fv("strExtraArtists").get_asString().empty())
3588       {
3589         std::vector<std::string> extraArtists = StringUtils::Split(m_pDS->fv("strExtraArtists").get_asString(), g_advancedSettings.m_musicItemSeparator);
3590         album.artist.insert(album.artist.end(), extraArtists.begin(), extraArtists.end());
3591       }
3592       albums.push_back(album);
3593       m_pDS->next();
3594     }
3595     m_pDS->close();
3596     m_pDS->exec("CREATE TABLE album_new ( idAlbum integer primary key, strAlbum varchar(256), strArtists text, idGenre integer, strExtraGenres text, iYear integer, idThumb integer)");
3597     m_pDS->exec("INSERT INTO album_new ( idAlbum, strAlbum, idGenre, strExtraGenres, iYear, idThumb ) SELECT idAlbum, strAlbum, idGenre, strExtraGenres, iYear, idThumb FROM album");
3598
3599     for (VECALBUMS::iterator it = albums.begin(); it != albums.end(); ++it)
3600     {
3601       CStdString strSQL;
3602       strSQL = PrepareSQL("UPDATE album_new SET strArtists='%s' WHERE idAlbum=%i", StringUtils::Join(it->artist, g_advancedSettings.m_musicItemSeparator).c_str(), it->idAlbum);
3603       m_pDS->exec(strSQL);
3604     }
3605
3606     m_pDS->exec("DROP TABLE album");
3607     m_pDS->exec("ALTER TABLE album_new RENAME TO album");
3608     m_pDS->exec("DROP TABLE IF EXISTS exartistalbum");
3609   }
3610
3611   if (version < 22)
3612   {
3613     m_pDS->exec("CREATE TABLE song_artist ( idArtist integer, idSong integer, boolFeatured integer, iOrder integer )\n");
3614     m_pDS->exec("INSERT INTO song_artist (idArtist, idSong, boolFeatured, iOrder) SELECT idArtist, idSong, 1, iPosition FROM exartistsong");
3615     m_pDS->exec("REPLACE INTO song_artist (idArtist, idSong, boolFeatured, iOrder) SELECT idArtist, idSong, 0, 0 FROM song");
3616
3617     CStdString strSQL;
3618     strSQL=PrepareSQL("SELECT song.idSong AS idSong, strExtraArtists,"
3619                       "  song.idArtist AS idArtist, strArtist FROM song "
3620                       "  LEFT OUTER JOIN artist ON song.idArtist=artist.idArtist");
3621     m_pDS->query(strSQL.c_str());
3622
3623     VECSONGS songs;
3624     while (!m_pDS->eof())
3625     {
3626       CSong song;
3627       song.idSong = m_pDS->fv("idSong").get_asInt();
3628       song.artist.push_back(m_pDS->fv("strArtist").get_asString());
3629       if (!m_pDS->fv("strExtraArtists").get_asString().empty())
3630       {
3631         std::vector<std::string> extraArtists = StringUtils::Split(m_pDS->fv("strExtraArtists").get_asString(), g_advancedSettings.m_musicItemSeparator);
3632         song.artist.insert(song.artist.end(), extraArtists.begin(), extraArtists.end());
3633       }
3634       songs.push_back(song);
3635       m_pDS->next();
3636     }
3637     m_pDS->close();
3638     m_pDS->exec("CREATE TABLE song_new ( idSong integer primary key, idAlbum integer, idPath integer, strArtists text, idGenre integer, strExtraGenres text, strTitle varchar(512), 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 varchar(20) default NULL, rating char default '0', comment text)");
3639     m_pDS->exec("INSERT INTO song_new ( idSong, idAlbum, idPath, idGenre, strExtraGenres, strTitle, iTrack, iDuration, iYear, dwFileNameCRC, strFileName, strMusicBrainzTrackID, strMusicBrainzArtistID, strMusicBrainzAlbumID, strMusicBrainzAlbumArtistID, strMusicBrainzTRMID, iTimesPlayed, iStartOffset, iEndOffset, idThumb, lastplayed, rating, comment ) SELECT idSong, idAlbum, idPath, idGenre, strExtraGenres, strTitle, iTrack, iDuration, iYear, dwFileNameCRC, strFileName, strMusicBrainzTrackID, strMusicBrainzArtistID, strMusicBrainzAlbumID, strMusicBrainzAlbumArtistID, strMusicBrainzTRMID, iTimesPlayed, iStartOffset, iEndOffset, idThumb, lastplayed, rating, comment FROM song");
3640
3641     for (VECSONGS::iterator it = songs.begin(); it != songs.end(); ++it)
3642     {
3643       CStdString strSQL;
3644       strSQL = PrepareSQL("UPDATE song_new SET strArtists='%s' WHERE idSong=%i", StringUtils::Join(it->artist, g_advancedSettings.m_musicItemSeparator).c_str(), it->idSong);
3645       m_pDS->exec(strSQL);
3646     }
3647
3648     m_pDS->exec("DROP TABLE song");
3649     m_pDS->exec("ALTER TABLE song_new RENAME TO song");
3650     m_pDS->exec("DROP TABLE IF EXISTS exartistsong");
3651   }
3652
3653   if (version < 23)
3654   {
3655     m_pDS->exec("CREATE TABLE album_genre ( idGenre integer, idAlbum integer, iOrder integer )\n");
3656     m_pDS->exec("INSERT INTO album_genre ( idGenre, idAlbum, iOrder) SELECT idGenre, idAlbum, iPosition FROM exgenrealbum");
3657     m_pDS->exec("REPLACE INTO album_genre ( idGenre, idAlbum, iOrder) SELECT idGenre, idAlbum, 0 FROM album");
3658
3659     CStdString strSQL;
3660     strSQL=PrepareSQL("SELECT album.idAlbum AS idAlbum, strExtraGenres,"
3661                       "  album.idGenre AS idGenre, strGenre FROM album "
3662                       "  JOIN genre ON album.idGenre=genre.idGenre");
3663     m_pDS->query(strSQL.c_str());
3664
3665     VECALBUMS albums;
3666     while (!m_pDS->eof())
3667     {
3668       CAlbum album;
3669       album.idAlbum = m_pDS->fv("idAlbum").get_asInt();
3670       album.genre.push_back(m_pDS->fv("strGenre").get_asString());
3671       if (!m_pDS->fv("strExtraGenres").get_asString().empty())
3672       {
3673         std::vector<std::string> extraGenres = StringUtils::Split(m_pDS->fv("strExtraGenres").get_asString(), g_advancedSettings.m_musicItemSeparator);
3674         album.genre.insert(album.genre.end(), extraGenres.begin(), extraGenres.end());
3675       }
3676       albums.push_back(album);
3677       m_pDS->next();
3678     }
3679     m_pDS->close();
3680     m_pDS->exec("CREATE TABLE album_new ( idAlbum integer primary key, strAlbum varchar(256), strArtists text, strGenres text, iYear integer, idThumb integer)");
3681     m_pDS->exec("INSERT INTO album_new ( idAlbum, strAlbum, strArtists, iYear, idThumb) SELECT idAlbum, strAlbum, strArtists, iYear, idThumb FROM album");
3682
3683     for (VECALBUMS::iterator it = albums.begin(); it != albums.end(); ++it)
3684     {
3685       CStdString strSQL;
3686       strSQL = PrepareSQL("UPDATE album_new SET strGenres='%s' WHERE idAlbum=%i", StringUtils::Join(it->genre, g_advancedSettings.m_musicItemSeparator).c_str(), it->idAlbum);
3687       m_pDS->exec(strSQL);
3688     }
3689
3690     m_pDS->exec("DROP TABLE album");
3691     m_pDS->exec("ALTER TABLE album_new RENAME TO album");
3692     m_pDS->exec("DROP TABLE IF EXISTS exgenrealbum");
3693   }
3694
3695   if (version < 24)
3696   {
3697     m_pDS->exec("CREATE TABLE song_genre ( idGenre integer, idSong integer, iOrder integer )\n");
3698     m_pDS->exec("INSERT INTO song_genre ( idGenre, idSong, iOrder) SELECT idGenre, idSong, iPosition FROM exgenresong");
3699     m_pDS->exec("REPLACE INTO song_genre ( idGenre, idSong, iOrder) SELECT idGenre, idSong, 0 FROM song");
3700
3701     CStdString strSQL;
3702     strSQL=PrepareSQL("SELECT song.idSong AS idSong, strExtraGenres,"
3703                       "  song.idGenre AS idGenre, strGenre FROM song "
3704                       "  JOIN genre ON song.idGenre=genre.idGenre");
3705     m_pDS->query(strSQL.c_str());
3706
3707     VECSONGS songs;
3708     while (!m_pDS->eof())
3709     {
3710       CSong song;
3711       song.idSong = m_pDS->fv("idSong").get_asInt();
3712       song.genre.push_back(m_pDS->fv("strGenre").get_asString());
3713       if (!m_pDS->fv("strExtraGenres").get_asString().empty())
3714       {
3715         std::vector<std::string> extraGenres = StringUtils::Split(m_pDS->fv("strExtraGenres").get_asString(), g_advancedSettings.m_musicItemSeparator);
3716         song.genre.insert(song.genre.end(), extraGenres.begin(), extraGenres.end());
3717       }
3718       songs.push_back(song);
3719       m_pDS->next();
3720     }
3721     m_pDS->close();
3722     m_pDS->exec("CREATE TABLE song_new ( idSong integer primary key, idAlbum integer, idPath integer, strArtists text, strGenres text, strTitle varchar(512), 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 varchar(20) default NULL, rating char default '0', comment text)\n");
3723     m_pDS->exec("INSERT INTO song_new ( idSong, idAlbum, idPath, strArtists, strTitle, iTrack, iDuration, iYear, dwFileNameCRC, strFileName, strMusicBrainzTrackID, strMusicBrainzArtistID, strMusicBrainzAlbumID, strMusicBrainzAlbumArtistID, strMusicBrainzTRMID, iTimesPlayed, iStartOffset, iEndOffset, idThumb, lastplayed, rating, comment) SELECT idSong, idAlbum, idPath, strArtists, strTitle, iTrack, iDuration, iYear, dwFileNameCRC, strFileName, strMusicBrainzTrackID, strMusicBrainzArtistID, strMusicBrainzAlbumID, strMusicBrainzAlbumArtistID, strMusicBrainzTRMID, iTimesPlayed, iStartOffset, iEndOffset, idThumb, lastplayed, rating, comment FROM song");
3724
3725     for (VECSONGS::iterator it = songs.begin(); it != songs.end(); ++it)
3726     {
3727       CStdString strSQL;
3728       strSQL = PrepareSQL("UPDATE song_new SET strGenres='%s' WHERE idSong=%i", StringUtils::Join(it->genre, g_advancedSettings.m_musicItemSeparator).c_str(), it->idSong);
3729       m_pDS->exec(strSQL);
3730     }
3731
3732     m_pDS->exec("DROP TABLE song");
3733     m_pDS->exec("ALTER TABLE song_new RENAME TO song");
3734     m_pDS->exec("DROP TABLE IF EXISTS exgenresong");
3735   }
3736
3737   if (version < 25)
3738   {
3739     m_pDS->exec("ALTER TABLE album ADD bCompilation integer not null default '0'");
3740   }
3741
3742   if (version < 26)
3743   { // add art table
3744     m_pDS->exec("CREATE TABLE art(art_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, type TEXT, url TEXT)");
3745   }
3746
3747   if (version < 27)
3748   {
3749     m_pDS->exec("DROP TABLE IF EXISTS thumb");
3750
3751     CMediaSettings::Get().SetMusicNeedsUpdate(27);
3752     CSettings::Get().Save();
3753   }
3754
3755   if (version < 29)
3756   { // update old art URLs
3757     m_pDS->query("select art_id,url from art where url like 'image://%%'");
3758     vector< pair<int, string> > art;
3759     while (!m_pDS->eof())
3760     {
3761       art.push_back(make_pair(m_pDS->fv(0).get_asInt(), CURL(m_pDS->fv(1).get_asString()).Get()));
3762       m_pDS->next();
3763     }
3764     m_pDS->close();
3765     for (vector< pair<int, string> >::iterator i = art.begin(); i != art.end(); ++i)
3766       m_pDS->exec(PrepareSQL("update art set url='%s' where art_id=%d", i->second.c_str(), i->first));
3767   }
3768   if (version < 30)
3769   { // update URL encoded paths
3770     m_pDS->query("select idSong, strFileName from song");
3771     vector< pair<int, string> > files;
3772     while (!m_pDS->eof())
3773     {
3774       files.push_back(make_pair(m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asString()));
3775       m_pDS->next();
3776     }
3777     m_pDS->close();
3778
3779     for (vector< pair<int, string> >::iterator i = files.begin(); i != files.end(); ++i)
3780     {
3781       std::string filename = i->second;
3782       if (URIUtils::UpdateUrlEncoding(filename))
3783         m_pDS->exec(PrepareSQL("UPDATE song SET strFileName='%s' WHERE idSong=%d", filename.c_str(), i->first));
3784     }
3785   }
3786
3787   if (version < 34)
3788   {
3789     m_pDS->exec("ALTER TABLE artist ADD strMusicBrainzArtistID text\n");
3790     m_pDS->exec("ALTER TABLE album ADD strMusicBrainzAlbumID text\n");
3791     m_pDS->exec("CREATE TABLE song_new ( idSong integer primary key, idAlbum integer, idPath integer, strArtists text, strGenres text, strTitle varchar(512), iTrack integer, iDuration integer, iYear integer, dwFileNameCRC text, strFileName text, strMusicBrainzTrackID text, iTimesPlayed integer, iStartOffset integer, iEndOffset integer, idThumb integer, lastplayed varchar(20) default NULL, rating char default '0', comment text)\n");
3792     m_pDS->exec("INSERT INTO song_new ( idSong, idAlbum, idPath, strArtists, strTitle, iTrack, iDuration, iYear, dwFileNameCRC, strFileName, strMusicBrainzTrackID, iTimesPlayed, iStartOffset, iEndOffset, idThumb, lastplayed, rating, comment) SELECT idSong, idAlbum, idPath, strArtists, strTitle, iTrack, iDuration, iYear, dwFileNameCRC, strFileName, strMusicBrainzTrackID, iTimesPlayed, iStartOffset, iEndOffset, idThumb, lastplayed, rating, comment FROM song");
3793     
3794     m_pDS->exec("DROP TABLE song");
3795     m_pDS->exec("ALTER TABLE song_new RENAME TO song");
3796  
3797     m_pDS->exec("UPDATE song SET strMusicBrainzTrackID = NULL");
3798   }
3799
3800   if (version < 35)
3801   {
3802     m_pDS->exec("ALTER TABLE album_artist ADD strJoinPhrase text\n");
3803     m_pDS->exec("ALTER TABLE song_artist ADD strJoinPhrase text\n");
3804     CMediaSettings::Get().SetMusicNeedsUpdate(35);
3805     CSettings::Get().Save();
3806   }
3807
3808   if (version < 36)
3809   {
3810     // translate legacy musicdb:// paths
3811     if (m_pDS->query("SELECT strPath FROM content"))
3812     {
3813       vector<string> contentPaths;
3814       while (!m_pDS->eof())
3815       {
3816         contentPaths.push_back(m_pDS->fv(0).get_asString());
3817         m_pDS->next();
3818       }
3819       m_pDS->close();
3820
3821       for (vector<string>::const_iterator it = contentPaths.begin(); it != contentPaths.end(); it++)
3822       {
3823         std::string originalPath = *it;
3824         std::string path = CLegacyPathTranslation::TranslateMusicDbPath(originalPath);
3825         m_pDS->exec(PrepareSQL("UPDATE content SET strPath='%s' WHERE strPath='%s'", path.c_str(), originalPath.c_str()).c_str());
3826       }
3827     }
3828   }
3829
3830   if (version < 39)
3831   {
3832     m_pDS->exec("CREATE TABLE album_new "
3833                 "(idAlbum integer primary key, "
3834                 " strAlbum varchar(256), strMusicBrainzAlbumID text, "
3835                 " strArtists text, strGenres text, "
3836                 " iYear integer, idThumb integer, "
3837                 " bCompilation integer not null default '0', "
3838                 " strMoods text, strStyles text, strThemes text, "
3839                 " strReview text, strImage text, strLabel text, "
3840                 " strType text, "
3841                 " iRating integer, "
3842                 " lastScraped varchar(20) default NULL, "
3843                 " dateAdded varchar (20) default NULL)");
3844     m_pDS->exec("INSERT INTO album_new "
3845                 "(idAlbum, "
3846                 " strAlbum, strMusicBrainzAlbumID, "
3847                 " strArtists, strGenres, "
3848                 " iYear, idThumb, "
3849                 " bCompilation, "
3850                 " strMoods, strStyles, strThemes, "
3851                 " strReview, strImage, strLabel, "
3852                 " strType, "
3853                 " iRating) "
3854                 " SELECT "
3855                 " album.idAlbum, "
3856                 " strAlbum, strMusicBrainzAlbumID, "
3857                 " strArtists, strGenres, "
3858                 " album.iYear, idThumb, "
3859                 " bCompilation, "
3860                 " strMoods, strStyles, strThemes, "
3861                 " strReview, strImage, strLabel, "
3862                 " strType, iRating "
3863                 " FROM album LEFT JOIN albuminfo ON album.idAlbum = albuminfo.idAlbum");
3864     m_pDS->exec("UPDATE albuminfosong SET idAlbumInfo = (SELECT idAlbum FROM albuminfo WHERE albuminfo.idAlbumInfo = albuminfosong.idAlbumInfo)");
3865     m_pDS->exec(PrepareSQL("UPDATE album_new SET lastScraped='%s' WHERE idAlbum IN (SELECT idAlbum FROM albuminfo)", CDateTime::GetCurrentDateTime().GetAsDBDateTime().c_str()));
3866     m_pDS->exec("DROP TABLE album");
3867     m_pDS->exec("DROP TABLE albuminfo");
3868     m_pDS->exec("ALTER TABLE album_new RENAME TO album");
3869   }
3870   if (version < 40)
3871   {
3872     m_pDS->exec("CREATE TABLE artist_new ( idArtist integer primary key, "
3873                 " strArtist varchar(256), strMusicBrainzArtistID text, "
3874                 " strBorn text, strFormed text, strGenres text, strMoods text, "
3875                 " strStyles text, strInstruments text, strBiography text, "
3876                 " strDied text, strDisbanded text, strYearsActive text, "
3877                 " strImage text, strFanart text, "
3878                 " lastScraped varchar(20) default NULL, "
3879                 " dateAdded varchar (20) default NULL)");
3880     m_pDS->exec("INSERT INTO artist_new "
3881                 "(idArtist, strArtist, strMusicBrainzArtistID, "
3882                 " strBorn, strFormed, strGenres, strMoods, "
3883                 " strStyles , strInstruments , strBiography , "
3884                 " strDied, strDisbanded, strYearsActive, "
3885                 " strImage, strFanart) "
3886                 " SELECT "
3887                 " artist.idArtist, "
3888                 " strArtist, strMusicBrainzArtistID, "
3889                 " strBorn, strFormed, strGenres, strMoods, "
3890                 " strStyles, strInstruments, strBiography, "
3891                 " strDied, strDisbanded, strYearsActive, "
3892                 " strImage, strFanart "
3893                 " FROM artist "
3894                 " LEFT JOIN artistinfo ON artist.idArtist = artistinfo.idArtist");
3895     m_pDS->exec(PrepareSQL("UPDATE artist_new SET lastScraped='%s' WHERE idArtist IN (SELECT idArtist FROM artistinfo)", CDateTime::GetCurrentDateTime().GetAsDBDateTime().c_str()));
3896     m_pDS->exec("DROP TABLE artist");
3897     m_pDS->exec("DROP TABLE artistinfo");
3898     m_pDS->exec("ALTER TABLE artist_new RENAME TO artist");
3899   }
3900   if (version < 42)
3901   {
3902     m_pDS->exec("ALTER TABLE album_artist ADD strArtist text\n");
3903     m_pDS->exec("ALTER TABLE song_artist ADD strArtist text\n");
3904     // populate these
3905     map<int, string> artists;
3906     CStdString sql = "select idArtist,strArtist from artist";
3907     m_pDS->query(sql.c_str());
3908     while (!m_pDS->eof())
3909     {
3910       m_pDS2->exec(PrepareSQL("UPDATE song_artist SET strArtist='%s' where idArtist=%i", m_pDS->fv(1).get_asString().c_str(), m_pDS->fv(0).get_asInt()));
3911       m_pDS2->exec(PrepareSQL("UPDATE album_artist SET strArtist='%s' where idArtist=%i", m_pDS->fv(1).get_asString().c_str(), m_pDS->fv(0).get_asInt()));
3912       m_pDS->next();
3913     }
3914     // drop the last separator if more than one
3915     m_pDS->exec("UPDATE song_artist SET strJoinPhrase = '' WHERE 100*idSong+iOrder IN (SELECT id FROM (SELECT 100*idSong+max(iOrder) AS id FROM song_artist GROUP BY idSong) AS sub)");
3916     m_pDS->exec("UPDATE album_artist SET strJoinPhrase = '' WHERE 100*idAlbum+iOrder IN (SELECT id FROM (SELECT 100*idAlbum+max(iOrder) AS id FROM album_artist GROUP BY idAlbum) AS sub)");
3917   }
3918 }
3919
3920 int CMusicDatabase::GetSchemaVersion() const
3921 {
3922   return 46;
3923 }
3924
3925 unsigned int CMusicDatabase::GetSongIDs(const Filter &filter, vector<pair<int,int> > &songIDs)
3926 {
3927   try
3928   {
3929     if (NULL == m_pDB.get()) return 0;
3930     if (NULL == m_pDS.get()) return 0;
3931
3932     CStdString strSQL = "select idSong from songview ";
3933     if (!CDatabase::BuildSQL(strSQL, filter, strSQL))
3934       return false;
3935
3936     if (!m_pDS->query(strSQL.c_str())) return 0;
3937     songIDs.clear();
3938     if (m_pDS->num_rows() == 0)
3939     {
3940       m_pDS->close();
3941       return 0;
3942     }
3943     songIDs.reserve(m_pDS->num_rows());
3944     while (!m_pDS->eof())
3945     {
3946       songIDs.push_back(make_pair<int,int>(1,m_pDS->fv(song_idSong).get_asInt()));
3947       m_pDS->next();
3948     }    // cleanup
3949     m_pDS->close();
3950     return songIDs.size();
3951   }
3952   catch (...)
3953   {
3954     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, filter.where.c_str());
3955   }
3956   return 0;
3957 }
3958
3959 int CMusicDatabase::GetSongsCount(const Filter &filter)
3960 {
3961   try
3962   {
3963     if (NULL == m_pDB.get()) return 0;
3964     if (NULL == m_pDS.get()) return 0;
3965
3966     CStdString strSQL = "select count(idSong) as NumSongs from songview ";
3967     if (!CDatabase::BuildSQL(strSQL, filter, strSQL))
3968       return false;
3969
3970     if (!m_pDS->query(strSQL.c_str())) return false;
3971     if (m_pDS->num_rows() == 0)
3972     {
3973       m_pDS->close();
3974       return 0;
3975     }
3976
3977     int iNumSongs = m_pDS->fv("NumSongs").get_asInt();
3978     // cleanup
3979     m_pDS->close();
3980     return iNumSongs;
3981   }
3982   catch (...)
3983   {
3984     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, filter.where.c_str());
3985   }
3986   return 0;
3987 }
3988
3989 bool CMusicDatabase::GetAlbumPath(int idAlbum, CStdString& path)
3990 {
3991   try
3992   {
3993     if (NULL == m_pDB.get()) return false;
3994     if (NULL == m_pDS2.get()) return false;
3995
3996     path.clear();
3997
3998     CStdString strSQL=PrepareSQL("select strPath from song join path on song.idPath = path.idPath where song.idAlbum=%ld", idAlbum);
3999     if (!m_pDS2->query(strSQL.c_str())) return false;
4000     int iRowsFound = m_pDS2->num_rows();
4001     if (iRowsFound == 0)
4002     {
4003       m_pDS2->close();
4004       return false;
4005     }
4006
4007     // if this returns more than one path, we just grab the first one.  It's just for determining where to obtain + place
4008     // a local thumbnail
4009     path = m_pDS2->fv("strPath").get_asString();
4010
4011     m_pDS2->close(); // cleanup recordset data
4012     return true;
4013   }
4014   catch (...)
4015   {
4016     CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idAlbum);
4017   }
4018
4019   return false;
4020 }
4021
4022 bool CMusicDatabase::SaveAlbumThumb(int idAlbum, const CStdString& strThumb)
4023 {
4024   SetArtForItem(idAlbum, "album", "thumb", strThumb);
4025   // TODO: We should prompt the user to update the art for songs
4026   CStdString sql = PrepareSQL("UPDATE art"
4027                               " SET url='-'"
4028                               " WHERE media_type='song'"
4029                               " AND type='thumb'"
4030                               " AND media_id IN"
4031                               " (SELECT idSong FROM song WHERE idAlbum=%ld)", idAlbum);
4032   ExecuteQuery(sql);
4033   return true;
4034 }
4035
4036 bool CMusicDatabase::GetArtistPath(int idArtist, CStdString &basePath)
4037 {
4038   try
4039   {
4040     if (NULL == m_pDB.get()) return false;
4041     if (NULL == m_pDS2.get()) return false;
4042
4043     // find all albums from this artist, and all the paths to the songs from those albums
4044     CStdString strSQL=PrepareSQL("SELECT strPath"
4045                                  "  FROM album_artist"
4046                                  "  JOIN song "
4047                                  "    ON album_artist.idAlbum = song.idAlbum"
4048                                  "  JOIN path"
4049                                  "    ON song.idPath = path.idPath"
4050                                  " WHERE album_artist.idArtist = %i"
4051                                  " GROUP BY song.idPath", idArtist);
4052
4053     // run query
4054     if (!m_pDS2->query(strSQL.c_str())) return false;
4055     int iRowsFound = m_pDS2->num_rows();
4056     if (iRowsFound == 0)
4057     {
4058       m_pDS2->close();
4059       return false;
4060     }
4061
4062     // special case for single path - assume that we're in an artist/album/songs filesystem
4063     if (iRowsFound == 1)
4064     {
4065       URIUtils::GetParentPath(m_pDS2->fv("strPath").get_asString(), basePath);
4066       m_pDS2->close();
4067       return true;
4068     }
4069
4070     // find the common path (if any) to these albums
4071     basePath.clear();
4072     while (!m_pDS2->eof())
4073     {
4074       CStdString path = m_pDS2->fv("strPath").get_asString();
4075       if (basePath.empty())
4076         basePath = path;
4077       else
4078         URIUtils::GetCommonPath(basePath,path);
4079
4080       m_pDS2->next();
4081     }
4082
4083     // cleanup
4084     m_pDS2->close();
4085     return true;
4086
4087   }
4088   catch (...)
4089   {
4090     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4091   }
4092   return false;
4093 }
4094
4095 int CMusicDatabase::GetArtistByName(const CStdString& strArtist)
4096 {
4097   try
4098   {
4099     if (NULL == m_pDB.get()) return false;
4100     if (NULL == m_pDS.get()) return false;
4101
4102     CStdString strSQL=PrepareSQL("select idArtist from artist where artist.strArtist like '%s'", strArtist.c_str());
4103
4104     // run query
4105     if (!m_pDS->query(strSQL.c_str())) return false;
4106     int iRowsFound = m_pDS->num_rows();
4107     if (iRowsFound != 1)
4108     {
4109       m_pDS->close();
4110       return -1;
4111     }
4112     int lResult = m_pDS->fv("artist.idArtist").get_asInt();
4113     m_pDS->close();
4114     return lResult;
4115   }
4116   catch (...)
4117   {
4118     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4119   }
4120   return -1;
4121 }
4122
4123 int CMusicDatabase::GetAlbumByName(const CStdString& strAlbum, const CStdString& strArtist)
4124 {
4125   try
4126   {
4127     if (NULL == m_pDB.get()) return false;
4128     if (NULL == m_pDS.get()) return false;
4129
4130     CStdString strSQL;
4131     if (strArtist.empty())
4132       strSQL=PrepareSQL("SELECT idAlbum FROM album WHERE album.strAlbum LIKE '%s'", strAlbum.c_str());
4133     else
4134       strSQL=PrepareSQL("SELECT album.idAlbum FROM album WHERE album.strAlbum LIKE '%s' AND album.strArtists LIKE '%s'", strAlbum.c_str(),strArtist.c_str());
4135     // run query
4136     if (!m_pDS->query(strSQL.c_str())) return false;
4137     int iRowsFound = m_pDS->num_rows();
4138     if (iRowsFound != 1)
4139     {
4140       m_pDS->close();
4141       return -1;
4142     }
4143     return m_pDS->fv("album.idAlbum").get_asInt();
4144   }
4145   catch (...)
4146   {
4147     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4148   }
4149   return -1;
4150 }
4151
4152 int CMusicDatabase::GetAlbumByName(const CStdString& strAlbum, const std::vector<std::string>& artist)
4153 {
4154   return GetAlbumByName(strAlbum, StringUtils::Join(artist, g_advancedSettings.m_musicItemSeparator));
4155 }
4156
4157 CStdString CMusicDatabase::GetGenreById(int id)
4158 {
4159   return GetSingleValue("genre", "strGenre", PrepareSQL("idGenre=%i", id));
4160 }
4161
4162 CStdString CMusicDatabase::GetArtistById(int id)
4163 {
4164   return GetSingleValue("artist", "strArtist", PrepareSQL("idArtist=%i", id));
4165 }
4166
4167 CStdString CMusicDatabase::GetAlbumById(int id)
4168 {
4169   return GetSingleValue("album", "strAlbum", PrepareSQL("idAlbum=%i", id));
4170 }
4171
4172 int CMusicDatabase::GetGenreByName(const CStdString& strGenre)
4173 {
4174   try
4175   {
4176     if (NULL == m_pDB.get()) return false;
4177     if (NULL == m_pDS.get()) return false;
4178
4179     CStdString strSQL;
4180     strSQL=PrepareSQL("select idGenre from genre where genre.strGenre like '%s'", strGenre.c_str());
4181     // run query
4182     if (!m_pDS->query(strSQL.c_str())) return false;
4183     int iRowsFound = m_pDS->num_rows();
4184     if (iRowsFound != 1)
4185     {
4186       m_pDS->close();
4187       return -1;
4188     }
4189     return m_pDS->fv("genre.idGenre").get_asInt();
4190   }
4191   catch (...)
4192   {
4193     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4194   }
4195   return -1;
4196 }
4197
4198 bool CMusicDatabase::GetRandomSong(CFileItem* item, int& idSong, const Filter &filter)
4199 {
4200   try
4201   {
4202     idSong = -1;
4203
4204     if (NULL == m_pDB.get()) return false;
4205     if (NULL == m_pDS.get()) return false;
4206
4207     // We don't use PrepareSQL here, as the WHERE clause is already formatted
4208     CStdString strSQL = PrepareSQL("select %s from songview ", !filter.fields.empty() ? filter.fields.c_str() : "*");
4209     Filter extFilter = filter;
4210     extFilter.AppendOrder(PrepareSQL("RANDOM()"));
4211     extFilter.limit = "1";
4212
4213     if (!CDatabase::BuildSQL(strSQL, extFilter, strSQL))
4214       return false;
4215
4216     CLog::Log(LOGDEBUG, "%s query = %s", __FUNCTION__, strSQL.c_str());
4217     // run query
4218     if (!m_pDS->query(strSQL.c_str()))
4219       return false;
4220     int iRowsFound = m_pDS->num_rows();
4221     if (iRowsFound != 1)
4222     {
4223       m_pDS->close();
4224       return false;
4225     }
4226     GetFileItemFromDataset(item, CMusicDbUrl());
4227     idSong = m_pDS->fv("songview.idSong").get_asInt();
4228     m_pDS->close();
4229     return true;
4230   }
4231   catch(...)
4232   {
4233     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, filter.where.c_str());
4234   }
4235   return false;
4236 }
4237
4238 bool CMusicDatabase::GetCompilationAlbums(const CStdString& strBaseDir, CFileItemList& items)
4239 {
4240   CMusicDbUrl musicUrl;
4241   if (!musicUrl.FromString(strBaseDir))
4242     return false;
4243
4244   musicUrl.AddOption("compilation", true);
4245   
4246   Filter filter;
4247   return GetAlbumsByWhere(musicUrl.ToString(), filter, items);
4248 }
4249
4250 bool CMusicDatabase::GetCompilationSongs(const CStdString& strBaseDir, CFileItemList& items)
4251 {
4252   CMusicDbUrl musicUrl;
4253   if (!musicUrl.FromString(strBaseDir))
4254     return false;
4255
4256   musicUrl.AddOption("compilation", true);
4257
4258   Filter filter;
4259   return GetSongsByWhere(musicUrl.ToString(), filter, items);
4260 }
4261
4262 int CMusicDatabase::GetCompilationAlbumsCount()
4263 {
4264   return strtol(GetSingleValue("album", "count(idAlbum)", "bCompilation = 1"), NULL, 10);
4265 }
4266
4267 void CMusicDatabase::SplitString(const CStdString &multiString, vector<string> &vecStrings, CStdString &extraStrings)
4268 {
4269   vecStrings = StringUtils::Split(multiString, g_advancedSettings.m_musicItemSeparator);
4270   for (unsigned int i = 1; i < vecStrings.size(); i++)
4271     extraStrings += g_advancedSettings.m_musicItemSeparator + CStdString(vecStrings[i]);
4272 }
4273
4274 bool CMusicDatabase::SetPathHash(const CStdString &path, const CStdString &hash)
4275 {
4276   try
4277   {
4278     if (NULL == m_pDB.get()) return false;
4279     if (NULL == m_pDS.get()) return false;
4280
4281     if (hash.empty())
4282     { // this is an empty folder - we need only add it to the path table
4283       // if the path actually exists
4284       if (!CDirectory::Exists(path))
4285         return false;
4286     }
4287     int idPath = AddPath(path);
4288     if (idPath < 0) return false;
4289
4290     CStdString strSQL=PrepareSQL("update path set strHash='%s' where idPath=%ld", hash.c_str(), idPath);
4291     m_pDS->exec(strSQL.c_str());
4292
4293     return true;
4294   }
4295   catch (...)
4296   {
4297     CLog::Log(LOGERROR, "%s (%s, %s) failed", __FUNCTION__, path.c_str(), hash.c_str());
4298   }
4299
4300   return false;
4301 }
4302
4303 bool CMusicDatabase::GetPathHash(const CStdString &path, CStdString &hash)
4304 {
4305   try
4306   {
4307     if (NULL == m_pDB.get()) return false;
4308     if (NULL == m_pDS.get()) return false;
4309
4310     CStdString strSQL=PrepareSQL("select strHash from path where strPath='%s'", path.c_str());
4311     m_pDS->query(strSQL.c_str());
4312     if (m_pDS->num_rows() == 0)
4313       return false;
4314     hash = m_pDS->fv("strHash").get_asString();
4315     return true;
4316   }
4317   catch (...)
4318   {
4319     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, path.c_str());
4320   }
4321
4322   return false;
4323 }
4324
4325 bool CMusicDatabase::RemoveSongsFromPath(const CStdString &path1, MAPSONGS& songs, bool exact)
4326 {
4327   // We need to remove all songs from this path, as their tags are going
4328   // to be re-read.  We need to remove all songs from the song table + all links to them
4329   // from the song link tables (as otherwise if a song is added back
4330   // to the table with the same idSong, these tables can't be cleaned up properly later)
4331
4332   // TODO: SQLite probably doesn't allow this, but can we rely on that??
4333
4334   // We don't need to remove orphaned albums at this point as in AddAlbum() we check
4335   // first whether the album has already been read during this scan, and if it hasn't
4336   // we check whether it's in the table and update accordingly at that point, removing the entries from
4337   // the album link tables.  The only failure point for this is albums
4338   // that span multiple folders, where just the files in one folder have been changed.  In this case
4339   // any linked fields that are only in the files that haven't changed will be removed.  Clearly
4340   // the primary albumartist still matches (as that's what we looked up based on) so is this really
4341   // an issue?  I don't think it is, as those artists will still have links to the album via the songs
4342   // which is generally what we rely on, so the only failure point is albumartist lookup.  In this
4343   // case, it will return only things in the album_artist table from the newly updated songs (and
4344   // only if they have additional artists).  I think the effect of this is minimal at best, as ALL
4345   // songs in the album should have the same albumartist!
4346
4347   // we also remove the path at this point as it will be added later on if the
4348   // path still exists.
4349   // After scanning we then remove the orphaned artists, genres and thumbs.
4350
4351   // Note: when used to remove all songs from a path and its subpath (exact=false), this
4352   // does miss archived songs.
4353   CStdString path(path1);
4354   try
4355   {
4356     if (!URIUtils::HasSlashAtEnd(path))
4357       URIUtils::AddSlashAtEnd(path);
4358
4359     if (NULL == m_pDB.get()) return false;
4360     if (NULL == m_pDS.get()) return false;
4361
4362     CStdString where;
4363     if (exact)
4364       where = PrepareSQL(" where strPath='%s'", path.c_str());
4365     else
4366       where = PrepareSQL(" where SUBSTR(strPath,1,%i)='%s'", StringUtils::utf8_strlen(path.c_str()), path.c_str());
4367     CStdString sql = "select * from songview" + where;
4368     if (!m_pDS->query(sql.c_str())) return false;
4369     int iRowsFound = m_pDS->num_rows();
4370     if (iRowsFound > 0)
4371     {
4372       std::vector<std::string> songIds;
4373       while (!m_pDS->eof())
4374       {
4375         CSong song = GetSongFromDataset();
4376         song.strThumb = GetArtForItem(song.idSong, "song", "thumb");
4377         songs.insert(make_pair(song.strFileName, song));
4378         songIds.push_back(PrepareSQL("%i", song.idSong));
4379         m_pDS->next();
4380       }
4381       m_pDS->close();
4382
4383       //TODO: move this below the m_pDS->exec block, once UPnP doesn't rely on this anymore
4384       for (MAPSONGS::iterator songit = songs.begin(); songit != songs.end(); ++songit)
4385         AnnounceRemove("song", songit->second.idSong);
4386
4387       // and delete all songs, and anything linked to them
4388       sql = "delete from song where idSong in (" + StringUtils::Join(songIds, ",") + ")";
4389       m_pDS->exec(sql.c_str());
4390     }
4391     // and remove the path as well (it'll be re-added later on with the new hash if it's non-empty)
4392     sql = "delete from path" + where;
4393     m_pDS->exec(sql.c_str());
4394     return iRowsFound > 0;
4395   }
4396   catch (...)
4397   {
4398     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, path.c_str());
4399   }
4400   return false;
4401 }
4402
4403 bool CMusicDatabase::GetPaths(set<string> &paths)
4404 {
4405   try
4406   {
4407     if (NULL == m_pDB.get()) return false;
4408     if (NULL == m_pDS.get()) return false;
4409
4410     paths.clear();
4411
4412     // find all paths
4413     if (!m_pDS->query("select strPath from path")) return false;
4414     int iRowsFound = m_pDS->num_rows();
4415     if (iRowsFound == 0)
4416     {
4417       m_pDS->close();
4418       return true;
4419     }
4420     while (!m_pDS->eof())
4421     {
4422       paths.insert(m_pDS->fv("strPath").get_asString());
4423       m_pDS->next();
4424     }
4425     m_pDS->close();
4426     return true;
4427   }
4428   catch (...)
4429   {
4430     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4431   }
4432   return false;
4433 }
4434
4435 bool CMusicDatabase::SetSongRating(const CStdString &filePath, char rating)
4436 {
4437   try
4438   {
4439     if (filePath.empty()) return false;
4440     if (NULL == m_pDB.get()) return false;
4441     if (NULL == m_pDS.get()) return false;
4442
4443     int songID = GetSongIDFromPath(filePath);
4444     if (-1 == songID) return false;
4445
4446     CStdString sql = PrepareSQL("update song set rating='%c' where idSong = %i", rating, songID);
4447     m_pDS->exec(sql.c_str());
4448     return true;
4449   }
4450   catch (...)
4451   {
4452     CLog::Log(LOGERROR, "%s (%s,%c) failed", __FUNCTION__, filePath.c_str(), rating);
4453   }
4454   return false;
4455 }
4456
4457 int CMusicDatabase::GetSongIDFromPath(const CStdString &filePath)
4458 {
4459   // grab the where string to identify the song id
4460   CURL url(filePath);
4461   if (url.GetProtocol()=="musicdb")
4462   {
4463     CStdString strFile=URIUtils::GetFileName(filePath);
4464     URIUtils::RemoveExtension(strFile);
4465     return atol(strFile.c_str());
4466   }
4467   // hit the db
4468   try
4469   {
4470     if (NULL == m_pDB.get()) return -1;
4471     if (NULL == m_pDS.get()) return -1;
4472
4473     CStdString strPath = URIUtils::GetDirectory(filePath);
4474     URIUtils::AddSlashAtEnd(strPath);
4475     DWORD crc = ComputeCRC(filePath);
4476
4477     CStdString sql = PrepareSQL("select idSong from song join path on song.idPath = path.idPath where song.dwFileNameCRC='%ul'and path.strPath='%s'", crc, strPath.c_str());
4478     if (!m_pDS->query(sql.c_str())) return -1;
4479
4480     if (m_pDS->num_rows() == 0)
4481     {
4482       m_pDS->close();
4483       return -1;
4484     }
4485
4486     int songID = m_pDS->fv("idSong").get_asInt();
4487     m_pDS->close();
4488     return songID;
4489   }
4490   catch (...)
4491   {
4492     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, filePath.c_str());
4493   }
4494   return -1;
4495 }
4496
4497 bool CMusicDatabase::CommitTransaction()
4498 {
4499   if (CDatabase::CommitTransaction())
4500   { // number of items in the db has likely changed, so reset the infomanager cache
4501     g_infoManager.SetLibraryBool(LIBRARY_HAS_MUSIC, GetSongsCount() > 0);
4502     return true;
4503   }
4504   return false;
4505 }
4506
4507 bool CMusicDatabase::SetScraperForPath(const CStdString& strPath, const ADDON::ScraperPtr& scraper)
4508 {
4509   try
4510   {
4511     if (NULL == m_pDB.get()) return false;
4512     if (NULL == m_pDS.get()) return false;
4513
4514     // wipe old settings
4515     CStdString strSQL = PrepareSQL("delete from content where strPath='%s'",strPath.c_str());
4516     m_pDS->exec(strSQL.c_str());
4517
4518     // insert new settings
4519     strSQL = PrepareSQL("insert into content (strPath, strScraperPath, strContent, strSettings) values ('%s','%s','%s','%s')",
4520       strPath.c_str(), scraper->ID().c_str(), ADDON::TranslateContent(scraper->Content()).c_str(), scraper->GetPathSettings().c_str());
4521     m_pDS->exec(strSQL.c_str());
4522
4523     return true;
4524   }
4525   catch (...)
4526   {
4527     CLog::Log(LOGERROR, "%s - (%s) failed", __FUNCTION__, strPath.c_str());
4528   }
4529   return false;
4530 }
4531
4532 bool CMusicDatabase::GetScraperForPath(const CStdString& strPath, ADDON::ScraperPtr& info, const ADDON::TYPE &type)
4533 {
4534   try
4535   {
4536     if (NULL == m_pDB.get()) return false;
4537     if (NULL == m_pDS.get()) return false;
4538
4539     CStdString strSQL = PrepareSQL("select * from content where strPath='%s'",strPath.c_str());
4540     m_pDS->query(strSQL.c_str());
4541     if (m_pDS->eof()) // no info set for path - fallback logic commencing
4542     {
4543       CQueryParams params;
4544       CDirectoryNode::GetDatabaseInfo(strPath, params);
4545       if (params.GetGenreId() != -1) // check genre
4546       {
4547         strSQL = PrepareSQL("select * from content where strPath='musicdb://genres/%i/'",params.GetGenreId());
4548         m_pDS->query(strSQL.c_str());
4549       }
4550       if (m_pDS->eof() && params.GetAlbumId() != -1) // check album
4551       {
4552         strSQL = PrepareSQL("select * from content where strPath='musicdb://albums/%i/'",params.GetAlbumId());
4553         m_pDS->query(strSQL.c_str());
4554         if (m_pDS->eof()) // general albums setting
4555         {
4556           strSQL = PrepareSQL("select * from content where strPath='musicdb://albums/'");
4557           m_pDS->query(strSQL.c_str());
4558         }
4559       }
4560       if (m_pDS->eof() && params.GetArtistId() != -1) // check artist
4561       {
4562         strSQL = PrepareSQL("select * from content where strPath='musicdb://artists/%i/'",params.GetArtistId());
4563         m_pDS->query(strSQL.c_str());
4564
4565         if (m_pDS->eof()) // general artist setting
4566         {
4567           strSQL = PrepareSQL("select * from content where strPath='musicdb://artists/'");
4568           m_pDS->query(strSQL.c_str());
4569         }
4570       }
4571     }
4572
4573     if (!m_pDS->eof())
4574     { // try and ascertain scraper for this path
4575       CONTENT_TYPE content = ADDON::TranslateContent(m_pDS->fv("content.strContent").get_asString());
4576       CStdString scraperUUID = m_pDS->fv("content.strScraperPath").get_asString();
4577
4578       if (content != CONTENT_NONE)
4579       { // content set, use pre configured or default scraper
4580         ADDON::AddonPtr addon;
4581         if (!scraperUUID.empty() && ADDON::CAddonMgr::Get().GetAddon(scraperUUID, addon) && addon)
4582         {
4583           info = boost::dynamic_pointer_cast<ADDON::CScraper>(addon->Clone());
4584           if (!info)
4585             return false;
4586           // store this path's settings
4587           info->SetPathSettings(content, m_pDS->fv("content.strSettings").get_asString());
4588         }
4589       }
4590       else
4591       { // use default scraper of the requested type
4592         ADDON::AddonPtr defaultScraper;
4593         if (ADDON::CAddonMgr::Get().GetDefault(type, defaultScraper))
4594         {
4595           info = boost::dynamic_pointer_cast<ADDON::CScraper>(defaultScraper->Clone());
4596         }
4597       }
4598     }
4599     m_pDS->close();
4600
4601     if (!info)
4602     { // use default music scraper instead
4603       ADDON::AddonPtr addon;
4604       if(ADDON::CAddonMgr::Get().GetDefault(type, addon))
4605       {
4606         info = boost::dynamic_pointer_cast<ADDON::CScraper>(addon);
4607         return (info);
4608       }
4609       else
4610         return false;
4611     }
4612
4613     return true;
4614   }
4615   catch (...)
4616   {
4617     CLog::Log(LOGERROR, "%s -(%s) failed", __FUNCTION__, strPath.c_str());
4618   }
4619   return false;
4620 }
4621
4622 bool CMusicDatabase::ScraperInUse(const CStdString &scraperID) const
4623 {
4624   try
4625   {
4626     if (NULL == m_pDB.get()) return false;
4627     if (NULL == m_pDS.get()) return false;
4628
4629     CStdString sql = PrepareSQL("select count(1) from content where strScraperPath='%s'",scraperID.c_str());
4630     if (!m_pDS->query(sql.c_str()) || m_pDS->num_rows() == 0)
4631       return false;
4632     bool found = m_pDS->fv(0).get_asInt() > 0;
4633     m_pDS->close();
4634     return found;
4635   }
4636   catch (...)
4637   {
4638     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, scraperID.c_str());
4639   }
4640   return false;
4641 }
4642
4643 bool CMusicDatabase::GetItems(const CStdString &strBaseDir, CFileItemList &items, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */)
4644 {
4645   CMusicDbUrl musicUrl;
4646   if (!musicUrl.FromString(strBaseDir))
4647     return false;
4648
4649   return GetItems(strBaseDir, musicUrl.GetType(), items, filter, sortDescription);
4650 }
4651
4652 bool CMusicDatabase::GetItems(const CStdString &strBaseDir, const CStdString &itemType, CFileItemList &items, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */)
4653 {
4654   if (itemType.Equals("genres"))
4655     return GetGenresNav(strBaseDir, items, filter);
4656   else if (itemType.Equals("years"))
4657     return GetYearsNav(strBaseDir, items, filter);
4658   else if (itemType.Equals("artists"))
4659     return GetArtistsNav(strBaseDir, items, !CSettings::Get().GetBool("musiclibrary.showcompilationartists"), -1, -1, -1, filter, sortDescription);
4660   else if (itemType.Equals("albums"))
4661     return GetAlbumsByWhere(strBaseDir, filter, items, sortDescription);
4662   else if (itemType.Equals("songs"))
4663     return GetSongsByWhere(strBaseDir, filter, items, sortDescription);
4664
4665   return false;
4666 }
4667
4668 CStdString CMusicDatabase::GetItemById(const CStdString &itemType, int id)
4669 {
4670   if (itemType.Equals("genres"))
4671     return GetGenreById(id);
4672   else if (itemType.Equals("years"))
4673     return StringUtils::Format("%d", id);
4674   else if (itemType.Equals("artists"))
4675     return GetArtistById(id);
4676   else if (itemType.Equals("albums"))
4677     return GetAlbumById(id);
4678
4679   return "";
4680 }
4681
4682 void CMusicDatabase::ExportToXML(const CStdString &xmlFile, bool singleFiles, bool images, bool overwrite)
4683 {
4684   try
4685   {
4686     if (NULL == m_pDB.get()) return;
4687     if (NULL == m_pDS.get()) return;
4688     if (NULL == m_pDS2.get()) return;
4689
4690     // find all albums
4691     vector<int> albumIds;
4692     CStdString sql = "select idAlbum FROM album WHERE lastScraped IS NOT NULL";
4693     m_pDS->query(sql.c_str());
4694
4695     int total = m_pDS->num_rows();
4696     int current = 0;
4697
4698     albumIds.reserve(total);
4699     while (!m_pDS->eof())
4700     {
4701       albumIds.push_back(m_pDS->fv("idAlbum").get_asInt());
4702       m_pDS->next();
4703     }
4704     m_pDS->close();
4705
4706     CGUIDialogProgress *progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
4707     if (progress)
4708     {
4709       progress->SetHeading(20196);
4710       progress->SetLine(0, 650);
4711       progress->SetLine(1, "");
4712       progress->SetLine(2, "");
4713       progress->SetPercentage(0);
4714       progress->StartModal();
4715       progress->ShowProgressBar(true);
4716     }
4717
4718     // create our xml document
4719     CXBMCTinyXML xmlDoc;
4720     TiXmlDeclaration decl("1.0", "UTF-8", "yes");
4721     xmlDoc.InsertEndChild(decl);
4722     TiXmlNode *pMain = NULL;
4723     if (singleFiles)
4724       pMain = &xmlDoc;
4725     else
4726     {
4727       TiXmlElement xmlMainElement("musicdb");
4728       pMain = xmlDoc.InsertEndChild(xmlMainElement);
4729     }
4730     for (vector<int>::iterator albumId = albumIds.begin(); albumId != albumIds.end(); ++albumId)
4731     {
4732       CAlbum album;
4733       GetAlbum(*albumId, album);
4734       CStdString strPath;
4735       GetAlbumPath(*albumId, strPath);
4736       album.Save(pMain, "album", strPath);
4737       if (singleFiles)
4738       {
4739         if (!CDirectory::Exists(strPath))
4740           CLog::Log(LOGDEBUG, "%s - Not exporting item %s as it does not exist", __FUNCTION__, strPath.c_str());
4741         else
4742         {
4743           CStdString nfoFile = URIUtils::AddFileToFolder(strPath, "album.nfo");
4744           if (overwrite || !CFile::Exists(nfoFile))
4745           {
4746             if (!xmlDoc.SaveFile(nfoFile))
4747               CLog::Log(LOGERROR, "%s: Album nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str());
4748           }
4749
4750           if (images)
4751           {
4752             string thumb = GetArtForItem(album.idAlbum, "album", "thumb");
4753             if (!thumb.empty() && (overwrite || !CFile::Exists(URIUtils::AddFileToFolder(strPath,"folder.jpg"))))
4754               CTextureCache::Get().Export(thumb, URIUtils::AddFileToFolder(strPath,"folder.jpg"));
4755           }
4756           xmlDoc.Clear();
4757           TiXmlDeclaration decl("1.0", "UTF-8", "yes");
4758           xmlDoc.InsertEndChild(decl);
4759         }
4760       }
4761
4762       if ((current % 50) == 0 && progress)
4763       {
4764         progress->SetLine(1, album.strAlbum);
4765         progress->SetPercentage(current * 100 / total);
4766         progress->Progress();
4767         if (progress->IsCanceled())
4768         {
4769           progress->Close();
4770           m_pDS->close();
4771           return;
4772         }
4773       }
4774       current++;
4775     }
4776
4777     // find all artists
4778     vector<int> artistIds;
4779     CStdString artistSQL = "SELECT idArtist FROM artist where lastScraped IS NOT NULL";
4780     m_pDS->query(artistSQL.c_str());
4781     total = m_pDS->num_rows();
4782     current = 0;
4783     artistIds.reserve(total);
4784     while (!m_pDS->eof())
4785     {
4786       artistIds.push_back(m_pDS->fv("idArtist").get_asInt());
4787       m_pDS->next();
4788     }
4789     m_pDS->close();
4790
4791     for (vector<int>::iterator artistId = artistIds.begin(); artistId != artistIds.end(); ++artistId)
4792     {
4793       CArtist artist;
4794       GetArtist(*artistId, artist);
4795       CStdString strPath;
4796       GetArtistPath(artist.idArtist,strPath);
4797       artist.Save(pMain, "artist", strPath);
4798
4799       map<string, string> artwork;
4800       if (GetArtForItem(artist.idArtist, "artist", artwork) && !singleFiles)
4801       { // append to the XML
4802         TiXmlElement additionalNode("art");
4803         for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
4804           XMLUtils::SetString(&additionalNode, i->first.c_str(), i->second);
4805         pMain->LastChild()->InsertEndChild(additionalNode);
4806       }
4807       if (singleFiles)
4808       {
4809         if (!CDirectory::Exists(strPath))
4810           CLog::Log(LOGDEBUG, "%s - Not exporting item %s as it does not exist", __FUNCTION__, strPath.c_str());
4811         else
4812         {
4813           CStdString nfoFile = URIUtils::AddFileToFolder(strPath, "artist.nfo");
4814           if (overwrite || !CFile::Exists(nfoFile))
4815           {
4816             if (!xmlDoc.SaveFile(nfoFile))
4817               CLog::Log(LOGERROR, "%s: Artist nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str());
4818           }
4819
4820           if (images && !artwork.empty())
4821           {
4822             CStdString savedThumb = URIUtils::AddFileToFolder(strPath,"folder.jpg");
4823             CStdString savedFanart = URIUtils::AddFileToFolder(strPath,"fanart.jpg");
4824             if (artwork.find("thumb") != artwork.end() && (overwrite || !CFile::Exists(savedThumb)))
4825               CTextureCache::Get().Export(artwork["thumb"], savedThumb);
4826             if (artwork.find("fanart") != artwork.end() && (overwrite || !CFile::Exists(savedFanart)))
4827               CTextureCache::Get().Export(artwork["fanart"], savedFanart);
4828           }
4829           xmlDoc.Clear();
4830           TiXmlDeclaration decl("1.0", "UTF-8", "yes");
4831           xmlDoc.InsertEndChild(decl);
4832         }
4833       }
4834
4835       if ((current % 50) == 0 && progress)
4836       {
4837         progress->SetLine(1, artist.strArtist);
4838         progress->SetPercentage(current * 100 / total);
4839         progress->Progress();
4840         if (progress->IsCanceled())
4841         {
4842           progress->Close();
4843           m_pDS->close();
4844           return;
4845         }
4846       }
4847       current++;
4848     }
4849
4850     if (progress)
4851       progress->Close();
4852
4853     xmlDoc.SaveFile(xmlFile);
4854   }
4855   catch (...)
4856   {
4857     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4858   }
4859 }
4860
4861 void CMusicDatabase::ImportFromXML(const CStdString &xmlFile)
4862 {
4863   CGUIDialogProgress *progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
4864   try
4865   {
4866     if (NULL == m_pDB.get()) return;
4867     if (NULL == m_pDS.get()) return;
4868
4869     CXBMCTinyXML xmlDoc;
4870     if (!xmlDoc.LoadFile(xmlFile))
4871       return;
4872
4873     TiXmlElement *root = xmlDoc.RootElement();
4874     if (!root) return;
4875
4876     if (progress)
4877     {
4878       progress->SetHeading(20197);
4879       progress->SetLine(0, 649);
4880       progress->SetLine(1, 330);
4881       progress->SetLine(2, "");
4882       progress->SetPercentage(0);
4883       progress->StartModal();
4884       progress->ShowProgressBar(true);
4885     }
4886
4887     TiXmlElement *entry = root->FirstChildElement();
4888     int current = 0;
4889     int total = 0;
4890     // first count the number of items...
4891     while (entry)
4892     {
4893       if (strnicmp(entry->Value(), "artist", 6)==0 ||
4894           strnicmp(entry->Value(), "album", 5)==0)
4895         total++;
4896       entry = entry->NextSiblingElement();
4897     }
4898
4899     BeginTransaction();
4900     entry = root->FirstChildElement();
4901     while (entry)
4902     {
4903       CStdString strTitle;
4904       if (strnicmp(entry->Value(), "artist", 6) == 0)
4905       {
4906         CArtist importedArtist;
4907         importedArtist.Load(entry);
4908         strTitle = importedArtist.strArtist;
4909         int idArtist = GetArtistByName(importedArtist.strArtist);
4910         if (idArtist > -1)
4911         {
4912           CArtist artist;
4913           GetArtist(idArtist, artist);
4914           artist.MergeScrapedArtist(importedArtist, true);
4915           UpdateArtist(artist);
4916         }
4917
4918         current++;
4919       }
4920       else if (strnicmp(entry->Value(), "album", 5) == 0)
4921       {
4922         CAlbum importedAlbum;
4923         importedAlbum.Load(entry);
4924         strTitle = importedAlbum.strAlbum;
4925         int idAlbum = GetAlbumByName(importedAlbum.strAlbum, importedAlbum.artist);
4926         if (idAlbum > -1)
4927         {
4928           CAlbum album;
4929           GetAlbum(idAlbum, album, true);
4930           album.MergeScrapedAlbum(importedAlbum, true);
4931           UpdateAlbum(album);
4932         }
4933
4934         current++;
4935       }
4936       entry = entry ->NextSiblingElement();
4937       if (progress && total)
4938       {
4939         progress->SetPercentage(current * 100 / total);
4940         progress->SetLine(2, strTitle);
4941         progress->Progress();
4942         if (progress->IsCanceled())
4943         {
4944           progress->Close();
4945           RollbackTransaction();
4946           return;
4947         }
4948       }
4949     }
4950     CommitTransaction();
4951
4952     g_infoManager.ResetLibraryBools();
4953   }
4954   catch (...)
4955   {
4956     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4957     RollbackTransaction();
4958   }
4959   if (progress)
4960     progress->Close();
4961 }
4962
4963 void CMusicDatabase::AddKaraokeData(int idSong, int iKaraokeNumber, DWORD crc)
4964 {
4965   try
4966   {
4967     CStdString strSQL;
4968
4969     // If song.iKaraokeNumber is non-zero, we already have it in the database. Just replace the song ID.
4970     if (iKaraokeNumber > 0)
4971     {
4972       CStdString strSQL = PrepareSQL("UPDATE karaokedata SET idSong=%i WHERE iKaraNumber=%i", idSong, iKaraokeNumber);
4973       m_pDS->exec(strSQL.c_str());
4974       return;
4975     }
4976
4977     // Get the maximum number allocated
4978     strSQL=PrepareSQL( "SELECT MAX(iKaraNumber) FROM karaokedata" );
4979     if (!m_pDS->query(strSQL.c_str())) return;
4980
4981     int iKaraokeNumber = g_advancedSettings.m_karaokeStartIndex;
4982
4983     if ( m_pDS->num_rows() == 1 )
4984       iKaraokeNumber = m_pDS->fv("MAX(iKaraNumber)").get_asInt() + 1;
4985
4986     // Add the data
4987     strSQL=PrepareSQL( "INSERT INTO karaokedata (iKaraNumber, idSong, iKaraDelay, strKaraEncoding, strKaralyrics, strKaraLyrFileCRC) "
4988         "VALUES( %i, %i, 0, NULL, NULL, '%ul' )", iKaraokeNumber, idSong, crc );
4989
4990     m_pDS->exec(strSQL.c_str());
4991   }
4992   catch (...)
4993   {
4994     CLog::Log(LOGERROR, "%s -(%i, %i) failed", __FUNCTION__, idSong, iKaraokeNumber);
4995   }
4996 }
4997
4998 bool CMusicDatabase::GetSongByKaraokeNumber(int number, CSong & song)
4999 {
5000   try
5001   {
5002     // Get info from karaoke db
5003     if (NULL == m_pDB.get()) return false;
5004     if (NULL == m_pDS.get()) return false;
5005
5006     CStdString strSQL=PrepareSQL("SELECT * FROM karaokedata where iKaraNumber=%ld", number);
5007
5008     if (!m_pDS->query(strSQL.c_str())) return false;
5009     if (m_pDS->num_rows() == 0)
5010     {
5011       m_pDS->close();
5012       return false;
5013     }
5014
5015     int idSong = m_pDS->fv("karaokedata.idSong").get_asInt();
5016     m_pDS->close();
5017
5018     return GetSong( idSong, song );
5019   }
5020   catch (...)
5021   {
5022     CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, number);
5023   }
5024
5025   return false;
5026 }
5027
5028 void CMusicDatabase::ExportKaraokeInfo(const CStdString & outFile, bool asHTML)
5029 {
5030   try
5031   {
5032     if (NULL == m_pDB.get()) return;
5033     if (NULL == m_pDS.get()) return;
5034
5035     // find all karaoke songs
5036     CStdString sql = "SELECT * FROM songview WHERE iKaraNumber > 0 ORDER BY strFileName";
5037
5038     m_pDS->query(sql.c_str());
5039
5040     int total = m_pDS->num_rows();
5041     int current = 0;
5042
5043     if ( total == 0 )
5044     {
5045       m_pDS->close();
5046       return;
5047     }
5048
5049     // Write the document
5050     XFILE::CFile file;
5051
5052     if ( !file.OpenForWrite( outFile, true ) )
5053       return;
5054
5055     CGUIDialogProgress *progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
5056     if (progress)
5057     {
5058       progress->SetHeading(asHTML ? 22034 : 22035);
5059       progress->SetLine(0, 650);
5060       progress->SetLine(1, "");
5061       progress->SetLine(2, "");
5062       progress->SetPercentage(0);
5063       progress->StartModal();
5064       progress->ShowProgressBar(true);
5065     }
5066
5067     CStdString outdoc;
5068     if ( asHTML )
5069     {
5070       outdoc = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></meta></head>\n"
5071           "<body>\n<table>\n";
5072
5073       file.Write( outdoc, outdoc.size() );
5074     }
5075
5076     while (!m_pDS->eof())
5077     {
5078       CSong song = GetSongFromDataset();
5079       CStdString songnum = StringUtils::Format("%06d", song.iKaraokeNumber);
5080
5081       if ( asHTML )
5082         outdoc = "<tr><td>" + songnum + "</td><td>" + StringUtils::Join(song.artist, g_advancedSettings.m_musicItemSeparator) + "</td><td>" + song.strTitle + "</td></tr>\r\n";
5083       else
5084         outdoc = songnum + "\t" + StringUtils::Join(song.artist, g_advancedSettings.m_musicItemSeparator) + "\t" + song.strTitle + "\t" + song.strFileName + "\r\n";
5085
5086       file.Write( outdoc, outdoc.size() );
5087
5088       if ((current % 50) == 0 && progress)
5089       {
5090         progress->SetPercentage(current * 100 / total);
5091         progress->Progress();
5092         if (progress->IsCanceled())
5093         {
5094           progress->Close();
5095           m_pDS->close();
5096           return;
5097         }
5098       }
5099       m_pDS->next();
5100       current++;
5101     }
5102
5103     m_pDS->close();
5104
5105     if ( asHTML )
5106     {
5107       outdoc = "</table>\n</body>\n</html>\n";
5108       file.Write( outdoc, outdoc.size() );
5109     }
5110
5111     file.Close();
5112
5113     if (progress)
5114       progress->Close();
5115   }
5116   catch (...)
5117   {
5118     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
5119   }
5120 }
5121
5122 void CMusicDatabase::ImportKaraokeInfo(const CStdString & inputFile)
5123 {
5124   CGUIDialogProgress *progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
5125
5126   try
5127   {
5128     if (NULL == m_pDB.get()) return;
5129
5130     XFILE::CFile file;
5131
5132     if ( !file.Open( inputFile ) )
5133     {
5134       CLog::Log( LOGERROR, "Cannot open karaoke import file %s", inputFile.c_str() );
5135       return;
5136     }
5137
5138     unsigned int size = (unsigned int) file.GetLength();
5139
5140     if ( !size )
5141       return;
5142
5143     // Read the file into memory array
5144     std::vector<char> data( size + 1 );
5145
5146     file.Seek( 0, SEEK_SET );
5147
5148     // Read the whole file
5149     if ( file.Read( &data[0], size) != size )
5150     {
5151       CLog::Log( LOGERROR, "Cannot read karaoke import file %s", inputFile.c_str() );
5152       return;
5153     }
5154
5155     file.Close();
5156     data[ size ] = '\0';
5157
5158     if (progress)
5159     {
5160       progress->SetHeading( 22036 );
5161       progress->SetLine(0, 649);
5162       progress->SetLine(1, "");
5163       progress->SetLine(2, "");
5164       progress->SetPercentage(0);
5165       progress->StartModal();
5166       progress->ShowProgressBar(true);
5167     }
5168
5169     if (NULL == m_pDS.get()) return;
5170     BeginTransaction();
5171
5172     //
5173     // A simple state machine to parse the file
5174     //
5175     char * linestart = &data[0];
5176     unsigned int offset = 0, lastpercentage = 0;
5177
5178     for ( char * p = &data[0]; *p; p++, offset++ )
5179     {
5180       // Skip \r
5181       if ( *p == 0x0D )
5182       {
5183         *p = '\0';
5184         continue;
5185       }
5186
5187       // Line number
5188       if ( *p == 0x0A )
5189       {
5190         *p = '\0';
5191
5192         unsigned int tabs = 0;
5193         char * songpath, *artist = 0, *title = 0;
5194         for ( songpath = linestart; *songpath; songpath++ )
5195         {
5196           if ( *songpath == '\t' )
5197           {
5198             tabs++;
5199             *songpath = '\0';
5200
5201             switch( tabs )
5202             {
5203               case 1: // the number end
5204                 artist = songpath + 1;
5205                 break; 
5206
5207               case 2: // the artist end
5208                 title = songpath + 1;
5209                 break; 
5210
5211               case 3: // the title end
5212                 break;
5213             }
5214           }
5215         }
5216
5217         int num = atoi( linestart );
5218         if ( num <= 0 || tabs < 3 || *artist == '\0' || *title == '\0' )
5219         {
5220           CLog::Log( LOGERROR, "Karaoke import: error in line %s", linestart );
5221           linestart = p + 1;
5222           continue;
5223         }
5224
5225         linestart = p + 1;
5226         CStdString strSQL=PrepareSQL("select idSong from songview "
5227                      "where strArtists like '%s' and strTitle like '%s'", artist, title );
5228
5229         if ( !m_pDS->query(strSQL.c_str()) )
5230         {
5231             RollbackTransaction();
5232             if (progress)
5233               progress->Close();
5234             m_pDS->close();
5235             return;
5236         }
5237
5238         int iRowsFound = m_pDS->num_rows();
5239         if (iRowsFound == 0)
5240         {
5241           CLog::Log( LOGERROR, "Karaoke import: song %s by %s #%d is not found in the database, skipped", 
5242                title, artist, num );
5243           continue;
5244         }
5245
5246         int lResult = m_pDS->fv(0).get_asInt();
5247         strSQL = PrepareSQL("UPDATE karaokedata SET iKaraNumber=%i WHERE idSong=%i", num, lResult );
5248         m_pDS->exec(strSQL.c_str());
5249
5250         if ( progress && (offset * 100 / size) != lastpercentage )
5251         {
5252           lastpercentage = offset * 100 / size;
5253           progress->SetPercentage( lastpercentage);
5254           progress->Progress();
5255           if ( progress->IsCanceled() )
5256           {
5257             RollbackTransaction();
5258             progress->Close();
5259             m_pDS->close();
5260             return;
5261           }
5262         }
5263       }
5264     }
5265
5266     CommitTransaction();
5267     CLog::Log( LOGNOTICE, "Karaoke import: file '%s' was imported successfully", inputFile.c_str() );
5268   }
5269   catch (...)
5270   {
5271     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
5272     RollbackTransaction();
5273   }
5274
5275   if (progress)
5276     progress->Close();
5277 }
5278
5279 bool CMusicDatabase::SetKaraokeSongDelay(int idSong, int delay)
5280 {
5281   try
5282   {
5283     if (NULL == m_pDB.get()) return false;
5284     if (NULL == m_pDS.get()) return false;
5285
5286     CStdString strSQL = PrepareSQL("UPDATE karaokedata SET iKaraDelay=%i WHERE idSong=%i", delay, idSong);
5287     m_pDS->exec(strSQL.c_str());
5288
5289     return true;
5290   }
5291   catch (...)
5292   {
5293     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
5294   }
5295
5296   return false;
5297 }
5298
5299 int CMusicDatabase::GetKaraokeSongsCount()
5300 {
5301   try
5302   {
5303     if (NULL == m_pDB.get()) return 0;
5304     if (NULL == m_pDS.get()) return 0;
5305
5306     if (!m_pDS->query( "select count(idSong) as NumSongs from karaokedata")) return 0;
5307     if (m_pDS->num_rows() == 0)
5308     {
5309       m_pDS->close();
5310       return 0;
5311     }
5312
5313     int iNumSongs = m_pDS->fv("NumSongs").get_asInt();
5314     // cleanup
5315     m_pDS->close();
5316     return iNumSongs;
5317   }
5318   catch (...)
5319   {
5320     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
5321   }
5322   return 0;
5323 }
5324
5325 void CMusicDatabase::SetPropertiesFromArtist(CFileItem& item, const CArtist& artist)
5326 {
5327   item.SetProperty("artist_instrument", StringUtils::Join(artist.instruments, g_advancedSettings.m_musicItemSeparator));
5328   item.SetProperty("artist_instrument_array", artist.instruments);
5329   item.SetProperty("artist_style", StringUtils::Join(artist.styles, g_advancedSettings.m_musicItemSeparator));
5330   item.SetProperty("artist_style_array", artist.styles);
5331   item.SetProperty("artist_mood", StringUtils::Join(artist.moods, g_advancedSettings.m_musicItemSeparator));
5332   item.SetProperty("artist_mood_array", artist.moods);
5333   item.SetProperty("artist_born", artist.strBorn);
5334   item.SetProperty("artist_formed", artist.strFormed);
5335   item.SetProperty("artist_description", artist.strBiography);
5336   item.SetProperty("artist_genre", StringUtils::Join(artist.genre, g_advancedSettings.m_musicItemSeparator));
5337   item.SetProperty("artist_genre_array", artist.genre);
5338   item.SetProperty("artist_died", artist.strDied);
5339   item.SetProperty("artist_disbanded", artist.strDisbanded);
5340   item.SetProperty("artist_yearsactive", StringUtils::Join(artist.yearsActive, g_advancedSettings.m_musicItemSeparator));
5341   item.SetProperty("artist_yearsactive_array", artist.yearsActive);
5342 }
5343
5344 void CMusicDatabase::SetPropertiesFromAlbum(CFileItem& item, const CAlbum& album)
5345 {
5346   item.SetProperty("album_description", album.strReview);
5347   item.SetProperty("album_theme", StringUtils::Join(album.themes, g_advancedSettings.m_musicItemSeparator));
5348   item.SetProperty("album_theme_array", album.themes);
5349   item.SetProperty("album_mood", StringUtils::Join(album.moods, g_advancedSettings.m_musicItemSeparator));
5350   item.SetProperty("album_mood_array", album.moods);
5351   item.SetProperty("album_style", StringUtils::Join(album.styles, g_advancedSettings.m_musicItemSeparator));
5352   item.SetProperty("album_style_array", album.styles);
5353   item.SetProperty("album_type", album.strType);
5354   item.SetProperty("album_label", album.strLabel);
5355   item.SetProperty("album_artist", StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator));
5356   item.SetProperty("album_artist_array", album.artist);
5357   item.SetProperty("album_genre", StringUtils::Join(album.genre, g_advancedSettings.m_musicItemSeparator));
5358   item.SetProperty("album_genre_array", album.genre);
5359   item.SetProperty("album_title", album.strAlbum);
5360   if (album.iRating > 0)
5361     item.SetProperty("album_rating", album.iRating);
5362 }
5363
5364 void CMusicDatabase::SetPropertiesForFileItem(CFileItem& item)
5365 {
5366   if (!item.HasMusicInfoTag())
5367     return;
5368   int idArtist = GetArtistByName(StringUtils::Join(item.GetMusicInfoTag()->GetArtist(), g_advancedSettings.m_musicItemSeparator));
5369   if (idArtist > -1)
5370   {
5371     CArtist artist;
5372     if (GetArtist(idArtist, artist))
5373       SetPropertiesFromArtist(item,artist);
5374   }
5375   int idAlbum = item.GetMusicInfoTag()->GetAlbumId();
5376   if (idAlbum <= 0)
5377     idAlbum = GetAlbumByName(item.GetMusicInfoTag()->GetAlbum(),
5378                              item.GetMusicInfoTag()->GetArtist());
5379   if (idAlbum > -1)
5380   {
5381     CAlbum album;
5382     if (GetAlbum(idAlbum, album, false))
5383       SetPropertiesFromAlbum(item,album);
5384   }
5385 }
5386
5387 void CMusicDatabase::SetArtForItem(int mediaId, const string &mediaType, const map<string, string> &art)
5388 {
5389   for (map<string, string>::const_iterator i = art.begin(); i != art.end(); ++i)
5390     SetArtForItem(mediaId, mediaType, i->first, i->second);
5391 }
5392
5393 void CMusicDatabase::SetArtForItem(int mediaId, const string &mediaType, const string &artType, const string &url)
5394 {
5395   try
5396   {
5397     if (NULL == m_pDB.get()) return;
5398     if (NULL == m_pDS.get()) return;
5399
5400     // don't set <foo>.<bar> art types - these are derivative types from parent items
5401     if (artType.find('.') != string::npos)
5402       return;
5403
5404     CStdString sql = PrepareSQL("SELECT art_id FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str());
5405     m_pDS->query(sql.c_str());
5406     if (!m_pDS->eof())
5407     { // update
5408       int artId = m_pDS->fv(0).get_asInt();
5409       m_pDS->close();
5410       sql = PrepareSQL("UPDATE art SET url='%s' where art_id=%d", url.c_str(), artId);
5411       m_pDS->exec(sql.c_str());
5412     }
5413     else
5414     { // insert
5415       m_pDS->close();
5416       sql = PrepareSQL("INSERT INTO art(media_id, media_type, type, url) VALUES (%d, '%s', '%s', '%s')", mediaId, mediaType.c_str(), artType.c_str(), url.c_str());
5417       m_pDS->exec(sql.c_str());
5418     }
5419   }
5420   catch (...)
5421   {
5422     CLog::Log(LOGERROR, "%s(%d, '%s', '%s', '%s') failed", __FUNCTION__, mediaId, mediaType.c_str(), artType.c_str(), url.c_str());
5423   }
5424 }
5425
5426 bool CMusicDatabase::GetArtForItem(int mediaId, const string &mediaType, map<string, string> &art)
5427 {
5428   try
5429   {
5430     if (NULL == m_pDB.get()) return false;
5431     if (NULL == m_pDS2.get()) return false; // using dataset 2 as we're likely called in loops on dataset 1
5432
5433     CStdString sql = PrepareSQL("SELECT type,url FROM art WHERE media_id=%i AND media_type='%s'", mediaId, mediaType.c_str());
5434     m_pDS2->query(sql.c_str());
5435     while (!m_pDS2->eof())
5436     {
5437       art.insert(make_pair(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString()));
5438       m_pDS2->next();
5439     }
5440     m_pDS2->close();
5441     return !art.empty();
5442   }
5443   catch (...)
5444   {
5445     CLog::Log(LOGERROR, "%s(%d) failed", __FUNCTION__, mediaId);
5446   }
5447   return false;
5448 }
5449
5450 string CMusicDatabase::GetArtForItem(int mediaId, const string &mediaType, const string &artType)
5451 {
5452   std::string query = PrepareSQL("SELECT url FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str());
5453   return GetSingleValue(query, m_pDS2);
5454 }
5455
5456 bool CMusicDatabase::GetArtistArtForItem(int mediaId, const std::string &mediaType, std::map<std::string, std::string> &art)
5457 {
5458   try
5459   {
5460     if (NULL == m_pDB.get()) return false;
5461     if (NULL == m_pDS2.get()) return false; // using dataset 2 as we're likely called in loops on dataset 1
5462
5463     CStdString sql = PrepareSQL("SELECT type,url FROM art WHERE media_id=(SELECT idArtist from %s_artist WHERE id%s=%i AND iOrder=0) AND media_type='artist'", mediaType.c_str(), mediaType.c_str(), mediaId);
5464     m_pDS2->query(sql.c_str());
5465     while (!m_pDS2->eof())
5466     {
5467       art.insert(make_pair(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString()));
5468       m_pDS2->next();
5469     }
5470     m_pDS2->close();
5471     return !art.empty();
5472   }
5473   catch (...)
5474   {
5475     CLog::Log(LOGERROR, "%s(%d) failed", __FUNCTION__, mediaId);
5476   }
5477   return false;
5478 }
5479
5480 string CMusicDatabase::GetArtistArtForItem(int mediaId, const string &mediaType, const string &artType)
5481 {
5482   std::string query = PrepareSQL("SELECT url FROM art WHERE media_id=(SELECT idArtist from %s_artist WHERE id%s=%i AND iOrder=0) AND media_type='artist' AND type='%s'", mediaType.c_str(), mediaType.c_str(), mediaId, artType.c_str());
5483   return GetSingleValue(query, m_pDS2);
5484 }
5485
5486 bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription &sorting)
5487 {
5488   if (!musicUrl.IsValid())
5489     return false;
5490
5491   std::string type = musicUrl.GetType();
5492   const CUrlOptions::UrlOptions& options = musicUrl.GetOptions();
5493   CUrlOptions::UrlOptions::const_iterator option;
5494
5495   if (type == "artists")
5496   {
5497     int idArtist = -1, idGenre = -1, idAlbum = -1, idSong = -1;
5498     bool albumArtistsOnly = false;
5499
5500     option = options.find("artistid");
5501     if (option != options.end())
5502       idArtist = (int)option->second.asInteger();
5503
5504     option = options.find("genreid");
5505     if (option != options.end())
5506       idGenre = (int)option->second.asInteger();
5507     else
5508     {
5509       option = options.find("genre");
5510       if (option != options.end())
5511         idGenre = GetGenreByName(option->second.asString());
5512     }
5513
5514     option = options.find("albumid");
5515     if (option != options.end())
5516       idAlbum = (int)option->second.asInteger();
5517     else
5518     {
5519       option = options.find("album");
5520       if (option != options.end())
5521         idAlbum = GetAlbumByName(option->second.asString());
5522     }
5523
5524     option = options.find("songid");
5525     if (option != options.end())
5526       idSong = (int)option->second.asInteger();
5527
5528     option = options.find("albumartistsonly");
5529     if (option != options.end())
5530       albumArtistsOnly = option->second.asBoolean();
5531
5532     CStdString strSQL = "(artistview.idArtist IN ";
5533     if (idArtist > 0)
5534       strSQL += PrepareSQL("(%d)", idArtist);
5535     else if (idAlbum > 0)
5536       strSQL += PrepareSQL("(SELECT album_artist.idArtist FROM album_artist WHERE album_artist.idAlbum = %i)", idAlbum);
5537     else if (idSong > 0)
5538       strSQL += PrepareSQL("(SELECT song_artist.idArtist FROM song_artist WHERE song_artist.idSong = %i)", idSong);
5539     else if (idGenre > 0)
5540     { // same statements as below, but limit to the specified genre
5541       // in this case we show the whole lot always - there is no limitation to just album artists
5542       if (!albumArtistsOnly)  // show all artists in this case (ie those linked to a song)
5543         strSQL+=PrepareSQL("(SELECT song_artist.idArtist FROM song_artist" // All artists linked to extra genres
5544                            " JOIN song_genre ON song_artist.idSong = song_genre.idSong"
5545                            " WHERE song_genre.idGenre = %i)"
5546                            " OR idArtist IN ", idGenre);
5547       // and add any artists linked to an album (may be different from above due to album artist tag)
5548       strSQL += PrepareSQL("(SELECT album_artist.idArtist FROM album_artist" // All album artists linked to extra genres
5549                            " JOIN album_genre ON album_artist.idAlbum = album_genre.idAlbum"
5550                            " WHERE album_genre.idGenre = %i)", idGenre);
5551     }
5552     else
5553     {
5554       if (!albumArtistsOnly)  // show all artists in this case (ie those linked to a song)
5555         strSQL += "(SELECT song_artist.idArtist FROM song_artist)"
5556                   " OR artistview.idArtist IN ";
5557
5558       // and always show any artists linked to an album (may be different from above due to album artist tag)
5559       strSQL +=   "(SELECT album_artist.idArtist FROM album_artist"; // All artists linked to an album
5560       if (albumArtistsOnly)
5561         strSQL += " JOIN album ON album.idAlbum = album_artist.idAlbum WHERE album.bCompilation = 0 ";            // then exclude those that have no extra artists
5562       strSQL +=   ")";
5563     }
5564
5565     // remove the null string
5566     strSQL += ") and artistview.strArtist != ''";
5567
5568     // and the various artist entry if applicable
5569     if (!albumArtistsOnly)
5570     {
5571       CStdString strVariousArtists = g_localizeStrings.Get(340);
5572       strSQL += PrepareSQL(" and artistview.strArtist <> '%s'", strVariousArtists.c_str());
5573     }
5574
5575     filter.AppendWhere(strSQL);
5576   }
5577   else if (type == "albums")
5578   {
5579     option = options.find("year");
5580     if (option != options.end())
5581       filter.AppendWhere(PrepareSQL("albumview.iYear = %i", (int)option->second.asInteger()));
5582     
5583     option = options.find("compilation");
5584     if (option != options.end())
5585       filter.AppendWhere(PrepareSQL("albumview.bCompilation = %i", option->second.asBoolean() ? 1 : 0));
5586
5587     option = options.find("genreid");
5588     if (option != options.end())
5589       filter.AppendWhere(PrepareSQL("albumview.idAlbum IN (SELECT song.idAlbum FROM song JOIN song_genre ON song.idSong = song_genre.idSong WHERE song_genre.idGenre = %i)", (int)option->second.asInteger()));
5590
5591     option = options.find("genre");
5592     if (option != options.end())
5593       filter.AppendWhere(PrepareSQL("albumview.idAlbum IN (SELECT song.idAlbum FROM song JOIN song_genre ON song.idSong = song_genre.idSong JOIN genre ON genre.idGenre = song_genre.idGenre WHERE genre.strGenre like '%s')", option->second.asString().c_str()));
5594
5595     option = options.find("artistid");
5596     if (option != options.end())
5597     {
5598       filter.AppendJoin("JOIN song ON song.idAlbum = albumview.idAlbum "
5599                         "JOIN song_artist ON song.idSong = song_artist.idSong "
5600                         "JOIN album_artist ON albumview.idAlbum = album_artist.idAlbum");
5601       filter.AppendWhere(PrepareSQL("      song_artist.idArtist = %i" // All albums linked to this artist via songs
5602                                     " OR  album_artist.idArtist = %i", // All albums where album artists fit
5603                                     (int)option->second.asInteger(), (int)option->second.asInteger()));
5604       filter.AppendGroup("albumview.idAlbum");
5605     }
5606     else
5607     {
5608       option = options.find("artist");
5609       if (option != options.end())
5610         filter.AppendWhere(PrepareSQL("albumview.idAlbum IN (SELECT song.idAlbum FROM song JOIN song_artist ON song.idSong = song_artist.idSong JOIN artist ON artist.idArtist = song_artist.idArtist WHERE artist.strArtist like '%s')" // All albums linked to this artist via songs
5611                                       " OR albumview.idAlbum IN (SELECT album_artist.idAlbum FROM album_artist JOIN artist ON artist.idArtist = album_artist.idArtist WHERE artist.strArtist like '%s')", // All albums where album artists fit
5612                                       option->second.asString().c_str(), option->second.asString().c_str()));
5613       // no artist given, so exclude any single albums (aka empty tagged albums)
5614       else
5615         filter.AppendWhere("albumview.strAlbum <> ''");
5616     }
5617   }
5618   else if (type == "songs" || type == "singles")
5619   {
5620     option = options.find("singles");
5621     if (option != options.end())
5622       filter.AppendWhere(PrepareSQL("songview.idAlbum %sIN (SELECT idAlbum FROM album WHERE strAlbum = '')", option->second.asBoolean() ? "" : "NOT "));
5623
5624     option = options.find("year");
5625     if (option != options.end())
5626       filter.AppendWhere(PrepareSQL("songview.iYear = %i", (int)option->second.asInteger()));
5627     
5628     option = options.find("compilation");
5629     if (option != options.end())
5630       filter.AppendWhere(PrepareSQL("songview.bCompilation = %i", option->second.asBoolean() ? 1 : 0));
5631
5632     option = options.find("albumid");
5633     if (option != options.end())
5634       filter.AppendWhere(PrepareSQL("songview.idAlbum = %i", (int)option->second.asInteger()));
5635     
5636     option = options.find("album");
5637     if (option != options.end())
5638       filter.AppendWhere(PrepareSQL("songview.strAlbum like '%s'", option->second.asString().c_str()));
5639
5640     option = options.find("genreid");
5641     if (option != options.end())
5642       filter.AppendWhere(PrepareSQL("songview.idSong IN (SELECT song_genre.idSong FROM song_genre WHERE song_genre.idGenre = %i)", (int)option->second.asInteger()));
5643
5644     option = options.find("genre");
5645     if (option != options.end())
5646       filter.AppendWhere(PrepareSQL("songview.idSong IN (SELECT song_genre.idSong FROM song_genre JOIN genre ON genre.idGenre = song_genre.idGenre WHERE genre.strGenre like '%s')", option->second.asString().c_str()));
5647
5648     option = options.find("artistid");
5649     if (option != options.end())
5650       filter.AppendWhere(PrepareSQL("songview.idSong IN (SELECT song_artist.idSong FROM song_artist WHERE song_artist.idArtist = %i)" // song artists
5651                                     " OR songview.idSong IN (SELECT song.idSong FROM song JOIN album_artist ON song.idAlbum=album_artist.idAlbum WHERE album_artist.idArtist = %i)", // album artists
5652                                     (int)option->second.asInteger(), (int)option->second.asInteger()));
5653
5654     option = options.find("artist");
5655     if (option != options.end())
5656       filter.AppendWhere(PrepareSQL("songview.idSong IN (SELECT song_artist.idSong FROM song_artist JOIN artist ON artist.idArtist = song_artist.idArtist WHERE artist.strArtist like '%s')" // song artists
5657                                     " OR songview.idSong IN (SELECT song.idSong FROM song JOIN album_artist ON song.idAlbum=album_artist.idAlbum JOIN artist ON artist.idArtist = album_artist.idArtist WHERE artist.strArtist like '%s')", // album artists
5658                                     option->second.asString().c_str(), option->second.asString().c_str()));
5659   }
5660
5661   option = options.find("xsp");
5662   if (option != options.end())
5663   {
5664     CSmartPlaylist xsp;
5665     if (!xsp.LoadFromJson(option->second.asString()))
5666       return false;
5667
5668     // check if the filter playlist matches the item type
5669     if (xsp.GetType()  == type ||
5670        (xsp.GetGroup() == type && !xsp.IsGroupMixed()))
5671     {
5672       std::set<CStdString> playlists;
5673       filter.AppendWhere(xsp.GetWhereClause(*this, playlists));
5674
5675       if (xsp.GetLimit() > 0)
5676         sorting.limitEnd = xsp.GetLimit();
5677       if (xsp.GetOrder() != SortByNone)
5678         sorting.sortBy = xsp.GetOrder();
5679       sorting.sortOrder = xsp.GetOrderAscending() ? SortOrderAscending : SortOrderDescending;
5680       if (CSettings::Get().GetBool("filelists.ignorethewhensorting"))
5681         sorting.sortAttributes = SortAttributeIgnoreArticle;
5682     }
5683   }
5684
5685   option = options.find("filter");
5686   if (option != options.end())
5687   {
5688     CSmartPlaylist xspFilter;
5689     if (!xspFilter.LoadFromJson(option->second.asString()))
5690       return false;
5691
5692     // check if the filter playlist matches the item type
5693     if (xspFilter.GetType() == type)
5694     {
5695       std::set<CStdString> playlists;
5696       filter.AppendWhere(xspFilter.GetWhereClause(*this, playlists));
5697     }
5698     // remove the filter if it doesn't match the item type
5699     else
5700       musicUrl.RemoveOption("filter");
5701   }
5702
5703   return true;
5704 }