00001
00002 #include <QCoreApplication>
00003 #include <QImage>
00004 #include <QFileInfo>
00005 #include <QDir>
00006 #include <QEvent>
00007
00008
00009 #include "mythcorecontext.h"
00010 #include "mythuihelper.h"
00011 #include "mythdirs.h"
00012 #include "httpcomms.h"
00013 #include "storagegroup.h"
00014 #include "metadataimagedownload.h"
00015 #include "remotefile.h"
00016 #include "mythdownloadmanager.h"
00017 #include "mythlogging.h"
00018
00019 QEvent::Type ImageDLEvent::kEventType =
00020 (QEvent::Type) QEvent::registerEventType();
00021
00022 QEvent::Type ImageDLFailureEvent::kEventType =
00023 (QEvent::Type) QEvent::registerEventType();
00024
00025 QEvent::Type ThumbnailDLEvent::kEventType =
00026 (QEvent::Type) QEvent::registerEventType();
00027
00028 MetadataImageDownload::MetadataImageDownload(QObject *parent) :
00029 MThread("MetadataImageDownload")
00030 {
00031 m_parent = parent;
00032 }
00033
00034 MetadataImageDownload::~MetadataImageDownload()
00035 {
00036 cancel();
00037 wait();
00038 }
00039
00040 void MetadataImageDownload::addThumb(QString title,
00041 QString url, QVariant data)
00042 {
00043 m_mutex.lock();
00044 ThumbnailData *id = new ThumbnailData();
00045 id->title = title;
00046 id->data = data;
00047 id->url = url;
00048 m_thumbnailList.append(id);
00049 if (!isRunning())
00050 start();
00051 m_mutex.unlock();
00052 }
00053
00054 void MetadataImageDownload::addDownloads(MetadataLookup *lookup)
00055 {
00056 m_mutex.lock();
00057 m_downloadList.append(lookup);
00058 if (!isRunning())
00059 start();
00060 m_mutex.unlock();
00061 }
00062
00063 void MetadataImageDownload::cancel()
00064 {
00065 m_mutex.lock();
00066 qDeleteAll(m_thumbnailList);
00067 m_thumbnailList.clear();
00068 qDeleteAll(m_downloadList);
00069 m_downloadList.clear();
00070 m_mutex.unlock();
00071 }
00072
00073 void MetadataImageDownload::run()
00074 {
00075 RunProlog();
00076
00077
00078 ThumbnailData *thumb;
00079 while ((thumb = moreThumbs()) != NULL)
00080 {
00081 QString sFilename = getDownloadFilename(thumb->title, thumb->url);
00082
00083 bool exists = QFile::exists(sFilename);
00084 if (!exists && !thumb->url.isEmpty())
00085 GetMythDownloadManager()->download(thumb->url, sFilename);
00086
00087
00088 if (QFile::exists(sFilename) && m_parent)
00089 {
00090 LOG(VB_GENERAL, LOG_DEBUG,
00091 QString("Threaded Image Thumbnail Download: %1")
00092 .arg(sFilename));
00093 thumb->url = sFilename;
00094 QCoreApplication::postEvent(m_parent,
00095 new ThumbnailDLEvent(thumb));
00096 }
00097 else
00098 delete thumb;
00099 }
00100
00101 MetadataLookup *lookup;
00102 while ((lookup = moreDownloads()) != NULL)
00103 {
00104 DownloadMap downloads = lookup->GetDownloads();
00105 DownloadMap downloaded;
00106
00107 for (DownloadMap::iterator i = downloads.begin();
00108 i != downloads.end(); ++i)
00109 {
00110 VideoArtworkType type = i.key();
00111 ArtworkInfo info = i.value();
00112 QString filename = getDownloadFilename( type, lookup,
00113 info.url );
00114 if (lookup->GetHost().isEmpty())
00115 {
00116 QString path = getLocalWritePath(lookup->GetType(), type);
00117 QDir dirPath(path);
00118 if (!dirPath.exists())
00119 if (!dirPath.mkpath(path))
00120 {
00121 LOG(VB_GENERAL, LOG_ERR,
00122 QString("Metadata Image Download: Unable to create "
00123 "path %1, aborting download.").arg(path));
00124 QCoreApplication::postEvent(m_parent,
00125 new ImageDLFailureEvent(lookup));
00126 continue;
00127 }
00128 QString finalfile = path + "/" + filename;
00129 QString oldurl = info.url;
00130 info.url = finalfile;
00131 if (!QFile::exists(finalfile) || lookup->GetAllowOverwrites())
00132 {
00133 QFile dest_file(finalfile);
00134 if (dest_file.exists())
00135 {
00136 QFileInfo fi(finalfile);
00137 GetMythUI()->RemoveFromCacheByFile(fi.fileName());
00138 dest_file.remove();
00139 }
00140
00141 LOG(VB_GENERAL, LOG_INFO,
00142 QString("Metadata Image Download: %1 ->%2")
00143 .arg(oldurl).arg(finalfile));
00144 QByteArray *download = new QByteArray();
00145 GetMythDownloadManager()->download(oldurl, download);
00146
00147 QImage testImage;
00148 bool didLoad = testImage.loadFromData(*download);
00149 if (!didLoad)
00150 {
00151 LOG(VB_GENERAL, LOG_ERR,
00152 QString("Tried to write %1, but it appears to be "
00153 "an HTML redirect (filesize %2).")
00154 .arg(oldurl).arg(download->size()));
00155 delete download;
00156 download = NULL;
00157 QCoreApplication::postEvent(m_parent,
00158 new ImageDLFailureEvent(lookup));
00159 continue;
00160 }
00161
00162 if (dest_file.open(QIODevice::WriteOnly))
00163 {
00164 off_t size = dest_file.write(*download,
00165 download->size());
00166 if (size != download->size())
00167 {
00168 LOG(VB_GENERAL, LOG_ERR,
00169 QString("Image Download: Error Writing Image "
00170 "to file: %1").arg(finalfile));
00171 QCoreApplication::postEvent(m_parent,
00172 new ImageDLFailureEvent(lookup));
00173 }
00174 else
00175 downloaded.insert(type, info);
00176 }
00177
00178 delete download;
00179 }
00180 else
00181 downloaded.insert(type, info);
00182 }
00183 else
00184 {
00185 QString path = getStorageGroupURL(type, lookup->GetHost());
00186 QString finalfile = path + filename;
00187 QString oldurl = info.url;
00188 info.url = finalfile;
00189 bool exists = false;
00190 bool onMaster = false;
00191 QString resolvedFN;
00192 if ((lookup->GetHost().toLower() == gCoreContext->GetHostName().toLower()) ||
00193 (gCoreContext->IsThisHost(lookup->GetHost())))
00194 {
00195 StorageGroup sg;
00196 resolvedFN = sg.FindFile(filename);
00197 exists = QFile::exists(resolvedFN);
00198 if (!exists)
00199 {
00200 resolvedFN = getLocalStorageGroupPath(type,
00201 lookup->GetHost()) + "/" + filename;
00202 }
00203 onMaster = true;
00204 }
00205 else
00206 exists = RemoteFile::Exists(finalfile);
00207
00208 if (!exists || lookup->GetAllowOverwrites())
00209 {
00210
00211 if (exists && !onMaster)
00212 {
00213 QFileInfo fi(finalfile);
00214 GetMythUI()->RemoveFromCacheByFile(fi.fileName());
00215 RemoteFile::DeleteFile(finalfile);
00216 }
00217 else if (exists)
00218 QFile::remove(resolvedFN);
00219
00220 LOG(VB_GENERAL, LOG_INFO,
00221 QString("Metadata Image Download: %1 -> %2")
00222 .arg(oldurl).arg(finalfile));
00223 QByteArray *download = new QByteArray();
00224 GetMythDownloadManager()->download(oldurl, download);
00225
00226 QImage testImage;
00227 bool didLoad = testImage.loadFromData(*download);
00228 if (!didLoad)
00229 {
00230 LOG(VB_GENERAL, LOG_ERR,
00231 QString("Tried to write %1, but it appears to be "
00232 "an HTML redirect or corrupt file "
00233 "(filesize %2).")
00234 .arg(oldurl).arg(download->size()));
00235 delete download;
00236 download = NULL;
00237 QCoreApplication::postEvent(m_parent,
00238 new ImageDLFailureEvent(lookup));
00239 continue;
00240 }
00241
00242 if (!onMaster)
00243 {
00244 RemoteFile *outFile = new RemoteFile(finalfile, true);
00245 if (!outFile->isOpen())
00246 {
00247 LOG(VB_GENERAL, LOG_ERR,
00248 QString("Image Download: Failed to open "
00249 "remote file (%1) for write. Does "
00250 "Storage Group Exist?")
00251 .arg(finalfile));
00252 delete outFile;
00253 outFile = NULL;
00254 QCoreApplication::postEvent(m_parent,
00255 new ImageDLFailureEvent(lookup));
00256 }
00257 else
00258 {
00259 off_t written = outFile->Write(*download,
00260 download->size());
00261 if (written != download->size())
00262 {
00263 LOG(VB_GENERAL, LOG_ERR,
00264 QString("Image Download: Error Writing Image "
00265 "to file: %1").arg(finalfile));
00266 QCoreApplication::postEvent(m_parent,
00267 new ImageDLFailureEvent(lookup));
00268 }
00269 else
00270 downloaded.insert(type, info);
00271 delete outFile;
00272 outFile = NULL;
00273 }
00274 }
00275 else
00276 {
00277 QFile dest_file(resolvedFN);
00278 if (dest_file.open(QIODevice::WriteOnly))
00279 {
00280 off_t size = dest_file.write(*download,
00281 download->size());
00282 if (size != download->size())
00283 {
00284 LOG(VB_GENERAL, LOG_ERR,
00285 QString("Image Download: Error Writing Image "
00286 "to file: %1").arg(finalfile));
00287 QCoreApplication::postEvent(m_parent,
00288 new ImageDLFailureEvent(lookup));
00289 }
00290 else
00291 downloaded.insert(type, info);
00292 }
00293 }
00294
00295 delete download;
00296 }
00297 else
00298 downloaded.insert(type, info);
00299 }
00300 }
00301 lookup->SetDownloads(downloaded);
00302 QCoreApplication::postEvent(m_parent, new ImageDLEvent(lookup));
00303 }
00304
00305 RunEpilog();
00306 }
00307
00308 ThumbnailData* MetadataImageDownload::moreThumbs()
00309 {
00310 ThumbnailData *ret = NULL;
00311 m_mutex.lock();
00312 if (!m_thumbnailList.isEmpty())
00313 ret = m_thumbnailList.takeFirst();
00314 m_mutex.unlock();
00315 return ret;
00316 }
00317
00318 MetadataLookup* MetadataImageDownload::moreDownloads()
00319 {
00320 MetadataLookup *ret = NULL;
00321 m_mutex.lock();
00322 if (!m_downloadList.isEmpty())
00323 ret = m_downloadList.takeFirst();
00324 m_mutex.unlock();
00325 return ret;
00326 }
00327
00328 QString getDownloadFilename(QString title, QString url)
00329 {
00330 QString fileprefix = GetConfDir();
00331
00332 QDir dir(fileprefix);
00333 if (!dir.exists())
00334 dir.mkdir(fileprefix);
00335
00336 fileprefix += "/thumbcache";
00337
00338 dir = QDir(fileprefix);
00339 if (!dir.exists())
00340 dir.mkdir(fileprefix);
00341
00342 QByteArray titlearr(title.toLatin1());
00343 quint16 titleChecksum = qChecksum(titlearr.data(), titlearr.length());
00344 QByteArray urlarr(url.toLatin1());
00345 quint16 urlChecksum = qChecksum(urlarr.data(), urlarr.length());
00346 QUrl qurl(url);
00347 QString ext = QFileInfo(qurl.path()).suffix();
00348 QString basefilename = QString("thumbnail_%1_%2.%3")
00349 .arg(QString::number(urlChecksum))
00350 .arg(QString::number(titleChecksum)).arg(ext);
00351
00352 QString outputfile = QString("%1/%2").arg(fileprefix).arg(basefilename);
00353
00354 return outputfile;
00355 }
00356
00357 QString getDownloadFilename(VideoArtworkType type, MetadataLookup *lookup,
00358 QString url)
00359 {
00360 QString basefilename;
00361 QString title;
00362 QString inter;
00363 uint tracknum = lookup->GetTrackNumber();
00364 uint season = lookup->GetSeason();
00365 uint episode = lookup->GetEpisode();
00366 QString system = lookup->GetSystem();
00367 if (season > 0 || episode > 0)
00368 {
00369 title = lookup->GetTitle();
00370 if (title.contains("/"))
00371 title.replace("/", "-");
00372 if (title.contains("?"))
00373 title.replace("?", "");
00374 if (title.contains("*"))
00375 title.replace("*", "");
00376 inter = QString(" Season %1").arg(QString::number(season));
00377 if (type == kArtworkScreenshot)
00378 inter += QString("x%1").arg(QString::number(episode));
00379 }
00380 else if (lookup->GetType() == kMetadataVideo ||
00381 lookup->GetType() == kMetadataRecording)
00382 title = lookup->GetInetref();
00383 else if (lookup->GetType() == kMetadataGame)
00384 title = QString("%1 (%2)").arg(lookup->GetTitle())
00385 .arg(lookup->GetSystem());
00386
00387 if (tracknum > 0)
00388 inter = QString(" Track %1").arg(QString::number(tracknum));
00389 else if (!system.isEmpty())
00390 inter = QString(" (%1)").arg(system);
00391
00392 QString suffix;
00393 QUrl qurl(url);
00394 QString ext = QFileInfo(qurl.path()).suffix();
00395
00396 if (type == kArtworkCoverart)
00397 suffix = "_coverart";
00398 else if (type == kArtworkFanart)
00399 suffix = "_fanart";
00400 else if (type == kArtworkBanner)
00401 suffix = "_banner";
00402 else if (type == kArtworkScreenshot)
00403 suffix = "_screenshot";
00404 else if (type == kArtworkPoster)
00405 suffix = "_poster";
00406 else if (type == kArtworkBackCover)
00407 suffix = "_backcover";
00408 else if (type == kArtworkInsideCover)
00409 suffix = "_insidecover";
00410 else if (type == kArtworkCDImage)
00411 suffix = "_cdimage";
00412
00413 basefilename = title + inter + suffix + "." + ext;
00414
00415 return basefilename;
00416 }
00417
00418 QString getLocalWritePath(MetadataType metadatatype, VideoArtworkType type)
00419 {
00420 QString ret;
00421
00422 if (metadatatype == kMetadataVideo)
00423 {
00424 if (type == kArtworkCoverart)
00425 ret = gCoreContext->GetSetting("VideoArtworkDir");
00426 else if (type == kArtworkFanart)
00427 ret = gCoreContext->GetSetting("mythvideo.fanartDir");
00428 else if (type == kArtworkBanner)
00429 ret = gCoreContext->GetSetting("mythvideo.bannerDir");
00430 else if (type == kArtworkScreenshot)
00431 ret = gCoreContext->GetSetting("mythvideo.screenshotDir");
00432 }
00433 else if (metadatatype == kMetadataMusic)
00434 {
00435 }
00436 else if (metadatatype == kMetadataGame)
00437 {
00438 if (type == kArtworkCoverart)
00439 ret = gCoreContext->GetSetting("mythgame.boxartdir");
00440 else if (type == kArtworkFanart)
00441 ret = gCoreContext->GetSetting("mythgame.fanartdir");
00442 else if (type == kArtworkScreenshot)
00443 ret = gCoreContext->GetSetting("mythgame.screenshotdir");
00444 }
00445
00446 return ret;
00447 }
00448
00449 QString getStorageGroupURL(VideoArtworkType type, QString host)
00450 {
00451 QString sgroup;
00452 QString ip = gCoreContext->GetSettingOnHost("BackendServerIP", host);
00453 uint port = gCoreContext->GetSettingOnHost("BackendServerPort",
00454 host).toUInt();
00455
00456 if (type == kArtworkCoverart)
00457 sgroup = "Coverart";
00458 else if (type == kArtworkFanart)
00459 sgroup = "Fanart";
00460 else if (type == kArtworkBanner)
00461 sgroup = "Banners";
00462 else if (type == kArtworkScreenshot)
00463 sgroup = "Screenshots";
00464 else
00465 sgroup = "Default";
00466
00467 return gCoreContext->GenMythURL(ip,port,"",sgroup);
00468 }
00469
00470 QString getLocalStorageGroupPath(VideoArtworkType type, QString host)
00471 {
00472 QString path;
00473
00474 StorageGroup sg;
00475
00476 if (type == kArtworkCoverart)
00477 sg.Init("Coverart", host);
00478 else if (type == kArtworkFanart)
00479 sg.Init("Fanart", host);
00480 else if (type == kArtworkBanner)
00481 sg.Init("Banners", host);
00482 else if (type == kArtworkScreenshot)
00483 sg.Init("Screenshots", host);
00484 else
00485 sg.Init("Default", host);
00486
00487 path = sg.FindNextDirMostFree();
00488
00489 return path;
00490 }
00491
00492 void cleanThumbnailCacheDir()
00493 {
00494 QString cache = QString("%1/thumbcache")
00495 .arg(GetConfDir());
00496 QDir cacheDir(cache);
00497 QStringList thumbs = cacheDir.entryList(QDir::Files);
00498
00499 for (QStringList::const_iterator i = thumbs.end() - 1;
00500 i != thumbs.begin() - 1; --i)
00501 {
00502 QString filename = QString("%1/%2").arg(cache).arg(*i);
00503 QFileInfo fi(filename);
00504 QDateTime lastmod = fi.lastModified();
00505 if (lastmod.addDays(2) < QDateTime::currentDateTime())
00506 {
00507 LOG(VB_GENERAL, LOG_DEBUG, QString("Deleting file %1")
00508 .arg(filename));
00509 QFile::remove(filename);
00510 }
00511 }
00512 }
00513