PageRenderTime 76ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/xbmc/music/MusicDatabase.cpp

https://github.com/weitao2012/android-1
C++ | 5054 lines | 4206 code | 620 blank | 228 comment | 826 complexity | e2588e40aa2eea637c6b827fc879f208 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0
  1. /*
  2. * Copyright (C) 2005-2008 Team XBMC
  3. * http://www.xbmc.org
  4. *
  5. * This Program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2, or (at your option)
  8. * any later version.
  9. *
  10. * This Program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with XBMC; see the file COPYING. If not, write to
  17. * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. */
  21. #include "threads/SystemClock.h"
  22. #include "system.h"
  23. #include "MusicDatabase.h"
  24. #include "network/cddb.h"
  25. #include "filesystem/DirectoryCache.h"
  26. #include "filesystem/MusicDatabaseDirectory/DirectoryNode.h"
  27. #include "filesystem/MusicDatabaseDirectory/QueryParams.h"
  28. #include "filesystem/MusicDatabaseDirectory.h"
  29. #include "filesystem/SpecialProtocol.h"
  30. #include "GUIInfoManager.h"
  31. #include "music/tags/MusicInfoTag.h"
  32. #include "addons/AddonManager.h"
  33. #include "addons/Scraper.h"
  34. #include "addons/Addon.h"
  35. #include "utils/URIUtils.h"
  36. #include "Artist.h"
  37. #include "Album.h"
  38. #include "Song.h"
  39. #include "guilib/GUIWindowManager.h"
  40. #include "dialogs/GUIDialogOK.h"
  41. #include "dialogs/GUIDialogProgress.h"
  42. #include "dialogs/GUIDialogYesNo.h"
  43. #include "dialogs/GUIDialogSelect.h"
  44. #include "filesystem/File.h"
  45. #include "settings/GUISettings.h"
  46. #include "settings/AdvancedSettings.h"
  47. #include "FileItem.h"
  48. #include "Application.h"
  49. #ifdef HAS_KARAOKE
  50. #include "karaoke/karaokelyricsfactory.h"
  51. #endif
  52. #include "storage/MediaManager.h"
  53. #include "settings/Settings.h"
  54. #include "utils/StringUtils.h"
  55. #include "guilib/LocalizeStrings.h"
  56. #include "utils/log.h"
  57. #include "utils/TimeUtils.h"
  58. #include "TextureCache.h"
  59. #include "addons/AddonInstaller.h"
  60. #include "utils/AutoPtrHandle.h"
  61. #include "interfaces/AnnouncementManager.h"
  62. #include "dbwrappers/dataset.h"
  63. #include "utils/XMLUtils.h"
  64. #include "URL.h"
  65. using namespace std;
  66. using namespace AUTOPTR;
  67. using namespace XFILE;
  68. using namespace MUSICDATABASEDIRECTORY;
  69. using ADDON::AddonPtr;
  70. #define RECENTLY_PLAYED_LIMIT 25
  71. #define MIN_FULL_SEARCH_LENGTH 3
  72. #ifdef HAS_DVD_DRIVE
  73. using namespace CDDB;
  74. #endif
  75. CMusicDatabase::CMusicDatabase(void)
  76. {
  77. }
  78. CMusicDatabase::~CMusicDatabase(void)
  79. {
  80. EmptyCache();
  81. }
  82. bool CMusicDatabase::Open()
  83. {
  84. return CDatabase::Open(g_advancedSettings.m_databaseMusic);
  85. }
  86. bool CMusicDatabase::CreateTables()
  87. {
  88. BeginTransaction();
  89. try
  90. {
  91. CDatabase::CreateTables();
  92. CLog::Log(LOGINFO, "create artist table");
  93. m_pDS->exec("CREATE TABLE artist ( idArtist integer primary key, strArtist varchar(256))\n");
  94. CLog::Log(LOGINFO, "create album table");
  95. m_pDS->exec("CREATE TABLE album ( idAlbum integer primary key, strAlbum varchar(256), strArtists text, strGenres text, iYear integer, idThumb integer, bCompilation integer not null default '0' )\n");
  96. CLog::Log(LOGINFO, "create album_artist table");
  97. m_pDS->exec("CREATE TABLE album_artist ( idArtist integer, idAlbum integer, boolFeatured integer, iOrder integer )\n");
  98. CLog::Log(LOGINFO, "create album_genre table");
  99. m_pDS->exec("CREATE TABLE album_genre ( idGenre integer, idAlbum integer, iOrder integer )\n");
  100. CLog::Log(LOGINFO, "create genre table");
  101. m_pDS->exec("CREATE TABLE genre ( idGenre integer primary key, strGenre varchar(256))\n");
  102. CLog::Log(LOGINFO, "create path table");
  103. m_pDS->exec("CREATE TABLE path ( idPath integer primary key, strPath varchar(512), strHash text)\n");
  104. CLog::Log(LOGINFO, "create song table");
  105. m_pDS->exec("CREATE TABLE song ( 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");
  106. CLog::Log(LOGINFO, "create song_artist table");
  107. m_pDS->exec("CREATE TABLE song_artist ( idArtist integer, idSong integer, boolFeatured integer, iOrder integer )\n");
  108. CLog::Log(LOGINFO, "create song_genre table");
  109. m_pDS->exec("CREATE TABLE song_genre ( idGenre integer, idSong integer, iOrder integer )\n");
  110. CLog::Log(LOGINFO, "create albuminfo table");
  111. m_pDS->exec("CREATE TABLE albuminfo ( idAlbumInfo integer primary key, idAlbum integer, iYear integer, strMoods text, strStyles text, strThemes text, strReview text, strImage text, strLabel text, strType text, iRating integer)\n");
  112. CLog::Log(LOGINFO, "create albuminfosong table");
  113. m_pDS->exec("CREATE TABLE albuminfosong ( idAlbumInfoSong integer primary key, idAlbumInfo integer, iTrack integer, strTitle text, iDuration integer)\n");
  114. CLog::Log(LOGINFO, "create artistnfo table");
  115. m_pDS->exec("CREATE TABLE artistinfo ( idArtistInfo integer primary key, idArtist integer, strBorn text, strFormed text, strGenres text, strMoods text, strStyles text, strInstruments text, strBiography text, strDied text, strDisbanded text, strYearsActive text, strImage text, strFanart text)\n");
  116. CLog::Log(LOGINFO, "create content table");
  117. m_pDS->exec("CREATE TABLE content (strPath text, strScraperPath text, strContent text, strSettings text)\n");
  118. CLog::Log(LOGINFO, "create discography table");
  119. m_pDS->exec("CREATE TABLE discography (idArtist integer, strAlbum text, strYear text)\n");
  120. CLog::Log(LOGINFO, "create karaokedata table");
  121. m_pDS->exec("CREATE TABLE karaokedata ( iKaraNumber integer, idSong integer, iKaraDelay integer, strKaraEncoding text, "
  122. "strKaralyrics text, strKaraLyrFileCRC text )\n");
  123. CLog::Log(LOGINFO, "create album index");
  124. m_pDS->exec("CREATE INDEX idxAlbum ON album(strAlbum)");
  125. CLog::Log(LOGINFO, "create album compilation index");
  126. m_pDS->exec("CREATE INDEX idxAlbum_1 ON album(bCompilation)");
  127. CLog::Log(LOGINFO, "create album_artist indexes");
  128. m_pDS->exec("CREATE UNIQUE INDEX idxAlbumArtist_1 ON album_artist ( idAlbum, idArtist )\n");
  129. m_pDS->exec("CREATE UNIQUE INDEX idxAlbumArtist_2 ON album_artist ( idArtist, idAlbum )\n");
  130. m_pDS->exec("CREATE INDEX idxAlbumArtist_3 ON album_artist ( boolFeatured )\n");
  131. CLog::Log(LOGINFO, "create album_genre indexes");
  132. m_pDS->exec("CREATE UNIQUE INDEX idxAlbumGenre_1 ON album_genre ( idAlbum, idGenre )\n");
  133. m_pDS->exec("CREATE UNIQUE INDEX idxAlbumGenre_2 ON album_genre ( idGenre, idAlbum )\n");
  134. CLog::Log(LOGINFO, "create genre index");
  135. m_pDS->exec("CREATE INDEX idxGenre ON genre(strGenre)");
  136. CLog::Log(LOGINFO, "create artist index");
  137. m_pDS->exec("CREATE INDEX idxArtist ON artist(strArtist)");
  138. CLog::Log(LOGINFO, "create path index");
  139. m_pDS->exec("CREATE INDEX idxPath ON path(strPath)");
  140. CLog::Log(LOGINFO, "create song index");
  141. m_pDS->exec("CREATE INDEX idxSong ON song(strTitle)");
  142. CLog::Log(LOGINFO, "create song index1");
  143. m_pDS->exec("CREATE INDEX idxSong1 ON song(iTimesPlayed)");
  144. CLog::Log(LOGINFO, "create song index2");
  145. m_pDS->exec("CREATE INDEX idxSong2 ON song(lastplayed)");
  146. CLog::Log(LOGINFO, "create song index3");
  147. m_pDS->exec("CREATE INDEX idxSong3 ON song(idAlbum)");
  148. CLog::Log(LOGINFO, "create song index6");
  149. m_pDS->exec("CREATE INDEX idxSong6 ON song(idPath)");
  150. CLog::Log(LOGINFO, "create song_artist indexes");
  151. m_pDS->exec("CREATE UNIQUE INDEX idxSongArtist_1 ON song_artist ( idSong, idArtist )\n");
  152. m_pDS->exec("CREATE UNIQUE INDEX idxSongArtist_2 ON song_artist ( idArtist, idSong )\n");
  153. m_pDS->exec("CREATE INDEX idxSongArtist_3 ON song_artist ( boolFeatured )\n");
  154. CLog::Log(LOGINFO, "create song_genre indexes");
  155. m_pDS->exec("CREATE UNIQUE INDEX idxSongGenre_1 ON song_genre ( idSong, idGenre )\n");
  156. m_pDS->exec("CREATE UNIQUE INDEX idxSongGenre_2 ON song_genre ( idGenre, idSong )\n");
  157. //m_pDS->exec("CREATE INDEX idxSong ON song(dwFileNameCRC)");
  158. CLog::Log(LOGINFO, "create artistinfo index");
  159. m_pDS->exec("CREATE INDEX idxArtistInfo on artistinfo(idArtist)");
  160. CLog::Log(LOGINFO, "create albuminfo index");
  161. m_pDS->exec("CREATE INDEX idxAlbumInfo on albuminfo(idAlbum)");
  162. CLog::Log(LOGINFO, "create karaokedata index");
  163. m_pDS->exec("CREATE INDEX idxKaraNumber on karaokedata(iKaraNumber)");
  164. m_pDS->exec("CREATE INDEX idxKarSong on karaokedata(idSong)");
  165. // Trigger
  166. CLog::Log(LOGINFO, "create albuminfo trigger");
  167. m_pDS->exec("CREATE TRIGGER tgrAlbumInfo AFTER delete ON albuminfo FOR EACH ROW BEGIN delete from albuminfosong where albuminfosong.idAlbumInfo=old.idAlbumInfo; END");
  168. CLog::Log(LOGINFO, "create art table, index and triggers");
  169. m_pDS->exec("CREATE TABLE art(art_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, type TEXT, url TEXT)");
  170. m_pDS->exec("CREATE INDEX ix_art ON art(media_id, media_type(20), type(20))");
  171. m_pDS->exec("CREATE TRIGGER delete_song AFTER DELETE ON song FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idSong AND media_type='song'; END");
  172. m_pDS->exec("CREATE TRIGGER delete_album AFTER DELETE ON album FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idAlbum AND media_type='album'; END");
  173. m_pDS->exec("CREATE TRIGGER delete_artist AFTER DELETE ON artist FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idArtist AND media_type='artist'; END");
  174. // we create views last to ensure all indexes are rolled in
  175. CreateViews();
  176. // Add 'Karaoke' genre
  177. AddGenre( "Karaoke" );
  178. }
  179. catch (...)
  180. {
  181. CLog::Log(LOGERROR, "%s unable to create tables:%i", __FUNCTION__, (int)GetLastError());
  182. RollbackTransaction();
  183. return false;
  184. }
  185. CommitTransaction();
  186. return true;
  187. }
  188. void CMusicDatabase::CreateViews()
  189. {
  190. CLog::Log(LOGINFO, "create song view");
  191. m_pDS->exec("DROP VIEW IF EXISTS songview");
  192. m_pDS->exec("CREATE VIEW songview AS SELECT "
  193. " song.idSong AS idSong, "
  194. " song.strArtists AS strArtists,"
  195. " song.strGenres AS strGenres,"
  196. " strTitle, iTrack, iDuration,"
  197. " song.iYear AS iYear, dwFileNameCRC, strFileName, strMusicBrainzTrackID,"
  198. " strMusicBrainzArtistID, strMusicBrainzAlbumID, strMusicBrainzAlbumArtistID,"
  199. " strMusicBrainzTRMID, iTimesPlayed, iStartOffset, iEndOffset, lastplayed,"
  200. " rating, comment, song.idAlbum AS idAlbum, strAlbum, strPath,"
  201. " iKaraNumber, iKaraDelay, strKaraEncoding,"
  202. " album.bCompilation AS bCompilation "
  203. "FROM song"
  204. " JOIN album ON"
  205. " song.idAlbum=album.idAlbum"
  206. " JOIN path ON"
  207. " song.idPath=path.idPath"
  208. " LEFT OUTER JOIN karaokedata ON"
  209. " song.idSong=karaokedata.idSong");
  210. CLog::Log(LOGINFO, "create album view");
  211. m_pDS->exec("DROP VIEW IF EXISTS albumview");
  212. m_pDS->exec("CREATE VIEW albumview AS SELECT"
  213. " album.idAlbum AS idAlbum, strAlbum, "
  214. " album.strArtists AS strArtists,"
  215. " album.strGenres AS strGenres, "
  216. " album.iYear AS iYear,"
  217. " idAlbumInfo, strMoods, strStyles, strThemes,"
  218. " strReview, strLabel, strType, strImage, iRating, "
  219. " bCompilation "
  220. "FROM album "
  221. " LEFT OUTER JOIN albuminfo ON"
  222. " album.idAlbum=albuminfo.idAlbum");
  223. CLog::Log(LOGINFO, "create artist view");
  224. m_pDS->exec("DROP VIEW IF EXISTS artistview");
  225. m_pDS->exec("CREATE VIEW artistview AS SELECT"
  226. " artist.idArtist AS idArtist, strArtist, "
  227. " strBorn, strFormed, strGenres,"
  228. " strMoods, strStyles, strInstruments, "
  229. " strBiography, strDied, strDisbanded, "
  230. " strYearsActive, strImage, strFanart "
  231. "FROM artist "
  232. " LEFT OUTER JOIN artistinfo ON"
  233. " artist.idArtist = artistinfo.idArtist");
  234. }
  235. int CMusicDatabase::AddAlbum(const CAlbum &album, vector<int> &songIDs)
  236. {
  237. // add the album
  238. int idAlbum = AddAlbum(album.strAlbum, StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator), StringUtils::Join(album.genre, g_advancedSettings.m_musicItemSeparator), album.iYear, album.bCompilation);
  239. SetArtForItem(idAlbum, "album", album.art);
  240. // add the songs
  241. for (VECSONGS::const_iterator i = album.songs.begin(); i != album.songs.end(); ++i)
  242. songIDs.push_back(AddSong(*i, false, idAlbum));
  243. return idAlbum;
  244. }
  245. int CMusicDatabase::AddSong(const CSong& song, bool bCheck, int idAlbum)
  246. {
  247. int idSong = -1;
  248. CStdString strSQL;
  249. try
  250. {
  251. // We need at least the title
  252. if (song.strTitle.IsEmpty())
  253. return -1;
  254. CStdString strPath, strFileName;
  255. URIUtils::Split(song.strFileName, strPath, strFileName);
  256. if (NULL == m_pDB.get()) return -1;
  257. if (NULL == m_pDS.get()) return -1;
  258. int idPath = AddPath(strPath);
  259. if (idAlbum < 0)
  260. {
  261. if (!song.albumArtist.empty()) // have an album artist
  262. idAlbum = AddAlbum(song.strAlbum, StringUtils::Join(song.albumArtist, g_advancedSettings.m_musicItemSeparator), StringUtils::Join(song.genre, g_advancedSettings.m_musicItemSeparator), song.iYear, song.bCompilation);
  263. else
  264. idAlbum = AddAlbum(song.strAlbum, StringUtils::Join(song.artist, g_advancedSettings.m_musicItemSeparator), StringUtils::Join(song.genre, g_advancedSettings.m_musicItemSeparator), song.iYear, song.bCompilation);
  265. }
  266. DWORD crc = ComputeCRC(song.strFileName);
  267. bool bInsert = true;
  268. bool bHasKaraoke = false;
  269. #ifdef HAS_KARAOKE
  270. bHasKaraoke = CKaraokeLyricsFactory::HasLyrics( song.strFileName );
  271. #endif
  272. if (bCheck)
  273. {
  274. strSQL=PrepareSQL("select * from song where idAlbum=%i and dwFileNameCRC='%ul' and strTitle='%s'",
  275. idAlbum, crc, song.strTitle.c_str());
  276. if (!m_pDS->query(strSQL.c_str()))
  277. return -1;
  278. if (m_pDS->num_rows() != 0)
  279. {
  280. idSong = m_pDS->fv("idSong").get_asInt();
  281. bInsert = false;
  282. }
  283. m_pDS->close();
  284. }
  285. if (bInsert)
  286. {
  287. CStdString strSQL1;
  288. CStdString strIdSong;
  289. if (song.idSong < 0)
  290. strIdSong = "NULL";
  291. else
  292. strIdSong.Format("%d", song.idSong);
  293. // we use replace because it can handle both inserting a new song
  294. // and replacing an existing song's record if the given idSong already exists
  295. strSQL=PrepareSQL("replace into song (idSong,idAlbum,idPath,strArtists,strGenres,strTitle,iTrack,iDuration,iYear,dwFileNameCRC,strFileName,strMusicBrainzTrackID,strMusicBrainzArtistID,strMusicBrainzAlbumID,strMusicBrainzAlbumArtistID,strMusicBrainzTRMID,iTimesPlayed,iStartOffset,iEndOffset,lastplayed,rating,comment) values (%s,%i,%i,'%s','%s','%s',%i,%i,%i,'%ul','%s','%s','%s','%s','%s','%s'",
  296. strIdSong.c_str(),
  297. idAlbum, idPath,
  298. StringUtils::Join(song.artist, g_advancedSettings.m_musicItemSeparator).c_str(),
  299. StringUtils::Join(song.genre, g_advancedSettings.m_musicItemSeparator).c_str(),
  300. song.strTitle.c_str(),
  301. song.iTrack, song.iDuration, song.iYear,
  302. crc, strFileName.c_str(),
  303. song.strMusicBrainzTrackID.c_str(),
  304. song.strMusicBrainzArtistID.c_str(),
  305. song.strMusicBrainzAlbumID.c_str(),
  306. song.strMusicBrainzAlbumArtistID.c_str(),
  307. song.strMusicBrainzTRMID.c_str());
  308. if (song.lastPlayed.IsValid())
  309. strSQL1=PrepareSQL(",%i,%i,%i,'%s','%c','%s')",
  310. song.iTimesPlayed, song.iStartOffset, song.iEndOffset, song.lastPlayed.GetAsDBDateTime().c_str(), song.rating, song.strComment.c_str());
  311. else
  312. strSQL1=PrepareSQL(",%i,%i,%i,NULL,'%c','%s')",
  313. song.iTimesPlayed, song.iStartOffset, song.iEndOffset, song.rating, song.strComment.c_str());
  314. strSQL+=strSQL1;
  315. m_pDS->exec(strSQL.c_str());
  316. if (song.idSong < 0)
  317. idSong = (int)m_pDS->lastinsertid();
  318. else
  319. idSong = song.idSong;
  320. }
  321. if (!song.strThumb.empty())
  322. SetArtForItem(idSong, "song", "thumb", song.strThumb);
  323. for (unsigned int index = 0; index < song.albumArtist.size(); index++)
  324. {
  325. int idAlbumArtist = AddArtist(song.albumArtist[index]);
  326. AddAlbumArtist(idAlbumArtist, idAlbum, index > 0 ? true : false, index);
  327. }
  328. for (unsigned int index = 0; index < song.artist.size(); index++)
  329. {
  330. int idArtist = AddArtist(song.artist[index]);
  331. AddSongArtist(idArtist, idSong, index > 0 ? true : false, index);
  332. }
  333. unsigned int index = 0;
  334. // If this is karaoke song, change the genre to 'Karaoke' (and add it if it's not there)
  335. if ( bHasKaraoke && g_advancedSettings.m_karaokeChangeGenreForKaraokeSongs )
  336. {
  337. int idGenre = AddGenre("Karaoke");
  338. AddSongGenre(idGenre, idSong, index);
  339. AddAlbumGenre(idGenre, idAlbum, index++);
  340. }
  341. for (vector<string>::const_iterator i = song.genre.begin(); i != song.genre.end(); ++i)
  342. {
  343. // index will be wrong for albums, but ordering is not all that relevant
  344. // for genres anyway
  345. int idGenre = AddGenre(*i);
  346. AddSongGenre(idGenre, idSong, index);
  347. AddAlbumGenre(idGenre, idAlbum, index++);
  348. }
  349. // Add karaoke information (if any)
  350. if ( bHasKaraoke )
  351. AddKaraokeData(idSong, song );
  352. AnnounceUpdate("song", idSong);
  353. }
  354. catch (...)
  355. {
  356. CLog::Log(LOGERROR, "musicdatabase:unable to addsong (%s)", strSQL.c_str());
  357. }
  358. return idSong;
  359. }
  360. int CMusicDatabase::UpdateSong(const CSong& song, int idSong /* = -1 */)
  361. {
  362. CStdString sql;
  363. if (idSong < 0)
  364. idSong = song.idSong;
  365. if (idSong < 0)
  366. return -1;
  367. // delete linked songs
  368. // we don't delete from the song table here because
  369. // AddSong will update the existing record
  370. sql.Format("delete from song_artist where idSong=%d", idSong);
  371. ExecuteQuery(sql);
  372. sql.Format("delete from song_genre where idSong=%d", idSong);
  373. ExecuteQuery(sql);
  374. sql.Format("delete from karaokedata where idSong=%d", idSong);
  375. ExecuteQuery(sql);
  376. CSong newSong = song;
  377. // Make sure newSong.idSong has a valid value (> 0)
  378. newSong.idSong = idSong;
  379. // re-add the song
  380. newSong.idSong = AddSong(newSong, false);
  381. if (newSong.idSong < 0)
  382. return -1;
  383. return newSong.idSong;
  384. }
  385. int CMusicDatabase::AddAlbum(const CStdString& strAlbum1, const CStdString &strArtist, const CStdString& strGenre, int year, bool bCompilation)
  386. {
  387. CStdString strSQL;
  388. try
  389. {
  390. CStdString strAlbum=strAlbum1;
  391. strAlbum.TrimLeft(" ");
  392. strAlbum.TrimRight(" ");
  393. if (NULL == m_pDB.get()) return -1;
  394. if (NULL == m_pDS.get()) return -1;
  395. map <CStdString, CAlbumCache>::const_iterator it;
  396. it = m_albumCache.find(strAlbum + strArtist);
  397. if (it != m_albumCache.end())
  398. return it->second.idAlbum;
  399. strSQL=PrepareSQL("select * from album where strArtists='%s' and strAlbum like '%s'", strArtist.c_str(), strAlbum.c_str());
  400. m_pDS->query(strSQL.c_str());
  401. if (m_pDS->num_rows() == 0)
  402. {
  403. m_pDS->close();
  404. // doesnt exists, add it
  405. strSQL=PrepareSQL("insert into album (idAlbum, strAlbum, strArtists, strGenres, iYear, bCompilation) values( NULL, '%s', '%s', '%s', %i, %i)", strAlbum.c_str(), strArtist.c_str(), strGenre.c_str(), year, bCompilation);
  406. m_pDS->exec(strSQL.c_str());
  407. CAlbumCache album;
  408. album.idAlbum = (int)m_pDS->lastinsertid();
  409. album.strAlbum = strAlbum;
  410. album.artist = StringUtils::Split(strArtist, g_advancedSettings.m_musicItemSeparator);
  411. m_albumCache.insert(pair<CStdString, CAlbumCache>(album.strAlbum + strArtist, album));
  412. return album.idAlbum;
  413. }
  414. else
  415. {
  416. // exists in our database and not scanned during this scan, so we should update it as the details
  417. // may have changed (there's a reason we're rescanning, afterall!)
  418. CAlbumCache album;
  419. album.idAlbum = m_pDS->fv("idAlbum").get_asInt();
  420. album.strAlbum = strAlbum;
  421. album.artist = StringUtils::Split(strArtist, g_advancedSettings.m_musicItemSeparator);
  422. m_albumCache.insert(pair<CStdString, CAlbumCache>(album.strAlbum + strArtist, album));
  423. m_pDS->close();
  424. strSQL=PrepareSQL("update album set strGenres='%s', iYear=%i where idAlbum=%i", strGenre.c_str(), year, album.idAlbum);
  425. m_pDS->exec(strSQL.c_str());
  426. // and clear the link tables - these are updated in AddSong()
  427. strSQL=PrepareSQL("delete from album_artist where idAlbum=%i", album.idAlbum);
  428. m_pDS->exec(strSQL.c_str());
  429. strSQL=PrepareSQL("delete from album_genre where idAlbum=%i", album.idAlbum);
  430. m_pDS->exec(strSQL.c_str());
  431. return album.idAlbum;
  432. }
  433. }
  434. catch (...)
  435. {
  436. CLog::Log(LOGERROR, "%s failed with query (%s)", __FUNCTION__, strSQL.c_str());
  437. }
  438. return -1;
  439. }
  440. int CMusicDatabase::AddGenre(const CStdString& strGenre1)
  441. {
  442. CStdString strSQL;
  443. try
  444. {
  445. CStdString strGenre = strGenre1;
  446. strGenre.TrimLeft(" ");
  447. strGenre.TrimRight(" ");
  448. if (strGenre.IsEmpty())
  449. strGenre=g_localizeStrings.Get(13205); // Unknown
  450. if (NULL == m_pDB.get()) return -1;
  451. if (NULL == m_pDS.get()) return -1;
  452. map <CStdString, int>::const_iterator it;
  453. it = m_genreCache.find(strGenre);
  454. if (it != m_genreCache.end())
  455. return it->second;
  456. strSQL=PrepareSQL("select * from genre where strGenre like '%s'", strGenre.c_str());
  457. m_pDS->query(strSQL.c_str());
  458. if (m_pDS->num_rows() == 0)
  459. {
  460. m_pDS->close();
  461. // doesnt exists, add it
  462. strSQL=PrepareSQL("insert into genre (idGenre, strGenre) values( NULL, '%s' )", strGenre.c_str());
  463. m_pDS->exec(strSQL.c_str());
  464. int idGenre = (int)m_pDS->lastinsertid();
  465. m_genreCache.insert(pair<CStdString, int>(strGenre1, idGenre));
  466. return idGenre;
  467. }
  468. else
  469. {
  470. int idGenre = m_pDS->fv("idGenre").get_asInt();
  471. m_genreCache.insert(pair<CStdString, int>(strGenre1, idGenre));
  472. m_pDS->close();
  473. return idGenre;
  474. }
  475. }
  476. catch (...)
  477. {
  478. CLog::Log(LOGERROR, "musicdatabase:unable to addgenre (%s)", strSQL.c_str());
  479. }
  480. return -1;
  481. }
  482. int CMusicDatabase::AddArtist(const CStdString& strArtist1)
  483. {
  484. CStdString strSQL;
  485. try
  486. {
  487. CStdString strArtist = strArtist1;
  488. strArtist.TrimLeft(" ");
  489. strArtist.TrimRight(" ");
  490. if (strArtist.IsEmpty())
  491. strArtist=g_localizeStrings.Get(13205); // Unknown
  492. if (NULL == m_pDB.get()) return -1;
  493. if (NULL == m_pDS.get()) return -1;
  494. map <CStdString, int>::const_iterator it;
  495. it = m_artistCache.find(strArtist);
  496. if (it != m_artistCache.end())
  497. return it->second;//.idArtist;
  498. strSQL=PrepareSQL("select * from artist where strArtist like '%s'", strArtist.c_str());
  499. m_pDS->query(strSQL.c_str());
  500. if (m_pDS->num_rows() == 0)
  501. {
  502. m_pDS->close();
  503. // doesnt exists, add it
  504. strSQL=PrepareSQL("insert into artist (idArtist, strArtist) values( NULL, '%s' )", strArtist.c_str());
  505. m_pDS->exec(strSQL.c_str());
  506. int idArtist = (int)m_pDS->lastinsertid();
  507. m_artistCache.insert(pair<CStdString, int>(strArtist1, idArtist));
  508. return idArtist;
  509. }
  510. else
  511. {
  512. int idArtist = (int)m_pDS->fv("idArtist").get_asInt();
  513. m_artistCache.insert(pair<CStdString, int>(strArtist1, idArtist));
  514. m_pDS->close();
  515. return idArtist;
  516. }
  517. }
  518. catch (...)
  519. {
  520. CLog::Log(LOGERROR, "musicdatabase:unable to addartist (%s)", strSQL.c_str());
  521. }
  522. return -1;
  523. }
  524. bool CMusicDatabase::AddSongArtist(int idArtist, int idSong, bool featured, int iOrder)
  525. {
  526. CStdString strSQL;
  527. strSQL=PrepareSQL("replace into song_artist (idArtist, idSong, boolFeatured, iOrder) values(%i,%i,%i,%i)",
  528. idArtist, idSong, featured == true ? 1 : 0, iOrder);
  529. return ExecuteQuery(strSQL);
  530. };
  531. bool CMusicDatabase::AddAlbumArtist(int idArtist, int idAlbum, bool featured, int iOrder)
  532. {
  533. CStdString strSQL;
  534. strSQL=PrepareSQL("replace into album_artist (idArtist, idAlbum, boolFeatured, iOrder) values(%i,%i,%i,%i)",
  535. idArtist, idAlbum, featured == true ? 1 : 0, iOrder);
  536. return ExecuteQuery(strSQL);
  537. };
  538. bool CMusicDatabase::AddSongGenre(int idGenre, int idSong, int iOrder)
  539. {
  540. if (idGenre == -1 || idSong == -1)
  541. return true;
  542. CStdString strSQL;
  543. strSQL=PrepareSQL("replace into song_genre (idGenre, idSong, iOrder) values(%i,%i,%i)",
  544. idGenre, idSong, iOrder);
  545. return ExecuteQuery(strSQL);};
  546. bool CMusicDatabase::AddAlbumGenre(int idGenre, int idAlbum, int iOrder)
  547. {
  548. if (idGenre == -1 || idAlbum == -1)
  549. return true;
  550. CStdString strSQL;
  551. strSQL=PrepareSQL("replace into album_genre (idGenre, idAlbum, iOrder) values(%i,%i,%i)",
  552. idGenre, idAlbum, iOrder);
  553. return ExecuteQuery(strSQL);
  554. };
  555. bool CMusicDatabase::GetAlbumsByArtist(int idArtist, bool includeFeatured, std::vector<long> &albums)
  556. {
  557. try
  558. {
  559. CStdString strSQL, strPrepSQL;
  560. strPrepSQL = "select idAlbum from album_artist where idArtist=%i";
  561. if (includeFeatured == false)
  562. strPrepSQL += " AND boolFeatured = 0";
  563. strSQL=PrepareSQL(strPrepSQL, idArtist);
  564. if (!m_pDS->query(strSQL.c_str()))
  565. return false;
  566. if (m_pDS->num_rows() == 0)
  567. {
  568. m_pDS->close();
  569. return false;
  570. }
  571. while (!m_pDS->eof())
  572. {
  573. albums.push_back(m_pDS->fv("idAlbum").get_asInt());
  574. m_pDS->next();
  575. }
  576. m_pDS->close();
  577. return true;
  578. }
  579. catch (...)
  580. {
  581. CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idArtist);
  582. }
  583. return false;
  584. }
  585. bool CMusicDatabase::GetArtistsByAlbum(int idAlbum, bool includeFeatured, std::vector<long> &artists)
  586. {
  587. try
  588. {
  589. CStdString strSQL, strPrepSQL;
  590. strPrepSQL = "select idArtist from album_artist where idAlbum=%i";
  591. if (includeFeatured == false)
  592. strPrepSQL += " AND boolFeatured = 0";
  593. strSQL=PrepareSQL(strPrepSQL, idAlbum);
  594. if (!m_pDS->query(strSQL.c_str()))
  595. return false;
  596. if (m_pDS->num_rows() == 0)
  597. {
  598. m_pDS->close();
  599. return false;
  600. }
  601. while (!m_pDS->eof())
  602. {
  603. artists.push_back(m_pDS->fv("idArtist").get_asInt());
  604. m_pDS->next();
  605. }
  606. m_pDS->close();
  607. return true;
  608. }
  609. catch (...)
  610. {
  611. CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idAlbum);
  612. }
  613. return false;
  614. }
  615. bool CMusicDatabase::GetSongsByArtist(int idArtist, bool includeFeatured, std::vector<long> &songs)
  616. {
  617. try
  618. {
  619. CStdString strSQL, strPrepSQL;
  620. strPrepSQL = "select idSong from song_artist where idArtist=%i";
  621. if (includeFeatured == false)
  622. strPrepSQL += " AND boolFeatured = 0";
  623. strSQL=PrepareSQL(strPrepSQL, idArtist);
  624. if (!m_pDS->query(strSQL.c_str()))
  625. return false;
  626. if (m_pDS->num_rows() == 0)
  627. {
  628. m_pDS->close();
  629. return false;
  630. }
  631. while (!m_pDS->eof())
  632. {
  633. songs.push_back(m_pDS->fv("idSong").get_asInt());
  634. m_pDS->next();
  635. }
  636. m_pDS->close();
  637. return true;
  638. }
  639. catch (...)
  640. {
  641. CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idArtist);
  642. }
  643. return false;
  644. };
  645. bool CMusicDatabase::GetArtistsBySong(int idSong, bool includeFeatured, std::vector<long> &artists)
  646. {
  647. try
  648. {
  649. CStdString strSQL, strPrepSQL;
  650. strPrepSQL = "select idArtist from song_artist where idSong=%i";
  651. if (includeFeatured == false)
  652. strPrepSQL += " AND boolFeatured = 0";
  653. strSQL=PrepareSQL(strPrepSQL, idSong);
  654. if (!m_pDS->query(strSQL.c_str()))
  655. return false;
  656. if (m_pDS->num_rows() == 0)
  657. {
  658. m_pDS->close();
  659. return false;
  660. }
  661. while (!m_pDS->eof())
  662. {
  663. artists.push_back(m_pDS->fv("idArtist").get_asInt());
  664. m_pDS->next();
  665. }
  666. m_pDS->close();
  667. return true;
  668. }
  669. catch (...)
  670. {
  671. CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idSong);
  672. }
  673. return false;
  674. };
  675. int CMusicDatabase::AddPath(const CStdString& strPath1)
  676. {
  677. CStdString strSQL;
  678. try
  679. {
  680. CStdString strPath(strPath1);
  681. if (!URIUtils::HasSlashAtEnd(strPath))
  682. URIUtils::AddSlashAtEnd(strPath);
  683. if (NULL == m_pDB.get()) return -1;
  684. if (NULL == m_pDS.get()) return -1;
  685. map <CStdString, int>::const_iterator it;
  686. it = m_pathCache.find(strPath);
  687. if (it != m_pathCache.end())
  688. return it->second;
  689. strSQL=PrepareSQL( "select * from path where strPath='%s'", strPath.c_str());
  690. m_pDS->query(strSQL.c_str());
  691. if (m_pDS->num_rows() == 0)
  692. {
  693. m_pDS->close();
  694. // doesnt exists, add it
  695. strSQL=PrepareSQL("insert into path (idPath, strPath) values( NULL, '%s' )", strPath.c_str());
  696. m_pDS->exec(strSQL.c_str());
  697. int idPath = (int)m_pDS->lastinsertid();
  698. m_pathCache.insert(pair<CStdString, int>(strPath, idPath));
  699. return idPath;
  700. }
  701. else
  702. {
  703. int idPath = m_pDS->fv("idPath").get_asInt();
  704. m_pathCache.insert(pair<CStdString, int>(strPath, idPath));
  705. m_pDS->close();
  706. return idPath;
  707. }
  708. }
  709. catch (...)
  710. {
  711. CLog::Log(LOGERROR, "musicdatabase:unable to addpath (%s)", strSQL.c_str());
  712. }
  713. return -1;
  714. }
  715. CSong CMusicDatabase::GetSongFromDataset(bool bWithMusicDbPath/*=false*/)
  716. {
  717. CSong song;
  718. song.idSong = m_pDS->fv(song_idSong).get_asInt();
  719. // get the full artist string
  720. song.artist = StringUtils::Split(m_pDS->fv(song_strArtists).get_asString(), g_advancedSettings.m_musicItemSeparator);
  721. // and the full genre string
  722. song.genre = StringUtils::Split(m_pDS->fv(song_strGenres).get_asString(), g_advancedSettings.m_musicItemSeparator);
  723. // and the rest...
  724. song.strAlbum = m_pDS->fv(song_strAlbum).get_asString();
  725. song.iAlbumId = m_pDS->fv(song_idAlbum).get_asInt();
  726. song.iTrack = m_pDS->fv(song_iTrack).get_asInt() ;
  727. song.iDuration = m_pDS->fv(song_iDuration).get_asInt() ;
  728. song.iYear = m_pDS->fv(song_iYear).get_asInt() ;
  729. song.strTitle = m_pDS->fv(song_strTitle).get_asString();
  730. song.iTimesPlayed = m_pDS->fv(song_iTimesPlayed).get_asInt();
  731. song.lastPlayed.SetFromDBDateTime(m_pDS->fv(song_lastplayed).get_asString());
  732. song.iStartOffset = m_pDS->fv(song_iStartOffset).get_asInt();
  733. song.iEndOffset = m_pDS->fv(song_iEndOffset).get_asInt();
  734. song.strMusicBrainzTrackID = m_pDS->fv(song_strMusicBrainzTrackID).get_asString();
  735. song.strMusicBrainzArtistID = m_pDS->fv(song_strMusicBrainzArtistID).get_asString();
  736. song.strMusicBrainzAlbumID = m_pDS->fv(song_strMusicBrainzAlbumID).get_asString();
  737. song.strMusicBrainzAlbumArtistID = m_pDS->fv(song_strMusicBrainzAlbumArtistID).get_asString();
  738. song.strMusicBrainzTRMID = m_pDS->fv(song_strMusicBrainzTRMID).get_asString();
  739. song.rating = m_pDS->fv(song_rating).get_asChar();
  740. song.strComment = m_pDS->fv(song_comment).get_asString();
  741. song.iKaraokeNumber = m_pDS->fv(song_iKarNumber).get_asInt();
  742. song.strKaraokeLyrEncoding = m_pDS->fv(song_strKarEncoding).get_asString();
  743. song.iKaraokeDelay = m_pDS->fv(song_iKarDelay).get_asInt();
  744. song.bCompilation = m_pDS->fv(song_bCompilation).get_asInt() == 1;
  745. // Get filename with full path
  746. if (!bWithMusicDbPath)
  747. URIUtils::AddFileToFolder(m_pDS->fv(song_strPath).get_asString(), m_pDS->fv(song_strFileName).get_asString(), song.strFileName);
  748. else
  749. {
  750. CStdString strFileName=m_pDS->fv(song_strFileName).get_asString();
  751. CStdString strExt=URIUtils::GetExtension(strFileName);
  752. song.strFileName.Format("musicdb://3/%ld/%ld%s", m_pDS->fv(song_idAlbum).get_asInt(), m_pDS->fv(song_idSong).get_asInt(), strExt.c_str());
  753. }
  754. return song;
  755. }
  756. void CMusicDatabase::GetFileItemFromDataset(CFileItem* item, const CStdString& strMusicDBbasePath)
  757. {
  758. return GetFileItemFromDataset(m_pDS->get_sql_record(), item, strMusicDBbasePath);
  759. }
  760. void CMusicDatabase::GetFileItemFromDataset(const dbiplus::sql_record* const record, CFileItem* item, const CStdString& strMusicDBbasePath)
  761. {
  762. // get the full artist string
  763. item->GetMusicInfoTag()->SetArtist(StringUtils::Split(record->at(song_strArtists).get_asString(), g_advancedSettings.m_musicItemSeparator));
  764. // and the full genre string
  765. item->GetMusicInfoTag()->SetGenre(record->at(song_strGenres).get_asString());
  766. // and the rest...
  767. item->GetMusicInfoTag()->SetAlbum(record->at(song_strAlbum).get_asString());
  768. item->GetMusicInfoTag()->SetAlbumId(record->at(song_idAlbum).get_asInt());
  769. item->GetMusicInfoTag()->SetTrackAndDiskNumber(record->at(song_iTrack).get_asInt());
  770. item->GetMusicInfoTag()->SetDuration(record->at(song_iDuration).get_asInt());
  771. item->GetMusicInfoTag()->SetDatabaseId(record->at(song_idSong).get_asInt(), "song");
  772. SYSTEMTIME stTime;
  773. stTime.wYear = (WORD)record->at(song_iYear).get_asInt();
  774. item->GetMusicInfoTag()->SetReleaseDate(stTime);
  775. item->GetMusicInfoTag()->SetTitle(record->at(song_strTitle).get_asString());
  776. item->SetLabel(record->at(song_strTitle).get_asString());
  777. item->m_lStartOffset = record->at(song_iStartOffset).get_asInt();
  778. item->SetProperty("item_start", item->m_lStartOffset);
  779. item->m_lEndOffset = record->at(song_iEndOffset).get_asInt();
  780. item->GetMusicInfoTag()->SetMusicBrainzTrackID(record->at(song_strMusicBrainzTrackID).get_asString());
  781. item->GetMusicInfoTag()->SetMusicBrainzArtistID(record->at(song_strMusicBrainzArtistID).get_asString());
  782. item->GetMusicInfoTag()->SetMusicBrainzAlbumID(record->at(song_strMusicBrainzAlbumID).get_asString());
  783. item->GetMusicInfoTag()->SetMusicBrainzAlbumArtistID(record->at(song_strMusicBrainzAlbumArtistID).get_asString());
  784. item->GetMusicInfoTag()->SetMusicBrainzTRMID(record->at(song_strMusicBrainzTRMID).get_asString());
  785. item->GetMusicInfoTag()->SetRating(record->at(song_rating).get_asChar());
  786. item->GetMusicInfoTag()->SetComment(record->at(song_comment).get_asString());
  787. item->GetMusicInfoTag()->SetPlayCount(record->at(song_iTimesPlayed).get_asInt());
  788. item->GetMusicInfoTag()->SetLastPlayed(record->at(song_lastplayed).get_asString());
  789. CStdString strRealPath;
  790. URIUtils::AddFileToFolder(record->at(song_strPath).get_asString(), record->at(song_strFileName).get_asString(), strRealPath);
  791. item->GetMusicInfoTag()->SetURL(strRealPath);
  792. item->GetMusicInfoTag()->SetCompilation(m_pDS->fv(song_bCompilation).get_asInt() == 1);
  793. item->GetMusicInfoTag()->SetLoaded(true);
  794. // Get filename with full path
  795. if (strMusicDBbasePath.IsEmpty())
  796. item->SetPath(strRealPath);
  797. else
  798. {
  799. CStdString strFileName = record->at(song_strFileName).get_asString();
  800. CStdString strExt = URIUtils::GetExtension(strFileName);
  801. CStdString path; path.Format("%s%ld%s", strMusicDBbasePath.c_str(), record->at(song_idSong).get_asInt(), strExt.c_str());
  802. item->SetPath(path);
  803. }
  804. }
  805. CAlbum CMusicDatabase::GetAlbumFromDataset(dbiplus::Dataset* pDS, bool imageURL /* = false*/)
  806. {
  807. return GetAlbumFromDataset(pDS->get_sql_record(), imageURL);
  808. }
  809. CAlbum CMusicDatabase::GetAlbumFromDataset(const dbiplus::sql_record* const record, bool imageURL /* = false*/)
  810. {
  811. CAlbum album;
  812. album.idAlbum = record->at(album_idAlbum).get_asInt();
  813. album.strAlbum = record->at(album_strAlbum).get_asString();
  814. if (album.strAlbum.IsEmpty())
  815. album.strAlbum = g_localizeStrings.Get(1050);
  816. album.artist = StringUtils::Split(record->at(album_strArtists).get_asString(), g_advancedSettings.m_musicItemSeparator);
  817. album.genre = StringUtils::Split(record->at(album_strGenres).get_asString(), g_advancedSettings.m_musicItemSeparator);
  818. album.iYear = record->at(album_iYear).get_asInt();
  819. if (imageURL)
  820. album.thumbURL.ParseString(record->at(album_strThumbURL).get_asString());
  821. album.iRating = record->at(album_iRating).get_asInt();
  822. album.iYear = record->at(album_iYear).get_asInt();
  823. album.strReview = record->at(album_strReview).get_asString();
  824. album.styles = StringUtils::Split(record->at(album_strStyles).get_asString(), g_advancedSettings.m_musicItemSeparator);
  825. album.moods = StringUtils::Split(record->at(album_strMoods).get_asString(), g_advancedSettings.m_musicItemSeparator);
  826. album.themes = StringUtils::Split(record->at(album_strThemes).get_asString(), g_advancedSettings.m_musicItemSeparator);
  827. album.strLabel = record->at(album_strLabel).get_asString();
  828. album.strType = record->at(album_strType).get_asString();
  829. album.bCompilation = record->at(album_bCompilation).get_asInt() == 1;
  830. return album;
  831. }
  832. CArtist CMusicDatabase::GetArtistFromDataset(dbiplus::Dataset* pDS, bool needThumb)
  833. {
  834. CArtist artist;
  835. artist.idArtist = pDS->fv(artist_idArtist).get_asInt();
  836. artist.strArtist = pDS->fv(artist_strArtist).get_asString();
  837. artist.genre = StringUtils::Split(pDS->fv(artist_strGenres).get_asString(), g_advancedSettings.m_musicItemSeparator);
  838. artist.strBiography = pDS->fv(artist_strBiography).get_asString();
  839. artist.styles = StringUtils::Split(pDS->fv(artist_strStyles).get_asString(), g_advancedSettings.m_musicItemSeparator);
  840. artist.moods = StringUtils::Split(pDS->fv(artist_strMoods).get_asString(), g_advancedSettings.m_musicItemSeparator);
  841. artist.strBorn = pDS->fv(artist_strBorn).get_asString();
  842. artist.strFormed = pDS->fv(artist_strFormed).get_asString();
  843. artist.strDied = pDS->fv(artist_strDied).get_asString();
  844. artist.strDisbanded = pDS->fv(artist_strDisbanded).get_asString();
  845. artist.yearsActive = StringUtils::Split(pDS->fv(artist_strYearsActive).get_asString(), g_advancedSettings.m_musicItemSeparator);
  846. artist.instruments = StringUtils::Split(pDS->fv(artist_strInstruments).get_asString(), g_advancedSettings.m_musicItemSeparator);
  847. if (needThumb)
  848. {
  849. artist.fanart.m_xml = pDS->fv(artist_strFanart).get_asString();
  850. artist.fanart.Unpack();
  851. artist.thumbURL.ParseString(pDS->fv(artist_strImage).get_asString());
  852. }
  853. return artist;
  854. }
  855. bool CMusicDatabase::GetSongByFileName(const CStdString& strFileName, CSong& song, int startOffset)
  856. {
  857. try
  858. {
  859. song.Clear();
  860. CURL url(strFileName);
  861. if (url.GetProtocol()=="musicdb")
  862. {
  863. CStdString strFile = URIUtils::GetFileName(strFileName);
  864. URIUtils::RemoveExtension(strFile);
  865. return GetSongById(atol(strFile.c_str()), song);
  866. }
  867. CStdString strPath;
  868. URIUtils::GetDirectory(strFileName, strPath);
  869. URIUtils::AddSlashAtEnd(strPath);
  870. if (NULL == m_pDB.get()) return false;
  871. if (NULL == m_pDS.get()) return false;
  872. DWORD crc = ComputeCRC(strFileName);
  873. CStdString strSQL=PrepareSQL("select * from songview "
  874. "where dwFileNameCRC='%ul' and strPath='%s'"
  875. , crc,
  876. strPath.c_str());
  877. if (startOffset)
  878. strSQL += PrepareSQL(" AND iStartOffset=%i", startOffset);
  879. if (!m_pDS->query(strSQL.c_str())) return false;
  880. int iRowsFound = m_pDS->num_rows();
  881. if (iRowsFound == 0)
  882. {
  883. m_pDS->close();
  884. return false;
  885. }
  886. song = GetSongFromDataset();
  887. m_pDS->close(); // cleanup recordset data
  888. return true;
  889. }
  890. catch (...)
  891. {
  892. CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strFileName.c_str());
  893. }
  894. return false;
  895. }
  896. int CMusicDatabase::GetAlbumIdByPath(const CStdString& strPath)
  897. {
  898. try
  899. {
  900. CStdString strSQL=PrepareSQL("select distinct idAlbum from song join path on song.idPath = path.idPath where path.strPath='%s'", strPath.c_str());
  901. m_pDS->query(strSQL.c_str());
  902. if (m_pDS->eof())
  903. return -1;
  904. int idAlbum = m_pDS->fv(0).get_asInt();
  905. m_pDS->close();
  906. return idAlbum;
  907. }
  908. catch (...)
  909. {
  910. CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strPath.c_str());
  911. }
  912. return false;
  913. }
  914. int CMusicDatabase::GetSongByArtistAndAlbumAndTitle(const CStdString& strArtist, const CStdString& strAlbum, const CStdString& strTitle)
  915. {
  916. try
  917. {
  918. CStdString strSQL=PrepareSQL("select idSong from songview "
  919. "where strArtist like '%s' and strAlbum like '%s' and "
  920. "strTitle like '%s'",strArtist.c_str(),strAlbum.c_str(),strTitle.c_str());
  921. if (!m_pDS->query(strSQL.c_str())) return false;
  922. int iRowsFound = m_pDS->num_rows();
  923. if (iRowsFound == 0)
  924. {
  925. m_pDS->close();
  926. return -1;
  927. }
  928. int lResult = m_pDS->fv(0).get_asInt();
  929. m_pDS->close(); // cleanup recordset data
  930. return lResult;
  931. }
  932. catch (...)
  933. {
  934. CLog::Log(LOGERROR, "%s (%s,%s,%s) failed", __FUNCTION__, strArtist.c_str(),strAlbum.c_str(),strTitle.c_str());
  935. }
  936. return -1;
  937. }
  938. bool CMusicDatabase::GetSongById(int idSong, CSong& song)
  939. {
  940. try
  941. {
  942. song.Clear();
  943. if (NULL == m_pDB.get()) return false;
  944. if (NULL == m_pDS.get()) return false;
  945. CStdString strSQL=PrepareSQL("select * from songview "
  946. "where idSong=%i"
  947. , idSong);
  948. if (!m_pDS->query(strSQL.c_str())) return false;
  949. int iRowsFound = m_pDS->num_rows();
  950. if (iRowsFound == 0)
  951. {
  952. m_pDS->close();
  953. return false;
  954. }
  955. song = GetSongFromDataset();
  956. m_pDS->close(); // cleanup recordset data
  957. return true;
  958. }
  959. catch (...)
  960. {
  961. CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idSong);
  962. }
  963. return false;
  964. }
  965. bool CMusicDatabase::SearchArtists(const CStdString& search, CFileItemList &artists)
  966. {
  967. try
  968. {
  969. if (NULL == m_pDB.get()) return false;
  970. if (NULL == m_pDS.get()) return false;
  971. // Exclude "Various Artists"
  972. int idVariousArtist = AddArtist(g_localizeStrings.Get(340));
  973. CStdString strSQL;
  974. if (search.GetLength() >= MIN_FULL_SEARCH_LENGTH)
  975. strSQL=PrepareSQL("select * from artist "
  976. "where (strArtist like '%s%%' or strArtist like '%% %s%%') and idArtist <> %i "
  977. , search.c_str(), search.c_str(), idVariousArtist );
  978. else
  979. strSQL=PrepareSQL("select * from artist "
  980. "where strArtist like '%s%%' and idArtist <> %i "
  981. , search.c_str(), idVariousArtist );
  982. if (!m_pDS->query(strSQL.c_str())) return false;
  983. if (m_pDS->num_rows() == 0)
  984. {
  985. m_pDS->close();
  986. return false;
  987. }
  988. CStdString artistLabel(g_localizeStrings.Get(557)); // Artist
  989. while (!m_pDS->eof())
  990. {
  991. CStdString path;
  992. path.Format("musicdb://2/%ld/", m_pDS->fv(0).get_asInt());
  993. CFileItemPtr pItem(new CFileItem(path, true));
  994. CStdString label;
  995. label.Format("[%s] %s", artistLabel.c_str(), m_pDS->fv(1).get_asString());
  996. pItem->SetLabel(label);
  997. label.Format("A %s", m_pDS->fv(1).get_asString()); // sort label is stored in the title tag
  998. pItem->GetMusicInfoTag()->SetTitle(label);
  999. pItem->GetMusicInfoTag()->SetDatabaseId(m_pDS->fv(0).get_asInt(), "artist");
  1000. artists.Add(pItem);
  1001. m_pDS->next();
  1002. }
  1003. m_pDS->close(); // cleanup recordset data
  1004. return true;
  1005. }
  1006. catch (...)
  1007. {
  1008. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  1009. }
  1010. return false;
  1011. }
  1012. bool CMusicDatabase::GetArbitraryQuery(const CStdString& strQuery, const CStdString& strOpenRecordSet, const CStdString& strCloseRecordSet,
  1013. const CStdString& strOpenRecord, const CStdString& strCloseRecord, const CStdString& strOpenField,
  1014. const CStdString& strCloseField, CStdString& strResult)
  1015. {
  1016. try
  1017. {
  1018. strResult = "";
  1019. if (NULL == m_pDB.get()) return false;
  1020. if (NULL == m_pDS.get()) return false;
  1021. CStdString strSQL=strQuery;
  1022. if (!m_pDS->query(strSQL.c_str()))
  1023. {
  1024. strResult = m_pDB->getErrorMsg();
  1025. return false;
  1026. }
  1027. strResult=strOpenRecordSet;
  1028. while (!m_pDS->eof())
  1029. {
  1030. strResult += strOpenRecord;
  1031. for (int i=0; i<m_pDS->fieldCount(); i++)
  1032. {
  1033. strResult += strOpenField;
  1034. strResult += m_pDS->fv(i).get_asString();
  1035. strResult += strCloseField;
  1036. }
  1037. strResult += strCloseRecord;
  1038. m_pDS->next();
  1039. }
  1040. strResult += strCloseRecordSet;
  1041. m_pDS->close();
  1042. return true;
  1043. }
  1044. catch (...)
  1045. {
  1046. CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strQuery.c_str());
  1047. }
  1048. try
  1049. {
  1050. if (NULL == m_pDB.get()) return false;
  1051. strResult = m_pDB->getErrorMsg();
  1052. }
  1053. catch (...)
  1054. {
  1055. }
  1056. return false;
  1057. }
  1058. bool CMusicDatabase::ArbitraryExec(const CStdString& strExec)
  1059. {
  1060. try
  1061. {
  1062. if (NULL == m_pDB.get()) return false;
  1063. if (NULL == m_pDS.get()) return false;
  1064. CStdString strSQL = strExec;
  1065. m_pDS->exec(strSQL.c_str());
  1066. m_pDS->close();
  1067. return true;
  1068. }
  1069. catch (...)
  1070. {
  1071. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  1072. }
  1073. return false;
  1074. }
  1075. bool CMusicDatabase::GetAlbumInfo(int idAlbum, CAlbum &info, VECSONGS* songs)
  1076. {
  1077. try
  1078. {
  1079. if (idAlbum == -1)
  1080. return false; // not in the database
  1081. CStdString strSQL=PrepareSQL("select * from albumview where idAlbum = %ld", idAlbum);
  1082. if (!m_pDS2->query(strSQL.c_str())) return false;
  1083. int iRowsFound = m_pDS2->num_rows();
  1084. if (iRowsFound != 0)
  1085. {
  1086. info = GetAlbumFromDataset(m_pDS2.get(), true); // true to grab the thumburl rather than the thumb
  1087. int idAlbumInfo = m_pDS2->fv(album_idAlbumInfo).get_asInt();
  1088. m_pDS2->close(); // cleanup recordset data
  1089. if (songs)
  1090. GetAlbumInfoSongs(idAlbumInfo, *songs);
  1091. return true;
  1092. }
  1093. m_pDS2->close();
  1094. return false;
  1095. }
  1096. catch (...)
  1097. {
  1098. CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idAlbum);
  1099. }
  1100. return false;
  1101. }
  1102. bool CMusicDatabase::HasAlbumInfo(int idAlbum)
  1103. {
  1104. try
  1105. {
  1106. if (idAlbum == -1)
  1107. return false; // not in the database
  1108. CStdString strSQL=PrepareSQL("select * from albuminfo where idAlbum = %ld", idAlbum);
  1109. if (!m_pDS2->query(strSQL.c_str())) return false;
  1110. int iRowsFound = m_pDS2->num_rows();
  1111. m_pDS2->close();
  1112. return iRowsFound > 0;
  1113. }
  1114. catch (...)
  1115. {
  1116. CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idAlbum);
  1117. }
  1118. return false;
  1119. }
  1120. bool CMusicDatabase::DeleteAlbumInfo(int idAlbum)
  1121. {
  1122. try
  1123. {
  1124. if (idAlbum == -1)
  1125. return false; // not in the database
  1126. CStdString strSQL = PrepareSQL("delete from albuminfo where idAlbum=%i",idAlbum);
  1127. if (!m_pDS2->exec(strSQL.c_str()))
  1128. return false;
  1129. return true;
  1130. }
  1131. catch (...)
  1132. {
  1133. CLog::Log(LOGERROR, "%s - (%i) failed", __FUNCTION__, idAlbum);
  1134. }
  1135. return false;
  1136. }
  1137. bool CMusicDatabase::GetArtistInfo(int idArtist, CArtist &info, bool needAll)
  1138. {
  1139. try
  1140. {
  1141. if (idArtist == -1)
  1142. return false; // not in the database
  1143. CStdString strSQL=PrepareSQL("SELECT artist.idArtist AS idArtist, strArtist, "
  1144. " strBorn, strFormed, strGenres,"
  1145. " strMoods, strStyles, strInstruments, "
  1146. " strBiography, strDied, strDisbanded, "
  1147. " strYearsActive, strImage, strFanart "
  1148. " FROM artist "
  1149. " JOIN artistinfo "
  1150. " ON artist.idArtist = artistinfo.idArtist "
  1151. " WHERE artistinfo.idArtist = %i"
  1152. , idArtist);
  1153. if (!m_pDS2->query(strSQL.c_str())) return false;
  1154. int iRowsFound = m_pDS2->num_rows();
  1155. if (iRowsFound != 0)
  1156. {
  1157. info = GetArtistFromDataset(m_pDS2.get(),needAll);
  1158. if (needAll)
  1159. {
  1160. strSQL=PrepareSQL("select * from discography where idArtist=%i",idArtist);
  1161. m_pDS2->query(strSQL.c_str());
  1162. while (!m_pDS2->eof())
  1163. {
  1164. info.discography.push_back(make_pair(m_pDS2->fv("strAlbum").get_asString(),m_pDS2->fv("strYear").get_asString()));
  1165. m_pDS2->next();
  1166. }
  1167. }
  1168. m_pDS2->close(); // cleanup recordset data
  1169. return true;
  1170. }
  1171. m_pDS2->close();
  1172. return false;
  1173. }
  1174. catch (...)
  1175. {
  1176. CLog::Log(LOGERROR, "%s - (%i) failed", __FUNCTION__, idArtist);
  1177. }
  1178. return false;
  1179. }
  1180. bool CMusicDatabase::DeleteArtistInfo(int idArtist)
  1181. {
  1182. try
  1183. {
  1184. if (idArtist == -1)
  1185. return false; // not in the database
  1186. CStdString strSQL = PrepareSQL("delete from artistinfo where idArtist=%i",idArtist);
  1187. if (!m_pDS2->exec(strSQL.c_str()))
  1188. return false;
  1189. return true;
  1190. }
  1191. catch (...)
  1192. {
  1193. CLog::Log(LOGERROR, "%s - (%i) failed", __FUNCTION__, idArtist);
  1194. }
  1195. return false;
  1196. }
  1197. bool CMusicDatabase::GetAlbumInfoSongs(int idAlbumInfo, VECSONGS& songs)
  1198. {
  1199. try
  1200. {
  1201. CStdString strSQL=PrepareSQL("select * from albuminfosong "
  1202. "where idAlbumInfo=%i "
  1203. "order by iTrack", idAlbumInfo);
  1204. if (!m_pDS2->query(strSQL.c_str())) return false;
  1205. int iRowsFound = m_pDS2->num_rows();
  1206. if (iRowsFound == 0) return false;
  1207. while (!m_pDS2->eof())
  1208. {
  1209. CSong song;
  1210. song.iTrack = m_pDS2->fv("iTrack").get_asInt();
  1211. song.strTitle = m_pDS2->fv("strTitle").get_asString();
  1212. song.iDuration = m_pDS2->fv("iDuration").get_asInt();
  1213. songs.push_back(song);
  1214. m_pDS2->next();
  1215. }
  1216. m_pDS2->close(); // cleanup recordset data
  1217. return true;
  1218. }
  1219. catch (...)
  1220. {
  1221. CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idAlbumInfo);
  1222. }
  1223. return false;
  1224. }
  1225. bool CMusicDatabase::GetTop100(const CStdString& strBaseDir, CFileItemList& items)
  1226. {
  1227. try
  1228. {
  1229. if (NULL == m_pDB.get()) return false;
  1230. if (NULL == m_pDS.get()) return false;
  1231. CStdString strSQL="select * from songview "
  1232. "where iTimesPlayed>0 "
  1233. "order by iTimesPlayed desc "
  1234. "limit 100";
  1235. CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
  1236. if (!m_pDS->query(strSQL.c_str())) return false;
  1237. int iRowsFound = m_pDS->num_rows();
  1238. if (iRowsFound == 0)
  1239. {
  1240. m_pDS->close();
  1241. return false;
  1242. }
  1243. items.Reserve(iRowsFound);
  1244. while (!m_pDS->eof())
  1245. {
  1246. CFileItemPtr item(new CFileItem);
  1247. GetFileItemFromDataset(item.get(), strBaseDir);
  1248. items.Add(item);
  1249. m_pDS->next();
  1250. }
  1251. m_pDS->close(); // cleanup recordset data
  1252. return true;
  1253. }
  1254. catch (...)
  1255. {
  1256. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  1257. }
  1258. return false;
  1259. }
  1260. bool CMusicDatabase::GetTop100Albums(VECALBUMS& albums)
  1261. {
  1262. try
  1263. {
  1264. albums.erase(albums.begin(), albums.end());
  1265. if (NULL == m_pDB.get()) return false;
  1266. if (NULL == m_pDS.get()) return false;
  1267. // NOTE: The song.idAlbum is needed for the group by, as for some reason group by albumview.idAlbum doesn't work
  1268. // consistently - possibly an SQLite bug, as it works fine in SQLiteSpy (v3.3.17)
  1269. CStdString strSQL = "select albumview.*, sum(song.iTimesPlayed) as total, song.idAlbum from song "
  1270. "join albumview on albumview.idAlbum=song.idAlbum "
  1271. "where song.iTimesPlayed>0 and albumview.strAlbum != '' "
  1272. "group by song.idAlbum "
  1273. "order by total desc "
  1274. "limit 100 ";
  1275. CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
  1276. if (!m_pDS->query(strSQL.c_str())) return false;
  1277. int iRowsFound = m_pDS->num_rows();
  1278. if (iRowsFound == 0)
  1279. {
  1280. m_pDS->close();
  1281. return false;
  1282. }
  1283. while (!m_pDS->eof())
  1284. {
  1285. albums.push_back(GetAlbumFromDataset(m_pDS.get()));
  1286. m_pDS->next();
  1287. }
  1288. m_pDS->close(); // cleanup recordset data
  1289. return true;
  1290. }
  1291. catch (...)
  1292. {
  1293. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  1294. }
  1295. return false;
  1296. }
  1297. bool CMusicDatabase::GetTop100AlbumSongs(const CStdString& strBaseDir, CFileItemList& items)
  1298. {
  1299. try
  1300. {
  1301. if (NULL == m_pDB.get()) return false;
  1302. if (NULL == m_pDS.get()) return false;
  1303. CStdString strSQL;
  1304. strSQL.Format("select * from songview join albumview on (songview.idAlbum = albumview.idAlbum) where albumview.idAlbum in (select song.idAlbum from song where song.iTimesPlayed>0 group by idAlbum order by sum(song.iTimesPlayed) desc limit 100) order by albumview.idAlbum in (select song.idAlbum from song where song.iTimesPlayed>0 group by idAlbum order by sum(song.iTimesPlayed) desc limit 100)");
  1305. CLog::Log(LOGDEBUG,"GetTop100AlbumSongs() query: %s", strSQL.c_str());
  1306. if (!m_pDS->query(strSQL.c_str())) return false;
  1307. int iRowsFound = m_pDS->num_rows();
  1308. if (iRowsFound == 0)
  1309. {
  1310. m_pDS->close();
  1311. return false;
  1312. }
  1313. // get data from returned rows
  1314. items.Reserve(iRowsFound);
  1315. while (!m_pDS->eof())
  1316. {
  1317. CFileItemPtr item(new CFileItem);
  1318. GetFileItemFromDataset(item.get(), strBaseDir);
  1319. items.Add(item);
  1320. m_pDS->next();
  1321. }
  1322. // cleanup
  1323. m_pDS->close();
  1324. return true;
  1325. }
  1326. catch (...)
  1327. {
  1328. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  1329. }
  1330. return false;
  1331. }
  1332. bool CMusicDatabase::GetRecentlyPlayedAlbums(VECALBUMS& albums)
  1333. {
  1334. try
  1335. {
  1336. albums.erase(albums.begin(), albums.end());
  1337. if (NULL == m_pDB.get()) return false;
  1338. if (NULL == m_pDS.get()) return false;
  1339. CStdString strSQL;
  1340. strSQL.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);
  1341. CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
  1342. if (!m_pDS->query(strSQL.c_str())) return false;
  1343. int iRowsFound = m_pDS->num_rows();
  1344. if (iRowsFound == 0)
  1345. {
  1346. m_pDS->close();
  1347. return false;
  1348. }
  1349. while (!m_pDS->eof())
  1350. {
  1351. albums.push_back(GetAlbumFromDataset(m_pDS.get()));
  1352. m_pDS->next();
  1353. }
  1354. m_pDS->close(); // cleanup recordset data
  1355. return true;
  1356. }
  1357. catch (...)
  1358. {
  1359. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  1360. }
  1361. return false;
  1362. }
  1363. bool CMusicDatabase::GetRecentlyPlayedAlbumSongs(const CStdString& strBaseDir, CFileItemList& items)
  1364. {
  1365. try
  1366. {
  1367. if (NULL == m_pDB.get()) return false;
  1368. if (NULL == m_pDS.get()) return false;
  1369. CStdString strSQL;
  1370. strSQL.Format("select * from songview join albumview on (songview.idAlbum = albumview.idAlbum) where albumview.idAlbum in (select distinct albumview.idAlbum from albumview join song on albumview.idAlbum=song.idAlbum where song.lastplayed IS NOT NULL order by song.lastplayed desc limit %i)", g_advancedSettings.m_iMusicLibraryRecentlyAddedItems);
  1371. CLog::Log(LOGDEBUG,"GetRecentlyPlayedAlbumSongs() query: %s", strSQL.c_str());
  1372. if (!m_pDS->query(strSQL.c_str())) return false;
  1373. int iRowsFound = m_pDS->num_rows();
  1374. if (iRowsFound == 0)
  1375. {
  1376. m_pDS->close();
  1377. return false;
  1378. }
  1379. // get data from returned rows
  1380. items.Reserve(iRowsFound);
  1381. while (!m_pDS->eof())
  1382. {
  1383. CFileItemPtr item(new CFileItem);
  1384. GetFileItemFromDataset(item.get(), strBaseDir);
  1385. items.Add(item);
  1386. m_pDS->next();
  1387. }
  1388. // cleanup
  1389. m_pDS->close();
  1390. return true;
  1391. }
  1392. catch (...)
  1393. {
  1394. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  1395. }
  1396. return false;
  1397. }
  1398. bool CMusicDatabase::GetRecentlyAddedAlbums(VECALBUMS& albums, unsigned int limit)
  1399. {
  1400. try
  1401. {
  1402. albums.erase(albums.begin(), albums.end());
  1403. if (NULL == m_pDB.get()) return false;
  1404. if (NULL == m_pDS.get()) return false;
  1405. CStdString strSQL;
  1406. strSQL.Format("select * from albumview order by idAlbum desc limit %u", limit ? limit : g_advancedSettings.m_iMusicLibraryRecentlyAddedItems);
  1407. CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
  1408. if (!m_pDS->query(strSQL.c_str())) return false;
  1409. int iRowsFound = m_pDS->num_rows();
  1410. if (iRowsFound == 0)
  1411. {
  1412. m_pDS->close();
  1413. return false;
  1414. }
  1415. while (!m_pDS->eof())
  1416. {
  1417. albums.push_back(GetAlbumFromDataset(m_pDS.get()));
  1418. m_pDS->next();
  1419. }
  1420. m_pDS->close(); // cleanup recordset data
  1421. return true;
  1422. }
  1423. catch (...)
  1424. {
  1425. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  1426. }
  1427. return false;
  1428. }
  1429. bool CMusicDatabase::GetRecentlyAddedAlbumSongs(const CStdString& strBaseDir, CFileItemList& items, unsigned int limit)
  1430. {
  1431. try
  1432. {
  1433. if (NULL == m_pDB.get()) return false;
  1434. if (NULL == m_pDS.get()) return false;
  1435. CStdString strSQL;
  1436. 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);
  1437. CLog::Log(LOGDEBUG,"GetRecentlyAddedAlbumSongs() query: %s", strSQL.c_str());
  1438. if (!m_pDS->query(strSQL.c_str())) return false;
  1439. int iRowsFound = m_pDS->num_rows();
  1440. if (iRowsFound == 0)
  1441. {
  1442. m_pDS->close();
  1443. return false;
  1444. }
  1445. // get data from returned rows
  1446. items.Reserve(iRowsFound);
  1447. while (!m_pDS->eof())
  1448. {
  1449. CFileItemPtr item(new CFileItem);
  1450. GetFileItemFromDataset(item.get(), strBaseDir);
  1451. items.Add(item);
  1452. m_pDS->next();
  1453. }
  1454. // cleanup
  1455. m_pDS->close();
  1456. return true;
  1457. }
  1458. catch (...)
  1459. {
  1460. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  1461. }
  1462. return false;
  1463. }
  1464. bool CMusicDatabase::IncrTop100CounterByFileName(const CStdString& strFileName)
  1465. {
  1466. try
  1467. {
  1468. if (NULL == m_pDB.get()) return false;
  1469. if (NULL == m_pDS.get()) return false;
  1470. int idSong = GetSongIDFromPath(strFileName);
  1471. CStdString sql=PrepareSQL("UPDATE song SET iTimesPlayed=iTimesPlayed+1, lastplayed=CURRENT_TIMESTAMP where idSong=%i", idSong);
  1472. m_pDS->exec(sql.c_str());
  1473. return true;
  1474. }
  1475. catch (...)
  1476. {
  1477. CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strFileName.c_str());
  1478. }
  1479. return false;
  1480. }
  1481. bool CMusicDatabase::GetSongsByPath(const CStdString& strPath1, CSongMap& songs, bool bAppendToMap)
  1482. {
  1483. CStdString strPath(strPath1);
  1484. try
  1485. {
  1486. if (!URIUtils::HasSlashAtEnd(strPath))
  1487. URIUtils::AddSlashAtEnd(strPath);
  1488. if (!bAppendToMap)
  1489. songs.Clear();
  1490. if (NULL == m_pDB.get()) return false;
  1491. if (NULL == m_pDS.get()) return false;
  1492. CStdString strSQL=PrepareSQL("select * from songview where strPath='%s'", strPath.c_str() );
  1493. if (!m_pDS->query(strSQL.c_str())) return false;
  1494. int iRowsFound = m_pDS->num_rows();
  1495. if (iRowsFound == 0)
  1496. {
  1497. m_pDS->close();
  1498. return false;
  1499. }
  1500. while (!m_pDS->eof())
  1501. {
  1502. CSong song = GetSongFromDataset();
  1503. songs.Add(song.strFileName, song);
  1504. m_pDS->next();
  1505. }
  1506. m_pDS->close(); // cleanup recordset data
  1507. return true;
  1508. }
  1509. catch (...)
  1510. {
  1511. CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strPath.c_str());
  1512. }
  1513. return false;
  1514. }
  1515. void CMusicDatabase::EmptyCache()
  1516. {
  1517. m_artistCache.erase(m_artistCache.begin(), m_artistCache.end());
  1518. m_genreCache.erase(m_genreCache.begin(), m_genreCache.end());
  1519. m_pathCache.erase(m_pathCache.begin(), m_pathCache.end());
  1520. m_albumCache.erase(m_albumCache.begin(), m_albumCache.end());
  1521. m_thumbCache.erase(m_thumbCache.begin(), m_thumbCache.end());
  1522. }
  1523. bool CMusicDatabase::Search(const CStdString& search, CFileItemList &items)
  1524. {
  1525. unsigned int time = XbmcThreads::SystemClockMillis();
  1526. // first grab all the artists that match
  1527. SearchArtists(search, items);
  1528. CLog::Log(LOGDEBUG, "%s Artist search in %i ms",
  1529. __FUNCTION__, XbmcThreads::SystemClockMillis() - time); time = XbmcThreads::SystemClockMillis();
  1530. // then albums that match
  1531. SearchAlbums(search, items);
  1532. CLog::Log(LOGDEBUG, "%s Album search in %i ms",
  1533. __FUNCTION__, XbmcThreads::SystemClockMillis() - time); time = XbmcThreads::SystemClockMillis();
  1534. // and finally songs
  1535. SearchSongs(search, items);
  1536. CLog::Log(LOGDEBUG, "%s Songs search in %i ms",
  1537. __FUNCTION__, XbmcThreads::SystemClockMillis() - time); time = XbmcThreads::SystemClockMillis();
  1538. return true;
  1539. }
  1540. bool CMusicDatabase::SearchSongs(const CStdString& search, CFileItemList &items)
  1541. {
  1542. try
  1543. {
  1544. if (NULL == m_pDB.get()) return false;
  1545. if (NULL == m_pDS.get()) return false;
  1546. CStdString strSQL;
  1547. if (search.GetLength() >= MIN_FULL_SEARCH_LENGTH)
  1548. strSQL=PrepareSQL("select * from songview where strTitle like '%s%%' or strTitle like '%% %s%%' limit 1000", search.c_str(), search.c_str());
  1549. else
  1550. strSQL=PrepareSQL("select * from songview where strTitle like '%s%%' limit 1000", search.c_str());
  1551. if (!m_pDS->query(strSQL.c_str())) return false;
  1552. if (m_pDS->num_rows() == 0) return false;
  1553. CStdString songLabel = g_localizeStrings.Get(179); // Song
  1554. while (!m_pDS->eof())
  1555. {
  1556. CFileItemPtr item(new CFileItem);
  1557. GetFileItemFromDataset(item.get(), "musicdb://4/");
  1558. items.Add(item);
  1559. m_pDS->next();
  1560. }
  1561. m_pDS->close();
  1562. return true;
  1563. }
  1564. catch (...)
  1565. {
  1566. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  1567. }
  1568. return false;
  1569. }
  1570. bool CMusicDatabase::SearchAlbums(const CStdString& search, CFileItemList &albums)
  1571. {
  1572. try
  1573. {
  1574. if (NULL == m_pDB.get()) return false;
  1575. if (NULL == m_pDS.get()) return false;
  1576. CStdString strSQL;
  1577. if (search.GetLength() >= MIN_FULL_SEARCH_LENGTH)
  1578. strSQL=PrepareSQL("select * from albumview where strAlbum like '%s%%' or strAlbum like '%% %s%%'", search.c_str(), search.c_str());
  1579. else
  1580. strSQL=PrepareSQL("select * from albumview where strAlbum like '%s%%'", search.c_str());
  1581. if (!m_pDS->query(strSQL.c_str())) return false;
  1582. CStdString albumLabel(g_localizeStrings.Get(558)); // Album
  1583. while (!m_pDS->eof())
  1584. {
  1585. CAlbum album = GetAlbumFromDataset(m_pDS.get());
  1586. CStdString path;
  1587. path.Format("musicdb://3/%ld/", album.idAlbum);
  1588. CFileItemPtr pItem(new CFileItem(path, album));
  1589. CStdString label;
  1590. label.Format("[%s] %s", albumLabel.c_str(), album.strAlbum);
  1591. pItem->SetLabel(label);
  1592. label.Format("B %s", album.strAlbum); // sort label is stored in the title tag
  1593. pItem->GetMusicInfoTag()->SetTitle(label);
  1594. albums.Add(pItem);
  1595. m_pDS->next();
  1596. }
  1597. m_pDS->close(); // cleanup recordset data
  1598. return true;
  1599. }
  1600. catch (...)
  1601. {
  1602. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  1603. }
  1604. return false;
  1605. }
  1606. int CMusicDatabase::SetAlbumInfo(int idAlbum, const CAlbum& album, const VECSONGS& songs, bool bTransaction)
  1607. {
  1608. CStdString strSQL;
  1609. try
  1610. {
  1611. if (NULL == m_pDB.get()) return -1;
  1612. if (NULL == m_pDS.get()) return -1;
  1613. if (bTransaction)
  1614. BeginTransaction();
  1615. // delete any album info we may have
  1616. strSQL=PrepareSQL("delete from albuminfo where idAlbum=%i", idAlbum);
  1617. m_pDS->exec(strSQL.c_str());
  1618. // insert the albuminfo
  1619. strSQL=PrepareSQL("insert into albuminfo (idAlbumInfo,idAlbum,strMoods,strStyles,strThemes,strReview,strImage,strLabel,strType,iRating,iYear) values(NULL,%i,'%s','%s','%s','%s','%s','%s','%s',%i,%i)",
  1620. idAlbum,
  1621. StringUtils::Join(album.moods, g_advancedSettings.m_musicItemSeparator).c_str(),
  1622. StringUtils::Join(album.styles, g_advancedSettings.m_musicItemSeparator).c_str(),
  1623. StringUtils::Join(album.themes, g_advancedSettings.m_musicItemSeparator).c_str(),
  1624. album.strReview.c_str(),
  1625. album.thumbURL.m_xml.c_str(),
  1626. album.strLabel.c_str(),
  1627. album.strType.c_str(),
  1628. album.iRating,
  1629. album.iYear);
  1630. m_pDS->exec(strSQL.c_str());
  1631. int idAlbumInfo = (int)m_pDS->lastinsertid();
  1632. if (SetAlbumInfoSongs(idAlbumInfo, songs))
  1633. {
  1634. if (bTransaction)
  1635. CommitTransaction();
  1636. }
  1637. else
  1638. {
  1639. if (bTransaction) // icky
  1640. RollbackTransaction();
  1641. idAlbumInfo = -1;
  1642. }
  1643. return idAlbumInfo;
  1644. }
  1645. catch (...)
  1646. {
  1647. CLog::Log(LOGERROR, "%s failed with query (%s)", __FUNCTION__, strSQL.c_str());
  1648. }
  1649. if (bTransaction)
  1650. RollbackTransaction();
  1651. return -1;
  1652. }
  1653. int CMusicDatabase::SetArtistInfo(int idArtist, const CArtist& artist)
  1654. {
  1655. CStdString strSQL;
  1656. try
  1657. {
  1658. if (NULL == m_pDB.get()) return -1;
  1659. if (NULL == m_pDS.get()) return -1;
  1660. // delete any artist info we may have
  1661. strSQL=PrepareSQL("delete from artistinfo where idArtist=%i", idArtist);
  1662. m_pDS->exec(strSQL.c_str());
  1663. strSQL=PrepareSQL("delete from discography where idArtist=%i", idArtist);
  1664. m_pDS->exec(strSQL.c_str());
  1665. // insert the artistinfo
  1666. strSQL=PrepareSQL("insert into artistinfo (idArtistInfo,idArtist,strBorn,strFormed,strGenres,strMoods,strStyles,strInstruments,strBiography,strDied,strDisbanded,strYearsActive,strImage,strFanart) values(NULL,%i,'%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')",
  1667. idArtist, artist.strBorn.c_str(),
  1668. artist.strFormed.c_str(),
  1669. StringUtils::Join(artist.genre, g_advancedSettings.m_musicItemSeparator).c_str(),
  1670. StringUtils::Join(artist.moods, g_advancedSettings.m_musicItemSeparator).c_str(),
  1671. StringUtils::Join(artist.styles, g_advancedSettings.m_musicItemSeparator).c_str(),
  1672. StringUtils::Join(artist.instruments, g_advancedSettings.m_musicItemSeparator).c_str(),
  1673. artist.strBiography.c_str(),
  1674. artist.strDied.c_str(),
  1675. artist.strDisbanded.c_str(),
  1676. StringUtils::Join(artist.yearsActive, g_advancedSettings.m_musicItemSeparator).c_str(),
  1677. artist.thumbURL.m_xml.c_str(),
  1678. artist.fanart.m_xml.c_str());
  1679. m_pDS->exec(strSQL.c_str());
  1680. int idArtistInfo = (int)m_pDS->lastinsertid();
  1681. for (unsigned int i=0;i<artist.discography.size();++i)
  1682. {
  1683. strSQL=PrepareSQL("insert into discography (idArtist,strAlbum,strYear) values (%i,'%s','%s')",idArtist,artist.discography[i].first.c_str(),artist.discography[i].second.c_str());
  1684. m_pDS->exec(strSQL.c_str());
  1685. }
  1686. return idArtistInfo;
  1687. }
  1688. catch (...)
  1689. {
  1690. CLog::Log(LOGERROR, "%s - failed with query (%s)", __FUNCTION__, strSQL.c_str());
  1691. }
  1692. return -1;
  1693. }
  1694. bool CMusicDatabase::SetAlbumInfoSongs(int idAlbumInfo, const VECSONGS& songs)
  1695. {
  1696. CStdString strSQL;
  1697. try
  1698. {
  1699. if (NULL == m_pDB.get()) return false;
  1700. if (NULL == m_pDS.get()) return false;
  1701. strSQL=PrepareSQL("delete from albuminfosong where idAlbumInfo=%i", idAlbumInfo);
  1702. m_pDS->exec(strSQL.c_str());
  1703. for (int i = 0; i < (int)songs.size(); i++)
  1704. {
  1705. CSong song = songs[i];
  1706. strSQL=PrepareSQL("insert into albuminfosong (idAlbumInfoSong,idAlbumInfo,iTrack,strTitle,iDuration) values(NULL,%i,%i,'%s',%i)",
  1707. idAlbumInfo,
  1708. song.iTrack,
  1709. song.strTitle.c_str(),
  1710. song.iDuration);
  1711. m_pDS->exec(strSQL.c_str());
  1712. }
  1713. return true;
  1714. }
  1715. catch (...)
  1716. {
  1717. CLog::Log(LOGERROR, "%s failed with query (%s)", __FUNCTION__, strSQL.c_str());
  1718. }
  1719. return false;
  1720. }
  1721. bool CMusicDatabase::CleanupSongsByIds(const CStdString &strSongIds)
  1722. {
  1723. try
  1724. {
  1725. if (NULL == m_pDB.get()) return false;
  1726. if (NULL == m_pDS.get()) return false;
  1727. // ok, now find all idSong's
  1728. CStdString strSQL=PrepareSQL("select * from song join path on song.idPath = path.idPath where song.idSong in %s", strSongIds.c_str());
  1729. if (!m_pDS->query(strSQL.c_str())) return false;
  1730. int iRowsFound = m_pDS->num_rows();
  1731. if (iRowsFound == 0)
  1732. {
  1733. m_pDS->close();
  1734. return true;
  1735. }
  1736. CStdString strSongsToDelete = "";
  1737. while (!m_pDS->eof())
  1738. { // get the full song path
  1739. CStdString strFileName;
  1740. URIUtils::AddFileToFolder(m_pDS->fv("path.strPath").get_asString(), m_pDS->fv("song.strFileName").get_asString(), strFileName);
  1741. // Special case for streams inside an ogg file. (oggstream)
  1742. // The last dir in the path is the ogg file that
  1743. // contains the stream, so test if its there
  1744. CStdString strExtension=URIUtils::GetExtension(strFileName);
  1745. if (strExtension==".oggstream" || strExtension==".nsfstream")
  1746. {
  1747. CStdString strFileAndPath=strFileName;
  1748. URIUtils::GetDirectory(strFileAndPath, strFileName);
  1749. // we are dropping back to a file, so remove the slash at end
  1750. URIUtils::RemoveSlashAtEnd(strFileName);
  1751. }
  1752. if (!CFile::Exists(strFileName))
  1753. { // file no longer exists, so add to deletion list
  1754. strSongsToDelete += m_pDS->fv("song.idSong").get_asString() + ",";
  1755. }
  1756. m_pDS->next();
  1757. }
  1758. m_pDS->close();
  1759. if ( ! strSongsToDelete.IsEmpty() )
  1760. {
  1761. strSongsToDelete = "(" + strSongsToDelete.TrimRight(",") + ")";
  1762. // ok, now delete these songs + all references to them from the linked tables
  1763. strSQL = "delete from song where idSong in " + strSongsToDelete;
  1764. m_pDS->exec(strSQL.c_str());
  1765. strSQL = "delete from song_artist where idSong in " + strSongsToDelete;
  1766. m_pDS->exec(strSQL.c_str());
  1767. strSQL = "delete from song_genre where idSong in " + strSongsToDelete;
  1768. m_pDS->exec(strSQL.c_str());
  1769. strSQL = "delete from karaokedata where idSong in " + strSongsToDelete;
  1770. m_pDS->exec(strSQL.c_str());
  1771. m_pDS->close();
  1772. }
  1773. return true;
  1774. }
  1775. catch (...)
  1776. {
  1777. CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupSongsFromPaths()");
  1778. }
  1779. return false;
  1780. }
  1781. bool CMusicDatabase::CleanupSongs()
  1782. {
  1783. try
  1784. {
  1785. // run through all songs and get all unique path ids
  1786. int iLIMIT = 1000;
  1787. for (int i=0;;i+=iLIMIT)
  1788. {
  1789. CStdString strSQL=PrepareSQL("select song.idSong from song order by song.idSong limit %i offset %i",iLIMIT,i);
  1790. if (!m_pDS->query(strSQL.c_str())) return false;
  1791. int iRowsFound = m_pDS->num_rows();
  1792. // keep going until no rows are left!
  1793. if (iRowsFound == 0)
  1794. {
  1795. m_pDS->close();
  1796. return true;
  1797. }
  1798. CStdString strSongIds = "(";
  1799. while (!m_pDS->eof())
  1800. {
  1801. strSongIds += m_pDS->fv("song.idSong").get_asString() + ",";
  1802. m_pDS->next();
  1803. }
  1804. m_pDS->close();
  1805. strSongIds.TrimRight(",");
  1806. strSongIds += ")";
  1807. CLog::Log(LOGDEBUG,"Checking songs from song ID list: %s",strSongIds.c_str());
  1808. if (!CleanupSongsByIds(strSongIds)) return false;
  1809. }
  1810. return true;
  1811. }
  1812. catch(...)
  1813. {
  1814. CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupSongs()");
  1815. }
  1816. return false;
  1817. }
  1818. bool CMusicDatabase::CleanupAlbums()
  1819. {
  1820. try
  1821. {
  1822. // This must be run AFTER songs have been cleaned up
  1823. // delete albums with no reference to songs
  1824. CStdString strSQL = "select * from album where album.idAlbum not in (select idAlbum from song)";
  1825. if (!m_pDS->query(strSQL.c_str())) return false;
  1826. int iRowsFound = m_pDS->num_rows();
  1827. if (iRowsFound == 0)
  1828. {
  1829. m_pDS->close();
  1830. return true;
  1831. }
  1832. CStdString strAlbumIds = "(";
  1833. while (!m_pDS->eof())
  1834. {
  1835. strAlbumIds += m_pDS->fv("album.idAlbum").get_asString() + ",";
  1836. m_pDS->next();
  1837. }
  1838. m_pDS->close();
  1839. strAlbumIds.TrimRight(",");
  1840. strAlbumIds += ")";
  1841. // ok, now we can delete them and the references in the linked tables
  1842. strSQL = "delete from album where idAlbum in " + strAlbumIds;
  1843. m_pDS->exec(strSQL.c_str());
  1844. strSQL = "delete from album_artist where idAlbum in " + strAlbumIds;
  1845. m_pDS->exec(strSQL.c_str());
  1846. strSQL = "delete from album_genre where idAlbum in " + strAlbumIds;
  1847. m_pDS->exec(strSQL.c_str());
  1848. strSQL = "delete from albuminfo where idAlbum in " + strAlbumIds;
  1849. m_pDS->exec(strSQL.c_str());
  1850. return true;
  1851. }
  1852. catch (...)
  1853. {
  1854. CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupAlbums()");
  1855. }
  1856. return false;
  1857. }
  1858. bool CMusicDatabase::CleanupPaths()
  1859. {
  1860. try
  1861. {
  1862. // needs to be done AFTER the songs and albums have been cleaned up.
  1863. // we can happily delete any path that has no reference to a song
  1864. // but we must keep all paths that have been scanned that may contain songs in subpaths
  1865. // first create a temporary table of song paths
  1866. m_pDS->exec("CREATE TEMPORARY TABLE songpaths (idPath integer, strPath varchar(512))\n");
  1867. m_pDS->exec("INSERT INTO songpaths select idPath,strPath from path where idPath in (select idPath from song)\n");
  1868. // grab all paths that aren't immediately connected with a song
  1869. CStdString sql = "select * from path where idPath not in (select idPath from song)";
  1870. if (!m_pDS->query(sql.c_str())) return false;
  1871. int iRowsFound = m_pDS->num_rows();
  1872. if (iRowsFound == 0)
  1873. {
  1874. m_pDS->close();
  1875. return true;
  1876. }
  1877. // and construct a list to delete
  1878. CStdString deleteSQL;
  1879. while (!m_pDS->eof())
  1880. {
  1881. // anything that isn't a parent path of a song path is to be deleted
  1882. CStdString path = m_pDS->fv("strPath").get_asString();
  1883. CStdString sql = PrepareSQL("select count(idPath) from songpaths where SUBSTR(strPath,1,%i)='%s'", StringUtils::utf8_strlen(path.c_str()), path.c_str());
  1884. if (m_pDS2->query(sql.c_str()) && m_pDS2->num_rows() == 1 && m_pDS2->fv(0).get_asInt() == 0)
  1885. deleteSQL += PrepareSQL("%i,", m_pDS->fv("idPath").get_asInt()); // nothing found, so delete
  1886. m_pDS2->close();
  1887. m_pDS->next();
  1888. }
  1889. m_pDS->close();
  1890. if ( ! deleteSQL.IsEmpty() )
  1891. {
  1892. deleteSQL = "DELETE FROM path WHERE idPath IN (" + deleteSQL.TrimRight(',') + ")";
  1893. // do the deletion, and drop our temp table
  1894. m_pDS->exec(deleteSQL.c_str());
  1895. }
  1896. m_pDS->exec("drop table songpaths");
  1897. return true;
  1898. }
  1899. catch (...)
  1900. {
  1901. CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupPaths() or was aborted");
  1902. }
  1903. return false;
  1904. }
  1905. bool CMusicDatabase::CleanupArtists()
  1906. {
  1907. try
  1908. {
  1909. // (nested queries by Bobbin007)
  1910. // must be executed AFTER the song, album and their artist link tables are cleaned.
  1911. // don't delete the "Various Artists" string
  1912. CStdString strVariousArtists = g_localizeStrings.Get(340);
  1913. int idVariousArtists = AddArtist(strVariousArtists);
  1914. CStdString strSQL = "delete from artist where idArtist not in (select idArtist from song_artist)";
  1915. strSQL += " and idArtist not in (select idArtist from album_artist)";
  1916. CStdString strSQL2;
  1917. strSQL2.Format(" and idArtist<>%i", idVariousArtists);
  1918. strSQL += strSQL2;
  1919. m_pDS->exec(strSQL.c_str());
  1920. m_pDS->exec("delete from artistinfo where idArtist not in (select idArtist from artist)");
  1921. m_pDS->exec("delete from album_artist where idArtist not in (select idArtist from artist)");
  1922. m_pDS->exec("delete from song_artist where idArtist not in (select idArtist from artist)");
  1923. m_pDS->exec("delete from discography where idArtist not in (select idArtist from artist)");
  1924. return true;
  1925. }
  1926. catch (...)
  1927. {
  1928. CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupArtists() or was aborted");
  1929. }
  1930. return false;
  1931. }
  1932. bool CMusicDatabase::CleanupGenres()
  1933. {
  1934. try
  1935. {
  1936. // Cleanup orphaned genres (ie those that don't belong to a song or an albuminfo entry)
  1937. // (nested queries by Bobbin007)
  1938. // Must be executed AFTER the song, song_genre, albuminfo and album_genre tables have been cleaned.
  1939. CStdString strSQL = "delete from genre where idGenre not in (select idGenre from song_genre) and";
  1940. strSQL += " idGenre not in (select idGenre from album_genre)";
  1941. m_pDS->exec(strSQL.c_str());
  1942. return true;
  1943. }
  1944. catch (...)
  1945. {
  1946. CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupGenres() or was aborted");
  1947. }
  1948. return false;
  1949. }
  1950. bool CMusicDatabase::CleanupOrphanedItems()
  1951. {
  1952. // paths aren't cleaned up here - they're cleaned up in RemoveSongsFromPath()
  1953. if (NULL == m_pDB.get()) return false;
  1954. if (NULL == m_pDS.get()) return false;
  1955. if (!CleanupAlbums()) return false;
  1956. if (!CleanupArtists()) return false;
  1957. if (!CleanupGenres()) return false;
  1958. return true;
  1959. }
  1960. int CMusicDatabase::Cleanup(CGUIDialogProgress *pDlgProgress)
  1961. {
  1962. if (NULL == m_pDB.get()) return ERROR_DATABASE;
  1963. if (NULL == m_pDS.get()) return ERROR_DATABASE;
  1964. unsigned int time = XbmcThreads::SystemClockMillis();
  1965. CLog::Log(LOGNOTICE, "%s: Starting musicdatabase cleanup ..", __FUNCTION__);
  1966. // first cleanup any songs with invalid paths
  1967. if (pDlgProgress)
  1968. {
  1969. pDlgProgress->SetHeading(700);
  1970. pDlgProgress->SetLine(0, "");
  1971. pDlgProgress->SetLine(1, 318);
  1972. pDlgProgress->SetLine(2, 330);
  1973. pDlgProgress->SetPercentage(0);
  1974. pDlgProgress->StartModal();
  1975. pDlgProgress->ShowProgressBar(true);
  1976. }
  1977. if (!CleanupSongs())
  1978. {
  1979. RollbackTransaction();
  1980. return ERROR_REORG_SONGS;
  1981. }
  1982. // then the albums that are not linked to a song or to albuminfo, or whose path is removed
  1983. if (pDlgProgress)
  1984. {
  1985. pDlgProgress->SetLine(1, 326);
  1986. pDlgProgress->SetPercentage(20);
  1987. pDlgProgress->Progress();
  1988. }
  1989. if (!CleanupAlbums())
  1990. {
  1991. RollbackTransaction();
  1992. return ERROR_REORG_ALBUM;
  1993. }
  1994. // now the paths
  1995. if (pDlgProgress)
  1996. {
  1997. pDlgProgress->SetLine(1, 324);
  1998. pDlgProgress->SetPercentage(40);
  1999. pDlgProgress->Progress();
  2000. }
  2001. if (!CleanupPaths())
  2002. {
  2003. RollbackTransaction();
  2004. return ERROR_REORG_PATH;
  2005. }
  2006. // and finally artists + genres
  2007. if (pDlgProgress)
  2008. {
  2009. pDlgProgress->SetLine(1, 320);
  2010. pDlgProgress->SetPercentage(60);
  2011. pDlgProgress->Progress();
  2012. }
  2013. if (!CleanupArtists())
  2014. {
  2015. RollbackTransaction();
  2016. return ERROR_REORG_ARTIST;
  2017. }
  2018. if (pDlgProgress)
  2019. {
  2020. pDlgProgress->SetLine(1, 322);
  2021. pDlgProgress->SetPercentage(80);
  2022. pDlgProgress->Progress();
  2023. }
  2024. if (!CleanupGenres())
  2025. {
  2026. RollbackTransaction();
  2027. return ERROR_REORG_GENRE;
  2028. }
  2029. // commit transaction
  2030. if (pDlgProgress)
  2031. {
  2032. pDlgProgress->SetLine(1, 328);
  2033. pDlgProgress->SetPercentage(90);
  2034. pDlgProgress->Progress();
  2035. }
  2036. if (!CommitTransaction())
  2037. {
  2038. RollbackTransaction();
  2039. return ERROR_WRITING_CHANGES;
  2040. }
  2041. // and compress the database
  2042. if (pDlgProgress)
  2043. {
  2044. pDlgProgress->SetLine(1, 331);
  2045. pDlgProgress->SetPercentage(100);
  2046. pDlgProgress->Progress();
  2047. }
  2048. time = XbmcThreads::SystemClockMillis() - time;
  2049. CLog::Log(LOGNOTICE, "%s: Cleaning musicdatabase done. Operation took %s", __FUNCTION__, StringUtils::SecondsToTimeString(time / 1000).c_str());
  2050. if (!Compress(false))
  2051. {
  2052. return ERROR_COMPRESSING;
  2053. }
  2054. return ERROR_OK;
  2055. }
  2056. void CMusicDatabase::DeleteAlbumInfo()
  2057. {
  2058. // open our database
  2059. Open();
  2060. if (NULL == m_pDB.get()) return ;
  2061. if (NULL == m_pDS.get()) return ;
  2062. // If we are scanning for music info in the background,
  2063. // other writing access to the database is prohibited.
  2064. if (g_application.IsMusicScanning())
  2065. {
  2066. CGUIDialogOK::ShowAndGetInput(189, 14057, 0, 0);
  2067. return;
  2068. }
  2069. CStdString strSQL="select * from albuminfo,album,artist where and albuminfo.idAlbum=album.idAlbum and album.idArtist=artist.idArtist order by album.strAlbum";
  2070. if (!m_pDS->query(strSQL.c_str())) return ;
  2071. int iRowsFound = m_pDS->num_rows();
  2072. if (iRowsFound == 0)
  2073. {
  2074. m_pDS->close();
  2075. CGUIDialogOK::ShowAndGetInput(313, 425, 0, 0);
  2076. }
  2077. vector<CAlbumCache> vecAlbums;
  2078. while (!m_pDS->eof())
  2079. {
  2080. CAlbumCache album;
  2081. album.idAlbum = m_pDS->fv("album.idAlbum").get_asInt() ;
  2082. album.strAlbum = m_pDS->fv("album.strAlbum").get_asString();
  2083. album.artist = StringUtils::Split(m_pDS->fv("album.strArtists").get_asString(), g_advancedSettings.m_musicItemSeparator);
  2084. vecAlbums.push_back(album);
  2085. m_pDS->next();
  2086. }
  2087. m_pDS->close();
  2088. // Show a selectdialog that the user can select the albuminfo to delete
  2089. CGUIDialogSelect *pDlg = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
  2090. if (pDlg)
  2091. {
  2092. pDlg->SetHeading(g_localizeStrings.Get(181).c_str());
  2093. pDlg->Reset();
  2094. for (int i = 0; i < (int)vecAlbums.size(); ++i)
  2095. {
  2096. CMusicDatabase::CAlbumCache& album = vecAlbums[i];
  2097. pDlg->Add(album.strAlbum + " - " + StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator));
  2098. }
  2099. pDlg->DoModal();
  2100. // and wait till user selects one
  2101. int iSelectedAlbum = pDlg->GetSelectedLabel();
  2102. if (iSelectedAlbum < 0)
  2103. {
  2104. vecAlbums.erase(vecAlbums.begin(), vecAlbums.end());
  2105. return ;
  2106. }
  2107. CAlbumCache& album = vecAlbums[iSelectedAlbum];
  2108. strSQL=PrepareSQL("delete from albuminfo where albuminfo.idAlbum=%i", album.idAlbum);
  2109. if (!m_pDS->exec(strSQL.c_str())) return ;
  2110. vecAlbums.erase(vecAlbums.begin(), vecAlbums.end());
  2111. }
  2112. }
  2113. bool CMusicDatabase::LookupCDDBInfo(bool bRequery/*=false*/)
  2114. {
  2115. #ifdef HAS_DVD_DRIVE
  2116. if (!g_guiSettings.GetBool("audiocds.usecddb"))
  2117. return false;
  2118. // check network connectivity
  2119. if (!g_application.getNetwork().IsAvailable())
  2120. return false;
  2121. // Get information for the inserted disc
  2122. CCdInfo* pCdInfo = g_mediaManager.GetCdInfo();
  2123. if (pCdInfo == NULL)
  2124. return false;
  2125. // If the disc has no tracks, we are finished here.
  2126. int nTracks = pCdInfo->GetTrackCount();
  2127. if (nTracks <= 0)
  2128. return false;
  2129. // Delete old info if any
  2130. if (bRequery)
  2131. {
  2132. CStdString strFile;
  2133. strFile.Format("%x.cddb", pCdInfo->GetCddbDiscId());
  2134. CFile::Delete(URIUtils::AddFileToFolder(g_settings.GetCDDBFolder(), strFile));
  2135. }
  2136. // Prepare cddb
  2137. Xcddb cddb;
  2138. cddb.setCacheDir(g_settings.GetCDDBFolder());
  2139. // Do we have to look for cddb information
  2140. if (pCdInfo->HasCDDBInfo() && !cddb.isCDCached(pCdInfo))
  2141. {
  2142. CGUIDialogProgress* pDialogProgress = (CGUIDialogProgress*)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
  2143. CGUIDialogSelect *pDlgSelect = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
  2144. if (!pDialogProgress) return false;
  2145. if (!pDlgSelect) return false;
  2146. // Show progress dialog if we have to connect to freedb.org
  2147. pDialogProgress->SetHeading(255); //CDDB
  2148. pDialogProgress->SetLine(0, ""); // Querying freedb for CDDB info
  2149. pDialogProgress->SetLine(1, 256);
  2150. pDialogProgress->SetLine(2, "");
  2151. pDialogProgress->ShowProgressBar(false);
  2152. pDialogProgress->StartModal();
  2153. // get cddb information
  2154. if (!cddb.queryCDinfo(pCdInfo))
  2155. {
  2156. pDialogProgress->Close();
  2157. int lasterror = cddb.getLastError();
  2158. // Have we found more then on match in cddb for this disc,...
  2159. if (lasterror == E_WAIT_FOR_INPUT)
  2160. {
  2161. // ...yes, show the matches found in a select dialog
  2162. // and let the user choose an entry.
  2163. pDlgSelect->Reset();
  2164. pDlgSelect->SetHeading(255);
  2165. int i = 1;
  2166. while (1)
  2167. {
  2168. CStdString strTitle = cddb.getInexactTitle(i);
  2169. if (strTitle == "") break;
  2170. CStdString strArtist = cddb.getInexactArtist(i);
  2171. if (!strArtist.IsEmpty())
  2172. strTitle += " - " + strArtist;
  2173. pDlgSelect->Add(strTitle);
  2174. i++;
  2175. }
  2176. pDlgSelect->DoModal();
  2177. // Has the user selected a match...
  2178. int iSelectedCD = pDlgSelect->GetSelectedLabel();
  2179. if (iSelectedCD >= 0)
  2180. {
  2181. // ...query cddb for the inexact match
  2182. if (!cddb.queryCDinfo(pCdInfo, 1 + iSelectedCD))
  2183. pCdInfo->SetNoCDDBInfo();
  2184. }
  2185. else
  2186. pCdInfo->SetNoCDDBInfo();
  2187. }
  2188. else if (lasterror == E_NO_MATCH_FOUND)
  2189. {
  2190. pCdInfo->SetNoCDDBInfo();
  2191. }
  2192. else
  2193. {
  2194. pCdInfo->SetNoCDDBInfo();
  2195. // ..no, an error occured, display it to the user
  2196. CStdString strErrorText;
  2197. strErrorText.Format("[%d] %s", cddb.getLastError(), cddb.getLastErrorText());
  2198. CGUIDialogOK::ShowAndGetInput(255, 257, strErrorText, 0);
  2199. }
  2200. } // if ( !cddb.queryCDinfo( pCdInfo ) )
  2201. else
  2202. pDialogProgress->Close();
  2203. } // if (pCdInfo->HasCDDBInfo() && g_settings.m_bUseCDDB)
  2204. // Filling the file items with cddb info happens in CMusicInfoTagLoaderCDDA
  2205. return pCdInfo->HasCDDBInfo();
  2206. #else
  2207. return false;
  2208. #endif
  2209. }
  2210. void CMusicDatabase::DeleteCDDBInfo()
  2211. {
  2212. #ifdef HAS_DVD_DRIVE
  2213. CFileItemList items;
  2214. if (!CDirectory::GetDirectory(g_settings.GetCDDBFolder(), items, ".cddb", DIR_FLAG_NO_FILE_DIRS))
  2215. {
  2216. CGUIDialogOK::ShowAndGetInput(313, 426, 0, 0);
  2217. return ;
  2218. }
  2219. // Show a selectdialog that the user can select the albuminfo to delete
  2220. CGUIDialogSelect *pDlg = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
  2221. if (pDlg)
  2222. {
  2223. pDlg->SetHeading(g_localizeStrings.Get(181).c_str());
  2224. pDlg->Reset();
  2225. map<ULONG, CStdString> mapCDDBIds;
  2226. for (int i = 0; i < items.Size(); ++i)
  2227. {
  2228. if (items[i]->m_bIsFolder)
  2229. continue;
  2230. CStdString strFile = URIUtils::GetFileName(items[i]->GetPath());
  2231. strFile.Delete(strFile.size() - 5, 5);
  2232. ULONG lDiscId = strtoul(strFile.c_str(), NULL, 16);
  2233. Xcddb cddb;
  2234. cddb.setCacheDir(g_settings.GetCDDBFolder());
  2235. if (!cddb.queryCache(lDiscId))
  2236. continue;
  2237. CStdString strDiskTitle, strDiskArtist;
  2238. cddb.getDiskTitle(strDiskTitle);
  2239. cddb.getDiskArtist(strDiskArtist);
  2240. CStdString str;
  2241. if (strDiskArtist.IsEmpty())
  2242. str = strDiskTitle;
  2243. else
  2244. str = strDiskTitle + " - " + strDiskArtist;
  2245. pDlg->Add(str);
  2246. mapCDDBIds.insert(pair<ULONG, CStdString>(lDiscId, str));
  2247. }
  2248. pDlg->Sort();
  2249. pDlg->DoModal();
  2250. // and wait till user selects one
  2251. int iSelectedAlbum = pDlg->GetSelectedLabel();
  2252. if (iSelectedAlbum < 0)
  2253. {
  2254. mapCDDBIds.erase(mapCDDBIds.begin(), mapCDDBIds.end());
  2255. return ;
  2256. }
  2257. CStdString strSelectedAlbum = pDlg->GetSelectedLabelText();
  2258. map<ULONG, CStdString>::iterator it;
  2259. for (it = mapCDDBIds.begin();it != mapCDDBIds.end();it++)
  2260. {
  2261. if (it->second == strSelectedAlbum)
  2262. {
  2263. CStdString strFile;
  2264. strFile.Format("%x.cddb", it->first);
  2265. CFile::Delete(URIUtils::AddFileToFolder(g_settings.GetCDDBFolder(), strFile));
  2266. break;
  2267. }
  2268. }
  2269. mapCDDBIds.erase(mapCDDBIds.begin(), mapCDDBIds.end());
  2270. }
  2271. #endif
  2272. }
  2273. void CMusicDatabase::Clean()
  2274. {
  2275. // If we are scanning for music info in the background,
  2276. // other writing access to the database is prohibited.
  2277. if (g_application.IsMusicScanning())
  2278. {
  2279. CGUIDialogOK::ShowAndGetInput(189, 14057, 0, 0);
  2280. return;
  2281. }
  2282. if (CGUIDialogYesNo::ShowAndGetInput(313, 333, 0, 0))
  2283. {
  2284. CGUIDialogProgress* dlgProgress = (CGUIDialogProgress*)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
  2285. if (dlgProgress)
  2286. {
  2287. CMusicDatabase musicdatabase;
  2288. if (musicdatabase.Open())
  2289. {
  2290. int iReturnString = musicdatabase.Cleanup(dlgProgress);
  2291. musicdatabase.Close();
  2292. if (iReturnString != ERROR_OK)
  2293. {
  2294. CGUIDialogOK::ShowAndGetInput(313, iReturnString, 0, 0);
  2295. }
  2296. }
  2297. dlgProgress->Close();
  2298. }
  2299. }
  2300. }
  2301. bool CMusicDatabase::GetGenresNav(const CStdString& strBaseDir, CFileItemList& items)
  2302. {
  2303. try
  2304. {
  2305. if (NULL == m_pDB.get()) return false;
  2306. if (NULL == m_pDS.get()) return false;
  2307. // get primary genres for songs - could be simplified to just SELECT * FROM genre?
  2308. CStdString strSQL="SELECT * "
  2309. " FROM genre "
  2310. " WHERE idGenre IN"
  2311. "(SELECT song_genre.idGenre "
  2312. " FROM song_genre) ";
  2313. // block null strings
  2314. strSQL += " AND genre.strGenre != \"\"";
  2315. // run query
  2316. CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
  2317. if (!m_pDS->query(strSQL.c_str())) return false;
  2318. int iRowsFound = m_pDS->num_rows();
  2319. if (iRowsFound == 0)
  2320. {
  2321. m_pDS->close();
  2322. return false;
  2323. }
  2324. // get data from returned rows
  2325. while (!m_pDS->eof())
  2326. {
  2327. CFileItemPtr pItem(new CFileItem(m_pDS->fv("strGenre").get_asString()));
  2328. pItem->GetMusicInfoTag()->SetGenre(m_pDS->fv("strGenre").get_asString());
  2329. pItem->GetMusicInfoTag()->SetDatabaseId(m_pDS->fv("idGenre").get_asInt(), "genre");
  2330. CStdString strDir;
  2331. strDir.Format("%ld/", m_pDS->fv("idGenre").get_asInt());
  2332. pItem->SetPath(strBaseDir + strDir);
  2333. pItem->m_bIsFolder=true;
  2334. items.Add(pItem);
  2335. m_pDS->next();
  2336. }
  2337. // cleanup
  2338. m_pDS->close();
  2339. return true;
  2340. }
  2341. catch (...)
  2342. {
  2343. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  2344. }
  2345. return false;
  2346. }
  2347. bool CMusicDatabase::GetYearsNav(const CStdString& strBaseDir, CFileItemList& items)
  2348. {
  2349. try
  2350. {
  2351. if (NULL == m_pDB.get()) return false;
  2352. if (NULL == m_pDS.get()) return false;
  2353. // get years from album list
  2354. CStdString strSQL="select distinct iYear from album where iYear <> 0";
  2355. // run query
  2356. CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
  2357. if (!m_pDS->query(strSQL.c_str())) return false;
  2358. int iRowsFound = m_pDS->num_rows();
  2359. if (iRowsFound == 0)
  2360. {
  2361. m_pDS->close();
  2362. return false;
  2363. }
  2364. // get data from returned rows
  2365. while (!m_pDS->eof())
  2366. {
  2367. CFileItemPtr pItem(new CFileItem(m_pDS->fv("iYear").get_asString()));
  2368. SYSTEMTIME stTime;
  2369. stTime.wYear = (WORD)m_pDS->fv("iYear").get_asInt();
  2370. pItem->GetMusicInfoTag()->SetReleaseDate(stTime);
  2371. CStdString strDir;
  2372. strDir.Format("%ld/", m_pDS->fv("iYear").get_asInt());
  2373. pItem->SetPath(strBaseDir + strDir);
  2374. pItem->m_bIsFolder=true;
  2375. items.Add(pItem);
  2376. m_pDS->next();
  2377. }
  2378. // cleanup
  2379. m_pDS->close();
  2380. return true;
  2381. }
  2382. catch (...)
  2383. {
  2384. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  2385. }
  2386. return false;
  2387. }
  2388. bool CMusicDatabase::GetAlbumsByYear(const CStdString& strBaseDir, CFileItemList& items, int year)
  2389. {
  2390. CStdString where = PrepareSQL("where iYear=%ld", year);
  2391. return GetAlbumsByWhere(strBaseDir, where, "", items);
  2392. }
  2393. bool CMusicDatabase::GetArtistsNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre, bool albumArtistsOnly)
  2394. {
  2395. if (NULL == m_pDB.get()) return false;
  2396. if (NULL == m_pDS.get()) return false;
  2397. try
  2398. {
  2399. unsigned int time = XbmcThreads::SystemClockMillis();
  2400. CStdString strSQL = "(idArtist IN ";
  2401. if (idGenre==-1)
  2402. {
  2403. if (!albumArtistsOnly) // show all artists in this case (ie those linked to a song)
  2404. strSQL += "("
  2405. "SELECT song_artist.idArtist FROM song_artist" // All artists linked to a song
  2406. ") "
  2407. "or idArtist IN ";
  2408. // and always show any artists linked to an album (may be different from above due to album artist tag)
  2409. strSQL += "("
  2410. "SELECT album_artist.idArtist from album_artist "; // All artists linked to an album
  2411. if (albumArtistsOnly)
  2412. strSQL += "WHERE album_artist.boolFeatured = 0"; // then exclude those that have no extra artists
  2413. strSQL += ")"
  2414. ") ";
  2415. }
  2416. else
  2417. { // same statements as above, but limit to the specified genre
  2418. // in this case we show the whole lot always - there is no limitation to just album artists
  2419. if (!albumArtistsOnly) // show all artists in this case (ie those linked to a song)
  2420. strSQL+=PrepareSQL("("
  2421. "SELECT song_artist.idArtist FROM song_artist " // All artists linked to extra genres
  2422. "JOIN song_genre ON song_artist.idSong = song_genre.idSong "
  2423. "WHERE song_genre.idGenre=%i"
  2424. ") "
  2425. "or idArtist IN "
  2426. , idGenre);
  2427. // and add any artists linked to an album (may be different from above due to album artist tag)
  2428. strSQL += PrepareSQL("("
  2429. "SELECT album_artist.idArtist FROM album_artist " // All album artists linked to extra genres
  2430. "JOIN album_genre ON album_artist.idAlbum = album_genre.idAlbum "
  2431. "WHERE album_genre.idGenre=%i"
  2432. ") "
  2433. ")", idGenre);
  2434. }
  2435. // remove the null string
  2436. strSQL += " and strArtist != \"\"";
  2437. // and the various artist entry if applicable
  2438. if (!albumArtistsOnly)
  2439. {
  2440. CStdString strVariousArtists = g_localizeStrings.Get(340);
  2441. int idVariousArtists = AddArtist(strVariousArtists);
  2442. strSQL+=PrepareSQL(" and artistview.idArtist<>%i", idVariousArtists);
  2443. }
  2444. bool result = GetArtistsByWhere(strBaseDir, strSQL, items);
  2445. CLog::Log(LOGDEBUG,"Time to retrieve artists from dataset = %i", XbmcThreads::SystemClockMillis() - time);
  2446. return result;
  2447. }
  2448. catch (...)
  2449. {
  2450. m_pDS->close();
  2451. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  2452. }
  2453. return false;
  2454. }
  2455. bool CMusicDatabase::GetArtistsByWhere(const CStdString& strBaseDir, const CStdString &where, CFileItemList& items)
  2456. {
  2457. if (NULL == m_pDB.get()) return false;
  2458. if (NULL == m_pDS.get()) return false;
  2459. try
  2460. {
  2461. CStdString strSQL = "select * from artistview";
  2462. if (!where.empty())
  2463. strSQL += " WHERE " + where;
  2464. // run query
  2465. CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
  2466. if (!m_pDS->query(strSQL.c_str())) return false;
  2467. int iRowsFound = m_pDS->num_rows();
  2468. if (iRowsFound == 0)
  2469. {
  2470. m_pDS->close();
  2471. return false;
  2472. }
  2473. items.Reserve(iRowsFound);
  2474. // get data from returned rows
  2475. while (!m_pDS->eof())
  2476. {
  2477. CArtist artist = GetArtistFromDataset(m_pDS.get(), false);
  2478. CFileItemPtr pItem(new CFileItem(artist));
  2479. CStdString strDir;
  2480. strDir.Format("%ld/", artist.idArtist);
  2481. pItem->SetPath(strBaseDir + strDir);
  2482. pItem->GetMusicInfoTag()->SetDatabaseId(artist.idArtist, "artist");
  2483. pItem->SetIconImage("DefaultArtist.png");
  2484. SetPropertiesFromArtist(*pItem,artist);
  2485. items.Add(pItem);
  2486. m_pDS->next();
  2487. }
  2488. // cleanup
  2489. m_pDS->close();
  2490. return true;
  2491. }
  2492. catch (...)
  2493. {
  2494. m_pDS->close();
  2495. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  2496. }
  2497. return false;
  2498. }
  2499. bool CMusicDatabase::GetAlbumFromSong(int idSong, CAlbum &album)
  2500. {
  2501. try
  2502. {
  2503. if (NULL == m_pDB.get()) return false;
  2504. if (NULL == m_pDS.get()) return false;
  2505. CStdString strSQL = PrepareSQL("select albumview.* from song join albumview on song.idAlbum = albumview.idAlbum where song.idSong='%i'", idSong);
  2506. if (!m_pDS->query(strSQL.c_str())) return false;
  2507. int iRowsFound = m_pDS->num_rows();
  2508. if (iRowsFound != 1)
  2509. {
  2510. m_pDS->close();
  2511. return false;
  2512. }
  2513. album = GetAlbumFromDataset(m_pDS.get());
  2514. m_pDS->close();
  2515. return true;
  2516. }
  2517. catch (...)
  2518. {
  2519. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  2520. }
  2521. return false;
  2522. }
  2523. // This function won't be required if/when the fileitem tag has idSong information
  2524. bool CMusicDatabase::GetAlbumFromSong(const CSong &song, CAlbum &album)
  2525. {
  2526. try
  2527. {
  2528. if (NULL == m_pDB.get()) return false;
  2529. if (NULL == m_pDS.get()) return false;
  2530. if (song.idSong != -1) return GetAlbumFromSong(song.idSong, album);
  2531. CStdString path, file;
  2532. URIUtils::Split(song.strFileName, path, file);
  2533. CStdString strSQL = PrepareSQL("select albumview.* from song join albumview on song.idAlbum = albumview.idAlbum join path on song.idPath = path.idPath where song.strFileName='%s' and path.strPath='%s'", file.c_str(), path.c_str());
  2534. if (!m_pDS->query(strSQL.c_str())) return false;
  2535. int iRowsFound = m_pDS->num_rows();
  2536. if (iRowsFound != 1)
  2537. {
  2538. m_pDS->close();
  2539. return false;
  2540. }
  2541. album = GetAlbumFromDataset(m_pDS.get());
  2542. m_pDS->close();
  2543. return true;
  2544. }
  2545. catch (...)
  2546. {
  2547. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  2548. }
  2549. return false;
  2550. }
  2551. bool CMusicDatabase::GetAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre, int idArtist, int start, int end, const SortDescription &sortDescription /* = SortDescription() */)
  2552. {
  2553. //Create limit
  2554. CStdString limit;
  2555. if (start >= 0 && end >= 0)
  2556. {
  2557. limit.Format(" limit %i,%i", start, end);
  2558. }
  2559. // where clause
  2560. CStdString strWhere;
  2561. if (idGenre!=-1)
  2562. {
  2563. strWhere+=PrepareSQL("where (idAlbum IN "
  2564. "("
  2565. "select song.idAlbum from song " // All song genres
  2566. "JOIN song_genre ON song.idSong=song_genre.idSong "
  2567. "where song_genre.idGenre=%i"
  2568. ")"
  2569. ") " + limit
  2570. , idGenre);
  2571. }
  2572. if (idArtist!=-1)
  2573. {
  2574. if (strWhere.IsEmpty())
  2575. strWhere += "where ";
  2576. else
  2577. strWhere += "and ";
  2578. strWhere +=PrepareSQL("(idAlbum IN "
  2579. "("
  2580. "select song.idAlbum from song " // All albums linked to this artist via songs
  2581. "JOIN song_artist ON song.idSong=song_artist.idSong "
  2582. "WHERE song_artist.idArtist=%i"
  2583. ")"
  2584. " or idAlbum IN "
  2585. "("
  2586. "select album_artist.idAlbum from album_artist " // All albums where album artists fit
  2587. "where album_artist.idArtist=%i"
  2588. ")"
  2589. ") " + limit
  2590. , idArtist, idArtist);
  2591. }
  2592. else
  2593. { // no artist given, so exclude any single albums (aka empty tagged albums)
  2594. if (strWhere.IsEmpty())
  2595. strWhere += "where albumview.strAlbum <> ''" + limit;
  2596. else
  2597. strWhere += "and albumview.strAlbum <> ''" + limit;
  2598. }
  2599. return GetAlbumsByWhere(strBaseDir, strWhere, "", items, sortDescription);
  2600. }
  2601. bool CMusicDatabase::GetAlbumsByWhere(const CStdString &baseDir, const CStdString &where, const CStdString &order, CFileItemList &items, const SortDescription &sortDescription /* = SortDescription() */)
  2602. {
  2603. if (m_pDB.get() == NULL || m_pDS.get() == NULL)
  2604. return false;
  2605. try
  2606. {
  2607. int total = -1;
  2608. CStdString sql = "select * from albumview " + where;
  2609. // Apply the limiting directly here if there's no special sorting but limiting
  2610. CStdString whereLower = where;
  2611. whereLower.ToLower();
  2612. if (whereLower.find(" limit ") == string::npos &&
  2613. sortDescription.sortBy == SortByNone &&
  2614. (sortDescription.limitStart > 0 || sortDescription.limitEnd > 0))
  2615. {
  2616. total = (int)strtol(GetSingleValue("SELECT COUNT(1) FROM albumview " + where, m_pDS).c_str(), NULL, 10);
  2617. sql += DatabaseUtils::BuildLimitClause(sortDescription.limitEnd, sortDescription.limitStart);
  2618. }
  2619. CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, sql.c_str());
  2620. // run query
  2621. unsigned int time = XbmcThreads::SystemClockMillis();
  2622. if (!m_pDS->query(sql.c_str()))
  2623. return false;
  2624. CLog::Log(LOGDEBUG, "%s - query took %i ms",
  2625. __FUNCTION__, XbmcThreads::SystemClockMillis() - time); time = XbmcThreads::SystemClockMillis();
  2626. int iRowsFound = m_pDS->num_rows();
  2627. if (iRowsFound <= 0)
  2628. {
  2629. m_pDS->close();
  2630. return false;
  2631. }
  2632. // store the total value of items as a property
  2633. if (total < iRowsFound)
  2634. total = iRowsFound;
  2635. items.SetProperty("total", total);
  2636. DatabaseResults results;
  2637. results.reserve(iRowsFound);
  2638. if (!SortUtils::SortFromDataset(sortDescription, MediaTypeAlbum, m_pDS, results))
  2639. return false;
  2640. // get data from returned rows
  2641. items.Reserve(results.size());
  2642. const dbiplus::query_data &data = m_pDS->get_result_set().records;
  2643. for (DatabaseResults::const_iterator it = results.begin(); it != results.end(); it++)
  2644. {
  2645. unsigned int targetRow = (unsigned int)it->at(FieldRow).asInteger();
  2646. const dbiplus::sql_record* const record = data.at(targetRow);
  2647. try
  2648. {
  2649. CStdString strDir;
  2650. int idAlbum = record->at(album_idAlbum).get_asInt();
  2651. strDir.Format("%s%ld/", baseDir.c_str(), idAlbum);
  2652. CFileItemPtr pItem(new CFileItem(strDir, GetAlbumFromDataset(record)));
  2653. pItem->SetIconImage("DefaultAlbumCover.png");
  2654. items.Add(pItem);
  2655. }
  2656. catch (...)
  2657. {
  2658. m_pDS->close();
  2659. CLog::Log(LOGERROR, "%s - out of memory getting listing (got %i)", __FUNCTION__, items.Size());
  2660. }
  2661. }
  2662. // cleanup
  2663. m_pDS->close();
  2664. return true;
  2665. }
  2666. catch (...)
  2667. {
  2668. m_pDS->close();
  2669. CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, where.c_str());
  2670. }
  2671. return false;
  2672. }
  2673. bool CMusicDatabase::GetSongsByWhere(const CStdString &baseDir, const CStdString &whereClause, CFileItemList &items, const SortDescription &sortDescription /* = SortDescription() */)
  2674. {
  2675. if (m_pDB.get() == NULL || m_pDS.get() == NULL)
  2676. return false;
  2677. try
  2678. {
  2679. unsigned int time = XbmcThreads::SystemClockMillis();
  2680. int total = -1;
  2681. // We don't use PrepareSQL here, as the WHERE clause is already formatted.
  2682. CStdString strSQL = "select * from songview " + whereClause;
  2683. // Apply the limiting directly here if there's no special sorting but limiting
  2684. CStdString whereLower = whereClause;
  2685. whereLower.ToLower();
  2686. if (whereLower.find(" limit ") == string::npos &&
  2687. sortDescription.sortBy == SortByNone &&
  2688. (sortDescription.limitStart > 0 || sortDescription.limitEnd > 0))
  2689. {
  2690. total = (int)strtol(GetSingleValue("SELECT COUNT(1) FROM songview " + whereClause, m_pDS).c_str(), NULL, 10);
  2691. strSQL += DatabaseUtils::BuildLimitClause(sortDescription.limitEnd, sortDescription.limitStart);
  2692. }
  2693. CLog::Log(LOGDEBUG, "%s query = %s", __FUNCTION__, strSQL.c_str());
  2694. // run query
  2695. if (!m_pDS->query(strSQL.c_str()))
  2696. return false;
  2697. int iRowsFound = m_pDS->num_rows();
  2698. if (iRowsFound == 0)
  2699. {
  2700. m_pDS->close();
  2701. return false;
  2702. }
  2703. // store the total value of items as a property
  2704. if (total < iRowsFound)
  2705. total = iRowsFound;
  2706. items.SetProperty("total", total);
  2707. DatabaseResults results;
  2708. results.reserve(iRowsFound);
  2709. if (!SortUtils::SortFromDataset(sortDescription, MediaTypeSong, m_pDS, results))
  2710. return false;
  2711. // get data from returned rows
  2712. items.Reserve(results.size());
  2713. const dbiplus::query_data &data = m_pDS->get_result_set().records;
  2714. int count = 0;
  2715. for (DatabaseResults::const_iterator it = results.begin(); it != results.end(); it++)
  2716. {
  2717. unsigned int targetRow = (unsigned int)it->at(FieldRow).asInteger();
  2718. const dbiplus::sql_record* const record = data.at(targetRow);
  2719. try
  2720. {
  2721. CFileItemPtr item(new CFileItem);
  2722. GetFileItemFromDataset(record, item.get(), baseDir);
  2723. // HACK for sorting by database returned order
  2724. item->m_iprogramCount = ++count;
  2725. items.Add(item);
  2726. }
  2727. catch (...)
  2728. {
  2729. m_pDS->close();
  2730. CLog::Log(LOGERROR, "%s: out of memory loading query: %s", __FUNCTION__, whereClause.c_str());
  2731. return (items.Size() > 0);
  2732. }
  2733. }
  2734. // cleanup
  2735. m_pDS->close();
  2736. CLog::Log(LOGDEBUG, "%s(%s) - took %d ms", __FUNCTION__, whereClause.c_str(), XbmcThreads::SystemClockMillis() - time);
  2737. return true;
  2738. }
  2739. catch (...)
  2740. {
  2741. // cleanup
  2742. m_pDS->close();
  2743. CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, whereClause.c_str());
  2744. }
  2745. return false;
  2746. }
  2747. bool CMusicDatabase::GetSongsByYear(const CStdString& baseDir, CFileItemList& items, int year)
  2748. {
  2749. CStdString where=PrepareSQL("where (iYear=%ld)", year);
  2750. return GetSongsByWhere(baseDir, where, items);
  2751. }
  2752. bool CMusicDatabase::GetSongsNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre, int idArtist,int idAlbum, const SortDescription &sortDescription /* = SortDescription() */)
  2753. {
  2754. CStdString strWhere;
  2755. if (idAlbum!=-1)
  2756. strWhere=PrepareSQL("where (idAlbum=%ld) ", idAlbum);
  2757. if (idGenre!=-1)
  2758. {
  2759. if (strWhere.IsEmpty())
  2760. strWhere += "where ";
  2761. else
  2762. strWhere += "and ";
  2763. strWhere += PrepareSQL("(idSong IN "
  2764. "("
  2765. "SELECT song_genre.idSong FROM song_genre "
  2766. "WHERE song_genre.idGenre = %i"
  2767. ")"
  2768. ") "
  2769. , idGenre);
  2770. }
  2771. if (idArtist!=-1)
  2772. {
  2773. if (strWhere.IsEmpty())
  2774. strWhere += "where ";
  2775. else
  2776. strWhere += "and ";
  2777. strWhere += PrepareSQL("(idSong IN " // song artists
  2778. "("
  2779. "SELECT song_artist.idSong FROM song_artist "
  2780. "WHERE song_artist.idArtist=%i"
  2781. ") "
  2782. "or idSong IN " // album artists
  2783. "("
  2784. "SELECT song.idSong FROM song "
  2785. "JOIN album_artist ON song.idAlbum=album_artist.idAlbum "
  2786. "WHERE album_artist.idArtist=%i"
  2787. ")"
  2788. ") "
  2789. , idArtist, idArtist);
  2790. }
  2791. // run query
  2792. return GetSongsByWhere(strBaseDir, strWhere, items, sortDescription);
  2793. }
  2794. bool CMusicDatabase::UpdateOldVersion(int version)
  2795. {
  2796. if (version < 16)
  2797. {
  2798. // only if MySQL is used and default character set is not utf8
  2799. // string data needs to be converted to proper utf8
  2800. CStdString charset = m_pDS->getDatabase()->getDefaultCharset();
  2801. if (!m_sqlite && !charset.empty() && charset != "utf8")
  2802. {
  2803. map<CStdString, CStdStringArray> tables;
  2804. map<CStdString, CStdStringArray>::iterator itt;
  2805. CStdStringArray::iterator itc;
  2806. //columns that need to be converted
  2807. CStdStringArray c1;
  2808. c1.push_back("strAlbum");
  2809. c1.push_back("strExtraArtists");
  2810. c1.push_back("strExtraGenres");
  2811. tables.insert(pair<CStdString, CStdStringArray> ("album", c1));
  2812. CStdStringArray c2;
  2813. c2.push_back("strExtraGenres");
  2814. c2.push_back("strMoods");
  2815. c2.push_back("strStyles");
  2816. c2.push_back("strThemes");
  2817. c2.push_back("strReview");
  2818. c2.push_back("strLabel");
  2819. tables.insert(pair<CStdString, CStdStringArray> ("albuminfo", c2));
  2820. CStdStringArray c3;
  2821. c3.push_back("strTitle");
  2822. tables.insert(pair<CStdString, CStdStringArray> ("albuminfosong", c3));
  2823. CStdStringArray c4;
  2824. c4.push_back("strArtist");
  2825. tables.insert(pair<CStdString, CStdStringArray> ("artist", c4));
  2826. CStdStringArray c5;
  2827. c5.push_back("strBorn");
  2828. c5.push_back("strFormed");
  2829. c5.push_back("strGenres");
  2830. c5.push_back("strMoods");
  2831. c5.push_back("strStyles");
  2832. c5.push_back("strInstruments");
  2833. c5.push_back("strBiography");
  2834. c5.push_back("strDied");
  2835. c5.push_back("strDisbanded");
  2836. c5.push_back("strYearsActive");
  2837. tables.insert(pair<CStdString, CStdStringArray> ("artistinfo", c5));
  2838. CStdStringArray c6;
  2839. c6.push_back("strAlbum");
  2840. tables.insert(pair<CStdString, CStdStringArray> ("discography", c6));
  2841. CStdStringArray c7;
  2842. c7.push_back("strGenre");
  2843. tables.insert(pair<CStdString, CStdStringArray> ("genre", c7));
  2844. CStdStringArray c8;
  2845. c8.push_back("strKaraLyrics");
  2846. tables.insert(pair<CStdString, CStdStringArray> ("karaokedata", c8));
  2847. CStdStringArray c9;
  2848. c9.push_back("strTitle");
  2849. c9.push_back("strFilename");
  2850. c9.push_back("comment");
  2851. tables.insert(pair<CStdString, CStdStringArray> ("song", c9));
  2852. CStdStringArray c10;
  2853. c10.push_back("strPath");
  2854. tables.insert(pair<CStdString, CStdStringArray> ("path", c10));
  2855. for (itt = tables.begin(); itt != tables.end(); ++itt)
  2856. {
  2857. CStdString q;
  2858. q = PrepareSQL("UPDATE `%s` SET", itt->first.c_str());
  2859. for (itc = itt->second.begin(); itc != itt->second.end(); ++itc)
  2860. {
  2861. q += PrepareSQL(" `%s` = CONVERT(CAST(CONVERT(`%s` USING %s) AS BINARY) USING utf8)",
  2862. itc->c_str(), itc->c_str(), charset.c_str());
  2863. if (*itc != itt->second.back())
  2864. {
  2865. q += ", ";
  2866. }
  2867. }
  2868. m_pDS->exec(q);
  2869. }
  2870. }
  2871. }
  2872. if (version < 17)
  2873. {
  2874. m_pDS->exec("CREATE INDEX idxAlbum2 ON album(idArtist)");
  2875. m_pDS->exec("CREATE INDEX idxSong3 ON song(idAlbum)");
  2876. m_pDS->exec("CREATE INDEX idxSong4 ON song(idArtist)");
  2877. m_pDS->exec("CREATE INDEX idxSong5 ON song(idGenre)");
  2878. m_pDS->exec("CREATE INDEX idxSong6 ON song(idPath)");
  2879. }
  2880. if (version < 19)
  2881. {
  2882. int len = g_advancedSettings.m_musicItemSeparator.size() + 1;
  2883. CStdString sql = PrepareSQL("UPDATE song SET strExtraArtists=SUBSTR(strExtraArtists,%i), strExtraGenres=SUBSTR(strExtraGenres,%i)", len, len);
  2884. m_pDS->exec(sql.c_str());
  2885. sql = PrepareSQL("UPDATE album SET strExtraArtists=SUBSTR(strExtraArtists,%i), strExtraGenres=SUBSTR(strExtraGenres,%i)", len, len);
  2886. m_pDS->exec(sql.c_str());
  2887. }
  2888. if (version < 21)
  2889. {
  2890. m_pDS->exec("CREATE TABLE album_artist ( idArtist integer, idAlbum integer, boolFeatured integer, iOrder integer )\n");
  2891. m_pDS->exec("CREATE UNIQUE INDEX idxAlbumArtist_1 ON album_artist ( idAlbum, idArtist )\n");
  2892. m_pDS->exec("CREATE UNIQUE INDEX idxAlbumArtist_2 ON album_artist ( idArtist, idAlbum )\n");
  2893. m_pDS->exec("CREATE INDEX idxAlbumArtist_3 ON album_artist ( boolFeatured )\n");
  2894. m_pDS->exec("INSERT INTO album_artist (idArtist, idAlbum, boolFeatured, iOrder) SELECT idArtist, idAlbum, 1, iPosition FROM exartistalbum");
  2895. m_pDS->exec("REPLACE INTO album_artist (idArtist, idAlbum, boolFeatured, iOrder) SELECT idArtist, idAlbum, 0, 0 FROM album");
  2896. CStdString strSQL;
  2897. strSQL=PrepareSQL("SELECT album.idAlbum AS idAlbum, strExtraArtists,"
  2898. " album.idArtist AS idArtist, strArtist FROM album "
  2899. " LEFT OUTER JOIN artist ON album.idArtist=artist.idArtist");
  2900. if (!m_pDS->query(strSQL.c_str()))
  2901. {
  2902. CLog::Log(LOGDEBUG, "%s could not upgrade albums table", __FUNCTION__);
  2903. return false;
  2904. }
  2905. VECALBUMS albums;
  2906. while (!m_pDS->eof())
  2907. {
  2908. CAlbum album;
  2909. album.idAlbum = m_pDS->fv("idAlbum").get_asInt();
  2910. album.artist.push_back(m_pDS->fv("strArtist").get_asString());
  2911. if (!m_pDS->fv("strExtraArtists").get_asString().empty())
  2912. {
  2913. std::vector<std::string> extraArtists = StringUtils::Split(m_pDS->fv("strExtraArtists").get_asString(), g_advancedSettings.m_musicItemSeparator);
  2914. album.artist.insert(album.artist.end(), extraArtists.begin(), extraArtists.end());
  2915. }
  2916. albums.push_back(album);
  2917. m_pDS->next();
  2918. }
  2919. m_pDS->close();
  2920. m_pDS->exec("CREATE TABLE album_new ( idAlbum integer primary key, strAlbum varchar(256), strArtists text, idGenre integer, strExtraGenres text, iYear integer, idThumb integer)");
  2921. m_pDS->exec("INSERT INTO album_new ( idAlbum, strAlbum, idGenre, strExtraGenres, iYear, idThumb ) SELECT idAlbum, strAlbum, idGenre, strExtraGenres, iYear, idThumb FROM album");
  2922. for (VECALBUMS::iterator it = albums.begin(); it != albums.end(); ++it)
  2923. {
  2924. CStdString strSQL;
  2925. strSQL = PrepareSQL("UPDATE album_new SET strArtists='%s' WHERE idAlbum=%i", StringUtils::Join(it->artist, g_advancedSettings.m_musicItemSeparator).c_str(), it->idAlbum);
  2926. m_pDS->exec(strSQL);
  2927. }
  2928. m_pDS->exec("DROP TABLE album");
  2929. m_pDS->exec("ALTER TABLE album_new RENAME TO album");
  2930. m_pDS->exec("CREATE INDEX idxAlbum ON album(strAlbum)");
  2931. m_pDS->exec("DROP TABLE IF EXISTS exartistalbum");
  2932. }
  2933. if (version < 22)
  2934. {
  2935. m_pDS->exec("CREATE TABLE song_artist ( idArtist integer, idSong integer, boolFeatured integer, iOrder integer )\n");
  2936. m_pDS->exec("CREATE UNIQUE INDEX idxSongArtist_1 ON song_artist ( idSong, idArtist )\n");
  2937. m_pDS->exec("CREATE UNIQUE INDEX idxSongArtist_2 ON song_artist ( idArtist, idSong )\n");
  2938. m_pDS->exec("CREATE INDEX idxSongArtist_3 ON song_artist ( boolFeatured )\n");
  2939. m_pDS->exec("INSERT INTO song_artist (idArtist, idSong, boolFeatured, iOrder) SELECT idArtist, idSong, 1, iPosition FROM exartistsong");
  2940. m_pDS->exec("REPLACE INTO song_artist (idArtist, idSong, boolFeatured, iOrder) SELECT idArtist, idSong, 0, 0 FROM song");
  2941. CStdString strSQL;
  2942. strSQL=PrepareSQL("SELECT song.idSong AS idSong, strExtraArtists,"
  2943. " song.idArtist AS idArtist, strArtist FROM song "
  2944. " LEFT OUTER JOIN artist ON song.idArtist=artist.idArtist");
  2945. if (!m_pDS->query(strSQL.c_str()))
  2946. {
  2947. CLog::Log(LOGDEBUG, "%s could not upgrade songs table", __FUNCTION__);
  2948. return false;
  2949. }
  2950. VECSONGS songs;
  2951. while (!m_pDS->eof())
  2952. {
  2953. CSong song;
  2954. song.idSong = m_pDS->fv("idSong").get_asInt();
  2955. song.artist.push_back(m_pDS->fv("strArtist").get_asString());
  2956. if (!m_pDS->fv("strExtraArtists").get_asString().empty())
  2957. {
  2958. std::vector<std::string> extraArtists = StringUtils::Split(m_pDS->fv("strExtraArtists").get_asString(), g_advancedSettings.m_musicItemSeparator);
  2959. song.artist.insert(song.artist.end(), extraArtists.begin(), extraArtists.end());
  2960. }
  2961. songs.push_back(song);
  2962. m_pDS->next();
  2963. }
  2964. m_pDS->close();
  2965. 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)");
  2966. 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");
  2967. for (VECSONGS::iterator it = songs.begin(); it != songs.end(); ++it)
  2968. {
  2969. CStdString strSQL;
  2970. strSQL = PrepareSQL("UPDATE song_new SET strArtists='%s' WHERE idSong=%i", StringUtils::Join(it->artist, g_advancedSettings.m_musicItemSeparator).c_str(), it->idSong);
  2971. m_pDS->exec(strSQL);
  2972. }
  2973. m_pDS->exec("DROP TABLE song");
  2974. m_pDS->exec("ALTER TABLE song_new RENAME TO song");
  2975. m_pDS->exec("CREATE INDEX idxSong ON song(strTitle)");
  2976. m_pDS->exec("CREATE INDEX idxSong1 ON song(iTimesPlayed)");
  2977. m_pDS->exec("CREATE INDEX idxSong2 ON song(lastplayed)");
  2978. m_pDS->exec("CREATE INDEX idxSong3 ON song(idAlbum)");
  2979. m_pDS->exec("CREATE INDEX idxSong6 ON song(idPath)");
  2980. m_pDS->exec("DROP TABLE IF EXISTS exartistsong");
  2981. }
  2982. if (version < 23)
  2983. {
  2984. m_pDS->exec("CREATE TABLE album_genre ( idGenre integer, idAlbum integer, iOrder integer )\n");
  2985. m_pDS->exec("CREATE UNIQUE INDEX idxAlbumGenre_1 ON album_genre ( idAlbum, idGenre )\n");
  2986. m_pDS->exec("CREATE UNIQUE INDEX idxAlbumGenre_2 ON album_genre ( idGenre, idAlbum )\n");
  2987. m_pDS->exec("INSERT INTO album_genre ( idGenre, idAlbum, iOrder) SELECT idGenre, idAlbum, iPosition FROM exgenrealbum");
  2988. m_pDS->exec("REPLACE INTO album_genre ( idGenre, idAlbum, iOrder) SELECT idGenre, idAlbum, 0 FROM album");
  2989. CStdString strSQL;
  2990. strSQL=PrepareSQL("SELECT album.idAlbum AS idAlbum, strExtraGenres,"
  2991. " album.idGenre AS idGenre, strGenre FROM album "
  2992. " JOIN genre ON album.idGenre=genre.idGenre");
  2993. if (!m_pDS->query(strSQL.c_str()))
  2994. {
  2995. CLog::Log(LOGDEBUG, "%s could not upgrade albums table", __FUNCTION__);
  2996. return false;
  2997. }
  2998. VECALBUMS albums;
  2999. while (!m_pDS->eof())
  3000. {
  3001. CAlbum album;
  3002. album.idAlbum = m_pDS->fv("idAlbum").get_asInt();
  3003. album.genre.push_back(m_pDS->fv("strGenre").get_asString());
  3004. if (!m_pDS->fv("strExtraGenres").get_asString().empty())
  3005. {
  3006. std::vector<std::string> extraGenres = StringUtils::Split(m_pDS->fv("strExtraGenres").get_asString(), g_advancedSettings.m_musicItemSeparator);
  3007. album.genre.insert(album.genre.end(), extraGenres.begin(), extraGenres.end());
  3008. }
  3009. albums.push_back(album);
  3010. m_pDS->next();
  3011. }
  3012. m_pDS->close();
  3013. m_pDS->exec("CREATE TABLE album_new ( idAlbum integer primary key, strAlbum varchar(256), strArtists text, strGenres text, iYear integer, idThumb integer)");
  3014. m_pDS->exec("INSERT INTO album_new ( idAlbum, strAlbum, strArtists, iYear, idThumb) SELECT idAlbum, strAlbum, strArtists, iYear, idThumb FROM album");
  3015. for (VECALBUMS::iterator it = albums.begin(); it != albums.end(); ++it)
  3016. {
  3017. CStdString strSQL;
  3018. strSQL = PrepareSQL("UPDATE album_new SET strGenres='%s' WHERE idAlbum=%i", StringUtils::Join(it->genre, g_advancedSettings.m_musicItemSeparator).c_str(), it->idAlbum);
  3019. m_pDS->exec(strSQL);
  3020. }
  3021. m_pDS->exec("DROP TABLE album");
  3022. m_pDS->exec("ALTER TABLE album_new RENAME TO album");
  3023. m_pDS->exec("CREATE INDEX idxAlbum ON album(strAlbum)");
  3024. m_pDS->exec("DROP TABLE IF EXISTS exgenrealbum");
  3025. }
  3026. if (version < 24)
  3027. {
  3028. m_pDS->exec("CREATE TABLE song_genre ( idGenre integer, idSong integer, iOrder integer )\n");
  3029. m_pDS->exec("CREATE UNIQUE INDEX idxSongGenre_1 ON song_genre ( idSong, idGenre )\n");
  3030. m_pDS->exec("CREATE UNIQUE INDEX idxSongGenre_2 ON song_genre ( idGenre, idSong )\n");
  3031. m_pDS->exec("INSERT INTO song_genre ( idGenre, idSong, iOrder) SELECT idGenre, idSong, iPosition FROM exgenresong");
  3032. m_pDS->exec("REPLACE INTO song_genre ( idGenre, idSong, iOrder) SELECT idGenre, idSong, 0 FROM song");
  3033. CStdString strSQL;
  3034. strSQL=PrepareSQL("SELECT song.idSong AS idSong, strExtraGenres,"
  3035. " song.idGenre AS idGenre, strGenre FROM song "
  3036. " JOIN genre ON song.idGenre=genre.idGenre");
  3037. if (!m_pDS->query(strSQL.c_str()))
  3038. {
  3039. CLog::Log(LOGDEBUG, "%s could not upgrade songs table", __FUNCTION__);
  3040. return false;
  3041. }
  3042. VECSONGS songs;
  3043. while (!m_pDS->eof())
  3044. {
  3045. CSong song;
  3046. song.idSong = m_pDS->fv("idSong").get_asInt();
  3047. song.genre.push_back(m_pDS->fv("strGenre").get_asString());
  3048. if (!m_pDS->fv("strExtraGenres").get_asString().empty())
  3049. {
  3050. std::vector<std::string> extraGenres = StringUtils::Split(m_pDS->fv("strExtraGenres").get_asString(), g_advancedSettings.m_musicItemSeparator);
  3051. song.genre.insert(song.genre.end(), extraGenres.begin(), extraGenres.end());
  3052. }
  3053. songs.push_back(song);
  3054. m_pDS->next();
  3055. }
  3056. m_pDS->close();
  3057. 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");
  3058. 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");
  3059. for (VECSONGS::iterator it = songs.begin(); it != songs.end(); ++it)
  3060. {
  3061. CStdString strSQL;
  3062. strSQL = PrepareSQL("UPDATE song_new SET strGenres='%s' WHERE idSong=%i", StringUtils::Join(it->genre, g_advancedSettings.m_musicItemSeparator).c_str(), it->idSong);
  3063. m_pDS->exec(strSQL);
  3064. }
  3065. m_pDS->exec("DROP TABLE song");
  3066. m_pDS->exec("ALTER TABLE song_new RENAME TO song");
  3067. m_pDS->exec("CREATE INDEX idxSong ON song(strTitle)");
  3068. m_pDS->exec("CREATE INDEX idxSong1 ON song(iTimesPlayed)");
  3069. m_pDS->exec("CREATE INDEX idxSong2 ON song(lastplayed)");
  3070. m_pDS->exec("CREATE INDEX idxSong3 ON song(idAlbum)");
  3071. m_pDS->exec("CREATE INDEX idxSong6 ON song(idPath)");
  3072. m_pDS->exec("DROP TABLE IF EXISTS exgenresong");
  3073. }
  3074. if (version < 25)
  3075. {
  3076. m_pDS->exec("ALTER TABLE album ADD bCompilation integer not null default '0'");
  3077. m_pDS->exec("CREATE INDEX idxAlbum_1 ON album(bCompilation)");
  3078. }
  3079. if (version < 26)
  3080. { // add art table
  3081. m_pDS->exec("CREATE TABLE art(art_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, type TEXT, url TEXT)");
  3082. m_pDS->exec("CREATE INDEX ix_art ON art(media_id, media_type(20), type(20))");
  3083. m_pDS->exec("CREATE TRIGGER delete_song AFTER DELETE ON song FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idSong AND media_type='song'; END");
  3084. m_pDS->exec("CREATE TRIGGER delete_album AFTER DELETE ON album FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idAlbum AND media_type='album'; END");
  3085. m_pDS->exec("CREATE TRIGGER delete_artist AFTER DELETE ON artist FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idArtist AND media_type='artist'; END");
  3086. }
  3087. if (version < 27)
  3088. {
  3089. m_pDS->exec("DROP TABLE thumb");
  3090. g_settings.m_musicNeedsUpdate = 27;
  3091. g_settings.Save();
  3092. }
  3093. // always recreate the views after any table change
  3094. CreateViews();
  3095. return true;
  3096. }
  3097. unsigned int CMusicDatabase::GetSongIDs(const CStdString& strWhere, vector<pair<int,int> > &songIDs)
  3098. {
  3099. try
  3100. {
  3101. if (NULL == m_pDB.get()) return 0;
  3102. if (NULL == m_pDS.get()) return 0;
  3103. CStdString strSQL = "select idSong from songview " + strWhere;
  3104. if (!m_pDS->query(strSQL.c_str())) return 0;
  3105. songIDs.clear();
  3106. if (m_pDS->num_rows() == 0)
  3107. {
  3108. m_pDS->close();
  3109. return 0;
  3110. }
  3111. songIDs.reserve(m_pDS->num_rows());
  3112. while (!m_pDS->eof())
  3113. {
  3114. songIDs.push_back(make_pair<int,int>(1,m_pDS->fv(song_idSong).get_asInt()));
  3115. m_pDS->next();
  3116. } // cleanup
  3117. m_pDS->close();
  3118. return songIDs.size();
  3119. }
  3120. catch (...)
  3121. {
  3122. CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strWhere.c_str());
  3123. }
  3124. return 0;
  3125. }
  3126. int CMusicDatabase::GetSongsCount(const CStdString& strWhere)
  3127. {
  3128. try
  3129. {
  3130. if (NULL == m_pDB.get()) return 0;
  3131. if (NULL == m_pDS.get()) return 0;
  3132. CStdString strSQL = "select count(idSong) as NumSongs from songview " + strWhere;
  3133. if (!m_pDS->query(strSQL.c_str())) return false;
  3134. if (m_pDS->num_rows() == 0)
  3135. {
  3136. m_pDS->close();
  3137. return 0;
  3138. }
  3139. int iNumSongs = m_pDS->fv("NumSongs").get_asInt();
  3140. // cleanup
  3141. m_pDS->close();
  3142. return iNumSongs;
  3143. }
  3144. catch (...)
  3145. {
  3146. CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strWhere.c_str());
  3147. }
  3148. return 0;
  3149. }
  3150. bool CMusicDatabase::GetAlbumPath(int idAlbum, CStdString& path)
  3151. {
  3152. try
  3153. {
  3154. if (NULL == m_pDB.get()) return false;
  3155. if (NULL == m_pDS2.get()) return false;
  3156. path.Empty();
  3157. CStdString strSQL=PrepareSQL("select strPath from song join path on song.idPath = path.idPath where song.idAlbum=%ld", idAlbum);
  3158. if (!m_pDS2->query(strSQL.c_str())) return false;
  3159. int iRowsFound = m_pDS2->num_rows();
  3160. if (iRowsFound == 0)
  3161. {
  3162. m_pDS2->close();
  3163. return false;
  3164. }
  3165. // if this returns more than one path, we just grab the first one. It's just for determining where to obtain + place
  3166. // a local thumbnail
  3167. path = m_pDS2->fv("strPath").get_asString();
  3168. m_pDS2->close(); // cleanup recordset data
  3169. return true;
  3170. }
  3171. catch (...)
  3172. {
  3173. CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, idAlbum);
  3174. }
  3175. return false;
  3176. }
  3177. bool CMusicDatabase::SaveAlbumThumb(int idAlbum, const CStdString& strThumb)
  3178. {
  3179. SetArtForItem(idAlbum, "album", "thumb", strThumb);
  3180. // TODO: We should prompt the user to update the art for songs
  3181. CStdString sql = PrepareSQL("UPDATE art"
  3182. " SET art_url='-'"
  3183. " WHERE media_type='song'"
  3184. " AND art_type='thumb'"
  3185. " AND media_id IN"
  3186. " (SELECT idSong FROM song WHERE idAlbum=%ld)", idAlbum);
  3187. ExecuteQuery(sql);
  3188. return true;
  3189. }
  3190. bool CMusicDatabase::GetArtistPath(int idArtist, CStdString &basePath)
  3191. {
  3192. try
  3193. {
  3194. if (NULL == m_pDB.get()) return false;
  3195. if (NULL == m_pDS2.get()) return false;
  3196. // find all albums from this artist, and all the paths to the songs from those albums
  3197. CStdString strSQL=PrepareSQL("SELECT strPath"
  3198. " FROM album_artist"
  3199. " JOIN song "
  3200. " ON album_artist.idAlbum = song.idAlbum"
  3201. " JOIN path"
  3202. " ON song.idPath = path.idPath"
  3203. " WHERE album_artist.idArtist = %i"
  3204. " GROUP BY song.idPath", idArtist);
  3205. // run query
  3206. if (!m_pDS2->query(strSQL.c_str())) return false;
  3207. int iRowsFound = m_pDS2->num_rows();
  3208. if (iRowsFound == 0)
  3209. {
  3210. m_pDS2->close();
  3211. return false;
  3212. }
  3213. // special case for single path - assume that we're in an artist/album/songs filesystem
  3214. if (iRowsFound == 1)
  3215. {
  3216. URIUtils::GetParentPath(m_pDS2->fv("strPath").get_asString(), basePath);
  3217. m_pDS2->close();
  3218. return true;
  3219. }
  3220. // find the common path (if any) to these albums
  3221. basePath.Empty();
  3222. while (!m_pDS2->eof())
  3223. {
  3224. CStdString path = m_pDS2->fv("strPath").get_asString();
  3225. if (basePath.IsEmpty())
  3226. basePath = path;
  3227. else
  3228. URIUtils::GetCommonPath(basePath,path);
  3229. m_pDS2->next();
  3230. }
  3231. // cleanup
  3232. m_pDS2->close();
  3233. return true;
  3234. }
  3235. catch (...)
  3236. {
  3237. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  3238. }
  3239. return false;
  3240. }
  3241. int CMusicDatabase::GetArtistByName(const CStdString& strArtist)
  3242. {
  3243. try
  3244. {
  3245. if (NULL == m_pDB.get()) return false;
  3246. if (NULL == m_pDS.get()) return false;
  3247. CStdString strSQL=PrepareSQL("select idArtist from artist where artist.strArtist like '%s'", strArtist.c_str());
  3248. // run query
  3249. if (!m_pDS->query(strSQL.c_str())) return false;
  3250. int iRowsFound = m_pDS->num_rows();
  3251. if (iRowsFound != 1)
  3252. {
  3253. m_pDS->close();
  3254. return -1;
  3255. }
  3256. int lResult = m_pDS->fv("artist.idArtist").get_asInt();
  3257. m_pDS->close();
  3258. return lResult;
  3259. }
  3260. catch (...)
  3261. {
  3262. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  3263. }
  3264. return -1;
  3265. }
  3266. int CMusicDatabase::GetAlbumByName(const CStdString& strAlbum, const CStdString& strArtist)
  3267. {
  3268. try
  3269. {
  3270. if (NULL == m_pDB.get()) return false;
  3271. if (NULL == m_pDS.get()) return false;
  3272. CStdString strSQL;
  3273. if (strArtist.IsEmpty())
  3274. strSQL=PrepareSQL("SELECT idAlbum FROM album WHERE album.strAlbum LIKE '%s'", strAlbum.c_str());
  3275. else
  3276. strSQL=PrepareSQL("SELECT album.idAlbum FROM album WHERE album.strAlbum LIKE '%s' AND album.strArtists LIKE '%s'", strAlbum.c_str(),strArtist.c_str());
  3277. // run query
  3278. if (!m_pDS->query(strSQL.c_str())) return false;
  3279. int iRowsFound = m_pDS->num_rows();
  3280. if (iRowsFound != 1)
  3281. {
  3282. m_pDS->close();
  3283. return -1;
  3284. }
  3285. return m_pDS->fv("album.idAlbum").get_asInt();
  3286. }
  3287. catch (...)
  3288. {
  3289. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  3290. }
  3291. return -1;
  3292. }
  3293. int CMusicDatabase::GetAlbumByName(const CStdString& strAlbum, const std::vector<std::string>& artist)
  3294. {
  3295. return GetAlbumByName(strAlbum, StringUtils::Join(artist, g_advancedSettings.m_musicItemSeparator));
  3296. }
  3297. CStdString CMusicDatabase::GetGenreById(int id)
  3298. {
  3299. return GetSingleValue("genre", "strGenre", PrepareSQL("idGenre=%i", id));
  3300. }
  3301. CStdString CMusicDatabase::GetArtistById(int id)
  3302. {
  3303. return GetSingleValue("artist", "strArtist", PrepareSQL("idArtist=%i", id));
  3304. }
  3305. CStdString CMusicDatabase::GetAlbumById(int id)
  3306. {
  3307. return GetSingleValue("album", "strAlbum", PrepareSQL("idAlbum=%i", id));
  3308. }
  3309. int CMusicDatabase::GetGenreByName(const CStdString& strGenre)
  3310. {
  3311. try
  3312. {
  3313. if (NULL == m_pDB.get()) return false;
  3314. if (NULL == m_pDS.get()) return false;
  3315. CStdString strSQL;
  3316. strSQL=PrepareSQL("select idGenre from genre where genre.strGenre like '%s'", strGenre.c_str());
  3317. // run query
  3318. if (!m_pDS->query(strSQL.c_str())) return false;
  3319. int iRowsFound = m_pDS->num_rows();
  3320. if (iRowsFound != 1)
  3321. {
  3322. m_pDS->close();
  3323. return -1;
  3324. }
  3325. return m_pDS->fv("genre.idGenre").get_asInt();
  3326. }
  3327. catch (...)
  3328. {
  3329. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  3330. }
  3331. return -1;
  3332. }
  3333. bool CMusicDatabase::GetRandomSong(CFileItem* item, int& idSong, const CStdString& strWhere)
  3334. {
  3335. try
  3336. {
  3337. idSong = -1;
  3338. int iCount = GetSongsCount(strWhere);
  3339. if (iCount <= 0)
  3340. return false;
  3341. int iRandom = rand() % iCount;
  3342. if (NULL == m_pDB.get()) return false;
  3343. if (NULL == m_pDS.get()) return false;
  3344. // We don't use PrepareSQL here, as the WHERE clause is already formatted
  3345. CStdString strSQL;
  3346. strSQL.Format("select * from songview %s order by idSong limit 1 offset %i", strWhere.c_str(), iRandom);
  3347. CLog::Log(LOGDEBUG, "%s query = %s", __FUNCTION__, strSQL.c_str());
  3348. // run query
  3349. if (!m_pDS->query(strSQL.c_str()))
  3350. return false;
  3351. int iRowsFound = m_pDS->num_rows();
  3352. if (iRowsFound != 1)
  3353. {
  3354. m_pDS->close();
  3355. return false;
  3356. }
  3357. GetFileItemFromDataset(item, "");
  3358. idSong = m_pDS->fv("songview.idSong").get_asInt();
  3359. m_pDS->close();
  3360. return true;
  3361. }
  3362. catch(...)
  3363. {
  3364. CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, strWhere.c_str());
  3365. }
  3366. return false;
  3367. }
  3368. bool CMusicDatabase::GetCompilationAlbums(const CStdString& strBaseDir, CFileItemList& items)
  3369. {
  3370. return GetAlbumsByWhere(strBaseDir, "WHERE bCompilation = 1", "", items);
  3371. }
  3372. bool CMusicDatabase::GetCompilationSongs(const CStdString& strBaseDir, CFileItemList& items)
  3373. {
  3374. return GetSongsByWhere(strBaseDir, "WHERE bCompilation = 1", items);
  3375. }
  3376. int CMusicDatabase::GetCompilationAlbumsCount()
  3377. {
  3378. return strtol(GetSingleValue("album", "count(idAlbum)", "bCompilation = 1"), NULL, 10);
  3379. }
  3380. void CMusicDatabase::SplitString(const CStdString &multiString, vector<string> &vecStrings, CStdString &extraStrings)
  3381. {
  3382. vecStrings = StringUtils::Split(multiString, g_advancedSettings.m_musicItemSeparator);
  3383. for (unsigned int i = 1; i < vecStrings.size(); i++)
  3384. extraStrings += g_advancedSettings.m_musicItemSeparator + CStdString(vecStrings[i]);
  3385. }
  3386. bool CMusicDatabase::SetPathHash(const CStdString &path, const CStdString &hash)
  3387. {
  3388. try
  3389. {
  3390. if (NULL == m_pDB.get()) return false;
  3391. if (NULL == m_pDS.get()) return false;
  3392. if (hash.IsEmpty())
  3393. { // this is an empty folder - we need only add it to the path table
  3394. // if the path actually exists
  3395. if (!CDirectory::Exists(path))
  3396. return false;
  3397. }
  3398. int idPath = AddPath(path);
  3399. if (idPath < 0) return false;
  3400. CStdString strSQL=PrepareSQL("update path set strHash='%s' where idPath=%ld", hash.c_str(), idPath);
  3401. m_pDS->exec(strSQL.c_str());
  3402. return true;
  3403. }
  3404. catch (...)
  3405. {
  3406. CLog::Log(LOGERROR, "%s (%s, %s) failed", __FUNCTION__, path.c_str(), hash.c_str());
  3407. }
  3408. return false;
  3409. }
  3410. bool CMusicDatabase::GetPathHash(const CStdString &path, CStdString &hash)
  3411. {
  3412. try
  3413. {
  3414. if (NULL == m_pDB.get()) return false;
  3415. if (NULL == m_pDS.get()) return false;
  3416. CStdString strSQL=PrepareSQL("select strHash from path where strPath='%s'", path.c_str());
  3417. m_pDS->query(strSQL.c_str());
  3418. if (m_pDS->num_rows() == 0)
  3419. return false;
  3420. hash = m_pDS->fv("strHash").get_asString();
  3421. return true;
  3422. }
  3423. catch (...)
  3424. {
  3425. CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, path.c_str());
  3426. }
  3427. return false;
  3428. }
  3429. bool CMusicDatabase::RemoveSongsFromPath(const CStdString &path1, CSongMap &songs, bool exact)
  3430. {
  3431. // We need to remove all songs from this path, as their tags are going
  3432. // to be re-read. We need to remove all songs from the song table + all links to them
  3433. // from the song link tables (as otherwise if a song is added back
  3434. // to the table with the same idSong, these tables can't be cleaned up properly later)
  3435. // TODO: SQLite probably doesn't allow this, but can we rely on that??
  3436. // We don't need to remove orphaned albums at this point as in AddAlbum() we check
  3437. // first whether the album has already been read during this scan, and if it hasn't
  3438. // we check whether it's in the table and update accordingly at that point, removing the entries from
  3439. // the album link tables. The only failure point for this is albums
  3440. // that span multiple folders, where just the files in one folder have been changed. In this case
  3441. // any linked fields that are only in the files that haven't changed will be removed. Clearly
  3442. // the primary albumartist still matches (as that's what we looked up based on) so is this really
  3443. // an issue? I don't think it is, as those artists will still have links to the album via the songs
  3444. // which is generally what we rely on, so the only failure point is albumartist lookup. In this
  3445. // case, it will return only things in the album_artist table from the newly updated songs (and
  3446. // only if they have additional artists). I think the effect of this is minimal at best, as ALL
  3447. // songs in the album should have the same albumartist!
  3448. // we also remove the path at this point as it will be added later on if the
  3449. // path still exists.
  3450. // After scanning we then remove the orphaned artists, genres and thumbs.
  3451. // Note: when used to remove all songs from a path and its subpath (exact=false), this
  3452. // does miss archived songs.
  3453. CStdString path(path1);
  3454. try
  3455. {
  3456. if (!URIUtils::HasSlashAtEnd(path))
  3457. URIUtils::AddSlashAtEnd(path);
  3458. if (NULL == m_pDB.get()) return false;
  3459. if (NULL == m_pDS.get()) return false;
  3460. CStdString where;
  3461. if (exact)
  3462. where = PrepareSQL(" where strPath='%s'", path.c_str());
  3463. else
  3464. where = PrepareSQL(" where SUBSTR(strPath,1,%i)='%s'", StringUtils::utf8_strlen(path.c_str()), path.c_str());
  3465. CStdString sql = "select * from songview" + where;
  3466. if (!m_pDS->query(sql.c_str())) return false;
  3467. int iRowsFound = m_pDS->num_rows();
  3468. if (iRowsFound > 0)
  3469. {
  3470. std::vector<int> ids;
  3471. CStdString songIds = "(";
  3472. while (!m_pDS->eof())
  3473. {
  3474. CSong song = GetSongFromDataset();
  3475. song.strThumb = GetArtForItem(song.idSong, "song", "thumb");
  3476. songs.Add(song.strFileName, song);
  3477. songIds += PrepareSQL("%i,", song.idSong);
  3478. ids.push_back(song.idSong);
  3479. m_pDS->next();
  3480. }
  3481. songIds.TrimRight(",");
  3482. songIds += ")";
  3483. m_pDS->close();
  3484. // and delete all songs, and anything linked to them
  3485. sql = "delete from song where idSong in " + songIds;
  3486. m_pDS->exec(sql.c_str());
  3487. sql = "delete from song_artist where idSong in " + songIds;
  3488. m_pDS->exec(sql.c_str());
  3489. sql = "delete from song_genre where idSong in " + songIds;
  3490. m_pDS->exec(sql.c_str());
  3491. sql = "delete from karaokedata where idSong in " + songIds;
  3492. m_pDS->exec(sql.c_str());
  3493. for (unsigned int i = 0; i < ids.size(); i++)
  3494. AnnounceRemove("song", ids[i]);
  3495. }
  3496. // and remove the path as well (it'll be re-added later on with the new hash if it's non-empty)
  3497. sql = "delete from path" + where;
  3498. m_pDS->exec(sql.c_str());
  3499. return iRowsFound > 0;
  3500. }
  3501. catch (...)
  3502. {
  3503. CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, path.c_str());
  3504. }
  3505. return false;
  3506. }
  3507. bool CMusicDatabase::GetPaths(set<CStdString> &paths)
  3508. {
  3509. try
  3510. {
  3511. if (NULL == m_pDB.get()) return false;
  3512. if (NULL == m_pDS.get()) return false;
  3513. paths.clear();
  3514. // find all paths
  3515. if (!m_pDS->query("select strPath from path")) return false;
  3516. int iRowsFound = m_pDS->num_rows();
  3517. if (iRowsFound == 0)
  3518. {
  3519. m_pDS->close();
  3520. return true;
  3521. }
  3522. while (!m_pDS->eof())
  3523. {
  3524. paths.insert(m_pDS->fv("strPath").get_asString());
  3525. m_pDS->next();
  3526. }
  3527. m_pDS->close();
  3528. return true;
  3529. }
  3530. catch (...)
  3531. {
  3532. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  3533. }
  3534. return false;
  3535. }
  3536. bool CMusicDatabase::SetSongRating(const CStdString &filePath, char rating)
  3537. {
  3538. try
  3539. {
  3540. if (filePath.IsEmpty()) return false;
  3541. if (NULL == m_pDB.get()) return false;
  3542. if (NULL == m_pDS.get()) return false;
  3543. int songID = GetSongIDFromPath(filePath);
  3544. if (-1 == songID) return false;
  3545. CStdString sql = PrepareSQL("update song set rating='%c' where idSong = %i", rating, songID);
  3546. m_pDS->exec(sql.c_str());
  3547. return true;
  3548. }
  3549. catch (...)
  3550. {
  3551. CLog::Log(LOGERROR, "%s (%s,%c) failed", __FUNCTION__, filePath.c_str(), rating);
  3552. }
  3553. return false;
  3554. }
  3555. int CMusicDatabase::GetSongIDFromPath(const CStdString &filePath)
  3556. {
  3557. // grab the where string to identify the song id
  3558. CURL url(filePath);
  3559. if (url.GetProtocol()=="musicdb")
  3560. {
  3561. CStdString strFile=URIUtils::GetFileName(filePath);
  3562. URIUtils::RemoveExtension(strFile);
  3563. return atol(strFile.c_str());
  3564. }
  3565. // hit the db
  3566. try
  3567. {
  3568. if (NULL == m_pDB.get()) return -1;
  3569. if (NULL == m_pDS.get()) return -1;
  3570. CStdString strPath;
  3571. URIUtils::GetDirectory(filePath, strPath);
  3572. URIUtils::AddSlashAtEnd(strPath);
  3573. DWORD crc = ComputeCRC(filePath);
  3574. 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());
  3575. if (!m_pDS->query(sql.c_str())) return -1;
  3576. if (m_pDS->num_rows() == 0)
  3577. {
  3578. m_pDS->close();
  3579. return -1;
  3580. }
  3581. int songID = m_pDS->fv("idSong").get_asInt();
  3582. m_pDS->close();
  3583. return songID;
  3584. }
  3585. catch (...)
  3586. {
  3587. CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, filePath.c_str());
  3588. }
  3589. return -1;
  3590. }
  3591. bool CMusicDatabase::CommitTransaction()
  3592. {
  3593. if (CDatabase::CommitTransaction())
  3594. { // number of items in the db has likely changed, so reset the infomanager cache
  3595. g_infoManager.SetLibraryBool(LIBRARY_HAS_MUSIC, GetSongsCount("") > 0);
  3596. return true;
  3597. }
  3598. return false;
  3599. }
  3600. bool CMusicDatabase::SetScraperForPath(const CStdString& strPath, const ADDON::ScraperPtr& scraper)
  3601. {
  3602. try
  3603. {
  3604. if (NULL == m_pDB.get()) return false;
  3605. if (NULL == m_pDS.get()) return false;
  3606. // wipe old settings
  3607. CStdString strSQL = PrepareSQL("delete from content where strPath='%s'",strPath.c_str());
  3608. m_pDS->exec(strSQL.c_str());
  3609. // insert new settings
  3610. strSQL = PrepareSQL("insert into content (strPath, strScraperPath, strContent, strSettings) values ('%s','%s','%s','%s')",
  3611. strPath.c_str(), scraper->ID().c_str(), ADDON::TranslateContent(scraper->Content()).c_str(), scraper->GetPathSettings().c_str());
  3612. m_pDS->exec(strSQL.c_str());
  3613. return true;
  3614. }
  3615. catch (...)
  3616. {
  3617. CLog::Log(LOGERROR, "%s - (%s) failed", __FUNCTION__, strPath.c_str());
  3618. }
  3619. return false;
  3620. }
  3621. bool CMusicDatabase::GetScraperForPath(const CStdString& strPath, ADDON::ScraperPtr& info, const ADDON::TYPE &type)
  3622. {
  3623. try
  3624. {
  3625. if (NULL == m_pDB.get()) return false;
  3626. if (NULL == m_pDS.get()) return false;
  3627. CStdString strSQL = PrepareSQL("select * from content where strPath='%s'",strPath.c_str());
  3628. m_pDS->query(strSQL.c_str());
  3629. if (m_pDS->eof()) // no info set for path - fallback logic commencing
  3630. {
  3631. CQueryParams params;
  3632. CDirectoryNode::GetDatabaseInfo(strPath, params);
  3633. if (params.GetGenreId() != -1) // check genre
  3634. {
  3635. strSQL = PrepareSQL("select * from content where strPath='musicdb://1/%i/'",params.GetGenreId());
  3636. m_pDS->query(strSQL.c_str());
  3637. }
  3638. if (m_pDS->eof() && params.GetAlbumId() != -1) // check album
  3639. {
  3640. strSQL = PrepareSQL("select * from content where strPath='musicdb://3/%i/'",params.GetGenreId());
  3641. m_pDS->query(strSQL.c_str());
  3642. }
  3643. if (m_pDS->eof() && params.GetArtistId() != -1) // check artist
  3644. {
  3645. strSQL = PrepareSQL("select * from content where strPath='musicdb://2/%i/'",params.GetArtistId());
  3646. m_pDS->query(strSQL.c_str());
  3647. }
  3648. if (m_pDS->eof()) // general albums setting
  3649. {
  3650. strSQL = PrepareSQL("select * from content where strPath='musicdb://3/'");
  3651. m_pDS->query(strSQL.c_str());
  3652. }
  3653. if (m_pDS->eof()) // general artist setting
  3654. {
  3655. strSQL = PrepareSQL("select * from content where strPath='musicdb://2/'");
  3656. m_pDS->query(strSQL.c_str());
  3657. }
  3658. }
  3659. if (!m_pDS->eof())
  3660. { // try and ascertain scraper for this path
  3661. CONTENT_TYPE content = ADDON::TranslateContent(m_pDS->fv("content.strContent").get_asString());
  3662. CStdString scraperUUID = m_pDS->fv("content.strScraperPath").get_asString();
  3663. if (content != CONTENT_NONE)
  3664. { // content set, use pre configured or default scraper
  3665. ADDON::AddonPtr addon;
  3666. if (!scraperUUID.empty() && ADDON::CAddonMgr::Get().GetAddon(scraperUUID, addon) && addon)
  3667. {
  3668. info = boost::dynamic_pointer_cast<ADDON::CScraper>(addon->Clone(addon));
  3669. if (!info)
  3670. return false;
  3671. // store this path's settings
  3672. info->SetPathSettings(content, m_pDS->fv("content.strSettings").get_asString());
  3673. }
  3674. }
  3675. else
  3676. { // use default scraper of the requested type
  3677. ADDON::AddonPtr defaultScraper;
  3678. if (ADDON::CAddonMgr::Get().GetDefault(type, defaultScraper))
  3679. {
  3680. info = boost::dynamic_pointer_cast<ADDON::CScraper>(defaultScraper->Clone(defaultScraper));
  3681. }
  3682. }
  3683. }
  3684. m_pDS->close();
  3685. if (!info)
  3686. { // use default music scraper instead
  3687. ADDON::AddonPtr addon;
  3688. if(ADDON::CAddonMgr::Get().GetDefault(type, addon))
  3689. {
  3690. info = boost::dynamic_pointer_cast<ADDON::CScraper>(addon);
  3691. return (info);
  3692. }
  3693. else
  3694. return false;
  3695. }
  3696. return true;
  3697. }
  3698. catch (...)
  3699. {
  3700. CLog::Log(LOGERROR, "%s -(%s) failed", __FUNCTION__, strPath.c_str());
  3701. }
  3702. return false;
  3703. }
  3704. bool CMusicDatabase::ScraperInUse(const CStdString &scraperID) const
  3705. {
  3706. try
  3707. {
  3708. if (NULL == m_pDB.get()) return false;
  3709. if (NULL == m_pDS.get()) return false;
  3710. CStdString sql = PrepareSQL("select count(1) from content where strScraperPath='%s'",scraperID.c_str());
  3711. if (!m_pDS->query(sql.c_str()) || m_pDS->num_rows() == 0)
  3712. return false;
  3713. bool found = m_pDS->fv(0).get_asInt() > 0;
  3714. m_pDS->close();
  3715. return found;
  3716. }
  3717. catch (...)
  3718. {
  3719. CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, scraperID.c_str());
  3720. }
  3721. return false;
  3722. }
  3723. void CMusicDatabase::ExportToXML(const CStdString &xmlFile, bool singleFiles, bool images, bool overwrite)
  3724. {
  3725. try
  3726. {
  3727. if (NULL == m_pDB.get()) return;
  3728. if (NULL == m_pDS.get()) return;
  3729. if (NULL == m_pDS2.get()) return;
  3730. // find all albums
  3731. CStdString sql = "select albumview.*,albuminfo.strImage,albuminfo.idAlbumInfo from albuminfo "
  3732. "join albumview on albuminfo.idAlbum=albumview.idAlbum ";
  3733. m_pDS->query(sql.c_str());
  3734. CGUIDialogProgress *progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
  3735. if (progress)
  3736. {
  3737. progress->SetHeading(20196);
  3738. progress->SetLine(0, 650);
  3739. progress->SetLine(1, "");
  3740. progress->SetLine(2, "");
  3741. progress->SetPercentage(0);
  3742. progress->StartModal();
  3743. progress->ShowProgressBar(true);
  3744. }
  3745. int total = m_pDS->num_rows();
  3746. int current = 0;
  3747. // create our xml document
  3748. CXBMCTinyXML xmlDoc;
  3749. TiXmlDeclaration decl("1.0", "UTF-8", "yes");
  3750. xmlDoc.InsertEndChild(decl);
  3751. TiXmlNode *pMain = NULL;
  3752. if (singleFiles)
  3753. pMain = &xmlDoc;
  3754. else
  3755. {
  3756. TiXmlElement xmlMainElement("musicdb");
  3757. pMain = xmlDoc.InsertEndChild(xmlMainElement);
  3758. }
  3759. while (!m_pDS->eof())
  3760. {
  3761. CAlbum album = GetAlbumFromDataset(m_pDS.get());
  3762. album.thumbURL.Clear();
  3763. album.thumbURL.ParseString(m_pDS->fv("albuminfo.strImage").get_asString());
  3764. int idAlbumInfo = m_pDS->fv("albuminfo.idAlbumInfo").get_asInt();
  3765. GetAlbumInfoSongs(idAlbumInfo,album.songs);
  3766. CStdString strPath;
  3767. GetAlbumPath(album.idAlbum,strPath);
  3768. album.Save(pMain, "album", strPath);
  3769. if (singleFiles)
  3770. {
  3771. if (!CDirectory::Exists(strPath))
  3772. CLog::Log(LOGDEBUG, "%s - Not exporting item %s as it does not exist", __FUNCTION__, strPath.c_str());
  3773. else
  3774. {
  3775. CStdString nfoFile;
  3776. URIUtils::AddFileToFolder(strPath, "album.nfo", nfoFile);
  3777. if (overwrite || !CFile::Exists(nfoFile))
  3778. {
  3779. if (!xmlDoc.SaveFile(nfoFile))
  3780. CLog::Log(LOGERROR, "%s: Album nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str());
  3781. }
  3782. if (images)
  3783. {
  3784. string thumb = GetArtForItem(album.idAlbum, "album", "thumb");
  3785. if (!thumb.empty() && (overwrite || !CFile::Exists(URIUtils::AddFileToFolder(strPath,"folder.jpg"))))
  3786. CTextureCache::Get().Export(thumb, URIUtils::AddFileToFolder(strPath,"folder.jpg"));
  3787. }
  3788. xmlDoc.Clear();
  3789. TiXmlDeclaration decl("1.0", "UTF-8", "yes");
  3790. xmlDoc.InsertEndChild(decl);
  3791. }
  3792. }
  3793. if ((current % 50) == 0 && progress)
  3794. {
  3795. progress->SetLine(1, album.strAlbum);
  3796. progress->SetPercentage(current * 100 / total);
  3797. progress->Progress();
  3798. if (progress->IsCanceled())
  3799. {
  3800. progress->Close();
  3801. m_pDS->close();
  3802. return;
  3803. }
  3804. }
  3805. m_pDS->next();
  3806. current++;
  3807. }
  3808. m_pDS->close();
  3809. // find all artists
  3810. sql = "SELECT artist.idArtist AS idArtist, strArtist, "
  3811. " strBorn, strFormed, strGenres,"
  3812. " strMoods, strStyles, strInstruments, "
  3813. " strBiography, strDied, strDisbanded, "
  3814. " strYearsActive, strImage, strFanart "
  3815. " FROM artist "
  3816. " JOIN artistinfo "
  3817. " ON artist.idArtist=artistinfo.idArtist";
  3818. // needed due to getartistpath
  3819. auto_ptr<dbiplus::Dataset> pDS;
  3820. pDS.reset(m_pDB->CreateDataset());
  3821. pDS->query(sql.c_str());
  3822. total = pDS->num_rows();
  3823. current = 0;
  3824. while (!pDS->eof())
  3825. {
  3826. CArtist artist = GetArtistFromDataset(pDS.get());
  3827. CStdString strSQL=PrepareSQL("select * from discography where idArtist=%i",artist.idArtist);
  3828. m_pDS->query(strSQL.c_str());
  3829. while (!m_pDS->eof())
  3830. {
  3831. artist.discography.push_back(make_pair(m_pDS->fv("strAlbum").get_asString(),m_pDS->fv("strYear").get_asString()));
  3832. m_pDS->next();
  3833. }
  3834. m_pDS->close();
  3835. CStdString strPath;
  3836. GetArtistPath(artist.idArtist,strPath);
  3837. artist.Save(pMain, "artist", strPath);
  3838. map<string, string> artwork;
  3839. if (GetArtForItem(artist.idArtist, "artist", artwork) && !singleFiles)
  3840. { // append to the XML
  3841. TiXmlElement additionalNode("art");
  3842. for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
  3843. XMLUtils::SetString(&additionalNode, i->first.c_str(), i->second);
  3844. pMain->LastChild()->InsertEndChild(additionalNode);
  3845. }
  3846. if (singleFiles)
  3847. {
  3848. if (!CDirectory::Exists(strPath))
  3849. CLog::Log(LOGDEBUG, "%s - Not exporting item %s as it does not exist", __FUNCTION__, strPath.c_str());
  3850. else
  3851. {
  3852. CStdString nfoFile;
  3853. URIUtils::AddFileToFolder(strPath, "artist.nfo", nfoFile);
  3854. if (overwrite || !CFile::Exists(nfoFile))
  3855. {
  3856. if (!xmlDoc.SaveFile(nfoFile))
  3857. CLog::Log(LOGERROR, "%s: Artist nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str());
  3858. }
  3859. if (images && !artwork.empty())
  3860. {
  3861. CStdString savedThumb = URIUtils::AddFileToFolder(strPath,"folder.jpg");
  3862. CStdString savedFanart = URIUtils::AddFileToFolder(strPath,"fanart.jpg");
  3863. if (artwork.find("thumb") != artwork.end() && (overwrite || !CFile::Exists(savedThumb)))
  3864. CTextureCache::Get().Export(artwork["thumb"], savedThumb);
  3865. if (artwork.find("fanart") != artwork.end() && (overwrite || !CFile::Exists(savedFanart)))
  3866. CTextureCache::Get().Export(artwork["fanart"], savedFanart);
  3867. }
  3868. xmlDoc.Clear();
  3869. TiXmlDeclaration decl("1.0", "UTF-8", "yes");
  3870. xmlDoc.InsertEndChild(decl);
  3871. }
  3872. }
  3873. if ((current % 50) == 0 && progress)
  3874. {
  3875. progress->SetLine(1, artist.strArtist);
  3876. progress->SetPercentage(current * 100 / total);
  3877. progress->Progress();
  3878. if (progress->IsCanceled())
  3879. {
  3880. progress->Close();
  3881. m_pDS->close();
  3882. return;
  3883. }
  3884. }
  3885. pDS->next();
  3886. current++;
  3887. }
  3888. pDS->close();
  3889. if (progress)
  3890. progress->Close();
  3891. xmlDoc.SaveFile(xmlFile);
  3892. }
  3893. catch (...)
  3894. {
  3895. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  3896. }
  3897. }
  3898. void CMusicDatabase::ImportFromXML(const CStdString &xmlFile)
  3899. {
  3900. CGUIDialogProgress *progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
  3901. try
  3902. {
  3903. if (NULL == m_pDB.get()) return;
  3904. if (NULL == m_pDS.get()) return;
  3905. CXBMCTinyXML xmlDoc;
  3906. if (!xmlDoc.LoadFile(xmlFile))
  3907. return;
  3908. TiXmlElement *root = xmlDoc.RootElement();
  3909. if (!root) return;
  3910. if (progress)
  3911. {
  3912. progress->SetHeading(20197);
  3913. progress->SetLine(0, 649);
  3914. progress->SetLine(1, 330);
  3915. progress->SetLine(2, "");
  3916. progress->SetPercentage(0);
  3917. progress->StartModal();
  3918. progress->ShowProgressBar(true);
  3919. }
  3920. TiXmlElement *entry = root->FirstChildElement();
  3921. int current = 0;
  3922. int total = 0;
  3923. // first count the number of items...
  3924. while (entry)
  3925. {
  3926. if (strnicmp(entry->Value(), "artist", 6)==0 ||
  3927. strnicmp(entry->Value(), "album", 5)==0)
  3928. total++;
  3929. entry = entry->NextSiblingElement();
  3930. }
  3931. BeginTransaction();
  3932. entry = root->FirstChildElement();
  3933. while (entry)
  3934. {
  3935. CStdString strTitle;
  3936. if (strnicmp(entry->Value(), "artist", 6) == 0)
  3937. {
  3938. CArtist artist;
  3939. artist.Load(entry);
  3940. strTitle = artist.strArtist;
  3941. int idArtist = GetArtistByName(artist.strArtist);
  3942. if (idArtist > -1)
  3943. SetArtistInfo(idArtist,artist);
  3944. current++;
  3945. }
  3946. else if (strnicmp(entry->Value(), "album", 5) == 0)
  3947. {
  3948. CAlbum album;
  3949. album.Load(entry);
  3950. strTitle = album.strAlbum;
  3951. int idAlbum = GetAlbumByName(album.strAlbum,album.artist);
  3952. if (idAlbum > -1)
  3953. SetAlbumInfo(idAlbum,album,album.songs,false);
  3954. current++;
  3955. }
  3956. entry = entry ->NextSiblingElement();
  3957. if (progress && total)
  3958. {
  3959. progress->SetPercentage(current * 100 / total);
  3960. progress->SetLine(2, strTitle);
  3961. progress->Progress();
  3962. if (progress->IsCanceled())
  3963. {
  3964. progress->Close();
  3965. RollbackTransaction();
  3966. return;
  3967. }
  3968. }
  3969. }
  3970. CommitTransaction();
  3971. g_infoManager.ResetLibraryBools();
  3972. }
  3973. catch (...)
  3974. {
  3975. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  3976. }
  3977. if (progress)
  3978. progress->Close();
  3979. }
  3980. void CMusicDatabase::AddKaraokeData(int idSong, const CSong& song)
  3981. {
  3982. try
  3983. {
  3984. CStdString strSQL;
  3985. // If song.iKaraokeNumber is non-zero, we already have it in the database. Just replace the song ID.
  3986. if ( song.iKaraokeNumber > 0 )
  3987. {
  3988. CStdString strSQL = PrepareSQL("UPDATE karaokedata SET idSong=%i WHERE iKaraNumber=%i", idSong, song.iKaraokeNumber);
  3989. m_pDS->exec(strSQL.c_str());
  3990. return;
  3991. }
  3992. // Add new karaoke data
  3993. DWORD crc = ComputeCRC( song.strFileName );
  3994. // Get the maximum number allocated
  3995. strSQL=PrepareSQL( "SELECT MAX(iKaraNumber) FROM karaokedata" );
  3996. if (!m_pDS->query(strSQL.c_str())) return;
  3997. int iKaraokeNumber = g_advancedSettings.m_karaokeStartIndex;
  3998. if ( m_pDS->num_rows() == 1 )
  3999. iKaraokeNumber = m_pDS->fv("MAX(iKaraNumber)").get_asInt() + 1;
  4000. // Add the data
  4001. strSQL=PrepareSQL( "INSERT INTO karaokedata (iKaraNumber, idSong, iKaraDelay, strKaraEncoding, strKaralyrics, strKaraLyrFileCRC) "
  4002. "VALUES( %i, %i, 0, NULL, NULL, '%ul' )", iKaraokeNumber, idSong, crc );
  4003. m_pDS->exec(strSQL.c_str());
  4004. }
  4005. catch (...)
  4006. {
  4007. CLog::Log(LOGERROR, "%s -(%s) failed", __FUNCTION__, song.strFileName.c_str());
  4008. }
  4009. }
  4010. bool CMusicDatabase::GetSongByKaraokeNumber(int number, CSong & song)
  4011. {
  4012. try
  4013. {
  4014. // Get info from karaoke db
  4015. if (NULL == m_pDB.get()) return false;
  4016. if (NULL == m_pDS.get()) return false;
  4017. CStdString strSQL=PrepareSQL("SELECT * FROM karaokedata where iKaraNumber=%ld", number);
  4018. if (!m_pDS->query(strSQL.c_str())) return false;
  4019. if (m_pDS->num_rows() == 0)
  4020. {
  4021. m_pDS->close();
  4022. return false;
  4023. }
  4024. int idSong = m_pDS->fv("karaokedata.idSong").get_asInt();
  4025. m_pDS->close();
  4026. return GetSongById( idSong, song );
  4027. }
  4028. catch (...)
  4029. {
  4030. CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, number);
  4031. }
  4032. return false;
  4033. }
  4034. void CMusicDatabase::ExportKaraokeInfo(const CStdString & outFile, bool asHTML)
  4035. {
  4036. try
  4037. {
  4038. if (NULL == m_pDB.get()) return;
  4039. if (NULL == m_pDS.get()) return;
  4040. // find all karaoke songs
  4041. CStdString sql = "SELECT * FROM songview WHERE iKaraNumber > 0 ORDER BY strFileName";
  4042. m_pDS->query(sql.c_str());
  4043. int total = m_pDS->num_rows();
  4044. int current = 0;
  4045. if ( total == 0 )
  4046. {
  4047. m_pDS->close();
  4048. return;
  4049. }
  4050. // Write the document
  4051. XFILE::CFile file;
  4052. if ( !file.OpenForWrite( outFile, true ) )
  4053. return;
  4054. CGUIDialogProgress *progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
  4055. if (progress)
  4056. {
  4057. progress->SetHeading(asHTML ? 22034 : 22035);
  4058. progress->SetLine(0, 650);
  4059. progress->SetLine(1, "");
  4060. progress->SetLine(2, "");
  4061. progress->SetPercentage(0);
  4062. progress->StartModal();
  4063. progress->ShowProgressBar(true);
  4064. }
  4065. CStdString outdoc;
  4066. if ( asHTML )
  4067. {
  4068. outdoc = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></meta></head>\n"
  4069. "<body>\n<table>\n";
  4070. file.Write( outdoc, outdoc.size() );
  4071. }
  4072. while (!m_pDS->eof())
  4073. {
  4074. CSong song = GetSongFromDataset( false );
  4075. CStdString songnum;
  4076. songnum.Format( "%06d", song.iKaraokeNumber );
  4077. if ( asHTML )
  4078. outdoc = "<tr><td>" + songnum + "</td><td>" + StringUtils::Join(song.artist, g_advancedSettings.m_musicItemSeparator) + "</td><td>" + song.strTitle + "</td></tr>\r\n";
  4079. else
  4080. outdoc = songnum + "\t" + StringUtils::Join(song.artist, g_advancedSettings.m_musicItemSeparator) + "\t" + song.strTitle + "\t" + song.strFileName + "\r\n";
  4081. file.Write( outdoc, outdoc.size() );
  4082. if ((current % 50) == 0 && progress)
  4083. {
  4084. progress->SetPercentage(current * 100 / total);
  4085. progress->Progress();
  4086. if (progress->IsCanceled())
  4087. {
  4088. progress->Close();
  4089. m_pDS->close();
  4090. return;
  4091. }
  4092. }
  4093. m_pDS->next();
  4094. current++;
  4095. }
  4096. m_pDS->close();
  4097. if ( asHTML )
  4098. {
  4099. outdoc = "</table>\n</body>\n</html>\n";
  4100. file.Write( outdoc, outdoc.size() );
  4101. }
  4102. file.Close();
  4103. if (progress)
  4104. progress->Close();
  4105. }
  4106. catch (...)
  4107. {
  4108. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  4109. }
  4110. }
  4111. void CMusicDatabase::ImportKaraokeInfo(const CStdString & inputFile)
  4112. {
  4113. CGUIDialogProgress *progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
  4114. try
  4115. {
  4116. if (NULL == m_pDB.get()) return;
  4117. XFILE::CFile file;
  4118. if ( !file.Open( inputFile ) )
  4119. {
  4120. CLog::Log( LOGERROR, "Cannot open karaoke import file %s", inputFile.c_str() );
  4121. return;
  4122. }
  4123. unsigned int size = (unsigned int) file.GetLength();
  4124. if ( !size )
  4125. return;
  4126. // Read the file into memory array
  4127. std::vector<char> data( size + 1 );
  4128. file.Seek( 0, SEEK_SET );
  4129. // Read the whole file
  4130. if ( file.Read( &data[0], size) != size )
  4131. {
  4132. CLog::Log( LOGERROR, "Cannot read karaoke import file %s", inputFile.c_str() );
  4133. return;
  4134. }
  4135. file.Close();
  4136. data[ size ] = '\0';
  4137. if (progress)
  4138. {
  4139. progress->SetHeading( 22036 );
  4140. progress->SetLine(0, 649);
  4141. progress->SetLine(1, "");
  4142. progress->SetLine(2, "");
  4143. progress->SetPercentage(0);
  4144. progress->StartModal();
  4145. progress->ShowProgressBar(true);
  4146. }
  4147. if (NULL == m_pDS.get()) return;
  4148. BeginTransaction();
  4149. //
  4150. // A simple state machine to parse the file
  4151. //
  4152. char * linestart = &data[0];
  4153. unsigned int offset = 0, lastpercentage = 0;
  4154. for ( char * p = &data[0]; *p; p++, offset++ )
  4155. {
  4156. // Skip \r
  4157. if ( *p == 0x0D )
  4158. {
  4159. *p = '\0';
  4160. continue;
  4161. }
  4162. // Line number
  4163. if ( *p == 0x0A )
  4164. {
  4165. *p = '\0';
  4166. unsigned int tabs = 0;
  4167. char * songpath, *artist = 0, *title = 0;
  4168. for ( songpath = linestart; *songpath; songpath++ )
  4169. {
  4170. if ( *songpath == '\t' )
  4171. {
  4172. tabs++;
  4173. *songpath = '\0';
  4174. switch( tabs )
  4175. {
  4176. case 1: // the number end
  4177. artist = songpath + 1;
  4178. break;
  4179. case 2: // the artist end
  4180. title = songpath + 1;
  4181. break;
  4182. case 3: // the title end
  4183. break;
  4184. }
  4185. }
  4186. }
  4187. int num = atoi( linestart );
  4188. if ( num <= 0 || tabs < 3 || *artist == '\0' || *title == '\0' )
  4189. {
  4190. CLog::Log( LOGERROR, "Karaoke import: error in line %s", linestart );
  4191. linestart = p + 1;
  4192. continue;
  4193. }
  4194. linestart = p + 1;
  4195. CStdString strSQL=PrepareSQL("select idSong from songview "
  4196. "where strArtist like '%s' and strTitle like '%s'", artist, title );
  4197. if ( !m_pDS->query(strSQL.c_str()) )
  4198. {
  4199. RollbackTransaction();
  4200. progress->Close();
  4201. m_pDS->close();
  4202. return;
  4203. }
  4204. int iRowsFound = m_pDS->num_rows();
  4205. if (iRowsFound == 0)
  4206. {
  4207. CLog::Log( LOGERROR, "Karaoke import: song %s by %s #%d is not found in the database, skipped",
  4208. title, artist, num );
  4209. continue;
  4210. }
  4211. int lResult = m_pDS->fv(0).get_asInt();
  4212. strSQL = PrepareSQL("UPDATE karaokedata SET iKaraNumber=%i WHERE idSong=%i", num, lResult );
  4213. m_pDS->exec(strSQL.c_str());
  4214. if ( progress && (offset * 100 / size) != lastpercentage )
  4215. {
  4216. lastpercentage = offset * 100 / size;
  4217. progress->SetPercentage( lastpercentage);
  4218. progress->Progress();
  4219. if ( progress->IsCanceled() )
  4220. {
  4221. RollbackTransaction();
  4222. progress->Close();
  4223. m_pDS->close();
  4224. return;
  4225. }
  4226. }
  4227. }
  4228. }
  4229. CommitTransaction();
  4230. CLog::Log( LOGNOTICE, "Karaoke import: file '%s' was imported successfully", inputFile.c_str() );
  4231. }
  4232. catch (...)
  4233. {
  4234. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  4235. }
  4236. if (progress)
  4237. progress->Close();
  4238. }
  4239. bool CMusicDatabase::SetKaraokeSongDelay(int idSong, int delay)
  4240. {
  4241. try
  4242. {
  4243. if (NULL == m_pDB.get()) return false;
  4244. if (NULL == m_pDS.get()) return false;
  4245. CStdString strSQL = PrepareSQL("UPDATE karaokedata SET iKaraDelay=%i WHERE idSong=%i", delay, idSong);
  4246. m_pDS->exec(strSQL.c_str());
  4247. return true;
  4248. }
  4249. catch (...)
  4250. {
  4251. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  4252. }
  4253. return false;
  4254. }
  4255. int CMusicDatabase::GetKaraokeSongsCount()
  4256. {
  4257. try
  4258. {
  4259. if (NULL == m_pDB.get()) return 0;
  4260. if (NULL == m_pDS.get()) return 0;
  4261. if (!m_pDS->query( "select count(idSong) as NumSongs from karaokedata")) return 0;
  4262. if (m_pDS->num_rows() == 0)
  4263. {
  4264. m_pDS->close();
  4265. return 0;
  4266. }
  4267. int iNumSongs = m_pDS->fv("NumSongs").get_asInt();
  4268. // cleanup
  4269. m_pDS->close();
  4270. return iNumSongs;
  4271. }
  4272. catch (...)
  4273. {
  4274. CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  4275. }
  4276. return 0;
  4277. }
  4278. void CMusicDatabase::SetPropertiesFromArtist(CFileItem& item, const CArtist& artist)
  4279. {
  4280. item.SetProperty("artist_instrument", StringUtils::Join(artist.instruments, g_advancedSettings.m_musicItemSeparator));
  4281. item.SetProperty("artist_instrument_array", artist.instruments);
  4282. item.SetProperty("artist_style", StringUtils::Join(artist.styles, g_advancedSettings.m_musicItemSeparator));
  4283. item.SetProperty("artist_style_array", artist.styles);
  4284. item.SetProperty("artist_mood", StringUtils::Join(artist.moods, g_advancedSettings.m_musicItemSeparator));
  4285. item.SetProperty("artist_mood_array", artist.moods);
  4286. item.SetProperty("artist_born", artist.strBorn);
  4287. item.SetProperty("artist_formed", artist.strFormed);
  4288. item.SetProperty("artist_description", artist.strBiography);
  4289. item.SetProperty("artist_genre", StringUtils::Join(artist.genre, g_advancedSettings.m_musicItemSeparator));
  4290. item.SetProperty("artist_genre_array", artist.genre);
  4291. item.SetProperty("artist_died", artist.strDied);
  4292. item.SetProperty("artist_disbanded", artist.strDisbanded);
  4293. item.SetProperty("artist_yearsactive", StringUtils::Join(artist.yearsActive, g_advancedSettings.m_musicItemSeparator));
  4294. item.SetProperty("artist_yearsactive_array", artist.yearsActive);
  4295. }
  4296. void CMusicDatabase::SetPropertiesFromAlbum(CFileItem& item, const CAlbum& album)
  4297. {
  4298. item.SetProperty("album_description", album.strReview);
  4299. item.SetProperty("album_theme", StringUtils::Join(album.themes, g_advancedSettings.m_musicItemSeparator));
  4300. item.SetProperty("album_theme_array", album.themes);
  4301. item.SetProperty("album_mood", StringUtils::Join(album.moods, g_advancedSettings.m_musicItemSeparator));
  4302. item.SetProperty("album_mood_array", album.moods);
  4303. item.SetProperty("album_style", StringUtils::Join(album.styles, g_advancedSettings.m_musicItemSeparator));
  4304. item.SetProperty("album_style_array", album.styles);
  4305. item.SetProperty("album_type", album.strType);
  4306. item.SetProperty("album_label", album.strLabel);
  4307. item.SetProperty("album_artist", StringUtils::Join(album.artist, g_advancedSettings.m_musicItemSeparator));
  4308. item.SetProperty("album_artist_array", album.artist);
  4309. item.SetProperty("album_genre", StringUtils::Join(album.genre, g_advancedSettings.m_musicItemSeparator));
  4310. item.SetProperty("album_genre_array", album.genre);
  4311. item.SetProperty("album_title", album.strAlbum);
  4312. if (album.iRating > 0)
  4313. item.SetProperty("album_rating", album.iRating);
  4314. }
  4315. void CMusicDatabase::SetPropertiesForFileItem(CFileItem& item)
  4316. {
  4317. if (!item.HasMusicInfoTag())
  4318. return;
  4319. int idArtist = GetArtistByName(StringUtils::Join(item.GetMusicInfoTag()->GetArtist(), g_advancedSettings.m_musicItemSeparator));
  4320. if (idArtist > -1)
  4321. {
  4322. CArtist artist;
  4323. if (GetArtistInfo(idArtist,artist))
  4324. SetPropertiesFromArtist(item,artist);
  4325. }
  4326. int idAlbum = GetAlbumByName(item.GetMusicInfoTag()->GetAlbum(),
  4327. item.GetMusicInfoTag()->GetArtist());
  4328. if (idAlbum > -1)
  4329. {
  4330. CAlbum album;
  4331. if (GetAlbumInfo(idAlbum,album,NULL))
  4332. SetPropertiesFromAlbum(item,album);
  4333. }
  4334. }
  4335. void CMusicDatabase::AnnounceRemove(std::string content, int id)
  4336. {
  4337. CVariant data;
  4338. data["type"] = content;
  4339. data["id"] = id;
  4340. ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnRemove", data);
  4341. }
  4342. void CMusicDatabase::AnnounceUpdate(std::string content, int id)
  4343. {
  4344. CVariant data;
  4345. data["type"] = content;
  4346. data["id"] = id;
  4347. ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::AudioLibrary, "xbmc", "OnUpdate", data);
  4348. }
  4349. void CMusicDatabase::SetArtForItem(int mediaId, const string &mediaType, const map<string, string> &art)
  4350. {
  4351. for (map<string, string>::const_iterator i = art.begin(); i != art.end(); ++i)
  4352. SetArtForItem(mediaId, mediaType, i->first, i->second);
  4353. }
  4354. void CMusicDatabase::SetArtForItem(int mediaId, const string &mediaType, const string &artType, const string &url)
  4355. {
  4356. try
  4357. {
  4358. if (NULL == m_pDB.get()) return;
  4359. if (NULL == m_pDS.get()) return;
  4360. 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());
  4361. m_pDS->query(sql.c_str());
  4362. if (!m_pDS->eof())
  4363. { // update
  4364. int artId = m_pDS->fv(0).get_asInt();
  4365. m_pDS->close();
  4366. sql = PrepareSQL("UPDATE art SET url='%s' where art_id=%d", url.c_str(), artId);
  4367. m_pDS->exec(sql.c_str());
  4368. }
  4369. else
  4370. { // insert
  4371. m_pDS->close();
  4372. 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());
  4373. m_pDS->exec(sql.c_str());
  4374. }
  4375. }
  4376. catch (...)
  4377. {
  4378. CLog::Log(LOGERROR, "%s(%d, '%s', '%s', '%s') failed", __FUNCTION__, mediaId, mediaType.c_str(), artType.c_str(), url.c_str());
  4379. }
  4380. }
  4381. bool CMusicDatabase::GetArtForItem(int mediaId, const string &mediaType, map<string, string> &art)
  4382. {
  4383. try
  4384. {
  4385. if (NULL == m_pDB.get()) return false;
  4386. if (NULL == m_pDS2.get()) return false; // using dataset 2 as we're likely called in loops on dataset 1
  4387. CStdString sql = PrepareSQL("SELECT type,url FROM art WHERE media_id=%i AND media_type='%s'", mediaId, mediaType.c_str());
  4388. m_pDS2->query(sql.c_str());
  4389. while (!m_pDS2->eof())
  4390. {
  4391. art.insert(make_pair(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString()));
  4392. m_pDS2->next();
  4393. }
  4394. m_pDS2->close();
  4395. return !art.empty();
  4396. }
  4397. catch (...)
  4398. {
  4399. CLog::Log(LOGERROR, "%s(%d) failed", __FUNCTION__, mediaId);
  4400. }
  4401. return false;
  4402. }
  4403. string CMusicDatabase::GetArtForItem(int mediaId, const string &mediaType, const string &artType)
  4404. {
  4405. 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());
  4406. return GetSingleValue(query, m_pDS2);
  4407. }
  4408. bool CMusicDatabase::GetArtistArtForItem(int mediaId, const std::string &mediaType, std::map<std::string, std::string> &art)
  4409. {
  4410. try
  4411. {
  4412. if (NULL == m_pDB.get()) return false;
  4413. if (NULL == m_pDS2.get()) return false; // using dataset 2 as we're likely called in loops on dataset 1
  4414. CStdString sql = PrepareSQL("SELECT type,url FROM art WHERE media_id=(SELECT idArtist from %s_artist WHERE id%s=%i) AND media_type='artist'", mediaType.c_str(), mediaType.c_str(), mediaId);
  4415. m_pDS2->query(sql.c_str());
  4416. while (!m_pDS2->eof())
  4417. {
  4418. art.insert(make_pair(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString()));
  4419. m_pDS2->next();
  4420. }
  4421. m_pDS2->close();
  4422. return !art.empty();
  4423. }
  4424. catch (...)
  4425. {
  4426. CLog::Log(LOGERROR, "%s(%d) failed", __FUNCTION__, mediaId);
  4427. }
  4428. return false;
  4429. }
  4430. string CMusicDatabase::GetArtistArtForItem(int mediaId, const string &mediaType, const string &artType)
  4431. {
  4432. std::string query = PrepareSQL("SELECT url FROM art WHERE media_id=(SELECT idArtist from %s_artist WHERE id%s=%i) AND media_type='artist' AND type='%s'", mediaType.c_str(), mediaType.c_str(), mediaId, artType.c_str());
  4433. return GetSingleValue(query, m_pDS2);
  4434. }