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 #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 url = args[0];
182  QString filename = args[1];
183  int fileSize = args[2].toInt();
184  QString errorStr = args[3];
185  int errorCode = args[4].toInt();
186 
187  if ((errorCode != 0) || (fileSize == 0))
188  {
189  LOG(VB_GENERAL, LOG_ERR, LOC + "HTTP Connection Error - " +
190  QString("%1\n\t\t\tExplanation: %2: %3, filesize: %4, filename: %5")
191  .arg(url).arg(errorCode).arg(errorStr).arg(fileSize).arg(filename));
192 
194  m_updateErrorString = QString("%1: %2").arg(errorCode).arg(errorStr);
195  emit finished(this);
196  return;
197  }
198 
199  m_updateErrorString.clear();
200  //m_data = data;
201 
202  if (m_name.isEmpty())
203  {
205  }
206  else
207  {
208  if (QFile::exists(filename))
209  {
212  }
213  else
214  {
216  }
217  }
218 
219  emit finished(this);
220  }
221  }
222  }
223 }
224 
226 {
227  QMutexLocker locker(&m_lock);
228 
229  m_articleList.clear();
230 
231  m_errorString = "";
232  if (RetrieveFailed == m_state)
233  m_errorString = tr("Retrieve Failed. ")+"\n";
234 
235  QDomDocument domDoc;
236 
237  QFile xmlFile(m_destDir+QString("/")+m_name);
238  if (!xmlFile.exists())
239  {
240  insertNewsArticle(NewsArticle(tr("Failed to retrieve news")));
241  m_errorString += tr("No Cached News.");
242  if (!m_updateErrorString.isEmpty())
244  return;
245  }
246 
247  if (!xmlFile.open(QIODevice::ReadOnly))
248  {
249  insertNewsArticle(NewsArticle(tr("Failed to retrieve news")));
250  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to open xmlfile");
251  if (!m_updateErrorString.isEmpty())
253  return;
254  }
255 
256  if (!domDoc.setContent(&xmlFile))
257  {
258  insertNewsArticle(NewsArticle(tr("Failed to retrieve news")));
259  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to set content from xmlfile");
260  m_errorString += tr("Failed to read downloaded file.");
261  if (!m_updateErrorString.isEmpty())
263  return;
264  }
265 
266  if (RetrieveFailed == m_state)
267  {
268  m_errorString += tr("Showing Cached News.");
269  if (!m_updateErrorString.isEmpty())
271  }
272 
273  //Check the type of the feed
274  QString rootName = domDoc.documentElement().nodeName();
275  if (rootName == "rss" || rootName == "rdf:RDF")
276  {
277  parseRSS(domDoc);
278  xmlFile.close();
279  return;
280  }
281  if (rootName == "feed")
282  {
283  parseAtom(domDoc);
284  xmlFile.close();
285  return;
286  }
287  LOG(VB_GENERAL, LOG_ERR, LOC + "XML-file is not valid RSS-feed");
288  m_errorString += tr("XML-file is not valid RSS-feed");
289 }
290 
291 static bool isImage(const QString &mimeType)
292 {
293  return mimeType == "image/png" || mimeType == "image/jpeg" ||
294  mimeType == "image/jpg" || mimeType == "image/gif" ||
295  mimeType == "image/bmp";
296 }
297 
298 static bool isVideo(const QString &mimeType)
299 {
300  return mimeType == "video/mpeg" || mimeType == "video/x-ms-wmv" ||
301  mimeType == "application/x-troff-msvideo" || mimeType == "video/avi" ||
302  mimeType == "video/msvideo" || mimeType == "video/x-msvideo";
303 }
304 
305 void NewsSite::parseRSS(const QDomDocument& domDoc)
306 {
307  QMutexLocker locker(&m_lock);
308 
309  QDomNode channelNode = domDoc.documentElement().namedItem("channel");
310 
311  m_desc = channelNode.namedItem("description")
312  .toElement().text().simplified();
313 
314  QDomNode imageNode = channelNode.namedItem("image");
315  if (!imageNode.isNull())
316  m_imageURL = imageNode.namedItem("url").toElement().text().simplified();
317 
318  QDomNodeList items = domDoc.elementsByTagName("item");
319 
320  for (unsigned int i = 0; i < (unsigned) items.count(); i++)
321  {
322  QDomNode itemNode = items.item(i);
323  QString title = ReplaceHtmlChar(itemNode.namedItem("title")
324  .toElement().text().simplified());
325 
326  QDomNode descNode = itemNode.namedItem("description");
327  QString description;
328  if (!descNode.isNull())
329  {
330  description = descNode.toElement().text().simplified();
332  }
333 
334  QDomNode linkNode = itemNode.namedItem("link");
335  QString url;
336  if (!linkNode.isNull())
337  url = linkNode.toElement().text().simplified();
338 
339  QDomNode enclosureNode = itemNode.namedItem("enclosure");
340  QString enclosure;
341  QString enclosure_type;
342  QString thumbnail;
343  if (!enclosureNode.isNull())
344  {
345  QDomAttr enclosureURL = enclosureNode.toElement()
346  .attributeNode("url");
347 
348  if (!enclosureURL.isNull())
349  enclosure = enclosureURL.value();
350 
351  QDomAttr enclosureType = enclosureNode.toElement()
352  .attributeNode("type");
353  if (!enclosureType.isNull())
354  {
355  enclosure_type = enclosureType.value();
356 
357  if (enclosure_type == "image/jpeg")
358  {
359  thumbnail = enclosure;
360  enclosure.clear();
361  }
362 
363  // fix for broken feeds that don't add the enclosure type
364  if (enclosure_type == "" || enclosure_type.isNull())
365  {
366  QStringList imageExtensions = QStringList() << ".jpg" << ".jpeg" << ".png" << ".gif";
367  for (int x = 0; x < imageExtensions.count(); x++)
368  {
369  if (enclosure.endsWith(imageExtensions[x], Qt::CaseInsensitive))
370  {
371  thumbnail = enclosure;
372  enclosure.clear();
373  break;
374  }
375  }
376  }
377  }
378  else
379  {
380  // fix broken feeds (like RT) that don't add the enclosure type
381  enclosure.clear();
382  }
383  }
384 
386  // From this point forward, we process RSS 2.0 media tags.
387  // Please put all other tag processing before this comment.
388  // See http://www.rssboard.org/media-rss for details
390 
391  // Some media: tags can be enclosed in a media:group item.
392  // If this item is present, use it to find the media tags,
393  // otherwise, proceed.
394  QDomNode mediaGroup = itemNode.namedItem("media:group");
395  if (!mediaGroup.isNull())
396  itemNode = mediaGroup;
397 
398  QDomNode thumbNode = itemNode.namedItem("media:thumbnail");
399  if (!thumbNode.isNull())
400  {
401  QDomAttr thumburl = thumbNode.toElement().attributeNode("url");
402  if (!thumburl.isNull())
403  thumbnail = thumburl.value();
404  }
405 
406  QDomNode playerNode = itemNode.namedItem("media:player");
407  QString mediaurl;
408  if (!playerNode.isNull())
409  {
410  QDomAttr mediaURL = playerNode.toElement().attributeNode("url");
411  if (!mediaURL.isNull())
412  mediaurl = mediaURL.value();
413  }
414 
415  // If present, the media:description superscedes the RSS description
416  descNode = itemNode.namedItem("media:description");
417  if (!descNode.isNull())
418  description = descNode.toElement().text().simplified();
419 
420  // parse any media:content items looking for any images or videos
421  QDomElement e = itemNode.toElement();
422  QDomNodeList mediaNodes = e.elementsByTagName("media:content");
423  for (int x = 0; x < mediaNodes.count(); x++)
424  {
425  QString medium;
426  QString type;
427  QString url2;
428 
429  QDomElement mediaElement = mediaNodes.at(x).toElement();
430 
431  if (mediaElement.isNull())
432  continue;
433 
434  if (mediaElement.hasAttribute("medium"))
435  medium = mediaElement.attributeNode("medium").value();
436 
437  if (mediaElement.hasAttribute("type"))
438  type = mediaElement.attributeNode("type").value();
439 
440  if (mediaElement.hasAttribute("url"))
441  url2 = mediaElement.attributeNode("url").value();
442 
443  LOG(VB_GENERAL, LOG_DEBUG,
444  QString("parseRSS found media:content: medium: %1, type: %2, url: %3")
445  .arg(medium, type, url2));
446 
447  // if this is an image use it as the thumbnail if we haven't found one yet
448  if (thumbnail.isEmpty() && (medium == "image" || isImage(type)))
449  thumbnail = url2;
450 
451  // if this is a video use it as the enclosure if we haven't found one yet
452  if (enclosure.isEmpty() && (medium == "video" || isVideo(type)))
453  enclosure = url2;
454  }
455 
457  thumbnail, mediaurl, enclosure));
458  }
459 }
460 
461 void NewsSite::parseAtom(const QDomDocument& domDoc)
462 {
463  QDomNodeList entries = domDoc.elementsByTagName("entry");
464 
465  for (unsigned int i = 0; i < (unsigned) entries.count(); i++)
466  {
467  QDomNode itemNode = entries.item(i);
468  QString title = ReplaceHtmlChar(itemNode.namedItem("title").toElement()
469  .text().simplified());
470 
471  QDomNode summNode = itemNode.namedItem("summary");
472  QString description;
473  if (!summNode.isNull())
474  {
476  summNode.toElement().text().simplified());
477  }
478 
479  QDomNode linkNode = itemNode.namedItem("link");
480  QString url;
481  if (!linkNode.isNull())
482  {
483  QDomAttr linkHref = linkNode.toElement().attributeNode("href");
484  if (!linkHref.isNull())
485  url = linkHref.value();
486  }
487 
489  }
490 }
491 
492 QString NewsSite::ReplaceHtmlChar(const QString &orig)
493 {
494  if (orig.isEmpty())
495  return orig;
496 
497  QString s = orig;
498  s.replace("&amp;", "&");
499  s.replace("&pound;", "£");
500  s.replace("&lt;", "<");
501  s.replace("&gt;", ">");
502  s.replace("&quot;", "\"");
503  s.replace("&apos;", "\'");
504  s.replace("&#8230;",QChar(8230));
505  s.replace("&#233;",QChar(233));
506  s.replace("&mdash;", QChar(8212));
507  s.replace("&nbsp;", " ");
508  s.replace("&#160;", QChar(160));
509  s.replace("&#225;", QChar(225));
510  s.replace("&#8216;", QChar(8216));
511  s.replace("&#8217;", QChar(8217));
512  s.replace("&#039;", "\'");
513  s.replace("&ndash;", QChar(8211));
514  // german umlauts
515  s.replace("&auml;", QChar(0x00e4));
516  s.replace("&ouml;", QChar(0x00f6));
517  s.replace("&uuml;", QChar(0x00fc));
518  s.replace("&Auml;", QChar(0x00c4));
519  s.replace("&Ouml;", QChar(0x00d6));
520  s.replace("&Uuml;", QChar(0x00dc));
521  s.replace("&szlig;", QChar(0x00df));
522 
523  return s;
524 }
build_compdb.args
args
Definition: build_compdb.py:11
NewsSite::Success
@ Success
Definition: newssite.h:64
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:62
isImage
static bool isImage(const QString &mimeType)
Definition: newssite.cpp:291
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:133
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:1107
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
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:37
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:134
NewsSite::ReplaceHtmlChar
static QString ReplaceHtmlChar(const QString &orig)
Definition: newssite.cpp:492
isVideo
static bool isVideo(const QString &mimeType)
Definition: newssite.cpp:298
NewsSite::Retrieving
@ Retrieving
Definition: newssite.h:61
NewsSite::m_destDir
QString m_destDir
Definition: newssite.h:126
mythdate.h
NewsSite::m_updateErrorString
QString m_updateErrorString
Definition: newssite.h:130
mythlogging.h
NewsSite::m_podcast
bool m_podcast
Definition: newssite.h:132
GetConfDir
QString GetConfDir(void)
Definition: mythdirs.cpp:256
NewsSite::m_state
State m_state
Definition: newssite.h:128
NewsSite::m_lock
QRecursiveMutex m_lock
Definition: newssite.h:118
NewsSiteItem::m_url
QString m_url
Definition: newssite.h:34
NewsArticle
Definition: newsarticle.h:10
NewsSite::WriteFailed
@ WriteFailed
Definition: newssite.h:63
NewsSite::errorMsg
QString errorMsg(void) const
Definition: newssite.cpp:84
NewsSite::m_imageURL
QString m_imageURL
Definition: newssite.h:131
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:1014
NewsSite::m_errorString
QString m_errorString
Definition: newssite.h:129
NewsSiteItem::m_name
QString m_name
Definition: newssite.h:32
NewsSite::process
void process(void)
Definition: newssite.cpp:225
newssite.h
NewsArticle::List
std::vector< NewsArticle > List
Definition: newsarticle.h:13
NewsSite::m_name
QString m_name
Definition: newssite.h:120
NewsSite::retrieve
void retrieve(void)
Definition: newssite.cpp:58
std
Definition: mythchrono.h:23
NewsSite::deleteLater
virtual void deleteLater()
Definition: newssite.cpp:30
NewsSite::m_url
QString m_url
Definition: newssite.h:122
NewsSite::m_desc
QString m_desc
Definition: newssite.h:124
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:121
NewsSite::m_updated
QDateTime m_updated
Definition: newssite.h:125
NewsSite::parseAtom
void parseAtom(const QDomDocument &domDoc)
Definition: newssite.cpp:461
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
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:305
NewsSite::sortName
QString sortName(void) const
Definition: newssite.cpp:102