2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "RSSDirectory.h"
13 #include "ServiceBroker.h"
15 #include "settings/AdvancedSettings.h"
16 #include "settings/Settings.h"
17 #include "settings/SettingsComponent.h"
18 #include "utils/FileExtensionProvider.h"
19 #include "utils/HTMLUtil.h"
20 #include "utils/StringUtils.h"
21 #include "utils/URIUtils.h"
22 #include "utils/XBMCTinyXML.h"
23 #include "utils/XMLUtils.h"
24 #include "utils/log.h"
25 #include "video/VideoInfoTag.h"
31 using namespace XFILE
;
32 using namespace MUSIC_INFO
;
49 typedef std::vector
<SResource
> SResources
;
53 std::map
<std::string
,CDateTime
> CRSSDirectory::m_cache
;
54 CCriticalSection
CRSSDirectory::m_section
;
56 CRSSDirectory::CRSSDirectory() = default;
58 CRSSDirectory::~CRSSDirectory() = default;
60 bool CRSSDirectory::ContainsFiles(const CURL
& url
)
63 if(!GetDirectory(url
, items
))
66 return items
.Size() > 0;
69 static bool IsPathToMedia(const std::string
& strPath
)
71 return URIUtils::HasExtension(strPath
,
72 CServiceBroker::GetFileExtensionProvider().GetVideoExtensions() + '|' +
73 CServiceBroker::GetFileExtensionProvider().GetMusicExtensions() + '|' +
74 CServiceBroker::GetFileExtensionProvider().GetPictureExtensions());
77 static bool IsPathToThumbnail(const std::string
& strPath
)
79 // Currently just check if this is an image, maybe we will add some
81 return URIUtils::HasExtension(strPath
,
82 CServiceBroker::GetFileExtensionProvider().GetPictureExtensions());
85 static time_t ParseDate(const std::string
& strDate
)
87 struct tm pubDate
= {};
88 //! @todo Handle time zone
89 strptime(strDate
.c_str(), "%a, %d %b %Y %H:%M:%S", &pubDate
);
90 // Check the difference between the time of last check and time of the item
91 return mktime(&pubDate
);
93 static void ParseItem(CFileItem
* item
, SResources
& resources
, TiXmlElement
* root
, const std::string
& path
);
95 static std::string
GetValue(TiXmlElement
*element
)
97 if (element
&& !element
->NoChildren())
98 return element
->FirstChild()->ValueStr();
102 static void ParseItemMRSS(CFileItem
* item
, SResources
& resources
, TiXmlElement
* item_child
, const std::string
& name
, const std::string
& xmlns
, const std::string
& path
)
104 CVideoInfoTag
* vtag
= item
->GetVideoInfoTag();
105 std::string text
= GetValue(item_child
);
107 if(name
== "content")
110 res
.tag
= "media:content";
111 res
.mime
= XMLUtils::GetAttribute(item_child
, "type");
112 res
.path
= XMLUtils::GetAttribute(item_child
, "url");
113 item_child
->Attribute("width", &res
.width
);
114 item_child
->Attribute("height", &res
.height
);
115 item_child
->Attribute("bitrate", &res
.bitrate
);
116 item_child
->Attribute("duration", &res
.duration
);
117 if(item_child
->Attribute("fileSize"))
118 res
.size
= std::atoll(item_child
->Attribute("fileSize"));
120 resources
.push_back(res
);
121 ParseItem(item
, resources
, item_child
, path
);
123 else if(name
== "group")
125 ParseItem(item
, resources
, item_child
, path
);
127 else if(name
== "thumbnail")
129 if(!item_child
->NoChildren() && IsPathToThumbnail(item_child
->FirstChild()->ValueStr()))
130 item
->SetArt("thumb", item_child
->FirstChild()->ValueStr());
133 const char * url
= item_child
->Attribute("url");
134 if(url
&& IsPathToThumbnail(url
))
135 item
->SetArt("thumb", url
);
138 else if (name
== "title")
143 if(text
.length() > item
->m_strTitle
.length())
144 item
->m_strTitle
= text
;
146 else if(name
== "description")
151 std::string description
= text
;
152 if(XMLUtils::GetAttribute(item_child
, "type") == "html")
153 HTML::CHTMLUtil::RemoveTags(description
);
154 item
->SetProperty("description", description
);
156 else if(name
== "category")
161 std::string scheme
= XMLUtils::GetAttribute(item_child
, "scheme");
163 /* okey this is silly, boxee what did you think?? */
164 if (scheme
== "urn:boxee:genre")
165 vtag
->m_genre
.push_back(text
);
166 else if(scheme
== "urn:boxee:title-type")
169 item
->SetProperty("boxee:istvshow", true);
170 else if(text
== "movie")
171 item
->SetProperty("boxee:ismovie", true);
173 else if(scheme
== "urn:boxee:episode")
174 vtag
->m_iEpisode
= atoi(text
.c_str());
175 else if(scheme
== "urn:boxee:season")
176 vtag
->m_iSeason
= atoi(text
.c_str());
177 else if(scheme
== "urn:boxee:show-title")
178 vtag
->m_strShowTitle
= text
.c_str();
179 else if(scheme
== "urn:boxee:view-count")
180 vtag
->SetPlayCount(atoi(text
.c_str()));
181 else if(scheme
== "urn:boxee:source")
182 item
->SetProperty("boxee:provider_source", text
);
184 vtag
->m_genre
= StringUtils::Split(text
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
);
186 else if(name
== "rating")
188 std::string scheme
= XMLUtils::GetAttribute(item_child
, "scheme");
189 if(scheme
== "urn:user")
190 vtag
->SetRating((float)atof(text
.c_str()));
192 vtag
->m_strMPAARating
= text
;
194 else if(name
== "credit")
196 std::string role
= XMLUtils::GetAttribute(item_child
, "role");
197 if (role
== "director")
198 vtag
->m_director
.push_back(text
);
199 else if(role
== "author"
201 vtag
->m_writingCredits
.push_back(text
);
202 else if(role
== "actor")
205 actor
.strName
= text
;
206 vtag
->m_cast
.push_back(actor
);
209 else if(name
== "copyright")
210 vtag
->m_studio
= StringUtils::Split(text
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
);
211 else if(name
== "keywords")
212 item
->SetProperty("keywords", text
);
216 static void ParseItemItunes(CFileItem
* item
, SResources
& resources
, TiXmlElement
* item_child
, const std::string
& name
, const std::string
& xmlns
, const std::string
& path
)
218 CVideoInfoTag
* vtag
= item
->GetVideoInfoTag();
219 std::string text
= GetValue(item_child
);
223 const char * url
= item_child
->Attribute("href");
225 item
->SetArt("thumb", url
);
227 item
->SetArt("thumb", text
);
229 else if(name
== "summary")
230 vtag
->m_strPlot
= text
;
231 else if(name
== "subtitle")
232 vtag
->m_strPlotOutline
= text
;
233 else if(name
== "author")
234 vtag
->m_writingCredits
.push_back(text
);
235 else if(name
== "duration")
236 vtag
->SetDuration(StringUtils::TimeStringToSeconds(text
));
237 else if(name
== "keywords")
238 item
->SetProperty("keywords", text
);
241 static void ParseItemRSS(CFileItem
* item
, SResources
& resources
, TiXmlElement
* item_child
, const std::string
& name
, const std::string
& xmlns
, const std::string
& path
)
243 std::string text
= GetValue(item_child
);
246 if(text
.length() > item
->m_strTitle
.length())
247 item
->m_strTitle
= text
;
249 else if (name
== "pubDate")
251 CDateTime
pubDate(ParseDate(text
));
252 item
->m_dateTime
= pubDate
;
254 else if (name
== "link")
257 res
.tag
= "rss:link";
259 resources
.push_back(res
);
261 else if(name
== "enclosure")
263 const char * len
= item_child
->Attribute("length");
266 res
.tag
= "rss:enclosure";
267 res
.path
= XMLUtils::GetAttribute(item_child
, "url");
268 res
.mime
= XMLUtils::GetAttribute(item_child
, "type");
270 res
.size
= std::atoll(len
);
272 resources
.push_back(res
);
274 else if(name
== "description")
276 std::string description
= text
;
277 HTML::CHTMLUtil::RemoveTags(description
);
278 item
->SetProperty("description", description
);
280 else if(name
== "guid")
282 if(IsPathToMedia(text
))
285 res
.tag
= "rss:guid";
287 resources
.push_back(res
);
292 static void ParseItemVoddler(CFileItem
* item
, SResources
& resources
, TiXmlElement
* element
, const std::string
& name
, const std::string
& xmlns
, const std::string
& path
)
294 CVideoInfoTag
* vtag
= item
->GetVideoInfoTag();
295 std::string text
= GetValue(element
);
297 if(name
== "trailer")
299 vtag
->m_strTrailer
= text
;
302 res
.tag
= "voddler:trailer";
303 res
.mime
= XMLUtils::GetAttribute(element
, "type");
305 resources
.push_back(res
);
307 else if(name
== "year")
308 vtag
->SetYear(atoi(text
.c_str()));
309 else if(name
== "rating")
310 vtag
->SetRating((float)atof(text
.c_str()));
311 else if(name
== "tagline")
312 vtag
->m_strTagLine
= text
;
313 else if(name
== "posterwall")
315 const char* url
= element
->Attribute("url");
317 item
->SetArt("fanart", url
);
318 else if(IsPathToThumbnail(text
))
319 item
->SetArt("fanart", text
);
323 static void ParseItemBoxee(CFileItem
* item
, SResources
& resources
, TiXmlElement
* element
, const std::string
& name
, const std::string
& xmlns
, const std::string
& path
)
325 CVideoInfoTag
* vtag
= item
->GetVideoInfoTag();
326 std::string text
= GetValue(element
);
329 item
->SetArt("thumb", text
);
330 else if(name
== "user_agent")
331 item
->SetProperty("boxee:user_agent", text
);
332 else if(name
== "content_type")
333 item
->SetMimeType(text
);
334 else if(name
== "runtime")
335 vtag
->SetDuration(StringUtils::TimeStringToSeconds(text
));
336 else if(name
== "episode")
337 vtag
->m_iEpisode
= atoi(text
.c_str());
338 else if(name
== "season")
339 vtag
->m_iSeason
= atoi(text
.c_str());
340 else if(name
== "view-count")
341 vtag
->SetPlayCount(atoi(text
.c_str()));
342 else if(name
== "tv-show-title")
343 vtag
->m_strShowTitle
= text
;
344 else if(name
== "release-date")
345 item
->SetProperty("boxee:releasedate", text
);
348 static void ParseItemZink(CFileItem
* item
, SResources
& resources
, TiXmlElement
* element
, const std::string
& name
, const std::string
& xmlns
, const std::string
& path
)
350 CVideoInfoTag
* vtag
= item
->GetVideoInfoTag();
351 std::string text
= GetValue(element
);
352 if (name
== "episode")
353 vtag
->m_iEpisode
= atoi(text
.c_str());
354 else if(name
== "season")
355 vtag
->m_iSeason
= atoi(text
.c_str());
356 else if(name
== "views")
357 vtag
->SetPlayCount(atoi(text
.c_str()));
358 else if(name
== "airdate")
359 vtag
->m_firstAired
.SetFromDateString(text
);
360 else if(name
== "userrating")
361 vtag
->SetRating((float)atof(text
.c_str()));
362 else if(name
== "duration")
363 vtag
->SetDuration(atoi(text
.c_str()));
364 else if(name
== "durationstr")
365 vtag
->SetDuration(StringUtils::TimeStringToSeconds(text
));
368 static void ParseItemSVT(CFileItem
* item
, SResources
& resources
, TiXmlElement
* element
, const std::string
& name
, const std::string
& xmlns
, const std::string
& path
)
370 std::string text
= GetValue(element
);
371 if (name
== "xmllink")
374 res
.tag
= "svtplay:xmllink";
376 res
.mime
= "application/rss+xml";
377 resources
.push_back(res
);
379 else if (name
== "broadcasts")
382 if(StringUtils::StartsWith(url
.GetFileName(), "v1/"))
385 res
.tag
= "svtplay:broadcasts";
386 res
.path
= url
.GetWithoutFilename() + "v1/video/list/" + text
;
387 res
.mime
= "application/rss+xml";
388 resources
.push_back(res
);
393 static void ParseItem(CFileItem
* item
, SResources
& resources
, TiXmlElement
* root
, const std::string
& path
)
395 for (TiXmlElement
* child
= root
->FirstChildElement(); child
; child
= child
->NextSiblingElement())
397 std::string name
= child
->Value();
399 size_t pos
= name
.find(':');
400 if(pos
!= std::string::npos
)
402 xmlns
= name
.substr(0, pos
);
403 name
.erase(0, pos
+1);
406 if (xmlns
== "media")
407 ParseItemMRSS (item
, resources
, child
, name
, xmlns
, path
);
408 else if (xmlns
== "itunes")
409 ParseItemItunes (item
, resources
, child
, name
, xmlns
, path
);
410 else if (xmlns
== "voddler")
411 ParseItemVoddler(item
, resources
, child
, name
, xmlns
, path
);
412 else if (xmlns
== "boxee")
413 ParseItemBoxee (item
, resources
, child
, name
, xmlns
, path
);
414 else if (xmlns
== "zn")
415 ParseItemZink (item
, resources
, child
, name
, xmlns
, path
);
416 else if (xmlns
== "svtplay")
417 ParseItemSVT (item
, resources
, child
, name
, xmlns
, path
);
419 ParseItemRSS (item
, resources
, child
, name
, xmlns
, path
);
423 static bool FindMime(const SResources
& resources
, const std::string
& mime
)
425 for (const auto& it
: resources
)
427 if (StringUtils::StartsWithNoCase(it
.mime
, mime
))
433 static void ParseItem(CFileItem
* item
, TiXmlElement
* root
, const std::string
& path
)
435 SResources resources
;
436 ParseItem(item
, resources
, root
, path
);
438 const char* prio
[] = { "media:content", "voddler:trailer", "rss:enclosure", "svtplay:broadcasts", "svtplay:xmllink", "rss:link", "rss:guid", NULL
};
441 if (FindMime(resources
, "video/"))
443 else if(FindMime(resources
, "audio/"))
445 else if(FindMime(resources
, "application/rss"))
446 mime
= "application/rss";
447 else if(FindMime(resources
, "image/"))
450 int maxrate
= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_NETWORK_BANDWIDTH
);
454 SResources::iterator best
= resources
.end();
455 for(const char** type
= prio
; *type
&& best
== resources
.end(); type
++)
457 for (SResources::iterator it
= resources
.begin(); it
!= resources
.end(); ++it
)
459 if(!StringUtils::StartsWith(it
->mime
, mime
))
464 if(best
== resources
.end())
470 if(it
->bitrate
== best
->bitrate
)
472 if(it
->width
*it
->height
> best
->width
*best
->height
)
477 if(it
->bitrate
> maxrate
)
479 if(it
->bitrate
< best
->bitrate
)
484 if(it
->bitrate
> best
->bitrate
)
493 if(best
!= resources
.end())
495 item
->SetMimeType(best
->mime
);
496 item
->SetPath(best
->path
);
497 item
->m_dwSize
= best
->size
;
500 item
->SetProperty("duration", StringUtils::SecondsToTimeString(best
->duration
));
502 /* handling of mimetypes fo directories are sub optimal at best */
503 if(best
->mime
== "application/rss+xml" && StringUtils::StartsWithNoCase(item
->GetPath(), "http://"))
504 item
->SetPath("rss://" + item
->GetPath().substr(7));
506 if(best
->mime
== "application/rss+xml" && StringUtils::StartsWithNoCase(item
->GetPath(), "https://"))
507 item
->SetPath("rsss://" + item
->GetPath().substr(8));
509 if(StringUtils::StartsWithNoCase(item
->GetPath(), "rss://")
510 || StringUtils::StartsWithNoCase(item
->GetPath(), "rsss://"))
511 item
->m_bIsFolder
= true;
513 item
->m_bIsFolder
= false;
516 if(!item
->m_strTitle
.empty())
517 item
->SetLabel(item
->m_strTitle
);
519 if(item
->HasVideoInfoTag())
521 CVideoInfoTag
* vtag
= item
->GetVideoInfoTag();
523 if(item
->HasProperty("duration") && !vtag
->GetDuration())
524 vtag
->SetDuration(StringUtils::TimeStringToSeconds(item
->GetProperty("duration").asString()));
526 if(item
->HasProperty("description") && vtag
->m_strPlot
.empty())
527 vtag
->m_strPlot
= item
->GetProperty("description").asString();
529 if(vtag
->m_strPlotOutline
.empty() && !vtag
->m_strPlot
.empty())
531 size_t pos
= vtag
->m_strPlot
.find('\n');
532 if(pos
!= std::string::npos
)
533 vtag
->m_strPlotOutline
= vtag
->m_strPlot
.substr(0, pos
);
535 vtag
->m_strPlotOutline
= vtag
->m_strPlot
;
538 if(!vtag
->GetDuration())
539 item
->SetLabel2(StringUtils::SecondsToTimeString(vtag
->GetDuration()));
543 bool CRSSDirectory::GetDirectory(const CURL
& url
, CFileItemList
&items
)
545 const std::string
pathToUrl(url
.Get());
546 std::string
strPath(pathToUrl
);
547 URIUtils::RemoveSlashAtEnd(strPath
);
548 std::map
<std::string
,CDateTime
>::iterator it
;
549 items
.SetPath(strPath
);
550 std::unique_lock
<CCriticalSection
> lock(m_section
);
551 if ((it
=m_cache
.find(strPath
)) != m_cache
.end())
553 if (it
->second
> CDateTime::GetCurrentDateTime() &&
561 if (!xmlDoc
.LoadFile(strPath
))
563 CLog::Log(LOGERROR
, "failed to load xml from <{}>. error: <{}>", strPath
, xmlDoc
.ErrorId());
568 CLog::Log(LOGERROR
, "error parsing xml doc from <{}>. error: <{}>", strPath
, xmlDoc
.ErrorId());
572 TiXmlElement
* rssXmlNode
= xmlDoc
.RootElement();
577 TiXmlHandle
docHandle( &xmlDoc
);
578 TiXmlElement
* channelXmlNode
= docHandle
.FirstChild( "rss" ).FirstChild( "channel" ).Element();
580 ParseItem(&items
, channelXmlNode
, pathToUrl
);
584 TiXmlElement
* child
= NULL
;
585 for (child
= channelXmlNode
->FirstChildElement("item"); child
; child
= child
->NextSiblingElement())
588 CFileItemPtr
item(new CFileItem());
589 ParseItem(item
.get(), child
, pathToUrl
);
591 item
->SetProperty("isrss", "1");
592 // Use channel image if item doesn't have one
593 if (!item
->HasArt("thumb") && items
.HasArt("thumb"))
594 item
->SetArt("thumb", items
.GetArt("thumb"));
596 if (!item
->GetPath().empty())
600 items
.AddSortMethod(SortByNone
, 231, LABEL_MASKS("%L", "%D", "%L", "")); // FileName, Duration | Foldername, empty
601 items
.AddSortMethod(SortByLabel
, 551, LABEL_MASKS("%L", "%D", "%L", "")); // FileName, Duration | Foldername, empty
602 items
.AddSortMethod(SortBySize
, 553, LABEL_MASKS("%L", "%I", "%L", "%I")); // FileName, Size | Foldername, Size
603 items
.AddSortMethod(SortByDate
, 552, LABEL_MASKS("%L", "%J", "%L", "%J")); // FileName, Date | Foldername, Date
605 CDateTime time
= CDateTime::GetCurrentDateTime();
607 TiXmlElement
* ttl
= docHandle
.FirstChild("rss").FirstChild("ttl").Element();
609 mins
= strtol(ttl
->FirstChild()->Value(),NULL
,10);
610 time
+= CDateTimeSpan(0,0,mins
,0);
611 items
.SetPath(strPath
);
613 std::unique_lock
<CCriticalSection
> lock2(m_section
);
614 m_cache
.insert(make_pair(strPath
,time
));
619 bool CRSSDirectory::Exists(const CURL
& url
)
622 return rss
.Exists(url
);