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 dest_file.close();
189 if (size != download.size())
190 {
191 // File creation failed for some reason, delete it
192 RemoteFile::DeleteFile(finalfile);
193 LOG(VB_GENERAL, LOG_ERR,
194 QString("Image Download: Error Writing Image "
195 "to file: %1").arg(finalfile));
196 errored = true;
197 break;
198 }
199 }
200 }
201 }
202 else
203 {
204 QString path = getStorageGroupURL(type, lookup->GetHost());
205 QString finalfile = path + filename;
206 QString oldurl = info.url;
207 info.url = finalfile;
208 bool exists = false;
209 bool onMaster = false;
210 QString resolvedFN;
212 gCoreContext->IsThisHost(lookup->GetHost()))
213 {
215 resolvedFN = sg.FindFile(filename);
216 exists = !resolvedFN.isEmpty() && QFile::exists(resolvedFN);
217 if (!exists)
218 {
219 resolvedFN = getLocalStorageGroupPath(type,
220 lookup->GetHost()) + "/" + filename;
221 }
222 onMaster = true;
223 }
224 else
225 {
226 exists = RemoteFile::Exists(finalfile);
227 }
228
229 if (!exists || lookup->GetAllowOverwrites())
230 {
231 if (exists && !onMaster)
232 {
233 QFileInfo fi(finalfile);
234 GetMythUI()->RemoveFromCacheByFile(fi.fileName());
235 RemoteFile::DeleteFile(finalfile);
236 }
237 else if (exists)
238 {
239 QFile::remove(resolvedFN);
240 }
241
242 LOG(VB_GENERAL, LOG_INFO,
243 QString("Metadata Image Download: %1 -> %2")
244 .arg(oldurl, finalfile));
245 QByteArray download;
246 GetMythDownloadManager()->download(oldurl, &download);
247
248 QImage testImage;
249 bool didLoad = testImage.loadFromData(download);
250 if (!didLoad)
251 {
252 LOG(VB_GENERAL, LOG_ERR,
253 QString("Tried to write %1, but it appears to be "
254 "an HTML redirect or corrupt file "
255 "(filesize %2).")
256 .arg(oldurl).arg(download.size()));
257 errored = true;
258 break;
259 }
260
261 if (!onMaster)
262 {
263 RemoteFile outFile(finalfile, true);
264
265 if (!outFile.isOpen())
266 {
267 LOG(VB_GENERAL, LOG_ERR,
268 QString("Image Download: Failed to open "
269 "remote file (%1) for write. Does "
270 "Storage Group Exist?")
271 .arg(finalfile));
272 errored = true;
273 break;
274 }
275 off_t written = outFile.Write(download.constData(),
276 download.size());
277 if (written != download.size())
278 {
279 // File creation failed for some reason, delete it
280 RemoteFile::DeleteFile(finalfile);
281
282 LOG(VB_GENERAL, LOG_ERR,
283 QString("Image Download: Error Writing Image "
284 "to file: %1").arg(finalfile));
285 errored = true;
286 break;
287 }
288 }
289 else
290 {
291 QFile dest_file(resolvedFN);
292 if (dest_file.open(QIODevice::WriteOnly))
293 {
294 off_t size = dest_file.write(download);
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 }
322 lookup->SetDownloads(downloaded);
323 QCoreApplication::postEvent(m_parent, new ImageDLEvent(lookup));
324 }
325
326 RunEpilog();
327}
328
330{
331 QMutexLocker lock(&m_mutex);
332 ThumbnailData *ret = nullptr;
333
334 if (!m_thumbnailList.isEmpty())
335 ret = m_thumbnailList.takeFirst();
336 return ret;
337}
338
339QString getDownloadFilename(const QString& title, const QString& url)
340{
341 QString fileprefix = GetConfDir();
342
343 QDir dir(fileprefix);
344 if (!dir.exists())
345 dir.mkdir(fileprefix);
346
347 fileprefix += "/cache/metadata-thumbcache";
348
349 dir.setPath(fileprefix);;
350 if (!dir.exists())
351 dir.mkdir(fileprefix);
352
353 QByteArray titlearr(title.toLatin1());
354 QByteArray urlarr(url.toLatin1());
355#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
356 quint16 titleChecksum = qChecksum(titlearr.data(), titlearr.length());
357 quint16 urlChecksum = qChecksum(urlarr.data(), urlarr.length());
358#else
359 quint16 titleChecksum = qChecksum(titlearr);
360 quint16 urlChecksum = qChecksum(urlarr);
361#endif
362 QUrl qurl(url);
363 QString ext = QFileInfo(qurl.path()).suffix();
364 QString basefilename = QString("thumbnail_%1_%2.%3")
365 .arg(QString::number(urlChecksum),
366 QString::number(titleChecksum), ext);
367
368 QString outputfile = QString("%1/%2").arg(fileprefix, basefilename);
369
370 return outputfile;
371}
372
374 const QString& url)
375{
376 QString basefilename;
377 QString title;
378 QString inter;
379 uint tracknum = lookup->GetTrackNumber();
380 uint season = lookup->GetSeason();
381 uint episode = lookup->GetEpisode();
382 QString system = lookup->GetSystem();
383 if (!lookup->GetIsCollection() && (season > 0 || episode > 0))
384 {
385 title = lookup->GetTitle();
386 if (title.contains("/"))
387 title.replace("/", "-");
388 if (title.contains("?"))
389 title.replace("?", "");
390 if (title.contains("*"))
391 title.replace("*", "");
392 inter = QString(" Season %1").arg(QString::number(season));
394 inter += QString("x%1").arg(QString::number(episode));
395 }
396 else if (lookup->GetType() == kMetadataVideo ||
397 lookup->GetType() == kMetadataRecording)
398 {
399 title = lookup->GetInetref();
400 }
401 else if (lookup->GetType() == kMetadataGame)
402 {
403 title = QString("%1 (%2)").arg(lookup->GetTitle(), lookup->GetSystem());
404 }
405
406 if (tracknum > 0)
407 inter = QString(" Track %1").arg(QString::number(tracknum));
408 else if (!system.isEmpty())
409 inter = QString(" (%1)").arg(system);
410
411 QString suffix;
412 QUrl qurl(url);
413 QString ext = QFileInfo(qurl.path()).suffix();
414
415 if (type == kArtworkCoverart)
416 suffix = "_coverart";
417 else if (type == kArtworkFanart)
418 suffix = "_fanart";
419 else if (type == kArtworkBanner)
420 suffix = "_banner";
421 else if (type == kArtworkScreenshot)
422 suffix = "_screenshot";
423 else if (type == kArtworkPoster)
424 suffix = "_poster";
425 else if (type == kArtworkBackCover)
426 suffix = "_backcover";
427 else if (type == kArtworkInsideCover)
428 suffix = "_insidecover";
429 else if (type == kArtworkCDImage)
430 suffix = "_cdimage";
431
432 basefilename = title + inter + suffix + "." + ext;
433
434 return basefilename;
435}
436
438{
439 QString ret;
440
441 if (metadatatype == kMetadataVideo)
442 {
443 if (type == kArtworkCoverart)
444 ret = gCoreContext->GetSetting("VideoArtworkDir");
445 else if (type == kArtworkFanart)
446 ret = gCoreContext->GetSetting("mythvideo.fanartDir");
447 else if (type == kArtworkBanner)
448 ret = gCoreContext->GetSetting("mythvideo.bannerDir");
449 else if (type == kArtworkScreenshot)
450 ret = gCoreContext->GetSetting("mythvideo.screenshotDir");
451 }
452 else if (metadatatype == kMetadataMusic)
453 {
454 }
455 else if (metadatatype == kMetadataGame)
456 {
457 if (type == kArtworkCoverart)
458 ret = gCoreContext->GetSetting("mythgame.boxartdir");
459 else if (type == kArtworkFanart)
460 ret = gCoreContext->GetSetting("mythgame.fanartdir");
461 else if (type == kArtworkScreenshot)
462 ret = gCoreContext->GetSetting("mythgame.screenshotdir");
463 }
464
465 return ret;
466}
467
468QString getStorageGroupURL(VideoArtworkType type, const QString& host)
469{
470 QString sgroup = getStorageGroupName(type);
472
473 return MythCoreContext::GenMythURL(host, port, "", sgroup);
474}
475
476QString getLocalStorageGroupPath(VideoArtworkType type, const QString& host)
477{
478 QString path;
479
481
482 path = sg.FindNextDirMostFree();
483
484 return path;
485}
486
488{
489 switch (type)
490 {
491 case kArtworkCoverart:
492 return "Coverart";
493 case kArtworkFanart:
494 return "Fanart";
495 case kArtworkBanner:
496 return "Banners";
498 return "Screenshots";
499 default:
500 return "Default";
501 }
502}
503
505{
506 QString cache = QString("%1/cache/metadata-thumbcache")
507 .arg(GetConfDir());
508 QDir cacheDir(cache);
509 QStringList thumbs = cacheDir.entryList(QDir::Files);
510
511 for (auto i = thumbs.crbegin(); i != thumbs.crend(); ++i)
512 {
513 QString filename = QString("%1/%2").arg(cache, *i);
514 QFileInfo fi(filename);
515 QDateTime lastmod = fi.lastModified();
516 if (lastmod.addDays(2) < MythDate::current())
517 {
518 LOG(VB_GENERAL, LOG_DEBUG, QString("Deleting file %1")
519 .arg(filename));
520 QFile::remove(filename);
521 }
522 }
523}
524
static const Type kEventType
static const Type kEventType
bool isRunning(void) const
Definition: mthread.cpp:261
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:194
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:281
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:207
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:298
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:420
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:463
bool isOpen(void) const
Definition: remotefile.cpp:247
int Write(const void *data, int size)
Definition: remotefile.cpp:838
QString FindFile(const QString &filename)
QString FindNextDirMostFree(void)
static const Type kEventType
unsigned int uint
Definition: compat.h:60
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:285
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
#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