MythTV master
videoscan.cpp
Go to the documentation of this file.
1
2#include "videoscan.h"
3
4#include <QApplication>
5#include <QImageReader>
6#include <QUrl>
7#include <utility>
8
9// mythtv
19
20// libmythmetadata
22#include "videoutils.h"
23#include "globals.h"
24#include "dbaccess.h"
25#include "dirscan.h"
26
27const QEvent::Type VideoScanChanges::kEventType =
28 (QEvent::Type) QEvent::registerEventType();
29
30namespace
31{
32 template <typename DirListType>
34 {
35 public:
36 dirhandler(DirListType &video_files,
37 const QStringList &image_extensions) :
38 m_videoFiles(video_files)
39 {
40 for (const auto& ext : std::as_const(image_extensions))
41 m_imageExt.insert(ext.toLower());
42 }
43
44 DirectoryHandler *newDir([[maybe_unused]] const QString &dir_name,
45 [[maybe_unused]] const QString &fq_dir_name) override // DirectoryHandler
46 {
47 return this;
48 }
49
50 void handleFile([[maybe_unused]] const QString &file_name,
51 const QString &fq_file_name,
52 const QString &extension,
53 const QString &host) override // DirectoryHandler
54
55 {
56#if 0
57 LOG(VB_GENERAL, LOG_DEBUG,
58 QString("handleFile: %1 :: %2").arg(fq_file_name).arg(host));
59#endif
60 if (m_imageExt.find(extension.toLower()) == m_imageExt.end())
61 {
62 m_videoFiles[fq_file_name].check = false;
63 m_videoFiles[fq_file_name].host = host;
64 }
65 }
66
67 private:
68 using image_ext = std::set<QString>;
70 DirListType &m_videoFiles;
71 };
72}
73
76
78 MThread("VideoScanner"),
79 m_parent(parent),
80 m_hasGUI(gCoreContext->HasGUI()),
81 m_dbMetadata(new VideoMetadataListManager)
82{
83 m_listUnknown = gCoreContext->GetBoolSetting("VideoListUnknownFiletypes", false);
84}
85
87{
88 delete m_dbMetadata;
89}
90
91void VideoScannerThread::SetHosts(const QStringList &hosts)
92{
93 m_liveSGHosts.clear();
94 for (const auto& host : std::as_const(hosts))
95 m_liveSGHosts << host.toLower();
96}
97
98void VideoScannerThread::SetDirs(QStringList dirs)
99{
100 QString master = gCoreContext->GetMasterHostName().toLower();
101 QStringList searchhosts;
102 QStringList mdirs;
103 m_offlineSGHosts.clear();
104
105 QStringList::iterator iter = dirs.begin();
106 QStringList::iterator iter2;
107 while ( iter != dirs.end() )
108 {
109 if (iter->startsWith("myth://"))
110 {
111 QUrl sgurl = *iter;
112 QString host = sgurl.host().toLower();
113 QString path = sgurl.path();
114
115 if (!m_liveSGHosts.contains(host))
116 {
117 // mark host as offline to warn user
118 if (!m_offlineSGHosts.contains(host))
119 m_offlineSGHosts.append(host);
120 // erase from directory list to skip scanning
121 iter = dirs.erase(iter);
122 continue;
123 }
124 if ((host == master) && (!mdirs.contains(path)))
125 {
126 // collect paths defined on master backend so other
127 // online backends can be set to fall through to them
128 mdirs.append(path);
129 }
130 else if (!searchhosts.contains(host))
131 {
132 // mark host as having directories defined so it
133 // does not fall through to those on the master
134 searchhosts.append(host);
135 }
136 }
137
138 ++iter;
139 }
140
141 for (iter = m_liveSGHosts.begin(); iter != m_liveSGHosts.end(); ++iter)
142 {
143 if ((!searchhosts.contains(*iter)) && (master != *iter))
144 {
145 for (iter2 = mdirs.begin(); iter2 != mdirs.end(); ++iter2)
146 {
147 // backend is online, but has no directories listed
148 // fall back to those on the master backend
149 dirs.append(MythCoreContext::GenMythURL(*iter,
150 0, *iter2, "Videos"));
151 }
152 }
153 }
154
155 m_directories = dirs;
156}
157
159{
160 RunProlog();
161
165
166 QList<QByteArray> image_types = QImageReader::supportedImageFormats();
167 QStringList imageExtensions;
168 for (const auto & format : std::as_const(image_types))
169 imageExtensions.push_back(QString(format));
170
171 LOG(VB_GENERAL, LOG_INFO, QString("Beginning Video Scan."));
172
173 uint counter = 0;
174 FileCheckList fs_files;
175
176 if (m_hasGUI)
177 SendProgressEvent(counter, (uint)m_directories.size(),
178 tr("Searching for video files"));
179 for (const auto & dir : std::as_const(m_directories))
180 {
181 if (!buildFileList(dir, imageExtensions, fs_files))
182 {
183 if (dir.startsWith("myth://"))
184 {
185 QUrl sgurl = dir;
186 QString host = sgurl.host().toLower();
187
188 m_liveSGHosts.removeAll(host);
189
190 LOG(VB_GENERAL, LOG_ERR,
191 QString("Failed to scan :%1:").arg(dir));
192 }
193 }
194 if (m_hasGUI)
195 SendProgressEvent(++counter);
196 }
197
198 PurgeList db_remove;
199 verifyFiles(fs_files, db_remove);
200 m_dbDataChanged = updateDB(fs_files, db_remove);
201
202 if (m_dbDataChanged)
203 {
204 QCoreApplication::postEvent(m_parent,
206 m_delList));
207
208 QStringList slist;
209
210 for (int id : std::as_const(m_addList))
211 slist << QString("added::%1").arg(id);
212 for (int id : std::as_const(m_movList))
213 slist << QString("moved::%1").arg(id);
214 for (int id : std::as_const(m_delList))
215 slist << QString("deleted::%1").arg(id);
216
217 MythEvent me("VIDEO_LIST_CHANGE", slist);
218
220 }
221 else
222 {
223 gCoreContext->SendMessage("VIDEO_LIST_NO_CHANGE");
224 }
225
226 RunEpilog();
227}
228
229
231 [[maybe_unused]] const QString &filename)
232{
233 // TODO: use single DB connection for all calls
234 if (m_removeAll)
236
237 if (!m_keepAll && !m_removeAll)
238 {
239 m_removeAll = true;
241 }
242}
243
245 PurgeList &remove)
246{
247 int counter = 0;
248 FileCheckList::iterator iter;
249
250 if (m_hasGUI)
251 SendProgressEvent(counter, (uint)m_dbMetadata->getList().size(),
252 tr("Verifying video files"));
253
254 // For every file we know about, check to see if it still exists.
255 for (const auto & file : m_dbMetadata->getList())
256 {
257 QString lname = file->GetFilename();
258 QString lhost = file->GetHost().toLower();
259 if (!lname.isEmpty())
260 {
261 iter = files.find(lname);
262 if (iter != files.end())
263 {
264 if (lhost != iter->second.host)
265 {
266 // file has changed hosts
267 // add to delete list for further processing
268 remove.emplace_back(file->GetID(), lname);
269 }
270 else
271 {
272 // file is on disk on the proper host and in the database
273 // we're done with it
274 iter->second.check = true;
275 }
276 }
277 else if (lhost.isEmpty())
278 {
279 // If it's only in the database, and not on a host we
280 // cannot reach, mark it as for removal later.
281 remove.emplace_back(file->GetID(), lname);
282 }
283 else if (m_liveSGHosts.contains(lhost))
284 {
285 LOG(VB_GENERAL, LOG_INFO,
286 QString("Removing file SG(%1) :%2:")
287 .arg(lhost, lname));
288 remove.emplace_back(file->GetID(), lname);
289 }
290 else
291 {
292 LOG(VB_GENERAL, LOG_WARNING,
293 QString("SG(%1) not available. Not removing file :%2:")
294 .arg(lhost, lname));
295 if (!m_offlineSGHosts.contains(lhost))
296 m_offlineSGHosts.append(lhost);
297 }
298 }
299 if (m_hasGUI)
300 SendProgressEvent(++counter);
301 }
302}
303
305{
306 int ret = 0;
307 uint counter = 0;
308 if (m_hasGUI)
309 SendProgressEvent(counter, (uint)(add.size() + remove.size()),
310 tr("Updating video database"));
311
312 for (auto p = add.cbegin(); p != add.cend(); ++p)
313 {
314 // add files not already in the DB
315 if (!p->second.check)
316 {
317 int id = -1;
318
319 // Are we sure this needs adding? Let's check our Hash list.
320 QString hash = VideoMetadata::VideoFileHash(p->first, p->second.host);
321 if (hash != "NULL" && !hash.isEmpty())
322 {
323 id = VideoMetadata::UpdateHashedDBRecord(hash, p->first, p->second.host);
324 if (id != -1)
325 {
326 // Whew, that was close. Let's remove that thing from
327 // our purge list, too.
328 LOG(VB_GENERAL, LOG_ERR,
329 QString("Hash %1 already exists in the "
330 "database, updating record %2 "
331 "with new filename %3")
332 .arg(hash).arg(id).arg(p->first));
333 m_movList.append(id);
334 }
335 }
336 if (id == -1)
337 {
338 VideoMetadata newFile(
339 p->first, QString(), hash,
345 QString(), QString(), QString(), QString(),
346 QString(),
348 QDate::fromString("0000-00-00","YYYY-MM-DD"),
349 VIDEO_INETREF_DEFAULT, 0, QString(),
351 0.0, VIDEO_RATING_DEFAULT, 0, 0,
352 0, 0,
353 MythDate::current().date(),
355
356 LOG(VB_GENERAL, LOG_INFO, QString("Adding : %1 : %2 : %3")
357 .arg(newFile.GetHost(), newFile.GetFilename(), hash));
358 newFile.SetHost(p->second.host);
359 newFile.SaveToDatabase();
360 m_addList << newFile.GetID();
361 }
362 ret += 1;
363 }
364 if (m_hasGUI)
365 SendProgressEvent(++counter);
366 }
367
368 // When prompting is restored, account for the answer here.
369 ret += remove.size();
370 for (const auto & item : remove)
371 {
372 if (!m_movList.contains(item.first))
373 {
374 removeOrphans(item.first, item.second);
375 m_delList << item.first;
376 }
377 if (m_hasGUI)
378 SendProgressEvent(++counter);
379 }
380
381 return ret > 0;
382}
383
384bool VideoScannerThread::buildFileList(const QString &directory,
385 const QStringList &imageExtensions,
386 FileCheckList &filelist) const
387{
388 // TODO: FileCheckList is a std::map, keyed off the filename. In the event
389 // multiple backends have access to shared storage, the potential exists
390 // for files to be scanned onto the wrong host. Add in some logic to prefer
391 // the backend with the content stored in a storage group determined to be
392 // local.
393
394 LOG(VB_GENERAL,LOG_INFO, QString("buildFileList directory = %1")
395 .arg(directory));
398
399 dirhandler<FileCheckList> dh(filelist, imageExtensions);
400 return ScanVideoDirectory(directory, &dh, ext_list, m_listUnknown);
401}
402
404 QString messsage)
405{
406 if (!m_dialog)
407 return;
408
409 auto *pue = new ProgressUpdateEvent(progress, total, std::move(messsage));
410 QApplication::postEvent(m_dialog, pue);
411}
412
414 : m_scanThread(new VideoScannerThread(this))
415{
416}
417
419{
421 delete m_scanThread;
422}
423
424void VideoScanner::doScan(const QStringList &dirs)
425{
426 if (m_scanThread->isRunning())
427 return;
428
429 if (gCoreContext->HasGUI())
430 {
431 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
432
433 auto *progressDlg = new MythUIProgressDialog("", popupStack,
434 "videoscanprogressdialog");
435
436 if (progressDlg->Create())
437 {
438 popupStack->AddScreen(progressDlg, false);
439 connect(m_scanThread->qthread(), &QThread::finished,
440 progressDlg, &MythScreenType::Close);
441 connect(m_scanThread->qthread(), &QThread::finished,
443 }
444 else
445 {
446 delete progressDlg;
447 progressDlg = nullptr;
448 }
449 m_scanThread->SetProgressDialog(progressDlg);
450 }
451
452 QStringList hosts;
453 if (!RemoteGetActiveBackends(&hosts))
454 {
455 LOG(VB_GENERAL, LOG_WARNING, "Could not retrieve list of "
456 "available backends.");
457 hosts.clear();
458 }
459 m_scanThread->SetHosts(hosts);
460 m_scanThread->SetDirs(dirs);
462}
463
465{
467}
468
470{
471 QStringList failedHosts = m_scanThread->GetOfflineSGHosts();
472 if (!failedHosts.empty())
473 {
474 QString hosts = failedHosts.join(" ");
475 QString msg = tr("Failed to Scan SG Video Hosts:\n\n%1\n\n"
476 "If they no longer exist please remove them")
477 .arg(hosts);
478
479 ShowOkPopup(msg);
480 }
481
483}
484
void getExtensionIgnoreList(ext_ignore_list &ext_ignore) const
Definition: dbaccess.cpp:816
std::vector< std::pair< QString, bool > > ext_ignore_list
Definition: dbaccess.h:155
static FileAssociations & getFileAssociation()
Definition: dbaccess.cpp:836
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
bool isRunning(void) const
Definition: mthread.cpp:263
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:196
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:283
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:209
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:233
void SendMessage(const QString &message)
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
QString GetMasterHostName(void)
void SendEvent(const MythEvent &event)
bool GetBoolSetting(const QString &key, bool defaultval=false)
bool HasGUI(void) const
This class is used as a container for messages.
Definition: mythevent.h:17
MythScreenStack * GetStack(const QString &Stackname)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
virtual void Close()
MythUIProgressDialog(QString message, MythScreenStack *parent, const char *name)
MythUIType * m_parent
Definition: mythuitype.h:297
void setList(metadata_list &list)
std::list< VideoMetadataPtr > metadata_list
bool purgeByID(unsigned int db_id)
static void loadAllFromDatabase(metadata_list &items, const QString &sql="", const QStringList &bindValues=QStringList())
Load videometadata database into memory.
const metadata_list & getList() const
const QString & GetHost() const
unsigned int GetID() const
static QString VideoFileHash(const QString &file_name, const QString &host)
static int UpdateHashedDBRecord(const QString &hash, const QString &file_name, const QString &host)
void SetHost(const QString &host)
const QString & GetFilename() const
static const Type kEventType
Definition: videoscan.h:57
std::vector< std::pair< int, QString > > PurgeList
Definition: videoscan.h:85
QList< int > m_addList
Definition: videoscan.h:112
bool getDataChanged() const
Definition: videoscan.h:73
void SetDirs(QStringList dirs)
Definition: videoscan.cpp:98
bool updateDB(const FileCheckList &add, const PurgeList &remove)
Definition: videoscan.cpp:304
std::map< QString, CheckStruct > FileCheckList
Definition: videoscan.h:86
~VideoScannerThread() override
Definition: videoscan.cpp:86
QObject * m_parent
Definition: videoscan.h:99
void SendProgressEvent(uint progress, uint total=0, QString messsage=QString())
Definition: videoscan.cpp:403
QStringList GetOfflineSGHosts(void)
Definition: videoscan.h:72
QList< int > m_movList
Definition: videoscan.h:113
MythUIProgressDialog * m_dialog
Definition: videoscan.h:110
void SetProgressDialog(MythUIProgressDialog *dialog)
Definition: videoscan.h:71
VideoMetadataListManager * m_dbMetadata
Definition: videoscan.h:109
void SetHosts(const QStringList &hosts)
Definition: videoscan.cpp:91
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: videoscan.cpp:158
void verifyFiles(FileCheckList &files, PurgeList &remove)
Definition: videoscan.cpp:244
QList< int > m_delList
Definition: videoscan.h:114
QStringList m_offlineSGHosts
Definition: videoscan.h:107
VideoScannerThread(QObject *parent)
Definition: videoscan.cpp:77
bool buildFileList(const QString &directory, const QStringList &imageExtensions, FileCheckList &filelist) const
Definition: videoscan.cpp:384
void removeOrphans(unsigned int id, const QString &filename)
Definition: videoscan.cpp:230
QStringList m_liveSGHosts
Definition: videoscan.h:106
QStringList m_directories
Definition: videoscan.h:105
void doScanAll(void)
Definition: videoscan.cpp:464
class VideoScannerThread * m_scanThread
Definition: videoscan.h:40
void finished(bool)
void doScan(const QStringList &dirs)
Definition: videoscan.cpp:424
~VideoScanner() override
Definition: videoscan.cpp:418
void finishedScan()
Definition: videoscan.cpp:469
void handleFile(const QString &file_name, const QString &fq_file_name, const QString &extension, const QString &host) override
Definition: videoscan.cpp:50
DirectoryHandler * newDir(const QString &dir_name, const QString &fq_dir_name) override
Definition: videoscan.cpp:44
dirhandler(DirListType &video_files, const QStringList &image_extensions)
Definition: videoscan.cpp:36
bool ScanVideoDirectory(const QString &start_path, DirectoryHandler *handler, const FileAssociations::ext_ignore_list &ext_disposition, bool list_unknown_extensions)
Definition: dirscan.cpp:228
unsigned int uint
Definition: freesurround.h:24
const QString VIDEO_INETREF_DEFAULT
Definition: globals.cpp:24
const QString VIDEO_PLOT_DEFAULT
Definition: globals.cpp:32
const QString VIDEO_TRAILER_DEFAULT
Definition: globals.cpp:26
const QString VIDEO_BANNER_DEFAULT
Definition: globals.cpp:28
const QString VIDEO_SCREENSHOT_DEFAULT
Definition: globals.cpp:27
const QString VIDEO_FANART_DEFAULT
Definition: globals.cpp:29
const QString VIDEO_RATING_DEFAULT
Definition: globals.cpp:30
const QString VIDEO_DIRECTOR_DEFAULT
Definition: globals.cpp:23
const QString VIDEO_COVERFILE_DEFAULT
Definition: globals.cpp:25
bool progress
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
bool RemoteGetActiveBackends(QStringList *list)
return list of backends currently connected to the master
Definition: remoteutil.cpp:576
static constexpr uint16_t VIDEO_YEAR_DEFAULT
Definition: videometadata.h:18
QStringList GetVideoDirs()
Definition: videoutils.cpp:116