MythTV  master
metadataimagedownload.cpp
Go to the documentation of this file.
1 // qt
2 #include <QCoreApplication>
3 #include <QImage>
4 #include <QFileInfo>
5 #include <QDir>
6 #include <QEvent>
7 
8 // myth
9 #include "mythcorecontext.h"
10 #include "mythuihelper.h"
11 #include "mythdirs.h"
12 #include "storagegroup.h"
13 #include "metadataimagedownload.h"
14 #include "remotefile.h"
15 #include "mythdownloadmanager.h"
16 #include "mythlogging.h"
17 #include "mythdate.h"
18 
19 QEvent::Type ImageDLEvent::kEventType =
20  (QEvent::Type) QEvent::registerEventType();
21 
23  (QEvent::Type) QEvent::registerEventType();
24 
25 QEvent::Type ThumbnailDLEvent::kEventType =
26  (QEvent::Type) QEvent::registerEventType();
27 
29  MThread("MetadataImageDownload")
30 {
31  m_parent = parent;
32 }
33 
35 {
36  cancel();
37  wait();
38 }
39 
41  QString url, QVariant data)
42 {
43  QMutexLocker lock(&m_mutex);
44 
45  ThumbnailData *id = new ThumbnailData();
46  id->title = title;
47  id->data = data;
48  id->url = url;
49  m_thumbnailList.append(id);
50  if (!isRunning())
51  start();
52 }
53 
59 {
60  QMutexLocker lock(&m_mutex);
61 
62  m_downloadList.append(lookup);
63  lookup->DecrRef();
64  if (!isRunning())
65  start();
66 }
67 
69 {
70  QMutexLocker lock(&m_mutex);
71 
72  qDeleteAll(m_thumbnailList);
73  m_thumbnailList.clear();
74  // clearing m_downloadList automatically delete all its content
75  m_downloadList.clear();
76 }
77 
79 {
80  RunProlog();
81 
82  // Always handle thumbnails first, they're higher priority.
83  ThumbnailData *thumb;
84  while ((thumb = moreThumbs()) != nullptr)
85  {
86  QString sFilename = getDownloadFilename(thumb->title, thumb->url);
87 
88  bool exists = QFile::exists(sFilename);
89  if (!exists && !thumb->url.isEmpty())
90  {
91  if (!GetMythDownloadManager()->download(thumb->url, sFilename))
92  {
93  LOG(VB_GENERAL, LOG_ERR,
94  QString("MetadataImageDownload: failed to download thumbnail from: %1")
95  .arg(thumb->url));
96 
97  delete thumb;
98  continue;
99  }
100  }
101 
102  // inform parent we have thumbnail ready for it
103  if (QFile::exists(sFilename) && m_parent)
104  {
105  LOG(VB_GENERAL, LOG_DEBUG,
106  QString("Threaded Image Thumbnail Download: %1")
107  .arg(sFilename));
108  thumb->url = sFilename;
109  QCoreApplication::postEvent(m_parent,
110  new ThumbnailDLEvent(thumb));
111  }
112  else
113  delete thumb;
114  }
115 
116  while (true)
117  {
118  m_mutex.lock();
119  if (m_downloadList.isEmpty())
120  {
121  // no more to process, we're done
122  m_mutex.unlock();
123  break;
124  }
125  // Ref owns the MetadataLookup object for the duration of the loop
126  // and it will be deleted automatically when the loop completes
128  m_mutex.unlock();
129  MetadataLookup *lookup = ref;
130  DownloadMap downloads = lookup->GetDownloads();
131  DownloadMap downloaded;
132 
133  bool errored = false;
134  for (DownloadMap::iterator i = downloads.begin();
135  i != downloads.end(); ++i)
136  {
137  VideoArtworkType type = i.key();
138  ArtworkInfo info = i.value();
139  QString filename = getDownloadFilename( type, lookup,
140  info.url );
141  if (lookup->GetHost().isEmpty())
142  {
143  QString path = getLocalWritePath(lookup->GetType(), type);
144  QDir dirPath(path);
145  if (!dirPath.exists())
146  {
147  if (!dirPath.mkpath(path))
148  {
149  LOG(VB_GENERAL, LOG_ERR,
150  QString("Metadata Image Download: Unable to create "
151  "path %1, aborting download.").arg(path));
152  errored = true;
153  break;
154  }
155  }
156  QString finalfile = path + "/" + filename;
157  QString oldurl = info.url;
158  info.url = finalfile;
159  if (!QFile::exists(finalfile) || lookup->GetAllowOverwrites())
160  {
161  QFile dest_file(finalfile);
162  if (dest_file.exists())
163  {
164  QFileInfo fi(finalfile);
165  GetMythUI()->RemoveFromCacheByFile(fi.fileName());
166  dest_file.remove();
167  }
168 
169  LOG(VB_GENERAL, LOG_INFO,
170  QString("Metadata Image Download: %1 ->%2")
171  .arg(oldurl).arg(finalfile));
172  QByteArray download;
173  GetMythDownloadManager()->download(oldurl, &download);
174 
175  QImage testImage;
176  bool didLoad = testImage.loadFromData(download);
177  if (!didLoad)
178  {
179  LOG(VB_GENERAL, LOG_ERR,
180  QString("Tried to write %1, but it appears to be "
181  "an HTML redirect (filesize %2).")
182  .arg(oldurl).arg(download.size()));
183  errored = true;
184  break;
185  }
186 
187  if (dest_file.open(QIODevice::WriteOnly))
188  {
189  off_t size = dest_file.write(download,
190  download.size());
191  dest_file.close();
192  if (size != download.size())
193  {
194  // File creation failed for some reason, delete it
195  RemoteFile::DeleteFile(finalfile);
196  LOG(VB_GENERAL, LOG_ERR,
197  QString("Image Download: Error Writing Image "
198  "to file: %1").arg(finalfile));
199  errored = true;
200  break;
201  }
202  }
203  }
204  }
205  else
206  {
207  QString path = getStorageGroupURL(type, lookup->GetHost());
208  QString finalfile = path + filename;
209  QString oldurl = info.url;
210  info.url = finalfile;
211  bool exists = false;
212  bool onMaster = false;
213  QString resolvedFN;
214  if (gCoreContext->IsMasterBackend() &&
215  gCoreContext->IsThisHost(lookup->GetHost()))
216  {
218  resolvedFN = sg.FindFile(filename);
219  exists = !resolvedFN.isEmpty() && QFile::exists(resolvedFN);
220  if (!exists)
221  {
222  resolvedFN = getLocalStorageGroupPath(type,
223  lookup->GetHost()) + "/" + filename;
224  }
225  onMaster = true;
226  }
227  else
228  exists = RemoteFile::Exists(finalfile);
229 
230  if (!exists || lookup->GetAllowOverwrites())
231  {
232  if (exists && !onMaster)
233  {
234  QFileInfo fi(finalfile);
235  GetMythUI()->RemoveFromCacheByFile(fi.fileName());
236  RemoteFile::DeleteFile(finalfile);
237  }
238  else if (exists)
239  QFile::remove(resolvedFN);
240 
241  LOG(VB_GENERAL, LOG_INFO,
242  QString("Metadata Image Download: %1 -> %2")
243  .arg(oldurl).arg(finalfile));
244  QByteArray download;
245  GetMythDownloadManager()->download(oldurl, &download);
246 
247  QImage testImage;
248  bool didLoad = testImage.loadFromData(download);
249  if (!didLoad)
250  {
251  LOG(VB_GENERAL, LOG_ERR,
252  QString("Tried to write %1, but it appears to be "
253  "an HTML redirect or corrupt file "
254  "(filesize %2).")
255  .arg(oldurl).arg(download.size()));
256  errored = true;
257  break;
258  }
259 
260  if (!onMaster)
261  {
262  RemoteFile outFile(finalfile, true);
263 
264  if (!outFile.isOpen())
265  {
266  LOG(VB_GENERAL, LOG_ERR,
267  QString("Image Download: Failed to open "
268  "remote file (%1) for write. Does "
269  "Storage Group Exist?")
270  .arg(finalfile));
271  errored = true;
272  break;
273  }
274  off_t written = outFile.Write(download,
275  download.size());
276  if (written != download.size())
277  {
278  // File creation failed for some reason, delete it
279  RemoteFile::DeleteFile(finalfile);
280 
281  LOG(VB_GENERAL, LOG_ERR,
282  QString("Image Download: Error Writing Image "
283  "to file: %1").arg(finalfile));
284  errored = true;
285  break;
286  }
287  }
288  else
289  {
290  QFile dest_file(resolvedFN);
291  if (dest_file.open(QIODevice::WriteOnly))
292  {
293  off_t size = dest_file.write(download,
294  download.size());
295  dest_file.close();
296  if (size != download.size())
297  {
298  // File creation failed for some reason, delete it
299  RemoteFile::DeleteFile(resolvedFN);
300  LOG(VB_GENERAL, LOG_ERR,
301  QString("Image Download: Error Writing Image "
302  "to file: %1").arg(finalfile));
303  errored = true;
304  break;
305  }
306  }
307  }
308  }
309  }
310  if (!errored)
311  {
312  // update future Artwork Map with what we've successfully
313  // retrieved (either downloaded or already existing
314  downloaded.insert(type, info);
315  }
316  }
317  if (errored)
318  {
319  QCoreApplication::postEvent(m_parent,
320  new ImageDLFailureEvent(lookup));
321  errored = false;
322  }
323  lookup->SetDownloads(downloaded);
324  QCoreApplication::postEvent(m_parent, new ImageDLEvent(lookup));
325  }
326 
327  RunEpilog();
328 }
329 
331 {
332  QMutexLocker lock(&m_mutex);
333  ThumbnailData *ret = nullptr;
334 
335  if (!m_thumbnailList.isEmpty())
336  ret = m_thumbnailList.takeFirst();
337  return ret;
338 }
339 
340 QString getDownloadFilename(QString title, QString url)
341 {
342  QString fileprefix = GetConfDir();
343 
344  QDir dir(fileprefix);
345  if (!dir.exists())
346  dir.mkdir(fileprefix);
347 
348  fileprefix += "/cache/metadata-thumbcache";
349 
350  dir = QDir(fileprefix);
351  if (!dir.exists())
352  dir.mkdir(fileprefix);
353 
354  QByteArray titlearr(title.toLatin1());
355  quint16 titleChecksum = qChecksum(titlearr.data(), titlearr.length());
356  QByteArray urlarr(url.toLatin1());
357  quint16 urlChecksum = qChecksum(urlarr.data(), urlarr.length());
358  QUrl qurl(url);
359  QString ext = QFileInfo(qurl.path()).suffix();
360  QString basefilename = QString("thumbnail_%1_%2.%3")
361  .arg(QString::number(urlChecksum))
362  .arg(QString::number(titleChecksum)).arg(ext);
363 
364  QString outputfile = QString("%1/%2").arg(fileprefix).arg(basefilename);
365 
366  return outputfile;
367 }
368 
370  QString url)
371 {
372  QString basefilename;
373  QString title;
374  QString inter;
375  uint tracknum = lookup->GetTrackNumber();
376  uint season = lookup->GetSeason();
377  uint episode = lookup->GetEpisode();
378  QString system = lookup->GetSystem();
379  if (!lookup->GetIsCollection() && (season > 0 || episode > 0))
380  {
381  title = lookup->GetTitle();
382  if (title.contains("/"))
383  title.replace("/", "-");
384  if (title.contains("?"))
385  title.replace("?", "");
386  if (title.contains("*"))
387  title.replace("*", "");
388  inter = QString(" Season %1").arg(QString::number(season));
389  if (type == kArtworkScreenshot)
390  inter += QString("x%1").arg(QString::number(episode));
391  }
392  else if (lookup->GetType() == kMetadataVideo ||
393  lookup->GetType() == kMetadataRecording)
394  title = lookup->GetInetref();
395  else if (lookup->GetType() == kMetadataGame)
396  title = QString("%1 (%2)").arg(lookup->GetTitle())
397  .arg(lookup->GetSystem());
398 
399  if (tracknum > 0)
400  inter = QString(" Track %1").arg(QString::number(tracknum));
401  else if (!system.isEmpty())
402  inter = QString(" (%1)").arg(system);
403 
404  QString suffix;
405  QUrl qurl(url);
406  QString ext = QFileInfo(qurl.path()).suffix();
407 
408  if (type == kArtworkCoverart)
409  suffix = "_coverart";
410  else if (type == kArtworkFanart)
411  suffix = "_fanart";
412  else if (type == kArtworkBanner)
413  suffix = "_banner";
414  else if (type == kArtworkScreenshot)
415  suffix = "_screenshot";
416  else if (type == kArtworkPoster)
417  suffix = "_poster";
418  else if (type == kArtworkBackCover)
419  suffix = "_backcover";
420  else if (type == kArtworkInsideCover)
421  suffix = "_insidecover";
422  else if (type == kArtworkCDImage)
423  suffix = "_cdimage";
424 
425  basefilename = title + inter + suffix + "." + ext;
426 
427  return basefilename;
428 }
429 
431 {
432  QString ret;
433 
434  if (metadatatype == kMetadataVideo)
435  {
436  if (type == kArtworkCoverart)
437  ret = gCoreContext->GetSetting("VideoArtworkDir");
438  else if (type == kArtworkFanart)
439  ret = gCoreContext->GetSetting("mythvideo.fanartDir");
440  else if (type == kArtworkBanner)
441  ret = gCoreContext->GetSetting("mythvideo.bannerDir");
442  else if (type == kArtworkScreenshot)
443  ret = gCoreContext->GetSetting("mythvideo.screenshotDir");
444  }
445  else if (metadatatype == kMetadataMusic)
446  {
447  }
448  else if (metadatatype == kMetadataGame)
449  {
450  if (type == kArtworkCoverart)
451  ret = gCoreContext->GetSetting("mythgame.boxartdir");
452  else if (type == kArtworkFanart)
453  ret = gCoreContext->GetSetting("mythgame.fanartdir");
454  else if (type == kArtworkScreenshot)
455  ret = gCoreContext->GetSetting("mythgame.screenshotdir");
456  }
457 
458  return ret;
459 }
460 
462 {
463  QString sgroup = getStorageGroupName(type);
464  uint port = gCoreContext->GetBackendServerPort(host);
465 
466  return gCoreContext->GenMythURL(host, port, "", sgroup);
467 }
468 
470 {
471  QString path;
472 
474 
475  path = sg.FindNextDirMostFree();
476 
477  return path;
478 }
479 
481 {
482  switch (type)
483  {
484  case kArtworkCoverart:
485  return "Coverart";
486  case kArtworkFanart:
487  return "Fanart";
488  case kArtworkBanner:
489  return "Banners";
490  case kArtworkScreenshot:
491  return "Screenshots";
492  default:
493  return "Default";
494  }
495 }
496 
498 {
499  QString cache = QString("%1/cache/metadata-thumbcache")
500  .arg(GetConfDir());
501  QDir cacheDir(cache);
502  QStringList thumbs = cacheDir.entryList(QDir::Files);
503 
504  for (QStringList::const_iterator i = thumbs.end() - 1;
505  i != thumbs.begin() - 1; --i)
506  {
507  QString filename = QString("%1/%2").arg(cache).arg(*i);
508  QFileInfo fi(filename);
509  QDateTime lastmod = fi.lastModified();
510  if (lastmod.addDays(2) < MythDate::current())
511  {
512  LOG(VB_GENERAL, LOG_DEBUG, QString("Deleting file %1")
513  .arg(filename));
514  QFile::remove(filename);
515  }
516  }
517 }
518 
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:216
void start(QThread::Priority=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:295
static bool DeleteFile(const QString &url)
Definition: remotefile.cpp:425
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:46
MetadataLookupList m_downloadList
QString GenMythURL(QString host=QString(), QString port=QString(), QString path=QString(), QString storageGroup=QString())
MetadataType
QList< ThumbnailData * > m_thumbnailList
bool GetIsCollection() const
bool wait(unsigned long time=ULONG_MAX)
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:312
QString getLocalStorageGroupPath(VideoArtworkType type, QString host)
VideoArtworkType
QString GetSystem() const
unsigned int uint
Definition: compat.h:140
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void cleanThumbnailCacheDir()
QString getStorageGroupName(VideoArtworkType type)
RefCountHandler< T > takeFirstAndDecr(void)
Removes the first item in the list and returns it.
uint GetTrackNumber() const
#define off_t
QString getLocalWritePath(MetadataType metadatatype, VideoArtworkType type)
bool IsThisHost(const QString &addr)
is this address mapped to this host
int GetBackendServerPort(void)
Returns the locally defined backend control port.
MetadataType GetType() const
uint GetSeason() const
QString GetTitle() const
QString GetConfDir(void)
Definition: mythdirs.cpp:224
uint GetEpisode() const
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
QString GetSetting(const QString &key, const QString &defaultval="")
void addDownloads(MetadataLookup *lookup)
addLookup: Add lookup to bottom of the queue MetadataDownload::m_downloadList takes ownership of the ...
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
QString FindNextDirMostFree(void)
bool isRunning(void) const
Definition: mthread.cpp:275
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
bool download(const QString &url, const QString &dest, const bool reload=false)
Downloads a URL to a file in blocking mode.
bool GetAllowOverwrites() const
void SetDownloads(ArtworkMap map)
QString GetInetref() const
MythUIHelper * GetMythUI()
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:468
QMap< VideoArtworkType, ArtworkInfo > DownloadMap
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
static Type kEventType
bool isOpen(void) const
Definition: remotefile.cpp:253
QString FindFile(const QString &filename)
QString getStorageGroupURL(VideoArtworkType type, QString host)
void addThumb(QString title, QString url, QVariant data)
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:203
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
DownloadMap GetDownloads() const
bool IsMasterBackend(void)
is this the actual MBE process
QString GetHost() const
int Write(const void *data, int size)
Definition: remotefile.cpp:845
QString getDownloadFilename(QString title, QString url)
MetadataImageDownload(QObject *parent)
void RemoveFromCacheByFile(const QString &fname)