2 * Copyright (C) 2005-2008 Team XBMC
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)
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"
28 #include "FileSystem/File.h"
29 #include "FileSystem/FileCurl.h"
31 #include "CocoaInterface.h"
34 #include "LocalizeStrings.h"
35 #include "GUIRSSControl.h"
36 #include "utils/TimeUtils.h"
37 #include "SingleLock.h"
41 using namespace XFILE
;
43 //////////////////////////////////////////////////////////////////////
44 // Construction/Destruction
45 //////////////////////////////////////////////////////////////////////
47 CRssReader::CRssReader() : CThread()
50 m_spacesBetweenFeeds
= 0;
55 CRssReader::~CRssReader()
58 m_pObserver
->OnFeedRelease();
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> ×
, int spacesBetweenFeeds
, bool rtl
)
66 CSingleLock
lock(*this);
68 m_pObserver
= aObserver
;
69 m_spacesBetweenFeeds
= spacesBetweenFeeds
;
71 m_strFeed
.resize(aUrls
.size());
72 m_strColors
.resize(aUrls
.size());
74 m_vecUpdateTimes
= times
;
76 m_requestRefresh
= false;
78 // update each feed on creation
79 for (unsigned int i
=0;i
<m_vecUpdateTimes
.size();++i
)
82 SYSTEMTIME
* time
= new SYSTEMTIME
;
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
);
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
] = "";
130 http
.SetUserAgent(g_settings
.m_userAgent
);
133 CStdString strUrl
= m_vecUrls
[iFeed
];
135 LeaveCriticalSection(*this);
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>";
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());
157 if (url
.GetProtocol() != "http" && url
.GetProtocol() != "https")
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';
171 if (http
.Get(strUrl
, strXML
))
173 CLog::Log(LOGDEBUG
, "Got rss feed: %s", strUrl
.c_str());
179 if ((!strXML
.IsEmpty()) && m_pObserver
)
181 // erase any <content:encoded> tags (also unsupported by tinyxml)
182 int iStart
= strXML
.Find("<content:encoded>");
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((LPSTR
)strXML
.c_str(),iFeed
))
197 CLog::Log(LOGDEBUG
, "Parsed rss feed: %s", strUrl
.c_str());
204 void CRssReader::getFeed(vecText
&text
)
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
)
231 m_strFeed
[iFeed
] = aString
+ m_strFeed
[iFeed
];
233 m_strFeed
[iFeed
] += aString
;
235 int nStringLength
= aString
.GetLength();
237 for (int i
= 0;i
< nStringLength
;i
++)
239 aString
[i
] = (CHAR
) (48 + aColour
);
243 m_strColors
[iFeed
] = aString
+ m_strColors
[iFeed
];
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
;
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())
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.
283 // <div dir="RTL">��� ����: ���� �� �����</div>
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())
309 CStdStringW
& text
= j
->second
;
310 AddString(text
, rsscolour
, iFeed
);
311 rsscolour
= RSS_COLOR_BODY
;
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
);
326 g_charsetConverter
.utf8logicalToVisualBiDi(strSourceUtf8
, flippedStrSource
);
328 flippedStrSource
= strSourceUtf8
;
329 g_charsetConverter
.utf8ToW(flippedStrSource
, strDest
, false);
332 bool CRssReader::Parse(LPSTR szBuffer
, int iFeed
)
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());
352 bool CRssReader::Parse(int iFeed
)
354 TiXmlElement
* rootXmlNode
= m_xml
.RootElement();
359 TiXmlElement
* rssXmlNode
= NULL
;
361 CStdString strValue
= rootXmlNode
->Value();
362 if (( strValue
.Find("rss") >= 0 ) || ( strValue
.Find("rdf") >= 0 ))
364 rssXmlNode
= rootXmlNode
;
368 // Unable to find root <rss> or <rdf> node
372 TiXmlElement
* channelXmlNode
= rssXmlNode
->FirstChildElement("channel");
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
" - ")
397 m_strFeed
[iFeed
].erase(0, 3);
398 m_strColors
[iFeed
].erase(0, 3);
402 m_strFeed
[iFeed
].erase(m_strFeed
[iFeed
].length()-3);
403 m_strColors
[iFeed
].erase(m_strColors
[iFeed
].length()-3);
409 void CRssReader::SetObserver(IRssObserver
*observer
)
411 m_pObserver
= observer
;
414 void CRssReader::UpdateObserver()
416 if (!m_pObserver
) return;
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()
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
]);
443 m_requestRefresh
= false;
446 CRssManager g_rssManager
;
448 CRssManager::CRssManager()
453 CRssManager::~CRssManager()
458 void CRssManager::Start()
463 void CRssManager::Stop()
466 for (unsigned int i
= 0; i
< m_readers
.size(); i
++)
468 if (m_readers
[i
].reader
)
470 delete m_readers
[i
].reader
;
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();
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
);