aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Blake <oak99sky@yahoo.co.uk>2019-04-06 13:42:48 +0100
committerGitHub <noreply@github.com>2019-04-06 13:42:48 +0100
commit4eb34ebe68452821b97befbcc4a8fcb663924f5b (patch)
tree70738333ad8476befa5c348d0022109837fb55a4
parent392b7ed4be1e5049755028d69b542814a9592d8f (diff)
parentf7caf87377eead02e259e55ef78655b86b744817 (diff)
Merge pull request #15864 from DaveTBlake/ExportSongs
Music lib export/import song playback history fix import use separate thread from GUI
-rw-r--r--addons/resource.language.en_gb/resources/strings.po40
-rw-r--r--xbmc/music/MusicDatabase.cpp426
-rw-r--r--xbmc/music/MusicDatabase.h6
-rw-r--r--xbmc/music/MusicLibraryQueue.cpp40
-rw-r--r--xbmc/music/MusicLibraryQueue.h7
-rw-r--r--xbmc/music/jobs/CMakeLists.txt2
-rw-r--r--xbmc/music/jobs/MusicLibraryImportJob.cpp41
-rw-r--r--xbmc/music/jobs/MusicLibraryImportJob.h42
-rw-r--r--xbmc/settings/LibExportSettings.cpp19
-rw-r--r--xbmc/settings/LibExportSettings.h4
-rw-r--r--xbmc/settings/MediaSettings.cpp7
-rw-r--r--xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp44
12 files changed, 621 insertions, 57 deletions
diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po
index 15c74ef484..4e239efb2e 100644
--- a/addons/resource.language.en_gb/resources/strings.po
+++ b/addons/resource.language.en_gb/resources/strings.po
@@ -564,6 +564,7 @@ msgstr ""
#: xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp
#: xbmc/filesystem/MusicDatabaseDirectory/DirectoryNodeOverview.cpp
#: xbmc/media/MediaTypes.cpp
+#: xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp
msgctxt "#134"
msgid "Songs"
msgstr ""
@@ -2986,12 +2987,12 @@ msgctxt "#648"
msgid "Import video library"
msgstr ""
-#: xbmc/music/MusicDatabase.cpp
+#. unused?
msgctxt "#649"
msgid "Importing"
msgstr ""
-#: xbmc/music/MusicDatabase.cpp
+#. unused?
msgctxt "#650"
msgid "Exporting"
msgstr ""
@@ -12393,6 +12394,7 @@ msgctxt "#20196"
msgid "Export music library"
msgstr ""
+#: xbmc/music/MusicDatabase.cpp
#: system/settings/settings.xml
msgctxt "#20197"
msgid "Import music library"
@@ -21392,7 +21394,39 @@ msgctxt "#38339"
msgid "How to apply information provider settings"
msgstr ""
-#empty strings from id 38340 to 38999
+#empty strings from id 38340 to 38349
+
+#. Progress statement when importing music library is processing song playback history data
+#: xbmc/music/MusicDatabase.cpp
+msgctxt "#38350"
+msgid "Importing song playback history"
+msgstr ""
+
+#. Progress statement when importing music library - data matching phase
+#: xbmc/music/MusicDatabase.cpp
+msgctxt "#38351"
+msgid "Matching data"
+msgstr ""
+
+#. Progress statement when importing music library - song history being updated
+#: xbmc/music/MusicDatabase.cpp
+msgctxt "#38352"
+msgid "Updating songs"
+msgstr ""
+
+#. Notification of import success "Importing song history - <number of songs matched> updated out of <total songs in xml> imported songs"
+#: xbmc/music/MusicDatabase.cpp
+msgctxt "#38353"
+msgid "Importing song history - {0:d} updated out of {0:d} imported songs"
+msgstr ""
+
+#. Message when reading the user specified XML for import to library fails
+#: xbmc/music/MusicDatabase.cpp
+msgctxt "#38354"
+msgid "Unable to read xml file"
+msgstr ""
+
+#empty strings from id 38355 to 38999
#: system/settings/settings.xml
msgctxt "#39000"
diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp
index 4367a08f14..1d72b8dd2e 100644
--- a/xbmc/music/MusicDatabase.cpp
+++ b/xbmc/music/MusicDatabase.cpp
@@ -20,6 +20,8 @@
#include "dialogs/GUIDialogKaiToast.h"
#include "dialogs/GUIDialogProgress.h"
#include "dialogs/GUIDialogSelect.h"
+#include "events/EventLog.h"
+#include "events/NotificationEvent.h"
#include "FileItem.h"
#include "filesystem/Directory.h"
#include "filesystem/DirectoryCache.h"
@@ -52,6 +54,7 @@
#include "utils/FileUtils.h"
#include "utils/LegacyPathTranslation.h"
#include "utils/log.h"
+#include "utils/MathUtils.h"
#include "utils/Random.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"
@@ -9479,12 +9482,13 @@ std::string CMusicDatabase::GetItemById(const std::string &itemType, int id)
return "";
}
-void CMusicDatabase::ExportToXML(const CLibExportSettings& settings, CGUIDialogProgress* progressDialog /*= NULL*/)
+void CMusicDatabase::ExportToXML(const CLibExportSettings& settings, CGUIDialogProgress* progressDialog /*= nullptr*/)
{
if (!settings.IsItemExported(ELIBEXPORT_ALBUMARTISTS) &&
!settings.IsItemExported(ELIBEXPORT_SONGARTISTS) &&
!settings.IsItemExported(ELIBEXPORT_OTHERARTISTS) &&
- !settings.IsItemExported(ELIBEXPORT_ALBUMS))
+ !settings.IsItemExported(ELIBEXPORT_ALBUMS) &&
+ !settings.IsItemExported(ELIBEXPORT_SONGS))
return;
// Exporting albums either art or NFO (or both) selected
@@ -9684,6 +9688,13 @@ void CMusicDatabase::ExportToXML(const CLibExportSettings& settings, CGUIDialog
}
}
+ // Export song playback history to single file only
+ if (settings.IsSingleFile() && settings.IsItemExported(ELIBEXPORT_SONGS))
+ {
+ if (!ExportSongHistory(pMain, progressDialog))
+ return;
+ }
+
if ((settings.IsArtists() || artistfoldersonly) && !strFolder.empty())
{
// Find artists to export
@@ -9828,45 +9839,106 @@ void CMusicDatabase::ExportToXML(const CLibExportSettings& settings, CGUIDialog
if (progressDialog)
progressDialog->Close();
- if (iFailCount > 0)
+ if (iFailCount > 0 && progressDialog)
HELPERS::ShowOKDialogLines(CVariant{20196}, CVariant{StringUtils::Format(g_localizeStrings.Get(15011).c_str(), iFailCount)});
}
-void CMusicDatabase::ImportFromXML(const std::string &xmlFile)
+bool CMusicDatabase::ExportSongHistory(TiXmlNode* pNode, CGUIDialogProgress* progressDialog)
{
- CGUIDialogProgress *progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
+ try
+ {
+ // Export songs with some playback history
+ std::string strSQL = "SELECT idSong, song.idAlbum, "
+ "strAlbum, strMusicBrainzAlbumID, album.strArtistDisp AS strAlbumArtistDisp, "
+ "song.strArtistDisp, strTitle, iTrack, strFileName, strMusicBrainzTrackID, "
+ "iTimesPlayed, lastplayed, song.rating, song.votes, song.userrating "
+ "FROM song JOIN album on album.idAlbum = song.idAlbum "
+ "WHERE iTimesPlayed > 0 OR rating > 0 or userrating > 0";
+
+ CLog::Log(LOGDEBUG, "{0} - {1}", __FUNCTION__, strSQL.c_str());
+ m_pDS->query(strSQL);
+
+ int total = m_pDS->num_rows();
+ int current = 0;
+ while (!m_pDS->eof())
+ {
+ TiXmlElement songElement("song");
+ TiXmlNode* song = pNode->InsertEndChild(songElement);
+
+ XMLUtils::SetInt(song, "idsong", m_pDS->fv("idSong").get_asInt());
+ XMLUtils::SetString(song, "artistdesc", m_pDS->fv("strArtistDisp").get_asString());
+ XMLUtils::SetString(song, "title", m_pDS->fv("strTitle").get_asString());
+ XMLUtils::SetInt(song, "track", m_pDS->fv("iTrack").get_asInt());
+ XMLUtils::SetString(song, "filename", m_pDS->fv("strFilename").get_asString());
+ XMLUtils::SetString(song, "musicbrainztrackid", m_pDS->fv("strMusicBrainzTrackID").get_asString());
+ XMLUtils::SetInt(song, "idalbum", m_pDS->fv("idAlbum").get_asInt());
+ XMLUtils::SetString(song, "albumtitle", m_pDS->fv("strAlbum").get_asString());
+ XMLUtils::SetString(song, "musicbrainzalbumid", m_pDS->fv("strMusicBrainzAlbumID").get_asString());
+ XMLUtils::SetString(song, "albumartistdesc", m_pDS->fv("strAlbumArtistDisp").get_asString());
+ XMLUtils::SetInt(song, "timesplayed", m_pDS->fv("iTimesplayed").get_asInt());
+ XMLUtils::SetString(song, "lastplayed", m_pDS->fv("lastplayed").get_asString());
+ auto* rating = XMLUtils::SetString(song, "rating", StringUtils::FormatNumber(m_pDS->fv("rating").get_asFloat()));
+ if (rating)
+ rating->ToElement()->SetAttribute("max", 10);
+ XMLUtils::SetInt(song, "votes", m_pDS->fv("votes").get_asInt());
+ auto* userrating = XMLUtils::SetInt(song, "userrating", m_pDS->fv("userrating").get_asInt());
+ if (userrating)
+ userrating->ToElement()->SetAttribute("max", 10);
+
+ if ((current % 100) == 0 && progressDialog)
+ {
+ progressDialog->SetLine(1, CVariant{ m_pDS->fv("strAlbum").get_asString() });
+ progressDialog->SetPercentage(current * 100 / total);
+ if (progressDialog->IsCanceled())
+ {
+ m_pDS->close();
+ return false;
+ }
+ }
+ current++;
+
+ m_pDS->next();
+ }
+ m_pDS->close();
+ return true;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "{0} failed", __FUNCTION__);
+ }
+ return false;
+}
+
+void CMusicDatabase::ImportFromXML(const std::string& xmlFile, CGUIDialogProgress* progressDialog)
+{
try
{
if (NULL == m_pDB.get()) return;
if (NULL == m_pDS.get()) return;
CXBMCTinyXML xmlDoc;
- if (!xmlDoc.LoadFile(xmlFile))
+ if (!xmlDoc.LoadFile(xmlFile) && progressDialog)
+ {
+ HELPERS::ShowOKDialogLines(CVariant{ 20197 }, CVariant{ 38354 }); //"Unable to read xml file"
return;
+ }
TiXmlElement *root = xmlDoc.RootElement();
if (!root) return;
- if (progress)
- {
- progress->SetHeading(CVariant{20197});
- progress->SetLine(0, CVariant{649});
- progress->SetLine(1, CVariant{330});
- progress->SetLine(2, CVariant{""});
- progress->SetPercentage(0);
- progress->Open();
- progress->ShowProgressBar(true);
- }
-
TiXmlElement *entry = root->FirstChildElement();
int current = 0;
int total = 0;
- // first count the number of items...
+ int songtotal = 0;
+ // Count the number of artists, albums and songs
while (entry)
{
if (strnicmp(entry->Value(), "artist", 6)==0 ||
strnicmp(entry->Value(), "album", 5)==0)
total++;
+ else if (strnicmp(entry->Value(), "song", 4) == 0)
+ songtotal++;
+
entry = entry->NextSiblingElement();
}
@@ -9914,14 +9986,13 @@ void CMusicDatabase::ImportFromXML(const std::string &xmlFile)
current++;
}
entry = entry ->NextSiblingElement();
- if (progress && total)
+ if (progressDialog && total)
{
- progress->SetPercentage(current * 100 / total);
- progress->SetLine(2, CVariant{std::move(strTitle)});
- progress->Progress();
- if (progress->IsCanceled())
+ progressDialog->SetPercentage(current * 100 / total);
+ progressDialog->SetLine(2, CVariant{std::move(strTitle)});
+ progressDialog->Progress();
+ if (progressDialog->IsCanceled())
{
- progress->Close();
RollbackTransaction();
return;
}
@@ -9929,6 +10000,11 @@ void CMusicDatabase::ImportFromXML(const std::string &xmlFile)
}
CommitTransaction();
+ // Import song playback history <song> entries found
+ if (songtotal > 0)
+ if (!ImportSongHistory(xmlFile, songtotal, progressDialog))
+ return;
+
CGUIComponent* gui = CServiceBroker::GetGUI();
if (gui)
gui->GetInfoManager().GetInfoProviders().GetLibraryInfoProvider().ResetLibraryBools();
@@ -9938,8 +10014,306 @@ void CMusicDatabase::ImportFromXML(const std::string &xmlFile)
CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
RollbackTransaction();
}
- if (progress)
- progress->Close();
+ if (progressDialog)
+ progressDialog->Close();
+}
+
+bool CMusicDatabase::ImportSongHistory(const std::string& xmlFile, const int total, CGUIDialogProgress* progressDialog)
+{
+ bool bHistSongExists = false;
+ try
+ {
+ CXBMCTinyXML xmlDoc;
+ if (!xmlDoc.LoadFile(xmlFile))
+ return false;
+
+ TiXmlElement* root = xmlDoc.RootElement();
+ if (!root)
+ return false;
+
+ TiXmlElement* entry = root->FirstChildElement();
+ int current = 0;
+
+ if (progressDialog)
+ {
+ progressDialog->SetLine(1, CVariant{38350}); //"Importing song playback history"
+ progressDialog->SetLine(2, CVariant{ "" });
+ }
+
+ // As can be many songs do in db, not song at a time which would be slow
+ // Convert xml entries into a SQL bulk insert statement
+ std::string strSQL;
+ entry = root->FirstChildElement();
+ while (entry)
+ {
+ std::string strArtistDisp;
+ std::string strTitle;
+ int iTrack;
+ std::string strFilename;
+ std::string strMusicBrainzTrackID;
+ std::string strAlbum;
+ std::string strMusicBrainzAlbumID;
+ std::string strAlbumArtistDisp;
+ int iTimesplayed;
+ std::string lastplayed;
+ int iUserrating = 0;
+ float fRating = 0.0;
+ int iVotes;
+ std::string strSQLSong;
+ if (strnicmp(entry->Value(), "song", 4) == 0)
+ {
+ XMLUtils::GetString(entry, "artistdesc", strArtistDisp);
+ XMLUtils::GetString(entry, "title", strTitle);
+ XMLUtils::GetInt(entry, "track", iTrack);
+ XMLUtils::GetString(entry, "filename", strFilename);
+ XMLUtils::GetString(entry, "musicbrainztrackid", strMusicBrainzTrackID);
+ XMLUtils::GetString(entry, "albumtitle", strAlbum);
+ XMLUtils::GetString(entry, "musicbrainzalbumid", strMusicBrainzAlbumID);
+ XMLUtils::GetString(entry, "albumartistdesc", strAlbumArtistDisp);
+ XMLUtils::GetInt(entry, "timesplayed", iTimesplayed);
+ XMLUtils::GetString(entry, "lastplayed", lastplayed);
+ const TiXmlElement* rElement = entry->FirstChildElement("rating");
+ if (rElement)
+ {
+ float rating = 0;
+ float max_rating = 10;
+ XMLUtils::GetFloat(entry, "rating", rating);
+ if (rElement->QueryFloatAttribute("max", &max_rating) == TIXML_SUCCESS && max_rating >= 1)
+ rating *= (10.f / max_rating); // Normalise the value to between 0 and 10
+ if (rating > 10.f)
+ rating = 10.f;
+ fRating = rating;
+ }
+ XMLUtils::GetInt(entry, "votes", iVotes);
+ const TiXmlElement* userrating = entry->FirstChildElement("userrating");
+ if (userrating)
+ {
+ float rating = 0;
+ float max_rating = 10;
+ XMLUtils::GetFloat(entry, "userrating", rating);
+ if (userrating->QueryFloatAttribute("max", &max_rating) == TIXML_SUCCESS && max_rating >= 1)
+ rating *= (10.f / max_rating); // Normalise the value to between 0 and 10
+ if (rating > 10.f)
+ rating = 10.f;
+ iUserrating = MathUtils::round_int(rating);
+ }
+
+ strSQLSong = PrepareSQL("(%d, %d, ", current + 1, iTrack);
+ strSQLSong += PrepareSQL("'%s', '%s', '%s', ", strArtistDisp.c_str(), strTitle.c_str(), strFilename.c_str());
+ if (strMusicBrainzTrackID.empty())
+ strSQLSong += PrepareSQL("NULL, ");
+ else
+ strSQLSong += PrepareSQL("'%s', ", strMusicBrainzTrackID.c_str());
+ strSQLSong += PrepareSQL("'%s', '%s', ", strAlbum.c_str(), strAlbumArtistDisp.c_str());
+ if (strMusicBrainzAlbumID.empty())
+ strSQLSong += PrepareSQL("NULL, ");
+ else
+ strSQLSong += PrepareSQL("'%s', ", strMusicBrainzAlbumID.c_str());
+ strSQLSong += PrepareSQL("%d, ", iTimesplayed);
+ if (lastplayed.empty())
+ strSQLSong += PrepareSQL("NULL, ");
+ else
+ strSQLSong += PrepareSQL("'%s', ", lastplayed.c_str());
+ strSQLSong += PrepareSQL("%.1f, %d, %d, -1, -1)", fRating, iVotes, iUserrating);
+
+ if (current > 0)
+ strSQLSong = ", " + strSQLSong;
+ strSQL += strSQLSong;
+ current++;
+ }
+
+ entry = entry->NextSiblingElement();
+
+ if ((current % 100) == 0 && progressDialog)
+ {
+ progressDialog->SetPercentage(current * 100 / total);
+ progressDialog->SetLine(3, CVariant{ std::move(strTitle) });
+ progressDialog->Progress();
+ if (progressDialog->IsCanceled())
+ return false;
+ }
+ }
+
+ CLog::Log(LOGINFO, "{0}: Create temporary HistSong table and insert {1} records", __FUNCTION__, total);
+ /* Can not use CREATE TEMPORARY TABLE as MySQL does not support updates of
+ song table using correlated subqueries to a temp table. An updatable join
+ to temp table would work in MySQL but SQLite not support updatable joins.
+ */
+ m_pDS->exec("CREATE TABLE HistSong ("
+ "idSongSrc INTEGER primary key, "
+ "strAlbum varchar(256), "
+ "strMusicBrainzAlbumID text, "
+ "strAlbumArtistDisp text, "
+ "strArtistDisp text, strTitle varchar(512), "
+ "iTrack INTEGER, strFileName text, strMusicBrainzTrackID text, "
+ "iTimesPlayed INTEGER, lastplayed varchar(20) default NULL, "
+ "rating FLOAT NOT NULL DEFAULT 0, votes INTEGER NOT NULL DEFAULT 0, "
+ "userrating INTEGER NOT NULL DEFAULT 0, "
+ "idAlbum INTEGER, idSong INTEGER)");
+ bHistSongExists = true;
+
+ strSQL = "INSERT INTO HistSong (idSongSrc, iTrack, strArtistDisp, strTitle, "
+ "strFileName, strMusicBrainzTrackID, "
+ "strAlbum, strAlbumArtistDisp, strMusicBrainzAlbumID, "
+ " iTimesPlayed, lastplayed, rating, votes, userrating, idAlbum, idSong) VALUES " + strSQL;
+ m_pDS->exec(strSQL);
+
+ if (progressDialog)
+ {
+ progressDialog->SetLine(2, CVariant{38351}); //"Matching data"
+ progressDialog->SetLine(3, CVariant{ "" });
+ progressDialog->Progress();
+ if (progressDialog->IsCanceled())
+ {
+ m_pDS->exec("DROP TABLE HistSong");
+ return false;
+ }
+ }
+
+ BeginTransaction();
+ // Match albums first on mbid then artist string and album title, setting idAlbum
+ strSQL = "UPDATE HistSong "
+ "SET idAlbum = (SELECT album.idAlbum FROM album "
+ "WHERE album.strMusicBrainzAlbumID = HistSong.strMusicBrainzAlbumID) "
+ "WHERE EXISTS(SELECT 1 FROM album "
+ "WHERE album.strMusicBrainzAlbumID = HistSong.strMusicBrainzAlbumID) AND idAlbum < 0";
+ m_pDS->exec(strSQL);
+
+ strSQL = "UPDATE HistSong "
+ "SET idAlbum = (SELECT album.idAlbum FROM album "
+ "WHERE HistSong.strAlbumArtistDisp = album.strArtistDisp AND HistSong.strAlbum = album.strAlbum) "
+ "WHERE EXISTS(SELECT 1 FROM album "
+ "WHERE HistSong.strAlbumArtistDisp = album.strArtistDisp AND HistSong.strAlbum = album.strAlbum)"
+ "AND idAlbum < 0";
+ m_pDS->exec(strSQL);
+ if (progressDialog)
+ {
+ progressDialog->Progress();
+ if (progressDialog->IsCanceled())
+ {
+ RollbackTransaction();
+ m_pDS->exec("DROP TABLE HistSong");
+ return false;
+ }
+ }
+
+ // Match songs on first on idAlbum, track and mbid, then idAlbum, track and title, setting idSong
+ strSQL = "UPDATE HistSong "
+ "SET idSong = (SELECT idsong FROM song "
+ "WHERE HistSong.idAlbum = song.idAlbum AND "
+ "HistSong.iTrack = song.iTrack AND "
+ "HistSong.strMusicBrainzTrackID = song.strMusicBrainzTrackID) "
+ "WHERE EXISTS(SELECT 1 FROM song "
+ "WHERE HistSong.idAlbum = song.idAlbum AND "
+ "HistSong.iTrack = song.iTrack AND "
+ "HistSong.strMusicBrainzTrackID = song.strMusicBrainzTrackID) AND idSong < 0";
+ m_pDS->exec(strSQL);
+
+ strSQL = "UPDATE HistSong "
+ "SET idSong = (SELECT idsong FROM song "
+ "WHERE HistSong.idAlbum = song.idAlbum AND "
+ "HistSong.iTrack = song.iTrack AND HistSong.strTitle = song.strTitle) "
+ "WHERE EXISTS(SELECT 1 FROM song "
+ "WHERE HistSong.idAlbum = song.idAlbum AND "
+ "HistSong.iTrack = song.iTrack AND HistSong.strTitle = song.strTitle) AND idSong < 0";
+ m_pDS->exec(strSQL);
+ CommitTransaction();
+ if (progressDialog)
+ {
+ progressDialog->Progress();
+ if (progressDialog->IsCanceled())
+ {
+ m_pDS->exec("DROP TABLE HistSong");
+ return false;
+ }
+ }
+
+ // Create an index to speed up the updates
+ m_pDS->exec("CREATE INDEX idxHistSong ON HistSong(idSong)");
+
+ // Log how many songs matched
+ int unmatched = static_cast<int>(strtol(GetSingleValue("SELECT COUNT(1) FROM HistSong WHERE idSong < 0", m_pDS).c_str(), nullptr, 10));
+ CLog::Log(LOGINFO, "{0}: Importing song history {1} of {2} songs matched", __FUNCTION__, total - unmatched, total);
+
+ if (progressDialog)
+ {
+ progressDialog->SetLine(2, CVariant{38352}); //"Updating song playback history"
+ progressDialog->Progress();
+ if (progressDialog->IsCanceled())
+ {
+ m_pDS->exec("DROP TABLE HistSong"); // Drops index too
+ return false;
+ }
+ }
+
+ /* Update song table using the song ids we have matched.
+ Use correlated subqueries as SQLite does not support updatable joins.
+ MySQL requires HistSong table not to be defined temporary for this.
+ */
+ BeginTransaction();
+ // Times played and last played date(when count is greater)
+ strSQL = "UPDATE song SET iTimesPlayed = "
+ "(SELECT iTimesPlayed FROM HistSong WHERE HistSong.idSong = song.idSong), "
+ "lastplayed = "
+ "(SELECT lastplayed FROM HistSong WHERE HistSong.idSong = song.idSong) "
+ "WHERE EXISTS(SELECT 1 FROM HistSong WHERE "
+ "HistSong.idSong = song.idSong AND HistSong.iTimesPlayed > song.iTimesPlayed)";
+ m_pDS->exec(strSQL);
+
+ // User rating
+ strSQL = "UPDATE song SET userrating = "
+ "(SELECT userrating FROM HistSong WHERE HistSong.idSong = song.idSong) "
+ "WHERE EXISTS(SELECT 1 FROM HistSong WHERE "
+ "HistSong.idSong = song.idSong AND HistSong.userrating > 0)";
+ m_pDS->exec(strSQL);
+
+ // Rating and votes
+ strSQL = "UPDATE song SET rating = "
+ "(SELECT rating FROM HistSong WHERE HistSong.idSong = song.idSong), "
+ "votes = "
+ "(SELECT votes FROM HistSong WHERE HistSong.idSong = song.idSong) "
+ "WHERE EXISTS(SELECT 1 FROM HistSong WHERE "
+ "HistSong.idSong = song.idSong AND HistSong.rating > 0)";
+ m_pDS->exec(strSQL);
+
+ if (progressDialog)
+ {
+ progressDialog->Progress();
+ if (progressDialog->IsCanceled())
+ {
+ RollbackTransaction();
+ m_pDS->exec("DROP TABLE HistSong");
+ return false;
+ }
+ }
+ CommitTransaction();
+
+ // Tidy up temp table (index also removed)
+ m_pDS->exec("DROP TABLE HistSong");
+ // Compact db to recover space as had to add/drop actual table
+ if (progressDialog)
+ {
+ progressDialog->SetLine(2, CVariant{ 331 });
+ progressDialog->Progress();
+ }
+ Compress(false);
+
+ // Write event log entry
+ // "Importing song history {1} of {2} songs matched", total - unmatched, total)
+ std::string strLine = StringUtils::Format(g_localizeStrings.Get(38353).c_str(), total - unmatched, total);
+ CServiceBroker::GetEventLog().Add(
+ EventPtr(new CNotificationEvent(20197, strLine, EventLevel::Information)));
+
+ return true;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
+ RollbackTransaction();
+ if (bHistSongExists)
+ m_pDS->exec("DROP TABLE HistSong");
+ }
+ return false;
}
void CMusicDatabase::SetPropertiesFromArtist(CFileItem& item, const CArtist& artist)
diff --git a/xbmc/music/MusicDatabase.h b/xbmc/music/MusicDatabase.h
index 3ef778d6d4..ed65526a94 100644
--- a/xbmc/music/MusicDatabase.h
+++ b/xbmc/music/MusicDatabase.h
@@ -497,8 +497,10 @@ public:
/////////////////////////////////////////////////
// XML
/////////////////////////////////////////////////
- void ExportToXML(const CLibExportSettings& settings, CGUIDialogProgress* progressDialog = NULL);
- void ImportFromXML(const std::string &xmlFile);
+ void ExportToXML(const CLibExportSettings& settings, CGUIDialogProgress* progressDialog = nullptr);
+ bool ExportSongHistory(TiXmlNode* pNode, CGUIDialogProgress* progressDialog = nullptr);
+ void ImportFromXML(const std::string& xmlFile, CGUIDialogProgress* progressDialog = nullptr);
+ bool ImportSongHistory(const std::string& xmlFile, const int total, CGUIDialogProgress* progressDialog = nullptr);
/////////////////////////////////////////////////
// Properties
diff --git a/xbmc/music/MusicLibraryQueue.cpp b/xbmc/music/MusicLibraryQueue.cpp
index 740fa3095b..acfaffb114 100644
--- a/xbmc/music/MusicLibraryQueue.cpp
+++ b/xbmc/music/MusicLibraryQueue.cpp
@@ -17,6 +17,7 @@
#include "GUIUserMessages.h"
#include "music/jobs/MusicLibraryCleaningJob.h"
#include "music/jobs/MusicLibraryExportJob.h"
+#include "music/jobs/MusicLibraryImportJob.h"
#include "music/jobs/MusicLibraryScanningJob.h"
#include "music/jobs/MusicLibraryJob.h"
#include "threads/SingleLock.h"
@@ -77,6 +78,45 @@ void CMusicLibraryQueue::ExportLibrary(const CLibExportSettings& settings, bool
}
}
+void CMusicLibraryQueue::ImportLibrary(const std::string& xmlFile, bool showDialog /* = false */)
+{
+ CGUIDialogProgress* progress = nullptr;
+ if (showDialog)
+ {
+ progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
+ if (progress)
+ {
+ progress->SetHeading(CVariant{ 20197 }); //"Import music library"
+ progress->SetText(CVariant{ 649 }); //"Importing"
+ progress->SetLine(1, CVariant{ 330 }); //"This could take some time"
+ progress->SetLine(2, CVariant{ "" });
+ progress->SetPercentage(0);
+ progress->Open();
+ progress->ShowProgressBar(true);
+ }
+ }
+
+ CMusicLibraryImportJob* importJob = new CMusicLibraryImportJob(xmlFile, progress);
+ if (showDialog)
+ {
+ AddJob(importJob);
+
+ // Wait for import to complete or be canceled, but render every 10ms so that the
+ // pointer movements work on dialog even when import is reporting progress infrequently
+ if (progress)
+ progress->Wait();
+ }
+ else
+ {
+ m_modal = true;
+ importJob->DoWork();
+
+ delete importJob;
+ m_modal = false;
+ Refresh();
+ }
+}
+
void CMusicLibraryQueue::ScanLibrary(const std::string& strDirectory, int flags /* = 0 */, bool showProgress /* = true */)
{
AddJob(new CMusicLibraryScanningJob(strDirectory, flags, showProgress));
diff --git a/xbmc/music/MusicLibraryQueue.h b/xbmc/music/MusicLibraryQueue.h
index 2ba993e13a..4ccf215524 100644
--- a/xbmc/music/MusicLibraryQueue.h
+++ b/xbmc/music/MusicLibraryQueue.h
@@ -44,6 +44,13 @@ public:
void ExportLibrary(const CLibExportSettings& settings, bool showDialog = false);
/*!
+ \brief Enqueue a music library import job.
+ \param[in] xmlFile xml file to import
+ \param[in] showDialog Show a progress dialog while (asynchronously) exporting, otherwise export in synchronous
+ */
+ void ImportLibrary(const std::string& xmlFile, bool showDialog = false);
+
+ /*!
\brief Enqueue a music library update job, scanning tags embedded in music files and optionally scraping additional data.
\param[in] strDirectory Directory to scan or "" (empty string) for a global scan.
\param[in] flags Flags for controlling the scanning process. See xbmc/music/infoscanner/MusicInfoScanner.h for possible values.
diff --git a/xbmc/music/jobs/CMakeLists.txt b/xbmc/music/jobs/CMakeLists.txt
index 0392781fee..8ee0f5dda6 100644
--- a/xbmc/music/jobs/CMakeLists.txt
+++ b/xbmc/music/jobs/CMakeLists.txt
@@ -2,12 +2,14 @@ set(SOURCES MusicLibraryJob.cpp
MusicLibraryProgressJob.cpp
MusicLibraryCleaningJob.cpp
MusicLibraryExportJob.cpp
+ MusicLibraryImportJob.cpp
MusicLibraryScanningJob.cpp)
set(HEADERS MusicLibraryJob.h
MusicLibraryProgressJob.h
MusicLibraryCleaningJob.h
MusicLibraryExportJob.h
+ MusicLibraryImportJob.h
MusicLibraryScanningJob.h)
core_add_library(music_jobs)
diff --git a/xbmc/music/jobs/MusicLibraryImportJob.cpp b/xbmc/music/jobs/MusicLibraryImportJob.cpp
new file mode 100644
index 0000000000..714e535b6e
--- /dev/null
+++ b/xbmc/music/jobs/MusicLibraryImportJob.cpp
@@ -0,0 +1,41 @@
+/*
+* Copyright (C) 2017-2018 Team Kodi
+* This file is part of Kodi - https://kodi.tv
+*
+* SPDX-License-Identifier: GPL-2.0-or-later
+* See LICENSES/README.md for more information.
+*/
+
+#include "MusicLibraryImportJob.h"
+#include "dialogs/GUIDialogProgress.h"
+#include "music/MusicDatabase.h"
+
+CMusicLibraryImportJob::CMusicLibraryImportJob(const std::string& xmlFile, CGUIDialogProgress* progressDialog)
+ : CMusicLibraryProgressJob(nullptr)
+ , m_xmlFile(xmlFile)
+{
+ if (progressDialog)
+ SetProgressIndicators(nullptr, progressDialog);
+ SetAutoClose(true);
+}
+
+CMusicLibraryImportJob::~CMusicLibraryImportJob() = default;
+
+bool CMusicLibraryImportJob::operator==(const CJob* job) const
+{
+ if (strcmp(job->GetType(), GetType()) != 0)
+ return false;
+
+ const CMusicLibraryImportJob* importJob = dynamic_cast<const CMusicLibraryImportJob*>(job);
+ if (importJob == nullptr)
+ return false;
+
+ return !(m_xmlFile != importJob->m_xmlFile);
+}
+
+bool CMusicLibraryImportJob::Work(CMusicDatabase &db)
+{
+ db.ImportFromXML(m_xmlFile, GetProgressDialog());
+
+ return true;
+}
diff --git a/xbmc/music/jobs/MusicLibraryImportJob.h b/xbmc/music/jobs/MusicLibraryImportJob.h
new file mode 100644
index 0000000000..bcca44f245
--- /dev/null
+++ b/xbmc/music/jobs/MusicLibraryImportJob.h
@@ -0,0 +1,42 @@
+/*
+* Copyright (C) 2017-2018 Team Kodi
+* This file is part of Kodi - https://kodi.tv
+*
+* SPDX-License-Identifier: GPL-2.0-or-later
+* See LICENSES/README.md for more information.
+*/
+
+#pragma once
+
+#include "MusicLibraryProgressJob.h"
+
+class CGUIDialogProgress;
+
+/*!
+\brief Music library job implementation for importing data to the music library.
+*/
+class CMusicLibraryImportJob : public CMusicLibraryProgressJob
+{
+public:
+ /*!
+ \brief Creates a new music library import job for the given xml file.
+
+ \param[in] xmlFile xml file to import
+ \param[in] progressDialog Progress dialog to be used to display the import progress
+ */
+ CMusicLibraryImportJob(const std::string &xmlFile, CGUIDialogProgress* progressDialog);
+
+ ~CMusicLibraryImportJob() override;
+
+ // specialization of CJob
+ const char *GetType() const override { return "MusicLibraryImportJob"; }
+ bool operator==(const CJob* job) const override;
+
+protected:
+ // implementation of CMusicLibraryJob
+ bool Work(CMusicDatabase &db) override;
+
+private:
+ std::string m_xmlFile;
+};
+
diff --git a/xbmc/settings/LibExportSettings.cpp b/xbmc/settings/LibExportSettings.cpp
index f58628d75d..15be272ec4 100644
--- a/xbmc/settings/LibExportSettings.cpp
+++ b/xbmc/settings/LibExportSettings.cpp
@@ -71,7 +71,26 @@ std::vector<int> CLibExportSettings::GetExportItems() const
values.emplace_back(ELIBEXPORT_OTHERARTISTS);
if (IsItemExported(ELIBEXPORT_ACTORTHUMBS))
values.emplace_back(ELIBEXPORT_ACTORTHUMBS);
+ if (IsItemExported(ELIBEXPORT_SONGS))
+ values.emplace_back(ELIBEXPORT_SONGS);
+ return values;
+}
+std::vector<int> CLibExportSettings::GetLimitedItems(int items) const
+{
+ std::vector<int> values;
+ if (IsItemExported(ELIBEXPORT_ALBUMS) && (items & ELIBEXPORT_ALBUMS))
+ values.emplace_back(ELIBEXPORT_ALBUMS);
+ if (IsItemExported(ELIBEXPORT_ALBUMARTISTS) && (items & ELIBEXPORT_ALBUMARTISTS))
+ values.emplace_back(ELIBEXPORT_ALBUMARTISTS);
+ if (IsItemExported(ELIBEXPORT_SONGARTISTS) && (items & ELIBEXPORT_SONGARTISTS))
+ values.emplace_back(ELIBEXPORT_SONGARTISTS);
+ if (IsItemExported(ELIBEXPORT_OTHERARTISTS) && (items & ELIBEXPORT_OTHERARTISTS))
+ values.emplace_back(ELIBEXPORT_OTHERARTISTS);
+ if (IsItemExported(ELIBEXPORT_ACTORTHUMBS) && (items & ELIBEXPORT_ACTORTHUMBS))
+ values.emplace_back(ELIBEXPORT_ACTORTHUMBS);
+ if (IsItemExported(ELIBEXPORT_SONGS) && (items & ELIBEXPORT_SONGS))
+ values.emplace_back(ELIBEXPORT_SONGS);
return values;
}
diff --git a/xbmc/settings/LibExportSettings.h b/xbmc/settings/LibExportSettings.h
index a2e35c01b1..b82f3cc88a 100644
--- a/xbmc/settings/LibExportSettings.h
+++ b/xbmc/settings/LibExportSettings.h
@@ -30,7 +30,8 @@ enum ELIBEXPORTOPTIONS
ELIBEXPORT_ARTWORK = 0x0100,
ELIBEXPORT_NFOFILES = 0x0200,
ELIBEXPORT_ACTORTHUMBS = 0x0400,
- ELIBEXPORT_ARTISTFOLDERS = 0x0800
+ ELIBEXPORT_ARTISTFOLDERS = 0x0800,
+ ELIBEXPORT_SONGS = 0x1000
};
class CLibExportSettings
@@ -43,6 +44,7 @@ public:
bool IsItemExported(ELIBEXPORTOPTIONS item) const;
bool IsArtists() const;
std::vector<int> GetExportItems() const;
+ std::vector<int> GetLimitedItems(int items) const;
void ClearItems() { m_itemstoexport = 0; }
void AddItem(ELIBEXPORTOPTIONS item) { m_itemstoexport += item; }
unsigned int GetItemsToExport() { return m_itemstoexport; }
diff --git a/xbmc/settings/MediaSettings.cpp b/xbmc/settings/MediaSettings.cpp
index 8cccd71c4d..d88829787c 100644
--- a/xbmc/settings/MediaSettings.cpp
+++ b/xbmc/settings/MediaSettings.cpp
@@ -19,7 +19,6 @@
#include "guilib/LocalizeStrings.h"
#include "interfaces/AnnouncementManager.h"
#include "interfaces/builtins/Builtins.h"
-#include "music/MusicDatabase.h"
#include "music/MusicLibraryQueue.h"
#include "messaging/helpers/DialogHelper.h"
#include "ServiceBroker.h"
@@ -315,10 +314,8 @@ void CMediaSettings::OnSettingAction(std::shared_ptr<const CSetting> setting)
if (CGUIDialogFileBrowser::ShowAndGetFile(shares, "musicdb.xml", g_localizeStrings.Get(651) , path))
{
- CMusicDatabase musicdatabase;
- musicdatabase.Open();
- musicdatabase.ImportFromXML(path);
- musicdatabase.Close();
+ // Import data to music library showing progress dialog
+ CMusicLibraryQueue::GetInstance().ImportLibrary(path, true);
}
}
else if (settingId == CSettings::SETTING_VIDEOLIBRARY_CLEANUP)
diff --git a/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp b/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp
index a7d2334ef9..98dac9acc7 100644
--- a/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp
+++ b/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp
@@ -258,10 +258,6 @@ void CGUIDialogLibExportSettings::SetupView()
SET_CONTROL_LABEL(CONTROL_SETTINGS_OKAY_BUTTON, 38319);
SET_CONTROL_LABEL(CONTROL_SETTINGS_CANCEL_BUTTON, 222);
- if (m_settings.IsSeparateFiles())
- ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED, !m_settings.m_skipnfo);
- else if (m_settings.IsToLibFolders())
- ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED, false);
UpdateButtons();
UpdateToggles();
UpdateDescription();
@@ -360,31 +356,39 @@ void CGUIDialogLibExportSettings::InitializeSettings()
entries.clear();
if (!m_settings.IsArtistFoldersOnly())
entries.push_back(std::make_pair(132, ELIBEXPORT_ALBUMS)); //ablums
+ if (m_settings.IsSingleFile())
+ entries.push_back(std::make_pair(134, ELIBEXPORT_SONGS)); //songs
entries.push_back(std::make_pair(38043, ELIBEXPORT_ALBUMARTISTS)); //album artists
entries.push_back(std::make_pair(38312, ELIBEXPORT_SONGARTISTS)); //song artists
entries.push_back(std::make_pair(38313, ELIBEXPORT_OTHERARTISTS)); //other artists
+
+ std::vector<int> items;
if (m_settings.IsArtistFoldersOnly())
{
- std::vector<int> artistitems; // Only artists, not albums
- if (m_settings.IsItemExported(ELIBEXPORT_SONGARTISTS))
- artistitems.emplace_back(ELIBEXPORT_SONGARTISTS);
- if (m_settings.IsItemExported(ELIBEXPORT_OTHERARTISTS))
- artistitems.emplace_back(ELIBEXPORT_OTHERARTISTS);
- if (m_settings.IsItemExported(ELIBEXPORT_ALBUMARTISTS) || (artistitems.size() == 0))
- artistitems.emplace_back(ELIBEXPORT_ALBUMARTISTS);
- AddList(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_ITEMS, 38306, SettingLevel::Basic, artistitems, entries, 133, 1);
+ // Only artists, not albums, at least album artists
+ items = m_settings.GetLimitedItems(ELIBEXPORT_ALBUMARTISTS + ELIBEXPORT_SONGARTISTS + ELIBEXPORT_OTHERARTISTS);
+ if (items.size() == 0)
+ items.emplace_back(ELIBEXPORT_ALBUMARTISTS);
}
- else
+ else if (!m_settings.IsSingleFile())
{
- AddList(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_ITEMS, 38306, SettingLevel::Basic, m_settings.GetExportItems(), entries, 133, 1);
+ // No songs unless single file export, at least album artists
+ items = m_settings.GetLimitedItems(ELIBEXPORT_ALBUMS + ELIBEXPORT_ALBUMARTISTS + ELIBEXPORT_SONGARTISTS + ELIBEXPORT_OTHERARTISTS);
+ if (items.size() == 0)
+ items.emplace_back(ELIBEXPORT_ALBUMARTISTS);
+ }
+ else
+ items = m_settings.GetExportItems();
+
+ AddList(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_ITEMS, 38306, SettingLevel::Basic, items, entries, 133, 1);
- if (!m_settings.IsSingleFile())
- {
- m_settingNFO = AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO, 38309, SettingLevel::Basic, !m_settings.m_skipnfo);
+ if (m_settings.IsToLibFolders() || m_settings.IsSeparateFiles())
+ {
+ m_settingNFO = AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO, 38309, SettingLevel::Basic, !m_settings.m_skipnfo);
+ if (m_settings.IsSeparateFiles())
AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED, 38308, SettingLevel::Basic, m_settings.m_unscraped);
- m_settingArt = AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK, 38307, SettingLevel::Basic, m_settings.m_artwork);
- AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE, 38311, SettingLevel::Basic, m_settings.m_overwrite);
- }
+ m_settingArt = AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK, 38307, SettingLevel::Basic, m_settings.m_artwork);
+ AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE, 38311, SettingLevel::Basic, m_settings.m_overwrite);
}
}