MythTV master
newssite.cpp
Go to the documentation of this file.
1// QT headers
2#include <QFile>
3
4// MythTV headers
11
12// MythNews headers
13#include "newssite.h"
14
15#define LOC QString("NewsSite: ")
16#define LOC_WARN QString("NewsSite, Warning: ")
17#define LOC_ERR QString("NewsSite, Error: ")
18
19NewsSite::NewsSite(QString name, const QString &url,
20 QDateTime updated, const bool podcast) :
21 m_name(std::move(name)), m_url(url), m_urlReq(url),
22 m_updated(std::move(updated)),
23 m_destDir(GetConfDir()+"/MythNews"),
24 m_podcast(podcast)
25{
26 std::shared_ptr<MythSortHelper>sh = getMythSortHelper();
27 m_sortName = sh->doTitle(m_name);
28}
29
31{
32 QMutexLocker locker(&m_lock);
35 m_articleList.clear();
36 QObject::deleteLater();
37}
38
40{
41 QMutexLocker locker(&m_lock);
44}
45
47{
48 QMutexLocker locker(&m_lock);
49 m_articleList.push_back(item);
50}
51
53{
54 QMutexLocker locker(&m_lock);
55 m_articleList.clear();
56}
57
59{
60 QMutexLocker locker(&m_lock);
61
62 stop();
64 m_errorString.clear();
65 m_updateErrorString.clear();
66 m_articleList.clear();
67 QString destFile = QString("%1/%2").arg(m_destDir, m_name);
68 GetMythDownloadManager()->queueDownload(m_url, destFile, this, true);
69}
70
72{
73 QMutexLocker locker(&m_lock);
76}
77
78bool NewsSite::successful(void) const
79{
80 QMutexLocker locker(&m_lock);
81 return (m_state == NewsSite::Success);
82}
83
84QString NewsSite::errorMsg(void) const
85{
86 QMutexLocker locker(&m_lock);
87 return m_errorString;
88}
89
90QString NewsSite::url(void) const
91{
92 QMutexLocker locker(&m_lock);
93 return m_url;
94}
95
96QString NewsSite::name(void) const
97{
98 QMutexLocker locker(&m_lock);
99 return m_name;
100}
101
102QString NewsSite::sortName(void) const
103{
104 QMutexLocker locker(&m_lock);
105 return m_sortName;
106}
107
108bool NewsSite::podcast(void) const
109{
110 QMutexLocker locker(&m_lock);
111 return m_podcast;
112}
113
114QString NewsSite::description(void) const
115{
116 QMutexLocker locker(&m_lock);
117 QString result;
118
119 if (!m_desc.isEmpty())
120 result = m_desc;
121
122 if (!m_errorString.isEmpty())
123 result += m_desc.isEmpty() ? m_errorString : '\n' + m_errorString;
124
125 return result;
126}
127
128QString NewsSite::imageURL(void) const
129{
130 QMutexLocker locker(&m_lock);
131 return m_imageURL;
132}
133
135{
136 QMutexLocker locker(&m_lock);
137 return m_articleList;
138}
139
140QDateTime NewsSite::lastUpdated(void) const
141{
142 QMutexLocker locker(&m_lock);
143 return m_updated;
144}
145
146std::chrono::minutes NewsSite::timeSinceLastUpdate(void) const
147{
148 QMutexLocker locker(&m_lock);
149
150 QDateTime curTime(MythDate::current());
151 auto secs = std::chrono::seconds(m_updated.secsTo(curTime));
152 return duration_cast<std::chrono::minutes>(secs);
153}
154
155void NewsSite::customEvent(QEvent *event)
156{
157 if (event->type() == MythEvent::kMythEventMessage)
158 {
159 auto *me = dynamic_cast<MythEvent *>(event);
160 if (me == nullptr)
161 return;
162 QStringList tokens = me->Message().split(" ", Qt::SkipEmptyParts);
163
164 if (tokens.isEmpty())
165 return;
166
167 if (tokens[0] == "DOWNLOAD_FILE")
168 {
169 QStringList args = me->ExtraDataList();
170
171 if (tokens[1] == "UPDATE")
172 {
173 // could update a progressbar here
174 }
175 else if (tokens[1] == "FINISHED")
176 {
177 const QString& url = args[0];
178 const QString& filename = args[1];
179 int fileSize = args[2].toInt();
180 const QString& errorStr = args[3];
181 int errorCode = args[4].toInt();
182
183 if ((errorCode != 0) || (fileSize == 0))
184 {
185 LOG(VB_GENERAL, LOG_ERR, LOC + "HTTP Connection Error - " +
186 QString("%1\n\t\t\tExplanation: %2: %3, filesize: %4, filename: %5")
187 .arg(url).arg(errorCode).arg(errorStr).arg(fileSize).arg(filename));
188
190 m_updateErrorString = QString("%1: %2").arg(errorCode).arg(errorStr);
191 emit finished(this);
192 return;
193 }
194
195 m_updateErrorString.clear();
196 //m_data = data;
197
198 if (m_name.isEmpty())
199 {
201 }
202 else
203 {
205 {
208 }
209 else
210 {
212 }
213 }
214
215 emit finished(this);
216 }
217 }
218 }
219}
220
222{
223 QMutexLocker locker(&m_lock);
224
225 m_articleList.clear();
226
227 m_errorString = "";
228 if (RetrieveFailed == m_state)
229 m_errorString = tr("Retrieve Failed. ")+"\n";
230
231 QDomDocument domDoc;
232
233 QFile xmlFile(m_destDir+QString("/")+m_name);
234 if (!xmlFile.exists())
235 {
236 insertNewsArticle(NewsArticle(tr("Failed to retrieve news")));
237 m_errorString += tr("No Cached News.");
238 if (!m_updateErrorString.isEmpty())
240 return;
241 }
242
243 if (!xmlFile.open(QIODevice::ReadOnly))
244 {
245 insertNewsArticle(NewsArticle(tr("Failed to retrieve news")));
246 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to open xmlfile");
247 if (!m_updateErrorString.isEmpty())
249 return;
250 }
251
252 if (!domDoc.setContent(&xmlFile))
253 {
254 insertNewsArticle(NewsArticle(tr("Failed to retrieve news")));
255 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to set content from xmlfile");
256 m_errorString += tr("Failed to read downloaded file.");
257 if (!m_updateErrorString.isEmpty())
259 return;
260 }
261
262 if (RetrieveFailed == m_state)
263 {
264 m_errorString += tr("Showing Cached News.");
265 if (!m_updateErrorString.isEmpty())
267 }
268
269 //Check the type of the feed
270 QString rootName = domDoc.documentElement().nodeName();
271 if (rootName == "rss" || rootName == "rdf:RDF")
272 {
273 parseRSS(domDoc);
274 xmlFile.close();
275 return;
276 }
277 if (rootName == "feed")
278 {
279 parseAtom(domDoc);
280 xmlFile.close();
281 return;
282 }
283 LOG(VB_GENERAL, LOG_ERR, LOC + "XML-file is not valid RSS-feed");
284 m_errorString += tr("XML-file is not valid RSS-feed");
285}
286
287static bool isImage(const QString &mimeType)
288{
289 return mimeType == "image/png" || mimeType == "image/jpeg" ||
290 mimeType == "image/jpg" || mimeType == "image/gif" ||
291 mimeType == "image/bmp";
292}
293
294static bool isVideo(const QString &mimeType)
295{
296 return mimeType == "video/mpeg" || mimeType == "video/x-ms-wmv" ||
297 mimeType == "application/x-troff-msvideo" || mimeType == "video/avi" ||
298 mimeType == "video/msvideo" || mimeType == "video/x-msvideo";
299}
300
301void NewsSite::parseRSS(const QDomDocument& domDoc)
302{
303 QMutexLocker locker(&m_lock);
304
305 QDomNode channelNode = domDoc.documentElement().namedItem("channel");
306
307 m_desc = channelNode.namedItem("description")
308 .toElement().text().simplified();
309
310 QDomNode imageNode = channelNode.namedItem("image");
311 if (!imageNode.isNull())
312 m_imageURL = imageNode.namedItem("url").toElement().text().simplified();
313
314 QDomNodeList items = domDoc.elementsByTagName("item");
315
316 for (unsigned int i = 0; i < (unsigned) items.count(); i++)
317 {
318 QDomNode itemNode = items.item(i);
319 QString title = ReplaceHtmlChar(itemNode.namedItem("title")
320 .toElement().text().simplified());
321
322 QDomNode descNode = itemNode.namedItem("description");
323 QString description;
324 if (!descNode.isNull())
325 {
326 description = descNode.toElement().text().simplified();
328 }
329
330 QDomNode linkNode = itemNode.namedItem("link");
331 QString url;
332 if (!linkNode.isNull())
333 url = linkNode.toElement().text().simplified();
334
335 QDomNode enclosureNode = itemNode.namedItem("enclosure");
336 QString enclosure;
337 QString enclosure_type;
338 QString thumbnail;
339 if (!enclosureNode.isNull())
340 {
341 QDomAttr enclosureURL = enclosureNode.toElement()
342 .attributeNode("url");
343
344 if (!enclosureURL.isNull())
345 enclosure = enclosureURL.value();
346
347 QDomAttr enclosureType = enclosureNode.toElement()
348 .attributeNode("type");
349 if (!enclosureType.isNull())
350 {
351 enclosure_type = enclosureType.value();
352
353 if (enclosure_type == "image/jpeg")
354 {
355 thumbnail = enclosure;
356 enclosure.clear();
357 }
358
359 // fix for broken feeds that don't add the enclosure type
360 if (enclosure_type == "" || enclosure_type.isNull())
361 {
362 QStringList imageExtensions = QStringList() << ".jpg" << ".jpeg" << ".png" << ".gif";
363 for (int x = 0; x < imageExtensions.count(); x++)
364 {
365 if (enclosure.endsWith(imageExtensions[x], Qt::CaseInsensitive))
366 {
367 thumbnail = enclosure;
368 enclosure.clear();
369 break;
370 }
371 }
372 }
373 }
374 else
375 {
376 // fix broken feeds (like RT) that don't add the enclosure type
377 enclosure.clear();
378 }
379 }
380
382 // From this point forward, we process RSS 2.0 media tags.
383 // Please put all other tag processing before this comment.
384 // See http://www.rssboard.org/media-rss for details
386
387 // Some media: tags can be enclosed in a media:group item.
388 // If this item is present, use it to find the media tags,
389 // otherwise, proceed.
390 QDomNode mediaGroup = itemNode.namedItem("media:group");
391 if (!mediaGroup.isNull())
392 itemNode = mediaGroup;
393
394 QDomNode thumbNode = itemNode.namedItem("media:thumbnail");
395 if (!thumbNode.isNull())
396 {
397 QDomAttr thumburl = thumbNode.toElement().attributeNode("url");
398 if (!thumburl.isNull())
399 thumbnail = thumburl.value();
400 }
401
402 QDomNode playerNode = itemNode.namedItem("media:player");
403 QString mediaurl;
404 if (!playerNode.isNull())
405 {
406 QDomAttr mediaURL = playerNode.toElement().attributeNode("url");
407 if (!mediaURL.isNull())
408 mediaurl = mediaURL.value();
409 }
410
411 // If present, the media:description superscedes the RSS description
412 descNode = itemNode.namedItem("media:description");
413 if (!descNode.isNull())
414 description = descNode.toElement().text().simplified();
415
416 // parse any media:content items looking for any images or videos
417 QDomElement e = itemNode.toElement();
418 QDomNodeList mediaNodes = e.elementsByTagName("media:content");
419 for (int x = 0; x < mediaNodes.count(); x++)
420 {
421 QString medium;
422 QString type;
423 QString url2;
424
425 QDomElement mediaElement = mediaNodes.at(x).toElement();
426
427 if (mediaElement.isNull())
428 continue;
429
430 if (mediaElement.hasAttribute("medium"))
431 medium = mediaElement.attributeNode("medium").value();
432
433 if (mediaElement.hasAttribute("type"))
434 type = mediaElement.attributeNode("type").value();
435
436 if (mediaElement.hasAttribute("url"))
437 url2 = mediaElement.attributeNode("url").value();
438
439 LOG(VB_GENERAL, LOG_DEBUG,
440 QString("parseRSS found media:content: medium: %1, type: %2, url: %3")
441 .arg(medium, type, url2));
442
443 // if this is an image use it as the thumbnail if we haven't found one yet
444 if (thumbnail.isEmpty() && (medium == "image" || isImage(type)))
445 thumbnail = url2;
446
447 // if this is a video use it as the enclosure if we haven't found one yet
448 if (enclosure.isEmpty() && (medium == "video" || isVideo(type)))
449 enclosure = url2;
450 }
451
453 thumbnail, mediaurl, enclosure));
454 }
455}
456
457void NewsSite::parseAtom(const QDomDocument& domDoc)
458{
459 QDomNodeList entries = domDoc.elementsByTagName("entry");
460
461 for (unsigned int i = 0; i < (unsigned) entries.count(); i++)
462 {
463 QDomNode itemNode = entries.item(i);
464 QString title = ReplaceHtmlChar(itemNode.namedItem("title").toElement()
465 .text().simplified());
466
467 QDomNode summNode = itemNode.namedItem("summary");
468 QString description;
469 if (!summNode.isNull())
470 {
472 summNode.toElement().text().simplified());
473 }
474
475 QDomNode linkNode = itemNode.namedItem("link");
476 QString url;
477 if (!linkNode.isNull())
478 {
479 QDomAttr linkHref = linkNode.toElement().attributeNode("href");
480 if (!linkHref.isNull())
481 url = linkHref.value();
482 }
483
485 }
486}
487
488QString NewsSite::ReplaceHtmlChar(const QString &orig)
489{
490 if (orig.isEmpty())
491 return orig;
492
493 QString s = orig;
494 s.replace("&amp;", "&");
495 s.replace("&pound;", "£");
496 s.replace("&lt;", "<");
497 s.replace("&gt;", ">");
498 s.replace("&quot;", "\"");
499 s.replace("&apos;", "\'");
500 s.replace("&#8230;",QChar(8230));
501 s.replace("&#233;",QChar(233));
502 s.replace("&mdash;", QChar(8212));
503 s.replace("&nbsp;", " ");
504 s.replace("&#160;", QChar(160));
505 s.replace("&#225;", QChar(225));
506 s.replace("&#8216;", QChar(8216));
507 s.replace("&#8217;", QChar(8217));
508 s.replace("&#039;", "\'");
509 s.replace("&ndash;", QChar(8211));
510 // german umlauts
511 s.replace("&auml;", QChar(0x00e4));
512 s.replace("&ouml;", QChar(0x00f6));
513 s.replace("&uuml;", QChar(0x00fc));
514 s.replace("&Auml;", QChar(0x00c4));
515 s.replace("&Ouml;", QChar(0x00d6));
516 s.replace("&Uuml;", QChar(0x00dc));
517 s.replace("&szlig;", QChar(0x00df));
518
519 return s;
520}
void cancelDownload(const QString &url, bool block=true)
Cancel a queued or current download.
void removeListener(QObject *caller)
Disconnects the specified caller from any existing MythDownloadInfo instances.
void queueDownload(const QString &url, const QString &dest, QObject *caller, bool reload=false)
Adds a url to the download queue.
This class is used as a container for messages.
Definition: mythevent.h:17
const QString & Message() const
Definition: mythevent.h:65
static const Type kMythEventMessage
Definition: mythevent.h:79
std::vector< NewsArticle > List
Definition: newsarticle.h:13
bool m_podcast
Definition: newssite.h:124
void insertNewsArticle(const NewsArticle &item)
Definition: newssite.cpp:46
QString m_name
Definition: newssite.h:112
~NewsSite() override
Definition: newssite.cpp:39
QString imageURL(void) const
Definition: newssite.cpp:128
void stop(void)
Definition: newssite.cpp:71
void parseAtom(const QDomDocument &domDoc)
Definition: newssite.cpp:457
QString m_destDir
Definition: newssite.h:118
QString m_url
Definition: newssite.h:114
bool podcast(void) const
Definition: newssite.cpp:108
NewsSite(QString name, const QString &url, QDateTime updated, bool podcast)
Definition: newssite.cpp:19
NewsArticle::List m_articleList
Definition: newssite.h:126
QString m_errorString
Definition: newssite.h:121
QString errorMsg(void) const
Definition: newssite.cpp:84
QString sortName(void) const
Definition: newssite.cpp:102
NewsArticle::List GetArticleList(void) const
Definition: newssite.cpp:134
QString m_sortName
Definition: newssite.h:113
State m_state
Definition: newssite.h:120
void parseRSS(const QDomDocument &domDoc)
Definition: newssite.cpp:301
void customEvent(QEvent *event) override
Definition: newssite.cpp:155
QRecursiveMutex m_lock
Definition: newssite.h:111
bool successful(void) const
Definition: newssite.cpp:78
void finished(NewsSite *item)
QString name(void) const
Definition: newssite.cpp:96
void retrieve(void)
Definition: newssite.cpp:58
QDateTime m_updated
Definition: newssite.h:117
QString m_updateErrorString
Definition: newssite.h:122
QDateTime lastUpdated(void) const
Definition: newssite.cpp:140
static QString ReplaceHtmlChar(const QString &orig)
Definition: newssite.cpp:488
QString m_imageURL
Definition: newssite.h:123
@ WriteFailed
Definition: newssite.h:59
@ Retrieving
Definition: newssite.h:57
@ Success
Definition: newssite.h:60
@ RetrieveFailed
Definition: newssite.h:58
virtual void deleteLater()
Definition: newssite.cpp:30
QString url(void) const
Definition: newssite.cpp:90
QString m_desc
Definition: newssite.h:116
std::chrono::minutes timeSinceLastUpdate(void) const
Definition: newssite.cpp:146
void clearNewsArticles(void)
Definition: newssite.cpp:52
QString description(void) const
Definition: newssite.cpp:114
void process(void)
Definition: newssite.cpp:221
QString GetConfDir(void)
Definition: mythdirs.cpp:263
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
std::shared_ptr< MythSortHelper > getMythSortHelper(void)
Get a pointer to the MythSortHelper singleton.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
STL namespace.
bool exists(str path)
Definition: xbmcvfs.py:51
#define LOC
Definition: newssite.cpp:15
static bool isImage(const QString &mimeType)
Definition: newssite.cpp:287
static bool isVideo(const QString &mimeType)
Definition: newssite.cpp:294