changed: update version strings for beta4
[xbmc.git] / xbmc / utils / RssReader.cpp
blob4bc96eced25d785bca145221a300813f7985d39b
1 /*
2 * Copyright (C) 2005-2008 Team XBMC
3 * http://www.xbmc.org
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, write to
17 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18 * http://www.gnu.org/copyleft/gpl.html
22 #include "RssReader.h"
23 #include "utils/HTMLUtil.h"
24 #include "../utils/Network.h"
25 #include "Application.h"
26 #include "CharsetConverter.h"
27 #include "URL.h"
28 #include "FileSystem/File.h"
29 #include "FileSystem/FileCurl.h"
30 #ifdef __APPLE__
31 #include "CocoaInterface.h"
32 #endif
33 #include "Settings.h"
34 #include "LocalizeStrings.h"
35 #include "GUIRSSControl.h"
36 #include "utils/TimeUtils.h"
37 #include "SingleLock.h"
38 #include "log.h"
40 using namespace std;
41 using namespace XFILE;
43 //////////////////////////////////////////////////////////////////////
44 // Construction/Destruction
45 //////////////////////////////////////////////////////////////////////
47 CRssReader::CRssReader() : CThread()
49 m_pObserver = NULL;
50 m_spacesBetweenFeeds = 0;
51 m_bIsRunning = false;
52 m_SavedScrollPos = 0;
55 CRssReader::~CRssReader()
57 if (m_pObserver)
58 m_pObserver->OnFeedRelease();
59 StopThread();
60 for (unsigned int i = 0; i < m_vecTimeStamps.size(); i++)
61 delete m_vecTimeStamps[i];
64 void CRssReader::Create(IRssObserver* aObserver, const vector<string>& aUrls, const vector<int> &times, int spacesBetweenFeeds, bool rtl)
66 CSingleLock lock(*this);
68 m_pObserver = aObserver;
69 m_spacesBetweenFeeds = spacesBetweenFeeds;
70 m_vecUrls = aUrls;
71 m_strFeed.resize(aUrls.size());
72 m_strColors.resize(aUrls.size());
73 // set update times
74 m_vecUpdateTimes = times;
75 m_rtlText = rtl;
76 m_requestRefresh = false;
78 // update each feed on creation
79 for (unsigned int i=0;i<m_vecUpdateTimes.size();++i )
81 AddToQueue(i);
82 SYSTEMTIME* time = new SYSTEMTIME;
83 GetLocalTime(time);
84 m_vecTimeStamps.push_back(time);
88 void CRssReader::requestRefresh()
90 m_requestRefresh = true;
93 void CRssReader::AddToQueue(int iAdd)
95 CSingleLock lock(*this);
96 if (iAdd < (int)m_vecUrls.size())
97 m_vecQueue.push_back(iAdd);
98 if (!m_bIsRunning)
100 StopThread();
101 m_bIsRunning = true;
102 CThread::Create(false, THREAD_MINSTACKSIZE);
106 void CRssReader::OnExit()
108 m_bIsRunning = false;
111 int CRssReader::GetQueueSize()
113 CSingleLock lock(*this);
114 return m_vecQueue.size();
117 void CRssReader::Process()
119 while (GetQueueSize())
121 EnterCriticalSection(*this);
123 int iFeed = m_vecQueue.front();
124 m_vecQueue.erase(m_vecQueue.begin());
126 m_strFeed[iFeed] = "";
127 m_strColors[iFeed] = "";
129 CFileCurl http;
130 http.SetUserAgent(g_settings.m_userAgent);
131 http.SetTimeout(2);
132 CStdString strXML;
133 CStdString strUrl = m_vecUrls[iFeed];
135 LeaveCriticalSection(*this);
137 int nRetries = 3;
138 CURL url(strUrl);
140 // we wait for the network to come up
141 if ((url.GetProtocol() == "http" || url.GetProtocol() == "https") && !g_application.getNetwork().IsAvailable(true))
142 strXML = "<rss><item><title>"+g_localizeStrings.Get(15301)+"</title></item></rss>";
143 else
145 unsigned int starttime = CTimeUtils::GetTimeMS();
146 while ( (!m_bStop) && (nRetries > 0) )
148 unsigned int currenttimer = CTimeUtils::GetTimeMS() - starttime;
149 if (currenttimer > 15000)
151 CLog::Log(LOGERROR,"Timeout whilst retrieving %s", strUrl.c_str());
152 http.Cancel();
153 break;
155 nRetries--;
157 if (url.GetProtocol() != "http" && url.GetProtocol() != "https")
159 CFile file;
160 if (file.Open(strUrl))
162 char *yo = new char[(int)file.GetLength()+1];
163 file.Read(yo,file.GetLength());
164 yo[file.GetLength()] = '\0';
165 strXML = yo;
166 delete[] yo;
167 break;
170 else
171 if (http.Get(strUrl, strXML))
173 CLog::Log(LOGDEBUG, "Got rss feed: %s", strUrl.c_str());
174 break;
177 http.Cancel();
179 if ((!strXML.IsEmpty()) && m_pObserver)
181 // erase any <content:encoded> tags (also unsupported by tinyxml)
182 int iStart = strXML.Find("<content:encoded>");
183 int iEnd = 0;
184 while (iStart > 0)
186 // get <content:encoded> end position
187 iEnd = strXML.Find("</content:encoded>", iStart) + 18;
189 // erase the section
190 strXML = strXML.erase(iStart, iEnd - iStart);
192 iStart = strXML.Find("<content:encoded>");
195 if (Parse((LPSTR)strXML.c_str(),iFeed))
197 CLog::Log(LOGDEBUG, "Parsed rss feed: %s", strUrl.c_str());
201 UpdateObserver();
204 void CRssReader::getFeed(vecText &text)
206 text.clear();
207 // double the spaces at the start of the set
208 for (int j = 0; j < m_spacesBetweenFeeds; j++)
209 text.push_back(L' ');
210 for (unsigned int i = 0; i < m_strFeed.size(); i++)
212 for (int j = 0; j < m_spacesBetweenFeeds; j++)
213 text.push_back(L' ');
215 for (unsigned int j = 0; j < m_strFeed[i].size(); j++)
217 character_t letter = m_strFeed[i][j] | ((m_strColors[i][j] - 48) << 16);
218 text.push_back(letter);
223 void CRssReader::AddTag(const CStdString &aString)
225 m_tagSet.push_back(aString);
228 void CRssReader::AddString(CStdStringW aString, int aColour, int iFeed)
230 if (m_rtlText)
231 m_strFeed[iFeed] = aString + m_strFeed[iFeed];
232 else
233 m_strFeed[iFeed] += aString;
235 int nStringLength = aString.GetLength();
237 for (int i = 0;i < nStringLength;i++)
239 aString[i] = (CHAR) (48 + aColour);
242 if (m_rtlText)
243 m_strColors[iFeed] = aString + m_strColors[iFeed];
244 else
245 m_strColors[iFeed] += aString;
248 void CRssReader::GetNewsItems(TiXmlElement* channelXmlNode, int iFeed)
250 HTML::CHTMLUtil html;
252 TiXmlElement * itemNode = channelXmlNode->FirstChildElement("item");
253 map <CStdString, CStdStringW> mTagElements;
254 typedef pair <CStdString, CStdStringW> StrPair;
255 list <CStdString>::iterator i;
257 bool bEmpty=true;
259 // Add the title tag in if we didn't pass any tags in at all
260 // Represents default behaviour before configurability
262 if (m_tagSet.empty())
263 AddTag("title");
265 while (itemNode > 0)
267 bEmpty = false;
268 TiXmlNode* childNode = itemNode->FirstChild();
269 mTagElements.clear();
270 while (childNode > 0)
272 CStdString strName = childNode->Value();
274 for (i = m_tagSet.begin(); i != m_tagSet.end(); i++)
276 if (!childNode->NoChildren() && i->Equals(strName))
278 CStdString htmlText = childNode->FirstChild()->Value();
280 // This usually happens in right-to-left languages where they want to
281 // specify in the RSS body that the text should be RTL.
282 // <title>
283 // <div dir="RTL">��� ����: ���� �� �����</div>
284 // </title>
285 if (htmlText.Equals("div") || htmlText.Equals("span"))
287 htmlText = childNode->FirstChild()->FirstChild()->Value();
290 CStdStringW unicodeText,unicodeText2;
292 fromRSSToUTF16(htmlText, unicodeText2);
293 html.ConvertHTMLToW(unicodeText2, unicodeText);
295 mTagElements.insert(StrPair(*i, unicodeText));
298 childNode = childNode->NextSibling();
301 int rsscolour = RSS_COLOR_HEADLINE;
302 for (i = m_tagSet.begin();i != m_tagSet.end();i++)
304 map <CStdString, CStdStringW>::iterator j = mTagElements.find(*i);
306 if (j == mTagElements.end())
307 continue;
309 CStdStringW& text = j->second;
310 AddString(text, rsscolour, iFeed);
311 rsscolour = RSS_COLOR_BODY;
312 text = " - ";
313 AddString(text, rsscolour, iFeed);
315 itemNode = itemNode->NextSiblingElement("item");
319 void CRssReader::fromRSSToUTF16(const CStdStringA& strSource, CStdStringW& strDest)
321 CStdString flippedStrSource;
322 CStdString strSourceUtf8;
324 g_charsetConverter.stringCharsetToUtf8(m_encoding, strSource, strSourceUtf8);
325 if (m_rtlText)
326 g_charsetConverter.utf8logicalToVisualBiDi(strSourceUtf8, flippedStrSource);
327 else
328 flippedStrSource = strSourceUtf8;
329 g_charsetConverter.utf8ToW(flippedStrSource, strDest, false);
332 bool CRssReader::Parse(LPSTR szBuffer, int iFeed)
334 m_xml.Clear();
335 m_xml.Parse((LPCSTR)szBuffer, 0, TIXML_ENCODING_LEGACY);
337 m_encoding = "UTF-8";
338 if (m_xml.RootElement())
340 TiXmlDeclaration *tiXmlDeclaration = m_xml.RootElement()->Parent()->FirstChild()->ToDeclaration();
341 if (tiXmlDeclaration != NULL && strlen(tiXmlDeclaration->Encoding()) > 0)
343 m_encoding = tiXmlDeclaration->Encoding();
347 CLog::Log(LOGDEBUG, "RSS feed encoding: %s", m_encoding.c_str());
349 return Parse(iFeed);
352 bool CRssReader::Parse(int iFeed)
354 TiXmlElement* rootXmlNode = m_xml.RootElement();
356 if (!rootXmlNode)
357 return false;
359 TiXmlElement* rssXmlNode = NULL;
361 CStdString strValue = rootXmlNode->Value();
362 if (( strValue.Find("rss") >= 0 ) || ( strValue.Find("rdf") >= 0 ))
364 rssXmlNode = rootXmlNode;
366 else
368 // Unable to find root <rss> or <rdf> node
369 return false;
372 TiXmlElement* channelXmlNode = rssXmlNode->FirstChildElement("channel");
373 if (channelXmlNode)
375 TiXmlElement* titleNode = channelXmlNode->FirstChildElement("title");
376 if (titleNode && !titleNode->NoChildren())
378 CStdString strChannel = titleNode->FirstChild()->Value();
379 CStdStringW strChannelUnicode;
380 fromRSSToUTF16(strChannel, strChannelUnicode);
381 AddString(strChannelUnicode, RSS_COLOR_CHANNEL, iFeed);
383 AddString(":", RSS_COLOR_CHANNEL, iFeed);
384 AddString(" ", RSS_COLOR_CHANNEL, iFeed);
387 GetNewsItems(channelXmlNode,iFeed);
390 GetNewsItems(rssXmlNode,iFeed);
392 // avoid trailing ' - '
393 if (m_strFeed[iFeed].size() > 3 && m_strFeed[iFeed].Mid(m_strFeed[iFeed].size()-3) == L" - ")
395 if (m_rtlText)
397 m_strFeed[iFeed].erase(0, 3);
398 m_strColors[iFeed].erase(0, 3);
400 else
402 m_strFeed[iFeed].erase(m_strFeed[iFeed].length()-3);
403 m_strColors[iFeed].erase(m_strColors[iFeed].length()-3);
406 return true;
409 void CRssReader::SetObserver(IRssObserver *observer)
411 m_pObserver = observer;
414 void CRssReader::UpdateObserver()
416 if (!m_pObserver) return;
417 vecText feed;
418 getFeed(feed);
419 if (feed.size() > 0)
421 g_graphicsContext.Lock();
422 if (m_pObserver) // need to check again when locked to make sure observer wasnt removed
423 m_pObserver->OnFeedUpdate(feed);
424 g_graphicsContext.Unlock();
428 void CRssReader::CheckForUpdates()
430 SYSTEMTIME time;
431 GetLocalTime(&time);
433 for (unsigned int i = 0;i < m_vecUpdateTimes.size(); ++i )
435 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] )
437 CLog::Log(LOGDEBUG, "Updating RSS");
438 GetLocalTime(m_vecTimeStamps[i]);
439 AddToQueue(i);
443 m_requestRefresh = false;
446 CRssManager g_rssManager;
448 CRssManager::CRssManager()
450 m_bActive = false;
453 CRssManager::~CRssManager()
455 Stop();
458 void CRssManager::Start()
460 m_bActive = true;
463 void CRssManager::Stop()
465 m_bActive = false;
466 for (unsigned int i = 0; i < m_readers.size(); i++)
468 if (m_readers[i].reader)
470 delete m_readers[i].reader;
473 m_readers.clear();
476 // returns true if the reader doesn't need creating, false otherwise
477 bool CRssManager::GetReader(int controlID, int windowID, IRssObserver* observer, CRssReader *&reader)
479 // check to see if we've already created this reader
480 for (unsigned int i = 0; i < m_readers.size(); i++)
482 if (m_readers[i].controlID == controlID && m_readers[i].windowID == windowID)
484 reader = m_readers[i].reader;
485 reader->SetObserver(observer);
486 reader->UpdateObserver();
487 return true;
490 // need to create a new one
491 READERCONTROL readerControl;
492 readerControl.controlID = controlID;
493 readerControl.windowID = windowID;
494 reader = readerControl.reader = new CRssReader;
495 m_readers.push_back(readerControl);
496 return false;