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.
11 #include "CharsetConverter.h"
12 #include "ServiceBroker.h"
14 #include "filesystem/CurlFile.h"
15 #include "filesystem/File.h"
16 #include "guilib/GUIRSSControl.h"
17 #include "guilib/LocalizeStrings.h"
19 #include "network/Network.h"
20 #include "settings/AdvancedSettings.h"
21 #include "settings/SettingsComponent.h"
22 #include "threads/SystemClock.h"
23 #include "utils/HTMLUtil.h"
24 #include "utils/XTimeUtils.h"
28 #define RSS_COLOR_BODY 0
29 #define RSS_COLOR_HEADLINE 1
30 #define RSS_COLOR_CHANNEL 2
32 using namespace XFILE
;
33 using namespace std::chrono_literals
;
35 //////////////////////////////////////////////////////////////////////
36 // Construction/Destruction
37 //////////////////////////////////////////////////////////////////////
39 CRssReader::CRssReader() : CThread("RSSReader")
42 m_spacesBetweenFeeds
= 0;
44 m_savedScrollPixelPos
= 0;
46 m_requestRefresh
= false;
49 CRssReader::~CRssReader()
52 m_pObserver
->OnFeedRelease();
54 for (unsigned int i
= 0; i
< m_vecTimeStamps
.size(); i
++)
55 delete m_vecTimeStamps
[i
];
58 void CRssReader::Create(IRssObserver
* aObserver
, const std::vector
<std::string
>& aUrls
, const std::vector
<int> ×
, int spacesBetweenFeeds
, bool rtl
)
60 std::unique_lock
<CCriticalSection
> lock(m_critical
);
62 m_pObserver
= aObserver
;
63 m_spacesBetweenFeeds
= spacesBetweenFeeds
;
65 m_strFeed
.resize(aUrls
.size());
66 m_strColors
.resize(aUrls
.size());
68 m_vecUpdateTimes
= times
;
70 m_requestRefresh
= false;
72 // update each feed on creation
73 for (unsigned int i
= 0; i
< m_vecUpdateTimes
.size(); ++i
)
76 KODI::TIME::SystemTime
* time
= new KODI::TIME::SystemTime
;
77 KODI::TIME::GetLocalTime(time
);
78 m_vecTimeStamps
.push_back(time
);
82 void CRssReader::requestRefresh()
84 m_requestRefresh
= true;
87 void CRssReader::AddToQueue(int iAdd
)
89 std::unique_lock
<CCriticalSection
> lock(m_critical
);
90 if (iAdd
< (int)m_vecUrls
.size())
91 m_vecQueue
.push_back(iAdd
);
96 CThread::Create(false);
100 void CRssReader::OnExit()
102 m_bIsRunning
= false;
105 int CRssReader::GetQueueSize()
107 std::unique_lock
<CCriticalSection
> lock(m_critical
);
108 return m_vecQueue
.size();
111 void CRssReader::Process()
113 while (GetQueueSize())
115 std::unique_lock
<CCriticalSection
> lock(m_critical
);
117 int iFeed
= m_vecQueue
.front();
118 m_vecQueue
.erase(m_vecQueue
.begin());
120 m_strFeed
[iFeed
].clear();
121 m_strColors
[iFeed
].clear();
124 http
.SetUserAgent(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_userAgent
);
127 std::string strUrl
= m_vecUrls
[iFeed
];
132 std::string fileCharset
;
134 // we wait for the network to come up
135 if ((url
.IsProtocol("http") || url
.IsProtocol("https")) &&
136 !CServiceBroker::GetNetwork().IsAvailable())
138 CLog::Log(LOGWARNING
, "RSS: No network connection");
139 strXML
= "<rss><item><title>"+g_localizeStrings
.Get(15301)+"</title></item></rss>";
143 XbmcThreads::EndTime
<> timeout(15s
);
144 while (!m_bStop
&& nRetries
> 0)
146 if (timeout
.IsTimePast())
148 CLog::Log(LOGERROR
, "Timeout while retrieving rss feed: {}", strUrl
);
153 if (!url
.IsProtocol("http") && !url
.IsProtocol("https"))
156 std::vector
<uint8_t> buffer
;
157 if (file
.LoadFile(strUrl
, buffer
) > 0)
159 strXML
.assign(reinterpret_cast<char*>(buffer
.data()), buffer
.size());
165 if (http
.Get(strUrl
, strXML
))
167 fileCharset
= http
.GetProperty(XFILE::FILE_PROPERTY_CONTENT_CHARSET
);
168 CLog::Log(LOGDEBUG
, "Got rss feed: {}", strUrl
);
171 else if (nRetries
> 0)
172 CThread::Sleep(5000ms
); // Network problems? Retry, but not immediately.
174 CLog::Log(LOGERROR
, "Unable to obtain rss feed: {}", strUrl
);
179 if (!strXML
.empty() && m_pObserver
)
181 // erase any <content:encoded> tags (also unsupported by tinyxml)
182 size_t iStart
= strXML
.find("<content:encoded>");
184 while (iStart
!= std::string::npos
)
186 // get <content:encoded> end position
187 iEnd
= strXML
.find("</content:encoded>", iStart
) + 18;
190 strXML
= strXML
.erase(iStart
, iEnd
- iStart
);
192 iStart
= strXML
.find("<content:encoded>");
195 if (Parse(strXML
, iFeed
, fileCharset
))
196 CLog::Log(LOGDEBUG
, "Parsed rss feed: {}", strUrl
);
202 void CRssReader::getFeed(vecText
&text
)
205 // double the spaces at the start of the set
206 for (int j
= 0; j
< m_spacesBetweenFeeds
; j
++)
207 text
.push_back(L
' ');
208 for (unsigned int i
= 0; i
< m_strFeed
.size(); i
++)
210 for (int j
= 0; j
< m_spacesBetweenFeeds
; j
++)
211 text
.push_back(L
' ');
213 for (unsigned int j
= 0; j
< m_strFeed
[i
].size(); j
++)
215 character_t letter
= m_strFeed
[i
][j
] | ((m_strColors
[i
][j
] - 48) << 16);
216 text
.push_back(letter
);
221 void CRssReader::AddTag(const std::string
&aString
)
223 m_tagSet
.push_back(aString
);
226 void CRssReader::AddString(std::wstring aString
, int aColour
, int iFeed
)
229 m_strFeed
[iFeed
] = aString
+ m_strFeed
[iFeed
];
231 m_strFeed
[iFeed
] += aString
;
233 size_t nStringLength
= aString
.size();
235 for (size_t i
= 0;i
< nStringLength
;i
++)
236 aString
[i
] = static_cast<char>(48 + aColour
);
239 m_strColors
[iFeed
] = aString
+ m_strColors
[iFeed
];
241 m_strColors
[iFeed
] += aString
;
244 void CRssReader::GetNewsItems(TiXmlElement
* channelXmlNode
, int iFeed
)
246 HTML::CHTMLUtil html
;
248 TiXmlElement
* itemNode
= channelXmlNode
->FirstChildElement("item");
249 std::map
<std::string
, std::wstring
> mTagElements
;
250 typedef std::pair
<std::string
, std::wstring
> StrPair
;
251 std::list
<std::string
>::iterator i
;
253 // Add the title tag in if we didn't pass any tags in at all
254 // Represents default behaviour before configurability
256 if (m_tagSet
.empty())
259 while (itemNode
!= nullptr)
261 TiXmlNode
* childNode
= itemNode
->FirstChild();
262 mTagElements
.clear();
263 while (childNode
!= nullptr)
265 std::string strName
= childNode
->ValueStr();
267 for (i
= m_tagSet
.begin(); i
!= m_tagSet
.end(); ++i
)
269 if (!childNode
->NoChildren() && *i
== strName
)
271 std::string htmlText
= childNode
->FirstChild()->ValueStr();
273 // This usually happens in right-to-left languages where they want to
274 // specify in the RSS body that the text should be RTL.
276 // <div dir="RTL">��� ����: ���� �� �����</div>
278 if (htmlText
== "div" || htmlText
== "span")
279 htmlText
= childNode
->FirstChild()->FirstChild()->ValueStr();
281 std::wstring unicodeText
, unicodeText2
;
283 g_charsetConverter
.utf8ToW(htmlText
, unicodeText2
, m_rtlText
);
284 html
.ConvertHTMLToW(unicodeText2
, unicodeText
);
286 mTagElements
.insert(StrPair(*i
, unicodeText
));
289 childNode
= childNode
->NextSibling();
292 int rsscolour
= RSS_COLOR_HEADLINE
;
293 for (i
= m_tagSet
.begin(); i
!= m_tagSet
.end(); ++i
)
295 std::map
<std::string
, std::wstring
>::iterator j
= mTagElements
.find(*i
);
297 if (j
== mTagElements
.end())
300 std::wstring
& text
= j
->second
;
301 AddString(text
, rsscolour
, iFeed
);
302 rsscolour
= RSS_COLOR_BODY
;
304 AddString(text
, rsscolour
, iFeed
);
306 itemNode
= itemNode
->NextSiblingElement("item");
310 bool CRssReader::Parse(const std::string
& data
, int iFeed
, const std::string
& charset
)
313 m_xml
.Parse(data
, charset
);
315 CLog::Log(LOGDEBUG
, "RSS feed encoding: {}", m_xml
.GetUsedCharset());
320 bool CRssReader::Parse(int iFeed
)
322 TiXmlElement
* rootXmlNode
= m_xml
.RootElement();
327 TiXmlElement
* rssXmlNode
= NULL
;
329 std::string strValue
= rootXmlNode
->ValueStr();
330 if (strValue
.find("rss") != std::string::npos
||
331 strValue
.find("rdf") != std::string::npos
)
332 rssXmlNode
= rootXmlNode
;
335 // Unable to find root <rss> or <rdf> node
339 TiXmlElement
* channelXmlNode
= rssXmlNode
->FirstChildElement("channel");
342 TiXmlElement
* titleNode
= channelXmlNode
->FirstChildElement("title");
343 if (titleNode
&& !titleNode
->NoChildren())
345 std::string strChannel
= titleNode
->FirstChild()->Value();
346 std::wstring strChannelUnicode
;
347 g_charsetConverter
.utf8ToW(strChannel
, strChannelUnicode
, m_rtlText
);
348 AddString(strChannelUnicode
, RSS_COLOR_CHANNEL
, iFeed
);
350 AddString(L
":", RSS_COLOR_CHANNEL
, iFeed
);
351 AddString(L
" ", RSS_COLOR_CHANNEL
, iFeed
);
354 GetNewsItems(channelXmlNode
,iFeed
);
357 GetNewsItems(rssXmlNode
,iFeed
);
359 // avoid trailing ' - '
360 if (m_strFeed
[iFeed
].size() > 3 && m_strFeed
[iFeed
].substr(m_strFeed
[iFeed
].size() - 3) == L
" - ")
364 m_strFeed
[iFeed
].erase(0, 3);
365 m_strColors
[iFeed
].erase(0, 3);
369 m_strFeed
[iFeed
].erase(m_strFeed
[iFeed
].length() - 3);
370 m_strColors
[iFeed
].erase(m_strColors
[iFeed
].length() - 3);
376 void CRssReader::SetObserver(IRssObserver
*observer
)
378 m_pObserver
= observer
;
381 void CRssReader::UpdateObserver()
390 std::unique_lock
<CCriticalSection
> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
391 if (m_pObserver
) // need to check again when locked to make sure observer wasnt removed
392 m_pObserver
->OnFeedUpdate(feed
);
396 void CRssReader::CheckForUpdates()
398 KODI::TIME::SystemTime time
;
399 KODI::TIME::GetLocalTime(&time
);
401 for (unsigned int i
= 0;i
< m_vecUpdateTimes
.size(); ++i
)
403 if (m_requestRefresh
|| ((time
.day
* 24 * 60) + (time
.hour
* 60) + time
.minute
) -
404 ((m_vecTimeStamps
[i
]->day
* 24 * 60) +
405 (m_vecTimeStamps
[i
]->hour
* 60) + m_vecTimeStamps
[i
]->minute
) >
408 CLog::Log(LOGDEBUG
, "Updating RSS");
409 KODI::TIME::GetLocalTime(m_vecTimeStamps
[i
]);
414 m_requestRefresh
= false;