Index: xbmc/lib/libscrobbler/scrobbler.h =================================================================== --- xbmc/lib/libscrobbler/scrobbler.h (revision 16881) +++ xbmc/lib/libscrobbler/scrobbler.h (working copy) @@ -20,6 +20,9 @@ #ifndef _SCROBBLER_H #define _SCROBBLER_H +#include +using namespace std; + namespace MUSIC_INFO { class CMusicInfoTag; @@ -59,6 +62,24 @@ }; +typedef struct SubmissionJournalEntry_s { + CStdString strArtist; + CStdString strAlbum; + CStdString strTitle; + CStdString strLength; + CStdString strStartTime; + CStdString strMusicBrainzID; + SubmissionJournalEntry_s() {}; + SubmissionJournalEntry_s(const struct SubmissionJournalEntry_s& j) { + strArtist = j.strArtist; + strAlbum = j.strAlbum; + strTitle = j.strTitle; + strLength = j.strLength; + strStartTime = j.strStartTime; + strMusicBrainzID = j.strMusicBrainzID; + }; +} SubmissionJournalEntry_t; + /** Audioscrobbler client class. $Id$ @@ -134,13 +155,6 @@ */ void SetUsername(const CStdString& strUser); - /** - Clears the internal submission cache. - - @see setCache() - */ - void ClearCache(); - void SetSubmitSong(bool bSubmit); bool ShouldSubmit(); void SetSongStartTime(); @@ -150,19 +164,6 @@ CStdString GetFilesCached(); void SetSecsTillSubmit(int iSecs); CStdString GetSubmitState(); -protected: - - /** - Sets the contents of the submission cache, to be called from - loadCache(). - - @param cache The contents of the cache to set. - @param numentries The number of entries in the cache. - @see loadCache() - @see clearCache() - */ - void SetCache(const CStdString& strCache, int iNumEntries); - private: /** @@ -194,23 +195,21 @@ /** Override this to save the cache - called from the destructor. - @param cache The contents of the cache to save. - @param numentries The number of entries in the cache - save this too. - @see loadCache() + @see LoadJournal() @retval 1 You saved some cache. @retval 0 You didn't save cache, or can't. */ - virtual int SaveCache(const CStdString& strCache, int iNumEntries); + virtual int SaveJournal(); /** This is called when you should load the cache. Use setCache. - @see saveCache() - @see setCache() + @see SaveJournal() @retval 1 You loaded some cache. @retval 0 There was no cache to load, or you can't. */ virtual int LoadCache(); + virtual int LoadJournal(); virtual void GenSessionKey(); @@ -230,6 +229,7 @@ #endif CStdString GetTempFileName(); + CStdString GetJournalFileName(); CStdString m_strUserName; CStdString m_strPassword; // MD5 hash @@ -265,8 +265,9 @@ int m_iSecsTillSubmit; bool m_bShouldSubmit; bool m_bReHandShaking; + + vector m_vecSubmissionJournal; - static CScrobbler* m_pInstance; }; Index: xbmc/lib/libscrobbler/scrobbler.cpp =================================================================== --- xbmc/lib/libscrobbler/scrobbler.cpp (revision 16881) +++ xbmc/lib/libscrobbler/scrobbler.cpp (working copy) @@ -29,6 +29,9 @@ #include "Application.h" #include "MusicInfoTag.h" #include "FileSystem/File.h" +#include "tinyXML/tinyxml.h" +#include "XMLUtils.h" +#include "RegExp.h" using namespace std; using namespace MUSIC_INFO; @@ -38,6 +41,7 @@ #define MINLENGTH 30 #define MAXLENGTH 10800 #define HS_FAIL_WAIT 100000 +#define MAXSUBMISSIONS 50 //#define CLIENT_ID "tst" #define CLIENT_ID "xbm" @@ -118,15 +122,14 @@ SetPassword(strPassword); SetUsername(strUserName); - ClearCache(); - LoadCache(); + LoadJournal(); DoHandshake(); } void CScrobbler::Term() { m_bReadyToSubmit=false; - SaveCache(m_strPostString.c_str(), m_iSongNum); + SaveJournal(); } void CScrobbler::SetPassword(const CStdString& strPass) @@ -162,18 +165,6 @@ m_strHsString = "http://post.audioscrobbler.com/?hs=true&p=1.1&c=" + m_strClientId + "&v=" + m_strClientVer + "&u=" + m_strUserName; } -void CScrobbler::ClearCache() -{ - m_iSongNum = 0; - m_strPostString = ""; -} - -void CScrobbler::SetCache(const CStdString& strCache, int iNumEntries) -{ - m_strPostString = strCache; - m_iSongNum = iNumEntries; -} - int CScrobbler::AddSong(const CMusicInfoTag& tag) { if ((!g_guiSettings.GetBool("lastfm.enable") && !g_guiSettings.GetBool("lastfm.recordtoprofile")) || !g_guiSettings.GetBool("network.enableinternet")) @@ -195,39 +186,71 @@ struct tm *today = gmtime(&m_SongStartTime); strftime(ti, sizeof(ti), "%Y-%m-%d %H:%M:%S", today); - CStdString a, b, t; + SubmissionJournalEntry_t entry; // our tags are stored as UTF-8, so no conversion needed - a = tag.GetArtist(); - b = tag.GetAlbum(); - t = tag.GetTitle(); - CStdString i=ti; - CStdString m=tag.GetMusicBrainzTrackID(); - CUtil::URLEncode(a); - CUtil::URLEncode(b); - CUtil::URLEncode(t); - CUtil::URLEncode(i); - CUtil::URLEncode(m); + entry.strArtist = tag.GetArtist(); + entry.strAlbum = tag.GetAlbum(); + entry.strTitle = tag.GetTitle(); + entry.strStartTime = ti; + entry.strLength.Format("%d",tag.GetDuration()); + entry.strMusicBrainzID = tag.GetMusicBrainzTrackID(); + m_vecSubmissionJournal.push_back(entry); + SaveJournal(); - CStdString strSubmitStr; - strSubmitStr.Format("a[%i]=%s&t[%i]=%s&b[%i]=%s&m[%i]=%s&l[%i]=%i&i[%i]=%s&", m_iSongNum, a.c_str(), m_iSongNum, t.c_str(), m_iSongNum, b.c_str(), m_iSongNum, m.c_str(), m_iSongNum, tag.GetDuration(), m_iSongNum, i.c_str()); - - if(m_strPostString.find(ti) != m_strPostString.npos) + while ((unsigned)m_iSongNum < m_vecSubmissionJournal.size() && m_iSongNum < MAXSUBMISSIONS) { - // we have already tried to add a song at this time stamp - // I have no idea how this could happen but apparently it does so - // we stop it now + entry = m_vecSubmissionJournal[m_iSongNum]; + CStdString strStartTime = entry.strStartTime; + CUtil::URLEncode(entry.strArtist); + CUtil::URLEncode(entry.strTitle); + CUtil::URLEncode(entry.strAlbum); + CUtil::URLEncode(entry.strMusicBrainzID); + CUtil::URLEncode(entry.strLength); + CUtil::URLEncode(entry.strStartTime); + CStdString strSubmitStr; + strSubmitStr.Format("a[%i]=%s&t[%i]=%s&b[%i]=%s&m[%i]=%s&l[%i]=%s&i[%i]=%s&", + m_iSongNum, + entry.strArtist, + m_iSongNum, + entry.strTitle, + m_iSongNum, + entry.strAlbum, + m_iSongNum, + entry.strMusicBrainzID, + m_iSongNum, + entry.strLength, + m_iSongNum, + entry.strStartTime); - StatusUpdate(S_NOT_SUBMITTING,strSubmitStr); - StatusUpdate(S_NOT_SUBMITTING,m_strPostString); + if(m_strPostString.find(strStartTime) != m_strPostString.npos) + { + // we have already tried to add a song at this time stamp + // I have no idea how this could happen but apparently it does so + // we stop it now - StatusUpdate(S_NOT_SUBMITTING,"Submission error, duplicate subbmission time found"); - return 3; - } + StatusUpdate(S_NOT_SUBMITTING,strSubmitStr); + StatusUpdate(S_NOT_SUBMITTING,m_strPostString); + StatusUpdate(S_NOT_SUBMITTING,"Submission error, duplicate subbmission time found"); + + vector::iterator it = m_vecSubmissionJournal.begin(); + for (int i = 0; i 975) { + printf("%d %s\n", m_strPostString.length(), m_strPostString.c_str()); + printf("%d %s\n", strSubmitStr.length(), strSubmitStr.c_str()); + break; + } + + m_strPostString += strSubmitStr; + m_iSongNum++; + } time_t now; time (&now); @@ -349,10 +372,8 @@ if (stricmp("OK", response) == 0) { StatusUpdate(S_SUBMIT_SUCCESS,"Submission succeeded."); - StatusUpdate(S_DEBUG,m_strPostString.c_str()); - ClearCache(); char *inttext = strtok(NULL, seps); if (inttext && (stricmp("INTERVAL", inttext) == 0)) { @@ -361,6 +382,14 @@ strBuf.Format("Submit interval set to %i seconds.", m_Interval); StatusUpdate(S_SUBMIT_INTERVAL, strBuf); } + + // Remove successfully submitted songs from journal and clear POST data + printf("before: %d %d\n", m_iSongNum, m_vecSubmissionJournal.size()); + while (--m_iSongNum) + m_vecSubmissionJournal.erase(m_vecSubmissionJournal.begin()); + printf("after: %d %d\n", m_iSongNum, m_vecSubmissionJournal.size()); + m_strPostString = ""; + SaveJournal(); } else if (stricmp("BADPASS", response) == 0) { @@ -432,43 +461,117 @@ int CScrobbler::LoadCache() { - StatusUpdate(S_SUBMITTING,"load cache"); - - int iNumEntries=0; - CStdString strCache; - CFile file; if (file.Open(GetTempFileName())) { CArchive ar(&file, CArchive::load); - ar >> iNumEntries; - ar >> strCache; + ar >> m_iSongNum; + ar >> m_strPostString; ar.Close(); file.Close(); - SetCache(strCache, iNumEntries); return 1; } return 0; } -int CScrobbler::SaveCache(const CStdString& strCache, int iNumEntries) -{ - if (iNumEntries<=0) +int CScrobbler::LoadJournal() { + SubmissionJournalEntry_t entry; + m_vecSubmissionJournal.clear(); + + if (LoadCache()) + { + // load old style cache + CLog::Log(LOGDEBUG, "Audioscrobbler: Found old submission cache, converting to submission journal."); + for (int i = 0; i < m_iSongNum; i++) + { + CStdString strRegEx; + strRegEx.Format("a\\[%i\\]=(.*?)&t\\[%i\\]=(.*?)&b\\[%i\\]=(.*?)&m\\[%i\\]=(.*?)&l\\[%i\\]=(.*?)&i\\[%i\\]=(.*?)&", i, i, i, i, i, i); + CRegExp re; + if (!re.RegComp(strRegEx.c_str())) + break; + if (re.RegFind(m_strPostString) < 0) { + continue; + } + entry.strArtist = re.GetReplaceString("\\1"); + entry.strTitle = re.GetReplaceString("\\2"); + entry.strAlbum = re.GetReplaceString("\\3"); + entry.strMusicBrainzID = re.GetReplaceString("\\4"); + entry.strLength = re.GetReplaceString("\\5"); + entry.strStartTime = re.GetReplaceString("\\6"); + CUtil::UrlDecode(entry.strArtist); + CUtil::UrlDecode(entry.strTitle); + CUtil::UrlDecode(entry.strAlbum); + CUtil::UrlDecode(entry.strMusicBrainzID); + CUtil::UrlDecode(entry.strLength); + CUtil::UrlDecode(entry.strStartTime); + m_vecSubmissionJournal.push_back(entry); + } + m_iSongNum = 0; + m_strPostString = ""; + ::DeleteFile(GetTempFileName()); + CLog::Log(LOGDEBUG, "Audioscrobbler: Added %d entries from old cache file (%s) to journal.", m_vecSubmissionJournal.size(), GetTempFileName().c_str()); + } + + TiXmlDocument xmlDoc; + CStdString JournalFileName = GetJournalFileName(); + if (!xmlDoc.LoadFile(JournalFileName)) + { + CLog::Log(LOGDEBUG, "Audioscrobbler: %s, Line %d (%s)", JournalFileName.c_str(), xmlDoc.ErrorRow(), xmlDoc.ErrorDesc()); return 0; + } - CFile file; - if (file.OpenForWrite(GetTempFileName(), true, true)) // overwrite always + TiXmlElement *pRoot = xmlDoc.RootElement(); + if (strcmpi(pRoot->Value(), "asjournal") != 0) { - CArchive ar(&file, CArchive::store); - ar << iNumEntries; - ar << strCache; - ar.Close(); - file.Close(); - return 1; + CLog::Log(LOGDEBUG, "Audioscrobbler: %s missing ", JournalFileName.c_str()); + return 0; } - return 0; + + TiXmlNode *pNode = pRoot->FirstChild("entry"); + while (pNode) + { + XMLUtils::GetString(pNode, "artist", entry.strArtist); + XMLUtils::GetString(pNode, "album", entry.strAlbum); + XMLUtils::GetString(pNode, "title", entry.strTitle); + XMLUtils::GetString(pNode, "length", entry.strLength); + XMLUtils::GetString(pNode, "starttime", entry.strStartTime); + XMLUtils::GetString(pNode, "musicbrainzid", entry.strMusicBrainzID); + m_vecSubmissionJournal.push_back(entry); + pNode = pNode->NextSibling("entry"); + } + + CLog::Log(LOGDEBUG, "Audioscrobbler: Journal loaded with %d entries.", m_vecSubmissionJournal.size()); + return (m_vecSubmissionJournal.size() > 0) ? 1 : 0; } +int CScrobbler::SaveJournal() +{ + TiXmlDocument xmlDoc; + TiXmlDeclaration decl("1.0", "utf-8", "yes"); + xmlDoc.InsertEndChild(decl); + TiXmlElement xmlRootElement("asjournal"); + TiXmlNode *pRoot = xmlDoc.InsertEndChild(xmlRootElement); + if (!pRoot) + return 0; + vector::iterator it = m_vecSubmissionJournal.begin(); + for (; it != m_vecSubmissionJournal.end(); it++) + { + TiXmlElement entryNode("entry"); + TiXmlNode *pNode = pRoot->InsertEndChild(entryNode); + if (!pNode) + return 0; + XMLUtils::SetString(pNode, "artist", it->strArtist); + XMLUtils::SetString(pNode, "album", it->strAlbum); + XMLUtils::SetString(pNode, "title", it->strTitle); + XMLUtils::SetString(pNode, "length", it->strLength); + XMLUtils::SetString(pNode, "starttime", it->strStartTime); + XMLUtils::SetString(pNode, "musicbrainzid", it->strMusicBrainzID); + } + + CStdString FileName = GetJournalFileName(); + return (xmlDoc.SaveFile(FileName)) ? 1 : 0; +} + void CScrobbler::StatusUpdate(ScrobbleStatus status, const CStdString& strText) { switch (status) @@ -541,8 +644,6 @@ if (m_bCloseThread) break; - StatusUpdate(S_DEBUG,"..."); - CHTTP http; CStdString strHtml; bool bSuccess; @@ -566,7 +667,6 @@ LPSTR lphtml=strHtml.GetBuffer(); HandleSubmit(lphtml); strHtml.ReleaseBuffer(); - ::DeleteFile(GetTempFileName()); if (m_bReHandShaking) continue; } } @@ -579,7 +679,6 @@ StatusUpdate(S_CONNECT_ERROR,"Could not connect to server."); } - // OK, if this was a handshake, it failed since m_bReadyToSubmit isn't true. Submissions get cached. while (!m_bReadyToSubmit) { @@ -634,7 +733,7 @@ return strCachedFiles; CStdString strFormat=g_localizeStrings.Get(15210); // Cached %i Songs - strCachedFiles.Format(strFormat.c_str(), m_iSongNum); + strCachedFiles.Format(strFormat.c_str(), m_vecSubmissionJournal.size()); return strCachedFiles; } @@ -683,3 +782,10 @@ CStdString strFileName = g_settings.GetProfileUserDataFolder(); return CUtil::AddFileToFolder(strFileName, "Scrobbler.tmp"); } + +CStdString CScrobbler::GetJournalFileName() +{ + CStdString strFileName = g_settings.GetProfileUserDataFolder(); + return CUtil::AddFileToFolder(strFileName, "Scrobbler.xml"); +} +