[cosmetics] update date in GPL header
[vuplus_xbmc] / xbmc / filesystem / LastFMDirectory.cpp
1 /*
2  *      Copyright (C) 2005-2013 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, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "LastFMDirectory.h"
22 #include "DirectoryCache.h"
23 #include "music/tags/MusicInfoTag.h"
24 #include "URL.h"
25 #include "guilib/GUIWindowManager.h"
26 #include "dialogs/GUIDialogProgress.h"
27 #include "settings/GUISettings.h"
28 #include "FileItem.h"
29 #include "CurlFile.h"
30 #include "utils/StringUtils.h"
31 #include "guilib/LocalizeStrings.h"
32 #include "utils/log.h"
33
34 using namespace MUSIC_INFO;
35 using namespace XFILE;
36
37 #define AUDIOSCROBBLER_BASE_URL      "http://ws.audioscrobbler.com/1.0/"
38
39 CLastFMDirectory::CLastFMDirectory()
40 {
41   m_Error = false;
42   m_Downloaded = false;
43 }
44
45 CLastFMDirectory::~CLastFMDirectory()
46 {
47 }
48
49 CStdString CLastFMDirectory::BuildURLFromInfo()
50 {
51   CStdString strURL = (CStdString)AUDIOSCROBBLER_BASE_URL;
52   strURL += m_objtype + "/" + m_encodedobjname + "/" + m_objrequest + ".xml";
53
54   return strURL;
55 }
56
57 bool CLastFMDirectory::RetrieveList(CStdString url)
58 {
59   m_dlgProgress = (CGUIDialogProgress*)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
60   if (m_dlgProgress)
61   {
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();
69   }
70
71   CThread thread(this, "CLastFMDirectory");
72   m_strSource = url;
73   m_strDestination = "special://temp/lastfm.xml";
74   thread.Create();
75
76   while (!m_Downloaded)
77   {
78     if (m_dlgProgress)
79     {
80       m_dlgProgress->Progress();
81
82       if (m_dlgProgress->IsCanceled())
83       {
84         m_http.Cancel();
85         thread.StopThread();
86         m_dlgProgress->Close();
87         return false;
88       }
89     }
90   }
91
92   if (m_dlgProgress && !m_dlgProgress->IsCanceled() && m_Error)
93   {
94     if (m_dlgProgress) m_dlgProgress->Close();
95     SetErrorDialog(257, 15280, 0, 0);
96     CLog::Log(LOGERROR, "Unable to retrieve list from last.fm");
97     return false;
98   }
99
100
101   if (!m_xmlDoc.LoadFile(m_strDestination))
102   {
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());
106     return false;
107   }
108
109   if (m_dlgProgress) m_dlgProgress->Close();
110
111   return true;
112 }
113
114 void CLastFMDirectory::AddEntry(int iString, CStdString strPath, CStdString strIconPath, bool bFolder, CFileItemList &items)
115 {
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);
123
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)
131   {
132     pItem->SetCanQueue(false);
133     pItem->SetExtraInfo("lastfmitem");
134   }
135
136   items.Add(pItem);
137 }
138
139 void CLastFMDirectory::AddListEntry(const char *name, const char *artist, const char *count, const char *date, const char *icon, CStdString strPath, CFileItemList &items)
140 {
141   CStdString strName;
142   CFileItemPtr pItem(new CFileItem);
143   CMusicInfoTag* musicinfotag = pItem->GetMusicInfoTag();
144   musicinfotag->SetTitle(name);
145
146   if (artist)
147   {
148     strName.Format("%s - %s", artist, name);
149     musicinfotag->SetArtist(artist);
150   }
151   else
152   {
153     strName = name;
154   }
155
156   if (count)
157   {
158     pItem->SetLabel2(count);
159     pItem->m_dwSize = _atoi64(count) * 100000000;
160
161     const char *dot;
162     if ((dot = (const char *)strstr(count, ".")))
163       pItem->m_dwSize += _atoi64(dot + 1);
164   }
165
166   pItem->SetLabel(strName);
167   pItem->SetPath(strPath);
168   pItem->m_bIsFolder = true;
169   pItem->SetLabelPreformated(true);
170
171   if (date)
172   {
173     LONGLONG ll = Int32x32To64(atoi(date), 10000000) + 116444736000000000LL;
174     FILETIME ft;
175
176     ft.dwLowDateTime = (DWORD)(ll & 0xFFFFFFFF);
177     ft.dwHighDateTime = (DWORD)(ll >> 32);
178
179     pItem->m_dateTime=ft;
180   }
181
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")))
185   {
186     if (m_objrequest.Equals("recentbannedtracks"))
187     {
188       pItem->SetExtraInfo("lastfmbanned");
189     }
190     else if (m_objrequest.Equals("recentlovedtracks"))
191     {
192       pItem->SetExtraInfo("lastfmloved");
193     }
194   }
195   if (pItem->GetExtraInfo().IsEmpty() && strPath.Find("lastfm://xbmc") >= 0)
196   {
197     pItem->SetExtraInfo("lastfmitem");
198   }
199
200   // icons? would probably take too long to retrieve them all
201   items.Add(pItem);
202 }
203
204 bool CLastFMDirectory::ParseArtistList(CStdString url, CFileItemList &items)
205 {
206   if (!RetrieveList(url))
207     return false;
208
209   TiXmlElement* pRootElement = m_xmlDoc.RootElement();
210
211   TiXmlElement* pEntry = pRootElement->FirstChildElement("artist");
212
213   while(pEntry)
214   {
215     TiXmlNode* name = pEntry->FirstChild("name");
216     TiXmlNode* count;
217     const char *countstr = NULL;
218     const char *namestr = NULL;
219
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");
225     else if (count)
226       countstr = count->FirstChild()->Value();
227     
228     if (name)
229       namestr = name->FirstChild()->Value();
230     else
231       namestr = pEntry->Attribute("name");
232
233
234     if (namestr && countstr)
235       AddListEntry(namestr, NULL, countstr, NULL, NULL,
236           "lastfm://xbmc/artist/" + (CStdString)namestr + "/", items);
237
238     pEntry = pEntry->NextSiblingElement("artist");
239   }
240
241   m_xmlDoc.Clear();
242   return true;
243 }
244
245 bool CLastFMDirectory::ParseAlbumList(CStdString url, CFileItemList &items)
246 {
247   if (!RetrieveList(url))
248     return false;
249
250   TiXmlElement* pRootElement = m_xmlDoc.RootElement();
251
252   TiXmlElement* pEntry = pRootElement->FirstChildElement("album");
253
254   while(pEntry)
255   {
256     const char *artist = pRootElement->Attribute("artist");
257     const char *name = NULL;
258     const char *count = NULL;
259
260     if (pEntry->Attribute("name"))
261       name = pEntry->Attribute("name");
262     else
263     {
264       TiXmlNode* nameNode = pEntry->FirstChild("name");
265       if (nameNode && nameNode->FirstChild())
266         name = nameNode->FirstChild()->Value();
267     }
268
269     TiXmlElement* artistElement = pEntry->FirstChildElement("artist");
270     if (artistElement && artistElement->Attribute("name"))
271       artist = artistElement->Attribute("name");
272     else
273     {
274       if (artistElement && artistElement->FirstChild())
275         artist = artistElement->FirstChild()->Value();
276     }
277
278     if (pEntry->Attribute("count"))
279       count = pEntry->Attribute("count");
280     else
281     {
282       TiXmlNode* countNode = pEntry->FirstChild("count");
283       if (!countNode) countNode = pEntry->FirstChild("playcount");
284       if (!countNode) countNode = pEntry->FirstChild("reach");
285       if (countNode)
286         count = countNode->FirstChild()->Value();
287     }
288
289     AddListEntry(name, artist, count, NULL, NULL,
290         "lastfm://xbmc/artist/" + (CStdString)artist + "/", items);
291
292     pEntry = pEntry->NextSiblingElement("album");
293   }
294
295   m_xmlDoc.Clear();
296   return true;
297 }
298
299 bool CLastFMDirectory::ParseUserList(CStdString url, CFileItemList &items)
300 {
301   if (!RetrieveList(url))
302     return false;
303
304   TiXmlElement* pRootElement = m_xmlDoc.RootElement();
305
306   TiXmlElement* pEntry = pRootElement->FirstChildElement("user");
307
308   while(pEntry)
309   {
310     const char *name = pEntry->Attribute("username");
311
312     TiXmlNode* count;
313     count = pEntry->FirstChild("weight");
314     if (!count) count = pEntry->FirstChild("match");
315
316     if (name)
317     {
318       AddListEntry(name, NULL, (count && count->FirstChild()) ? count->FirstChild()->Value() : NULL, NULL, NULL,
319           "lastfm://xbmc/user/" + (CStdString)name + "/", items);
320     }
321
322     pEntry = pEntry->NextSiblingElement("user");
323   }
324
325   m_xmlDoc.Clear();
326   return true;
327 }
328
329 bool CLastFMDirectory::ParseTagList(CStdString url, CFileItemList &items)
330 {
331   if (!RetrieveList(url))
332     return false;
333
334   TiXmlElement* pRootElement = m_xmlDoc.RootElement();
335
336   TiXmlElement* pEntry = pRootElement->FirstChildElement("tag");
337
338   while(pEntry)
339   {
340     TiXmlNode* name = pEntry->FirstChild("name");
341     TiXmlNode* count;
342     const char *countstr = NULL;
343     const char *namestr = NULL;
344
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();
352
353     if (name && name->FirstChild())
354       namestr = name->FirstChild()->Value();
355     else
356       namestr = pEntry->Attribute("name");
357
358     if (namestr && countstr)
359     {
360       AddListEntry(namestr, NULL, countstr, NULL, NULL,
361           "lastfm://xbmc/tag/" + (CStdString)namestr + "/", items);
362     }
363
364     pEntry = pEntry->NextSiblingElement("tag");
365   }
366
367   m_xmlDoc.Clear();
368   return true;
369 }
370
371 bool CLastFMDirectory::ParseTrackList(CStdString url, CFileItemList &items)
372 {
373   if (!RetrieveList(url))
374     return false;
375
376   TiXmlElement* pRootElement = m_xmlDoc.RootElement();
377
378   TiXmlElement* pEntry = pRootElement->FirstChildElement("track");
379
380   while(pEntry)
381   {
382     TiXmlNode* name = pEntry->FirstChild("name");
383     TiXmlNode* artist = pEntry->FirstChild("artist");
384     TiXmlElement *date = pEntry->FirstChildElement("date");
385
386     TiXmlNode* count;
387     count = pEntry->FirstChild("count");
388     if (!count) count = pEntry->FirstChild("playcount");
389     if (!count) count = pEntry->FirstChild("match");
390
391     if (name)
392     {
393       if (artist)
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);
399       else
400         // no artist in xml, assuming we're retrieving track list for the artist in m_objname...
401         AddListEntry(name->FirstChild()->Value(),
402             m_objname.c_str(),
403             (count) ? count->FirstChild()->Value() : NULL,
404             NULL, NULL, "lastfm://xbmc/artist/" + m_objname + "/", items);
405     }
406     else
407     {
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");
412
413       if (name)
414         AddListEntry(name, artist, count, NULL, NULL,
415             "lastfm://xbmc/artist/" + (CStdString)artist + "/", items);
416     }
417
418     pEntry = pEntry->NextSiblingElement("track");
419   }
420
421   m_xmlDoc.Clear();
422   return true;
423 }
424
425 bool CLastFMDirectory::SearchSimilarArtists(CFileItemList &items)
426 {
427   CStdString strSearchTerm = "";
428
429   if (!GetKeyboardInput(15281, strSearchTerm))
430     return false;
431
432   m_objname = m_encodedobjname = strSearchTerm;
433   CURL::Encode(m_encodedobjname);
434   CURL::Decode(m_objname);
435
436   AddEntry(15267, "lastfm://artist/%name%/similarartists", "", false, items);
437   return ParseArtistList(BuildURLFromInfo(), items);
438 }
439
440 bool CLastFMDirectory::SearchSimilarTags(CFileItemList &items)
441 {
442   CStdString strSearchTerm = "";
443
444   if (!GetKeyboardInput(15282, strSearchTerm))
445     return false;
446
447   m_objname = m_encodedobjname = strSearchTerm;
448   CURL::Encode(m_encodedobjname);
449   CURL::Decode(m_objname);
450
451   return ParseTagList(BuildURLFromInfo(), items);
452 }
453
454 bool CLastFMDirectory::GetArtistInfo(CFileItemList &items)
455 {
456   if (m_objname == "*" && m_objrequest == "similar")
457     return SearchSimilarArtists(items);
458
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 == "")
470   {
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);
476   }
477   else
478     return false;
479
480   return true;
481 }
482
483 bool CLastFMDirectory::GetUserInfo(CFileItemList &items)
484 {
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 == "")
512   {
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);
525   }
526   else
527     return false;
528
529   return true;
530 }
531
532 bool CLastFMDirectory::GetTagInfo(CFileItemList &items)
533 {
534   if (m_objname == "*" && m_objrequest== "search")
535     return SearchSimilarTags(items);
536
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 == "")
546   {
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);
550   }
551
552   return true;
553 }
554
555 bool CLastFMDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
556 {
557   CStdString strURL = strPath;
558   CURL url(strURL);
559   strURL=url.GetFileName();
560
561   // parse the URL, finding object type, name, and requested info
562   CStdStringArray vecURLParts;
563
564   m_objtype = "";
565   m_objname = "";
566   m_objrequest = "";
567
568   switch(StringUtils::SplitString(strURL, "/", vecURLParts))
569   {
570   case 1:
571     // simple lastfm:// root URL...
572     g_directoryCache.ClearSubPaths("lastfm://");
573     break;
574   // the following fallthru's are on purpose
575   case 5:
576     m_objrequest = vecURLParts[3];
577   case 4:
578     m_objname = vecURLParts[2];
579     m_encodedobjname = vecURLParts[2];
580     CURL::Encode(m_encodedobjname);
581     CURL::Decode(m_objname);
582   case 3:
583     m_objtype = vecURLParts[1];
584   case 2:
585     if (vecURLParts[0] != "xbmc")
586       return false;
587     break;
588   default:
589     return false;
590   }
591
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 == "")
599   {
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") != "")
604     {
605       m_encodedobjname = m_objname = g_guiSettings.GetString("scrobbler.lastfmusername");
606       CURL::Decode(m_encodedobjname);
607       AddEntry(15255, "lastfm://xbmc/user/%name%/", "", true, items);
608     }
609     return true;
610   }
611   else
612     return false;
613
614   return m_Error;
615 }
616
617 DIR_CACHE_TYPE CLastFMDirectory::GetCacheType(const CStdString& strPath) const
618 {
619   if (strPath == "lastfm://")
620     return DIR_CACHE_ONCE;
621   return DIR_CACHE_ALWAYS;
622 }
623
624 void CLastFMDirectory::Run()
625 {
626   XFILE::CCurlFile http;
627   if (!http.Download(m_strSource, m_strDestination))
628     m_Error=true;
629
630   m_Downloaded=true;
631 }