MythTV  master
newssite.cpp
Go to the documentation of this file.
1 // QT headers
2 #include <QFile>
3 
4 // MythTV headers
5 #include <libmythbase/mythdate.h>
6 #include <libmythbase/mythdirs.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, m_name);
68  GetMythDownloadManager()->queueDownload(m_url, destFile, this, true);
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 std::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 
155 void 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  {
204  if (QFile::exists(filename))
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 
287 static 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 
294 static 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 
301 void 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 
457 void 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 
488 QString 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 }
build_compdb.args
args
Definition: build_compdb.py:11
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
NewsSite::RetrieveFailed
@ RetrieveFailed
Definition: newssite.h:58
isImage
static bool isImage(const QString &mimeType)
Definition: newssite.cpp:287
MythEvent::kMythEventMessage
static const Type kMythEventMessage
Definition: mythevent.h:79
getMythSortHelper
std::shared_ptr< MythSortHelper > getMythSortHelper(void)
Get a pointer to the MythSortHelper singleton.
Definition: mythsorthelper.cpp:129
NewsSite::podcast
bool podcast(void) const
Definition: newssite.cpp:108
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:1106
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:393
NewsSite::WriteFailed
@ WriteFailed
Definition: newssite.h:59
xbmcvfs.exists
bool exists(str path)
Definition: xbmcvfs.py:51
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
NewsSite::description
QString description(void) const
Definition: newssite.cpp:114
mythdirs.h
mythsorthelper.h
NewsSiteItem::m_podcast
bool m_podcast
Definition: newssite.h:33
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:14
MythEvent::Message
const QString & Message() const
Definition: mythevent.h:65
NewsSite::m_articleList
NewsArticle::List m_articleList
Definition: newssite.h:126
NewsSite::ReplaceHtmlChar
static QString ReplaceHtmlChar(const QString &orig)
Definition: newssite.cpp:488
isVideo
static bool isVideo(const QString &mimeType)
Definition: newssite.cpp:294
NewsSite::m_destDir
QString m_destDir
Definition: newssite.h:118
mythdate.h
NewsSite::m_updateErrorString
QString m_updateErrorString
Definition: newssite.h:122
mythlogging.h
MythSortHelper::doTitle
QString doTitle(const QString &title) const
Create the sortable form of an title string.
Definition: mythsorthelper.cpp:163
NewsSite::m_podcast
bool m_podcast
Definition: newssite.h:124
GetConfDir
QString GetConfDir(void)
Definition: mythdirs.cpp:256
NewsSite::m_state
State m_state
Definition: newssite.h:120
NewsSite::m_lock
QRecursiveMutex m_lock
Definition: newssite.h:111
NewsSiteItem::m_url
QString m_url
Definition: newssite.h:30
NewsArticle
Definition: newsarticle.h:10
NewsSite::errorMsg
QString errorMsg(void) const
Definition: newssite.cpp:84
NewsSite::m_imageURL
QString m_imageURL
Definition: newssite.h:123
NewsSite::timeSinceLastUpdate
std::chrono::minutes timeSinceLastUpdate(void) const
Definition: newssite.cpp:146
NewsSite::clearNewsArticles
void clearNewsArticles(void)
Definition: newssite.cpp:52
NewsSite::name
QString name(void) const
Definition: newssite.cpp:96
NewsSite::lastUpdated
QDateTime lastUpdated(void) const
Definition: newssite.cpp:140
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:1013
NewsSite::m_errorString
QString m_errorString
Definition: newssite.h:121
NewsSiteItem::m_name
QString m_name
Definition: newssite.h:28
NewsSite::process
void process(void)
Definition: newssite.cpp:221
NewsSite::Success
@ Success
Definition: newssite.h:60
newssite.h
NewsArticle::List
std::vector< NewsArticle > List
Definition: newsarticle.h:13
NewsSite::m_name
QString m_name
Definition: newssite.h:112
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:114
NewsSite::m_desc
QString m_desc
Definition: newssite.h:116
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:113
NewsSite::m_updated
QDateTime m_updated
Definition: newssite.h:117
NewsSite::parseAtom
void parseAtom(const QDomDocument &domDoc)
Definition: newssite.cpp:457
mythdownloadmanager.h
build_compdb.filename
filename
Definition: build_compdb.py:21
LOC
#define LOC
Definition: newssite.cpp:15
NewsSite::successful
bool successful(void) const
Definition: newssite.cpp:78
NewsSite::Retrieving
@ Retrieving
Definition: newssite.h:57
GetMythDownloadManager
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
Definition: mythdownloadmanager.cpp:145
NewsSite::parseRSS
void parseRSS(const QDomDocument &domDoc)
Definition: newssite.cpp:301
NewsSite::sortName
QString sortName(void) const
Definition: newssite.cpp:102