summaryrefslogtreecommitdiffstats
path: root/xbmc/utils/RssReader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/utils/RssReader.cpp')
-rw-r--r--xbmc/utils/RssReader.cpp415
1 files changed, 415 insertions, 0 deletions
diff --git a/xbmc/utils/RssReader.cpp b/xbmc/utils/RssReader.cpp
new file mode 100644
index 0000000..0b227b6
--- /dev/null
+++ b/xbmc/utils/RssReader.cpp
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "RssReader.h"
+
+#include "CharsetConverter.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "filesystem/CurlFile.h"
+#include "filesystem/File.h"
+#include "guilib/GUIRSSControl.h"
+#include "guilib/LocalizeStrings.h"
+#include "log.h"
+#include "network/Network.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/SystemClock.h"
+#include "utils/HTMLUtil.h"
+#include "utils/XTimeUtils.h"
+
+#include <mutex>
+
+#define RSS_COLOR_BODY 0
+#define RSS_COLOR_HEADLINE 1
+#define RSS_COLOR_CHANNEL 2
+
+using namespace XFILE;
+using namespace std::chrono_literals;
+
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+
+CRssReader::CRssReader() : CThread("RSSReader")
+{
+ m_pObserver = NULL;
+ m_spacesBetweenFeeds = 0;
+ m_bIsRunning = false;
+ m_savedScrollPixelPos = 0;
+ m_rtlText = false;
+ m_requestRefresh = false;
+}
+
+CRssReader::~CRssReader()
+{
+ if (m_pObserver)
+ m_pObserver->OnFeedRelease();
+ StopThread();
+ for (unsigned int i = 0; i < m_vecTimeStamps.size(); i++)
+ delete m_vecTimeStamps[i];
+}
+
+void CRssReader::Create(IRssObserver* aObserver, const std::vector<std::string>& aUrls, const std::vector<int> &times, int spacesBetweenFeeds, bool rtl)
+{
+ std::unique_lock<CCriticalSection> lock(m_critical);
+
+ m_pObserver = aObserver;
+ m_spacesBetweenFeeds = spacesBetweenFeeds;
+ m_vecUrls = aUrls;
+ m_strFeed.resize(aUrls.size());
+ m_strColors.resize(aUrls.size());
+ // set update times
+ m_vecUpdateTimes = times;
+ m_rtlText = rtl;
+ m_requestRefresh = false;
+
+ // update each feed on creation
+ for (unsigned int i = 0; i < m_vecUpdateTimes.size(); ++i)
+ {
+ AddToQueue(i);
+ KODI::TIME::SystemTime* time = new KODI::TIME::SystemTime;
+ KODI::TIME::GetLocalTime(time);
+ m_vecTimeStamps.push_back(time);
+ }
+}
+
+void CRssReader::requestRefresh()
+{
+ m_requestRefresh = true;
+}
+
+void CRssReader::AddToQueue(int iAdd)
+{
+ std::unique_lock<CCriticalSection> lock(m_critical);
+ if (iAdd < (int)m_vecUrls.size())
+ m_vecQueue.push_back(iAdd);
+ if (!m_bIsRunning)
+ {
+ StopThread();
+ m_bIsRunning = true;
+ CThread::Create(false);
+ }
+}
+
+void CRssReader::OnExit()
+{
+ m_bIsRunning = false;
+}
+
+int CRssReader::GetQueueSize()
+{
+ std::unique_lock<CCriticalSection> lock(m_critical);
+ return m_vecQueue.size();
+}
+
+void CRssReader::Process()
+{
+ while (GetQueueSize())
+ {
+ std::unique_lock<CCriticalSection> lock(m_critical);
+
+ int iFeed = m_vecQueue.front();
+ m_vecQueue.erase(m_vecQueue.begin());
+
+ m_strFeed[iFeed].clear();
+ m_strColors[iFeed].clear();
+
+ CCurlFile http;
+ http.SetUserAgent(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_userAgent);
+ http.SetTimeout(2);
+ std::string strXML;
+ std::string strUrl = m_vecUrls[iFeed];
+ lock.unlock();
+
+ int nRetries = 3;
+ CURL url(strUrl);
+ std::string fileCharset;
+
+ // we wait for the network to come up
+ if ((url.IsProtocol("http") || url.IsProtocol("https")) &&
+ !CServiceBroker::GetNetwork().IsAvailable())
+ {
+ CLog::Log(LOGWARNING, "RSS: No network connection");
+ strXML = "<rss><item><title>"+g_localizeStrings.Get(15301)+"</title></item></rss>";
+ }
+ else
+ {
+ XbmcThreads::EndTime<> timeout(15s);
+ while (!m_bStop && nRetries > 0)
+ {
+ if (timeout.IsTimePast())
+ {
+ CLog::Log(LOGERROR, "Timeout while retrieving rss feed: {}", strUrl);
+ break;
+ }
+ nRetries--;
+
+ if (!url.IsProtocol("http") && !url.IsProtocol("https"))
+ {
+ CFile file;
+ std::vector<uint8_t> buffer;
+ if (file.LoadFile(strUrl, buffer) > 0)
+ {
+ strXML.assign(reinterpret_cast<char*>(buffer.data()), buffer.size());
+ break;
+ }
+ }
+ else
+ {
+ if (http.Get(strUrl, strXML))
+ {
+ fileCharset = http.GetProperty(XFILE::FILE_PROPERTY_CONTENT_CHARSET);
+ CLog::Log(LOGDEBUG, "Got rss feed: {}", strUrl);
+ break;
+ }
+ else if (nRetries > 0)
+ CThread::Sleep(5000ms); // Network problems? Retry, but not immediately.
+ else
+ CLog::Log(LOGERROR, "Unable to obtain rss feed: {}", strUrl);
+ }
+ }
+ http.Cancel();
+ }
+ if (!strXML.empty() && m_pObserver)
+ {
+ // erase any <content:encoded> tags (also unsupported by tinyxml)
+ size_t iStart = strXML.find("<content:encoded>");
+ size_t iEnd = 0;
+ while (iStart != std::string::npos)
+ {
+ // get <content:encoded> end position
+ iEnd = strXML.find("</content:encoded>", iStart) + 18;
+
+ // erase the section
+ strXML = strXML.erase(iStart, iEnd - iStart);
+
+ iStart = strXML.find("<content:encoded>");
+ }
+
+ if (Parse(strXML, iFeed, fileCharset))
+ CLog::Log(LOGDEBUG, "Parsed rss feed: {}", strUrl);
+ }
+ }
+ UpdateObserver();
+}
+
+void CRssReader::getFeed(vecText &text)
+{
+ text.clear();
+ // double the spaces at the start of the set
+ for (int j = 0; j < m_spacesBetweenFeeds; j++)
+ text.push_back(L' ');
+ for (unsigned int i = 0; i < m_strFeed.size(); i++)
+ {
+ for (int j = 0; j < m_spacesBetweenFeeds; j++)
+ text.push_back(L' ');
+
+ for (unsigned int j = 0; j < m_strFeed[i].size(); j++)
+ {
+ character_t letter = m_strFeed[i][j] | ((m_strColors[i][j] - 48) << 16);
+ text.push_back(letter);
+ }
+ }
+}
+
+void CRssReader::AddTag(const std::string &aString)
+{
+ m_tagSet.push_back(aString);
+}
+
+void CRssReader::AddString(std::wstring aString, int aColour, int iFeed)
+{
+ if (m_rtlText)
+ m_strFeed[iFeed] = aString + m_strFeed[iFeed];
+ else
+ m_strFeed[iFeed] += aString;
+
+ size_t nStringLength = aString.size();
+
+ for (size_t i = 0;i < nStringLength;i++)
+ aString[i] = static_cast<char>(48 + aColour);
+
+ if (m_rtlText)
+ m_strColors[iFeed] = aString + m_strColors[iFeed];
+ else
+ m_strColors[iFeed] += aString;
+}
+
+void CRssReader::GetNewsItems(TiXmlElement* channelXmlNode, int iFeed)
+{
+ HTML::CHTMLUtil html;
+
+ TiXmlElement * itemNode = channelXmlNode->FirstChildElement("item");
+ std::map<std::string, std::wstring> mTagElements;
+ typedef std::pair<std::string, std::wstring> StrPair;
+ std::list<std::string>::iterator i;
+
+ // Add the title tag in if we didn't pass any tags in at all
+ // Represents default behaviour before configurability
+
+ if (m_tagSet.empty())
+ AddTag("title");
+
+ while (itemNode != nullptr)
+ {
+ TiXmlNode* childNode = itemNode->FirstChild();
+ mTagElements.clear();
+ while (childNode != nullptr)
+ {
+ std::string strName = childNode->ValueStr();
+
+ for (i = m_tagSet.begin(); i != m_tagSet.end(); ++i)
+ {
+ if (!childNode->NoChildren() && *i == strName)
+ {
+ std::string htmlText = childNode->FirstChild()->ValueStr();
+
+ // This usually happens in right-to-left languages where they want to
+ // specify in the RSS body that the text should be RTL.
+ // <title>
+ // <div dir="RTL">��� ����: ���� �� �����</div>
+ // </title>
+ if (htmlText == "div" || htmlText == "span")
+ htmlText = childNode->FirstChild()->FirstChild()->ValueStr();
+
+ std::wstring unicodeText, unicodeText2;
+
+ g_charsetConverter.utf8ToW(htmlText, unicodeText2, m_rtlText);
+ html.ConvertHTMLToW(unicodeText2, unicodeText);
+
+ mTagElements.insert(StrPair(*i, unicodeText));
+ }
+ }
+ childNode = childNode->NextSibling();
+ }
+
+ int rsscolour = RSS_COLOR_HEADLINE;
+ for (i = m_tagSet.begin(); i != m_tagSet.end(); ++i)
+ {
+ std::map<std::string, std::wstring>::iterator j = mTagElements.find(*i);
+
+ if (j == mTagElements.end())
+ continue;
+
+ std::wstring& text = j->second;
+ AddString(text, rsscolour, iFeed);
+ rsscolour = RSS_COLOR_BODY;
+ text = L" - ";
+ AddString(text, rsscolour, iFeed);
+ }
+ itemNode = itemNode->NextSiblingElement("item");
+ }
+}
+
+bool CRssReader::Parse(const std::string& data, int iFeed, const std::string& charset)
+{
+ m_xml.Clear();
+ m_xml.Parse(data, charset);
+
+ CLog::Log(LOGDEBUG, "RSS feed encoding: {}", m_xml.GetUsedCharset());
+
+ return Parse(iFeed);
+}
+
+bool CRssReader::Parse(int iFeed)
+{
+ TiXmlElement* rootXmlNode = m_xml.RootElement();
+
+ if (!rootXmlNode)
+ return false;
+
+ TiXmlElement* rssXmlNode = NULL;
+
+ std::string strValue = rootXmlNode->ValueStr();
+ if (strValue.find("rss") != std::string::npos ||
+ strValue.find("rdf") != std::string::npos)
+ rssXmlNode = rootXmlNode;
+ else
+ {
+ // Unable to find root <rss> or <rdf> node
+ return false;
+ }
+
+ TiXmlElement* channelXmlNode = rssXmlNode->FirstChildElement("channel");
+ if (channelXmlNode)
+ {
+ TiXmlElement* titleNode = channelXmlNode->FirstChildElement("title");
+ if (titleNode && !titleNode->NoChildren())
+ {
+ std::string strChannel = titleNode->FirstChild()->Value();
+ std::wstring strChannelUnicode;
+ g_charsetConverter.utf8ToW(strChannel, strChannelUnicode, m_rtlText);
+ AddString(strChannelUnicode, RSS_COLOR_CHANNEL, iFeed);
+
+ AddString(L":", RSS_COLOR_CHANNEL, iFeed);
+ AddString(L" ", RSS_COLOR_CHANNEL, iFeed);
+ }
+
+ GetNewsItems(channelXmlNode,iFeed);
+ }
+
+ GetNewsItems(rssXmlNode,iFeed);
+
+ // avoid trailing ' - '
+ if (m_strFeed[iFeed].size() > 3 && m_strFeed[iFeed].substr(m_strFeed[iFeed].size() - 3) == L" - ")
+ {
+ if (m_rtlText)
+ {
+ m_strFeed[iFeed].erase(0, 3);
+ m_strColors[iFeed].erase(0, 3);
+ }
+ else
+ {
+ m_strFeed[iFeed].erase(m_strFeed[iFeed].length() - 3);
+ m_strColors[iFeed].erase(m_strColors[iFeed].length() - 3);
+ }
+ }
+ return true;
+}
+
+void CRssReader::SetObserver(IRssObserver *observer)
+{
+ m_pObserver = observer;
+}
+
+void CRssReader::UpdateObserver()
+{
+ if (!m_pObserver)
+ return;
+
+ vecText feed;
+ getFeed(feed);
+ if (!feed.empty())
+ {
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ if (m_pObserver) // need to check again when locked to make sure observer wasnt removed
+ m_pObserver->OnFeedUpdate(feed);
+ }
+}
+
+void CRssReader::CheckForUpdates()
+{
+ KODI::TIME::SystemTime time;
+ KODI::TIME::GetLocalTime(&time);
+
+ for (unsigned int i = 0;i < m_vecUpdateTimes.size(); ++i )
+ {
+ if (m_requestRefresh || ((time.day * 24 * 60) + (time.hour * 60) + time.minute) -
+ ((m_vecTimeStamps[i]->day * 24 * 60) +
+ (m_vecTimeStamps[i]->hour * 60) + m_vecTimeStamps[i]->minute) >
+ m_vecUpdateTimes[i])
+ {
+ CLog::Log(LOGDEBUG, "Updating RSS");
+ KODI::TIME::GetLocalTime(m_vecTimeStamps[i]);
+ AddToQueue(i);
+ }
+ }
+
+ m_requestRefresh = false;
+}