00001 #include <QImageReader>
00002 #include <QApplication>
00003 #include <QUrl>
00004
00005 #include "mythcontext.h"
00006 #include "mythscreenstack.h"
00007 #include "mythprogressdialog.h"
00008 #include "mythdialogbox.h"
00009 #include "globals.h"
00010 #include "dbaccess.h"
00011 #include "dirscan.h"
00012 #include "videometadatalistmanager.h"
00013 #include "videoscan.h"
00014 #include "videoutils.h"
00015 #include "mythevent.h"
00016 #include "remoteutil.h"
00017 #include "mythlogging.h"
00018
00019 QEvent::Type VideoScanChanges::kEventType =
00020 (QEvent::Type) QEvent::registerEventType();
00021
00022 namespace
00023 {
00024 template <typename DirListType>
00025 class dirhandler : public DirectoryHandler
00026 {
00027 public:
00028 dirhandler(DirListType &video_files,
00029 const QStringList &image_extensions) :
00030 m_video_files(video_files)
00031 {
00032 for (QStringList::const_iterator p = image_extensions.begin();
00033 p != image_extensions.end(); ++p)
00034 {
00035 m_image_ext.insert((*p).toLower());
00036 }
00037 }
00038
00039 DirectoryHandler *newDir(const QString &dir_name,
00040 const QString &fq_dir_name)
00041 {
00042 (void) dir_name;
00043 (void) fq_dir_name;
00044 return this;
00045 }
00046
00047 void handleFile(const QString &file_name,
00048 const QString &fq_file_name,
00049 const QString &extension,
00050 const QString &host)
00051
00052 {
00053 #if 0
00054 LOG(VB_GENERAL, LOG_DEBUG,
00055 QString("handleFile: %1 :: %2").arg(fq_file_name).arg(host));
00056 #endif
00057 (void) file_name;
00058 if (m_image_ext.find(extension.toLower()) == m_image_ext.end())
00059 {
00060 m_video_files[fq_file_name].check = false;
00061 m_video_files[fq_file_name].host = host;
00062 }
00063 }
00064
00065 private:
00066 typedef std::set<QString> image_ext;
00067 image_ext m_image_ext;
00068 DirListType &m_video_files;
00069 };
00070 }
00071
00072 class VideoMetadataListManager;
00073 class MythUIProgressDialog;
00074
00075 VideoScannerThread::VideoScannerThread(QObject *parent) :
00076 MThread("VideoScanner"),
00077 m_RemoveAll(false), m_KeepAll(false),
00078 m_DBDataChanged(false)
00079 {
00080 m_parent = parent;
00081 m_dbmetadata = new VideoMetadataListManager;
00082 m_HasGUI = gCoreContext->HasGUI();
00083 m_ListUnknown = gCoreContext->GetNumSetting("VideoListUnknownFiletypes", 0);
00084 }
00085
00086 VideoScannerThread::~VideoScannerThread()
00087 {
00088 delete m_dbmetadata;
00089 }
00090
00091 void VideoScannerThread::SetHosts(const QStringList &hosts)
00092 {
00093 m_liveSGHosts.clear();
00094 QStringList::const_iterator iter = hosts.begin();
00095 for (; iter != hosts.end(); ++iter)
00096 m_liveSGHosts << iter->toLower();
00097 }
00098
00099 void VideoScannerThread::SetDirs(QStringList dirs)
00100 {
00101 QString master = gCoreContext->GetMasterHostName().toLower();
00102 QStringList searchhosts, mdirs;
00103 m_offlineSGHosts.clear();
00104
00105 QStringList::iterator iter = dirs.begin(), iter2;
00106 while ( iter != dirs.end() )
00107 {
00108 if (iter->startsWith("myth://"))
00109 {
00110 QUrl sgurl = *iter;
00111 QString host = sgurl.host().toLower();
00112 QString path = sgurl.path();
00113
00114 if (!m_liveSGHosts.contains(host))
00115 {
00116
00117 if (!m_offlineSGHosts.contains(host))
00118 m_offlineSGHosts.append(host);
00119
00120 iter = dirs.erase(iter);
00121 continue;
00122 }
00123 else if ((host == master) && (!mdirs.contains(path)))
00124
00125
00126 mdirs.append(path);
00127 else if (!searchhosts.contains(host))
00128
00129
00130 searchhosts.append(host);
00131 }
00132
00133 ++iter;
00134 }
00135
00136 for (iter = m_liveSGHosts.begin(); iter != m_liveSGHosts.end(); ++iter)
00137 if ((!searchhosts.contains(*iter)) && (master != *iter))
00138 for (iter2 = mdirs.begin(); iter2 != mdirs.end(); ++iter2)
00139
00140
00141 dirs.append(gCoreContext->GenMythURL(*iter,
00142 0, *iter2, "Videos"));
00143
00144 m_directories = dirs;
00145 }
00146
00147 void VideoScannerThread::run()
00148 {
00149 RunProlog();
00150
00151 VideoMetadataListManager::metadata_list ml;
00152 VideoMetadataListManager::loadAllFromDatabase(ml);
00153 m_dbmetadata->setList(ml);
00154
00155 QList<QByteArray> image_types = QImageReader::supportedImageFormats();
00156 QStringList imageExtensions;
00157 for (QList<QByteArray>::const_iterator p = image_types.begin();
00158 p != image_types.end(); ++p)
00159 {
00160 imageExtensions.push_back(QString(*p));
00161 }
00162
00163 LOG(VB_GENERAL, LOG_INFO, QString("Beginning Video Scan."));
00164
00165 uint counter = 0;
00166 FileCheckList fs_files;
00167
00168 if (m_HasGUI)
00169 SendProgressEvent(counter, (uint)m_directories.size(),
00170 QObject::tr("Searching for video files"));
00171 for (QStringList::const_iterator iter = m_directories.begin();
00172 iter != m_directories.end(); ++iter)
00173 {
00174 if (!buildFileList(*iter, imageExtensions, fs_files))
00175 {
00176 if (iter->startsWith("myth://"))
00177 {
00178 QUrl sgurl = *iter;
00179 QString host = sgurl.host().toLower();
00180 QString path = sgurl.path();
00181
00182 m_liveSGHosts.removeAll(host);
00183
00184 LOG(VB_GENERAL, LOG_ERR,
00185 QString("Failed to scan :%1:").arg(*iter));
00186 }
00187 }
00188 if (m_HasGUI)
00189 SendProgressEvent(++counter);
00190 }
00191
00192 PurgeList db_remove;
00193 verifyFiles(fs_files, db_remove);
00194 m_DBDataChanged = updateDB(fs_files, db_remove);
00195
00196 if (m_DBDataChanged)
00197 {
00198 QCoreApplication::postEvent(m_parent,
00199 new VideoScanChanges(m_addList, m_movList,
00200 m_delList));
00201
00202 QStringList slist;
00203
00204 QList<int>::const_iterator i;
00205 for (i = m_addList.begin(); i != m_addList.end(); ++i)
00206 slist << QString("added::%1").arg(*i);
00207 for (i = m_movList.begin(); i != m_movList.end(); ++i)
00208 slist << QString("moved::%1").arg(*i);
00209 for (i = m_delList.begin(); i != m_delList.end(); ++i)
00210 slist << QString("deleted::%1").arg(*i);
00211
00212 MythEvent me("VIDEO_LIST_CHANGE", slist);
00213
00214 gCoreContext->SendEvent(me);
00215 }
00216 else
00217 gCoreContext->SendMessage("VIDEO_LIST_NO_CHANGE");
00218
00219 RunEpilog();
00220 }
00221
00222
00223 void VideoScannerThread::removeOrphans(unsigned int id,
00224 const QString &filename)
00225 {
00226 (void) filename;
00227
00228
00229 if (m_RemoveAll)
00230 m_dbmetadata->purgeByID(id);
00231
00232 if (!m_KeepAll && !m_RemoveAll)
00233 {
00234 m_RemoveAll = true;
00235 m_dbmetadata->purgeByID(id);
00236 }
00237 }
00238
00239 void VideoScannerThread::verifyFiles(FileCheckList &files,
00240 PurgeList &remove)
00241 {
00242 int counter = 0;
00243 FileCheckList::iterator iter;
00244
00245 if (m_HasGUI)
00246 SendProgressEvent(counter, (uint)m_dbmetadata->getList().size(),
00247 QObject::tr("Verifying video files"));
00248
00249
00250 for (VideoMetadataListManager::metadata_list::const_iterator p =
00251 m_dbmetadata->getList().begin();
00252 p != m_dbmetadata->getList().end(); ++p)
00253 {
00254 QString lname = (*p)->GetFilename();
00255 QString lhost = (*p)->GetHost().toLower();
00256 if (lname != QString::null)
00257 {
00258 iter = files.find(lname);
00259 if (iter != files.end())
00260 {
00261 if (lhost != iter->second.host)
00262
00263
00264 remove.push_back(std::make_pair((*p)->GetID(), lname));
00265 else
00266
00267
00268 iter->second.check = true;
00269 }
00270 else if (lhost.isEmpty())
00271 {
00272
00273
00274 remove.push_back(std::make_pair((*p)->GetID(), lname));
00275 }
00276 else if (m_liveSGHosts.contains(lhost))
00277 {
00278 LOG(VB_GENERAL, LOG_INFO,
00279 QString("Removing file SG(%1) :%2:")
00280 .arg(lhost).arg(lname));
00281 remove.push_back(std::make_pair((*p)->GetID(), lname));
00282 }
00283 else
00284 {
00285 LOG(VB_GENERAL, LOG_WARNING,
00286 QString("SG(%1) not available. Not removing file :%2:")
00287 .arg(lhost).arg(lname));
00288 if (!m_offlineSGHosts.contains(lhost))
00289 m_offlineSGHosts.append(lhost);
00290 }
00291 }
00292 if (m_HasGUI)
00293 SendProgressEvent(++counter);
00294 }
00295 }
00296
00297 bool VideoScannerThread::updateDB(const FileCheckList &add, const PurgeList &remove)
00298 {
00299 int ret = 0;
00300 uint counter = 0;
00301 if (m_HasGUI)
00302 SendProgressEvent(counter, (uint)(add.size() + remove.size()),
00303 QObject::tr("Updating video database"));
00304
00305 for (FileCheckList::const_iterator p = add.begin(); p != add.end(); ++p)
00306 {
00307
00308 if (!p->second.check)
00309 {
00310 int id = -1;
00311
00312
00313 QString hash = VideoMetadata::VideoFileHash(p->first, p->second.host);
00314 if (hash != "NULL" && !hash.isEmpty())
00315 {
00316 id = VideoMetadata::UpdateHashedDBRecord(hash, p->first, p->second.host);
00317 if (id != -1)
00318 {
00319
00320
00321 LOG(VB_GENERAL, LOG_ERR,
00322 QString("Hash %1 already exists in the "
00323 "database, updating record %2 "
00324 "with new filename %3")
00325 .arg(hash).arg(id).arg(p->first));
00326 m_movList.append(id);
00327 }
00328 }
00329 if (id == -1)
00330 {
00331 VideoMetadata newFile(p->first, hash,
00332 VIDEO_TRAILER_DEFAULT,
00333 VIDEO_COVERFILE_DEFAULT,
00334 VIDEO_SCREENSHOT_DEFAULT,
00335 VIDEO_BANNER_DEFAULT,
00336 VIDEO_FANART_DEFAULT,
00337 VideoMetadata::FilenameToMeta(p->first, 1),
00338 VideoMetadata::FilenameToMeta(p->first, 4),
00339 QString(),
00340 VIDEO_YEAR_DEFAULT,
00341 QDate::fromString("0000-00-00","YYYY-MM-DD"),
00342 VIDEO_INETREF_DEFAULT, 0, QString(),
00343 VIDEO_DIRECTOR_DEFAULT, QString(), VIDEO_PLOT_DEFAULT,
00344 0.0, VIDEO_RATING_DEFAULT, 0, 0,
00345 VideoMetadata::FilenameToMeta(p->first, 2).toInt(),
00346 VideoMetadata::FilenameToMeta(p->first, 3).toInt(),
00347 QDate::currentDate(),
00348 0, ParentalLevel::plLowest);
00349
00350 LOG(VB_GENERAL, LOG_INFO, QString("Adding : %1 : %2 : %3")
00351 .arg(newFile.GetHost()).arg(newFile.GetFilename())
00352 .arg(hash));
00353 newFile.SetHost(p->second.host);
00354 newFile.SaveToDatabase();
00355 m_addList << newFile.GetID();
00356 }
00357 ret += 1;
00358 }
00359 if (m_HasGUI)
00360 SendProgressEvent(++counter);
00361 }
00362
00363
00364 ret += remove.size();
00365 for (PurgeList::const_iterator p = remove.begin(); p != remove.end();
00366 ++p)
00367 {
00368 if (!m_movList.contains(p->first))
00369 {
00370 removeOrphans(p->first, p->second);
00371 m_delList << p->first;
00372 }
00373 if (m_HasGUI)
00374 SendProgressEvent(++counter);
00375 }
00376
00377 return ret;
00378 }
00379
00380 bool VideoScannerThread::buildFileList(const QString &directory,
00381 const QStringList &imageExtensions,
00382 FileCheckList &filelist)
00383 {
00384
00385
00386
00387
00388
00389
00390 LOG(VB_GENERAL,LOG_INFO, QString("buildFileList directory = %1")
00391 .arg(directory));
00392 FileAssociations::ext_ignore_list ext_list;
00393 FileAssociations::getFileAssociation().getExtensionIgnoreList(ext_list);
00394
00395 dirhandler<FileCheckList> dh(filelist, imageExtensions);
00396 return ScanVideoDirectory(directory, &dh, ext_list, m_ListUnknown);
00397 }
00398
00399 void VideoScannerThread::SendProgressEvent(uint progress, uint total,
00400 QString messsage)
00401 {
00402 if (!m_dialog)
00403 return;
00404
00405 ProgressUpdateEvent *pue = new ProgressUpdateEvent(progress, total,
00406 messsage);
00407 QApplication::postEvent(m_dialog, pue);
00408 }
00409
00410 VideoScanner::VideoScanner()
00411 {
00412 m_scanThread = new VideoScannerThread(this);
00413 }
00414
00415 VideoScanner::~VideoScanner()
00416 {
00417 if (m_scanThread && m_scanThread->wait())
00418 delete m_scanThread;
00419 }
00420
00421 void VideoScanner::doScan(const QStringList &dirs)
00422 {
00423 if (m_scanThread->isRunning())
00424 return;
00425
00426 if (gCoreContext->HasGUI())
00427 {
00428 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
00429
00430 MythUIProgressDialog *progressDlg = new MythUIProgressDialog("",
00431 popupStack, "videoscanprogressdialog");
00432
00433 if (progressDlg->Create())
00434 {
00435 popupStack->AddScreen(progressDlg, false);
00436 connect(m_scanThread->qthread(), SIGNAL(finished()),
00437 progressDlg, SLOT(Close()));
00438 connect(m_scanThread->qthread(), SIGNAL(finished()),
00439 SLOT(finishedScan()));
00440 }
00441 else
00442 {
00443 delete progressDlg;
00444 progressDlg = NULL;
00445 }
00446 m_scanThread->SetProgressDialog(progressDlg);
00447 }
00448
00449 QStringList hosts;
00450 if (!RemoteGetActiveBackends(&hosts))
00451 {
00452 LOG(VB_GENERAL, LOG_WARNING, "Could not retrieve list of "
00453 "available backends.");
00454 hosts.clear();
00455 }
00456 m_scanThread->SetHosts(hosts);
00457 m_scanThread->SetDirs(dirs);
00458 m_scanThread->start();
00459 }
00460
00461 void VideoScanner::doScanAll()
00462 {
00463 doScan(GetVideoDirs());
00464 }
00465
00466 void VideoScanner::finishedScan()
00467 {
00468 QStringList failedHosts = m_scanThread->GetOfflineSGHosts();
00469 if (failedHosts.size() > 0)
00470 {
00471 QString msg = tr("Failed to Scan SG Video Hosts") + ":\n\n";
00472
00473 for (int i = 0; i < failedHosts.size(); ++i)
00474 msg += " " + failedHosts.at(i);
00475
00476 msg += "\n" + tr("If they no longer exist please remove them") + "\n\n";
00477
00478 ShowOkPopup(msg);
00479 }
00480
00481 emit finished(m_scanThread->getDataChanged());
00482 }
00483