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(const QString &name, const QString &mount,
37  MythMediaDevice *media = nullptr, QTemporaryDir *import = nullptr)
38  : m_present(true), m_name(name), m_mount(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  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 {
247  StringMap paths;
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 (FileAssociations::association_list::const_iterator p =
319  faList.begin(); p != faList.end(); ++p)
320  {
321  if (!p->use_default && p->playcommand == "Internal")
322  formats << QString(p->extension);
323  }
324  return formats;
325 }
326 
327 
336 ImageItem *ImageAdapterLocal::CreateItem(const QFileInfo &fi, int parentId,
337  int devId, const QString & /*base*/) const
338 {
339  ImageItem *im = new ImageItem();
340 
341  im->m_parentId = parentId;
342  im->m_device = devId;
343  im->m_filePath = fi.absoluteFilePath();
344 
345  if (parentId == GALLERY_DB_ID)
346  {
347  // Import devices show time of import, other devices show 'last scan time'
348 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
349  im->m_date = im->m_filePath.contains(IMPORTDIR)
350  ? fi.lastModified().toTime_t()
351  : QDateTime::currentMSecsSinceEpoch() / 1000;
352 #else
353  im->m_date = im->m_filePath.contains(IMPORTDIR)
354  ? fi.lastModified().toSecsSinceEpoch()
355  : QDateTime::currentSecsSinceEpoch();
356 #endif
357  im->m_modTime = im->m_date;
358  im->m_type = kDevice;
359  return im;
360  }
361 
362 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
363  im->m_modTime = fi.lastModified().toTime_t();
364 #else
365  im->m_modTime = fi.lastModified().toSecsSinceEpoch();
366 #endif
367 
368  if (fi.isDir())
369  {
370  im->m_type = kDirectory;
371  return im;
372  }
373 
374  im->m_extension = fi.suffix().toLower();
375  im->m_type = GetImageType(im->m_extension);
376 
377  if (im->m_type == kUnknown)
378  {
379  delete im;
380  return nullptr;
381  }
382 
384  im->m_size = fi.size();
385 
386  return im;
387 }
388 
389 
395 void ImageAdapterLocal::Notify(const QString &mesg,
396  const QStringList &extra) const
397 {
398  QString host(gCoreContext->GetHostName());
399  gCoreContext->SendEvent(MythEvent(QString("%1 %2").arg(mesg, host), extra));
400 }
401 
402 
411 ImageItem *ImageAdapterSg::CreateItem(const QFileInfo &fi, int parentId,
412  int /*devId*/, const QString &base) const
413 {
414  ImageItem *im = new ImageItem();
415 
416  im->m_device = 0;
417  im->m_parentId = parentId;
418 
419  if (parentId == GALLERY_DB_ID)
420  {
421  // All SG dirs map to a single Db dir
422  im->m_filePath = "";
423  im->m_type = kDevice;
424  im->m_date = QDateTime::currentMSecsSinceEpoch() / 1000;
425  im->m_modTime = im->m_date;
426  return im;
427  }
428 
429  // Strip SG path & leading / to leave a relative path
430  im->m_filePath = fi.absoluteFilePath().mid(base.size() + 1);
431 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
432  im->m_modTime = fi.lastModified().toTime_t();
433 #else
434  im->m_modTime = fi.lastModified().toSecsSinceEpoch();
435 #endif
436 
437  if (fi.isDir())
438  {
439  im->m_type = kDirectory;
440  return im;
441  }
442 
443  im->m_extension = fi.suffix().toLower();
444  im->m_type = GetImageType(im->m_extension);
445 
446  if (im->m_type == kUnknown)
447  {
448  delete im;
449  return nullptr;
450  }
451 
453  im->m_size = fi.size();
454 
455  return im;
456 }
457 
458 
464 void ImageAdapterSg::Notify(const QString &mesg,
465  const QStringList &extra) const
466 {
467  gCoreContext->SendEvent(MythEvent(mesg, extra));
468 }
469 
470 
476 {
477  StringMap map;
478  int i = 0;
479  foreach (const QString &path, m_sg.GetDirList())
480  map.insert(i++, path);
481  return map;
482 }
483 
484 
492 {
493  if (im->IsDevice())
494  return m_sg.FindNextDirMostFree();
495  return im->m_filePath.startsWith("/") ? im->m_filePath
496  : m_sg.FindFile(im->m_filePath);
497 }
498 
499 
500 // Database fields used by several image queries
501 #define DB_COLUMNS \
502 "file_id, filename, name, dir_id, type, modtime, size, " \
503 "extension, date, hidden, orientation, angle, path, zoom"
504 // Id, filepath, basename, parentId, type, modtime, size,
505 // extension, image date, hidden, orientation, cover id, comment, device id
506 
507 
513 template <class FS>
515 {
516  ImageItem *im = new ImageItem(FS::ImageId(query.value(0).toInt()));
517 
518  // Ordered as per DB_COLUMNS
519  im->m_filePath = query.value(1).toString();
520  im->m_baseName = query.value(2).toString();
521  im->m_parentId = FS::ImageId(query.value(3).toInt());
522  im->m_type = query.value(4).toInt();
523  im->m_modTime = query.value(5).toInt();
524  im->m_size = query.value(6).toInt();
525  im->m_extension = query.value(7).toString();
526  im->m_date = query.value(8).toUInt();
527  im->m_isHidden = query.value(9).toBool();
528  im->m_orientation = query.value(10).toInt();
529  im->m_userThumbnail = FS::ImageId(query.value(11).toInt());
530  im->m_comment = query.value(12).toString();
531  im->m_device = query.value(13).toInt();
532  im->m_url = FS::MakeFileUrl(im->m_filePath);
533 
534  if (im->IsFile())
535  {
536  // Only pics/vids have thumbs
537  QString thumbPath(FS::ThumbPath(*im));
538  QString devPath(FS::ThumbDir(im->m_device));
539  QString url(FS::MakeThumbUrl(devPath, thumbPath));
540 
541  im->m_thumbPath = FS::GetAbsThumbPath(devPath, thumbPath);
542  im->m_thumbNails.append(qMakePair(im->m_id, url));
543  }
544  return im;
545 }
546 
547 
556 template <class FS>
557 int ImageDb<FS>::GetImages(const QString &ids, ImageList &files, ImageList &dirs,
558  const QString &refine) const
559 {
560  if (ids.isEmpty())
561  return 0;
562 
563  QString select = QString("file_id IN (%1) %2").arg(FS::DbIds(ids), refine);
564  return ReadImages(dirs, files, select);
565 }
566 
567 
576 template <class FS>
577 int ImageDb<FS>::GetChildren(QString ids, ImageList &files, ImageList &dirs,
578  const QString &refine) const
579 {
580  QString select = QString("dir_id IN (%1) %2").arg(FS::DbIds(ids), refine);
581  return ReadImages(dirs, files, select);
582 }
583 
584 
594 template <class FS>
596  ImageList &files, ImageList &dirs,
597  const QString &refine) const
598 {
599  MSqlQuery query(MSqlQuery::InitCon());
600  query.prepare(QString("SELECT " DB_COLUMNS " FROM %1 "
601  "WHERE (dir_id = :ID1 OR file_id = :ID2) "
602  "%2;").arg(m_table, refine));
603 
604  // Qt < 5.4 won't bind multiple occurrences
605  int dbId = FS::DbId(id);
606  query.bindValue(":ID1", dbId);
607  query.bindValue(":ID2", dbId);
608 
609  if (!query.exec())
610  {
611  MythDB::DBError(DBLOC, query);
612  return -1;
613  }
614  while (query.next())
615  {
616  ImagePtr im(CreateImage(query));
617 
618  if (im->IsFile())
619  files.append(im);
620  else if (im->m_id == id)
621  parent = im;
622  else
623  dirs.append(im);
624  }
625  return query.size();
626 }
627 
628 
636 template <class FS>
637 bool ImageDb<FS>::GetDescendants(const QString &ids,
638  ImageList &files, ImageList &dirs) const
639 {
640  if (ids.isEmpty())
641  return false;
642 
643  if (ReadImages(dirs, files, QString("file_id IN (%1)").arg(FS::DbIds(ids))) < 0)
644  return false;
645 
646  MSqlQuery query(MSqlQuery::InitCon());
647  QString sql =
648  QString("SELECT " DB_COLUMNS
649  ", LENGTH(filename) - LENGTH(REPLACE(filename, '/', ''))"
650  " AS depth "
651  "FROM %1 WHERE filename LIKE :PREFIX "
652  "ORDER BY depth;").arg(m_table);
653 
654  foreach (const ImagePtr &im1, dirs)
655  {
656  query.prepare(sql);
657  query.bindValue(":PREFIX", im1->m_filePath + "/%");
658 
659  if (!query.exec())
660  {
661  MythDB::DBError(DBLOC, query);
662  return false;
663  }
664 
665  while (query.next())
666  {
667  ImagePtr im2(CreateImage(query));
668  if (im2->IsDirectory())
669  dirs.append(im2);
670  else
671  files.append(im2);
672  }
673  }
674  return true;
675 }
676 
677 
685 template <class FS>
686 bool ImageDb<FS>::GetImageTree(int id, ImageList &files, const QString &refine) const
687 {
688  // Load starting children
689  ImageList dirs;
690  if (GetChildren(QString::number(id), files, dirs, refine) < 0)
691  return false;
692 
693  foreach (const ImagePtr &im, dirs)
694  if (!GetImageTree(im->m_id, files, refine))
695  return false;
696  return true;
697 }
698 
699 
705 template <class FS>
707 {
708  MSqlQuery query(MSqlQuery::InitCon());
709  query.prepare(QString("SELECT " DB_COLUMNS " FROM %1").arg(m_table));
710 
711  if (!query.exec())
712  {
713  MythDB::DBError(DBLOC, query);
714  return false;
715  }
716 
717  while (query.next())
718  {
719  ImagePtr im(CreateImage(query));
720  if (im->IsDirectory())
721  dirs.insert(im->m_filePath, im);
722  else
723  files.insert(im->m_filePath, im);
724  }
725  return true;
726 }
727 
728 
736 template <class FS>
737 void ImageDb<FS>::ClearDb(int devId, const QString &action)
738 {
739  if (action == "DEVICE CLOSE ALL")
740  // Retain Db images when closing UI
741  return;
742 
743  MSqlQuery query(MSqlQuery::InitCon());
744 
745  if (action == "DEVICE CLEAR ALL")
746  {
747  // Clear images from all devices. Reset auto-increment
748  query.prepare(QString("TRUNCATE TABLE %1;").arg(m_table));
749 
750  if (!query.exec())
751  MythDB::DBError(DBLOC, query);
752  }
753  else // Actions DEVICE REMOVE & DEVICE EJECT
754  {
755  // Delete all images of the device
756  query.prepare(QString("DELETE IGNORE FROM %1 WHERE zoom = :FS;").arg(m_table));
757  query.bindValue(":FS", devId);
758 
759  if (!query.exec())
760  MythDB::DBError(DBLOC, query);
761  }
762 }
763 
764 
772 template <class FS>
773 int ImageDb<FS>::InsertDbImage(ImageItemK &im, bool checkForDuplicate) const
774 {
775  MSqlQuery query(MSqlQuery::InitCon());
776 
777  if (checkForDuplicate)
778  {
779  query.prepare(QString("SELECT file_id FROM %1 WHERE filename = :NAME;")
780  .arg(m_table));
781 
782  query.bindValue(":NAME", im.m_filePath);
783 
784  if (!query.exec())
785  {
786  MythDB::DBError(DBLOC, query);
787  return -1;
788  }
789 
790  if (query.size() > 0)
791  {
792  LOG(VB_FILE, LOG_DEBUG, QString("Image: %1 already exists in Db")
793  .arg(im.m_filePath));
794  return query.value(0).toInt();
795  }
796  }
797 
798  query.prepare(QString("INSERT INTO %1 (" DB_COLUMNS ") VALUES (0, "
799  ":FILEPATH, :NAME, :PARENT, :TYPE, :MODTIME, "
800  ":SIZE, :EXTENSION, :DATE, :HIDDEN, :ORIENT, "
801  ":COVER, :COMMENT, :FS);").arg(m_table));
802 
803  query.bindValue(":FILEPATH", im.m_filePath);
804  query.bindValue(":NAME", FS::BaseNameOf(im.m_filePath));
805  query.bindValue(":FS", im.m_device);
806  query.bindValue(":PARENT", FS::DbId(im.m_parentId));
807  query.bindValue(":TYPE", im.m_type);
808  query.bindValue(":MODTIME", im.m_modTime);
809  query.bindValue(":SIZE", im.m_size);
810  query.bindValue(":EXTENSION", im.m_extension);
811  query.bindValue(":DATE", im.m_date);
812  query.bindValue(":ORIENT", im.m_orientation);
813  query.bindValue(":COMMENT", im.m_comment.isNull() ? "" : im.m_comment);
814  query.bindValue(":HIDDEN", im.m_isHidden);
815  query.bindValue(":COVER", FS::DbId(im.m_userThumbnail));
816 
817  if (query.exec())
818  return FS::ImageId(query.lastInsertId().toInt());
819 
820  MythDB::DBError(DBLOC, query);
821  return -1;
822 }
823 
824 
830 template <class FS>
832 {
833  MSqlQuery query(MSqlQuery::InitCon());
834  query.prepare(QString
835  ("UPDATE %1 SET "
836  "filename = :FILEPATH, name = :NAME, "
837  "dir_id = :PARENT, type = :TYPE, "
838  "modtime = :MODTIME, size = :SIZE, "
839  "extension = :EXTENSION, date = :DATE, zoom = :FS, "
840  "hidden = :HIDDEN, orientation = :ORIENT, "
841  "angle = :COVER, path = :COMMENT "
842  "WHERE file_id = :ID;").arg(m_table));
843 
844  query.bindValue(":ID", FS::DbId(im.m_id));
845  query.bindValue(":FILEPATH", im.m_filePath);
846  query.bindValue(":NAME", FS::BaseNameOf(im.m_filePath));
847  query.bindValue(":PARENT", FS::DbId(im.m_parentId));
848  query.bindValue(":TYPE", im.m_type);
849  query.bindValue(":MODTIME", im.m_modTime);
850  query.bindValue(":SIZE", im.m_size);
851  query.bindValue(":EXTENSION", im.m_extension);
852  query.bindValue(":DATE", im.m_date);
853  query.bindValue(":FS", im.m_device);
854  query.bindValue(":HIDDEN", im.m_isHidden);
855  query.bindValue(":ORIENT", im.m_orientation);
856  query.bindValue(":COVER", FS::DbId(im.m_userThumbnail));
857  query.bindValue(":COMMENT", im.m_comment.isNull() ? "" : im.m_comment);
858 
859  if (query.exec())
860  return true;
861 
862  MythDB::DBError(DBLOC, query);
863  return false;
864 }
865 
866 
873 template <class FS>
874 QStringList ImageDb<FS>::RemoveFromDB(const ImageList &imList) const
875 {
876  QStringList ids;
877  if (!imList.isEmpty())
878  {
879  foreach (const ImagePtr &im, imList)
880  ids << QString::number(FS::DbId(im->m_id));
881 
882  QString idents = ids.join(",");
883  MSqlQuery query(MSqlQuery::InitCon());
884  query.prepare(QString("DELETE IGNORE FROM %1 WHERE file_id IN (%2);")
885  .arg(m_table, idents));
886 
887  if (!query.exec())
888  {
889  MythDB::DBError(DBLOC, query);
890  return QStringList();
891  }
892  }
893  return ids;
894 }
895 
896 
903 template <class FS>
904 bool ImageDb<FS>::SetHidden(bool hide, QString ids) const
905 {
906  if (ids.isEmpty())
907  return false;
908 
909  MSqlQuery query(MSqlQuery::InitCon());
910 
911  query.prepare(QString("UPDATE %1 SET "
912  "hidden = :HIDDEN "
913  "WHERE file_id IN (%2);").arg(m_table, FS::DbIds(ids)));
914  query.bindValue(":HIDDEN", hide ? 1 : 0);
915 
916  if (query.exec())
917  return true;
918 
919  MythDB::DBError(DBLOC, query);
920  return false;
921 }
922 
923 
929 template <class FS>
930 bool ImageDb<FS>::SetCover(int dir, int id) const
931 {
932  MSqlQuery query(MSqlQuery::InitCon());
933 
934  query.prepare(QString("UPDATE %1 SET "
935  "angle = :COVER "
936  "WHERE file_id = :DIR").arg(m_table));
937  query.bindValue(":COVER", FS::DbId(id));
938  query.bindValue(":DIR", FS::DbId(dir));
939  \
940  if (query.exec())
941  return true;
942 
943  MythDB::DBError(DBLOC, query);
944  return false;
945 }
946 
947 
953 template <class FS>
954 bool ImageDb<FS>::SetOrientation(int id, int orientation) const
955 {
956  MSqlQuery query(MSqlQuery::InitCon());
957 
958  query.prepare(QString("UPDATE %1 SET ").arg(m_table) +
959  "orientation = :ORIENTATION "
960  "WHERE file_id = :ID");
961  query.bindValue(":ORIENTATION", orientation);
962  query.bindValue(":ID", FS::DbId(id));
963  \
964  if (query.exec())
965  return true;
966 
967  MythDB::DBError(DBLOC, query);
968  return false;
969 }
970 
971 
979 template <class FS>
981  const QString &selector) const
982 {
983  MSqlQuery query(MSqlQuery::InitCon());
984  query.prepare(QString("SELECT " DB_COLUMNS " FROM %1 WHERE %2")
985  .arg(m_table, selector));
986  if (!query.exec())
987  {
988  MythDB::DBError(DBLOC, query);
989  return -1;
990  }
991 
992  while (query.next())
993  {
994  ImagePtr im(CreateImage(query));
995 
996  if (im->IsFile())
997  files.append(im);
998  else
999  dirs.append(im);
1000  }
1001  return query.size();
1002 }
1003 
1004 
1014 template <class FS>
1015 void ImageDb<FS>::GetDescendantCount(int id, bool all, int &dirs,
1016  int &pics, int &videos, int &sizeKb) const
1017 {
1018  QString whereClause;
1019  if (!all)
1020  {
1021  whereClause = "WHERE filename LIKE "
1022  "( SELECT CONCAT(filename, '/%') "
1023  " FROM %2 WHERE file_id = :ID);";
1024  }
1025 
1026  MSqlQuery query(MSqlQuery::InitCon());
1027  query.prepare(QString("SELECT SUM(type <= :FLDR) AS Fldr, "
1028  " SUM(type = :PIC) AS Pics, "
1029  " SUM(type = :VID) AS Vids, "
1030  " SUM(size / 1024) "
1031  "FROM %2 %1;").arg(whereClause).arg(m_table));
1032 
1033  query.bindValue(":FLDR", kDirectory);
1034  query.bindValue(":PIC", kImageFile);
1035  query.bindValue(":VID", kVideoFile);
1036  if (!all)
1037  query.bindValue(":ID", FS::DbId(id));
1038 
1039  if (!query.exec())
1040  {
1041  MythDB::DBError(DBLOC, query);
1042  }
1043  else if (query.next())
1044  {
1045  dirs += query.value(0).toInt();
1046  pics += query.value(1).toInt();
1047  videos += query.value(2).toInt();
1048  sizeKb += query.value(3).toInt();
1049  }
1050 }
1051 
1052 
1057 {
1058  // Be has a single SG device
1060 }
1061 
1062 
1067  : ImageDb(QString("`%1_%2`").arg(DB_TABLE, gCoreContext->GetHostName())),
1068  m_DbExists(false)
1069 {
1070  // Remove any table leftover from a previous FE crash
1071  DropTable();
1072 }
1073 
1074 
1079 {
1080  MSqlQuery query(MSqlQuery::InitCon());
1081  query.prepare(QString("DROP TABLE IF EXISTS %1;").arg(m_table));
1082  if (query.exec())
1083  m_DbExists = false;
1084  else
1085  MythDB::DBError(DBLOC, query);
1086 }
1087 
1088 
1093 {
1094  if (m_DbExists)
1095  return true;
1096 
1097  MSqlQuery query(MSqlQuery::InitCon());
1098 
1099  // Create temporary table
1100  query.prepare(QString("CREATE TABLE %1 LIKE " DB_TABLE ";").arg(m_table));
1101  if (query.exec())
1102  {
1103  // Store it in memory only
1104  query.prepare(QString("ALTER TABLE %1 ENGINE = MEMORY;").arg(m_table));
1105  if (query.exec())
1106  {
1107  m_DbExists = true;
1108  LOG(VB_FILE, LOG_DEBUG, QString("Created Db table %1").arg(m_table));
1109  return true;
1110  }
1111  }
1112  MythDB::DBError(DBLOC, query);
1113 
1114  // Clean up after failure
1115  query.prepare(QString("DROP TABLE IF EXISTS %1;").arg(m_table));
1116  query.exec();
1117  return false;
1118 }
1119 
1120 
1124 class ReadMetaThread : public QRunnable
1125 {
1126 public:
1127  ReadMetaThread(ImagePtrK im, const QString &path)
1128  : m_im(std::move(im)), m_path(path) {}
1129 
1130  void run() override // QRunnable
1131  {
1132  QStringList tags;
1133  QString orientation, size;
1134 
1135  // Read metadata for files only
1136  if (m_im->IsFile())
1137  {
1138  ImageMetaData *metadata = (m_im->m_type == kVideoFile)
1141  tags = metadata->GetAllTags();
1142  orientation = Orientation(m_im->m_orientation).Description();
1143  size = ImageAdapterBase::FormatSize(m_im->m_size / 1024);
1144  delete metadata;
1145  }
1146 
1147  // Add identifier at front
1148  tags.prepend(QString::number(m_im->m_id));
1149 
1150  // Include file info
1151  tags << ImageMetaData::ToString(EXIF_MYTH_HOST, "Host",
1153  tags << ImageMetaData::ToString(EXIF_MYTH_PATH, "Path",
1155  tags << ImageMetaData::ToString(EXIF_MYTH_NAME, "Name",
1157  tags << ImageMetaData::ToString(EXIF_MYTH_SIZE, "Size", size);
1158  tags << ImageMetaData::ToString(EXIF_MYTH_ORIENT, "Orientation",
1159  orientation);
1160 
1161  MythEvent me("IMAGE_METADATA", tags);
1162  gCoreContext->SendEvent(me);
1163  }
1164 
1165 private:
1167  QString m_path;
1168 };
1169 
1170 
1179 template <class DBFS>
1180 QStringList ImageHandler<DBFS>::HandleGetMetadata(const QString &id) const
1181 {
1182  // Find image in DB
1183  ImageList files, 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  ReadMetaThread *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, dirs;
1219  if (DBFS::GetImages(id, files, dirs) != 1)
1220  RESULT_ERR("Image not found", QString("Image %1 not in Db").arg(id))
1221 
1222  ImagePtr im = files.isEmpty() ? dirs[0] : files[0];
1223 
1224  // Find file
1225  QString oldPath = DBFS::GetAbsFilePath(im);
1226  if (oldPath.isEmpty())
1227  RESULT_ERR("Image not found",
1228  QString("File %1 not found").arg(im->m_filePath))
1229 
1230  // Generate new filename
1231  QFileInfo oldFi = QFileInfo(oldPath);
1232  QString newName = im->IsDirectory()
1233  ? newBase : QString("%1.%2").arg(newBase, oldFi.suffix());
1234 
1235  im->m_filePath = DBFS::ConstructPath(DBFS::PathOf(im->m_filePath), newName);
1236 
1237  // Ensure no SG duplicate files are created. (Creating clone dirs is ok)
1238  if (im->IsFile())
1239  {
1240  QString existPath = DBFS::GetAbsFilePath(im);
1241  if (!existPath.isEmpty())
1242  RESULT_ERR("Filename already used",
1243  QString("Renaming %1 to %2 will create a duplicate of %3")
1244  .arg(oldPath, im->m_filePath, existPath))
1245  }
1246 
1247  // Rename file or directory
1248  QString newPath = oldFi.dir().absoluteFilePath(newName);
1249  if (!QFile::rename(oldPath, newPath))
1250  RESULT_ERR("Rename failed",
1251  QString("Rename of %1 -> %2 failed").arg(oldPath, newPath))
1252 
1253  if (im->IsDirectory())
1254  {
1255  // Dir name change affects path of all sub-dirs & files and their thumbs
1256  HandleScanRequest("START");
1257  }
1258  else // file
1259  {
1260  // Update db
1261  DBFS::UpdateDbImage(*im);
1262 
1263  // Image is modified, not deleted
1264  QStringList mesg("");
1265  mesg << QString::number(im->m_id);
1266 
1267  // Rename thumbnail.
1268  m_thumbGen->MoveThumbnail(im);
1269 
1270  // Notify clients of changed image
1271  DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1272  }
1273  RESULT_OK(QString("Renamed %1 -> %2").arg(oldPath, newPath))
1274 }
1275 
1276 
1284 template <class DBFS>
1285 QStringList ImageHandler<DBFS>::HandleDelete(const QString &ids) const
1286 {
1287  // Get subtree of all files
1288  ImageList files, dirs;
1289  // Dirs will be in depth-first order, (subdirs after parent)
1290  DBFS::GetDescendants(ids, files, dirs);
1291 
1292  // Remove files from filesystem first
1293  RemoveFiles(files);
1294  // ... then dirs, which should now be empty
1295  RemoveFiles(dirs);
1296 
1297  // Fail if nothing deleted
1298  if (files.isEmpty() && dirs.isEmpty())
1299  RESULT_ERR("Delete failed", QString("Delete of %1 failed").arg(ids))
1300 
1301  // Update Db
1302  DBFS::RemoveFromDB(files + dirs);
1303 
1304  // Clean up thumbnails
1305  QStringList mesg(m_thumbGen->DeleteThumbs(files));
1306 
1307  // Notify clients of deleted ids
1308  DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1309 
1310  return QStringList("OK");
1311 }
1312 
1313 
1326 template <class DBFS>
1327 QStringList ImageHandler<DBFS>::HandleDbCreate(QStringList defs) const
1328 {
1329  if (defs.isEmpty())
1330  RESULT_ERR("Copy Failed", "Empty defs")
1331 
1332  // First item is the field seperator
1333  const QString separator = defs.takeFirst();
1334 
1335  // Convert cover ids to their new equivalent. Map<source id, new id>
1336  // Dirs follow their children so new cover ids will be defined before they
1337  // are used
1338  QHash<QString, int> idMap;
1339 
1340  // Create skeleton Db images using copied settings.
1341  // Scanner will update other attributes
1342  ImageItem im;
1343  foreach (const QString &def, defs)
1344  {
1345  QStringList aDef = def.split(separator);
1346 
1347  // Expects id, type, path, hidden, orientation, cover
1348  if (aDef.size() != 6)
1349  {
1350  // Coding error
1351  LOG(VB_GENERAL, LOG_ERR,
1352  LOC + QString("Bad definition: (%1)").arg(def));
1353  continue;
1354  }
1355 
1356  LOG(VB_FILE, LOG_DEBUG, LOC + QString("Creating %1").arg(aDef.join(",")));
1357 
1358  im.m_type = aDef[1].toInt();
1359  im.m_filePath = aDef[2];
1360  im.m_isHidden = (aDef[3].toInt() != 0);
1361  im.m_orientation = aDef[4].toInt();
1362  im.m_userThumbnail = idMap.value(aDef[5]);
1363 
1364  // Don't insert duplicate filepaths
1365  int newId = DBFS::InsertDbImage(im, true);
1366 
1367  // Record old->new id map in case it's being used as a cover
1368  idMap.insert(aDef[0], newId);
1369  }
1370  HandleScanRequest("START");
1371 
1372  RESULT_OK("Created Db images")
1373 }
1374 
1375 
1385 template <class DBFS>
1386 QStringList ImageHandler<DBFS>::HandleDbMove(const QString &ids,
1387  const QString &srcPath,
1388  QString destPath) const
1389 {
1390  // Sanity check new path
1391  if (destPath.contains(".."))
1392  RESULT_ERR("Invalid path", QString("Invalid path %1").arg(destPath))
1393 
1394  // Get subtrees of renamed files
1395  ImageList images, dirs, files;
1396  bool ok = DBFS::GetDescendants(ids, files, dirs);
1397  images << dirs << files;
1398 
1399  if (!ok || images.isEmpty())
1400  RESULT_ERR("Image not found", QString("Images %1 not in Db").arg(ids))
1401 
1402  if (!destPath.isEmpty() && !destPath.endsWith(QChar('/')))
1403  destPath.append("/");
1404 
1405  // Update path of images only. Scanner will repair parentId
1406  foreach (const ImagePtr &im, images)
1407  {
1408  QString old = im->m_filePath;
1409 
1410  if (srcPath.isEmpty())
1411  {
1412  // Image in SG root
1413  im->m_filePath.prepend(destPath);
1414  }
1415  else if (im->m_filePath.startsWith(srcPath))
1416  {
1417  // All other images
1418  im->m_filePath.replace(srcPath, destPath);
1419  }
1420  else
1421  {
1422  // Coding error
1423  LOG(VB_GENERAL, LOG_ERR,
1424  LOC + QString("Bad image: (%1 -> %2)").arg(srcPath, destPath));
1425  continue;
1426  }
1427 
1428  LOG(VB_FILE, LOG_DEBUG,
1429  LOC + QString("Db Renaming %1 -> %2").arg(old, im->m_filePath));
1430 
1431  DBFS::UpdateDbImage(*im);
1432 
1433  // Rename thumbnail
1434  if (im->IsFile())
1435  m_thumbGen->MoveThumbnail(im);
1436  }
1437  HandleScanRequest("START");
1438 
1439  RESULT_OK(QString("Moved %1 from %2 -> %3").arg(ids).arg(srcPath, destPath))
1440 }
1441 
1442 
1450 template <class DBFS>
1451 QStringList ImageHandler<DBFS>::HandleHide(bool hide, const QString &ids) const
1452 {
1453  if (!DBFS::SetHidden(hide, ids))
1454  RESULT_ERR("Hide failed", QString("Db hide failed for %1").arg(ids))
1455 
1456  // Send changed ids only (none deleted)
1457  QStringList mesg = QStringList("") << ids;
1458  DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1459 
1460  RESULT_OK(QString("Images %1 now %2hidden").arg(ids, hide ? "" : "un"))
1461 }
1462 
1463 
1472 template <class DBFS>
1473 QStringList ImageHandler<DBFS>::HandleTransform(int transform,
1474  const QString &ids) const
1475 {
1476  if (transform < kResetToExif || transform > kFlipVertical)
1477  RESULT_ERR("Transform failed", QString("Bad transform %1").arg(transform))
1478 
1479  ImageList files, dirs;
1480  if (DBFS::GetImages(ids, files, dirs) < 1 || files.isEmpty())
1481  RESULT_ERR("Image not found", QString("Images %1 not in Db").arg(ids))
1482 
1483  // Update db
1484  foreach (ImagePtr im, files)
1485  {
1486  int old = im->m_orientation;
1487  im->m_orientation = Orientation(im->m_orientation).Transform(transform);
1488 
1489  // Update Db
1490  if (DBFS::SetOrientation(im->m_id, im->m_orientation))
1491  {
1492  LOG(VB_FILE, LOG_DEBUG, LOC + QString("Transformed %1 from %2 to %3")
1493  .arg(im->m_filePath).arg(old).arg(im->m_orientation));
1494  }
1495  }
1496 
1497  // Images are changed, not deleted
1498  QStringList mesg("");
1499 
1500  // Clean up thumbnails
1501  mesg << m_thumbGen->DeleteThumbs(files);
1502 
1503  // Notify clients of changed images
1504  DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1505 
1506  return QStringList("OK");
1507 }
1508 
1509 
1518 template <class DBFS>
1519 QStringList ImageHandler<DBFS>::HandleDirs(const QString &destId,
1520  bool rescan,
1521  const QStringList &relPaths) const
1522 {
1523  // Find image in DB
1524  ImageList files, dirs;
1525  if (DBFS::GetImages(destId, files, dirs) != 1 || dirs.isEmpty())
1526  RESULT_ERR("Destination not found",
1527  QString("Image %1 not in Db").arg(destId))
1528 
1529  // Find dir. SG device (Photographs) uses most-free filesystem
1530  QString destPath = DBFS::GetAbsFilePath(dirs[0]);
1531  if (destPath.isEmpty())
1532  RESULT_ERR("Destination not found",
1533  QString("Dest dir %1 not found").arg(dirs[0]->m_filePath))
1534 
1535  QDir destDir(destPath);
1536  bool succeeded = false;
1537  foreach (const QString &relPath, relPaths)
1538  {
1539  // Validate dir name
1540  if (relPath.isEmpty() || relPath.contains("..") || relPath.startsWith(QChar('/')))
1541  continue;
1542 
1543  QString newPath = DBFS::ConstructPath(destDir.absolutePath(), relPath);
1544  if (!destDir.mkpath(relPath))
1545  {
1546  LOG(VB_GENERAL, LOG_ERR,
1547  LOC + QString("Failed to create dir %1").arg(newPath));
1548  continue;
1549  }
1550  LOG(VB_FILE, LOG_DEBUG, LOC + QString("Dir %1 created").arg(newPath));
1551  succeeded = true;
1552  }
1553 
1554  if (!succeeded)
1555  // Failures should only occur due to user input
1556  RESULT_ERR("Invalid Name", QString("Invalid name %1")
1557  .arg(relPaths.join(",")))
1558 
1559  if (rescan)
1560  // Rescan to detect new dir
1561  HandleScanRequest("START");
1562 
1563  return QStringList("OK");
1564 }
1565 
1566 
1573 template <class DBFS>
1574 QStringList ImageHandler<DBFS>::HandleCover(int dir, int cover) const
1575 {
1576  if (!DBFS::SetCover(dir, cover))
1577  RESULT_ERR("Set Cover failed",
1578  QString("Failed to set %1 to cover %2").arg(dir).arg(cover))
1579 
1580  // Image has changed, nothing deleted
1581  QStringList mesg = QStringList("") << QString::number(dir);
1582  DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1583 
1584  RESULT_OK(QString("Cover of %1 is now %2").arg(dir).arg(cover));
1585 }
1586 
1587 
1596 template <class DBFS>
1597 QStringList ImageHandler<DBFS>::HandleIgnore(const QString &exclusions) const
1598 {
1599  // Save new setting. FE will have already saved it but not cleared the cache
1600  gCoreContext->SaveSettingOnHost("GalleryIgnoreFilter", exclusions, nullptr);
1601 
1602  // Rescan
1603  HandleScanRequest("START");
1604 
1605  RESULT_OK(QString("Using exclusions '%1'").arg(exclusions))
1606 }
1607 
1608 
1616 template <class DBFS>
1617 QStringList ImageHandler<DBFS>::HandleScanRequest(const QString &command,
1618  int devId) const
1619 {
1620  if (!m_scanner)
1621  RESULT_ERR("Missing Scanner", "Missing Scanner");
1622 
1623  if (command == "START")
1624  {
1625  // Must be dormant to start a scan
1626  if (m_scanner->IsScanning())
1627  RESULT_ERR("", "Scanner is busy");
1628 
1629  m_scanner->ChangeState(true);
1630  RESULT_OK("Scan requested");
1631  }
1632  else if (command == "STOP")
1633  {
1634  // Must be scanning to interrupt
1635  if (!m_scanner->IsScanning())
1636  RESULT_ERR("Scanner not running", "Scanner not running");
1637 
1638  m_scanner->ChangeState(false);
1639  RESULT_OK("Terminate scan requested");
1640  }
1641  else if (command == "QUERY")
1642  {
1643  return QStringList("OK") << m_scanner->GetProgress();
1644  }
1645  else if (command.startsWith(QString("DEVICE")))
1646  {
1647  m_scanner->EnqueueClear(devId, command);
1648  RESULT_OK(QString("Clearing device %1 %2").arg(command).arg(devId))
1649  }
1650  RESULT_ERR("Unknown command", QString("Unknown command %1").arg(command));
1651 }
1652 
1653 
1661 template <class DBFS>
1663 (const QStringList &message) const
1664 {
1665  if (message.size() != 2)
1666  RESULT_ERR("Unknown Command",
1667  QString("Bad request: %1").arg(message.join("|")))
1668 
1669  int priority = message.at(0).toInt()
1671 
1672  // get specific image details from db
1673  ImageList files, dirs;
1674  DBFS::GetImages(message.at(1), files, dirs);
1675 
1676  foreach (const ImagePtrK &im, files)
1677  // notify clients when done; highest priority
1678  m_thumbGen->CreateThumbnail(im, priority, true);
1679 
1680  return QStringList("OK");
1681 }
1682 
1683 
1693 template <class DBFS>
1695 {
1696  QMutableListIterator<ImagePtr> it(images);
1697  it.toBack();
1698  while (it.hasPrevious())
1699  {
1700  ImagePtrK im = it.previous();
1701 
1702  // Remove file or directory
1703  QString absFilename = DBFS::GetAbsFilePath(im);
1704 
1705  bool success = !absFilename.isEmpty()
1706  && (im->IsFile() ? QFile::remove(absFilename)
1707  : QDir::root().rmdir(absFilename));
1708  if (success)
1709  LOG(VB_FILE, LOG_DEBUG, LOC + QString("Deleted %1").arg(absFilename));
1710  else
1711  {
1712  LOG(VB_GENERAL, LOG_ERR, LOC +
1713  QString("Can't delete %1").arg(absFilename));
1714  // Remove from list
1715  it.remove();
1716  }
1717  }
1718 }
1719 
1720 
1727 {
1728  switch (type)
1729  {
1730  case kPicOnly: return QString("AND type != %1").arg(kVideoFile);
1731  case kVideoOnly: return QString("AND type != %1").arg(kImageFile);
1732  case kPicAndVideo: return "";
1733  }
1734  return "";
1735 }
1736 
1737 
1744 {
1745  m_refineClause = QString("%2 %3 "
1746  "ORDER BY "
1747  "CASE WHEN type <= %1 THEN %4, "
1748  "CASE WHEN type > %1 THEN %5 ")
1749  .arg(kDirectory)
1750  .arg(m_showHidden ? "" : "AND hidden = 0",
1754 }
1755 
1756 
1763 {
1764  // prepare the sorting statement
1765  switch (order)
1766  {
1767  default:
1768  case kSortByNameAsc: return "name END ASC";
1769  case kSortByNameDesc: return "name END DESC";
1770  case kSortByModTimeAsc: return "modtime END ASC";
1771  case kSortByModTimeDesc: return "modtime END DESC";
1772  case kSortByExtAsc: return "extension END ASC, name ASC";
1773  case kSortByExtDesc: return "extension END DESC, name DESC";
1774  case kSortBySizeAsc: return "size END ASC, name ASC";
1775  case kSortBySizeDesc: return "size END DESC, name DESC";
1776  case kSortByDateAsc: return "IF(date=0, modtime, date) END ASC";
1777  case kSortByDateDesc: return "IF(date=0, modtime, date) END DESC";
1778  }
1779 }
1780 
1781 
1791  ImageList &files, ImageList &dirs) const
1792 {
1793  // Only Root node will invoke both Db queries but result set will be small
1794  // For Root the SG is always ordered before local devices
1795  // Root node has no Db entry so the 2 queries will not overwrite the parent.
1796  int count = 0;
1797  if (!ImageItem::IsLocalId(id))
1798  count = m_remote->GetDirectory(id, parent, files, dirs, m_refineClause);
1800  count += ImageHandler::GetDirectory(id, parent, files, dirs, m_refineClause);
1801 
1802  if (id == GALLERY_DB_ID)
1803  {
1804  // Add a Root node
1805  parent = ImagePtr(new ImageItem(GALLERY_DB_ID));
1806  parent->m_parentId = GALLERY_DB_ID;
1807  parent->m_type = kDevice;
1808 
1809  ++count;
1810  }
1811  return count;
1812 }
1813 
1814 
1823  ImageList &files, ImageList &dirs) const
1824 {
1825  // Ids are either all local or all remote. GALLERY_DB_ID not valid
1826  StringPair lists = ImageItem::PartitionIds(ids);
1827 
1828  if (!lists.second.isEmpty())
1829  return m_remote->GetImages(lists.second, files, dirs, m_refineClause);
1830  if (m_DbExists && !lists.first.isEmpty())
1831  return ImageHandler::GetImages(lists.first, files, dirs, m_refineClause);
1832  return 0;
1833 }
1834 
1835 
1843 int ImageDbReader::GetChildren(int id, ImageList &files, ImageList &dirs) const
1844 {
1845  int count = 0;
1846  if (!ImageItem::IsLocalId(id))
1847  count = m_remote->GetChildren(QString::number(id), files, dirs,
1848  m_refineClause);
1850  count += ImageHandler::GetChildren(QString::number(id), files, dirs,
1851  m_refineClause);
1852  return count;
1853 }
1854 
1855 
1863  ImageList &files, ImageList &dirs) const
1864 {
1865  // Ids are either all local or all remote
1866  StringPair lists = ImageItem::PartitionIds(ids);
1867 
1868  if (!lists.second.isEmpty())
1869  m_remote->GetDescendants(lists.second, files, dirs);
1870  if (m_DbExists && !lists.first.isEmpty())
1871  ImageHandler::GetDescendants(lists.first, files, dirs);
1872 }
1873 
1874 
1881 void ImageDbReader::GetImageTree(int id, ImageList &files) const
1882 {
1883  if (!ImageItem::IsLocalId(id))
1884  m_remote->GetImageTree(id, files, m_refineClause);
1886  ImageHandler::GetImageTree(id, files, m_refineClause);
1887 }
1888 
1889 
1898 void ImageDbReader::GetDescendantCount(int id, int &dirs, int &pics,
1899  int &videos, int &sizeKb) const
1900 {
1901  if (id == GALLERY_DB_ID)
1902  {
1903  // Sum both unfiltered tables
1904  m_remote->GetDescendantCount(id, true, dirs, pics, videos, sizeKb);
1905  if (m_DbExists)
1906  ImageHandler::GetDescendantCount(id, true, dirs, pics, videos, sizeKb);
1907  }
1908  else if (!ImageItem::IsLocalId(id))
1909  {
1910  // Don't filter on SG path (it's blank)
1912  dirs, pics, videos, sizeKb);
1913  }
1914  else if (m_DbExists)
1915  {
1916  // Always filter on device/dir
1917  ImageHandler::GetDescendantCount(id, false, dirs, pics, videos, sizeKb);
1918  }
1919 }
1920 
1921 
1926 
1927 
1933 {
1934  if (!s_instance)
1935  s_instance = new ImageManagerBe();
1936  return s_instance;
1937 }
1938 
1939 
1945 {
1946  if (!s_instance)
1947  // Use saved settings
1949  (gCoreContext->GetNumSetting("GalleryImageOrder"),
1950  gCoreContext->GetNumSetting("GalleryDirOrder"),
1951  gCoreContext->GetBoolSetting("GalleryShowHidden"),
1952  gCoreContext->GetNumSetting("GalleryShowType"),
1953  gCoreContext->GetSetting("GalleryDateFormat"));
1954  return *s_instance;
1955 }
1956 
1957 
1966 void ImageManagerFe::CreateThumbnails(const ImageIdList &ids, bool forFolder)
1967 {
1968  // Split images into <locals, remotes>
1969  StringPair lists = ImageItem::PartitionIds(ids);
1970 
1971  if (!lists.second.isEmpty())
1972  {
1973  LOG(VB_FILE, LOG_DEBUG, LOC +
1974  QString("Sending CREATE_THUMBNAILS %1 (forFolder %2)")
1975  .arg(lists.second).arg(forFolder));
1976 
1977  QStringList message;
1978  message << QString::number(forFolder) << lists.second;
1979  gCoreContext->SendEvent(MythEvent("CREATE_THUMBNAILS", message));
1980  }
1981 
1982  if (!lists.first.isEmpty())
1983  {
1984  LOG(VB_FILE, LOG_DEBUG, LOC +
1985  QString("Creating local thumbnails %1 (forFolder %2)")
1986  .arg(lists.first).arg(forFolder));
1987 
1988  QStringList message;
1989  message << QString::number(forFolder) << lists.first;
1990  HandleCreateThumbnails(message);
1991  }
1992 }
1993 
1994 
2001 QString ImageManagerFe::ScanImagesAction(bool start, bool local)
2002 {
2003  QStringList command;
2004  command << (start ? "START" : "STOP");
2005 
2006  if (!local)
2007  {
2008  command.push_front("IMAGE_SCAN");
2009  bool ok = gCoreContext->SendReceiveStringList(command, true);
2010  return ok ? "" : command[1];
2011  }
2012 
2013  // Create database on first scan
2014  if (!CreateTable())
2015  return "Couldn't create database";
2016 
2017  QStringList err = HandleScanRequest(command[0]);
2018  return err[0] == "OK" ? "" : err[1];
2019 }
2020 
2021 
2028 {
2029  QStringList strList;
2030  strList << "IMAGE_SCAN" << "QUERY";
2031 
2032  if (!gCoreContext->SendReceiveStringList(strList))
2033  {
2034  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Scan query failed : %1")
2035  .arg(strList.join(",")));
2036  }
2037  return strList;
2038 }
2039 
2040 
2047 QString ImageManagerFe::HideFiles(bool hidden, const ImageIdList &ids)
2048 {
2049  // Split images into <locals, remotes>
2050  StringPair lists = ImageItem::PartitionIds(ids);
2051  QString result = "";
2052 
2053  if (!lists.second.isEmpty())
2054  {
2055  QStringList message;
2056  message << "IMAGE_HIDE" << QString::number(hidden) << lists.second;
2057 
2058  if (!gCoreContext->SendReceiveStringList(message, true))
2059  result = message[1];
2060  }
2061 
2062  if (!lists.first.isEmpty())
2063  {
2064  QStringList err = HandleHide(hidden, lists.first);
2065  if (err[0] != "OK")
2066  result = err[1];
2067  }
2068  return result;
2069 }
2070 
2071 
2079  const ImageIdList &ids)
2080 {
2081  // Split images into <locals, remotes>
2082  StringPair lists = ImageItem::PartitionIds(ids);
2083  QString result = "";
2084 
2085  if (!lists.second.isEmpty())
2086  {
2087  QStringList message;
2088  message << "IMAGE_TRANSFORM" << QString::number(transform) << lists.second;
2089 
2090  if (!gCoreContext->SendReceiveStringList(message, true))
2091  result = message[1];
2092  }
2093 
2094  if (!lists.first.isEmpty())
2095  {
2096  QStringList err = HandleTransform(transform, lists.first);
2097  if (err[0] != "OK")
2098  result = err[1];
2099  }
2100  return result;
2101 }
2102 
2103 
2110 QString ImageManagerFe::SetCover(int parent, int cover)
2111 {
2112  if (!ImageItem::IsLocalId(parent))
2113  {
2114  QStringList message;
2115  message << "IMAGE_COVER" << QString::number(parent) << QString::number(cover);
2116 
2117  bool ok = gCoreContext->SendReceiveStringList(message, true);
2118  return ok ? "" : message[1];
2119  }
2120 
2121  QStringList err = HandleCover(parent, cover);
2122  return err[0] == "OK" ? "" : err[1];
2123 }
2124 
2125 
2131 {
2132  if (ImageItem::IsLocalId(id))
2133  HandleGetMetadata(QString::number(id));
2134  else
2135  gCoreContext->SendEvent(MythEvent("IMAGE_GET_METADATA", QString::number(id)));
2136 }
2137 
2138 
2141 {
2142  QStringList message("IMAGE_SCAN");
2143  message << "DEVICE CLEAR ALL";
2144  gCoreContext->SendReceiveStringList(message, true);
2145 }
2146 
2147 
2154 QString ImageManagerFe::IgnoreDirs(const QString &excludes)
2155 {
2156  QStringList message("IMAGE_IGNORE");
2157  message << excludes;
2158  bool ok = gCoreContext->SendReceiveStringList(message, true);
2159  return ok ? "" : message[1];
2160 }
2161 
2162 
2170 QString ImageManagerFe::MakeDir(int parent, const QStringList &names, bool rescan)
2171 {
2172  QString destId = QString::number(parent);
2173 
2174  if (!ImageItem::IsLocalId(parent))
2175  {
2176  QStringList message("IMAGE_CREATE_DIRS");
2177  message << destId << QString::number(rescan) << names;
2178  bool ok = gCoreContext->SendReceiveStringList(message, true);
2179  return ok ? "" : message[1];
2180  }
2181  QStringList err = HandleDirs(destId, rescan, names);
2182  return (err[0] == "OK") ? "" : err[1];
2183 }
2184 
2185 
2192 QString ImageManagerFe::RenameFile(const ImagePtrK& im, const QString &name)
2193 {
2194  if (!im->IsLocal())
2195  {
2196  QStringList message("IMAGE_RENAME");
2197  message << QString::number(im->m_id) << name;
2198  bool ok = gCoreContext->SendReceiveStringList(message, true);
2199  return ok ? "" : message[1];
2200  }
2201  QStringList err = HandleRename(QString::number(im->m_id), name);
2202  return (err[0] == "OK") ? "" : err[1];
2203 }
2204 
2205 
2212 QString ImageManagerFe::CreateImages(int destId, const ImageListK &images)
2213 {
2214  if (images.isEmpty())
2215  return "";
2216 
2217  // Define field seperator & include it in message
2218  const QString seperator("...");
2219  QStringList imageDefs(seperator);
2220  ImageIdList ids;
2221  foreach (const ImagePtrK &im, images)
2222  {
2223  ids << im->m_id;
2224 
2225  // Copies preserve hide state, orientation & cover
2226  QStringList aDef;
2227  aDef << QString::number(im->m_id)
2228  << QString::number(im->m_type)
2229  << im->m_filePath
2230  << QString::number(im->m_isHidden)
2231  << QString::number(im->m_orientation)
2232  << QString::number(im->m_userThumbnail);
2233 
2234  imageDefs << aDef.join(seperator);
2235  }
2236 
2237  // Images are either all local or all remote
2238  if (ImageItem::IsLocalId(destId))
2239  {
2240  QStringList err = HandleDbCreate(imageDefs);
2241  return (err[0] == "OK") ? "" : err[1];
2242  }
2243  imageDefs.prepend("IMAGE_COPY");
2244  bool ok = gCoreContext->SendReceiveStringList(imageDefs, true);
2245  return ok ? "" : imageDefs[1];
2246 }
2247 
2248 
2256 QString ImageManagerFe::MoveDbImages(const ImagePtrK& destDir, ImageListK &images,
2257  const QString &srcPath)
2258 {
2259  QStringList idents;
2260  foreach (const ImagePtrK &im, images)
2261  idents << QString::number(im->m_id);
2262 
2263  // Images are either all local or all remote
2264  if (destDir->IsLocal())
2265  {
2266  QStringList err = HandleDbMove(idents.join(","), srcPath,
2267  destDir->m_filePath);
2268  return (err[0] == "OK") ? "" : err[1];
2269  }
2270 
2271  QStringList message("IMAGE_MOVE");
2272  message << idents.join(",") << srcPath << destDir->m_filePath;
2273  bool ok = gCoreContext->SendReceiveStringList(message, true);
2274  return ok ? "" : message[1];
2275 }
2276 
2277 
2284 {
2285  StringPair lists = ImageItem::PartitionIds(ids);
2286 
2287  QString result = "";
2288  if (!lists.second.isEmpty())
2289  {
2290  QStringList message("IMAGE_DELETE");
2291  message << lists.second;
2292 
2293  bool ok = gCoreContext->SendReceiveStringList(message, true);
2294  if (!ok)
2295  result = message[1];
2296  }
2297  if (!lists.first.isEmpty())
2298  {
2299  QStringList err = HandleDelete(lists.first);
2300  if (err[0] != "OK")
2301  result = err[1];
2302  }
2303  return result;
2304 }
2305 
2306 
2313 QString ImageManagerFe::LongDateOf(const ImagePtrK& im) const
2314 {
2315  if (im->m_id == GALLERY_DB_ID)
2316  return "";
2317 
2318 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
2319  uint secs = 0;
2320 #else
2321  qint64 secs = 0;
2322 #endif
2324 
2325  if (im->m_date > 0)
2326  {
2327  secs = im->m_date;
2328  format |= MythDate::kTime;
2329  }
2330  else
2331  secs = im->m_modTime;
2332 
2333 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
2334  return MythDate::toString(QDateTime::fromTime_t(secs), format);
2335 #else
2336  return MythDate::toString(QDateTime::fromSecsSinceEpoch(secs), format);
2337 #endif
2338 }
2339 
2340 
2347 QString ImageManagerFe::ShortDateOf(const ImagePtrK& im) const
2348 {
2349  if (im->m_id == GALLERY_DB_ID)
2350  return "";
2351 
2352 #if QT_VERSION < QT_VERSION_CHECK(5,8,0)
2353  uint secs(im->m_date > 0 ? im->m_date : im->m_modTime);
2354  return QDateTime::fromTime_t(secs).date().toString(m_dateFormat);
2355 #else
2356  qint64 secs(im->m_date > 0 ? im->m_date : im->m_modTime);
2357  return QDateTime::fromSecsSinceEpoch(secs).date().toString(m_dateFormat);
2358 #endif
2359 }
2360 
2361 
2368 {
2369  if (im.m_id == GALLERY_DB_ID)
2370  return tr("Gallery");
2371  if (im.m_id == PHOTO_DB_ID)
2372  return tr("Photographs");
2373  return im.IsLocal() ? DeviceName(im.m_device)
2374  : m_remote->DeviceName(im.m_device);
2375 }
2376 
2377 
2385 QString ImageManagerFe::CrumbName(ImageItemK &im, bool getPath) const
2386 {
2387  if (im.IsDevice())
2388  return DeviceCaption(im);
2389 
2390  if (!getPath)
2391  return im.m_baseName;
2392 
2393  QString dev, path(im.m_filePath);
2394 
2395  if (im.IsLocal())
2396  {
2397  // Replace local mount path with device name
2398  path.remove(0, DeviceMount(im.m_device).size());
2399  dev = DeviceName(im.m_device);
2400  }
2401  return dev + path.replace("/", " > ");
2402 }
2403 
2404 
2405 void ImageManagerFe::CloseDevices(int devId, bool eject)
2406 {
2407  QString reason = (devId == DEVICE_INVALID)
2408  ? "DEVICE CLOSE ALL"
2409  : eject ? "DEVICE EJECT" : "DEVICE REMOVE";
2410  HandleScanRequest(reason, devId);
2411 }
2412 
2413 
2419 {
2421  if (!monitor)
2422  return false;
2423 
2424  // Detect all local media
2425  QList<MythMediaDevice*> devices
2427 
2428  foreach (MythMediaDevice* dev, devices)
2429  {
2430  if (monitor->ValidateAndLock(dev) && dev->isUsable())
2431  OpenDevice(dev->getDeviceModel(), dev->getMountPath(), dev);
2432  else
2433  monitor->Unlock(dev);
2434  }
2435 
2436  if (DeviceCount() > 0)
2437  {
2438  // Close devices that are no longer present
2439  foreach (int devId, GetAbsentees())
2440  CloseDevices(devId);
2441 
2442  // Start local scan
2443  QString err = ScanImagesAction(true, true);
2444  if (!err.isEmpty())
2445  LOG(VB_GENERAL, LOG_ERR, LOC + err);
2446  }
2447  return DeviceCount() > 0;
2448 }
2449 
2450 
2456 {
2458 
2459  if (!event || !monitor)
2460  return;
2461 
2462  MythMediaDevice *dev = event->getDevice();
2463  if (!dev)
2464  return;
2465 
2466  MythMediaType type = dev->getMediaType();
2467  MythMediaStatus status = dev->getStatus();
2468 
2469  LOG(VB_FILE, LOG_DEBUG, LOC +
2470  QString("Media event for %1 (%2) at %3, type %4, status %5 (was %6)")
2471  .arg(dev->getDeviceModel(), dev->getVolumeID(), dev->getMountPath())
2472  .arg(type).arg(status).arg(event->getOldStatus()));
2473 
2475  {
2476  LOG(VB_FILE, LOG_DEBUG, LOC +
2477  QString("Ignoring event - wrong type %1").arg(type));
2478  return;
2479  }
2480 
2481  if (status == MEDIASTAT_USEABLE || status == MEDIASTAT_MOUNTED)
2482  {
2483  // New device. Lock it & scan
2484  if (monitor->ValidateAndLock(dev))
2485  {
2486  OpenDevice(dev->getDeviceModel(), dev->getMountPath(), dev);
2487  ScanImagesAction(true, true);
2488  }
2489  else
2490  monitor->Unlock(dev);
2491  return;
2492  }
2493 
2494  // Device has disappeared
2495  int devId = LocateMount(dev->getMountPath());
2496  if (devId != DEVICE_INVALID)
2497  CloseDevices(devId);
2498 }
2499 
2500 
2502 {
2503  QTemporaryDir *tmp = new QTemporaryDir(QDir::tempPath() % "/" IMPORTDIR "-XXXXXX");
2504  if (!tmp->isValid())
2505  {
2506  delete tmp;
2507  return "";
2508  }
2509 
2510  QString time(QDateTime::currentDateTime().toString("mm:ss"));
2511  OpenDevice("Import " + time, tmp->path(), nullptr, tmp);
2512  return tmp->path();
2513 }
2514 
2515 
2516 // Must define the valid template implementations to generate code for the
2517 // instantiations (as they are defined in the cpp rather than header).
2518 // Otherwise the linker will fail with undefined references...
2519 template class ImageDb<ImageAdapterSg>;
2520 template class ImageDb<ImageAdapterLocal>;
2521 template class ImageHandler<ImageDbSg>;
2522 template class ImageHandler<ImageDbLocal>;
MythMediaType getMediaType() const
Definition: mythmedia.h:91
void DropTable()
Remove local image table.
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:782
A video.
Definition: imagetypes.h:39
static QString FormatSize(int sizeKib)
Definition: imagemanager.h:139
QString DeviceName(int devId) const
Get model name of the device.
QStringList HandleDirs(const QString &, bool rescan, const QStringList &relPaths) const
Creates new image directories.
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
QString m_thumbPath
Myth URL of image (abs filepath for locals)
Definition: imagetypes.h:125
const QString & getDevicePath() const
Definition: mythmedia.h:61
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:863
QStringList GetDirList(void) const
Definition: storagegroup.h:23
static Device kNullDevice
Abstract class for image metadata.
Definition: imagemetadata.h:95
int ReadImages(ImageList &dirs, ImageList &files, const QString &selector) const
Read selected database images/dirs.
MythMediaType
Definition: mythmedia.h:24
QList< ThumbPair > m_thumbNails
Definition: imagetypes.h:126
void Notify(const QString &mesg, const QStringList &extra) const
Send local message to UI about local ids.
Encapsulates Exif orientation processing.
Definition: imagemetadata.h:58
bool RemoveFromDB(Bookmark *site)
VERBOSE_PREAMBLE Most true
Definition: verbosedefs.h:91
QString Description()
Generate text description of orientation.
QString ShortDateOf(const ImagePtrK &) const
Return a short datestamp for thumbnail captions.
void RemoveDirContents(const QString &path)
Clears all files and sub-dirs within a directory.
QStringList HandleDbMove(const QString &, const QString &, QString) const
Updates images that have been renamed.
int m_dirOrder
Display ordering of dirs.
Definition: imagemanager.h:436
QString m_url
Definition: imagetypes.h:124
#define DB_COLUMNS
QString toString(MarkTypes type)
void EjectMedia(const QString &path)
QHash< QString, ImagePtr > ImageHash
Definition: imagetypes.h:175
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:439
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:510
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:144
The image manager for use by Frontends.
Definition: imagemanager.h:452
std::vector< file_association > association_list
Definition: dbaccess.h:153
qint64 m_date
Image creation date, from Exif metadata.
Definition: imagetypes.h:114
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:100
const association_list & getList() const
Definition: dbaccess.cpp:811
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
QStringList HandleGetMetadata(const QString &) const
Read meta data for an image.
QString MakeDir(int, const QStringList &names, bool rescan=true)
Create directories.
Device(const QString &name, const QString &mount, MythMediaDevice *media=nullptr, QTemporaryDir *import=nullptr)
#define RESULT_ERR(ERR, MESG)
Database API.
Definition: imagemanager.h:270
Manages a collection of images.
ReadMetaThread(ImagePtrK im, const QString &path)
int size(void) const
Definition: mythdbcon.h:203
#define RESULT_OK(MESG)
#define EXIF_MYTH_SIZE
Definition: imagemetadata.h:37
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:148
QDir m_dirFilter
A pre-configured dir for reading image/video files.
Definition: imagemanager.h:171
int m_parentId
Id of parent dir.
Definition: imagetypes.h:103
ImageDbSg * m_remote
Remote database access.
Definition: imagemanager.h:434
Show Pictures & Videos.
Definition: imagemanager.h:74
QString m_refineClause
SQL clause for image filtering/ordering.
Definition: imagemanager.h:440
QString GetAbsFilePath(const ImagePtrK &im) const
Get absolute filepath for a remote image.
unsigned int uint
Definition: compat.h:140
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
bool m_showHidden
Whether hidden images are displayed.
Definition: imagemanager.h:438
ImageManagerFe(int order, int dirOrder, bool showAll, int showType, QString dateFormat)
Definition: imagemanager.h:497
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:131
const char * formats[8]
Definition: vbilut.cpp:190
StorageGroup m_sg
Images storage group.
Definition: imagemanager.h:262
int GetChildren(int id, ImageList &files, ImageList &dirs) const
Return (local or remote) images that are direct children of a dir.
QMap< int, QString > StringMap
Definition: imagetypes.h:62
bool IsLocal() const
Definition: imagetypes.h:134
static guint32 * tmp
Definition: goom_core.c:35
Exif date Latest -> Earliest.
Definition: imagetypes.h:54
QStringList RemoveFromDB(const ImageList &imList) const
Remove images/dirs from database.
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.
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:101
bool isPresent() const
void RemoveFiles(ImageList &) const
Deletes images and dirs from the filesystem.
QStringList HandleDelete(const QString &) const
Deletes images/dirs.
The image manager to be used by the Backend.
Definition: imagemanager.h:374
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:75
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:121
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:42
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:173
int m_fileOrder
Display ordering of pics/videos.
Definition: imagemanager.h:437
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:387
This class is used as a container for messages.
Definition: mythevent.h:16
#define TEMP_SUBDIR
Definition: imagemanager.h:61
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
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:85
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:139
#define EXIF_MYTH_ORIENT
Definition: imagemetadata.h:38
QVariant lastInsertId()
Return the id of the last inserted row.
Definition: mythdbcon.cpp:887
QString LongDateOf(const ImagePtrK &) const
Return a timestamp/datestamp for an image or dir.
ImageAdapterBase()
Constructor.
static void clear(SettingsMap &cache, SettingsMap &overrides, const QString &myKey)
Definition: mythdb.cpp:830
Default local time.
Definition: mythdate.h:19
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:161
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.
QStringList HandleCreateThumbnails(const QStringList &) const
Creates thumbnails on-demand.
QStringList HandleTransform(int, const QString &) const
Change orientation of pictures by applying a transformation.
#define THUMBNAIL_SUBDIR
Definition: imagemanager.h:63
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.
QString CreateImages(int, const ImageListK &images)
Copies database images (but not the files themselves).
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.
int Transform(int)
Adjust orientation to apply a transform to an image.
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
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:132
QString FindNextDirMostFree(void)
Reflect about horizontal axis.
Definition: imagemetadata.h:47
Default local time.
Definition: mythdate.h:16
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:507
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.
qint64 m_modTime
Filesystem modified datestamp.
Definition: imagetypes.h:108
const QString & getDeviceModel() const
Definition: mythmedia.h:67
virtual QStringList GetAllTags()=0
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:155
QString m_table
Db table name.
Definition: imagemanager.h:306
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
const char * name
Definition: ParseText.cpp:328
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:137
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:116
QString m_baseName
File/Dir name with extension (no path)
Definition: imagetypes.h:99
DeviceMap m_devices
Device store.
Definition: imagemanager.h:115
bool GetDescendants(const QString &ids, ImageList &files, ImageList &dirs) const
Return images and all of their descendants.
QPair< QString, QString > StringPair
Definition: imagetypes.h:60
QSharedPointer< ImageItemK > ImagePtrK
Definition: imagetypes.h:179
QList< ImagePtr > ImageList
Definition: imagetypes.h:174
#define EXIF_MYTH_PATH
Definition: imagemetadata.h:35
ImageDbSg()
SG database constructor.
static FileAssociations & getFileAssociation()
Definition: dbaccess.cpp:836
void DeviceEvent(MythMediaEvent *event)
Manage events for local devices.
Task to read all metadata from file.
A picture.
Definition: imagetypes.h:38
QStringList HandleRename(const QString &, const QString &) const
Change name of an image/dir.
QString DeleteFiles(const ImageIdList &)
Delete images.
static MThreadPool * globalInstance(void)
QStringList HandleDbCreate(QStringList) const
Creates images for files created by a copy operation.
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.
bool IsFile() const
Definition: imagetypes.h:133
int GetNumSetting(const QString &key, int defaultval=0)
Client request to display an image thumbnail.
Definition: imagethumbs.h:30
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:807
#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 MoveDbImages(const ImagePtrK &destDir, ImageListK &images, const QString &)
Moves database images (but not the files themselves).
QString FindFile(const QString &filename)
const QString & getMountPath() const
Definition: mythmedia.h:58
QSharedPointer< ImageItem > ImagePtr
Definition: imagetypes.h:173
QStringList m_videoFileExt
List of file extensions recognised as videos.
Definition: imagemanager.h:175
int m_device
Id of media device. Always 0 (SG) for remotes, 1+ for local devices.
Definition: imagetypes.h:102
void CloseDevices(int devId=DEVICE_INVALID, bool eject=false)
Client request to display a directory thumbnail.
Definition: imagethumbs.h:31
void Notify(const QString &mesg, const QStringList &extra) const
Send message to all clients about remote ids.
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:34
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
QStringList ScanQuery()
Returns storage group scanner status.
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:104
#define EXIF_MYTH_NAME
Definition: imagemetadata.h:36
#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.
QList< int > ImageIdList
Definition: imagetypes.h:59
Add year to string if not included.
Definition: mythdate.h:22
ImageDbLocal()
Local database constructor.
Hide pictures.
Definition: imagemanager.h:76
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:96
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
int LocateMount(const QString &mount) const
Find the id of a device.
static ImageManagerFe & getInstance()
Get Frontend Gallery.
QStringList HandleCover(int, int) const
Updates/resets cover thumbnail for an image dir.
QString CreateImport()
QStringList HandleScanRequest(const QString &, int devId=DEVICE_INVALID) const
Process scan requests.
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:96
QString GetHostName(void)
QString m_comment
User comment, from Exif metadata.
Definition: imagetypes.h:117
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.
QList< ImagePtrK > ImageListK
Definition: imagetypes.h:180
QStringList HandleHide(bool, const QString &ids) const
Hides/unhides images/dirs.
int m_size
Filesize (files only)
Definition: imagetypes.h:110
#define GALLERY_DB_ID
Definition: imagetypes.h:26
QStringList HandleIgnore(const QString &) const
Updates exclusion list for images.
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:136
#define DEVICE_INVALID
Definition: imagemanager.h:65
bool m_isHidden
If true, image won't be shown.
Definition: imagetypes.h:120
#define IMAGE_STORAGE_GROUP
Definition: imagemanager.h:57
StringMap GetDeviceDirs() const
Get all known devices.
void Close(bool eject=false)
Releases device.