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
12
13#include "dbaccess.h" // for FileAssociations
14
15#define LOC QString("ImageManager: ")
16#define DBLOC QString("ImageDb(%1): ").arg(m_table)
17
18// Must be empty as it's prepended to path
19static constexpr const char* STORAGE_GROUP_MOUNT { "" };
20
21static constexpr const char* DB_TABLE { "gallery_files" };
22
23// NOLINTBEGIN(cppcoreguidelines-macro-usage)
24#define RESULT_ERR(ERR, MESG) \
25{ LOG(VB_GENERAL, LOG_ERR, LOC + (MESG)); \
26 return QStringList("ERROR") << (ERR); }
27
28#define RESULT_OK(MESG) \
29{ LOG(VB_FILE, LOG_DEBUG, LOC + (MESG)); \
30 return QStringList("OK"); }
31// NOLINTEND(cppcoreguidelines-macro-usage)
32
33static constexpr const char* IMPORTDIR { "Import" };
34
35
37class Device
38{
39public:
40 Device(QString name, QString mount,
41 MythMediaDevice *media = nullptr, QTemporaryDir *import = nullptr)
42 : m_name(std::move(name)), m_mount(std::move(mount)),
43 m_media(media), m_dir(import)
44 {
45 // Path relative to TEMP storage group
46 m_thumbs = QString("%1/%2").arg(THUMBNAIL_SUBDIR, m_name);
47 }
48
49
52 {
53 Close();
54
55 // Remove imported images
56 delete m_dir;
57
58 // Clean up non-SG thumbnails
61 }
62
63
65 void Close(bool eject = false)
66 {
67 // Imports remain present; others do not
69
70 // Release device
71 if (m_media)
72 {
73 if (eject)
74 {
75 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Ejecting '%1' at '%2'")
76 .arg(m_name, m_mount));
78 }
79 else
80 {
81 LOG(VB_MEDIA, LOG_DEBUG, LOC + QString("Unlocked '%1'").arg(m_name));
82 }
83
85 m_media = nullptr;
86 }
87 }
88
89
94 static void RemoveDirContents(const QString& path)
95 {
96 QDir(path).removeRecursively();
97 }
98
99
101 void RemoveThumbs(void) const
102 {
103 // Remove thumbnails
104 QString dirFmt = QString("%1/") % TEMP_SUBDIR % "/%2";
105 QString dir = dirFmt.arg(GetConfDir(), m_thumbs);
106 LOG(VB_FILE, LOG_INFO, LOC + QString("Removing thumbnails in %1").arg(dir));
108 QDir::root().rmpath(dir);
109 }
110
111
112 bool isImport() const { return m_dir; }
113 bool isPresent() const { return m_present; }
114 void setPresent(MythMediaDevice *media) { m_present = true; m_media = media; }
115
117 bool m_present { true };
118 QString m_name;
119 QString m_mount;
120 QString m_thumbs;
122 QTemporaryDir *m_dir { nullptr };
123};
124
125
126static Device kNullDevice = Device("Unknown Device", "<Invalid Path>");
127
128
130{
131 qDeleteAll(m_devices);
132}
133
134
136QString DeviceManager::DeviceMount(int devId) const
137{
138 return m_devices.value(devId, &kNullDevice)->m_mount;
139}
140
141
143QString DeviceManager::DeviceName(int devId) const
144{
145 return m_devices.value(devId, &kNullDevice)->m_name;
146}
147
148
149QString DeviceManager::ThumbDir(int fs) const
150{
151 return m_devices.value(fs, &kNullDevice)->m_thumbs;
152}
153
154
164int DeviceManager::OpenDevice(const QString &name, const QString &mount,
165 MythMediaDevice *media, QTemporaryDir *dir)
166{
167 // Handle devices reappearing at same mountpoint.
168 // If a USB is unplugged whilst in use (without unmounting) we get no event
169 // but we do when it's re-inserted
170 QString state("Known");
171 int id = LocateMount(mount);
172
173 if (id == DEVICE_INVALID)
174 {
175 state = "New";
176 id = m_devices.isEmpty() ? 0 : m_devices.lastKey() + 1;
177 m_devices.insert(id, new Device(name, mount, media, dir));
178 }
179 else
180 {
181 Device *dev = m_devices.value(id);
182 if (dev)
183 dev->setPresent(media);
184 }
185
186 LOG(VB_GENERAL, LOG_INFO, LOC +
187 QString("%1 device %2 mounted at '%3' [Id %4]")
188 .arg(state, name, mount).arg(id));
189
190 return id;
191}
192
193
200QStringList DeviceManager::CloseDevices(int devId, const QString &action)
201{
202 QStringList clear;
203
204 if (action == "DEVICE CLOSE ALL")
205 {
206 // Close all devices but retain their thumbnails
207 for (auto *dev : std::as_const(m_devices))
208 if (dev)
209 dev->Close();
210 }
211 else if (action == "DEVICE CLEAR ALL")
212 {
213 // Remove all thumbnails but retain devices
214 for (const auto *dev : std::as_const(m_devices)) {
215 if (dev)
216 {
217 clear << dev->m_mount;
218 dev->RemoveThumbs();
219 }
220 }
221 }
222 else
223 {
224 // Remove single device & its thumbnails, optionally ejecting it
225 Device *dev = m_devices.take(devId);
226 if (dev)
227 {
228 if (action == "DEVICE EJECT")
229 dev->Close(true);
230 clear << dev->m_mount;
231 delete dev;
232 }
233 }
234 return clear;
235}
236
237
243int DeviceManager::LocateMount(const QString &mount) const
244{
245 DeviceMap::const_iterator it = m_devices.constBegin();
246 while (it != m_devices.constEnd())
247 {
248 if (it.value()->m_mount == mount)
249 return it.key();
250 ++it;
251 }
252 return DEVICE_INVALID;
253}
254
255
258{
260 for (auto it = m_devices.constKeyValueBegin();
261 it != m_devices.constKeyValueEnd(); ++it)
262 {
263 if (it->second)
264 paths.insert(it->first, it->second->m_mount);
265 }
266 return paths;
267}
268
269
272{
273 QList<int> absent;
274 for (auto it = m_devices.constKeyValueBegin();
275 it != m_devices.constKeyValueEnd(); it++)
276 {
277 Device *dev = it->second;
278 if (dev && !dev->isPresent())
279 absent << it->first;
280 }
281 return absent;
284
289 m_imageFileExt(SupportedImages()),
290 m_videoFileExt(SupportedVideos())
292 // Generate glob list from supported extensions
293 QStringList glob;
294 QStringList allExt = m_imageFileExt + m_videoFileExt;
295 for (const auto& ext : std::as_const(allExt))
296 glob << "*." + ext;
298 // Apply filters to only detect image files
299 m_dirFilter.setNameFilters(glob);
300 m_dirFilter.setFilter(QDir::AllDirs | QDir::Files | QDir::Readable |
301 QDir::NoDotAndDotDot | QDir::NoSymLinks);
302
303 // Sync files before dirs to improve thumb generation response
304 // Order by time (oldest first) - this determines the order thumbs appear
305 m_dirFilter.setSorting(QDir::DirsLast | QDir::Time | QDir::Reversed);
307
313{
314 // Determine supported picture formats from Qt
315 QStringList formats;
316 QList<QByteArray> supported = QImageReader::supportedImageFormats();
317 for (const auto& ext : std::as_const(supported))
318 formats << QString(ext);
319 return formats;
320}
321
322
328{
329 // Determine supported video formats from MythVideo
330 QStringList formats;
333 for (const auto & fa : faList)
334 {
335 if (!fa.use_default && fa.playcommand == "Internal")
336 formats << QString(fa.extension);
337 }
338 return formats;
339}
350ImageItem *ImageAdapterLocal::CreateItem(const QFileInfo &fi, int parentId,
351 int devId, const QString & /*base*/) const
353 auto *im = new ImageItem();
354
355 im->m_parentId = parentId;
356 im->m_device = devId;
357 im->m_filePath = fi.absoluteFilePath();
358
359 if (parentId == GALLERY_DB_ID)
360 {
361 // Import devices show time of import, other devices show 'last scan time'
362 auto secs = im->m_filePath.contains(IMPORTDIR)
363 ? fi.lastModified().toSecsSinceEpoch()
364 : QDateTime::currentSecsSinceEpoch();
365 im->m_date = std::chrono::seconds(secs);
366 im->m_modTime = im->m_date;
367 im->m_type = kDevice;
368 return im;
369 }
370
371 im->m_modTime = std::chrono::seconds(fi.lastModified().toSecsSinceEpoch());
372
373 if (fi.isDir())
374 {
375 im->m_type = kDirectory;
376 return im;
377 }
378
379 im->m_extension = fi.suffix().toLower();
380 im->m_type = GetImageType(im->m_extension);
381
382 if (im->m_type == kUnknown)
383 {
384 delete im;
385 return nullptr;
386 }
387
388 im->m_thumbPath = GetAbsThumbPath(ThumbDir(im->m_device), ThumbPath(*im));
389 im->m_size = fi.size();
390
391 return im;
392}
393
394
400void ImageAdapterLocal::Notify(const QString &mesg,
401 const QStringList &extra)
402{
403 QString host(gCoreContext->GetHostName());
404 gCoreContext->SendEvent(MythEvent(QString("%1 %2").arg(mesg, host), extra));
405}
406
408 m_hostname(gCoreContext->GetMasterHostName()),
409 m_hostport(MythCoreContext::GetMasterServerPort()),
410 m_sg(StorageGroup(IMAGE_STORAGE_GROUP, m_hostname, false))
411{
412}
413
414QString ImageAdapterSg::MakeFileUrl(const QString &path) const
415{
417}
418
419QString ImageAdapterSg::MakeThumbUrl(const QString &devPath, const QString &path) const
420{
421 return MythCoreContext::GenMythURL(m_hostname, m_hostport, devPath + "/" + path,
423}
424
433ImageItem *ImageAdapterSg::CreateItem(const QFileInfo &fi, int parentId,
434 int /*devId*/, const QString &base) const
435{
436 auto *im = new ImageItem();
437
438 im->m_device = 0;
439 im->m_parentId = parentId;
440
441 if (parentId == GALLERY_DB_ID)
442 {
443 // All SG dirs map to a single Db dir
444 im->m_filePath = "";
445 im->m_type = kDevice;
446 im->m_date = std::chrono::seconds(QDateTime::currentSecsSinceEpoch());
447 im->m_modTime = im->m_date;
448 return im;
449 }
450
451 // Strip SG path & leading / to leave a relative path
452 im->m_filePath = fi.absoluteFilePath().mid(base.size() + 1);
453 im->m_modTime = std::chrono::seconds(fi.lastModified().toSecsSinceEpoch());
454
455 if (fi.isDir())
456 {
457 im->m_type = kDirectory;
458 return im;
459 }
460
461 im->m_extension = fi.suffix().toLower();
462 im->m_type = GetImageType(im->m_extension);
463
464 if (im->m_type == kUnknown)
465 {
466 delete im;
467 return nullptr;
468 }
469
470 im->m_thumbPath = GetAbsThumbPath(ThumbDir(im->m_device), ThumbPath(*im));
471 im->m_size = fi.size();
472
473 return im;
474}
475
476
482void ImageAdapterSg::Notify(const QString &mesg,
483 const QStringList &extra)
484{
485 gCoreContext->SendEvent(MythEvent(mesg, extra));
486}
487
488
494{
495 StringMap map;
496 int i = 0;
497 QStringList paths = m_sg.GetDirList();
498 for (const auto& path : std::as_const(paths))
499 map.insert(i++, path);
500 return map;
501}
502
503
511{
512 if (im->IsDevice())
513 return m_sg.FindNextDirMostFree();
514 return im->m_filePath.startsWith("/") ? im->m_filePath
515 : m_sg.FindFile(im->m_filePath);
516}
517
518
519// Database fields used by several image queries
520static constexpr const char* kDBColumns {
521"file_id, filename, name, dir_id, type, modtime, size, "
522"extension, date, hidden, orientation, angle, path, zoom"
523// Id, filepath, basename, parentId, type, modtime, size,
524// extension, image date, hidden, orientation, cover id, comment, device id
525};
526
532template <class FS>
534{
535 auto *im = new ImageItem(FS::ImageId(query.value(0).toInt()));
536
537 // Ordered as per kDBColumns
538 im->m_filePath = query.value(1).toString();
539 im->m_baseName = query.value(2).toString();
540 im->m_parentId = FS::ImageId(query.value(3).toInt());
541 im->m_type = query.value(4).toInt();
542 im->m_modTime = std::chrono::seconds(query.value(5).toInt());
543 im->m_size = query.value(6).toInt();
544 im->m_extension = query.value(7).toString();
545 im->m_date = std::chrono::seconds(query.value(8).toUInt());
546 im->m_isHidden = query.value(9).toBool();
547 im->m_orientation = query.value(10).toInt();
548 im->m_userThumbnail = FS::ImageId(query.value(11).toInt());
549 im->m_comment = query.value(12).toString();
550 im->m_device = query.value(13).toInt();
551 im->m_url = FS::MakeFileUrl(im->m_filePath);
552
553 if (im->IsFile())
554 {
555 // Only pics/vids have thumbs
556 QString thumbPath(FS::ThumbPath(*im));
557 QString devPath(FS::ThumbDir(im->m_device));
558 QString url(FS::MakeThumbUrl(devPath, thumbPath));
559
560 im->m_thumbPath = FS::GetAbsThumbPath(devPath, thumbPath);
561 im->m_thumbNails.append(qMakePair(im->m_id, url));
562 }
563 return im;
564}
565
566
575template <class FS>
576int ImageDb<FS>::GetImages(const QString &ids, ImageList &files, ImageList &dirs,
577 const QString &refine) const
578{
579 if (ids.isEmpty())
580 return 0;
581
582 QString select = QString("file_id IN (%1) %2").arg(FS::DbIds(ids), refine);
583 return ReadImages(dirs, files, select);
584}
585
586
595template <class FS>
596int ImageDb<FS>::GetChildren(const QString &ids, ImageList &files, ImageList &dirs,
597 const QString &refine) const
598{
599 QString select = QString("dir_id IN (%1) %2").arg(FS::DbIds(ids), refine);
600 return ReadImages(dirs, files, select);
601}
602
603
613template <class FS>
615 ImageList &files, ImageList &dirs,
616 const QString &refine) const
617{
619 query.prepare(QString("SELECT %1 FROM %2 "
620 "WHERE (dir_id = :ID1 OR file_id = :ID2) "
621 "%3;").arg(kDBColumns, m_table, refine));
622
623 // Qt < 5.4 won't bind multiple occurrences
624 int dbId = FS::DbId(id);
625 query.bindValue(":ID1", dbId);
626 query.bindValue(":ID2", dbId);
627
628 if (!query.exec())
629 {
630 MythDB::DBError(DBLOC, query);
631 return -1;
632 }
633 while (query.next())
634 {
635 ImagePtr im(CreateImage(query));
636
637 if (im->IsFile())
638 files.append(im);
639 else if (im->m_id == id)
640 parent = im;
641 else
642 dirs.append(im);
643 }
644 return query.size();
645}
646
647
655template <class FS>
656bool ImageDb<FS>::GetDescendants(const QString &ids,
657 ImageList &files, ImageList &dirs) const
658{
659 if (ids.isEmpty())
660 return false;
661
662 if (ReadImages(dirs, files, QString("file_id IN (%1)").arg(FS::DbIds(ids))) < 0)
663 return false;
664
666 QString sql =
667 QString("SELECT %1"
668 ", LENGTH(filename) - LENGTH(REPLACE(filename, '/', ''))"
669 " AS depth "
670 "FROM %2 WHERE filename LIKE :PREFIX "
671 "ORDER BY depth;").arg(kDBColumns,m_table);
672
673 for (const auto& im1 : std::as_const(dirs))
674 {
675 query.prepare(sql);
676 query.bindValue(":PREFIX", im1->m_filePath + "/%");
677
678 if (!query.exec())
679 {
680 MythDB::DBError(DBLOC, query);
681 return false;
682 }
683
684 while (query.next())
685 {
686 ImagePtr im2(CreateImage(query));
687 if (im2->IsDirectory())
688 dirs.append(im2);
689 else
690 files.append(im2);
691 }
692 }
693 return true;
694}
695
696
704template <class FS>
705bool ImageDb<FS>::GetImageTree(int id, ImageList &files, const QString &refine) const
706{
707 // Load starting children
708 ImageList dirs;
709 if (GetChildren(QString::number(id), files, dirs, refine) < 0)
710 return false;
711
712 for (const auto& im : std::as_const(dirs))
713 if (!GetImageTree(im->m_id, files, refine))
714 return false;
715 return true;
716}
717
718
724template <class FS>
726{
728 query.prepare(QString("SELECT %1 FROM %2").arg(kDBColumns, m_table));
729
730 if (!query.exec())
731 {
732 MythDB::DBError(DBLOC, query);
733 return false;
734 }
735
736 while (query.next())
737 {
738 ImagePtr im(CreateImage(query));
739 if (im->IsDirectory())
740 dirs.insert(im->m_filePath, im);
741 else
742 files.insert(im->m_filePath, im);
743 }
744 return true;
745}
746
747
755template <class FS>
756void ImageDb<FS>::ClearDb(int devId, const QString &action)
757{
758 if (action == "DEVICE CLOSE ALL")
759 // Retain Db images when closing UI
760 return;
761
763
764 if (action == "DEVICE CLEAR ALL")
765 {
766 // Clear images from all devices. Reset auto-increment
767 query.prepare(QString("TRUNCATE TABLE %1;").arg(m_table));
768
769 if (!query.exec())
770 MythDB::DBError(DBLOC, query);
771 }
772 else // Actions DEVICE REMOVE & DEVICE EJECT
773 {
774 // Delete all images of the device
775 query.prepare(QString("DELETE IGNORE FROM %1 WHERE zoom = :FS;").arg(m_table));
776 query.bindValue(":FS", devId);
777
778 if (!query.exec())
779 MythDB::DBError(DBLOC, query);
780 }
781}
782
783
791template <class FS>
792int ImageDb<FS>::InsertDbImage(ImageItemK &im, bool checkForDuplicate) const
793{
795
796 if (checkForDuplicate)
797 {
798 query.prepare(QString("SELECT file_id FROM %1 WHERE filename = :NAME;")
799 .arg(m_table));
800
801 query.bindValue(":NAME", im.m_filePath);
802
803 if (!query.exec())
804 {
805 MythDB::DBError(DBLOC, query);
806 return -1;
807 }
808
809 if (query.size() > 0)
810 {
811 LOG(VB_FILE, LOG_DEBUG, QString("Image: %1 already exists in Db")
812 .arg(im.m_filePath));
813 return query.value(0).toInt();
814 }
815 }
816
817 query.prepare(QString("INSERT INTO %1 (%2) VALUES (0, "
818 ":FILEPATH, :NAME, :PARENT, :TYPE, :MODTIME, "
819 ":SIZE, :EXTENSION, :DATE, :HIDDEN, :ORIENT, "
820 ":COVER, :COMMENT, :FS);").arg(m_table, kDBColumns));
821
822 query.bindValueNoNull(":FILEPATH", im.m_filePath);
823 query.bindValueNoNull(":NAME", FS::BaseNameOf(im.m_filePath));
824 query.bindValue(":FS", im.m_device);
825 query.bindValue(":PARENT", FS::DbId(im.m_parentId));
826 query.bindValue(":TYPE", im.m_type);
827 query.bindValue(":MODTIME", static_cast<qint64>(im.m_modTime.count()));
828 query.bindValue(":SIZE", im.m_size);
829 query.bindValueNoNull(":EXTENSION", im.m_extension);
830 query.bindValue(":DATE", static_cast<qint64>(im.m_date.count()));
831 query.bindValue(":ORIENT", im.m_orientation);
832 query.bindValueNoNull(":COMMENT", im.m_comment);
833 query.bindValue(":HIDDEN", im.m_isHidden);
834 query.bindValue(":COVER", FS::DbId(im.m_userThumbnail));
835
836 if (query.exec())
837 return FS::ImageId(query.lastInsertId().toInt());
838
839 MythDB::DBError(DBLOC, query);
840 return -1;
841}
842
843
849template <class FS>
851{
853 query.prepare(QString
854 ("UPDATE %1 SET "
855 "filename = :FILEPATH, name = :NAME, "
856 "dir_id = :PARENT, type = :TYPE, "
857 "modtime = :MODTIME, size = :SIZE, "
858 "extension = :EXTENSION, date = :DATE, zoom = :FS, "
859 "hidden = :HIDDEN, orientation = :ORIENT, "
860 "angle = :COVER, path = :COMMENT "
861 "WHERE file_id = :ID;").arg(m_table));
862
863 query.bindValue(":ID", FS::DbId(im.m_id));
864 query.bindValue(":FILEPATH", im.m_filePath);
865 query.bindValue(":NAME", FS::BaseNameOf(im.m_filePath));
866 query.bindValue(":PARENT", FS::DbId(im.m_parentId));
867 query.bindValue(":TYPE", im.m_type);
868 query.bindValue(":MODTIME", static_cast<qint64>(im.m_modTime.count()));
869 query.bindValue(":SIZE", im.m_size);
870 query.bindValue(":EXTENSION", im.m_extension);
871 query.bindValue(":DATE", static_cast<qint64>(im.m_date.count()));
872 query.bindValue(":FS", im.m_device);
873 query.bindValue(":HIDDEN", im.m_isHidden);
874 query.bindValue(":ORIENT", im.m_orientation);
875 query.bindValue(":COVER", FS::DbId(im.m_userThumbnail));
876 query.bindValueNoNull(":COMMENT", im.m_comment);
877
878 if (query.exec())
879 return true;
880
881 MythDB::DBError(DBLOC, query);
882 return false;
883}
884
885
892template <class FS>
893QStringList ImageDb<FS>::RemoveFromDB(const ImageList &imList) const
894{
895 QStringList ids;
896 if (!imList.isEmpty())
897 {
898 for (const auto& im : std::as_const(imList))
899 ids << QString::number(FS::DbId(im->m_id));
900
901 QString idents = ids.join(",");
903 query.prepare(QString("DELETE IGNORE FROM %1 WHERE file_id IN (%2);")
904 .arg(m_table, idents));
905
906 if (!query.exec())
907 {
908 MythDB::DBError(DBLOC, query);
909 return {};
910 }
911 }
912 return ids;
913}
914
915
922template <class FS>
923bool ImageDb<FS>::SetHidden(bool hide, const QString &ids) const
924{
925 if (ids.isEmpty())
926 return false;
927
929
930 query.prepare(QString("UPDATE %1 SET "
931 "hidden = :HIDDEN "
932 "WHERE file_id IN (%2);").arg(m_table, FS::DbIds(ids)));
933 query.bindValue(":HIDDEN", hide ? 1 : 0);
934
935 if (query.exec())
936 return true;
937
938 MythDB::DBError(DBLOC, query);
939 return false;
940}
941
942
948template <class FS>
949bool ImageDb<FS>::SetCover(int dir, int id) const
950{
952
953 query.prepare(QString("UPDATE %1 SET "
954 "angle = :COVER "
955 "WHERE file_id = :DIR").arg(m_table));
956 query.bindValue(":COVER", FS::DbId(id));
957 query.bindValue(":DIR", FS::DbId(dir));
958 \
959 if (query.exec())
960 return true;
961
962 MythDB::DBError(DBLOC, query);
963 return false;
964}
965
966
972template <class FS>
973bool ImageDb<FS>::SetOrientation(int id, int orientation) const
974{
976
977 query.prepare(QString("UPDATE %1 SET ").arg(m_table) +
978 "orientation = :ORIENTATION "
979 "WHERE file_id = :ID");
980 query.bindValue(":ORIENTATION", orientation);
981 query.bindValue(":ID", FS::DbId(id));
982 \
983 if (query.exec())
984 return true;
985
986 MythDB::DBError(DBLOC, query);
987 return false;
988}
989
990
998template <class FS>
1000 const QString &selector) const
1001{
1003 query.prepare(QString("SELECT %1 FROM %2 WHERE %3")
1004 .arg(kDBColumns, m_table, selector));
1005 if (!query.exec())
1006 {
1007 MythDB::DBError(DBLOC, query);
1008 return -1;
1009 }
1010
1011 while (query.next())
1012 {
1013 ImagePtr im(CreateImage(query));
1014
1015 if (im->IsFile())
1016 files.append(im);
1017 else
1018 dirs.append(im);
1019 }
1020 return query.size();
1021}
1022
1023
1033template <class FS>
1034void ImageDb<FS>::GetDescendantCount(int id, bool all, int &dirs,
1035 int &pics, int &videos, int &sizeKb) const
1036{
1037 QString whereClause;
1038 if (!all)
1039 {
1040 whereClause = "WHERE filename LIKE "
1041 "( SELECT CONCAT(filename, '/%') "
1042 " FROM %2 WHERE file_id = :ID);";
1043 }
1044
1046 query.prepare(QString("SELECT SUM(type <= :FLDR) AS Fldr, "
1047 " SUM(type = :PIC) AS Pics, "
1048 " SUM(type = :VID) AS Vids, "
1049 " SUM(size / 1024) "
1050 "FROM %2 %1;").arg(whereClause, m_table));
1051
1052 query.bindValue(":FLDR", kDirectory);
1053 query.bindValue(":PIC", kImageFile);
1054 query.bindValue(":VID", kVideoFile);
1055 if (!all)
1056 query.bindValue(":ID", FS::DbId(id));
1057
1058 if (!query.exec())
1059 {
1060 MythDB::DBError(DBLOC, query);
1061 }
1062 else if (query.next())
1063 {
1064 dirs += query.value(0).toInt();
1065 pics += query.value(1).toInt();
1066 videos += query.value(2).toInt();
1067 sizeKb += query.value(3).toInt();
1068 }
1069}
1070
1071
1076{
1077 // Be has a single SG device
1079}
1080
1081
1086 : ImageDb(QString("`%1_%2`").arg(DB_TABLE, gCoreContext->GetHostName()))
1087{
1088 // Remove any table leftover from a previous FE crash
1089 DropTable();
1090}
1091
1092
1097{
1099 query.prepare(QString("DROP TABLE IF EXISTS %1;").arg(m_table));
1100 if (query.exec())
1101 m_dbExists = false;
1102 else
1103 MythDB::DBError(DBLOC, query);
1104}
1105
1106
1111{
1112 if (m_dbExists)
1113 return true;
1114
1116
1117 // Create temporary table
1118 query.prepare(QString("CREATE TABLE %1 LIKE %2;").arg(m_table, DB_TABLE));
1119 if (query.exec())
1120 {
1121 // Store it in memory only
1122 query.prepare(QString("ALTER TABLE %1 ENGINE = MEMORY;").arg(m_table));
1123 if (query.exec())
1124 {
1125 m_dbExists = true;
1126 LOG(VB_FILE, LOG_DEBUG, QString("Created Db table %1").arg(m_table));
1127 return true;
1128 }
1129 }
1130 MythDB::DBError(DBLOC, query);
1131
1132 // Clean up after failure
1133 query.prepare(QString("DROP TABLE IF EXISTS %1;").arg(m_table));
1134 query.exec();
1135 return false;
1136}
1137
1138
1142class ReadMetaThread : public QRunnable
1143{
1144public:
1145 ReadMetaThread(ImagePtrK im, QString path)
1146 : m_im(std::move(im)), m_path(std::move(path)) {}
1147
1148 void run() override // QRunnable
1149 {
1150 QStringList tags;
1151 QString orientation;
1152 QString size;
1153
1154 // Read metadata for files only
1155 if (m_im->IsFile())
1156 {
1157 ImageMetaData *metadata = (m_im->m_type == kVideoFile)
1160 tags = metadata->GetAllTags();
1161 orientation = Orientation(m_im->m_orientation).Description();
1162 size = ImageAdapterBase::FormatSize(m_im->m_size / 1024);
1163 delete metadata;
1164 }
1165
1166 // Add identifier at front
1167 tags.prepend(QString::number(m_im->m_id));
1168
1169 // Include file info
1176 tags << ImageMetaData::ToString(EXIF_MYTH_SIZE, "Size", size);
1177 tags << ImageMetaData::ToString(EXIF_MYTH_ORIENT, "Orientation",
1178 orientation);
1179
1180 MythEvent me("IMAGE_METADATA", tags);
1182 }
1183
1184private:
1186 QString m_path;
1187};
1188
1189
1198template <class DBFS>
1199QStringList ImageHandler<DBFS>::HandleGetMetadata(const QString &id) const
1200{
1201 // Find image in DB
1202 ImageList files;
1203 ImageList dirs;
1204 if (DBFS::GetImages(id, files, dirs) != 1)
1205 RESULT_ERR("Image not found", QString("Unknown image %1").arg(id))
1206
1207 ImagePtr im = files.isEmpty() ? dirs[0] : files[0];
1208
1209 QString absPath = DBFS::GetAbsFilePath(im);
1210 if (absPath.isEmpty())
1211 RESULT_ERR("Image not found",
1212 QString("File %1 not found").arg(im->m_filePath))
1213
1214 auto *worker = new ReadMetaThread(im, absPath);
1215
1216 MThreadPool::globalInstance()->start(worker, "ImageMetaData");
1217
1218 RESULT_OK(QString("Fetching metadata for %1").arg(id))
1219}
1220
1221
1229template <class DBFS>
1230QStringList ImageHandler<DBFS>::HandleRename(const QString &id,
1231 const QString &newBase) const
1232{
1233 // Sanity check new name
1234 if (newBase.isEmpty() || newBase.contains("/") || newBase.contains("\\"))
1235 RESULT_ERR("Invalid name", QString("Invalid name %1").arg(newBase))
1236
1237 // Find image in DB
1238 ImageList files;
1239 ImageList dirs;
1240 if (DBFS::GetImages(id, files, dirs) != 1)
1241 RESULT_ERR("Image not found", QString("Image %1 not in Db").arg(id))
1242
1243 ImagePtr im = files.isEmpty() ? dirs[0] : files[0];
1244
1245 // Find file
1246 QString oldPath = DBFS::GetAbsFilePath(im);
1247 if (oldPath.isEmpty())
1248 RESULT_ERR("Image not found",
1249 QString("File %1 not found").arg(im->m_filePath))
1250
1251 // Generate new filename
1252 QFileInfo oldFi = QFileInfo(oldPath);
1253 QString newName = im->IsDirectory()
1254 ? newBase : QString("%1.%2").arg(newBase, oldFi.suffix());
1255
1256 im->m_filePath = DBFS::ConstructPath(DBFS::PathOf(im->m_filePath), newName);
1257
1258 // Ensure no SG duplicate files are created. (Creating clone dirs is ok)
1259 if (im->IsFile())
1260 {
1261 QString existPath = DBFS::GetAbsFilePath(im);
1262 if (!existPath.isEmpty())
1263 RESULT_ERR("Filename already used",
1264 QString("Renaming %1 to %2 will create a duplicate of %3")
1265 .arg(oldPath, im->m_filePath, existPath))
1266 }
1267
1268 // Rename file or directory
1269 QString newPath = oldFi.dir().absoluteFilePath(newName);
1270 if (!QFile::rename(oldPath, newPath))
1271 RESULT_ERR("Rename failed",
1272 QString("Rename of %1 -> %2 failed").arg(oldPath, newPath))
1273
1274 if (im->IsDirectory())
1275 {
1276 // Dir name change affects path of all sub-dirs & files and their thumbs
1277 HandleScanRequest("START");
1278 }
1279 else // file
1280 {
1281 // Update db
1282 DBFS::UpdateDbImage(*im);
1283
1284 // Image is modified, not deleted
1285 QStringList mesg("");
1286 mesg << QString::number(im->m_id);
1287
1288 // Rename thumbnail.
1289 m_thumbGen->MoveThumbnail(im);
1290
1291 // Notify clients of changed image
1292 DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1293 }
1294 RESULT_OK(QString("Renamed %1 -> %2").arg(oldPath, newPath))
1295}
1296
1297
1305template <class DBFS>
1306QStringList ImageHandler<DBFS>::HandleDelete(const QString &ids) const
1307{
1308 // Get subtree of all files
1309 ImageList files;
1310 ImageList dirs;
1311 // Dirs will be in depth-first order, (subdirs after parent)
1312 DBFS::GetDescendants(ids, files, dirs);
1313
1314 // Remove files from filesystem first
1315 RemoveFiles(files);
1316 // ... then dirs, which should now be empty
1317 RemoveFiles(dirs);
1318
1319 // Fail if nothing deleted
1320 if (files.isEmpty() && dirs.isEmpty())
1321 RESULT_ERR("Delete failed", QString("Delete of %1 failed").arg(ids))
1322
1323 // Update Db
1324 DBFS::RemoveFromDB(files + dirs);
1325
1326 // Clean up thumbnails
1327 QStringList mesg(m_thumbGen->DeleteThumbs(files));
1328
1329 // Notify clients of deleted ids
1330 DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1331
1332 return QStringList("OK");
1333}
1334
1335
1348template <class DBFS>
1349QStringList ImageHandler<DBFS>::HandleDbCreate(QStringList defs) const
1350{
1351 if (defs.isEmpty())
1352 RESULT_ERR("Copy Failed", "Empty defs")
1353
1354 // First item is the field seperator
1355 const QString separator = defs.takeFirst();
1356
1357 // Convert cover ids to their new equivalent. Map<source id, new id>
1358 // Dirs follow their children so new cover ids will be defined before they
1359 // are used
1360 QHash<QString, int> idMap;
1361
1362 // Create skeleton Db images using copied settings.
1363 // Scanner will update other attributes
1364 ImageItem im;
1365 for (const auto& def : std::as_const(defs))
1366 {
1367 QStringList aDef = def.split(separator);
1368
1369 // Expects id, type, path, hidden, orientation, cover
1370 if (aDef.size() != 6)
1371 {
1372 // Coding error
1373 LOG(VB_GENERAL, LOG_ERR,
1374 LOC + QString("Bad definition: (%1)").arg(def));
1375 continue;
1376 }
1377
1378 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Creating %1").arg(aDef.join(",")));
1379
1380 im.m_type = aDef[1].toInt();
1381 im.m_filePath = aDef[2];
1382 im.m_isHidden = (aDef[3].toInt() != 0);
1383 im.m_orientation = aDef[4].toInt();
1384 im.m_userThumbnail = idMap.value(aDef[5]);
1385
1386 // Don't insert duplicate filepaths
1387 int newId = DBFS::InsertDbImage(im, true);
1388
1389 // Record old->new id map in case it's being used as a cover
1390 idMap.insert(aDef[0], newId);
1391 }
1392 HandleScanRequest("START");
1393
1394 RESULT_OK("Created Db images")
1395}
1396
1397
1407template <class DBFS>
1408QStringList ImageHandler<DBFS>::HandleDbMove(const QString &ids,
1409 const QString &srcPath,
1410 QString destPath) const
1411{
1412 // Sanity check new path
1413 if (destPath.contains(".."))
1414 RESULT_ERR("Invalid path", QString("Invalid path %1").arg(destPath))
1415
1416 // Get subtrees of renamed files
1417 ImageList images;
1418 ImageList dirs;
1419 ImageList files;
1420 bool ok = DBFS::GetDescendants(ids, files, dirs);
1421 images << dirs << files;
1422
1423 if (!ok || images.isEmpty())
1424 RESULT_ERR("Image not found", QString("Images %1 not in Db").arg(ids))
1425
1426 if (!destPath.isEmpty() && !destPath.endsWith(QChar('/')))
1427 destPath.append("/");
1428
1429 // Update path of images only. Scanner will repair parentId
1430 for (const auto& im : std::as_const(images))
1431 {
1432 QString old = im->m_filePath;
1433
1434 if (srcPath.isEmpty())
1435 {
1436 // Image in SG root
1437 im->m_filePath.prepend(destPath);
1438 }
1439 else if (im->m_filePath.startsWith(srcPath))
1440 {
1441 // All other images
1442 im->m_filePath.replace(srcPath, destPath);
1443 }
1444 else
1445 {
1446 // Coding error
1447 LOG(VB_GENERAL, LOG_ERR,
1448 LOC + QString("Bad image: (%1 -> %2)").arg(srcPath, destPath));
1449 continue;
1450 }
1451
1452 LOG(VB_FILE, LOG_DEBUG,
1453 LOC + QString("Db Renaming %1 -> %2").arg(old, im->m_filePath));
1454
1455 DBFS::UpdateDbImage(*im);
1456
1457 // Rename thumbnail
1458 if (im->IsFile())
1459 m_thumbGen->MoveThumbnail(im);
1460 }
1461 HandleScanRequest("START");
1462
1463 RESULT_OK(QString("Moved %1 from %2 -> %3").arg(ids, srcPath, destPath))
1464}
1465
1466
1474template <class DBFS>
1475QStringList ImageHandler<DBFS>::HandleHide(bool hide, const QString &ids) const
1476{
1477 if (!DBFS::SetHidden(hide, ids))
1478 RESULT_ERR("Hide failed", QString("Db hide failed for %1").arg(ids))
1479
1480 // Send changed ids only (none deleted)
1481 QStringList mesg = QStringList("") << ids;
1482 DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1483
1484 RESULT_OK(QString("Images %1 now %2hidden").arg(ids, hide ? "" : "un"))
1485}
1486
1487
1496template <class DBFS>
1497QStringList ImageHandler<DBFS>::HandleTransform(int transform,
1498 const QString &ids) const
1499{
1500 if (transform < kResetToExif || transform > kFlipVertical)
1501 RESULT_ERR("Transform failed", QString("Bad transform %1").arg(transform))
1502
1503 ImageList files;
1504 ImageList dirs;
1505 if (DBFS::GetImages(ids, files, dirs) < 1 || files.isEmpty())
1506 RESULT_ERR("Image not found", QString("Images %1 not in Db").arg(ids))
1507
1508 // Update db
1509 for (const auto& im : std::as_const(files))
1510 {
1511 int old = im->m_orientation;
1512 im->m_orientation = Orientation(im->m_orientation).Transform(transform);
1513
1514 // Update Db
1515 if (DBFS::SetOrientation(im->m_id, im->m_orientation))
1516 {
1517 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Transformed %1 from %2 to %3")
1518 .arg(im->m_filePath).arg(old).arg(im->m_orientation));
1519 }
1520 }
1521
1522 // Images are changed, not deleted
1523 QStringList mesg("");
1524
1525 // Clean up thumbnails
1526 mesg << m_thumbGen->DeleteThumbs(files);
1527
1528 // Notify clients of changed images
1529 DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1530
1531 return QStringList("OK");
1532}
1533
1534
1543template <class DBFS>
1544QStringList ImageHandler<DBFS>::HandleDirs(const QString &destId,
1545 bool rescan,
1546 const QStringList &relPaths) const
1547{
1548 // Find image in DB
1549 ImageList files;
1550 ImageList dirs;
1551 if (DBFS::GetImages(destId, files, dirs) != 1 || dirs.isEmpty())
1552 RESULT_ERR("Destination not found",
1553 QString("Image %1 not in Db").arg(destId))
1554
1555 // Find dir. SG device (Photographs) uses most-free filesystem
1556 QString destPath = DBFS::GetAbsFilePath(dirs[0]);
1557 if (destPath.isEmpty())
1558 RESULT_ERR("Destination not found",
1559 QString("Dest dir %1 not found").arg(dirs[0]->m_filePath))
1560
1561 QDir destDir(destPath);
1562 bool succeeded = false;
1563 for (const auto& relPath : std::as_const(relPaths))
1564 {
1565 // Validate dir name
1566 if (relPath.isEmpty() || relPath.contains("..") || relPath.startsWith(QChar('/')))
1567 continue;
1568
1569 QString newPath = DBFS::ConstructPath(destDir.absolutePath(), relPath);
1570 if (!destDir.mkpath(relPath))
1571 {
1572 LOG(VB_GENERAL, LOG_ERR,
1573 LOC + QString("Failed to create dir %1").arg(newPath));
1574 continue;
1575 }
1576 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Dir %1 created").arg(newPath));
1577 succeeded = true;
1578 }
1579
1580 if (!succeeded)
1581 // Failures should only occur due to user input
1582 RESULT_ERR("Invalid Name", QString("Invalid name %1")
1583 .arg(relPaths.join(",")))
1584
1585 if (rescan)
1586 // Rescan to detect new dir
1587 HandleScanRequest("START");
1588
1589 return QStringList("OK");
1590}
1591
1592
1599template <class DBFS>
1600QStringList ImageHandler<DBFS>::HandleCover(int dir, int cover) const
1601{
1602 if (!DBFS::SetCover(dir, cover))
1603 RESULT_ERR("Set Cover failed",
1604 QString("Failed to set %1 to cover %2").arg(dir).arg(cover))
1605
1606 // Image has changed, nothing deleted
1607 QStringList mesg = QStringList("") << QString::number(dir);
1608 DBFS::Notify("IMAGE_DB_CHANGED", mesg);
1609
1610 RESULT_OK(QString("Cover of %1 is now %2").arg(dir).arg(cover));
1611}
1612
1613
1622template <class DBFS>
1623QStringList ImageHandler<DBFS>::HandleIgnore(const QString &exclusions) const
1624{
1625 // Save new setting. FE will have already saved it but not cleared the cache
1626 gCoreContext->SaveSettingOnHost("GalleryIgnoreFilter", exclusions, nullptr);
1627
1628 // Rescan
1629 HandleScanRequest("START");
1630
1631 RESULT_OK(QString("Using exclusions '%1'").arg(exclusions))
1632}
1633
1634
1642template <class DBFS>
1643QStringList ImageHandler<DBFS>::HandleScanRequest(const QString &command,
1644 int devId) const
1645{
1646 if (!m_scanner)
1647 RESULT_ERR("Missing Scanner", "Missing Scanner");
1648
1649 if (command == "START")
1650 {
1651 // Must be dormant to start a scan
1652 if (m_scanner->IsScanning())
1653 RESULT_ERR("", "Scanner is busy");
1654
1655 m_scanner->ChangeState(true);
1656 RESULT_OK("Scan requested");
1657 }
1658 else if (command == "STOP")
1659 {
1660 // Must be scanning to interrupt
1661 if (!m_scanner->IsScanning())
1662 RESULT_ERR("Scanner not running", "Scanner not running");
1663
1664 m_scanner->ChangeState(false);
1665 RESULT_OK("Terminate scan requested");
1666 }
1667 else if (command == "QUERY")
1668 {
1669 return QStringList("OK") << m_scanner->GetProgress();
1670 }
1671 else if (command.startsWith(QString("DEVICE")))
1672 {
1673 m_scanner->EnqueueClear(devId, command);
1674 RESULT_OK(QString("Clearing device %1 %2").arg(command).arg(devId))
1675 }
1676 RESULT_ERR("Unknown command", QString("Unknown command %1").arg(command));
1677}
1678
1679
1687template <class DBFS>
1689(const QStringList &message) const
1690{
1691 if (message.size() != 2)
1692 RESULT_ERR("Unknown Command",
1693 QString("Bad request: %1").arg(message.join("|")))
1694
1695 int priority = message.at(0).toInt()
1697
1698 // get specific image details from db
1699 ImageList files;
1700 ImageList dirs;
1701 DBFS::GetImages(message.at(1), files, dirs);
1702
1703 for (const auto& im : std::as_const(files))
1704 // notify clients when done; highest priority
1705 m_thumbGen->CreateThumbnail(im, priority, true);
1706
1707 return QStringList("OK");
1708}
1709
1710
1720template <class DBFS>
1722{
1723 if (images.empty())
1724 return;
1725
1726 // Qt6 changed the iterator, so 'auto*' fails to compile.
1727 // NOLINTNEXTLINE(readability-qualified-auto)
1728 for (auto it = images.end();
1729 it != images.begin();
1730 /* no inc */)
1731 {
1732 ImagePtrK im = *(--it);
1733
1734 // Remove file or directory
1735 QString absFilename = DBFS::GetAbsFilePath(im);
1736
1737 bool success = !absFilename.isEmpty()
1738 && (im->IsFile() ? QFile::remove(absFilename)
1739 : QDir::root().rmdir(absFilename));
1740 if (success)
1741 {
1742 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Deleted %1").arg(absFilename));
1743 }
1744 else
1745 {
1746 LOG(VB_GENERAL, LOG_ERR, LOC +
1747 QString("Can't delete %1").arg(absFilename));
1748 // Remove from list
1749 it = images.erase(it);
1750 }
1751 }
1752}
1753
1754
1761{
1762 switch (type)
1763 {
1764 case kPicOnly: return QString("AND type != %1").arg(kVideoFile);
1765 case kVideoOnly: return QString("AND type != %1").arg(kImageFile);
1766 case kPicAndVideo: return "";
1767 }
1768 return "";
1769}
1770
1771
1778{
1779 m_refineClause = QString("%2 %3 "
1780 "ORDER BY "
1781 "CASE WHEN type <= %1 THEN %4, "
1782 "CASE WHEN type > %1 THEN %5 ")
1783 .arg(kDirectory)
1784 .arg(m_showHidden ? "" : "AND hidden = 0",
1788}
1789
1790
1797{
1798 // prepare the sorting statement
1799 switch (order)
1800 {
1801 default:
1802 case kSortByNameAsc: return "name END ASC";
1803 case kSortByNameDesc: return "name END DESC";
1804 case kSortByModTimeAsc: return "modtime END ASC";
1805 case kSortByModTimeDesc: return "modtime END DESC";
1806 case kSortByExtAsc: return "extension END ASC, name ASC";
1807 case kSortByExtDesc: return "extension END DESC, name DESC";
1808 case kSortBySizeAsc: return "size END ASC, name ASC";
1809 case kSortBySizeDesc: return "size END DESC, name DESC";
1810 case kSortByDateAsc: return "IF(date=0, modtime, date) END ASC";
1811 case kSortByDateDesc: return "IF(date=0, modtime, date) END DESC";
1812 }
1813}
1814
1815
1825 ImageList &files, ImageList &dirs) const
1826{
1827 // Only Root node will invoke both Db queries but result set will be small
1828 // For Root the SG is always ordered before local devices
1829 // Root node has no Db entry so the 2 queries will not overwrite the parent.
1830 int count = 0;
1831 if (!ImageItem::IsLocalId(id))
1832 count = m_remote->GetDirectory(id, parent, files, dirs, m_refineClause);
1834 count += ImageHandler::GetDirectory(id, parent, files, dirs, m_refineClause);
1835
1836 if (id == GALLERY_DB_ID)
1837 {
1838 // Add a Root node
1839 parent = ImagePtr(new ImageItem(GALLERY_DB_ID));
1840 parent->m_parentId = GALLERY_DB_ID;
1841 parent->m_type = kDevice;
1842
1843 ++count;
1844 }
1845 return count;
1846}
1847
1848
1857 ImageList &files, ImageList &dirs) const
1858{
1859 // Ids are either all local or all remote. GALLERY_DB_ID not valid
1861
1862 if (!lists.second.isEmpty())
1863 return m_remote->GetImages(lists.second, files, dirs, m_refineClause);
1864 if (m_dbExists && !lists.first.isEmpty())
1865 return ImageHandler::GetImages(lists.first, files, dirs, m_refineClause);
1866 return 0;
1867}
1868
1869
1877int ImageDbReader::GetChildren(int id, ImageList &files, ImageList &dirs) const
1878{
1879 int count = 0;
1880 if (!ImageItem::IsLocalId(id))
1881 count = m_remote->GetChildren(QString::number(id), files, dirs,
1884 count += ImageHandler::GetChildren(QString::number(id), files, dirs,
1886 return count;
1887}
1888
1889
1897 ImageList &files, ImageList &dirs) const
1898{
1899 // Ids are either all local or all remote
1901
1902 if (!lists.second.isEmpty())
1903 m_remote->GetDescendants(lists.second, files, dirs);
1904 if (m_dbExists && !lists.first.isEmpty())
1905 ImageHandler::GetDescendants(lists.first, files, dirs);
1906}
1907
1908
1915void ImageDbReader::GetImageTree(int id, ImageList &files) const
1916{
1917 if (!ImageItem::IsLocalId(id))
1920 ImageHandler::GetImageTree(id, files, m_refineClause);
1921}
1922
1923
1932void ImageDbReader::GetDescendantCount(int id, int &dirs, int &pics,
1933 int &videos, int &sizeKb) const
1934{
1935 if (id == GALLERY_DB_ID)
1936 {
1937 // Sum both unfiltered tables
1938 m_remote->GetDescendantCount(id, true, dirs, pics, videos, sizeKb);
1939 if (m_dbExists)
1940 ImageHandler::GetDescendantCount(id, true, dirs, pics, videos, sizeKb);
1941 }
1942 else if (!ImageItem::IsLocalId(id))
1943 {
1944 // Don't filter on SG path (it's blank)
1946 dirs, pics, videos, sizeKb);
1947 }
1948 else if (m_dbExists)
1949 {
1950 // Always filter on device/dir
1951 ImageHandler::GetDescendantCount(id, false, dirs, pics, videos, sizeKb);
1952 }
1953}
1954
1955
1960
1961
1967{
1968 if (!s_instance)
1969 s_instance = new ImageManagerBe();
1970 return s_instance;
1971}
1972
1973
1979{
1980 if (!s_instance)
1981 {
1982 // Use saved settings
1984 (gCoreContext->GetNumSetting("GalleryImageOrder"),
1985 gCoreContext->GetNumSetting("GalleryDirOrder"),
1986 gCoreContext->GetBoolSetting("GalleryShowHidden"),
1987 gCoreContext->GetNumSetting("GalleryShowType"),
1988 gCoreContext->GetSetting("GalleryDateFormat"));
1989 }
1990 return *s_instance;
1991}
1992
1993
2002void ImageManagerFe::CreateThumbnails(const ImageIdList &ids, bool forFolder)
2003{
2004 // Split images into <locals, remotes>
2006
2007 if (!lists.second.isEmpty())
2008 {
2009 LOG(VB_FILE, LOG_DEBUG, LOC +
2010 QString("Sending CREATE_THUMBNAILS %1 (forFolder %2)")
2011 .arg(lists.second).arg(forFolder));
2012
2013 QStringList message;
2014 message << QString::number(static_cast<int>(forFolder)) << lists.second;
2015 gCoreContext->SendEvent(MythEvent("CREATE_THUMBNAILS", message));
2016 }
2017
2018 if (!lists.first.isEmpty())
2019 {
2020 LOG(VB_FILE, LOG_DEBUG, LOC +
2021 QString("Creating local thumbnails %1 (forFolder %2)")
2022 .arg(lists.first).arg(forFolder));
2023
2024 QStringList message;
2025 message << QString::number(static_cast<int>(forFolder)) << lists.first;
2026 HandleCreateThumbnails(message);
2027 }
2028}
2029
2030
2037QString ImageManagerFe::ScanImagesAction(bool start, bool local)
2038{
2039 QStringList command;
2040 command << (start ? "START" : "STOP");
2041
2042 if (!local)
2043 {
2044 command.push_front("IMAGE_SCAN");
2045 bool ok = gCoreContext->SendReceiveStringList(command, true);
2046 return ok ? "" : command[1];
2047 }
2048
2049 // Create database on first scan
2050 if (!CreateTable())
2051 return "Couldn't create database";
2052
2053 QStringList err = HandleScanRequest(command[0]);
2054 return err[0] == "OK" ? "" : err[1];
2055}
2056
2057
2064{
2065 QStringList strList;
2066 strList << "IMAGE_SCAN" << "QUERY";
2067
2068 if (!gCoreContext->SendReceiveStringList(strList))
2069 {
2070 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Scan query failed : %1")
2071 .arg(strList.join(",")));
2072 }
2073 return strList;
2074}
2075
2076
2083QString ImageManagerFe::HideFiles(bool hidden, const ImageIdList &ids)
2084{
2085 // Split images into <locals, remotes>
2087 QString result = "";
2088
2089 if (!lists.second.isEmpty())
2090 {
2091 QStringList message;
2092 message << "IMAGE_HIDE" << QString::number(static_cast<int>(hidden)) << lists.second;
2093
2094 if (!gCoreContext->SendReceiveStringList(message, true))
2095 result = message[1];
2096 }
2097
2098 if (!lists.first.isEmpty())
2099 {
2100 QStringList err = HandleHide(hidden, lists.first);
2101 if (err[0] != "OK")
2102 result = err[1];
2103 }
2104 return result;
2105}
2106
2107
2115 const ImageIdList &ids)
2116{
2117 // Split images into <locals, remotes>
2119 QString result = "";
2120
2121 if (!lists.second.isEmpty())
2122 {
2123 QStringList message;
2124 message << "IMAGE_TRANSFORM" << QString::number(transform) << lists.second;
2125
2126 if (!gCoreContext->SendReceiveStringList(message, true))
2127 result = message[1];
2128 }
2129
2130 if (!lists.first.isEmpty())
2131 {
2132 QStringList err = HandleTransform(transform, lists.first);
2133 if (err[0] != "OK")
2134 result = err[1];
2135 }
2136 return result;
2137}
2138
2139
2146QString ImageManagerFe::SetCover(int parent, int cover)
2147{
2148 if (!ImageItem::IsLocalId(parent))
2149 {
2150 QStringList message;
2151 message << "IMAGE_COVER" << QString::number(parent) << QString::number(cover);
2152
2153 bool ok = gCoreContext->SendReceiveStringList(message, true);
2154 return ok ? "" : message[1];
2155 }
2156
2157 QStringList err = HandleCover(parent, cover);
2158 return err[0] == "OK" ? "" : err[1];
2159}
2160
2161
2167{
2168 if (ImageItem::IsLocalId(id))
2169 HandleGetMetadata(QString::number(id));
2170 else
2171 gCoreContext->SendEvent(MythEvent("IMAGE_GET_METADATA", QString::number(id)));
2172}
2173
2174
2177{
2178 QStringList message("IMAGE_SCAN");
2179 message << "DEVICE CLEAR ALL";
2180 gCoreContext->SendReceiveStringList(message, true);
2181}
2182
2183
2190QString ImageManagerFe::IgnoreDirs(const QString &excludes)
2191{
2192 QStringList message("IMAGE_IGNORE");
2193 message << excludes;
2194 bool ok = gCoreContext->SendReceiveStringList(message, true);
2195 return ok ? "" : message[1];
2196}
2197
2198
2206QString ImageManagerFe::MakeDir(int parent, const QStringList &names, bool rescan)
2207{
2208 QString destId = QString::number(parent);
2209
2210 if (!ImageItem::IsLocalId(parent))
2211 {
2212 QStringList message("IMAGE_CREATE_DIRS");
2213 message << destId << QString::number(static_cast<int>(rescan)) << names;
2214 bool ok = gCoreContext->SendReceiveStringList(message, true);
2215 return ok ? "" : message[1];
2216 }
2217 QStringList err = HandleDirs(destId, rescan, names);
2218 return (err[0] == "OK") ? "" : err[1];
2219}
2220
2221
2228QString ImageManagerFe::RenameFile(const ImagePtrK& im, const QString &name)
2229{
2230 if (!im->IsLocal())
2231 {
2232 QStringList message("IMAGE_RENAME");
2233 message << QString::number(im->m_id) << name;
2234 bool ok = gCoreContext->SendReceiveStringList(message, true);
2235 return ok ? "" : message[1];
2236 }
2237 QStringList err = HandleRename(QString::number(im->m_id), name);
2238 return (err[0] == "OK") ? "" : err[1];
2239}
2240
2241
2248QString ImageManagerFe::CreateImages(int destId, const ImageListK &images)
2249{
2250 if (images.isEmpty())
2251 return "";
2252
2253 // Define field seperator & include it in message
2254 const QString seperator("...");
2255 QStringList imageDefs(seperator);
2256 ImageIdList ids;
2257 for (const auto& im : std::as_const(images))
2258 {
2259 ids << im->m_id;
2260
2261 // Copies preserve hide state, orientation & cover
2262 QStringList aDef;
2263 aDef << QString::number(im->m_id)
2264 << QString::number(im->m_type)
2265 << im->m_filePath
2266 << QString::number(static_cast<int>(im->m_isHidden))
2267 << QString::number(im->m_orientation)
2268 << QString::number(im->m_userThumbnail);
2269
2270 imageDefs << aDef.join(seperator);
2271 }
2272
2273 // Images are either all local or all remote
2274 if (ImageItem::IsLocalId(destId))
2275 {
2276 QStringList err = HandleDbCreate(imageDefs);
2277 return (err[0] == "OK") ? "" : err[1];
2278 }
2279 imageDefs.prepend("IMAGE_COPY");
2280 bool ok = gCoreContext->SendReceiveStringList(imageDefs, true);
2281 return ok ? "" : imageDefs[1];
2282}
2283
2284
2292QString ImageManagerFe::MoveDbImages(const ImagePtrK& destDir, ImageListK &images,
2293 const QString &srcPath)
2294{
2295 QStringList idents;
2296 for (const auto& im : std::as_const(images))
2297 idents << QString::number(im->m_id);
2298
2299 // Images are either all local or all remote
2300 if (destDir->IsLocal())
2301 {
2302 QStringList err = HandleDbMove(idents.join(","), srcPath,
2303 destDir->m_filePath);
2304 return (err[0] == "OK") ? "" : err[1];
2305 }
2306
2307 QStringList message("IMAGE_MOVE");
2308 message << idents.join(",") << srcPath << destDir->m_filePath;
2309 bool ok = gCoreContext->SendReceiveStringList(message, true);
2310 return ok ? "" : message[1];
2311}
2312
2313
2320{
2322
2323 QString result = "";
2324 if (!lists.second.isEmpty())
2325 {
2326 QStringList message("IMAGE_DELETE");
2327 message << lists.second;
2328
2329 bool ok = gCoreContext->SendReceiveStringList(message, true);
2330 if (!ok)
2331 result = message[1];
2332 }
2333 if (!lists.first.isEmpty())
2334 {
2335 QStringList err = HandleDelete(lists.first);
2336 if (err[0] != "OK")
2337 result = err[1];
2338 }
2339 return result;
2340}
2341
2342
2350{
2351 if (im->m_id == GALLERY_DB_ID)
2352 return "";
2353
2354 std::chrono::seconds secs = 0s;
2356
2357 if (im->m_date > 0s)
2358 {
2359 secs = im->m_date;
2360 format |= MythDate::kTime;
2361 }
2362 else
2363 {
2364 secs = im->m_modTime;
2365 }
2366
2367 return MythDate::toString(QDateTime::fromSecsSinceEpoch(secs.count()), format);
2368}
2369
2370
2378{
2379 if (im->m_id == GALLERY_DB_ID)
2380 return "";
2381
2382 std::chrono::seconds secs(im->m_date > 0s ? im->m_date : im->m_modTime);
2383 return QDateTime::fromSecsSinceEpoch(secs.count()).date().toString(m_dateFormat);
2384}
2385
2386
2393{
2394 if (im.m_id == GALLERY_DB_ID)
2395 return tr("Gallery");
2396 if (im.m_id == PHOTO_DB_ID)
2397 return tr("Photographs");
2398 return im.IsLocal() ? DeviceName(im.m_device)
2400}
2401
2402
2410QString ImageManagerFe::CrumbName(ImageItemK &im, bool getPath) const
2411{
2412 if (im.IsDevice())
2413 return DeviceCaption(im);
2414
2415 if (!getPath)
2416 return im.m_baseName;
2417
2418 QString dev;
2419 QString path(im.m_filePath);
2420
2421 if (im.IsLocal())
2422 {
2423 // Replace local mount path with device name
2424 path.remove(0, DeviceMount(im.m_device).size());
2425 dev = DeviceName(im.m_device);
2426 }
2427 return dev + path.replace("/", " > ");
2428}
2429
2430
2431void ImageManagerFe::CloseDevices(int devId, bool eject)
2432{
2433 QString reason { "DEVICE REMOVE" };
2434 if (devId == DEVICE_INVALID)
2435 reason = "DEVICE CLOSE ALL";
2436 else if (eject)
2437 reason = "DEVICE EJECT";
2438 HandleScanRequest(reason, devId);
2439}
2440
2441
2447{
2449 if (!monitor)
2450 return false;
2451
2452 // Detect all local media
2453 QList<MythMediaDevice*> devices
2455
2456 for (auto *dev : std::as_const(devices))
2457 {
2458 if (monitor->ValidateAndLock(dev) && dev->isUsable())
2459 OpenDevice(dev->getDeviceModel(), dev->getMountPath(), dev);
2460 else
2461 monitor->Unlock(dev);
2462 }
2463
2464 if (DeviceCount() > 0)
2465 {
2466 // Close devices that are no longer present
2467 QList absentees = GetAbsentees();
2468 for (int devId : std::as_const(absentees))
2469 CloseDevices(devId);
2470
2471 // Start local scan
2472 QString err = ScanImagesAction(true, true);
2473 if (!err.isEmpty())
2474 LOG(VB_GENERAL, LOG_ERR, LOC + err);
2475 }
2476 return DeviceCount() > 0;
2477}
2478
2479
2485{
2487
2488 if (!event || !monitor)
2489 return;
2490
2491 MythMediaDevice *dev = event->getDevice();
2492 if (!dev)
2493 return;
2494
2496 MythMediaStatus status = dev->getStatus();
2497
2498 LOG(VB_FILE, LOG_DEBUG, LOC +
2499 QString("Media event for %1 (%2) at %3, type %4, status %5 (was %6)")
2500 .arg(dev->getDeviceModel(), dev->getVolumeID(), dev->getMountPath())
2501 .arg(type).arg(status).arg(event->getOldStatus()));
2502
2504 {
2505 LOG(VB_FILE, LOG_DEBUG, LOC +
2506 QString("Ignoring event - wrong type %1").arg(type));
2507 return;
2508 }
2509
2510 if (status == MEDIASTAT_USEABLE || status == MEDIASTAT_MOUNTED)
2511 {
2512 // New device. Lock it & scan
2513 if (monitor->ValidateAndLock(dev))
2514 {
2515 OpenDevice(dev->getDeviceModel(), dev->getMountPath(), dev);
2516 ScanImagesAction(true, true);
2517 }
2518 else
2519 {
2520 monitor->Unlock(dev);
2521 }
2522 return;
2523 }
2524
2525 // Device has disappeared
2526 int devId = LocateMount(dev->getMountPath());
2527 if (devId != DEVICE_INVALID)
2528 CloseDevices(devId);
2529}
2530
2531
2533{
2534 auto *tmp = new QTemporaryDir(QDir::tempPath() % "/" % IMPORTDIR % "-XXXXXX");
2535 if (!tmp->isValid())
2536 {
2537 delete tmp;
2538 return "";
2539 }
2540
2541 QString time(QDateTime::currentDateTime().toString("mm:ss"));
2542 OpenDevice("Import " + time, tmp->path(), nullptr, tmp);
2543 return tmp->path();
2544}
2545
2546
2547// Must define the valid template implementations to generate code for the
2548// instantiations (as they are defined in the cpp rather than header).
2549// Otherwise the linker will fail with undefined references...
2550template class ImageDb<ImageAdapterSg>;
2551template class ImageDb<ImageAdapterLocal>;
2552template class ImageHandler<ImageDbSg>;
2553template class ImageHandler<ImageDbLocal>;
bool RemoveFromDB(Bookmark *site)
static const std::array< const std::string, 8 > formats
QList< int > GetAbsentees()
Get list of mountpoints for non-import devices.
int LocateMount(const QString &mount) const
Find the id of a device.
QString ThumbDir(int fs) const
QStringList CloseDevices(int devId, const QString &action)
Remove a device (or all devices)
QString DeviceMount(int devId) const
Get path at which the device is mounted.
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...
QString DeviceName(int devId) const
Get model name of the device.
DeviceMap m_devices
Device store.
Definition: imagemanager.h:119
StringMap GetDeviceDirs() const
Get all known devices.
A device containing images (ie. USB stick, CD, storage group etc)
void RemoveThumbs(void) const
Delete thumbnails associated with device.
void setPresent(MythMediaDevice *media)
QString m_thumbs
Dir sub-path of device thumbnails.
bool m_present
True when gallery UI is running & device is useable. Always true for imports.
Device(QString name, QString mount, MythMediaDevice *media=nullptr, QTemporaryDir *import=nullptr)
void Close(bool eject=false)
Releases device.
MythMediaDevice * m_media
Set for MediaMonitor devices only.
~Device()
Delete device, its thumbnails and any imported images.
static void RemoveDirContents(const QString &path)
Clears all files and sub-dirs within a directory.
QString m_name
Device model/volume/id.
bool isImport() const
QString m_mount
Mountpoint.
QTemporaryDir * m_dir
Dir path of images: import devices only.
bool isPresent() const
static FileAssociations & getFileAssociation()
Definition: dbaccess.cpp:840
const association_list & getList() const
Definition: dbaccess.cpp:815
std::vector< file_association > association_list
Definition: dbaccess.h:154
static QString ThumbPath(const ImageItem &im)
Thumbnails of videos are a JPEG snapshot with jpg suffix appended.
Definition: imagemanager.h:155
static QString PathOf(const QString &path)
Extracts path from a filepath.
Definition: imagemanager.h:140
static QStringList SupportedImages()
Return recognised pictures.
ImageNodeType GetImageType(const QString &ext) const
Determine file type from its extension.
Definition: imagemanager.h:168
static QString FormatSize(int sizeKib)
Definition: imagemanager.h:143
static QStringList SupportedVideos()
Return recognised video extensions.
ImageAdapterBase()
Constructor.
static QString BaseNameOf(const QString &path)
Extracts file name (incl extension) from a filepath.
Definition: imagemanager.h:136
static QString GetAbsThumbPath(const QString &devPath, const QString &path)
Get absolute filepath for thumbnail of an image.
Definition: imagemanager.h:148
static void Notify(const QString &mesg, const QStringList &extra)
Send local message to UI about local ids.
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.
StorageGroup m_sg
Images storage group.
Definition: imagemanager.h:265
QString m_hostname
Host of SG.
Definition: imagemanager.h:261
QString MakeFileUrl(const QString &path) const
Construct URL of a remote image.
QString MakeThumbUrl(const QString &devPath, const QString &path="") const
Construct URL of the thumbnail of a remote image.
ImageItem * CreateItem(const QFileInfo &fi, int parentId, int devId, const QString &base) const
Construct a remote image from a file.
QString GetAbsFilePath(const ImagePtrK &im) const
Get absolute filepath for a remote image.
static void Notify(const QString &mesg, const QStringList &extra)
Send message to all clients about remote ids.
ImageDbLocal()
Local database constructor.
bool CreateTable()
Create local database table, if it doesn't exist.
void DropTable()
Remove local image table.
void GetDescendants(const ImageIdList &ids, ImageList &files, ImageList &dirs) const
Return all (local or remote) images that are direct children of a dir.
int GetDirectory(int id, ImagePtr &parent, ImageList &files, ImageList &dirs) const
Return images (local and/or remote) for a dir and its direct children.
QString m_refineClause
SQL clause for image filtering/ordering.
Definition: imagemanager.h:443
int m_fileOrder
Display ordering of pics/videos.
Definition: imagemanager.h:440
static QString OrderSelector(int order)
Generate SQL ordering clause.
ImageDbSg * m_remote
Remote database access.
Definition: imagemanager.h:437
int GetImages(const ImageIdList &ids, ImageList &files, ImageList &dirs) const
Returns images (local or remote but not a combination)
bool m_showHidden
Whether hidden images are displayed.
Definition: imagemanager.h:441
static QString TypeSelector(int type)
Generate SQL type filter clause.
void GetImageTree(int id, ImageList &files) const
Return all files (local or remote) in the sub-trees of a dir.
void SetRefinementClause()
Sets filter/ordering SQL clause used when reading database according to current filter/sort settings.
int GetChildren(int id, ImageList &files, ImageList &dirs) const
Return (local or remote) images that are direct children of a dir.
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.
int m_showType
Type of images to display - pic only/video only/both.
Definition: imagemanager.h:442
int m_dirOrder
Display ordering of dirs.
Definition: imagemanager.h:439
ImageDbSg()
SG database constructor.
Database API.
Definition: imagemanager.h:274
void ClearDb(int devId, const QString &action)
Clear Db for device & remove device.
int ReadImages(ImageList &dirs, ImageList &files, const QString &selector) const
Read selected database images/dirs.
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.
int GetDirectory(int id, ImagePtr &parent, ImageList &files, ImageList &dirs, const QString &refine) const
Read a dir and its immediate children from Db.
bool SetOrientation(int id, int orientation) const
Sets image orientation in Db.
int GetChildren(const QString &ids, ImageList &files, ImageList &dirs, const QString &refine="") const
Read immediate children of a dir.
bool SetHidden(bool hide, const QString &ids) const
Sets hidden status of an image/dir in database.
bool GetImageTree(int id, ImageList &files, const QString &refine) const
Returns all files in the sub-tree of a dir.
ImageItem * CreateImage(const MSqlQuery &query) const
Create image from Db query data.
bool UpdateDbImage(ImageItemK &im) const
Updates or creates database image or dir.
int InsertDbImage(ImageItemK &im, bool checkForDuplicate=false) const
Adds new image to database, optionally checking for existing filepath.
bool GetDescendants(const QString &ids, ImageList &files, ImageList &dirs) const
Return images and all of their descendants.
bool ReadAllImages(ImageHash &files, ImageHash &dirs) const
Read all database images and dirs as map. No filters or ordering applied.
QString m_table
Db table name.
Definition: imagemanager.h:309
int GetImages(const QString &ids, ImageList &files, ImageList &dirs, const QString &refine="") const
Read database images/dirs by id.
bool SetCover(int dir, int id) const
Set the thumbnail(s) to be used for a dir.
QStringList RemoveFromDB(const ImageList &imList) const
Remove images/dirs from database.
QStringList HandleDbCreate(QStringList defs) const
Creates images for files created by a copy operation.
QStringList HandleCover(int dir, int cover) const
Updates/resets cover thumbnail for an image dir.
QStringList HandleDelete(const QString &ids) const
Deletes images/dirs.
void RemoveFiles(ImageList &images) const
Deletes images and dirs from the filesystem.
QStringList HandleTransform(int transform, const QString &ids) const
Change orientation of pictures by applying a transformation.
QStringList HandleHide(bool hide, const QString &ids) const
Hides/unhides images/dirs.
QStringList HandleDirs(const QString &destId, bool rescan, const QStringList &relPaths) const
Creates new image directories.
QStringList HandleCreateThumbnails(const QStringList &message) const
Creates thumbnails on-demand.
QStringList HandleGetMetadata(const QString &id) const
Read meta data for an image.
QStringList HandleScanRequest(const QString &command, int devId=DEVICE_INVALID) const
Process scan requests.
QStringList HandleDbMove(const QString &ids, const QString &srcPath, QString destPath) const
Updates images that have been renamed.
QStringList HandleRename(const QString &id, const QString &newBase) const
Change name of an image/dir.
QStringList HandleIgnore(const QString &exclusions) const
Updates exclusion list for images.
Represents a picture, video or directory.
Definition: imagetypes.h:69
static bool IsLocalParent(int id)
Parents of locals are locals or root.
Definition: imagetypes.h:124
int m_id
Uniquely identifies an image (file/dir).
Definition: imagetypes.h:89
QString m_extension
Image file extension.
Definition: imagetypes.h:94
bool IsLocal() const
Definition: imagetypes.h:119
std::chrono::seconds m_date
Image creation date, from Exif metadata.
Definition: imagetypes.h:100
int m_size
Filesize (files only)
Definition: imagetypes.h:99
static bool IsLocalId(int id)
Determine image type (local/remote) from its id. Root/Gallery is remote.
Definition: imagetypes.h:122
bool m_isHidden
If true, image won't be shown.
Definition: imagetypes.h:105
QString m_comment
User comment, from Exif metadata.
Definition: imagetypes.h:102
QString m_baseName
File/Dir name with extension (no path)
Definition: imagetypes.h:92
int m_parentId
Id of parent dir.
Definition: imagetypes.h:96
int m_device
Id of media device. Always 0 (SG) for remotes, 1+ for local devices.
Definition: imagetypes.h:95
bool IsDevice() const
Definition: imagetypes.h:116
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:140
QString m_filePath
Absolute for local images. Usually SG-relative for remotes.
Definition: imagetypes.h:93
std::chrono::seconds m_modTime
Filesystem modified datestamp.
Definition: imagetypes.h:98
int m_orientation
Image orientation.
Definition: imagetypes.h:101
int m_userThumbnail
Id of thumbnail to use as cover (dirs only)
Definition: imagetypes.h:106
int m_type
Type of node: dir, video etc.
Definition: imagetypes.h:97
The image manager to be used by the Backend.
Definition: imagemanager.h:378
static ImageManagerBe * s_instance
BE Gallery instance.
Definition: imagemanager.h:390
static ImageManagerBe * getInstance()
Get Backend Gallery.
The image manager for use by Frontends.
Definition: imagemanager.h:456
QString ShortDateOf(const ImagePtrK &im) const
Return a short datestamp for thumbnail captions.
void RequestMetaData(int id)
Requests all exif/ffmpeg tags for an image, which returns by event.
QString ChangeOrientation(ImageFileTransform transform, const ImageIdList &ids)
Apply an orientation transform to images.
QString CreateImages(int destId, const ImageListK &images)
Copies database images (but not the files themselves).
ImageManagerFe(int order, int dirOrder, bool showAll, int showType, QString dateFormat)
Definition: imagemanager.h:500
static void ClearStorageGroup()
Clear database & thumbnails of Storage Group images.
QString MoveDbImages(const ImagePtrK &destDir, ImageListK &images, const QString &srcPath)
Moves database images (but not the files themselves).
QString CrumbName(ImageItemK &im, bool getPath=false) const
Return a displayable name (with optional path) for an image.
static QString LongDateOf(const ImagePtrK &im)
Return a timestamp/datestamp for an image or dir.
static QStringList ScanQuery()
Returns storage group scanner status.
void DeviceEvent(MythMediaEvent *event)
Manage events for local devices.
QString DeleteFiles(const ImageIdList &ids)
Delete images.
QString DeviceCaption(ImageItemK &im) const
Return translated device name.
QString MakeDir(int parent, const QStringList &names, bool rescan=true)
Create directories.
QString SetCover(int parent, int cover)
Set image to use as a cover thumbnail(s)
static ImageManagerFe * s_instance
FE Gallery instance.
Definition: imagemanager.h:510
QString CreateImport()
QString HideFiles(bool hidden, const ImageIdList &ids)
Hide/unhide images.
void CloseDevices(int devId=DEVICE_INVALID, bool eject=false)
static QString IgnoreDirs(const QString &excludes)
Set directories to ignore during scans of the storage group.
static ImageManagerFe & getInstance()
Get Frontend Gallery.
void CreateThumbnails(const ImageIdList &ids, bool forFolder)
Create thumbnails or verify that they already exist.
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...
QString ScanImagesAction(bool start, bool local=false)
Handle scanner start/stop commands.
QString m_dateFormat
UI format for thumbnail date captions.
Definition: imagemanager.h:513
int DeviceCount() const
Definition: imagemanager.h:100
QString RenameFile(const ImagePtrK &im, const QString &name)
Rename an image.
bool DetectLocalDevices()
Detect and scan local devices.
Abstract class for image metadata.
Definition: imagemetadata.h:92
static ImageMetaData * FromPicture(const QString &filePath)
Factory to retrieve metadata from pictures.
virtual QStringList GetAllTags()=0
static QString ToString(const QString &name, const QString &label, const QString &value)
Encodes metadata into a string as <tag name><tag label><tag value>
static ImageMetaData * FromVideo(const QString &filePath)
Factory to retrieve metadata from videos.
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:838
QVariant value(int i) const
Definition: mythdbcon.h:204
int size(void) const
Definition: mythdbcon.h:214
void bindValueNoNull(const QString &placeholder, const QVariant &val)
Add a single binding, taking care not to set a NULL value.
Definition: mythdbcon.cpp:903
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
QVariant lastInsertId()
Return the id of the last inserted row.
Definition: mythdbcon.cpp:936
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:813
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
static MThreadPool * globalInstance(void)
void start(QRunnable *runnable, const QString &debugName, int priority=0)
static MediaMonitor * GetMediaMonitor(void)
bool ValidateAndLock(MythMediaDevice *pMedia)
Validates the MythMediaDevice and increments its reference count.
void Unlock(MythMediaDevice *pMedia)
decrements the MythMediaDevices reference count
void EjectMedia(const QString &path)
QList< MythMediaDevice * > GetMedias(unsigned mediatypes)
Ask for available media.
This class contains the runtime context for MythTV.
QString GetHostName(void)
QString GetSetting(const QString &key, const QString &defaultval="")
bool SaveSettingOnHost(const QString &key, const QString &newValue, const QString &host)
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
int GetNumSetting(const QString &key, int defaultval=0)
void SendEvent(const MythEvent &event)
bool GetBoolSetting(const QString &key, bool defaultval=false)
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
This class is used as a container for messages.
Definition: mythevent.h:17
const QString & getMountPath() const
Definition: mythmedia.h:58
MythMediaStatus getStatus() const
Definition: mythmedia.h:70
const QString & getDevicePath() const
Definition: mythmedia.h:61
const QString & getVolumeID() const
Definition: mythmedia.h:72
MythMediaType getMediaType() const
Definition: mythmedia.h:91
const QString & getDeviceModel() const
Definition: mythmedia.h:67
MythMediaStatus getOldStatus(void) const
Definition: mythmedia.h:190
Encapsulates Exif orientation processing.
Definition: imagemetadata.h:63
int Transform(int transform)
Adjust orientation to apply a transform to an image.
QString Description() const
Generate text description of orientation.
Task to read all metadata from file.
void run() override
ReadMetaThread(ImagePtrK im, QString path)
QStringList GetDirList(void) const
Definition: storagegroup.h:23
QString FindFile(const QString &filename)
QString FindNextDirMostFree(void)
unsigned int uint
Definition: compat.h:60
#define LOC
static constexpr const char * STORAGE_GROUP_MOUNT
static Device kNullDevice
#define RESULT_OK(MESG)
#define RESULT_ERR(ERR, MESG)
#define DBLOC
static constexpr const char * DB_TABLE
static constexpr const char * IMPORTDIR
static constexpr const char * kDBColumns
Manages a collection of images.
static constexpr const char * IMAGE_STORAGE_GROUP
Definition: imagemanager.h:63
static constexpr int DEVICE_INVALID
Definition: imagemanager.h:71
static constexpr const char * TEMP_SUBDIR
Definition: imagemanager.h:67
@ kVideoOnly
Hide pictures.
Definition: imagemanager.h:80
@ kPicAndVideo
Show Pictures & Videos.
Definition: imagemanager.h:78
@ kPicOnly
Hide videos.
Definition: imagemanager.h:79
static constexpr const char * THUMBNAIL_SUBDIR
Definition: imagemanager.h:69
static constexpr const char * THUMBNAIL_STORAGE_GROUP
Definition: imagemanager.h:64
static constexpr const char * EXIF_MYTH_ORIENT
Definition: imagemetadata.h:42
static constexpr const char * EXIF_MYTH_SIZE
Definition: imagemetadata.h:41
static constexpr const char * EXIF_MYTH_HOST
Definition: imagemetadata.h:38
ImageFileTransform
Image transformations.
Definition: imagemetadata.h:46
@ kFlipVertical
Reflect about horizontal axis.
Definition: imagemetadata.h:51
static constexpr const char * EXIF_MYTH_NAME
Definition: imagemetadata.h:40
static constexpr const char * EXIF_MYTH_PATH
Definition: imagemetadata.h:39
@ kPicRequestPriority
Client request to display an image thumbnail.
Definition: imagethumbs.h:34
@ kDirRequestPriority
Client request to display a directory thumbnail.
Definition: imagethumbs.h:35
QHash< QString, ImagePtr > ImageHash
Definition: imagetypes.h:161
QVector< ImagePtr > ImageList
Definition: imagetypes.h:160
@ kSortBySizeAsc
File size Smallest -> Largest.
Definition: imagetypes.h:52
@ kSortByNameAsc
Name A-Z.
Definition: imagetypes.h:46
@ kSortByDateAsc
Exif date Earliest -> Latest.
Definition: imagetypes.h:54
@ kSortByExtAsc
Extension A-Z.
Definition: imagetypes.h:50
@ kSortByExtDesc
Extension Z-A.
Definition: imagetypes.h:51
@ kSortByNameDesc
Name Z-A.
Definition: imagetypes.h:47
@ kSortBySizeDesc
File size Largest -> Smallest.
Definition: imagetypes.h:53
@ kSortByModTimeAsc
File modified time Earliest -> Latest.
Definition: imagetypes.h:48
@ kSortByModTimeDesc
File modified time Latest -> Earliest.
Definition: imagetypes.h:49
@ kSortByDateDesc
Exif date Latest -> Earliest.
Definition: imagetypes.h:55
QList< ImagePtrK > ImageListK
Definition: imagetypes.h:166
QSharedPointer< ImageItemK > ImagePtrK
Definition: imagetypes.h:165
QMap< int, QString > StringMap
Definition: imagetypes.h:63
static constexpr int GALLERY_DB_ID
Definition: imagetypes.h:27
static constexpr int PHOTO_DB_ID
Definition: imagetypes.h:29
@ kDevice
Storage Group and local mounted media.
Definition: imagetypes.h:36
@ kDirectory
A device sub directory.
Definition: imagetypes.h:38
@ kImageFile
A picture.
Definition: imagetypes.h:39
@ kUnknown
Unprocessable file type.
Definition: imagetypes.h:35
@ kVideoFile
A video.
Definition: imagetypes.h:40
QSharedPointer< ImageItem > ImagePtr
Definition: imagetypes.h:159
QPair< QString, QString > StringPair
Definition: imagetypes.h:61
QList< int > ImageIdList
Definition: imagetypes.h:60
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static void clear(SettingsMap &cache, SettingsMap &overrides, const QString &myKey)
Definition: mythdb.cpp:948
QString GetConfDir(void)
Definition: mythdirs.cpp:285
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMediaType
Definition: mythmedia.h:24
@ MEDIATYPE_MIXED
Definition: mythmedia.h:27
@ MEDIATYPE_MGALLERY
Definition: mythmedia.h:33
@ MEDIATYPE_DATA
Definition: mythmedia.h:26
MythMediaStatus
Definition: mythmedia.h:12
@ MEDIASTAT_USEABLE
Definition: mythmedia.h:19
@ MEDIASTAT_MOUNTED
Definition: mythmedia.h:21
MBASE_PUBLIC QDateTime fromSecsSinceEpoch(int64_t seconds)
This function takes the number of seconds since the start of the epoch and returns a QDateTime with t...
Definition: mythdate.cpp:81
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
@ kDateFull
Default local time.
Definition: mythdate.h:19
@ kTime
Default local time.
Definition: mythdate.h:22
@ kAddYear
Add year to string if not included.
Definition: mythdate.h:25
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:80