mirror of
https://github.com/Team-Resurgent/XBMC4Xbox.git
synced 2026-08-15 11:18:13 -07:00
If ever adding the XBMC4Xbox source to GitHub use "git add -f -A" or it wont add all the files.
496 lines
12 KiB
C++
496 lines
12 KiB
C++
/*
|
|
* Copyright (C) 2005-2013 Team XBMC
|
|
* http://xbmc.org
|
|
*
|
|
* This Program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This Program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with XBMC; see the file COPYING. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
|
|
#include "system.h"
|
|
#include "RssReader.h"
|
|
#include "utils/HTMLUtil.h"
|
|
#include "Application.h"
|
|
#include "settings/GUISettings.h"
|
|
#include "URL.h"
|
|
#include "LocalizeStrings.h"
|
|
#include "FileSystem/File.h"
|
|
#include "FileSystem/CurlFile.h"
|
|
#ifdef __APPLE__
|
|
#include "CocoaUtils.h"
|
|
#endif
|
|
#include "SystemInfo.h"
|
|
#include "GUIRSSControl.h"
|
|
#include "utils/CharsetConverter.h"
|
|
#include "utils/log.h"
|
|
|
|
using namespace std;
|
|
using namespace XFILE;
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Construction/Destruction
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
CRssReader::CRssReader() : CThread()
|
|
{
|
|
m_pObserver = NULL;
|
|
m_spacesBetweenFeeds = 0;
|
|
m_bIsRunning = false;
|
|
m_SavedScrollPos = 0;
|
|
|
|
m_userAgent = g_sysinfo.GetUserAgent();
|
|
}
|
|
|
|
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 vector<string>& aUrls, const vector<int> ×, int spacesBetweenFeeds, bool rtl)
|
|
{
|
|
CSingleLock lock(*this);
|
|
|
|
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);
|
|
SYSTEMTIME* time = new SYSTEMTIME;
|
|
GetLocalTime(time);
|
|
m_vecTimeStamps.push_back(time);
|
|
}
|
|
}
|
|
|
|
void CRssReader::requestRefresh()
|
|
{
|
|
m_requestRefresh = true;
|
|
}
|
|
|
|
void CRssReader::AddToQueue(int iAdd)
|
|
{
|
|
CSingleLock lock(*this);
|
|
if (iAdd < (int)m_vecUrls.size())
|
|
m_vecQueue.push_back(iAdd);
|
|
if (!m_bIsRunning)
|
|
{
|
|
StopThread();
|
|
m_bIsRunning = true;
|
|
CThread::Create(false, THREAD_MINSTACKSIZE);
|
|
}
|
|
}
|
|
|
|
void CRssReader::OnExit()
|
|
{
|
|
m_bIsRunning = false;
|
|
}
|
|
|
|
int CRssReader::GetQueueSize()
|
|
{
|
|
CSingleLock lock(*this);
|
|
return m_vecQueue.size();
|
|
}
|
|
|
|
void CRssReader::Process()
|
|
{
|
|
while (GetQueueSize())
|
|
{
|
|
EnterCriticalSection(*this);
|
|
|
|
int iFeed = m_vecQueue.front();
|
|
m_vecQueue.erase(m_vecQueue.begin());
|
|
|
|
m_strFeed[iFeed] = "";
|
|
m_strColors[iFeed] = "";
|
|
|
|
CCurlFile http;
|
|
http.SetUserAgent(m_userAgent);
|
|
http.SetTimeout(2);
|
|
CStdString strXML;
|
|
CStdString strUrl = m_vecUrls[iFeed];
|
|
|
|
LeaveCriticalSection(*this);
|
|
|
|
int nRetries = 3;
|
|
CURL url(strUrl);
|
|
|
|
// we wait for the network to come up
|
|
if ((url.GetProtocol() == "http" || url.GetProtocol() == "https") && !g_application.getNetwork().IsAvailable())
|
|
strXML = "<rss><item><title>"+g_localizeStrings.Get(15301)+"</title></item></rss>";
|
|
else
|
|
{
|
|
DWORD starttime = timeGetTime();
|
|
while ( (!m_bStop) && (nRetries > 0) )
|
|
{
|
|
DWORD currenttimer = timeGetTime() - starttime;
|
|
if (currenttimer > 15000)
|
|
{
|
|
CLog::Log(LOGERROR,"Timeout whilst retrieving %s", strUrl.c_str());
|
|
http.Cancel();
|
|
break;
|
|
}
|
|
nRetries--;
|
|
|
|
if (url.GetProtocol() != "http" && url.GetProtocol() != "https")
|
|
{
|
|
CFile file;
|
|
if (file.Open(strUrl))
|
|
{
|
|
char *yo = new char[(int)file.GetLength()+1];
|
|
file.Read(yo,file.GetLength());
|
|
yo[file.GetLength()] = '\0';
|
|
strXML = yo;
|
|
delete[] yo;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
if (http.Get(strUrl, strXML))
|
|
{
|
|
CLog::Log(LOGDEBUG, "Got rss feed: %s", strUrl.c_str());
|
|
break;
|
|
}
|
|
}
|
|
http.Cancel();
|
|
}
|
|
if ((!strXML.IsEmpty()) && m_pObserver)
|
|
{
|
|
// erase any <content:encoded> tags (also unsupported by tinyxml)
|
|
int iStart = strXML.Find("<content:encoded>");
|
|
int iEnd = 0;
|
|
while (iStart > 0)
|
|
{
|
|
// 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((LPSTR)strXML.c_str(),iFeed))
|
|
{
|
|
CLog::Log(LOGDEBUG, "Parsed rss feed: %s", strUrl.c_str());
|
|
}
|
|
}
|
|
}
|
|
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 CStdString &aString)
|
|
{
|
|
m_tagSet.push_back(aString);
|
|
}
|
|
|
|
void CRssReader::AddString(CStdStringW aString, int aColour, int iFeed)
|
|
{
|
|
if (m_rtlText)
|
|
m_strFeed[iFeed] = aString + m_strFeed[iFeed];
|
|
else
|
|
m_strFeed[iFeed] += aString;
|
|
|
|
int nStringLength = aString.GetLength();
|
|
|
|
for (int i = 0;i < nStringLength;i++)
|
|
{
|
|
aString[i] = (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");
|
|
map <CStdString, CStdStringW> mTagElements;
|
|
typedef pair <CStdString, CStdStringW> StrPair;
|
|
list <CStdString>::iterator i;
|
|
|
|
bool bEmpty=true;
|
|
|
|
// 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 > 0)
|
|
{
|
|
bEmpty = false;
|
|
TiXmlNode* childNode = itemNode->FirstChild();
|
|
mTagElements.clear();
|
|
while (childNode > 0)
|
|
{
|
|
CStdString strName = childNode->Value();
|
|
|
|
for (i = m_tagSet.begin(); i != m_tagSet.end(); i++)
|
|
{
|
|
if (!childNode->NoChildren() && i->Equals(strName))
|
|
{
|
|
CStdString htmlText = childNode->FirstChild()->Value();
|
|
|
|
// 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.Equals("div") || htmlText.Equals("span"))
|
|
{
|
|
htmlText = childNode->FirstChild()->FirstChild()->Value();
|
|
}
|
|
|
|
CStdStringW unicodeText,unicodeText2;
|
|
|
|
fromRSSToUTF16(htmlText, unicodeText2);
|
|
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++)
|
|
{
|
|
map <CStdString, CStdStringW>::iterator j = mTagElements.find(*i);
|
|
|
|
if (j == mTagElements.end())
|
|
continue;
|
|
|
|
CStdStringW& text = j->second;
|
|
AddString(text, rsscolour, iFeed);
|
|
rsscolour = RSS_COLOR_BODY;
|
|
text = " - ";
|
|
AddString(text, rsscolour, iFeed);
|
|
}
|
|
itemNode = itemNode->NextSiblingElement("item");
|
|
}
|
|
}
|
|
|
|
void CRssReader::fromRSSToUTF16(const CStdStringA& strSource, CStdStringW& strDest)
|
|
{
|
|
CStdString flippedStrSource;
|
|
CStdString strSourceUtf8;
|
|
|
|
g_charsetConverter.stringCharsetToUtf8(m_encoding, strSource, strSourceUtf8);
|
|
if (m_rtlText)
|
|
g_charsetConverter.utf8logicalToVisualBiDi(strSourceUtf8, flippedStrSource);
|
|
else
|
|
flippedStrSource = strSourceUtf8;
|
|
g_charsetConverter.utf8ToW(flippedStrSource, strDest, false);
|
|
}
|
|
|
|
bool CRssReader::Parse(LPSTR szBuffer, int iFeed)
|
|
{
|
|
m_xml.Clear();
|
|
m_xml.Parse((LPCSTR)szBuffer, 0, TIXML_ENCODING_LEGACY);
|
|
|
|
m_encoding = "UTF-8";
|
|
if (m_xml.RootElement())
|
|
{
|
|
TiXmlDeclaration *tiXmlDeclaration = m_xml.RootElement()->Parent()->FirstChild()->ToDeclaration();
|
|
if (tiXmlDeclaration != NULL && strlen(tiXmlDeclaration->Encoding()) > 0)
|
|
{
|
|
m_encoding = tiXmlDeclaration->Encoding();
|
|
}
|
|
}
|
|
|
|
CLog::Log(LOGDEBUG, "RSS feed encoding: %s", m_encoding.c_str());
|
|
|
|
return Parse(iFeed);
|
|
}
|
|
|
|
bool CRssReader::Parse(int iFeed)
|
|
{
|
|
TiXmlElement* rootXmlNode = m_xml.RootElement();
|
|
|
|
if (!rootXmlNode)
|
|
return false;
|
|
|
|
TiXmlElement* rssXmlNode = NULL;
|
|
|
|
CStdString strValue = rootXmlNode->Value();
|
|
if (( strValue.Find("rss") >= 0 ) || ( strValue.Find("rdf") >= 0 ))
|
|
{
|
|
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())
|
|
{
|
|
CStdString strChannel = titleNode->FirstChild()->Value();
|
|
CStdStringW strChannelUnicode;
|
|
fromRSSToUTF16(strChannel, strChannelUnicode);
|
|
AddString(strChannelUnicode, RSS_COLOR_CHANNEL, iFeed);
|
|
|
|
AddString(":", RSS_COLOR_CHANNEL, iFeed);
|
|
AddString(" ", RSS_COLOR_CHANNEL, iFeed);
|
|
}
|
|
|
|
GetNewsItems(channelXmlNode,iFeed);
|
|
}
|
|
|
|
GetNewsItems(rssXmlNode,iFeed);
|
|
|
|
// avoid trailing ' - '
|
|
if (m_strFeed[iFeed].size() > 3 && m_strFeed[iFeed].Mid(m_strFeed[iFeed].size()-3) == L" - " && !m_rtlText)
|
|
{
|
|
m_strFeed[iFeed].erase(m_strFeed[iFeed].length()-3);
|
|
m_strColors[iFeed].erase(m_strColors[iFeed].length()-3);
|
|
}
|
|
else if (m_strFeed[iFeed].size() > 3 && m_strFeed[iFeed].Mid(m_strFeed[iFeed].size()-3) == L" - " && m_rtlText)
|
|
{
|
|
m_strFeed[iFeed].erase(0, 3);
|
|
m_strColors[iFeed].erase(0, 3);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CRssReader::SetObserver(IRssObserver *observer)
|
|
{
|
|
m_pObserver = observer;
|
|
}
|
|
|
|
void CRssReader::UpdateObserver()
|
|
{
|
|
if (!m_pObserver) return;
|
|
vecText feed;
|
|
getFeed(feed);
|
|
if (feed.size() > 0)
|
|
{
|
|
g_graphicsContext.Lock();
|
|
if (m_pObserver) // need to check again when locked to make sure observer wasnt removed
|
|
m_pObserver->OnFeedUpdate(feed);
|
|
g_graphicsContext.Unlock();
|
|
}
|
|
}
|
|
|
|
void CRssReader::CheckForUpdates()
|
|
{
|
|
SYSTEMTIME time;
|
|
GetLocalTime(&time);
|
|
|
|
for (unsigned int i = 0;i < m_vecUpdateTimes.size(); ++i )
|
|
{
|
|
if (m_requestRefresh || ((time.wDay * 24 * 60) + (time.wHour * 60) + time.wMinute) - ((m_vecTimeStamps[i]->wDay * 24 * 60) + (m_vecTimeStamps[i]->wHour * 60) + m_vecTimeStamps[i]->wMinute) > m_vecUpdateTimes[i] )
|
|
{
|
|
CLog::Log(LOGDEBUG, "Updating RSS");
|
|
GetLocalTime(m_vecTimeStamps[i]);
|
|
AddToQueue(i);
|
|
}
|
|
}
|
|
|
|
m_requestRefresh = false;
|
|
}
|
|
|
|
CRssManager g_rssManager;
|
|
|
|
CRssManager::CRssManager()
|
|
{
|
|
m_bActive = false;
|
|
}
|
|
|
|
CRssManager::~CRssManager()
|
|
{
|
|
Stop();
|
|
}
|
|
|
|
void CRssManager::Start()
|
|
{
|
|
m_bActive = true;
|
|
}
|
|
|
|
void CRssManager::Stop()
|
|
{
|
|
m_bActive = false;
|
|
for (unsigned int i = 0; i < m_readers.size(); i++)
|
|
{
|
|
if (m_readers[i].reader)
|
|
{
|
|
delete m_readers[i].reader;
|
|
}
|
|
}
|
|
m_readers.clear();
|
|
}
|
|
|
|
// returns true if the reader doesn't need creating, false otherwise
|
|
bool CRssManager::GetReader(int controlID, int windowID, IRssObserver* observer, CRssReader *&reader)
|
|
{
|
|
// check to see if we've already created this reader
|
|
for (unsigned int i = 0; i < m_readers.size(); i++)
|
|
{
|
|
if (m_readers[i].controlID == controlID && m_readers[i].windowID == windowID)
|
|
{
|
|
reader = m_readers[i].reader;
|
|
reader->SetObserver(observer);
|
|
reader->UpdateObserver();
|
|
return true;
|
|
}
|
|
}
|
|
// need to create a new one
|
|
READERCONTROL readerControl;
|
|
readerControl.controlID = controlID;
|
|
readerControl.windowID = windowID;
|
|
reader = readerControl.reader = new CRssReader;
|
|
m_readers.push_back(readerControl);
|
|
return false;
|
|
}
|
|
|