MythTV  master
imagethumbs.cpp
Go to the documentation of this file.
1 #include "imagethumbs.h"
2 
3 #include <QDir>
4 #include <QStringList>
5 
6 #include "mythlogging.h"
7 #include "mythcorecontext.h" // for events
8 #include "mythsystemlegacy.h" // for previewgen
9 #include "mythdirs.h" // for previewgen
10 #include "exitcodes.h" // for previewgen
11 #include "mythimage.h"
12 
13 #include "imagemetadata.h"
14 
18 template <class DBFS>
20 {
21  cancel();
22  wait();
23 }
24 
25 
29 template <class DBFS>
31 {
32  // Clear all queues
33  QMutexLocker locker(&m_mutex);
34  m_requestQ.clear();
35  m_backgroundQ.clear();
36 }
37 
38 
43 template <class DBFS>
45 {
46  if (task)
47  {
48  bool background = task->m_priority > kBackgroundPriority;
49 
50  QMutexLocker locker(&m_mutex);
51  if (background)
52  m_backgroundQ.insert(task->m_priority, task);
53  else
54  m_requestQ.insert(task->m_priority, task);
55 
56  // restart if not already running
57  if ((m_doBackground || !background) && !this->isRunning())
58  this->start();
59  }
60 }
61 
62 
67 template <class DBFS>
68 void ThumbThread<DBFS>::AbortDevice(int devId, const QString &action)
69 {
70  if (action == "DEVICE CLOSE ALL" || action == "DEVICE CLEAR ALL")
71  {
72  if (isRunning())
73  LOG(VB_FILE, LOG_INFO,
74  QString("Aborting all thumbnails %1").arg(action));
75 
76  // Abort thumbnail generation for all devices
77  cancel();
78  return;
79  }
80 
81  LOG(VB_FILE, LOG_INFO, QString("Aborting thumbnails (%1) for '%2'")
82  .arg(action).arg(m_dbfs.DeviceName(devId)));
83 
84  // Remove all tasks for specific device from every queue
85  QMutexLocker locker(&m_mutex);
86  RemoveTasks(m_requestQ, devId);
87  RemoveTasks(m_backgroundQ, devId);
88  if (isRunning())
89  // Wait until current task is complete - it may be using the device
90  m_taskDone.wait(&m_mutex, 3000);
91 }
92 
93 
94 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
95 #define QMutableMultiMapIterator QMutableMapIterator
96 #endif
97 
101 template <class DBFS>
103 {
104  QMutableMultiMapIterator<int, TaskPtr> it(queue);
105  while (it.hasNext())
106  {
107  it.next();
108  TaskPtr task = it.value();
109  // All thumbs in a task come from same device
110  if (task && !task->m_images.isEmpty()
111  && task->m_images.at(0)->m_device == devId)
112  it.remove();
113  }
114 }
115 
116 
123 template <class DBFS>
125 {
126  RunProlog();
127 
128  setPriority(QThread::LowestPriority);
129 
130  while (true)
131  {
132  // Signal previous task is complete (its files have been closed)
133  m_taskDone.wakeAll();
134 
135  // Do all we can to run in background
136  QThread::yieldCurrentThread();
137 
138  // process next highest-priority task
139  TaskPtr task;
140  {
141  QMutexLocker locker(&m_mutex);
142  if (!m_requestQ.isEmpty())
143  task = m_requestQ.take(m_requestQ.constBegin().key());
144  else if (m_doBackground && !m_backgroundQ.isEmpty())
145  task = m_backgroundQ.take(m_backgroundQ.constBegin().key());
146  else
147  // quit when both queues exhausted
148  break;
149  }
150 
151  // Shouldn't receive empty requests
152  if (task->m_images.isEmpty())
153  continue;
154 
155  if (task->m_action == "CREATE")
156  {
157  ImagePtrK im = task->m_images.at(0);
158 
159  QString err = CreateThumbnail(im, task->m_priority);
160 
161  if (!err.isEmpty())
162  {
163  LOG(VB_GENERAL, LOG_ERR, QString("%1").arg(err));
164  }
165  else if (task->m_notify)
166  {
167  // notify clients when done
168  m_dbfs.Notify("THUMB_AVAILABLE",
169  QStringList(QString::number(im->m_id)));
170  }
171  }
172  else if (task->m_action == "DELETE")
173  {
174  for (const auto& im : qAsConst(task->m_images))
175  {
176  QString thumbnail = im->m_thumbPath;
177  if (!QDir::root().remove(thumbnail))
178  {
179  LOG(VB_FILE, LOG_WARNING,
180  QString("Failed to delete thumbnail %1").arg(thumbnail));
181  continue;
182  }
183  LOG(VB_FILE, LOG_DEBUG,
184  QString("Deleted thumbnail %1").arg(thumbnail));
185 
186  // Clean up empty dirs
187  QString path = QFileInfo(thumbnail).path();
188  if (QDir::root().rmpath(path))
189  LOG(VB_FILE, LOG_DEBUG,
190  QString("Cleaned up path %1").arg(path));
191  }
192  }
193  else if (task->m_action == "MOVE")
194  {
195  for (const auto& im : qAsConst(task->m_images))
196  {
197  // Build new thumb path
198  QString newThumbPath =
199  m_dbfs.GetAbsThumbPath(m_dbfs.ThumbDir(im->m_device),
200  m_dbfs.ThumbPath(*im.data()));
201 
202  // Ensure path exists
203  if (QDir::root().mkpath(QFileInfo(newThumbPath).path())
204  && QFile::rename(im->m_thumbPath, newThumbPath))
205  {
206  LOG(VB_FILE, LOG_DEBUG, QString("Moved thumbnail %1 -> %2")
207  .arg(im->m_thumbPath, newThumbPath));
208  }
209  else
210  {
211  LOG(VB_FILE, LOG_WARNING,
212  QString("Failed to rename thumbnail %1 -> %2")
213  .arg(im->m_thumbPath, newThumbPath));
214  continue;
215  }
216 
217  // Clean up empty dirs
218  QString path = QFileInfo(im->m_thumbPath).path();
219  if (QDir::root().rmpath(path))
220  LOG(VB_FILE, LOG_DEBUG,
221  QString("Cleaned up path %1").arg(path));
222  }
223  }
224  else
225  LOG(VB_GENERAL, LOG_ERR,
226  QString("Unknown task %1").arg(task->m_action));
227  }
228 
229  RunEpilog();
230 }
231 
232 
238 template <class DBFS>
239 QString ThumbThread<DBFS>::CreateThumbnail(ImagePtrK im, int thumbPriority)
240 {
241  if (QDir::root().exists(im->m_thumbPath))
242  {
243  LOG(VB_FILE, LOG_DEBUG, QString("[%3] %2 already exists")
244  .arg(im->m_thumbPath).arg(thumbPriority));
245  return QString(); // Notify anyway
246  }
247 
248  // Local filenames are always absolute
249  // Remote filenames are absolute from the scanner only
250  // UI requests (derived from Db) are relative
251  QString imagePath = m_dbfs.GetAbsFilePath(im);
252  if (imagePath.isEmpty())
253  return QString("Empty image path: %1").arg(im->m_filePath);
254 
255  // Ensure path exists
256  QDir::root().mkpath(QFileInfo(im->m_thumbPath).path());
257 
258  QImage image;
259  if (im->m_type == kImageFile)
260  {
261  if (!image.load(imagePath))
262  return QString("Failed to open image %1").arg(imagePath);
263 
264  // Resize to optimise load/display time by FE's
265  image = image.scaled(QSize(240,180), Qt::KeepAspectRatio, Qt::SmoothTransformation);
266  }
267  else if (im->m_type == kVideoFile)
268  {
269  // Run Preview Generator in foreground
270  QString cmd = GetAppBinDir() + MYTH_APPNAME_MYTHPREVIEWGEN;
271  QStringList args;
272  args << "--size 320x240"; // Video thumbnails are also shown in slideshow
273  args << QString("--infile '%1'").arg(imagePath);
274  args << QString("--outfile '%1'").arg(im->m_thumbPath);
275 
276  MythSystemLegacy ms(cmd, args,
277  kMSRunShell |
283  ms.SetNice(10);
284  ms.SetIOPrio(7);
285  ms.Run(30s);
286  if (ms.Wait() != GENERIC_EXIT_OK)
287  {
288  LOG(VB_GENERAL, LOG_ERR, QString("Failed to run %2 %3")
289  .arg(cmd, args.join(" ")));
290  return QString("Preview Generator failed for %1").arg(imagePath);
291  }
292 
293  if (!image.load(im->m_thumbPath))
294  return QString("Failed to open preview %1").arg(im->m_thumbPath);
295  }
296  else
297  return QString("Can't create thumbnail for type %1 (image %2)")
298  .arg(im->m_type).arg(imagePath);
299 
300  // Compensate for any Qt auto-orientation
301  int orientBy = Orientation(im->m_orientation)
302  .GetCurrent(im->m_type == kImageFile);
303 
304  // Orientate now to optimise load/display time - no orientation
305  // is required when displaying thumbnails
306  image = MythImage::ApplyExifOrientation(image, orientBy);
307 
308  // Create the thumbnail
309  if (!image.save(im->m_thumbPath))
310  return QString("Failed to create thumbnail %1").arg(im->m_thumbPath);
311 
312  LOG(VB_FILE, LOG_INFO, QString("[%2] Created %1")
313  .arg(im->m_thumbPath).arg(thumbPriority));
314  return QString();
315 }
316 
317 
321 template <class DBFS>
323 {
324  QMutexLocker locker(&m_mutex);
325  m_doBackground = !pause;
326 
327  // restart if not already running
328  if (m_doBackground && !this->isRunning())
329  this->start();
330 }
331 
332 
336 template <class DBFS>
338  : m_dbfs(*dbfs),
339  m_imageThread(new ThumbThread<DBFS>("ImageThumbs", dbfs)),
340  m_videoThread(new ThumbThread<DBFS>("VideoThumbs", dbfs))
341 {}
342 
343 
347 template <class DBFS>
349 {
350  delete m_imageThread;
351  delete m_videoThread;
352  m_imageThread = m_videoThread = nullptr;
353 }
354 
355 
362 template <class DBFS>
363 void ImageThumb<DBFS>::ClearThumbs(int devId, const QString &action)
364 {
365  // Cancel pending requests for the device
366  // Waits for current generator task to complete
367  if (m_imageThread)
368  m_imageThread->AbortDevice(devId, action);
369  if (m_videoThread)
370  m_videoThread->AbortDevice(devId, action);
371 
372  // Remove devices now they are not in use
373  QStringList mountPaths = m_dbfs.CloseDevices(devId, action);
374 // if (mountPaths.isEmpty())
375 // return;
376 
377  // Generate file & thumbnail urls (as per image cache) of mountpoints
378  QStringList mesg;
379  for (const auto& mount : qAsConst(mountPaths))
380  mesg << m_dbfs.MakeFileUrl(mount)
381  << m_dbfs.MakeThumbUrl(mount);
382 
383  // Notify clients to clear cache
384  m_dbfs.Notify("IMAGE_DEVICE_CHANGED", mesg);
385 }
386 
387 
393 template <class DBFS>
395 {
396  // Determine affected images and redundant images/thumbnails
397  QStringList ids;
398 
399  // Pictures & videos are deleted by their own threads
400  ImageListK pics;
401  ImageListK videos;
402  for (const auto& im : qAsConst(images))
403  {
404  if (im->m_type == kVideoFile)
405  videos.append(im);
406  else
407  pics.append(im);
408 
409  ids << QString::number(im->m_id);
410  }
411 
412  if (!pics.isEmpty() && m_imageThread)
413  m_imageThread->Enqueue(TaskPtr(new ThumbTask("DELETE", pics)));
414  if (!videos.isEmpty() && m_videoThread)
415  m_videoThread->Enqueue(TaskPtr(new ThumbTask("DELETE", videos)));
416  return ids.join(",");
417 }
418 
419 
426 template <class DBFS>
427 void ImageThumb<DBFS>::CreateThumbnail(const ImagePtrK &im, int priority,
428  bool notify)
429 {
430  if (!im)
431  return;
432 
433  // Allocate task a (hopefully) unique priority
434  if (priority == kBackgroundPriority)
435  priority = Priority(*im);
436 
437  TaskPtr task(new ThumbTask("CREATE", im, priority, notify));
438 
439  if (im->m_type == kImageFile && m_imageThread)
440  {
441  m_imageThread->Enqueue(task);
442  }
443  else if (im->m_type == kVideoFile && m_videoThread)
444  {
445  m_videoThread->Enqueue(task);
446  }
447  else
448  {
449  LOG(VB_FILE, LOG_INFO, QString("Ignoring create thumbnail %1, type %2")
450  .arg(im->m_id).arg(im->m_type));
451  }
452 }
453 
454 
459 template <class DBFS>
461 {
462  if (!im)
463  return;
464 
465  TaskPtr task(new ThumbTask("MOVE", im));
466 
467  if (im->m_type == kImageFile && m_imageThread)
468  {
469  m_imageThread->Enqueue(task);
470  }
471  else if (im->m_type == kVideoFile && m_videoThread)
472  {
473  m_videoThread->Enqueue(task);
474  }
475  else
476  {
477  LOG(VB_FILE, LOG_INFO, QString("Ignoring move thumbnail %1, type %2")
478  .arg(im->m_id).arg(im->m_type));
479  }
480 }
481 
482 
486 template <class DBFS>
488 {
489  LOG(VB_FILE, LOG_INFO, QString("Paused %1").arg(pause));
490 
491  if (m_imageThread)
492  m_imageThread->PauseBackground(pause);
493  if (m_videoThread)
494  m_videoThread->PauseBackground(pause);
495 }
496 
497 
498 // Must define the valid template implementations to generate code for the
499 // instantiations (as they are defined in the cpp rather than header).
500 // Otherwise the linker will fail with undefined references...
501 #include "imagemanager.h"
502 template class ImageThumb<ImageDbLocal>;
503 template class ImageThumb<ImageDbSg>;
ImagePtrK
QSharedPointer< ImageItemK > ImagePtrK
Definition: imagetypes.h:165
build_compdb.args
args
Definition: build_compdb.py:11
ImageThumb::PauseBackground
void PauseBackground(bool pause)
Pauses or restarts processing of background tasks (scanner requests)
Definition: imagethumbs.cpp:487
GENERIC_EXIT_OK
#define GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:10
kVideoFile
@ kVideoFile
A video.
Definition: imagetypes.h:40
ImageThumb::CreateThumbnail
void CreateThumbnail(const ImagePtrK &im, int priority=kBackgroundPriority, bool notify=false)
Creates a thumbnail.
Definition: imagethumbs.cpp:427
kMSDontBlockInputDevs
@ kMSDontBlockInputDevs
avoid blocking LIRC & Joystick Menu
Definition: mythsystem.h:36
MythSystemLegacy
Definition: mythsystemlegacy.h:67
ThumbThread::~ThumbThread
~ThumbThread() override
Destructor.
Definition: imagethumbs.cpp:19
imagemanager.h
Manages a collection of images.
MythSystemLegacy::SetIOPrio
bool SetIOPrio(int prio)
Definition: mythsystemlegacy.cpp:202
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
ThumbThread::RemoveTasks
static void RemoveTasks(ThumbQueue &queue, int devId)
Definition: imagethumbs.cpp:102
MYTH_APPNAME_MYTHPREVIEWGEN
#define MYTH_APPNAME_MYTHPREVIEWGEN
Definition: mythcorecontext.h:26
mythdirs.h
ThumbThread< ImageDbSg >::ThumbQueue
QMultiMap< int, TaskPtr > ThumbQueue
A priority queue where 0 is highest priority.
Definition: imagethumbs.h:111
Orientation::GetCurrent
int GetCurrent(bool compensate)
Determines orientation required for an image.
Definition: imagemetadata.cpp:85
ImageThumb::DeleteThumbs
QString DeleteThumbs(const ImageList &images)
Remove specific thumbnails.
Definition: imagethumbs.cpp:394
mythsystemlegacy.h
ImageThumb< ImageDbLocal >
ImageThumb::~ImageThumb
~ImageThumb()
Destructor .
Definition: imagethumbs.cpp:348
ThumbThread::PauseBackground
void PauseBackground(bool pause)
Pauses or restarts processing of background tasks (scanner requests)
Definition: imagethumbs.cpp:322
mythlogging.h
MythImage::ApplyExifOrientation
static QImage ApplyExifOrientation(QImage &image, int orientation)
Definition: mythimage.cpp:99
ThumbThread::CreateThumbnail
QString CreateThumbnail(ImagePtrK im, int thumbPriority)
Generate thumbnail for an image.
Definition: imagethumbs.cpp:239
MythSystemLegacy::Wait
uint Wait(std::chrono::seconds timeout=0s)
Definition: mythsystemlegacy.cpp:242
ImageListK
QList< ImagePtrK > ImageListK
Definition: imagetypes.h:166
ThumbThread::cancel
void cancel()
Clears all queues so that the thread will terminate.
Definition: imagethumbs.cpp:30
ThumbThread
A generator worker thread.
Definition: imagethumbs.h:87
kMSPropagateLogs
@ kMSPropagateLogs
add arguments for MythTV log propagation
Definition: mythsystem.h:52
ImageList
QVector< ImagePtr > ImageList
Definition: imagetypes.h:160
ThumbThread::AbortDevice
void AbortDevice(int devId, const QString &action)
Clears thumbnail request queue.
Definition: imagethumbs.cpp:68
kMSRunShell
@ kMSRunShell
run process through shell
Definition: mythsystem.h:43
kMSAutoCleanup
@ kMSAutoCleanup
automatically delete if backgrounded
Definition: mythsystem.h:45
ThumbTask
A generator request that is queued.
Definition: imagethumbs.h:41
mythimage.h
isRunning
static bool isRunning(const char *program)
Returns true if a program containing the specified string is running on this machine.
Definition: mythtv/programs/mythshutdown/main.cpp:202
kImageFile
@ kImageFile
A picture.
Definition: imagetypes.h:39
mythcorecontext.h
TaskPtr
QSharedPointer< ThumbTask > TaskPtr
Definition: imagethumbs.h:82
kMSProcessEvents
@ kMSProcessEvents
process events while waiting
Definition: mythsystem.h:39
imagemetadata.h
Handles Exif/FFMpeg metadata tags for images.
ImageThumb::ClearThumbs
void ClearThumbs(int devId, const QString &action)
Clears thumbnails for a device.
Definition: imagethumbs.cpp:363
imagethumbs.h
Creates and manages thumbnails.
MythSystemLegacy::SetNice
bool SetNice(int nice)
Definition: mythsystemlegacy.cpp:193
GetAppBinDir
QString GetAppBinDir(void)
Definition: mythdirs.cpp:221
ThumbThread::Enqueue
void Enqueue(const TaskPtr &task)
Queues a Create request.
Definition: imagethumbs.cpp:44
build_compdb.action
action
Definition: build_compdb.py:9
ImageThumb::MoveThumbnail
void MoveThumbnail(const ImagePtrK &im)
Renames a thumbnail.
Definition: imagethumbs.cpp:460
kBackgroundPriority
@ kBackgroundPriority
Scanner background request.
Definition: imagethumbs.h:36
MythSystemLegacy::Run
void Run(std::chrono::seconds timeout=0s)
Runs a command inside the /bin/sh shell. Returns immediately.
Definition: mythsystemlegacy.cpp:212
ImageThumb::ImageThumb
ImageThumb(DBFS *dbfs)
Constructor.
Definition: imagethumbs.cpp:337
kMSDontDisableDrawing
@ kMSDontDisableDrawing
avoid disabling UI drawing
Definition: mythsystem.h:37
exitcodes.h
ThumbThread::run
void run() override
Handles thumbnail requests by priority.
Definition: imagethumbs.cpp:124
Priority
Definition: channelsettings.cpp:191
Orientation
Encapsulates Exif orientation processing.
Definition: imagemetadata.h:62