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