2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "LastFMDirectory.h"
22 #include "DirectoryCache.h"
23 #include "music/tags/MusicInfoTag.h"
25 #include "guilib/GUIWindowManager.h"
26 #include "dialogs/GUIDialogProgress.h"
27 #include "settings/GUISettings.h"
30 #include "utils/StringUtils.h"
31 #include "guilib/LocalizeStrings.h"
32 #include "utils/log.h"
34 using namespace MUSIC_INFO;
35 using namespace XFILE;
37 #define AUDIOSCROBBLER_BASE_URL "http://ws.audioscrobbler.com/1.0/"
39 CLastFMDirectory::CLastFMDirectory()
45 CLastFMDirectory::~CLastFMDirectory()
49 CStdString CLastFMDirectory::BuildURLFromInfo()
51 CStdString strURL = (CStdString)AUDIOSCROBBLER_BASE_URL;
52 strURL += m_objtype + "/" + m_encodedobjname + "/" + m_objrequest + ".xml";
57 bool CLastFMDirectory::RetrieveList(CStdString url)
59 m_dlgProgress = (CGUIDialogProgress*)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
62 m_dlgProgress->ShowProgressBar(false);
63 m_dlgProgress->SetHeading(2);
64 m_dlgProgress->SetLine(0, 15279);
65 m_dlgProgress->SetLine(1, m_objrequest);
66 m_dlgProgress->SetLine(2, m_objname);
67 m_dlgProgress->StartModal();
68 m_dlgProgress->Progress();
71 CThread thread(this, "CLastFMDirectory");
73 m_strDestination = "special://temp/lastfm.xml";
80 m_dlgProgress->Progress();
82 if (m_dlgProgress->IsCanceled())
86 m_dlgProgress->Close();
92 if (m_dlgProgress && !m_dlgProgress->IsCanceled() && m_Error)
94 if (m_dlgProgress) m_dlgProgress->Close();
95 SetErrorDialog(257, 15280, 0, 0);
96 CLog::Log(LOGERROR, "Unable to retrieve list from last.fm");
101 if (!m_xmlDoc.LoadFile(m_strDestination))
103 if (m_dlgProgress) m_dlgProgress->Close();
104 SetErrorDialog(257, 15280, 0, 0);
105 CLog::Log(LOGERROR, "Error parsing file from audioscrobbler web services, Line %d\n%s", m_xmlDoc.ErrorRow(), m_xmlDoc.ErrorDesc());
109 if (m_dlgProgress) m_dlgProgress->Close();
114 void CLastFMDirectory::AddEntry(int iString, CStdString strPath, CStdString strIconPath, bool bFolder, CFileItemList &items)
116 CStdString strLabel = g_localizeStrings.Get(iString);
117 strLabel.Replace("%name%", m_objname);
118 strLabel.Replace("%type%", m_objtype);
119 strLabel.Replace("%request%", m_objrequest);
120 strPath.Replace("%name%", m_encodedobjname);
121 strPath.Replace("%type%", m_objtype);
122 strPath.Replace("%request%", m_objrequest);
124 CFileItemPtr pItem(new CFileItem);
125 pItem->SetLabel(strLabel);
126 pItem->SetPath(strPath);
127 pItem->m_bIsFolder = bFolder;
128 pItem->SetLabelPreformated(true);
129 //the extra info is used in the mediawindows to determine which items are needed in the contextmenu
130 if (strPath.Find("lastfm://xbmc") >= 0)
132 pItem->SetCanQueue(false);
133 pItem->SetExtraInfo("lastfmitem");
139 void CLastFMDirectory::AddListEntry(const char *name, const char *artist, const char *count, const char *date, const char *icon, CStdString strPath, CFileItemList &items)
142 CFileItemPtr pItem(new CFileItem);
143 CMusicInfoTag* musicinfotag = pItem->GetMusicInfoTag();
144 musicinfotag->SetTitle(name);
148 strName.Format("%s - %s", artist, name);
149 musicinfotag->SetArtist(artist);
158 pItem->SetLabel2(count);
159 pItem->m_dwSize = _atoi64(count) * 100000000;
162 if ((dot = (const char *)strstr(count, ".")))
163 pItem->m_dwSize += _atoi64(dot + 1);
166 pItem->SetLabel(strName);
167 pItem->SetPath(strPath);
168 pItem->m_bIsFolder = true;
169 pItem->SetLabelPreformated(true);
173 LONGLONG ll = Int32x32To64(atoi(date), 10000000) + 116444736000000000LL;
176 ft.dwLowDateTime = (DWORD)(ll & 0xFFFFFFFF);
177 ft.dwHighDateTime = (DWORD)(ll >> 32);
179 pItem->m_dateTime=ft;
182 pItem->SetCanQueue(false);
183 //the extra info is used in the mediawindows to determine which items are needed in the contextmenu
184 if (m_objname.Equals(g_guiSettings.GetString("scrobbler.lastfmusername")))
186 if (m_objrequest.Equals("recentbannedtracks"))
188 pItem->SetExtraInfo("lastfmbanned");
190 else if (m_objrequest.Equals("recentlovedtracks"))
192 pItem->SetExtraInfo("lastfmloved");
195 if (pItem->GetExtraInfo().IsEmpty() && strPath.Find("lastfm://xbmc") >= 0)
197 pItem->SetExtraInfo("lastfmitem");
200 // icons? would probably take too long to retrieve them all
204 bool CLastFMDirectory::ParseArtistList(CStdString url, CFileItemList &items)
206 if (!RetrieveList(url))
209 TiXmlElement* pRootElement = m_xmlDoc.RootElement();
211 TiXmlElement* pEntry = pRootElement->FirstChildElement("artist");
215 TiXmlNode* name = pEntry->FirstChild("name");
217 const char *countstr = NULL;
218 const char *namestr = NULL;
220 count = pEntry->FirstChild("count");
221 if (!count) count = pEntry->FirstChild("playcount");
222 if (!count) count = pEntry->FirstChild("match");
223 if (!count && pEntry->Attribute("count"))
224 countstr = pEntry->Attribute("count");
226 countstr = count->FirstChild()->Value();
229 namestr = name->FirstChild()->Value();
231 namestr = pEntry->Attribute("name");
234 if (namestr && countstr)
235 AddListEntry(namestr, NULL, countstr, NULL, NULL,
236 "lastfm://xbmc/artist/" + (CStdString)namestr + "/", items);
238 pEntry = pEntry->NextSiblingElement("artist");
245 bool CLastFMDirectory::ParseAlbumList(CStdString url, CFileItemList &items)
247 if (!RetrieveList(url))
250 TiXmlElement* pRootElement = m_xmlDoc.RootElement();
252 TiXmlElement* pEntry = pRootElement->FirstChildElement("album");
256 const char *artist = pRootElement->Attribute("artist");
257 const char *name = NULL;
258 const char *count = NULL;
260 if (pEntry->Attribute("name"))
261 name = pEntry->Attribute("name");
264 TiXmlNode* nameNode = pEntry->FirstChild("name");
265 if (nameNode && nameNode->FirstChild())
266 name = nameNode->FirstChild()->Value();
269 TiXmlElement* artistElement = pEntry->FirstChildElement("artist");
270 if (artistElement && artistElement->Attribute("name"))
271 artist = artistElement->Attribute("name");
274 if (artistElement && artistElement->FirstChild())
275 artist = artistElement->FirstChild()->Value();
278 if (pEntry->Attribute("count"))
279 count = pEntry->Attribute("count");
282 TiXmlNode* countNode = pEntry->FirstChild("count");
283 if (!countNode) countNode = pEntry->FirstChild("playcount");
284 if (!countNode) countNode = pEntry->FirstChild("reach");
286 count = countNode->FirstChild()->Value();
289 AddListEntry(name, artist, count, NULL, NULL,
290 "lastfm://xbmc/artist/" + (CStdString)artist + "/", items);
292 pEntry = pEntry->NextSiblingElement("album");
299 bool CLastFMDirectory::ParseUserList(CStdString url, CFileItemList &items)
301 if (!RetrieveList(url))
304 TiXmlElement* pRootElement = m_xmlDoc.RootElement();
306 TiXmlElement* pEntry = pRootElement->FirstChildElement("user");
310 const char *name = pEntry->Attribute("username");
313 count = pEntry->FirstChild("weight");
314 if (!count) count = pEntry->FirstChild("match");
318 AddListEntry(name, NULL, (count && count->FirstChild()) ? count->FirstChild()->Value() : NULL, NULL, NULL,
319 "lastfm://xbmc/user/" + (CStdString)name + "/", items);
322 pEntry = pEntry->NextSiblingElement("user");
329 bool CLastFMDirectory::ParseTagList(CStdString url, CFileItemList &items)
331 if (!RetrieveList(url))
334 TiXmlElement* pRootElement = m_xmlDoc.RootElement();
336 TiXmlElement* pEntry = pRootElement->FirstChildElement("tag");
340 TiXmlNode* name = pEntry->FirstChild("name");
342 const char *countstr = NULL;
343 const char *namestr = NULL;
345 count = pEntry->FirstChild("count");
346 if (!count) count = pEntry->FirstChild("playcount");
347 if (!count) count = pEntry->FirstChild("match");
348 if (!count && pEntry->Attribute("count"))
349 countstr = pEntry->Attribute("count");
350 else if (count && count->FirstChild())
351 countstr = count->FirstChild()->Value();
353 if (name && name->FirstChild())
354 namestr = name->FirstChild()->Value();
356 namestr = pEntry->Attribute("name");
358 if (namestr && countstr)
360 AddListEntry(namestr, NULL, countstr, NULL, NULL,
361 "lastfm://xbmc/tag/" + (CStdString)namestr + "/", items);
364 pEntry = pEntry->NextSiblingElement("tag");
371 bool CLastFMDirectory::ParseTrackList(CStdString url, CFileItemList &items)
373 if (!RetrieveList(url))
376 TiXmlElement* pRootElement = m_xmlDoc.RootElement();
378 TiXmlElement* pEntry = pRootElement->FirstChildElement("track");
382 TiXmlNode* name = pEntry->FirstChild("name");
383 TiXmlNode* artist = pEntry->FirstChild("artist");
384 TiXmlElement *date = pEntry->FirstChildElement("date");
387 count = pEntry->FirstChild("count");
388 if (!count) count = pEntry->FirstChild("playcount");
389 if (!count) count = pEntry->FirstChild("match");
394 AddListEntry(name->FirstChild()->Value(),
395 artist->FirstChild()->Value(),
396 (count) ? count->FirstChild()->Value() : ((date) ? date->FirstChild()->Value() : NULL),
397 (date) ? date->Attribute("uts") : NULL,
398 NULL, "lastfm://xbmc/artist/" + (CStdString)artist->FirstChild()->Value() + "/", items);
400 // no artist in xml, assuming we're retrieving track list for the artist in m_objname...
401 AddListEntry(name->FirstChild()->Value(),
403 (count) ? count->FirstChild()->Value() : NULL,
404 NULL, NULL, "lastfm://xbmc/artist/" + m_objname + "/", items);
408 // no luck, try another way :)
409 const char *name = pEntry->Attribute("name");
410 const char *artist = pEntry->FirstChildElement("artist")->Attribute("name");
411 const char *count = pEntry->Attribute("count");
414 AddListEntry(name, artist, count, NULL, NULL,
415 "lastfm://xbmc/artist/" + (CStdString)artist + "/", items);
418 pEntry = pEntry->NextSiblingElement("track");
425 bool CLastFMDirectory::SearchSimilarArtists(CFileItemList &items)
427 CStdString strSearchTerm = "";
429 if (!GetKeyboardInput(15281, strSearchTerm))
432 m_objname = m_encodedobjname = strSearchTerm;
433 CURL::Encode(m_encodedobjname);
434 CURL::Decode(m_objname);
436 AddEntry(15267, "lastfm://artist/%name%/similarartists", "", false, items);
437 return ParseArtistList(BuildURLFromInfo(), items);
440 bool CLastFMDirectory::SearchSimilarTags(CFileItemList &items)
442 CStdString strSearchTerm = "";
444 if (!GetKeyboardInput(15282, strSearchTerm))
447 m_objname = m_encodedobjname = strSearchTerm;
448 CURL::Encode(m_encodedobjname);
449 CURL::Decode(m_objname);
451 return ParseTagList(BuildURLFromInfo(), items);
454 bool CLastFMDirectory::GetArtistInfo(CFileItemList &items)
456 if (m_objname == "*" && m_objrequest == "similar")
457 return SearchSimilarArtists(items);
459 if (m_objrequest == "similar")
460 return ParseArtistList(BuildURLFromInfo(), items);
461 else if (m_objrequest == "topalbums")
462 return ParseAlbumList(BuildURLFromInfo(), items);
463 else if (m_objrequest == "toptracks")
464 return ParseTrackList(BuildURLFromInfo(), items);
465 else if (m_objrequest == "toptags")
466 return ParseTagList(BuildURLFromInfo(), items);
467 else if (m_objrequest == "fans")
468 return ParseUserList(BuildURLFromInfo(), items);
469 else if (m_objrequest == "")
471 AddEntry(15261, "lastfm://xbmc/artist/%name%/similar/", "", true, items);
472 AddEntry(15262, "lastfm://xbmc/artist/%name%/topalbums/", "", true, items);
473 AddEntry(15263, "lastfm://xbmc/artist/%name%/toptracks/", "", true, items);
474 AddEntry(15264, "lastfm://xbmc/artist/%name%/toptags/", "", true, items);
475 AddEntry(15265, "lastfm://xbmc/artist/%name%/fans/", "", true, items);
483 bool CLastFMDirectory::GetUserInfo(CFileItemList &items)
485 if (m_objrequest == "topartists")
486 return ParseArtistList(BuildURLFromInfo(), items);
487 else if (m_objrequest == "topalbums")
488 return ParseAlbumList(BuildURLFromInfo(), items);
489 else if (m_objrequest == "toptracks")
490 return ParseTrackList(BuildURLFromInfo(), items);
491 else if (m_objrequest == "toptags")
492 return ParseTagList(BuildURLFromInfo(), items);
493 else if (m_objrequest == "tags")
494 return ParseTagList(BuildURLFromInfo(), items);
495 else if (m_objrequest == "friends")
496 return ParseUserList(BuildURLFromInfo(), items);
497 else if (m_objrequest == "neighbours")
498 return ParseUserList(BuildURLFromInfo(), items);
499 else if (m_objrequest == "weeklyartistchart")
500 return ParseArtistList(BuildURLFromInfo(), items);
501 else if (m_objrequest == "weeklyalbumchart")
502 return ParseAlbumList(BuildURLFromInfo(), items);
503 else if (m_objrequest == "weeklytrackchart")
504 return ParseTrackList(BuildURLFromInfo(), items);
505 else if (m_objrequest == "recenttracks")
506 return ParseTrackList(BuildURLFromInfo(), items);
507 else if (m_objrequest == "recentlovedtracks")
508 return ParseTrackList(BuildURLFromInfo(), items);
509 else if (m_objrequest == "recentbannedtracks")
510 return ParseTrackList(BuildURLFromInfo(), items);
511 else if (m_objrequest == "")
513 AddEntry(15268, "lastfm://xbmc/user/%name%/topartists/", "", true, items);
514 AddEntry(15269, "lastfm://xbmc/user/%name%/topalbums/", "", true, items);
515 AddEntry(15270, "lastfm://xbmc/user/%name%/toptracks/", "", true, items);
516 AddEntry(15285, "lastfm://xbmc/user/%name%/tags/", "", true, items);
517 AddEntry(15271, "lastfm://xbmc/user/%name%/friends/", "", true, items);
518 AddEntry(15272, "lastfm://xbmc/user/%name%/neighbours/", "", true, items);
519 AddEntry(15273, "lastfm://xbmc/user/%name%/weeklyartistchart/", "", true, items);
520 AddEntry(15274, "lastfm://xbmc/user/%name%/weeklyalbumchart/", "", true, items);
521 AddEntry(15275, "lastfm://xbmc/user/%name%/weeklytrackchart/", "", true, items);
522 AddEntry(15283, "lastfm://xbmc/user/%name%/recenttracks/", "", true, items);
523 AddEntry(15293, "lastfm://xbmc/user/%name%/recentlovedtracks/", "", true, items);
524 AddEntry(15294, "lastfm://xbmc/user/%name%/recentbannedtracks/", "", true, items);
532 bool CLastFMDirectory::GetTagInfo(CFileItemList &items)
534 if (m_objname == "*" && m_objrequest== "search")
535 return SearchSimilarTags(items);
537 if (m_objrequest == "topartists")
538 return ParseArtistList(BuildURLFromInfo(), items);
539 else if (m_objrequest == "topalbums")
540 return ParseAlbumList(BuildURLFromInfo(), items);
541 else if (m_objrequest == "toptracks")
542 return ParseTrackList(BuildURLFromInfo(), items);
543 else if (m_objrequest == "toptags")
544 return ParseTagList(BuildURLFromInfo(), items);
545 else if (m_objrequest == "")
547 AddEntry(15257, "lastfm://xbmc/tag/%name%/topartists/", "", true, items);
548 AddEntry(15258, "lastfm://xbmc/tag/%name%/topalbums/", "", true, items);
549 AddEntry(15259, "lastfm://xbmc/tag/%name%/toptracks/", "", true, items);
555 bool CLastFMDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
557 CStdString strURL = strPath;
559 strURL=url.GetFileName();
561 // parse the URL, finding object type, name, and requested info
562 CStdStringArray vecURLParts;
568 switch(StringUtils::SplitString(strURL, "/", vecURLParts))
571 // simple lastfm:// root URL...
572 g_directoryCache.ClearSubPaths("lastfm://");
574 // the following fallthru's are on purpose
576 m_objrequest = vecURLParts[3];
578 m_objname = vecURLParts[2];
579 m_encodedobjname = vecURLParts[2];
580 CURL::Encode(m_encodedobjname);
581 CURL::Decode(m_objname);
583 m_objtype = vecURLParts[1];
585 if (vecURLParts[0] != "xbmc")
592 if (m_objtype == "user")
593 m_Error = GetUserInfo(items);
594 else if (m_objtype == "tag")
595 m_Error = GetTagInfo(items);
596 else if (m_objtype == "artist")
597 m_Error = GetArtistInfo(items);
598 else if (m_objtype == "")
600 AddEntry(15253, "lastfm://xbmc/artist/*/similar/", "", true, items);
601 AddEntry(15254, "lastfm://xbmc/tag/*/search/", "", true, items);
602 AddEntry(15256, "lastfm://xbmc/tag/xbmc/toptags/", "", true, items);
603 if (g_guiSettings.GetString("scrobbler.lastfmusername") != "")
605 m_encodedobjname = m_objname = g_guiSettings.GetString("scrobbler.lastfmusername");
606 CURL::Decode(m_encodedobjname);
607 AddEntry(15255, "lastfm://xbmc/user/%name%/", "", true, items);
617 DIR_CACHE_TYPE CLastFMDirectory::GetCacheType(const CStdString& strPath) const
619 if (strPath == "lastfm://")
620 return DIR_CACHE_ONCE;
621 return DIR_CACHE_ALWAYS;
624 void CLastFMDirectory::Run()
626 XFILE::CCurlFile http;
627 if (!http.Download(m_strSource, m_strDestination))