|
MythTV
0.26-pre
|
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
1.7.6.1