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