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