MythTV  0.26-pre
videoscan.cpp
Go to the documentation of this file.
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                 // mark host as offline to warn user
00117                 if (!m_offlineSGHosts.contains(host))
00118                     m_offlineSGHosts.append(host);
00119                 // erase from directory list to skip scanning
00120                 iter = dirs.erase(iter);
00121                 continue;
00122             }
00123             else if ((host == master) &&  (!mdirs.contains(path)))
00124                 // collect paths defined on master backend so other
00125                 // online backends can be set to fall through to them
00126                 mdirs.append(path);
00127             else if (!searchhosts.contains(host))
00128                 // mark host as having directories defined so it
00129                 // does not fall through to those on the master
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                 // backend is online, but has no directories listed
00140                 // fall back to those on the master backend
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     // TODO: use single DB connection for all calls
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     // For every file we know about, check to see if it still exists.
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                     // file has changed hosts
00263                     // add to delete list for further processing
00264                     remove.push_back(std::make_pair((*p)->GetID(), lname));
00265                 else
00266                     // file is on disk on the proper host and in the database
00267                     // we're done with it
00268                     iter->second.check = true;
00269             }
00270             else if (lhost.isEmpty())
00271             {
00272                 // If it's only in the database, and not on a host we
00273                 // cannot reach, mark it as for removal later.
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         // add files not already in the DB
00308         if (!p->second.check)
00309         {
00310             int id = -1;
00311 
00312             // Are we sure this needs adding?  Let's check our Hash list.
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                     // Whew, that was close.  Let's remove that thing from
00320                     // our purge list, too.
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     // When prompting is restored, account for the answer here.
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     // TODO: FileCheckList is a std::map, keyed off the filename. In the event
00385     // multiple backends have access to shared storage, the potential exists
00386     // for files to be scanned onto the wrong host. Add in some logic to prefer
00387     // the backend with the content stored in a storage group determined to be
00388     // local.
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 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends