summaryrefslogtreecommitdiffstats
path: root/xbmc/utils/RssReader.cpp
diff options
context:
space:
mode:
authormanuel <manuel@mausz.at>2020-10-19 00:52:24 +0200
committermanuel <manuel@mausz.at>2020-10-19 00:52:24 +0200
commitbe933ef2241d79558f91796cc5b3a161f72ebf9c (patch)
treefe3ab2f130e20c99001f2d7a81d610c78c96a3f4 /xbmc/utils/RssReader.cpp
parent5f8335c1e49ce108ef3481863833c98efa00411b (diff)
downloadkodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.tar.gz
kodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.tar.bz2
kodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.zip
sync with upstream
Diffstat (limited to 'xbmc/utils/RssReader.cpp')
-rw-r--r--xbmc/utils/RssReader.cpp413
1 files changed, 413 insertions, 0 deletions
diff --git a/xbmc/utils/RssReader.cpp b/xbmc/utils/RssReader.cpp
new file mode 100644
index 0000000..ecebc93
--- /dev/null
+++ b/xbmc/utils/RssReader.cpp
@@ -0,0 +1,413 @@
1/*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#include "RssReader.h"
10
11#include "CharsetConverter.h"
12#include "ServiceBroker.h"
13#include "URL.h"
14#include "filesystem/CurlFile.h"
15#include "filesystem/File.h"
16#include "guilib/GUIRSSControl.h"
17#include "guilib/LocalizeStrings.h"
18#include "log.h"
19#include "network/Network.h"
20#include "settings/AdvancedSettings.h"
21#include "settings/SettingsComponent.h"
22#include "threads/SingleLock.h"
23#include "threads/SystemClock.h"
24#include "utils/HTMLUtil.h"
25#include "utils/XTimeUtils.h"
26
27#define RSS_COLOR_BODY 0
28#define RSS_COLOR_HEADLINE 1
29#define RSS_COLOR_CHANNEL 2
30
31using namespace XFILE;
32
33//////////////////////////////////////////////////////////////////////
34// Construction/Destruction
35//////////////////////////////////////////////////////////////////////
36
37CRssReader::CRssReader() : CThread("RSSReader")
38{
39 m_pObserver = NULL;
40 m_spacesBetweenFeeds = 0;
41 m_bIsRunning = false;
42 m_savedScrollPixelPos = 0;
43 m_rtlText = false;
44 m_requestRefresh = false;
45}
46
47CRssReader::~CRssReader()
48{
49 if (m_pObserver)
50 m_pObserver->OnFeedRelease();
51 StopThread();
52 for (unsigned int i = 0; i < m_vecTimeStamps.size(); i++)
53 delete m_vecTimeStamps[i];
54}
55
56void CRssReader::Create(IRssObserver* aObserver, const std::vector<std::string>& aUrls, const std::vector<int> &times, int spacesBetweenFeeds, bool rtl)
57{
58 CSingleLock lock(m_critical);
59
60 m_pObserver = aObserver;
61 m_spacesBetweenFeeds = spacesBetweenFeeds;
62 m_vecUrls = aUrls;
63 m_strFeed.resize(aUrls.size());
64 m_strColors.resize(aUrls.size());
65 // set update times
66 m_vecUpdateTimes = times;
67 m_rtlText = rtl;
68 m_requestRefresh = false;
69
70 // update each feed on creation
71 for (unsigned int i = 0; i < m_vecUpdateTimes.size(); ++i)
72 {
73 AddToQueue(i);
74 KODI::TIME::SystemTime* time = new KODI::TIME::SystemTime;
75 KODI::TIME::GetLocalTime(time);
76 m_vecTimeStamps.push_back(time);
77 }
78}
79
80void CRssReader::requestRefresh()
81{
82 m_requestRefresh = true;
83}
84
85void CRssReader::AddToQueue(int iAdd)
86{
87 CSingleLock lock(m_critical);
88 if (iAdd < (int)m_vecUrls.size())
89 m_vecQueue.push_back(iAdd);
90 if (!m_bIsRunning)
91 {
92 StopThread();
93 m_bIsRunning = true;
94 CThread::Create(false);
95 }
96}
97
98void CRssReader::OnExit()
99{
100 m_bIsRunning = false;
101}
102
103int CRssReader::GetQueueSize()
104{
105 CSingleLock lock(m_critical);
106 return m_vecQueue.size();
107}
108
109void CRssReader::Process()
110{
111 while (GetQueueSize())
112 {
113 CSingleLock lock(m_critical);
114
115 int iFeed = m_vecQueue.front();
116 m_vecQueue.erase(m_vecQueue.begin());
117
118 m_strFeed[iFeed].clear();
119 m_strColors[iFeed].clear();
120
121 CCurlFile http;
122 http.SetUserAgent(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_userAgent);
123 http.SetTimeout(2);
124 std::string strXML;
125 std::string strUrl = m_vecUrls[iFeed];
126 lock.Leave();
127
128 int nRetries = 3;
129 CURL url(strUrl);
130 std::string fileCharset;
131
132 // we wait for the network to come up
133 if ((url.IsProtocol("http") || url.IsProtocol("https")) &&
134 !CServiceBroker::GetNetwork().IsAvailable())
135 {
136 CLog::Log(LOGWARNING, "RSS: No network connection");
137 strXML = "<rss><item><title>"+g_localizeStrings.Get(15301)+"</title></item></rss>";
138 }
139 else
140 {
141 XbmcThreads::EndTime timeout(15000);
142 while (!m_bStop && nRetries > 0)
143 {
144 if (timeout.IsTimePast())
145 {
146 CLog::Log(LOGERROR, "Timeout while retrieving rss feed: %s", strUrl.c_str());
147 break;
148 }
149 nRetries--;
150
151 if (!url.IsProtocol("http") && !url.IsProtocol("https"))
152 {
153 CFile file;
154 auto_buffer buffer;
155 if (file.LoadFile(strUrl, buffer) > 0)
156 {
157 strXML.assign(buffer.get(), buffer.length());
158 break;
159 }
160 }
161 else
162 {
163 if (http.Get(strUrl, strXML))
164 {
165 fileCharset = http.GetProperty(XFILE::FILE_PROPERTY_CONTENT_CHARSET);
166 CLog::Log(LOGDEBUG, "Got rss feed: %s", strUrl.c_str());
167 break;
168 }
169 else if (nRetries > 0)
170 CThread::Sleep(5000); // Network problems? Retry, but not immediately.
171 else
172 CLog::Log(LOGERROR, "Unable to obtain rss feed: %s", strUrl.c_str());
173 }
174 }
175 http.Cancel();
176 }
177 if (!strXML.empty() && m_pObserver)
178 {
179 // erase any <content:encoded> tags (also unsupported by tinyxml)
180 size_t iStart = strXML.find("<content:encoded>");
181 size_t iEnd = 0;
182 while (iStart != std::string::npos)
183 {
184 // get <content:encoded> end position
185 iEnd = strXML.find("</content:encoded>", iStart) + 18;
186
187 // erase the section
188 strXML = strXML.erase(iStart, iEnd - iStart);
189
190 iStart = strXML.find("<content:encoded>");
191 }
192
193 if (Parse(strXML, iFeed, fileCharset))
194 CLog::Log(LOGDEBUG, "Parsed rss feed: %s", strUrl.c_str());
195 }
196 }
197 UpdateObserver();
198}
199
200void CRssReader::getFeed(vecText &text)
201{
202 text.clear();
203 // double the spaces at the start of the set
204 for (int j = 0; j < m_spacesBetweenFeeds; j++)
205 text.push_back(L' ');
206 for (unsigned int i = 0; i < m_strFeed.size(); i++)
207 {
208 for (int j = 0; j < m_spacesBetweenFeeds; j++)
209 text.push_back(L' ');
210
211 for (unsigned int j = 0; j < m_strFeed[i].size(); j++)
212 {
213 character_t letter = m_strFeed[i][j] | ((m_strColors[i][j] - 48) << 16);
214 text.push_back(letter);
215 }
216 }
217}
218
219void CRssReader::AddTag(const std::string &aString)
220{
221 m_tagSet.push_back(aString);
222}
223
224void CRssReader::AddString(std::wstring aString, int aColour, int iFeed)
225{
226 if (m_rtlText)
227 m_strFeed[iFeed] = aString + m_strFeed[iFeed];
228 else
229 m_strFeed[iFeed] += aString;
230
231 size_t nStringLength = aString.size();
232
233 for (size_t i = 0;i < nStringLength;i++)
234 aString[i] = static_cast<char>(48 + aColour);
235
236 if (m_rtlText)
237 m_strColors[iFeed] = aString + m_strColors[iFeed];
238 else
239 m_strColors[iFeed] += aString;
240}
241
242void CRssReader::GetNewsItems(TiXmlElement* channelXmlNode, int iFeed)
243{
244 HTML::CHTMLUtil html;
245
246 TiXmlElement * itemNode = channelXmlNode->FirstChildElement("item");
247 std::map<std::string, std::wstring> mTagElements;
248 typedef std::pair<std::string, std::wstring> StrPair;
249 std::list<std::string>::iterator i;
250
251 // Add the title tag in if we didn't pass any tags in at all
252 // Represents default behaviour before configurability
253
254 if (m_tagSet.empty())
255 AddTag("title");
256
257 while (itemNode != nullptr)
258 {
259 TiXmlNode* childNode = itemNode->FirstChild();
260 mTagElements.clear();
261 while (childNode != nullptr)
262 {
263 std::string strName = childNode->ValueStr();
264
265 for (i = m_tagSet.begin(); i != m_tagSet.end(); ++i)
266 {
267 if (!childNode->NoChildren() && *i == strName)
268 {
269 std::string htmlText = childNode->FirstChild()->ValueStr();
270
271 // This usually happens in right-to-left languages where they want to
272 // specify in the RSS body that the text should be RTL.
273 // <title>
274 // <div dir="RTL">��� ����: ���� �� �����</div>
275 // </title>
276 if (htmlText == "div" || htmlText == "span")
277 htmlText = childNode->FirstChild()->FirstChild()->ValueStr();
278
279 std::wstring unicodeText, unicodeText2;
280
281 g_charsetConverter.utf8ToW(htmlText, unicodeText2, m_rtlText);
282 html.ConvertHTMLToW(unicodeText2, unicodeText);
283
284 mTagElements.insert(StrPair(*i, unicodeText));
285 }
286 }
287 childNode = childNode->NextSibling();
288 }
289
290 int rsscolour = RSS_COLOR_HEADLINE;
291 for (i = m_tagSet.begin(); i != m_tagSet.end(); ++i)
292 {
293 std::map<std::string, std::wstring>::iterator j = mTagElements.find(*i);
294
295 if (j == mTagElements.end())
296 continue;
297
298 std::wstring& text = j->second;
299 AddString(text, rsscolour, iFeed);
300 rsscolour = RSS_COLOR_BODY;
301 text = L" - ";
302 AddString(text, rsscolour, iFeed);
303 }
304 itemNode = itemNode->NextSiblingElement("item");
305 }
306}
307
308bool CRssReader::Parse(const std::string& data, int iFeed, const std::string& charset)
309{
310 m_xml.Clear();
311 m_xml.Parse(data, charset);
312
313 CLog::Log(LOGDEBUG, "RSS feed encoding: %s", m_xml.GetUsedCharset().c_str());
314
315 return Parse(iFeed);
316}
317
318bool CRssReader::Parse(int iFeed)
319{
320 TiXmlElement* rootXmlNode = m_xml.RootElement();
321
322 if (!rootXmlNode)
323 return false;
324
325 TiXmlElement* rssXmlNode = NULL;
326
327 std::string strValue = rootXmlNode->ValueStr();
328 if (strValue.find("rss") != std::string::npos ||
329 strValue.find("rdf") != std::string::npos)
330 rssXmlNode = rootXmlNode;
331 else
332 {
333 // Unable to find root <rss> or <rdf> node
334 return false;
335 }
336
337 TiXmlElement* channelXmlNode = rssXmlNode->FirstChildElement("channel");
338 if (channelXmlNode)
339 {
340 TiXmlElement* titleNode = channelXmlNode->FirstChildElement("title");
341 if (titleNode && !titleNode->NoChildren())
342 {
343 std::string strChannel = titleNode->FirstChild()->Value();
344 std::wstring strChannelUnicode;
345 g_charsetConverter.utf8ToW(strChannel, strChannelUnicode, m_rtlText);
346 AddString(strChannelUnicode, RSS_COLOR_CHANNEL, iFeed);
347
348 AddString(L":", RSS_COLOR_CHANNEL, iFeed);
349 AddString(L" ", RSS_COLOR_CHANNEL, iFeed);
350 }
351
352 GetNewsItems(channelXmlNode,iFeed);
353 }
354
355 GetNewsItems(rssXmlNode,iFeed);
356
357 // avoid trailing ' - '
358 if (m_strFeed[iFeed].size() > 3 && m_strFeed[iFeed].substr(m_strFeed[iFeed].size() - 3) == L" - ")
359 {
360 if (m_rtlText)
361 {
362 m_strFeed[iFeed].erase(0, 3);
363 m_strColors[iFeed].erase(0, 3);
364 }
365 else
366 {
367 m_strFeed[iFeed].erase(m_strFeed[iFeed].length() - 3);
368 m_strColors[iFeed].erase(m_strColors[iFeed].length() - 3);
369 }
370 }
371 return true;
372}
373
374void CRssReader::SetObserver(IRssObserver *observer)
375{
376 m_pObserver = observer;
377}
378
379void CRssReader::UpdateObserver()
380{
381 if (!m_pObserver)
382 return;
383
384 vecText feed;
385 getFeed(feed);
386 if (!feed.empty())
387 {
388 CSingleLock lock(CServiceBroker::GetWinSystem()->GetGfxContext());
389 if (m_pObserver) // need to check again when locked to make sure observer wasnt removed
390 m_pObserver->OnFeedUpdate(feed);
391 }
392}
393
394void CRssReader::CheckForUpdates()
395{
396 KODI::TIME::SystemTime time;
397 KODI::TIME::GetLocalTime(&time);
398
399 for (unsigned int i = 0;i < m_vecUpdateTimes.size(); ++i )
400 {
401 if (m_requestRefresh || ((time.day * 24 * 60) + (time.hour * 60) + time.minute) -
402 ((m_vecTimeStamps[i]->day * 24 * 60) +
403 (m_vecTimeStamps[i]->hour * 60) + m_vecTimeStamps[i]->minute) >
404 m_vecUpdateTimes[i])
405 {
406 CLog::Log(LOGDEBUG, "Updating RSS");
407 KODI::TIME::GetLocalTime(m_vecTimeStamps[i]);
408 AddToQueue(i);
409 }
410 }
411
412 m_requestRefresh = false;
413}