MythTV  master
imagemanager.cpp
Go to the documentation of this file.
1 #include "imagemanager.h"
2 
3 #include <QImageReader>
4 #include <QRunnable>
5 #include <utility>
6 
7 #include "dbaccess.h" // for FileAssociations
8 #include "mthreadpool.h"
9 #include "mythdate.h"
10 #include "mythmediamonitor.h"
11 
12 
13 #define LOC QString("ImageManager: ")
14 #define DBLOC QString("ImageDb(%1): ").arg(m_table)
15 
16 // Must be empty as it's prepended to path
17 #define STORAGE_GROUP_MOUNT ""
18 
19 #define DB_TABLE "gallery_files"
20 
21 #define RESULT_ERR(ERR, MESG) \
22 { LOG(VB_GENERAL, LOG_ERR, LOC + (MESG)); \
23  return QStringList("ERROR") << (ERR); }
24 
25 #define RESULT_OK(MESG) \
26 { LOG(VB_FILE, LOG_DEBUG, LOC + (MESG)); \
27  return QStringList("OK"); }
28 
29 #define IMPORTDIR "Import"
30 
31 
33 class Device
34 {
35 public:
36  Device(QString name, QString mount,
37  MythMediaDevice *media = nullptr, QTemporaryDir *import = nullptr)
38  : m_present(true), m_name(std::move(name)), m_mount(std::move(mount)),
39  m_media(media), m_dir(import)
40  {
41  // Path relative to TEMP storage group
42  m_thumbs = QString("%1/%2").arg(THUMBNAIL_SUBDIR, m_name);
43  }
44 
45 
48  {
49  Close();
50 
51  // Remove imported images
52  delete m_dir;
53 
54  // Clean up non-SG thumbnails
56  RemoveThumbs();
57  }
58 
59 
61  void Close(bool eject = false)
62  {
63  // Imports remain present; others do not
64  m_present = isImport();
65 
66  // Release device
67  if (m_media)
68  {
69  if (eject)
70  {
71  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Ejecting '%1' at '%2'")
72  .arg(m_name, m_mount));
74  }
75  else
76  LOG(VB_MEDIA, LOG_DEBUG, LOC + QString("Unlocked '%1'").arg(m_name));
77 
79  m_media = nullptr;
80  }
81  }
82 
83 
88  static void RemoveDirContents(const QString& path)
89  {
90  QDir(path).removeRecursively();
91  }
92 
93 
95  void RemoveThumbs(void)
96  {
97  // Remove thumbnails
98  QString dir = QString("%1/" TEMP_SUBDIR "/%2").arg(GetConfDir(), m_thumbs);
99  LOG(VB_FILE, LOG_INFO, LOC + QString("Removing thumbnails in %1").arg(dir));
100  RemoveDirContents(dir);
101  QDir::root().rmpath(dir);
102  }
103 
104 
105  bool isImport() const { return m_dir; }
106  bool isPresent() const { return m_present; }
107  void setPresent(MythMediaDevice *media) { m_present = true; m_media = media; }
108 
110  bool m_present;
111  QString m_name;
112  QString m_mount;
113  QString m_thumbs;
115  QTemporaryDir *m_dir;
116 };
117 
118 
119 static Device kNullDevice = Device("Unknown Device", "<Invalid Path>");
120 
121 
123 {
124  qDeleteAll(m_devices);
125 }
126 
127 
129 QString DeviceManager::DeviceMount(int devId) const
130 {
131  return m_devices.value(devId, &kNullDevice)->m_mount;
132 }
133 
134 
136 QString DeviceManager::DeviceName(int devId) const
137 {
138  return m_devices.value(devId, &kNullDevice)->m_name;
139 }
140 
141 
142 QString DeviceManager::ThumbDir(int fs) const
143 {
144  return m_devices.value(fs, &kNullDevice)->m_thumbs;
145 }
146 
147 
157 int DeviceManager::OpenDevice(const QString &name, const QString &mount,
158  MythMediaDevice *media, QTemporaryDir *dir)
159 {
160  // Handle devices reappearing at same mountpoint.
161  // If a USB is unplugged whilst in use (without unmounting) we get no event
162  // but we do when it's re-inserted
163  QString state("Known");
164  int id = LocateMount(mount);
165 
166  if (id == DEVICE_INVALID)
167  {
168  state = "New";
169  id = m_devices.isEmpty() ? 0 : (m_devices.constEnd() - 1).key() + 1;
170  m_devices.insert(id, new Device(name, mount, media, dir));
171  }
172  else if (m_devices.value(id))
173  m_devices.value(id)->setPresent(media);
174 
175  LOG(VB_GENERAL, LOG_INFO, LOC +
176  QString("%1 device %2 mounted at '%3' [Id %4]")
177  .arg(state, name, mount).arg(id));
178 
179  return id;
180 }
181 
182 
189 QStringList DeviceManager::CloseDevices(int devId, const QString &action)
190 {
191  QStringList clear;
192 
193  if (action == "DEVICE CLOSE ALL")
194  {
195  // Close all devices but retain their thumbnails
196  foreach (Device *dev, m_devices)
197  if (dev)
198  dev->Close();
199  }
200  else if (action == "DEVICE CLEAR ALL")
201  {
202  // Remove all thumbnails but retain devices
203  foreach (Device *dev, m_devices)
204  if (dev)
205  {
206  clear << dev->m_mount;
207  dev->RemoveThumbs();
208  }
209  }
210  else
211  {
212  // Remove single device & its thumbnails, optionally ejecting it
213  Device *dev = m_devices.take(devId);
214  if (dev)
215  {
216  if (action == "DEVICE EJECT")
217  dev->Close(true);
218  clear << dev->m_mount;
219  delete dev;
220  }
221  }
222  return clear;
223 }
224 
225 
231 int DeviceManager::LocateMount(const QString &mount) const
232 {
233  DeviceMap::const_iterator it = m_devices.constBegin();
234  while (it != m_devices.constEnd())
235  {
236  if (it.value()->m_mount == mount)
237  return it.key();
238  ++it;
239  }
240  return DEVICE_INVALID;
241 }
242 
243 
246 {
248  foreach (int id, m_devices.keys())
249  {
250  Device *dev = m_devices.value(id);
251  if (dev)
252  paths.insert(id, dev->m_mount);
253  }
254  return paths;
255 }
256 
257 
260 {
261  QList<int> absent;
262  foreach (int id, m_devices.keys())
263  {
264  Device *dev = m_devices.value(id);
265  if (dev && !dev->isPresent())
266  absent << id;
267  }
268  return absent;
269 }
270 
271 
276  m_imageFileExt(SupportedImages()),
277  m_videoFileExt(SupportedVideos())
278 {
279  // Generate glob list from supported extensions
280  QStringList glob;
281  foreach (const QString &ext, m_imageFileExt + m_videoFileExt)
282  glob << "*." + ext;
283 
284  // Apply filters to only detect image files
285  m_dirFilter.setNameFilters(glob);
286  m_dirFilter.setFilter(QDir::AllDirs | QDir::Files | QDir::Readable |
287  QDir::NoDotAndDotDot | QDir::NoSymLinks);
288 
289  // Sync files before dirs to improve thumb generation response
290  // Order by time (oldest first) - this determines the order thumbs appear
291  m_dirFilter.setSorting(QDir::DirsLast | QDir::Time | QDir::Reversed);
292 }
293 
299 {
300  // Determine supported picture formats from Qt
301  QStringList formats;
302  foreach (const QByteArray &ext, QImageReader::supportedImageFormats())
303  formats << QString(ext);
304  return formats;
305 }
306 
307 
313 {
314  // Determine supported video formats from MythVideo
315  QStringList formats;
318  for (const auto & fa : faList)
319  {
320  if (!fa.use_default && fa.playcommand == "Internal")
321  formats << QString(fa.extension);
322  }
323  return formats;
324 }
325 
326 
335 ImageItem *ImageAdapterLocal::CreateItem(const QFileInfo &fi, int parentId,
336  int devId, const QString & /*base*/) const
337 {
338  auto *im = new ImageItem();
339 
340  im->m_parentId = parentId;
341  im->m_device = devId;
342  im->m_filePath = fi.absoluteFilePath();
343 
344  if (parentId == GALLERY_DB_ID)
345  {
346  // Import devices show time of import, other devices show 'last scan time'
347 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
348  im->m_date = im->m_filePath.contains(IMPORTDIR)
349  ? fi.lastModified().toTime_t()
350  : QDateTime::currentMSecsSinceEpoch() / 1000;
351 #else
352  im->m_date = im->m_filePath.contains(IMPORTDIR)
353  ? fi.lastModified().toSecsSinceEpoch()
354  : QDateTime::currentSecsSinceEpoch();
355 #endif
356  im->m_modTime = im->m_date;
357  im->m_type = kDevice;
358  return im;
359  }
360 
361 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
362  im->m_modTime = fi.lastModified().toTime_t();
363 #else
364  im->m_modTime = fi.lastModified().toSecsSinceEpoch();
365 #endif
366 
367  if (fi.isDir())
368  {
369  im->m_type = kDirectory;
370  return im;
371  }
372 
373  im->m_extension = fi.suffix().toLower();
374  im->m_type = GetImageType(im->m_extension);
375 
376  if (im->m_type == kUnknown)
377  {
378  delete im;
379  return nullptr;
380  }
381 
382  im->m_thumbPath = GetAbsThumbPath(ThumbDir(im->m_device), ThumbPath(*im));
383  im->m_size = fi.size();
384 
385  return im;
386 }
387 
388 
394 void ImageAdapterLocal::Notify(const QString &mesg,
395  const QStringList &extra)
396 {
397  QString host(gCoreContext->GetHostName());
398  gCoreContext->SendEvent(MythEvent(QString("%1 %2").arg(mesg, host), extra));
399 }
400 
401 
410 ImageItem *ImageAdapterSg::CreateItem(const QFileInfo &fi, int parentId,
411  int /*devId*/, const QString &base) const
412 {
413  auto *im = new ImageItem();
414 
415  im->m_device = 0;
416  im->m_parentId = parentId;
417 
418  if (parentId == GALLERY_DB_ID)
419  {
420  // All SG dirs map to a single Db dir
421  im->m_filePath = "";
422  im->m_type = kDevice;
423  im->m_date = QDateTime::currentMSecsSinceEpoch() / 1000;
424  im->m_modTime = im->m_date;
425  return im;
426  }
427 
428  // Strip SG path & leading / to leave a relative path
429  im->m_filePath = fi.absoluteFilePath().mid(base.size() + 1);
430 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
431  im->m_modTime = fi.lastModified().toTime_t();
432 #else
433  im->m_modTime = fi.lastModified().toSecsSinceEpoch();
434 #endif
435 
436  if (fi.isDir())
437  {
438  im->m_type = kDirectory;
439  return im;
440  }
441 
442  im->m_extension = fi.suffix().toLower();
443  im->m_type = GetImageType(im->m_extension);
444 
445  if (im->m_type == kUnknown)
446  {
447  delete im;
448  return nullptr;
449  }
450 
451  im->m_thumbPath = GetAbsThumbPath(ThumbDir(im->m_device), ThumbPath(*im));
452  im->m_size = fi.size();
453 
454  return im;
455 }
456 
457 
463 void ImageAdapterSg::Notify(const QString &mesg,
464  const QStringList &extra)
465 {
466  gCoreContext->SendEvent(MythEvent(mesg, extra));
467 }
468 
469 
475 {
476  StringMap map;
477  int i = 0;
478  foreach (const QString &path, m_sg.GetDirList())
479  map.insert(i++, path);
480  return map;
481 }
482 
483 
491 {
492  if (im->IsDevice())
493  return m_sg.FindNextDirMostFree();
494  return im->m_filePath.startsWith("/") ? im->m_filePath
495  : m_sg.FindFile(im->m_filePath);
496 }
497 
498 
499 // Database fields used by several image queries
500 #define DB_COLUMNS \
501 "file_id, filename, name, dir_id, type, modtime, size, " \
502 "extension, date, hidden, orientation, angle, path, zoom"
503 // Id, filepath, basename, parentId, type, modtime, size,
504 // extension, image date, hidden, orientation, cover id, comment, device id
505 
506 
512 template <class FS>
514 {
515  auto *im = new ImageItem(FS::ImageId(query.value(0).toInt()));
516 
517  // Ordered as per DB_COLUMNS
518  im->m_filePath = query.value(1).toString();
519  im->m_baseName = query.value(2).toString();
520  im->m_parentId = FS::ImageId(query.value(3).toInt());
521  im->m_type = query.value(4).toInt();
522  im->m_modTime = query.value(5).toInt();
523  im->m_size = query.value(6).toInt();
524  im->m_extension = query.value(7).toString();
525  im->m_date = query.value(8).toUInt();
526  im->m_isHidden = query.value(9).toBool();
527  im->m_orientation = query.value(10).toInt();
528  im->m_userThumbnail = FS::ImageId(query.value(11).toInt());
529  im->m_comment = query.value(12).toString();
530  im->m_device = query.value(13).toInt();
531  im->m_url = FS::MakeFileUrl(im->m_filePath);
532 
533  if (im->IsFile())
534  {
535  // Only pics/vids have thumbs
536  QString thumbPath(FS::ThumbPath(*im));
537  QString devPath(FS::ThumbDir(im->m_device));
538  QString url(FS::MakeThumbUrl(devPath, thumbPath));
539 
540  im->m_thumbPath = FS::GetAbsThumbPath(devPath, thumbPath);
541  im->m_thumbNails.append(qMakePair(im->m_id, url));
542  }
543  return im;
544 }
545 
546 
555 template <class FS>
556 int ImageDb<FS>::GetImages(const QString &ids, ImageList &files, ImageList &dirs,
557  const QString &refine) const
558 {
559  if (ids.isEmpty())
560  return 0;
561 
562  QString select = QString("file_id IN (%1) %2").arg(FS::DbIds(ids), refine);
563  return ReadImages(dirs, files, select);
564 }
565 
566 
575 template <class FS>
576 int ImageDb<FS>::GetChildren(QString ids, ImageList &files, ImageList &dirs,
577  const QString &refine) const
578 {
579  QString select = QString("dir_id IN (%1) %2").arg(FS::DbIds(ids), refine);
580  return ReadImages(dirs, files, select);
581 }
582 
583 
593 template <class FS>
595  ImageList &files, ImageList &dirs,
596  const QString &refine) const
597 {
598  MSqlQuery query(MSqlQuery::InitCon());
599  query.prepare(QString("SELECT " DB_COLUMNS " FROM %1 "
600  "WHERE (dir_id = :ID1 OR file_id = :ID2) "
601  "%2;").arg(m_table, refine));
602 
603  // Qt < 5.4 won't bind multiple occurrences
604  int dbId = FS::DbId(id);
605  query.bindValue(":ID1", dbId);
606  query.bindValue(":ID2", dbId);
607 
608  if (!query.exec())
609  {
610  MythDB::DBError(DBLOC, query);
611  return -1;
612  }
613  while (query.next())
614  {
615  ImagePtr im(CreateImage(query));
616 
617  if (im->IsFile())
618  files.append(im);
619  else if (im->m_id == id)
620  parent = im;
621  else
622  dirs.append(im);
623  }
624  return query.size();
625 }
626 
627 
635 template <class FS>
636 bool ImageDb<FS>::GetDescendants(const QString &ids,
637  ImageList &files, ImageList &dirs) const
638 {
639  if (ids.isEmpty())
640  return false;
641 
642  if (ReadImages(dirs, files, QString("file_id IN (%1)").arg(FS::DbIds(ids))) < 0)
643  return false;
644 
645  MSqlQuery query(MSqlQuery::InitCon());
646  QString sql =
647  QString("SELECT " DB_COLUMNS
648  ", LENGTH(filename) - LENGTH(REPLACE(filename, '/', ''))"
649  " AS depth "
650  "FROM %1 WHERE filename LIKE :PREFIX "
651  "ORDER BY depth;").arg(m_table);
652 
653  foreach (const ImagePtr &im1, dirs)
654  {
655  query.prepare(sql);
656  query.bindValue(":PREFIX", im1->m_filePath + "/%");
657 
658  if (!query.exec())
659  {
660  MythDB::DBError(DBLOC, query);
661  return false;
662  }
663 
664  while (query.next())
665  {
666  ImagePtr im2(CreateImage(query));
667  if (im2->IsDirectory())
668  dirs.append(im2);
669  else
670  files.append(im2);
671  }
672  }
673  return true;
674 }
675 
676 
684 template <class FS>
685 bool ImageDb<FS>::GetImageTree(int id, ImageList &files, const QString &refine) const
686 {
687  // Load starting children
688  ImageList dirs;
689  if (GetChildren(QString::number(id), files, dirs, refine) < 0)
690  return false;
691 
692  foreach (const ImagePtr &im, dirs)
693  if (!GetImageTree(im->m_id, files, refine))
694  return false;
695  return true;
696 }
697 
698 
704 template <class FS>
706 {
707  MSqlQuery query(MSqlQuery::InitCon());
708  query.prepare(QString("SELECT " DB_COLUMNS " FROM %1").arg(m_table));
709 
710  if (!query.exec())
711  {
712  MythDB::DBError(DBLOC, query);
713  return false;
714  }
715 
716  while (query.next())
717  {
718  ImagePtr im(CreateImage(query));
719  if (im->IsDirectory())
720  dirs.insert(im->m_filePath, im);
721  else
722  files.insert(im->m_filePath, im);
723  }
724  return true;
725 }
726 
727 
735 template <class FS>
736 void ImageDb<FS>::ClearDb(int devId, const QString &action)
737 {
738  if (action == "DEVICE CLOSE ALL")
739  // Retain Db images when closing UI
740  return;
741 
742  MSqlQuery query(MSqlQuery::InitCon());
743 
744  if (action == "DEVICE CLEAR ALL")
745  {
746  // Clear images from all devices. Reset auto-increment
747  query.prepare(QString("TRUNCATE TABLE %1;").arg(m_table));
748 
749  if (!query.exec())
750  MythDB::DBError(DBLOC, query);
751  }
752  else // Actions DEVICE REMOVE & DEVICE EJECT
753  {
754  // Delete all images of the device
755  query.prepare(QString("DELETE IGNORE FROM %1 WHERE zoom = :FS;").arg(m_table));
756  query.bindValue(":FS", devId);
757 
758  if (!query.exec())
759  MythDB::DBError(DBLOC, query);
760  }
761 }
762 
763 
771 template <class FS>
772 int ImageDb<FS>::InsertDbImage(ImageItemK &im, bool checkForDuplicate) const
773 {
774  MSqlQuery query(MSqlQuery::InitCon());
775 
776  if (checkForDuplicate)
777  {
778  query.prepare(QString("SELECT file_id FROM %1 WHERE filename = :NAME;")
779  .arg(m_table));
780 
781  query.bindValue(":NAME", im.m_filePath);
782 
783  if (!query.exec())
784  {
785  MythDB::DBError(DBLOC, query);
786  return -1;
787  }
788 
789  if (query.size() > 0)
790  {
791  LOG(VB_FILE, LOG_DEBUG, QString("Image: %1 already exists in Db")
792  .arg(im.m_filePath));
793  return query.value(0).toInt();
794  }
795  }
796 
797  query.prepare(QString("INSERT INTO %1 (" DB_COLUMNS ") VALUES (0, "
798  ":FILEPATH, :NAME, :PARENT, :TYPE, :MODTIME, "
799  ":SIZE, :EXTENSION, :DATE, :HIDDEN, :ORIENT, "
800  ":COVER, :COMMENT, :FS);").arg(m_table));
801 
802  query.bindValue(":FILEPATH", im.m_filePath);
803  query.bindValue(":NAME", FS::BaseNameOf(im.m_filePath));
804  query.bindValue(":FS", im.m_device);
805  query.bindValue(":PARENT", FS::DbId(im.m_parentId));
806  query.bindValue(":TYPE", im.m_type);
807  query.bindValue(":MODTIME", im.m_modTime);
808  query.bindValue(":SIZE", im.m_size);
809  query.bindValue(":EXTENSION", im.m_extension);
810  query.bindValue(":DATE", im.m_date);
811  query.bindValue(":ORIENT", im.m_orientation);
812  query.bindValue(":COMMENT", im.m_comment.isNull() ? "" : im.m_comment);
813  query.bindValue(":HIDDEN", im.m_isHidden);
814  query.bindValue(":COVER", FS::DbId(im.m_userThumbnail));
815 
816  if (query.exec())
817  return FS::ImageId(query.lastInsertId().toInt());
818 
819  MythDB::DBError(DBLOC, query);
820  return -1;
821 }
822 
823 
829 template <class FS>
831 {
832  MSqlQuery query(MSqlQuery::InitCon());
833  query.prepare(QString
834  ("UPDATE %1 SET "
835  "filename = :FILEPATH, name = :NAME, "
836  "dir_id = :PARENT, type = :TYPE, "
837  "modtime = :MODTIME, size = :SIZE, "
838  "extension = :EXTENSION, date = :DATE, zoom = :FS, "
839  "hidden = :HIDDEN, orientation = :ORIENT, "
840  "angle = :COVER, path = :COMMENT "
841  "WHERE file_id = :ID;").arg(m_table));
842 
843  query.bindValue(":ID", FS::DbId(im.m_id));
844  query.bindValue(":FILEPATH", im.m_filePath);
845  query.bindValue(":NAME", FS::BaseNameOf(im.m_filePath));
846  query.bindValue(":PARENT", FS::DbId(im.m_parentId));
847  query.bindValue(":TYPE", im.m_type);
848  query.bindValue(":MODTIME", im.m_modTime);
849  query.bindValue(":SIZE", im.m_size);
850  query.bindValue(":EXTENSION", im.m_extension);
851  query.bindValue(":DATE", im.m_date);
852  query.bindValue(":FS", im.m_device);
853  query.bindValue(":HIDDEN", im.m_isHidden);
854  query.bindValue(":ORIENT", im.m_orientation);
855  query.bindValue(":COVER", FS::DbId(im.m_userThumbnail));
856  query.bindValue(":COMMENT", im.m_comment.isNull() ? "" : im.m_comment);
857 
858  if (query.exec())
859  return true;
860 
861  MythDB::DBError(DBLOC, query);
862  return false;
863 }
864 
865 
872 template <class FS>
873 QStringList ImageDb<FS>::RemoveFromDB(const ImageList &imList) const
874 {
875  QStringList ids;
876  if (!imList.isEmpty())
877  {
878  foreach (const ImagePtr &im, imList)
879  ids << QString::number(FS::DbId(im->m_id));
880 
881  QString idents = ids.join(",");
882  MSqlQuery query(MSqlQuery::InitCon());
883  query.prepare(QString("DELETE IGNORE FROM %1 WHERE file_id IN (%2);")
884  .arg(m_table, idents));
885 
886  if (!query.exec())
887  {
888  MythDB::DBError(DBLOC, query);
889  return QStringList();
890  }
891  }
892  return ids;
893 }
894 
895 
902 template <class FS>
903 bool ImageDb<FS>::SetHidden(bool hide, QString ids) const
904 {
905  if (ids.isEmpty())
906  return false;
907 
908  MSqlQuery query(MSqlQuery::InitCon());
909 
910  query.prepare(QString("UPDATE %1 SET "
911  "hidden = :HIDDEN "
912  "WHERE file_id IN (%2);").arg(m_table, FS::DbIds(ids)));
913  query.bindValue(":HIDDEN", hide ? 1 : 0);
914 
915  if (query.exec())
916  return true;
917 
918  MythDB::DBError(DBLOC, query);
919  return false;
920 }
921 
922 
928 template <class FS>
929 bool ImageDb<FS>::SetCover(int dir, int id) const
930 {
931  MSqlQuery query(MSqlQuery::InitCon());
932 
933  query.prepare(QString("UPDATE %1 SET "
934  "angle = :COVER "
935  "WHERE file_id = :DIR").arg(m_table));
936  query.bindValue(":COVER", FS::DbId(id));
937  query.bindValue(":DIR", FS::DbId(dir));
938  \
939  if (query.exec())
940  return true;
941 
942  MythDB::DBError(DBLOC, query);
943  return false;
944 }
945 
946 
952 template <class FS>
953 bool ImageDb<FS>::SetOrientation(int id, int orientation) const
954 {
955  MSqlQuery query(MSqlQuery::InitCon());
956 
957  query.prepare(QString("UPDATE %1 SET ").arg(m_table) +
958  "orientation = :ORIENTATION "
959  "WHERE file_id = :ID");
960  query.bindValue(":ORIENTATION", orientation);
961  query.bindValue(":ID", FS::DbId(id));
962  \
963  if (query.exec())
964  return true;
965 
966  MythDB::DBError(DBLOC, query);
967  return false;
968 }
969 
970 
978 template <class FS>
980  const QString &selector) const
981 {
982  MSqlQuery query(MSqlQuery::InitCon());
983  query.prepare(QString("SELECT " DB_COLUMNS " FROM %1 WHERE %2")
984  .arg(m_table, selector));
985  if (!query.exec())
986  {
987  MythDB::DBError(DBLOC, query);
988  return -1;
989  }
990 
991  while (query.next())
992  {
993  ImagePtr im(CreateImage(query));
994 
995  if (im->IsFile())
996  files.append(im);
997  else
998  dirs.append(im);
999  }
1000  return query.size();
1001 }
1002 
1003 
1013 template <class FS>
1014 void ImageDb<FS>::GetDescendantCount(int id, bool all, int &dirs,
1015  int &pics, int &videos, int &sizeKb) const
1016 {
1017  QString whereClause;
1018  if (!all)
1019  {
1020  whereClause = "WHERE filename LIKE "
1021  "( SELECT CONCAT(filename, '/%') "
1022  " FROM %2 WHERE file_id = :ID);";
1023  }
1024 
1025  MSqlQuery query(MSqlQuery::InitCon());
1026  query.prepare(QString("SELECT SUM(type <= :FLDR) AS Fldr, "
1027  " SUM(type = :PIC) AS Pics, "
1028  " SUM(type = :VID) AS Vids, "
1029  " SUM(size / 1024) "
1030  "FROM %2 %1;").arg(whereClause).arg(m_table));
1031 
1032  query.bindValue(":FLDR", kDirectory);
1033  query.bindValue(":PIC", kImageFile);
1034  query.bindValue(":VID", kVideoFile);
1035  if (!all)
1036  query.bindValue(":ID", FS::DbId(id));
1037 
1038  if (!query.exec())
1039  {
1040  MythDB::DBError(DBLOC, query);
1041  }
1042  else if (query.next())
1043  {
1044  dirs += query.value(0).toInt();
1045  pics += query.value(1).toInt();
1046  videos += query.value(2).toInt();
1047  sizeKb += query.value(3).toInt();
1048  }
1049 }
1050 
1051 
1056 {
1057  // Be has a single SG device
1059 }
1060 
1061 
1066  : ImageDb(QString("`%1_%2`").arg(DB_TABLE, gCoreContext->GetHostName()))
1067 {
1068  // Remove any table leftover from a previous FE crash
1069  DropTable();
1070 }
1071 
1072 
1077 {
1078  MSqlQuery query(MSqlQuery::InitCon());
1079  query.prepare(QString("DROP TABLE IF EXISTS %1;").arg(m_table));
1080  if (query.exec())
1081  m_DbExists = false;
1082  else
1083  MythDB::DBError(DBLOC, query);
1084 }
1085 
1086 
1091 {
1092  if (m_DbExists)
1093  return true;
1094 
1095  MSqlQuery query(MSqlQuery::InitCon());
1096 
1097  // Create temporary table
1098  query.prepare(QString("CREATE TABLE %1 LIKE " DB_TABLE ";").arg(m_table));
1099  if (query.exec())
1100  {
1101  // Store it in memory only
1102  query.prepare(QString("ALTER TABLE %1 ENGINE = MEMORY;").arg(m_table));
1103  if (query.exec())
1104  {
1105  m_DbExists = true;
1106  LOG(VB_FILE, LOG_DEBUG, QString("Created Db table %1").arg(m_table));
1107  return true;
1108  }
1109  }
1110  MythDB::DBError(DBLOC, query);
1111 
1112  // Clean up after failure
1113  query.prepare(QString("DROP TABLE IF EXISTS %1;").arg(m_table));
1114  query.exec();
1115  return false;
1116 }
1117 
1118 
1122 class ReadMetaThread : public QRunnable
1123 {
1124 public:
1125  ReadMetaThread(ImagePtrK im, QString path)
1126  : m_im(std::move(im)), m_path(std::move(path)) {}
1127 
1128  void run() override // QRunnable
1129  {
1130  QStringList tags;
1131  QString orientation;
1132  QString size;
1133 
1134  // Read metadata for files only
1135  if (m_im->IsFile())
1136  {
1137  ImageMetaData *metadata = (m_im->m_type == kVideoFile)
1140  tags = metadata->GetAllTags();
1141  orientation = Orientation(m_im->m_orientation).Description();
1142  size = ImageAdapterBase::FormatSize(m_im->m_size / 1024);
1143  delete metadata;
1144  }
1145 
1146  // Add identifier at front
1147  tags.prepend(QString::number(m_im->m_id));
1148 
1149  // Include file info
1150  tags << ImageMetaData::ToString(EXIF_MYTH_HOST, "Host",
1152  tags << ImageMetaData::ToString(EXIF_MYTH_PATH, "Path",
1154  tags << ImageMetaData::ToString(EXIF_MYTH_NAME, "Name",
1156  tags << ImageMetaData::ToString(EXIF_MYTH_SIZE, "Size", size);
1157  tags << ImageMetaData::ToString(EXIF_MYTH_ORIENT, "Orientation",
1158  orientation);
1159 
1160  MythEvent me("IMAGE_METADATA", tags);
1161  gCoreContext->SendEvent(me);
1162  }
1163 
1164 private:
1166  QString m_path;
1167 };
1168 
1169 
1178 template <class DBFS>
1179 QStringList ImageHandler<DBFS>::HandleGetMetadata(const QString &id) const
1180 {
1181  // Find image in DB
1182  ImageList files;
1183  ImageList dirs;
1184  if (DBFS::GetImages(id, files, dirs) != 1)
1185  RESULT_ERR("Image not found", QString("Unknown image %1").arg(id))
1186 
1187  ImagePtr im = files.isEmpty() ? dirs[0] : files[0];
1188 
1189  QString absPath = DBFS::GetAbsFilePath(im);
1190  if (absPath.isEmpty())
1191  RESULT_ERR("Image not found",
1192  QString("File %1 not found").arg(im->m_filePath))
1193 
1194  auto *worker = new ReadMetaThread(im, absPath);
1195 
1196  MThreadPool::globalInstance()->start(worker, "ImageMetaData");
1197 
1198  RESULT_OK(QString("Fetching metadata for %1").arg(id))
1199 }
1200 
1201 
1209 template <class DBFS>
1210 QStringList ImageHandler<DBFS>::HandleRename(const QString &id,
1211  const QString &newBase) const
1212 {
1213  // Sanity check new name
1214  if (newBase.isEmpty() || newBase.contains("/") || newBase.contains("\\"))
1215  RESULT_ERR("Invalid name", QString("Invalid name %1").arg(newBase))
1216 
1217  // Find image in DB
1218  ImageList files;
1219  ImageList dirs;
1220  if (DBFS::GetImages(id, files, dirs) != 1)
1221  RESULT_ERR("Image not found", QString("Image %1 not in Db").arg(id))
1222 
1223  ImagePtr im = files.isEmpty() ? dirs[0] : files[0];
1224 
1225  // Find file
1226  QString oldPath = DBFS::GetAbsFilePath(im);
1227  if (oldPath.isEmpty())
1228  RESULT_ERR("Image not found",
1229  QString("File %1 not found").arg(im->m_filePath))
1230 
1231  // Generate new filename
1232  QFileInfo oldFi = QFileInfo(oldPath);
1233  QString newName = im->IsDirectory()
1234  ? newBase : QString("%1.%2").arg(newBase, oldFi.suffix());
1235 
1236  im->m_filePath = DBFS::ConstructPath(DBFS::PathOf(im->m_filePath), newName);
1237 
1238  // Ensure no SG duplicate files are created. (Creating clone dirs is ok)
1239  if (im->IsFile())
1240  {
1241  QString existPath = DBFS::GetAbsFilePath(im);
1242  if (!existPath.isEmpty())
1243  RESULT_ERR("Filename already used",
1244  QString("Renaming %1 to %2 will create a duplicate of %3")
1245  .arg(oldPath, im->m_filePath, existPath))
1246  }
1247 
1248  // Rename file or directory
1249  QString newPath = oldFi.dir().absoluteFilePath(newName);
1250  if (!QFile::rename(oldPath, newPath))
1251  RESULT_ERR("Rename failed",
1252  QString("Rename of %1 -> %2 failed").arg(oldPath, newPath))
1253 
1254  if (im->IsDirectory())
1255  {
1256  // Dir name change affects path of all sub-dirs & files and their thumbs
1257  HandleScanRequest("START");
1258  }
1259  else // file
1260  {
1261  // Update db
1262  DBFS::UpdateDbImage(*im);
1263 
1264  // Image is modified, not deleted
1265  QStringList mesg("");
1266  mesg << QString::number(im->m_id);
1267 
1268  // Rename thumbnail.
1269  m_thumbGen->MoveThumbnail(im);
1270 
1271  // Notify clients of changed image
1272  DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1273  }
1274  RESULT_OK(QString("Renamed %1 -> %2").arg(oldPath, newPath))
1275 }
1276 
1277 
1285 template <class DBFS>
1286 QStringList ImageHandler<DBFS>::HandleDelete(const QString &ids) const
1287 {
1288  // Get subtree of all files
1289  ImageList files;
1290  ImageList dirs;
1291  // Dirs will be in depth-first order, (subdirs after parent)
1292  DBFS::GetDescendants(ids, files, dirs);
1293 
1294  // Remove files from filesystem first
1295  RemoveFiles(files);
1296  // ... then dirs, which should now be empty
1297  RemoveFiles(dirs);
1298 
1299  // Fail if nothing deleted
1300  if (files.isEmpty() && dirs.isEmpty())
1301  RESULT_ERR("Delete failed", QString("Delete of %1 failed").arg(ids))
1302 
1303  // Update Db
1304  DBFS::RemoveFromDB(files + dirs);
1305 
1306  // Clean up thumbnails
1307  QStringList mesg(m_thumbGen->DeleteThumbs(files));
1308 
1309  // Notify clients of deleted ids
1310  DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1311 
1312  return QStringList("OK");
1313 }
1314 
1315 
1328 template <class DBFS>
1329 QStringList ImageHandler<DBFS>::HandleDbCreate(QStringList defs) const
1330 {
1331  if (defs.isEmpty())
1332  RESULT_ERR("Copy Failed", "Empty defs")
1333 
1334  // First item is the field seperator
1335  const QString separator = defs.takeFirst();
1336 
1337  // Convert cover ids to their new equivalent. Map<source id, new id>
1338  // Dirs follow their children so new cover ids will be defined before they
1339  // are used
1340  QHash<QString, int> idMap;
1341 
1342  // Create skeleton Db images using copied settings.
1343  // Scanner will update other attributes
1344  ImageItem im;
1345  foreach (const QString &def, defs)
1346  {
1347  QStringList aDef = def.split(separator);
1348 
1349  // Expects id, type, path, hidden, orientation, cover
1350  if (aDef.size() != 6)
1351  {
1352  // Coding error
1353  LOG(VB_GENERAL, LOG_ERR,
1354  LOC + QString("Bad definition: (%1)").arg(def));
1355  continue;
1356  }
1357 
1358  LOG(VB_FILE, LOG_DEBUG, LOC + QString("Creating %1").arg(aDef.join(",")));
1359 
1360  im.m_type = aDef[1].toInt();
1361  im.m_filePath = aDef[2];
1362  im.m_isHidden = (aDef[3].toInt() != 0);
1363  im.m_orientation = aDef[4].toInt();
1364  im.m_userThumbnail = idMap.value(aDef[5]);
1365 
1366  // Don't insert duplicate filepaths
1367  int newId = DBFS::InsertDbImage(im, true);
1368 
1369  // Record old->new id map in case it's being used as a cover
1370  idMap.insert(aDef[0], newId);
1371  }
1372  HandleScanRequest("START");
1373 
1374  RESULT_OK("Created Db images")
1375 }
1376 
1377 
1387 template <class DBFS>
1388 QStringList ImageHandler<DBFS>::HandleDbMove(const QString &ids,
1389  const QString &srcPath,
1390  QString destPath) const
1391 {
1392  // Sanity check new path
1393  if (destPath.contains(".."))
1394  RESULT_ERR("Invalid path", QString("Invalid path %1").arg(destPath))
1395 
1396  // Get subtrees of renamed files
1397  ImageList images;
1398  ImageList dirs;
1399  ImageList files;
1400  bool ok = DBFS::GetDescendants(ids, files, dirs);
1401  images << dirs << files;
1402 
1403  if (!ok || images.isEmpty())
1404  RESULT_ERR("Image not found", QString("Images %1 not in Db").arg(ids))
1405 
1406  if (!destPath.isEmpty() && !destPath.endsWith(QChar('/')))
1407  destPath.append("/");
1408 
1409  // Update path of images only. Scanner will repair parentId
1410  foreach (const ImagePtr &im, images)
1411  {
1412  QString old = im->m_filePath;
1413 
1414  if (srcPath.isEmpty())
1415  {
1416  // Image in SG root
1417  im->m_filePath.prepend(destPath);
1418  }
1419  else if (im->m_filePath.startsWith(srcPath))
1420  {
1421  // All other images
1422  im->m_filePath.replace(srcPath, destPath);
1423  }
1424  else
1425  {
1426  // Coding error
1427  LOG(VB_GENERAL, LOG_ERR,
1428  LOC + QString("Bad image: (%1 -> %2)").arg(srcPath, destPath));
1429  continue;
1430  }
1431 
1432  LOG(VB_FILE, LOG_DEBUG,
1433  LOC + QString("Db Renaming %1 -> %2").arg(old, im->m_filePath));
1434 
1435  DBFS::UpdateDbImage(*im);
1436 
1437  // Rename thumbnail
1438  if (im->IsFile())
1439  m_thumbGen->MoveThumbnail(im);
1440  }
1441  HandleScanRequest("START");
1442 
1443  RESULT_OK(QString("Moved %1 from %2 -> %3").arg(ids).arg(srcPath, destPath))
1444 }
1445 
1446 
1454 template <class DBFS>
1455 QStringList ImageHandler<DBFS>::HandleHide(bool hide, const QString &ids) const
1456 {
1457  if (!DBFS::SetHidden(hide, ids))
1458  RESULT_ERR("Hide failed", QString("Db hide failed for %1").arg(ids))
1459 
1460  // Send changed ids only (none deleted)
1461  QStringList mesg = QStringList("") << ids;
1462  DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1463 
1464  RESULT_OK(QString("Images %1 now %2hidden").arg(ids, hide ? "" : "un"))
1465 }
1466 
1467 
1476 template <class DBFS>
1477 QStringList ImageHandler<DBFS>::HandleTransform(int transform,
1478  const QString &ids) const
1479 {
1480  if (transform < kResetToExif || transform > kFlipVertical)
1481  RESULT_ERR("Transform failed", QString("Bad transform %1").arg(transform))
1482 
1483  ImageList files;
1484  ImageList dirs;
1485  if (DBFS::GetImages(ids, files, dirs) < 1 || files.isEmpty())
1486  RESULT_ERR("Image not found", QString("Images %1 not in Db").arg(ids))
1487 
1488  // Update db
1489  foreach (ImagePtr im, files)
1490  {
1491  int old = im->m_orientation;
1492  im->m_orientation = Orientation(im->m_orientation).Transform(transform);
1493 
1494  // Update Db
1495  if (DBFS::SetOrientation(im->m_id, im->m_orientation))
1496  {
1497  LOG(VB_FILE, LOG_DEBUG, LOC + QString("Transformed %1 from %2 to %3")
1498  .arg(im->m_filePath).arg(old).arg(im->m_orientation));
1499  }
1500  }
1501 
1502  // Images are changed, not deleted
1503  QStringList mesg("");
1504 
1505  // Clean up thumbnails
1506  mesg << m_thumbGen->DeleteThumbs(files);
1507 
1508  // Notify clients of changed images
1509  DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1510 
1511  return QStringList("OK");
1512 }
1513 
1514 
1523 template <class DBFS>
1524 QStringList ImageHandler<DBFS>::HandleDirs(const QString &destId,
1525  bool rescan,
1526  const QStringList &relPaths) const
1527 {
1528  // Find image in DB
1529  ImageList files;
1530  ImageList dirs;
1531  if (DBFS::GetImages(destId, files, dirs) != 1 || dirs.isEmpty())
1532  RESULT_ERR("Destination not found",
1533  QString("Image %1 not in Db").arg(destId))
1534 
1535  // Find dir. SG device (Photographs) uses most-free filesystem
1536  QString destPath = DBFS::GetAbsFilePath(dirs[0]);
1537  if (destPath.isEmpty())
1538  RESULT_ERR("Destination not found",
1539  QString("Dest dir %1 not found").arg(dirs[0]->m_filePath))
1540 
1541  QDir destDir(destPath);
1542  bool succeeded = false;
1543  foreach (const QString &relPath, relPaths)
1544  {
1545  // Validate dir name
1546  if (relPath.isEmpty() || relPath.contains("..") || relPath.startsWith(QChar('/')))
1547  continue;
1548 
1549  QString newPath = DBFS::ConstructPath(destDir.absolutePath(), relPath);
1550  if (!destDir.mkpath(relPath))
1551  {
1552  LOG(VB_GENERAL, LOG_ERR,
1553  LOC + QString("Failed to create dir %1").arg(newPath));
1554  continue;
1555  }
1556  LOG(VB_FILE, LOG_DEBUG, LOC + QString("Dir %1 created").arg(newPath));
1557  succeeded = true;
1558  }
1559 
1560  if (!succeeded)
1561  // Failures should only occur due to user input
1562  RESULT_ERR("Invalid Name", QString("Invalid name %1")
1563  .arg(relPaths.join(",")))
1564 
1565  if (rescan)
1566  // Rescan to detect new dir
1567  HandleScanRequest("START");
1568 
1569  return QStringList("OK");
1570 }
1571 
1572 
1579 template <class DBFS>
1580 QStringList ImageHandler<DBFS>::HandleCover(int dir, int cover) const
1581 {
1582  if (!DBFS::SetCover(dir, cover))
1583  RESULT_ERR("Set Cover failed",
1584  QString("Failed to set %1 to cover %2").arg(dir).arg(cover))
1585 
1586  // Image has changed, nothing deleted
1587  QStringList mesg = QStringList("") << QString::number(dir);
1588  DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1589 
1590  RESULT_OK(QString("Cover of %1 is now %2").arg(dir).arg(cover));
1591 }
1592 
1593 
1602 template <class DBFS>
1603 QStringList ImageHandler<DBFS>::HandleIgnore(const QString &exclusions) const
1604 {
1605  // Save new setting. FE will have already saved it but not cleared the cache
1606  gCoreContext->SaveSettingOnHost("GalleryIgnoreFilter", exclusions, nullptr);
1607 
1608  // Rescan
1609  HandleScanRequest("START");
1610 
1611  RESULT_OK(QString("Using exclusions '%1'").arg(exclusions))
1612 }
1613 
1614 
1622 template <class DBFS>
1623 QStringList ImageHandler<DBFS>::HandleScanRequest(const QString &command,
1624  int devId) const
1625 {
1626  if (!m_scanner)
1627  RESULT_ERR("Missing Scanner", "Missing Scanner");
1628 
1629  if (command == "START")
1630  {
1631  // Must be dormant to start a scan
1632  if (m_scanner->IsScanning())
1633  RESULT_ERR("", "Scanner is busy");
1634 
1635  m_scanner->ChangeState(true);
1636  RESULT_OK("Scan requested");
1637  }
1638  else if (command == "STOP")
1639  {
1640  // Must be scanning to interrupt
1641  if (!m_scanner->IsScanning())
1642  RESULT_ERR("Scanner not running", "Scanner not running");
1643 
1644  m_scanner->ChangeState(false);
1645  RESULT_OK("Terminate scan requested");
1646  }
1647  else if (command == "QUERY")
1648  {
1649  return QStringList("OK") << m_scanner->GetProgress();
1650  }
1651  else if (command.startsWith(QString("DEVICE")))
1652  {
1653  m_scanner->EnqueueClear(devId, command);
1654  RESULT_OK(QString("Clearing device %1 %2").arg(command).arg(devId))
1655  }
1656  RESULT_ERR("Unknown command", QString("Unknown command %1").arg(command));
1657 }
1658 
1659 
1667 template <class DBFS>
1669 (const QStringList &message) const
1670 {
1671  if (message.size() != 2)
1672  RESULT_ERR("Unknown Command",
1673  QString("Bad request: %1").arg(message.join("|")))
1674 
1675  int priority = message.at(0).toInt()
1677 
1678  // get specific image details from db
1679  ImageList files;
1680  ImageList dirs;
1681  DBFS::GetImages(message.at(1), files, dirs);
1682 
1683  foreach (const ImagePtrK &im, files)
1684  // notify clients when done; highest priority
1685  m_thumbGen->CreateThumbnail(im, priority, true);
1686 
1687  return QStringList("OK");
1688 }
1689 
1690 
1700 template <class DBFS>
1702 {
1703  QMutableListIterator<ImagePtr> it(images);
1704  it.toBack();
1705  while (it.hasPrevious())
1706  {
1707  ImagePtrK im = it.previous();
1708 
1709  // Remove file or directory
1710  QString absFilename = DBFS::GetAbsFilePath(im);
1711 
1712  bool success = !absFilename.isEmpty()
1713  && (im->IsFile() ? QFile::remove(absFilename)
1714  : QDir::root().rmdir(absFilename));
1715  if (success)
1716  LOG(VB_FILE, LOG_DEBUG, LOC + QString("Deleted %1").arg(absFilename));
1717  else
1718  {
1719  LOG(VB_GENERAL, LOG_ERR, LOC +
1720  QString("Can't delete %1").arg(absFilename));
1721  // Remove from list
1722  it.remove();
1723  }
1724  }
1725 }
1726 
1727 
1734 {
1735  switch (type)
1736  {
1737  case kPicOnly: return QString("AND type != %1").arg(kVideoFile);
1738  case kVideoOnly: return QString("AND type != %1").arg(kImageFile);
1739  case kPicAndVideo: return "";
1740  }
1741  return "";
1742 }
1743 
1744 
1751 {
1752  m_refineClause = QString("%2 %3 "
1753  "ORDER BY "
1754  "CASE WHEN type <= %1 THEN %4, "
1755  "CASE WHEN type > %1 THEN %5 ")
1756  .arg(kDirectory)
1757  .arg(m_showHidden ? "" : "AND hidden = 0",
1761 }
1762 
1763 
1770 {
1771  // prepare the sorting statement
1772  switch (order)
1773  {
1774  default:
1775  case kSortByNameAsc: return "name END ASC";
1776  case kSortByNameDesc: return "name END DESC";
1777  case kSortByModTimeAsc: return "modtime END ASC";
1778  case kSortByModTimeDesc: return "modtime END DESC";
1779  case kSortByExtAsc: return "extension END ASC, name ASC";
1780  case kSortByExtDesc: return "extension END DESC, name DESC";
1781  case kSortBySizeAsc: return "size END ASC, name ASC";
1782  case kSortBySizeDesc: return "size END DESC, name DESC";
1783  case kSortByDateAsc: return "IF(date=0, modtime, date) END ASC";
1784  case kSortByDateDesc: return "IF(date=0, modtime, date) END DESC";
1785  }
1786 }
1787 
1788 
1798  ImageList &files, ImageList &dirs) const
1799 {
1800  // Only Root node will invoke both Db queries but result set will be small
1801  // For Root the SG is always ordered before local devices
1802  // Root node has no Db entry so the 2 queries will not overwrite the parent.
1803  int count = 0;
1804  if (!ImageItem::IsLocalId(id))
1805  count = m_remote->GetDirectory(id, parent, files, dirs, m_refineClause);
1807  count += ImageHandler::GetDirectory(id, parent, files, dirs, m_refineClause);
1808 
1809  if (id == GALLERY_DB_ID)
1810  {
1811  // Add a Root node
1812  parent = ImagePtr(new ImageItem(GALLERY_DB_ID));
1813  parent->m_parentId = GALLERY_DB_ID;
1814  parent->m_type = kDevice;
1815 
1816  ++count;
1817  }
1818  return count;
1819 }
1820 
1821 
1830  ImageList &files, ImageList &dirs) const
1831 {
1832  // Ids are either all local or all remote. GALLERY_DB_ID not valid
1833  StringPair lists = ImageItem::PartitionIds(ids);
1834 
1835  if (!lists.second.isEmpty())
1836  return m_remote->GetImages(lists.second, files, dirs, m_refineClause);
1837  if (m_DbExists && !lists.first.isEmpty())
1838  return ImageHandler::GetImages(lists.first, files, dirs, m_refineClause);
1839  return 0;
1840 }
1841 
1842 
1850 int ImageDbReader::GetChildren(int id, ImageList &files, ImageList &dirs) const
1851 {
1852  int count = 0;
1853  if (!ImageItem::IsLocalId(id))
1854  count = m_remote->GetChildren(QString::number(id), files, dirs,
1855  m_refineClause);
1857  count += ImageHandler::GetChildren(QString::number(id), files, dirs,
1858  m_refineClause);
1859  return count;
1860 }
1861 
1862 
1870  ImageList &files, ImageList &dirs) const
1871 {
1872  // Ids are either all local or all remote
1873  StringPair lists = ImageItem::PartitionIds(ids);
1874 
1875  if (!lists.second.isEmpty())
1876  m_remote->GetDescendants(lists.second, files, dirs);
1877  if (m_DbExists && !lists.first.isEmpty())
1878  ImageHandler::GetDescendants(lists.first, files, dirs);
1879 }
1880 
1881 
1888 void ImageDbReader::GetImageTree(int id, ImageList &files) const
1889 {
1890  if (!ImageItem::IsLocalId(id))
1891  m_remote->GetImageTree(id, files, m_refineClause);
1893  ImageHandler::GetImageTree(id, files, m_refineClause);
1894 }
1895 
1896 
1905 void ImageDbReader::GetDescendantCount(int id, int &dirs, int &pics,
1906  int &videos, int &sizeKb) const
1907 {
1908  if (id == GALLERY_DB_ID)
1909  {
1910  // Sum both unfiltered tables
1911  m_remote->GetDescendantCount(id, true, dirs, pics, videos, sizeKb);
1912  if (m_DbExists)
1913  ImageHandler::GetDescendantCount(id, true, dirs, pics, videos, sizeKb);
1914  }
1915  else if (!ImageItem::IsLocalId(id))
1916  {
1917  // Don't filter on SG path (it's blank)
1919  dirs, pics, videos, sizeKb);
1920  }
1921  else if (m_DbExists)
1922  {
1923  // Always filter on device/dir
1924  ImageHandler::GetDescendantCount(id, false, dirs, pics, videos, sizeKb);
1925  }
1926 }
1927 
1928 
1933 
1934 
1940 {
1941  if (!s_instance)
1942  s_instance = new ImageManagerBe();
1943  return s_instance;
1944 }
1945 
1946 
1952 {
1953  if (!s_instance)
1954  {
1955  // Use saved settings
1957  (gCoreContext->GetNumSetting("GalleryImageOrder"),
1958  gCoreContext->GetNumSetting("GalleryDirOrder"),
1959  gCoreContext->GetBoolSetting("GalleryShowHidden"),
1960  gCoreContext->GetNumSetting("GalleryShowType"),
1961  gCoreContext->GetSetting("GalleryDateFormat"));
1962  }
1963  return *s_instance;
1964 }
1965 
1966 
1975 void ImageManagerFe::CreateThumbnails(const ImageIdList &ids, bool forFolder)
1976 {
1977  // Split images into <locals, remotes>
1978  StringPair lists = ImageItem::PartitionIds(ids);
1979 
1980  if (!lists.second.isEmpty())
1981  {
1982  LOG(VB_FILE, LOG_DEBUG, LOC +
1983  QString("Sending CREATE_THUMBNAILS %1 (forFolder %2)")
1984  .arg(lists.second).arg(forFolder));
1985 
1986  QStringList message;
1987  message << QString::number(forFolder) << lists.second;
1988  gCoreContext->SendEvent(MythEvent("CREATE_THUMBNAILS", message));
1989  }
1990 
1991  if (!lists.first.isEmpty())
1992  {
1993  LOG(VB_FILE, LOG_DEBUG, LOC +
1994  QString("Creating local thumbnails %1 (forFolder %2)")
1995  .arg(lists.first).arg(forFolder));
1996 
1997  QStringList message;
1998  message << QString::number(forFolder) << lists.first;
1999  HandleCreateThumbnails(message);
2000  }
2001 }
2002 
2003 
2010 QString ImageManagerFe::ScanImagesAction(bool start, bool local)
2011 {
2012  QStringList command;
2013  command << (start ? "START" : "STOP");
2014 
2015  if (!local)
2016  {
2017  command.push_front("IMAGE_SCAN");
2018  bool ok = gCoreContext->SendReceiveStringList(command, true);
2019  return ok ? "" : command[1];
2020  }
2021 
2022  // Create database on first scan
2023  if (!CreateTable())
2024  return "Couldn't create database";
2025 
2026  QStringList err = HandleScanRequest(command[0]);
2027  return err[0] == "OK" ? "" : err[1];
2028 }
2029 
2030 
2037 {
2038  QStringList strList;
2039  strList << "IMAGE_SCAN" << "QUERY";
2040 
2041  if (!gCoreContext->SendReceiveStringList(strList))
2042  {
2043  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Scan query failed : %1")
2044  .arg(strList.join(",")));
2045  }
2046  return strList;
2047 }
2048 
2049 
2056 QString ImageManagerFe::HideFiles(bool hidden, const ImageIdList &ids)
2057 {
2058  // Split images into <locals, remotes>
2059  StringPair lists = ImageItem::PartitionIds(ids);
2060  QString result = "";
2061 
2062  if (!lists.second.isEmpty())
2063  {
2064  QStringList message;
2065  message << "IMAGE_HIDE" << QString::number(hidden) << lists.second;
2066 
2067  if (!gCoreContext->SendReceiveStringList(message, true))
2068  result = message[1];
2069  }
2070 
2071  if (!lists.first.isEmpty())
2072  {
2073  QStringList err = HandleHide(hidden, lists.first);
2074  if (err[0] != "OK")
2075  result = err[1];
2076  }
2077  return result;
2078 }
2079 
2080 
2088  const ImageIdList &ids)
2089 {
2090  // Split images into <locals, remotes>
2091  StringPair lists = ImageItem::PartitionIds(ids);
2092  QString result = "";
2093 
2094  if (!lists.second.isEmpty())
2095  {
2096  QStringList message;
2097  message << "IMAGE_TRANSFORM" << QString::number(transform) << lists.second;
2098 
2099  if (!gCoreContext->SendReceiveStringList(message, true))
2100  result = message[1];
2101  }
2102 
2103  if (!lists.first.isEmpty())
2104  {
2105  QStringList err = HandleTransform(transform, lists.first);
2106  if (err[0] != "OK")
2107  result = err[1];
2108  }
2109  return result;
2110 }
2111 
2112 
2119 QString ImageManagerFe::SetCover(int parent, int cover)
2120 {
2121  if (!ImageItem::IsLocalId(parent))
2122  {
2123  QStringList message;
2124  message << "IMAGE_COVER" << QString::number(parent) << QString::number(cover);
2125 
2126  bool ok = gCoreContext->SendReceiveStringList(message, true);
2127  return ok ? "" : message[1];
2128  }
2129 
2130  QStringList err = HandleCover(parent, cover);
2131  return err[0] == "OK" ? "" : err[1];
2132 }
2133 
2134 
2140 {
2141  if (ImageItem::IsLocalId(id))
2142  HandleGetMetadata(QString::number(id));
2143  else
2144  gCoreContext->SendEvent(MythEvent("IMAGE_GET_METADATA", QString::number(id)));
2145 }
2146 
2147 
2150 {
2151  QStringList message("IMAGE_SCAN");
2152  message << "DEVICE CLEAR ALL";
2153  gCoreContext->SendReceiveStringList(message, true);
2154 }
2155 
2156 
2163 QString ImageManagerFe::IgnoreDirs(const QString &excludes)
2164 {
2165  QStringList message("IMAGE_IGNORE");
2166  message << excludes;
2167  bool ok = gCoreContext->SendReceiveStringList(message, true);
2168  return ok ? "" : message[1];
2169 }
2170 
2171 
2179 QString ImageManagerFe::MakeDir(int parent, const QStringList &names, bool rescan)
2180 {
2181  QString destId = QString::number(parent);
2182 
2183  if (!ImageItem::IsLocalId(parent))
2184  {
2185  QStringList message("IMAGE_CREATE_DIRS");
2186  message << destId << QString::number(rescan) << names;
2187  bool ok = gCoreContext->SendReceiveStringList(message, true);
2188  return ok ? "" : message[1];
2189  }
2190  QStringList err = HandleDirs(destId, rescan, names);
2191  return (err[0] == "OK") ? "" : err[1];
2192 }
2193 
2194 
2201 QString ImageManagerFe::RenameFile(const ImagePtrK& im, const QString &name)
2202 {
2203  if (!im->IsLocal())
2204  {
2205  QStringList message("IMAGE_RENAME");
2206  message << QString::number(im->m_id) << name;
2207  bool ok = gCoreContext->SendReceiveStringList(message, true);
2208  return ok ? "" : message[1];
2209  }
2210  QStringList err = HandleRename(QString::number(im->m_id), name);
2211  return (err[0] == "OK") ? "" : err[1];
2212 }
2213 
2214 
2221 QString ImageManagerFe::CreateImages(int destId, const ImageListK &images)
2222 {
2223  if (images.isEmpty())
2224  return "";
2225 
2226  // Define field seperator & include it in message
2227  const QString seperator("...");
2228  QStringList imageDefs(seperator);
2229  ImageIdList ids;
2230  foreach (const ImagePtrK &im, images)
2231  {
2232  ids << im->m_id;
2233 
2234  // Copies preserve hide state, orientation & cover
2235  QStringList aDef;
2236  aDef << QString::number(im->m_id)
2237  << QString::number(im->m_type)
2238  << im->m_filePath
2239  << QString::number(im->m_isHidden)
2240  << QString::number(im->m_orientation)
2241  << QString::number(im->m_userThumbnail);
2242 
2243  imageDefs << aDef.join(seperator);
2244  }
2245 
2246  // Images are either all local or all remote
2247  if (ImageItem::IsLocalId(destId))
2248  {
2249  QStringList err = HandleDbCreate(imageDefs);
2250  return (err[0] == "OK") ? "" : err[1];
2251  }
2252  imageDefs.prepend("IMAGE_COPY");
2253  bool ok = gCoreContext->SendReceiveStringList(imageDefs, true);
2254  return ok ? "" : imageDefs[1];
2255 }
2256 
2257 
2265 QString ImageManagerFe::MoveDbImages(const ImagePtrK& destDir, ImageListK &images,
2266  const QString &srcPath)
2267 {
2268  QStringList idents;
2269  foreach (const ImagePtrK &im, images)
2270  idents << QString::number(im->m_id);
2271 
2272  // Images are either all local or all remote
2273  if (destDir->IsLocal())
2274  {
2275  QStringList err = HandleDbMove(idents.join(","), srcPath,
2276  destDir->m_filePath);
2277  return (err[0] == "OK") ? "" : err[1];
2278  }
2279 
2280  QStringList message("IMAGE_MOVE");
2281  message << idents.join(",") << srcPath << destDir->m_filePath;
2282  bool ok = gCoreContext->SendReceiveStringList(message, true);
2283  return ok ? "" : message[1];
2284 }
2285 
2286 
2293 {
2294  StringPair lists = ImageItem::PartitionIds(ids);
2295 
2296  QString result = "";
2297  if (!lists.second.isEmpty())
2298  {
2299  QStringList message("IMAGE_DELETE");
2300  message << lists.second;
2301 
2302  bool ok = gCoreContext->SendReceiveStringList(message, true);
2303  if (!ok)
2304  result = message[1];
2305  }
2306  if (!lists.first.isEmpty())
2307  {
2308  QStringList err = HandleDelete(lists.first);
2309  if (err[0] != "OK")
2310  result = err[1];
2311  }
2312  return result;
2313 }
2314 
2315 
2323 {
2324  if (im->m_id == GALLERY_DB_ID)
2325  return "";
2326 
2327 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
2328  uint secs = 0;
2329 #else
2330  qint64 secs = 0;
2331 #endif
2333 
2334  if (im->m_date > 0)
2335  {
2336  secs = im->m_date;
2337  format |= MythDate::kTime;
2338  }
2339  else
2340  secs = im->m_modTime;
2341 
2342 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
2343  return MythDate::toString(QDateTime::fromTime_t(secs), format);
2344 #else
2345  return MythDate::toString(QDateTime::fromSecsSinceEpoch(secs), format);
2346 #endif
2347 }
2348 
2349 
2356 QString ImageManagerFe::ShortDateOf(const ImagePtrK& im) const
2357 {
2358  if (im->m_id == GALLERY_DB_ID)
2359  return "";
2360 
2361 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
2362  uint secs(im->m_date > 0 ? im->m_date : im->m_modTime);
2363  return QDateTime::fromTime_t(secs).date().toString(m_dateFormat);
2364 #else
2365  qint64 secs(im->m_date > 0 ? im->m_date : im->m_modTime);
2366  return QDateTime::fromSecsSinceEpoch(secs).date().toString(m_dateFormat);
2367 #endif
2368 }
2369 
2370 
2377 {
2378  if (im.m_id == GALLERY_DB_ID)
2379  return tr("Gallery");
2380  if (im.m_id == PHOTO_DB_ID)
2381  return tr("Photographs");
2382  return im.IsLocal() ? DeviceName(im.m_device)
2383  : m_remote->DeviceName(im.m_device);
2384 }
2385 
2386 
2394 QString ImageManagerFe::CrumbName(ImageItemK &im, bool getPath) const
2395 {
2396  if (im.IsDevice())
2397  return DeviceCaption(im);
2398 
2399  if (!getPath)
2400  return im.m_baseName;
2401 
2402  QString dev;
2403  QString path(im.m_filePath);
2404 
2405  if (im.IsLocal())
2406  {
2407  // Replace local mount path with device name
2408  path.remove(0, DeviceMount(im.m_device).size());
2409  dev = DeviceName(im.m_device);
2410  }
2411  return dev + path.replace("/", " > ");
2412 }
2413 
2414 
2415 void ImageManagerFe::CloseDevices(int devId, bool eject)
2416 {
2417  QString reason = (devId == DEVICE_INVALID)
2418  ? "DEVICE CLOSE ALL"
2419  : eject ? "DEVICE EJECT" : "DEVICE REMOVE";
2420  HandleScanRequest(reason, devId);
2421 }
2422 
2423 
2429 {
2431  if (!monitor)
2432  return false;
2433 
2434  // Detect all local media
2435  QList<MythMediaDevice*> devices
2437 
2438  foreach (MythMediaDevice* dev, devices)
2439  {
2440  if (monitor->ValidateAndLock(dev) && dev->isUsable())
2441  OpenDevice(dev->getDeviceModel(), dev->getMountPath(), dev);
2442  else
2443  monitor->Unlock(dev);
2444  }
2445 
2446  if (DeviceCount() > 0)
2447  {
2448  // Close devices that are no longer present
2449  foreach (int devId, GetAbsentees())
2450  CloseDevices(devId);
2451 
2452  // Start local scan
2453  QString err = ScanImagesAction(true, true);
2454  if (!err.isEmpty())
2455  LOG(VB_GENERAL, LOG_ERR, LOC + err);
2456  }
2457  return DeviceCount() > 0;
2458 }
2459 
2460 
2466 {
2468 
2469  if (!event || !monitor)
2470  return;
2471 
2472  MythMediaDevice *dev = event->getDevice();
2473  if (!dev)
2474  return;
2475 
2476  MythMediaType type = dev->getMediaType();
2477  MythMediaStatus status = dev->getStatus();
2478 
2479  LOG(VB_FILE, LOG_DEBUG, LOC +
2480  QString("Media event for %1 (%2) at %3, type %4, status %5 (was %6)")
2481  .arg(dev->getDeviceModel(), dev->getVolumeID(), dev->getMountPath())
2482  .arg(type).arg(status).arg(event->getOldStatus()));
2483 
2485  {
2486  LOG(VB_FILE, LOG_DEBUG, LOC +
2487  QString("Ignoring event - wrong type %1").arg(type));
2488  return;
2489  }
2490 
2491  if (status == MEDIASTAT_USEABLE || status == MEDIASTAT_MOUNTED)
2492  {
2493  // New device. Lock it & scan
2494  if (monitor->ValidateAndLock(dev))
2495  {
2496  OpenDevice(dev->getDeviceModel(), dev->getMountPath(), dev);
2497  ScanImagesAction(true, true);
2498  }
2499  else
2500  monitor->Unlock(dev);
2501  return;
2502  }
2503 
2504  // Device has disappeared
2505  int devId = LocateMount(dev->getMountPath());
2506  if (devId != DEVICE_INVALID)
2507  CloseDevices(devId);
2508 }
2509 
2510 
2512 {
2513  auto *tmp = new QTemporaryDir(QDir::tempPath() % "/" IMPORTDIR "-XXXXXX");
2514  if (!tmp->isValid())
2515  {
2516  delete tmp;
2517  return "";
2518  }
2519 
2520  QString time(QDateTime::currentDateTime().toString("mm:ss"));
2521  OpenDevice("Import " + time, tmp->path(), nullptr, tmp);
2522  return tmp->path();
2523 }
2524 
2525 
2526 // Must define the valid template implementations to generate code for the
2527 // instantiations (as they are defined in the cpp rather than header).
2528 // Otherwise the linker will fail with undefined references...
2529 template class ImageDb<ImageAdapterSg>;
2530 template class ImageDb<ImageAdapterLocal>;
2531 template class ImageHandler<ImageDbSg>;
2532 template class ImageHandler<ImageDbLocal>;
MythMediaType getMediaType() const
Definition: mythmedia.h:91
void DropTable()
Remove local image table.
QStringList HandleDelete(const QString &ids) const
Deletes images/dirs.
File size Largest -> Smallest.
Definition: imagetypes.h:52
static QString OrderSelector(int order)
Generate SQL ordering clause.
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:783
A video.
Definition: imagetypes.h:39
static QString FormatSize(int sizeKib)
Definition: imagemanager.h:143
QString DeviceName(int devId) const
Get model name of the device.
QSharedPointer< ImageItem > ImagePtr
Definition: imagetypes.h:166
void GetDescendantCount(int id, bool all, int &dirs, int &pics, int &videos, int &sizeKb) const
Return counts of dirs, pics, videos and size in the subtree of a dir.
Name Z-A.
Definition: imagetypes.h:46
const QString & getDevicePath() const
Definition: mythmedia.h:61
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:864
QStringList GetDirList(void) const
Definition: storagegroup.h:23
static Device kNullDevice
Abstract class for image metadata.
Definition: imagemetadata.h:99
int ReadImages(ImageList &dirs, ImageList &files, const QString &selector) const
Read selected database images/dirs.
MythMediaType
Definition: mythmedia.h:24
Encapsulates Exif orientation processing.
Definition: imagemetadata.h:62
VERBOSE_PREAMBLE Most true
Definition: verbosedefs.h:91
bool RemoveFromDB(Bookmark *site)
QString Description()
Generate text description of orientation.
int m_dirOrder
Display ordering of dirs.
Definition: imagemanager.h:440
QString MakeDir(int parent, const QStringList &names, bool rescan=true)
Create directories.
#define DB_COLUMNS
QString toString(MarkTypes type)
void EjectMedia(const QString &path)
A device sub directory.
Definition: imagetypes.h:37
QString ChangeOrientation(ImageFileTransform transform, const ImageIdList &ids)
Apply an orientation transform to images.
bool CreateTable()
Create local database table, if it doesn't exist.
Extension Z-A.
Definition: imagetypes.h:50
int m_showType
Type of images to display - pic only/video only/both.
Definition: imagemanager.h:443
int InsertDbImage(ImageItemK &im, bool checkForDuplicate=false) const
Adds new image to database, optionally checking for existing filepath.
QString m_dateFormat
UI format for thumbnail date captions.
Definition: imagemanager.h:514
void GetDescendantCount(int id, int &dirs, int &pics, int &videos, int &sizeKb) const
Return counts of dirs, pics and videos in the subtree of a dir. Also dir size.
ImageItem * CreateImage(const MSqlQuery &query) const
Create image from Db query data.
#define STORAGE_GROUP_MOUNT
static QString GetAbsThumbPath(const QString &devPath, const QString &path)
Get absolute filepath for thumbnail of an image.
Definition: imagemanager.h:148
The image manager for use by Frontends.
Definition: imagemanager.h:456
qint64 m_date
Image creation date, from Exif metadata.
Definition: imagetypes.h:106
bool SetHidden(bool hide, QString ids) const
Sets hidden status of an image/dir in database.
QString m_filePath
Absolute for local images. Usually SG-relative for remotes.
Definition: imagetypes.h:92
const association_list & getList() const
Definition: dbaccess.cpp:806
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
void RemoveFiles(ImageList &images) const
Deletes images and dirs from the filesystem.
#define RESULT_ERR(ERR, MESG)
Database API.
Definition: imagemanager.h:274
Manages a collection of images.
QStringList HandleDbMove(const QString &ids, const QString &srcPath, QString destPath) const
Updates images that have been renamed.
int Transform(int transform)
Adjust orientation to apply a transform to an image.
int size(void) const
Definition: mythdbcon.h:203
#define RESULT_OK(MESG)
#define EXIF_MYTH_SIZE
Definition: imagemetadata.h:41
static QString LongDateOf(const ImagePtrK &im)
Return a timestamp/datestamp for an image or dir.
static void ClearStorageGroup()
Clear database & thumbnails of Storage Group images.
static QString ThumbPath(const ImageItem &im)
Thumbnails of videos are a JPEG snapshot with jpg suffix appended.
Definition: imagemanager.h:152
QDir m_dirFilter
A pre-configured dir for reading image/video files.
Definition: imagemanager.h:175
int m_parentId
Id of parent dir.
Definition: imagetypes.h:95
ImageDbSg * m_remote
Remote database access.
Definition: imagemanager.h:438
Show Pictures & Videos.
Definition: imagemanager.h:78
std::vector< file_association > association_list
Definition: dbaccess.h:154
QString m_refineClause
SQL clause for image filtering/ordering.
Definition: imagemanager.h:444
QStringList HandleDirs(const QString &destId, bool rescan, const QStringList &relPaths) const
Creates new image directories.
QString GetAbsFilePath(const ImagePtrK &im) const
Get absolute filepath for a remote image.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString CreateImages(int destId, const ImageListK &images)
Copies database images (but not the files themselves).
bool m_showHidden
Whether hidden images are displayed.
Definition: imagemanager.h:442
ImageManagerFe(int order, int dirOrder, bool showAll, int showType, QString dateFormat)
Definition: imagemanager.h:501
static void Notify(const QString &mesg, const QStringList &extra)
Send message to all clients about remote ids.
int OpenDevice(const QString &name, const QString &mount, MythMediaDevice *media=nullptr, QTemporaryDir *dir=nullptr)
Define a new device and assign it a unique id. If the device is already known, its existing id is ret...
bool isImport() const
QStringList CloseDevices(int devId, const QString &action)
Remove a device (or all devices)
bool IsDevice() const
Definition: imagetypes.h:123
const char * formats[8]
Definition: vbilut.cpp:190
StorageGroup m_sg
Images storage group.
Definition: imagemanager.h:266
int GetChildren(int id, ImageList &files, ImageList &dirs) const
Return (local or remote) images that are direct children of a dir.
bool IsLocal() const
Definition: imagetypes.h:126
static guint32 * tmp
Definition: goom_core.c:35
Exif date Latest -> Earliest.
Definition: imagetypes.h:54
QString DeleteFiles(const ImageIdList &ids)
Delete images.
QStringList RemoveFromDB(const ImageList &imList) const
Remove images/dirs from database.
static void RemoveDirContents(const QString &path)
Clears all files and sub-dirs within a directory.
bool ReadAllImages(ImageHash &files, ImageHash &dirs) const
Read all database images and dirs as map. No filters or ordering applied.
void CreateThumbnails(const ImageIdList &ids, bool forFolder)
Create thumbnails or verify that they already exist.
QList< int > GetAbsentees()
Get list of mountpoints for non-import devices.
QSharedPointer< ImageItemK > ImagePtrK
Definition: imagetypes.h:172
static ImageMetaData * FromPicture(const QString &filePath)
Factory to retrieve metadata from pictures.
QString HideFiles(bool hidden, const ImageIdList &ids)
Hide/unhide images.
QString m_extension
Image file extension.
Definition: imagetypes.h:93
bool isPresent() const
The image manager to be used by the Backend.
Definition: imagemanager.h:378
MythMediaStatus getOldStatus(void) const
Definition: mythmedia.h:189
QString GetConfDir(void)
Definition: mythdirs.cpp:224
QVariant value(int i) const
Definition: mythdbcon.h:198
Hide videos.
Definition: imagemanager.h:79
A device containing images (ie. USB stick, CD, storage group etc)
QString m_mount
Mountpoint.
bool UpdateDbImage(ImageItemK &im) const
Updates or creates database image or dir.
const QString & getVolumeID() const
Definition: mythmedia.h:72
int m_userThumbnail
Id of thumbnail to use as cover (dirs only)
Definition: imagetypes.h:113
void run() override
MythMediaDevice * m_media
Set for MediaMonitor devices only.
int GetDirectory(int id, ImagePtr &parent, ImageList &files, ImageList &dirs) const
Return images (local and/or remote) for a dir and its direct children.
ImageFileTransform
Image transformations.
Definition: imagemetadata.h:46
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
QStringList m_imageFileExt
List of file extensions recognised as pictures.
Definition: imagemanager.h:177
int m_fileOrder
Display ordering of pics/videos.
Definition: imagemanager.h:441
QString ThumbDir(int fs) const
QString SetCover(int parent, int cover)
Set image to use as a cover thumbnail(s)
static ImageManagerBe * s_instance
BE Gallery instance.
Definition: imagemanager.h:391
This class is used as a container for messages.
Definition: mythevent.h:16
#define TEMP_SUBDIR
Definition: imagemanager.h:67
MBASE_PUBLIC QDateTime fromSecsSinceEpoch(uint seconds)
This function takes the number of seconds since the start of the epoch and returns a QDateTime with t...
Definition: mythdate.cpp:88
QStringList HandleCreateThumbnails(const QStringList &message) const
Creates thumbnails on-demand.
int GetChildren(QString ids, ImageList &files, ImageList &dirs, const QString &refine="") const
Read immediate children of a dir.
static bool IsLocalParent(int id)
Parents of locals are locals or root.
Definition: imagetypes.h:131
#define EXIF_MYTH_ORIENT
Definition: imagemetadata.h:42
QVariant lastInsertId()
Return the id of the last inserted row.
Definition: mythdbcon.cpp:888
ImageAdapterBase()
Constructor.
QStringList HandleScanRequest(const QString &command, int devId=DEVICE_INVALID) const
Process scan requests.
static void clear(SettingsMap &cache, SettingsMap &overrides, const QString &myKey)
Definition: mythdb.cpp:846
static ImageManagerBe * getInstance()
Get Backend Gallery.
Name A-Z.
Definition: imagetypes.h:45
ImageNodeType GetImageType(const QString &ext) const
Determine file type from its extension.
Definition: imagemanager.h:165
QString ScanImagesAction(bool start, bool local=false)
Handle scanner start/stop commands.
bool SetCover(int dir, int id) const
Set the thumbnail(s) to be used for a dir.
QMap< int, QString > StringMap
Definition: imagetypes.h:62
#define THUMBNAIL_SUBDIR
Definition: imagemanager.h:69
QString GetSetting(const QString &key, const QString &defaultval="")
static QString ToString(const QString &name, const QString &label, const QString &value)
Encodes metadata into a string as <tag name><tag label><tag value>
bool GetImageTree(int id, ImageList &files, const QString &refine) const
Returns all files in the sub-tree of a dir.
ImageItem * CreateItem(const QFileInfo &fi, int parentId, int devId, const QString &base) const
Construct a local image from a file.
StringMap GetScanDirs() const
Returns SG dirs.
Default local time.
Definition: mythdate.h:16
static MediaMonitor * GetMediaMonitor(void)
int GetDirectory(int id, ImagePtr &parent, ImageList &files, ImageList &dirs, const QString &refine) const
Read a dir and its immediate children from Db.
Extension A-Z.
Definition: imagetypes.h:49
static QString IgnoreDirs(const QString &excludes)
Set directories to ignore during scans of the storage group.
static QString BaseNameOf(const QString &path)
Extracts file name (incl extension) from a filepath.
Definition: imagemanager.h:136
QString FindNextDirMostFree(void)
Reflect about horizontal axis.
Definition: imagemetadata.h:51
void SetRefinementClause()
Sets filter/ordering SQL clause used when reading database according to current filter/sort settings.
QString RenameFile(const ImagePtrK &im, const QString &name)
Rename an image.
int GetImages(const ImageIdList &ids, ImageList &files, ImageList &dirs) const
Returns images (local or remote but not a combination)
static ImageManagerFe * s_instance
FE Gallery instance.
Definition: imagemanager.h:511
static QStringList SupportedVideos()
Return recognised video extensions.
int GetImages(const QString &ids, ImageList &files, ImageList &dirs, const QString &refine="") const
Read database images/dirs by id.
QList< MythMediaDevice * > GetMedias(unsigned mediatypes)
Ask for available media.
QStringList HandleRename(const QString &id, const QString &newBase) const
Change name of an image/dir.
QStringList HandleGetMetadata(const QString &id) const
Read meta data for an image.
qint64 m_modTime
Filesystem modified datestamp.
Definition: imagetypes.h:100
Add year to string if not included.
Definition: mythdate.h:22
const QString & getDeviceModel() const
Definition: mythmedia.h:67
virtual QStringList GetAllTags()=0
unsigned int uint
Definition: compat.h:140
void GetImageTree(int id, ImageList &files) const
Return all files (local or remote) in the sub-trees of a dir.
static StringPair PartitionIds(const ImageIdList &ids)
Separates list of ids into a list of local ids and a list of remote ids.
Definition: imagetypes.h:147
QString m_table
Db table name.
Definition: imagemanager.h:310
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
ReadMetaThread(ImagePtrK im, QString path)
void Unlock(MythMediaDevice *pMedia)
decrements the MythMediaDevices reference count
File modified time Latest -> Earliest.
Definition: imagetypes.h:48
#define IMPORTDIR
static bool IsLocalId(int id)
Determine image type (local/remote) from its id. Root/Gallery is remote.
Definition: imagetypes.h:129
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:101
int m_orientation
Image orientation.
Definition: imagetypes.h:108
QString m_baseName
File/Dir name with extension (no path)
Definition: imagetypes.h:91
DeviceMap m_devices
Device store.
Definition: imagemanager.h:119
QPair< QString, QString > StringPair
Definition: imagetypes.h:60
bool GetDescendants(const QString &ids, ImageList &files, ImageList &dirs) const
Return images and all of their descendants.
QList< ImagePtr > ImageList
Definition: imagetypes.h:167
#define EXIF_MYTH_PATH
Definition: imagemetadata.h:39
ImageDbSg()
SG database constructor.
static FileAssociations & getFileAssociation()
Definition: dbaccess.cpp:831
void DeviceEvent(MythMediaEvent *event)
Manage events for local devices.
Task to read all metadata from file.
A picture.
Definition: imagetypes.h:38
static MThreadPool * globalInstance(void)
void setPresent(MythMediaDevice *media)
QString DeviceCaption(ImageItemK &im) const
Return translated device name.
void ClearDb(int devId, const QString &action)
Clear Db for device & remove device.
int GetNumSetting(const QString &key, int defaultval=0)
Client request to display an image thumbnail.
Definition: imagethumbs.h:34
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:808
QList< int > ImageIdList
Definition: imagetypes.h:59
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
Exif date Earliest -> Latest.
Definition: imagetypes.h:53
void start(QRunnable *runnable, const QString &debugName, int priority=0)
bool ValidateAndLock(MythMediaDevice *pMedia)
Validates the MythMediaDevice and increments its reference count.
bool GetBoolSetting(const QString &key, bool defaultval=false)
QString DeviceMount(int devId) const
Get path at which the device is mounted.
QString FindFile(const QString &filename)
const QString & getMountPath() const
Definition: mythmedia.h:58
QStringList m_videoFileExt
List of file extensions recognised as videos.
Definition: imagemanager.h:179
int m_device
Id of media device. Always 0 (SG) for remotes, 1+ for local devices.
Definition: imagetypes.h:94
void CloseDevices(int devId=DEVICE_INVALID, bool eject=false)
Client request to display a directory thumbnail.
Definition: imagethumbs.h:35
QStringList HandleCover(int dir, int cover) const
Updates/resets cover thumbnail for an image dir.
void RemoveThumbs(void)
Delete thumbnails associated with device.
#define LOC
bool SetOrientation(int id, int orientation) const
Sets image orientation in Db.
#define EXIF_MYTH_HOST
Definition: imagemetadata.h:38
static QString TypeSelector(int type)
Generate SQL type filter clause.
Represents a picture, video or directory.
Definition: imagetypes.h:67
#define DBLOC
QString m_name
Device model/volume/id.
bool isUsable() const
Is this device "ready", for a plugin to access?
Definition: mythmedia.h:84
static QStringList ScanQuery()
Returns storage group scanner status.
QStringList HandleHide(bool hide, const QString &ids) const
Hides/unhides images/dirs.
bool DetectLocalDevices()
Detect and scan local devices.
void GetDescendants(const ImageIdList &ids, ImageList &files, ImageList &dirs) const
Return all (local or remote) images that are direct children of a dir.
int m_type
Type of node: dir, video etc.
Definition: imagetypes.h:96
#define EXIF_MYTH_NAME
Definition: imagemetadata.h:40
#define PHOTO_DB_ID
Definition: imagetypes.h:28
Storage Group and local mounted media.
Definition: imagetypes.h:35
File modified time Earliest -> Latest.
Definition: imagetypes.h:47
QString m_thumbs
Dir sub-path of device thumbnails.
QStringList HandleDbCreate(QStringList defs) const
Creates images for files created by a copy operation.
QString MoveDbImages(const ImagePtrK &destDir, ImageListK &images, const QString &srcPath)
Moves database images (but not the files themselves).
ImageDbLocal()
Local database constructor.
QString ShortDateOf(const ImagePtrK &im) const
Return a short datestamp for thumbnail captions.
Hide pictures.
Definition: imagemanager.h:80
MythMediaStatus
Definition: mythmedia.h:12
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
bool m_present
True when gallery UI is running & device is useable. Always true for imports.
Unprocessable file type.
Definition: imagetypes.h:34
int DeviceCount() const
Definition: imagemanager.h:100
bool SaveSettingOnHost(const QString &key, const QString &newValue, const QString &host)
QTemporaryDir * m_dir
Dir path of images: import devices only.
#define DB_TABLE
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
static void Notify(const QString &mesg, const QStringList &extra)
Send local message to UI about local ids.
int LocateMount(const QString &mount) const
Find the id of a device.
static ImageManagerFe & getInstance()
Get Frontend Gallery.
QStringList HandleTransform(int transform, const QString &ids) const
Change orientation of pictures by applying a transformation.
QString CreateImport()
QHash< QString, ImagePtr > ImageHash
Definition: imagetypes.h:168
void RequestMetaData(int id)
Requests all exif/ffmpeg tags for an image, which returns by event.
int m_id
Uniquely identifies an image (file/dir).
Definition: imagetypes.h:88
QString GetHostName(void)
QStringList HandleIgnore(const QString &exclusions) const
Updates exclusion list for images.
QString m_comment
User comment, from Exif metadata.
Definition: imagetypes.h:109
QString CrumbName(ImageItemK &im, bool getPath=false) const
Return a displayable name (with optional path) for an image.
ImageItem * CreateItem(const QFileInfo &fi, int parentId, int devId, const QString &base) const
Construct a remote image from a file.
void SendEvent(const MythEvent &event)
~Device()
Delete device, its thumbnails and any imported images.
int m_size
Filesize (files only)
Definition: imagetypes.h:102
#define GALLERY_DB_ID
Definition: imagetypes.h:26
Device(QString name, QString mount, MythMediaDevice *media=nullptr, QTemporaryDir *import=nullptr)
static QStringList SupportedImages()
Return recognised pictures.
File size Smallest -> Largest.
Definition: imagetypes.h:51
static ImageMetaData * FromVideo(const QString &filePath)
Factory to retrieve metadata from videos.
MythMediaStatus getStatus() const
Definition: mythmedia.h:70
static QString PathOf(const QString &path)
Extracts path from a filepath.
Definition: imagemanager.h:140
#define DEVICE_INVALID
Definition: imagemanager.h:71
bool m_isHidden
If true, image won't be shown.
Definition: imagetypes.h:112
#define IMAGE_STORAGE_GROUP
Definition: imagemanager.h:63
StringMap GetDeviceDirs() const
Get all known devices.
Default local time.
Definition: mythdate.h:19
QList< ImagePtrK > ImageListK
Definition: imagetypes.h:173
void Close(bool eject=false)
Releases device.