[cstdstring] demise Format, replacing with StringUtils::Format
[vuplus_xbmc] / xbmc / interfaces / legacy / ListItem.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://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 <sstream>
22
23 #include "ListItem.h"
24 #include "AddonUtils.h"
25
26 #include "video/VideoInfoTag.h"
27 #include "music/tags/MusicInfoTag.h"
28 #include "pictures/PictureInfoTag.h"
29 #include "utils/log.h"
30 #include "utils/Variant.h"
31 #include "utils/StringUtils.h"
32 #include "settings/AdvancedSettings.h"
33
34 namespace XBMCAddon
35 {
36   namespace xbmcgui
37   {
38     ListItem::ListItem(const String& label, 
39                        const String& label2,
40                        const String& iconImage,
41                        const String& thumbnailImage,
42                        const String& path)
43     {
44       item.reset();
45
46       // create CFileItem
47       item.reset(new CFileItem());
48       if (!item) // not sure if this is really possible
49         return;
50
51       if (!label.empty())
52         item->SetLabel( label );
53       if (!label2.empty())
54         item->SetLabel2( label2 );
55       if (!iconImage.empty())
56         item->SetIconImage( iconImage );
57       if (!thumbnailImage.empty())
58         item->SetArt("thumb",  thumbnailImage );
59       if (!path.empty())
60         item->SetPath(path);
61     }
62
63     ListItem::~ListItem()
64     {
65       item.reset();
66     }
67
68     String ListItem::getLabel()
69     {
70       if (!item) return "";
71
72       String ret;
73       {
74         LOCKGUI;
75         ret = item->GetLabel();
76       }
77
78       return ret;
79     }
80
81     String ListItem::getLabel2()
82     {
83       if (!item) return "";
84
85       String ret;
86       {
87         LOCKGUI;
88         ret = item->GetLabel2();
89       }
90
91       return ret;
92     }
93
94     void ListItem::setLabel(const String& label)
95     {
96       if (!item) return;
97       // set label
98       {
99         LOCKGUI;
100         item->SetLabel(label);
101       }
102     }
103
104     void ListItem::setLabel2(const String& label)
105     {
106       if (!item) return;
107       // set label
108       {
109         LOCKGUI;
110         item->SetLabel2(label);
111       }
112     }
113
114     void ListItem::setIconImage(const String& iconImage)
115     {
116       if (!item) return;
117       {
118         LOCKGUI;
119         item->SetIconImage(iconImage);
120       }
121     }
122
123     void ListItem::setThumbnailImage(const String& thumbFilename)
124     {
125       if (!item) return;
126       {
127         LOCKGUI;
128         item->SetArt("thumb", thumbFilename);
129       }
130     }
131
132     void ListItem::select(bool selected)
133     {
134       if (!item) return;
135       {
136         LOCKGUI;
137         item->Select(selected);
138       }
139     }
140
141
142     bool ListItem::isSelected()
143     {
144       if (!item) return false;
145
146       bool ret;
147       {
148         LOCKGUI;
149         ret = item->IsSelected();
150       }
151
152       return ret;
153     }
154
155     void ListItem::setProperty(const char * key, const String& value)
156     {
157       LOCKGUI;
158       CStdString lowerKey = key;
159       if (lowerKey.CompareNoCase("startoffset") == 0)
160       { // special case for start offset - don't actually store in a property,
161         // we store it in item.m_lStartOffset instead
162         item->m_lStartOffset = (int)(atof(value.c_str()) * 75.0); // we store the offset in frames, or 1/75th of a second
163       }
164       else if (lowerKey.CompareNoCase("mimetype") == 0)
165       { // special case for mime type - don't actually stored in a property,
166         item->SetMimeType(value.c_str());
167       }
168       else if (lowerKey.CompareNoCase("totaltime") == 0)
169         item->GetVideoInfoTag()->m_resumePoint.totalTimeInSeconds = (float)atof(value.c_str());
170       else if (lowerKey.CompareNoCase("resumetime") == 0)
171         item->GetVideoInfoTag()->m_resumePoint.timeInSeconds = (float)atof(value.c_str());
172       else if (lowerKey.CompareNoCase("specialsort") == 0)
173       {
174         if (value == "bottom")
175           item->SetSpecialSort(SortSpecialOnBottom);
176         else if (value == "top")
177           item->SetSpecialSort(SortSpecialOnTop);
178       }
179       else if (lowerKey.CompareNoCase("fanart_image") == 0)
180         item->SetArt("fanart", value);
181       else
182         item->SetProperty(lowerKey.ToLower(), value.c_str());
183     }
184
185     String ListItem::getProperty(const char* key)
186     {
187       LOCKGUI;
188       CStdString lowerKey = key;
189       CStdString value;
190       if (lowerKey.CompareNoCase("startoffset") == 0)
191       { // special case for start offset - don't actually store in a property,
192         // we store it in item.m_lStartOffset instead
193         value = StringUtils::Format("%f", item->m_lStartOffset / 75.0);
194       }
195       else if (lowerKey.CompareNoCase("totaltime") == 0)
196         value = StringUtils::Format("%f", item->GetVideoInfoTag()->m_resumePoint.totalTimeInSeconds);
197       else if (lowerKey.CompareNoCase("resumetime") == 0)
198         value = StringUtils::Format("%f", item->GetVideoInfoTag()->m_resumePoint.timeInSeconds);
199       else if (lowerKey.CompareNoCase("fanart_image") == 0)
200         value = item->GetArt("fanart");
201       else
202         value = item->GetProperty(lowerKey.ToLower()).asString();
203
204       return value.c_str();
205     }
206
207     void ListItem::setPath(const String& path)
208     {
209       LOCKGUI;
210       item->SetPath(path);
211     }
212
213     void ListItem::setMimeType(const String& mimetype)
214     {
215       LOCKGUI;
216       item->SetMimeType(mimetype);
217     }
218
219     String ListItem::getdescription()
220     {
221       return item->GetLabel();
222     }
223
224     String ListItem::getduration()
225     {
226       if (item->LoadMusicTag())
227       {
228         std::ostringstream oss;
229         oss << item->GetMusicInfoTag()->GetDuration();
230         return oss.str();
231       }
232
233       if (item->HasVideoInfoTag())
234       {
235         std::ostringstream oss;
236         oss << item->GetVideoInfoTag()->GetDuration() / 60;
237         return oss.str();
238       }
239       return "0";
240     }
241
242     String ListItem::getfilename()
243     {
244       return item->GetPath();
245     }
246
247     void ListItem::setInfo(const char* type, const Dictionary& infoLabels)
248     {
249       LOCKGUI;
250
251       if (strcmpi(type, "video") == 0)
252       {
253         for (Dictionary::const_iterator it = infoLabels.begin(); it != infoLabels.end(); ++it)
254         {
255           CStdString key = it->first;
256           key.ToLower();
257           const CStdString value(it->second.c_str());
258
259           if (key == "year")
260             item->GetVideoInfoTag()->m_iYear = strtol(value.c_str(), NULL, 10);
261           else if (key == "episode")
262             item->GetVideoInfoTag()->m_iEpisode = strtol(value.c_str(), NULL, 10);
263           else if (key == "season")
264             item->GetVideoInfoTag()->m_iSeason = strtol(value.c_str(), NULL, 10);
265           else if (key == "top250")
266             item->GetVideoInfoTag()->m_iTop250 = strtol(value.c_str(), NULL, 10);
267           else if (key == "tracknumber")
268             item->GetVideoInfoTag()->m_iTrack = strtol(value.c_str(), NULL, 10);
269           else if (key == "count")
270             item->m_iprogramCount = strtol(value.c_str(), NULL, 10);
271           else if (key == "rating")
272             item->GetVideoInfoTag()->m_fRating = (float)strtod(value.c_str(), NULL);
273           else if (key == "size")
274             item->m_dwSize = (int64_t)strtoll(value.c_str(), NULL, 10);
275           else if (key == "watched") // backward compat - do we need it?
276             item->GetVideoInfoTag()->m_playCount = strtol(value.c_str(), NULL, 10);
277           else if (key == "playcount")
278             item->GetVideoInfoTag()->m_playCount = strtol(value.c_str(), NULL, 10);
279           else if (key == "overlay")
280           {
281             long overlay = strtol(value.c_str(), NULL, 10);
282             if (overlay >= 0 && overlay <= 8)
283               item->SetOverlayImage((CGUIListItem::GUIIconOverlay)overlay);
284           }
285 // TODO: This is a dynamic type for the value where a list is expected as the 
286 //   Dictionary value.
287 //          else if (key == "cast" || key == "castandrole")
288 //          {
289 //            if (!PyObject_TypeCheck(value, &PyList_Type)) continue;
290 //            item->GetVideoInfoTag()->m_cast.clear();
291 //            for (int i = 0; i < PyList_Size(value); i++)
292 //            {
293 //              PyObject *pTuple = NULL;
294 //              pTuple = PyList_GetItem(value, i);
295 //              if (pTuple == NULL) continue;
296 //              PyObject *pActor = NULL;
297 //              PyObject *pRole = NULL;
298 //              if (PyObject_TypeCheck(pTuple, &PyTuple_Type))
299 //              {
300 //                if (!PyArg_ParseTuple(pTuple, (char*)"O|O", &pActor, &pRole)) continue;
301 //              }
302 //              else
303 //                pActor = pTuple;
304 //              SActorInfo info;
305 //              if (!PyXBMCGetUnicodeString(info.strName, pActor, 1)) continue;
306 //              if (pRole != NULL)
307 //                PyXBMCGetUnicodeString(info.strRole, pRole, 1);
308 //              item->GetVideoInfoTag()->m_cast.push_back(info);
309 //            }
310 //          }
311 //          else if (strcmpi(PyString_AsString(key), "artist") == 0)
312 //          {
313 //            if (!PyObject_TypeCheck(value, &PyList_Type)) continue;
314 //            self->item->GetVideoInfoTag()->m_artist.clear();
315 //            for (int i = 0; i < PyList_Size(value); i++)
316 //            {
317 //              PyObject *pActor = PyList_GetItem(value, i);
318 //              if (pActor == NULL) continue;
319 //              String actor;
320 //              if (!PyXBMCGetUnicodeString(actor, pActor, 1)) continue;
321 //              self->item->GetVideoInfoTag()->m_artist.push_back(actor);
322 //            }
323 //          }
324           else if (key == "genre")
325             item->GetVideoInfoTag()->m_genre = StringUtils::Split(value, g_advancedSettings.m_videoItemSeparator);
326           else if (key == "director")
327             item->GetVideoInfoTag()->m_director = StringUtils::Split(value, g_advancedSettings.m_videoItemSeparator);
328           else if (key == "mpaa")
329             item->GetVideoInfoTag()->m_strMPAARating = value;
330           else if (key == "plot")
331             item->GetVideoInfoTag()->m_strPlot = value;
332           else if (key == "plotoutline")
333             item->GetVideoInfoTag()->m_strPlotOutline = value;
334           else if (key == "title")
335             item->GetVideoInfoTag()->m_strTitle = value;
336           else if (key == "originaltitle")
337             item->GetVideoInfoTag()->m_strOriginalTitle = value;
338           else if (key == "sorttitle")
339             item->GetVideoInfoTag()->m_strSortTitle = value;
340           else if (key == "duration")
341             item->GetVideoInfoTag()->m_duration = CVideoInfoTag::GetDurationFromMinuteString(value);
342           else if (key == "studio")
343             item->GetVideoInfoTag()->m_studio = StringUtils::Split(value, g_advancedSettings.m_videoItemSeparator);            
344           else if (key == "tagline")
345             item->GetVideoInfoTag()->m_strTagLine = value;
346           else if (key == "writer")
347             item->GetVideoInfoTag()->m_writingCredits = StringUtils::Split(value, g_advancedSettings.m_videoItemSeparator);
348           else if (key == "tvshowtitle")
349             item->GetVideoInfoTag()->m_strShowTitle = value;
350           else if (key == "premiered")
351             item->GetVideoInfoTag()->m_premiered.SetFromDateString(value);
352           else if (key == "status")
353             item->GetVideoInfoTag()->m_strStatus = value;
354           else if (key == "code")
355             item->GetVideoInfoTag()->m_strProductionCode = value;
356           else if (key == "aired")
357             item->GetVideoInfoTag()->m_firstAired.SetFromDateString(value);
358           else if (key == "credits")
359             item->GetVideoInfoTag()->m_writingCredits = StringUtils::Split(value, g_advancedSettings.m_videoItemSeparator);
360           else if (key == "lastplayed")
361             item->GetVideoInfoTag()->m_lastPlayed.SetFromDBDateTime(value);
362           else if (key == "album")
363             item->GetVideoInfoTag()->m_strAlbum = value;
364           else if (key == "votes")
365             item->GetVideoInfoTag()->m_strVotes = value;
366           else if (key == "trailer")
367             item->GetVideoInfoTag()->m_strTrailer = value;
368           else if (key == "date")
369           {
370             if (value.length() == 10)
371               item->m_dateTime.SetDate(atoi(value.Right(4).c_str()), atoi(value.Mid(3,4).c_str()), atoi(value.Left(2).c_str()));
372             else
373               CLog::Log(LOGERROR,"NEWADDON Invalid Date Format \"%s\"",value.c_str());
374           }
375           else if (key == "dateadded")
376             item->GetVideoInfoTag()->m_dateAdded.SetFromDBDateTime(value.c_str());
377         }
378       }
379       else if (strcmpi(type, "music") == 0)
380       {
381         for (Dictionary::const_iterator it = infoLabels.begin(); it != infoLabels.end(); ++it)
382         {
383           CStdString key = it->first;
384
385           key.ToLower();
386           const CStdString value(it->second.c_str());
387
388           // TODO: add the rest of the infolabels
389           if (key == "tracknumber")
390             item->GetMusicInfoTag()->SetTrackNumber(strtol(value, NULL, 10));
391           else if (key == "count")
392             item->m_iprogramCount = strtol(value, NULL, 10);
393           else if (key == "size")
394             item->m_dwSize = (int64_t)strtoll(value, NULL, 10);
395           else if (key == "duration")
396             item->GetMusicInfoTag()->SetDuration(strtol(value, NULL, 10));
397           else if (key == "year")
398             item->GetMusicInfoTag()->SetYear(strtol(value, NULL, 10));
399           else if (key == "listeners")
400             item->GetMusicInfoTag()->SetListeners(strtol(value, NULL, 10));
401           else if (key == "playcount")
402             item->GetMusicInfoTag()->SetPlayCount(strtol(value, NULL, 10));
403           else if (key == "genre")
404             item->GetMusicInfoTag()->SetGenre(value);
405           else if (key == "album")
406             item->GetMusicInfoTag()->SetAlbum(value);
407           else if (key == "artist")
408             item->GetMusicInfoTag()->SetArtist(value);
409           else if (key == "title")
410             item->GetMusicInfoTag()->SetTitle(value);
411           else if (key == "rating")
412             item->GetMusicInfoTag()->SetRating(*value);
413           else if (key == "lyrics")
414             item->GetMusicInfoTag()->SetLyrics(value);
415           else if (key == "lastplayed")
416             item->GetMusicInfoTag()->SetLastPlayed(value);
417           else if (key == "musicbrainztrackid")
418             item->GetMusicInfoTag()->SetMusicBrainzTrackID(value);
419           else if (key == "musicbrainzartistid")
420             item->GetMusicInfoTag()->SetMusicBrainzArtistID(StringUtils::Split(value, g_advancedSettings.m_musicItemSeparator));
421           else if (key == "musicbrainzalbumid")
422             item->GetMusicInfoTag()->SetMusicBrainzAlbumID(value);
423           else if (key == "musicbrainzalbumartistid")
424             item->GetMusicInfoTag()->SetMusicBrainzAlbumArtistID(StringUtils::Split(value, g_advancedSettings.m_musicItemSeparator));
425           else if (key == "musicbrainztrmid")
426             item->GetMusicInfoTag()->SetMusicBrainzTRMID(value);
427           else if (key == "comment")
428             item->GetMusicInfoTag()->SetComment(value);
429           else if (key == "date")
430           {
431             if (strlen(value) == 10)
432               item->m_dateTime.SetDate(atoi(value.Right(4)), atoi(value.Mid(3,4)), atoi(value.Left(2)));
433           }
434           else
435             CLog::Log(LOGERROR,"NEWADDON Unknown Music Info Key \"%s\"", key.c_str());
436
437           // This should probably be set outside of the loop but since the original
438           //  implementation set it inside of the loop, I'll leave it that way. - Jim C.
439           item->GetMusicInfoTag()->SetLoaded(true);
440         }
441       }
442       else if (strcmpi(type,"pictures") == 0)
443       {
444         for (Dictionary::const_iterator it = infoLabels.begin(); it != infoLabels.end(); ++it)
445         {
446           CStdString key = it->first;
447           key.ToLower();
448           const CStdString value(it->second.c_str());
449
450           if (key == "count")
451             item->m_iprogramCount = strtol(value, NULL, 10);
452           else if (key == "size")
453             item->m_dwSize = (int64_t)strtoll(value, NULL, 10);
454           else if (key == "title")
455             item->m_strTitle = value;
456           else if (key == "picturepath")
457             item->SetPath(value);
458           else if (key == "date")
459           {
460             if (strlen(value) == 10)
461               item->m_dateTime.SetDate(atoi(value.Right(4)), atoi(value.Mid(3,4)), atoi(value.Left(2)));
462           }
463           else
464           {
465             const CStdString& exifkey = key;
466             if (!StringUtils::StartsWithNoCase(exifkey, "exif:") || exifkey.length() < 6) continue;
467             int info = CPictureInfoTag::TranslateString(exifkey.Mid(5));
468             item->GetPictureInfoTag()->SetInfo(info, value);
469           }
470         }
471       }
472     } // end ListItem::setInfo
473
474     void ListItem::addStreamInfo(const char* cType, const Dictionary& dictionary)
475     {
476       LOCKGUI;
477
478       if (strcmpi(cType, "video") == 0)
479       {
480         CStreamDetailVideo* video = new CStreamDetailVideo;
481         for (Dictionary::const_iterator it = dictionary.begin(); it != dictionary.end(); ++it)
482         {
483           const String& key = it->first;
484           const CStdString value(it->second.c_str());
485
486           if (key == "codec")
487             video->m_strCodec = value;
488           else if (key == "aspect")
489             video->m_fAspect = (float)atof(value);
490           else if (key == "width")
491             video->m_iWidth = strtol(value, NULL, 10);
492           else if (key == "height")
493             video->m_iHeight = strtol(value, NULL, 10);
494           else if (key == "duration")
495             video->m_iDuration = strtol(value, NULL, 10);
496           else if (key == "stereomode")
497             video->m_strStereoMode = value;
498         }
499         item->GetVideoInfoTag()->m_streamDetails.AddStream(video);
500       }
501       else if (strcmpi(cType, "audio") == 0)
502       {
503         CStreamDetailAudio* audio = new CStreamDetailAudio;
504         for (Dictionary::const_iterator it = dictionary.begin(); it != dictionary.end(); ++it)
505         {
506           const String& key = it->first;
507           const String& value = it->second;
508
509           if (key == "codec")
510             audio->m_strCodec = value;
511           else if (key == "language")
512             audio->m_strLanguage = value;
513           else if (key == "channels")
514             audio->m_iChannels = strtol(value.c_str(), NULL, 10);
515         }
516         item->GetVideoInfoTag()->m_streamDetails.AddStream(audio);
517       }
518       else if (strcmpi(cType, "subtitle") == 0)
519       {
520         CStreamDetailSubtitle* subtitle = new CStreamDetailSubtitle;
521         for (Dictionary::const_iterator it = dictionary.begin(); it != dictionary.end(); ++it)
522         {
523           const String& key = it->first;
524           const String& value = it->second;
525
526           if (key == "language")
527             subtitle->m_strLanguage = value;
528         }
529         item->GetVideoInfoTag()->m_streamDetails.AddStream(subtitle);
530       }
531     } // end ListItem::addStreamInfo
532
533     void ListItem::addContextMenuItems(const std::vector<Tuple<String,String> >& items, bool replaceItems /* = false */)
534       throw (ListItemException)
535     {
536       int itemCount = 0;
537       for (std::vector<Tuple<String,String> >::const_iterator iter = items.begin(); iter < items.end(); ++iter, ++itemCount)
538       {
539         Tuple<String,String> tuple = *iter;
540         if (tuple.GetNumValuesSet() != 2)
541           throw ListItemException("Must pass in a list of tuples of pairs of strings. One entry in the list only has %d elements.",tuple.GetNumValuesSet());
542         std::string uText = tuple.first();
543         std::string uAction = tuple.second();
544
545         LOCKGUI;
546         CStdString property;
547         property = StringUtils::Format("contextmenulabel(%i)", itemCount);
548         item->SetProperty(property, uText);
549
550         property = StringUtils::Format("contextmenuaction(%i)", itemCount);
551         item->SetProperty(property, uAction);
552       }
553
554       // set our replaceItems status
555       if (replaceItems)
556         item->SetProperty("pluginreplacecontextitems", replaceItems);
557     } // end addContextMenuItems
558   }
559 }