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 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Deleted %1").arg(absFilename));
1742 else
1743 {
1744 LOG(VB_GENERAL, LOG_ERR, LOC +
1745 QString("Can't delete %1").arg(absFilename));
1746 // Remove from list
1747 it = images.erase(it);
1748 }
1749 }
1750}
1751
1752
1759{
1760 switch (type)
1761 {
1762 case kPicOnly: return QString("AND type != %1").arg(kVideoFile);
1763 case kVideoOnly: return QString("AND type != %1").arg(kImageFile);
1764 case kPicAndVideo: return "";
1765 }
1766 return "";
1767}
1768
1769
1776{
1777 m_refineClause = QString("%2 %3 "
1778 "ORDER BY "
1779 "CASE WHEN type <= %1 THEN %4, "
1780 "CASE WHEN type > %1 THEN %5 ")
1781 .arg(kDirectory)
1782 .arg(m_showHidden ? "" : "AND hidden = 0",
1786}
1787
1788
1795{
1796 // prepare the sorting statement
1797 switch (order)
1798 {
1799 default:
1800 case kSortByNameAsc: return "name END ASC";
1801 case kSortByNameDesc: return "name END DESC";
1802 case kSortByModTimeAsc: return "modtime END ASC";
1803 case kSortByModTimeDesc: return "modtime END DESC";
1804 case kSortByExtAsc: return "extension END ASC, name ASC";
1805 case kSortByExtDesc: return "extension END DESC, name DESC";
1806 case kSortBySizeAsc: return "size END ASC, name ASC";
1807 case kSortBySizeDesc: return "size END DESC, name DESC";
1808 case kSortByDateAsc: return "IF(date=0, modtime, date) END ASC";
1809 case kSortByDateDesc: return "IF(date=0, modtime, date) END DESC";
1810 }
1811}
1812
1813
1823 ImageList &files, ImageList &dirs) const
1824{
1825 // Only Root node will invoke both Db queries but result set will be small
1826 // For Root the SG is always ordered before local devices
1827 // Root node has no Db entry so the 2 queries will not overwrite the parent.
1828 int count = 0;
1829 if (!ImageItem::IsLocalId(id))
1830 count = m_remote->GetDirectory(id, parent, files, dirs, m_refineClause);
1832 count += ImageHandler::GetDirectory(id, parent, files, dirs, m_refineClause);
1833
1834 if (id == GALLERY_DB_ID)
1835 {
1836 // Add a Root node
1837 parent = ImagePtr(new ImageItem(GALLERY_DB_ID));
1838 parent->m_parentId = GALLERY_DB_ID;
1839 parent->m_type = kDevice;
1840
1841 ++count;
1842 }
1843 return count;
1844}
1845
1846
1855 ImageList &files, ImageList &dirs) const
1856{
1857 // Ids are either all local or all remote. GALLERY_DB_ID not valid
1859
1860 if (!lists.second.isEmpty())
1861 return m_remote->GetImages(lists.second, files, dirs, m_refineClause);
1862 if (m_dbExists && !lists.first.isEmpty())
1863 return ImageHandler::GetImages(lists.first, files, dirs, m_refineClause);
1864 return 0;
1865}
1866
1867
1875int ImageDbReader::GetChildren(int id, ImageList &files, ImageList &dirs) const
1876{
1877 int count = 0;
1878 if (!ImageItem::IsLocalId(id))
1879 count = m_remote->GetChildren(QString::number(id), files, dirs,
1882 count += ImageHandler::GetChildren(QString::number(id), files, dirs,
1884 return count;
1885}
1886
1887
1895 ImageList &files, ImageList &dirs) const
1896{
1897 // Ids are either all local or all remote
1899
1900 if (!lists.second.isEmpty())
1901 m_remote->GetDescendants(lists.second, files, dirs);
1902 if (m_dbExists && !lists.first.isEmpty())
1903 ImageHandler::GetDescendants(lists.first, files, dirs);
1904}
1905
1906
1913void ImageDbReader::GetImageTree(int id, ImageList &files) const
1914{
1915 if (!ImageItem::IsLocalId(id))
1918 ImageHandler::GetImageTree(id, files, m_refineClause);
1919}
1920
1921
1930void ImageDbReader::GetDescendantCount(int id, int &dirs, int &pics,
1931 int &videos, int &sizeKb) const
1932{
1933 if (id == GALLERY_DB_ID)
1934 {
1935 // Sum both unfiltered tables
1936 m_remote->GetDescendantCount(id, true, dirs, pics, videos, sizeKb);
1937 if (m_dbExists)
1938 ImageHandler::GetDescendantCount(id, true, dirs, pics, videos, sizeKb);
1939 }
1940 else if (!ImageItem::IsLocalId(id))
1941 {
1942 // Don't filter on SG path (it's blank)
1944 dirs, pics, videos, sizeKb);
1945 }
1946 else if (m_dbExists)
1947 {
1948 // Always filter on device/dir
1949 ImageHandler::GetDescendantCount(id, false, dirs, pics, videos, sizeKb);
1950 }
1951}
1952
1953
1958
1959
1965{
1966 if (!s_instance)
1967 s_instance = new ImageManagerBe();
1968 return s_instance;
1969}
1970
1971
1977{
1978 if (!s_instance)
1979 {
1980 // Use saved settings
1982 (gCoreContext->GetNumSetting("GalleryImageOrder"),
1983 gCoreContext->GetNumSetting("GalleryDirOrder"),
1984 gCoreContext->GetBoolSetting("GalleryShowHidden"),
1985 gCoreContext->GetNumSetting("GalleryShowType"),
1986 gCoreContext->GetSetting("GalleryDateFormat"));
1987 }
1988 return *s_instance;
1989}
1990
1991
2000void ImageManagerFe::CreateThumbnails(const ImageIdList &ids, bool forFolder)
2001{
2002 // Split images into <locals, remotes>
2004
2005 if (!lists.second.isEmpty())
2006 {
2007 LOG(VB_FILE, LOG_DEBUG, LOC +
2008 QString("Sending CREATE_THUMBNAILS %1 (forFolder %2)")
2009 .arg(lists.second).arg(forFolder));
2010
2011 QStringList message;
2012 message << QString::number(static_cast<int>(forFolder)) << lists.second;
2013 gCoreContext->SendEvent(MythEvent("CREATE_THUMBNAILS", message));
2014 }
2015
2016 if (!lists.first.isEmpty())
2017 {
2018 LOG(VB_FILE, LOG_DEBUG, LOC +
2019 QString("Creating local thumbnails %1 (forFolder %2)")
2020 .arg(lists.first).arg(forFolder));
2021
2022 QStringList message;
2023 message << QString::number(static_cast<int>(forFolder)) << lists.first;
2024 HandleCreateThumbnails(message);
2025 }
2026}
2027
2028
2035QString ImageManagerFe::ScanImagesAction(bool start, bool local)
2036{
2037 QStringList command;
2038 command << (start ? "START" : "STOP");
2039
2040 if (!local)
2041 {
2042 command.push_front("IMAGE_SCAN");
2043 bool ok = gCoreContext->SendReceiveStringList(command, true);
2044 return ok ? "" : command[1];
2045 }
2046
2047 // Create database on first scan
2048 if (!CreateTable())
2049 return "Couldn't create database";
2050
2051 QStringList err = HandleScanRequest(command[0]);
2052 return err[0] == "OK" ? "" : err[1];
2053}
2054
2055
2062{
2063 QStringList strList;
2064 strList << "IMAGE_SCAN" << "QUERY";
2065
2066 if (!gCoreContext->SendReceiveStringList(strList))
2067 {
2068 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Scan query failed : %1")
2069 .arg(strList.join(",")));
2070 }
2071 return strList;
2072}
2073
2074
2081QString ImageManagerFe::HideFiles(bool hidden, const ImageIdList &ids)
2082{
2083 // Split images into <locals, remotes>
2085 QString result = "";
2086
2087 if (!lists.second.isEmpty())
2088 {
2089 QStringList message;
2090 message << "IMAGE_HIDE" << QString::number(static_cast<int>(hidden)) << lists.second;
2091
2092 if (!gCoreContext->SendReceiveStringList(message, true))
2093 result = message[1];
2094 }
2095
2096 if (!lists.first.isEmpty())
2097 {
2098 QStringList err = HandleHide(hidden, lists.first);
2099 if (err[0] != "OK")
2100 result = err[1];
2101 }
2102 return result;
2103}
2104
2105
2113 const ImageIdList &ids)
2114{
2115 // Split images into <locals, remotes>
2117 QString result = "";
2118
2119 if (!lists.second.isEmpty())
2120 {
2121 QStringList message;
2122 message << "IMAGE_TRANSFORM" << QString::number(transform) << lists.second;
2123
2124 if (!gCoreContext->SendReceiveStringList(message, true))
2125 result = message[1];
2126 }
2127
2128 if (!lists.first.isEmpty())
2129 {
2130 QStringList err = HandleTransform(transform, lists.first);
2131 if (err[0] != "OK")
2132 result = err[1];
2133 }
2134 return result;
2135}
2136
2137
2144QString ImageManagerFe::SetCover(int parent, int cover)
2145{
2146 if (!ImageItem::IsLocalId(parent))
2147 {
2148 QStringList message;
2149 message << "IMAGE_COVER" << QString::number(parent) << QString::number(cover);
2150
2151 bool ok = gCoreContext->SendReceiveStringList(message, true);
2152 return ok ? "" : message[1];
2153 }
2154
2155 QStringList err = HandleCover(parent, cover);
2156 return err[0] == "OK" ? "" : err[1];
2157}
2158
2159
2165{
2166 if (ImageItem::IsLocalId(id))
2167 HandleGetMetadata(QString::number(id));
2168 else
2169 gCoreContext->SendEvent(MythEvent("IMAGE_GET_METADATA", QString::number(id)));
2170}
2171
2172
2175{
2176 QStringList message("IMAGE_SCAN");
2177 message << "DEVICE CLEAR ALL";
2178 gCoreContext->SendReceiveStringList(message, true);
2179}
2180
2181
2188QString ImageManagerFe::IgnoreDirs(const QString &excludes)
2189{
2190 QStringList message("IMAGE_IGNORE");
2191 message << excludes;
2192 bool ok = gCoreContext->SendReceiveStringList(message, true);
2193 return ok ? "" : message[1];
2194}
2195
2196
2204QString ImageManagerFe::MakeDir(int parent, const QStringList &names, bool rescan)
2205{
2206 QString destId = QString::number(parent);
2207
2208 if (!ImageItem::IsLocalId(parent))
2209 {
2210 QStringList message("IMAGE_CREATE_DIRS");
2211 message << destId << QString::number(static_cast<int>(rescan)) << names;
2212 bool ok = gCoreContext->SendReceiveStringList(message, true);
2213 return ok ? "" : message[1];
2214 }
2215 QStringList err = HandleDirs(destId, rescan, names);
2216 return (err[0] == "OK") ? "" : err[1];
2217}
2218
2219
2226QString ImageManagerFe::RenameFile(const ImagePtrK& im, const QString &name)
2227{
2228 if (!im->IsLocal())
2229 {
2230 QStringList message("IMAGE_RENAME");
2231 message << QString::number(im->m_id) << name;
2232 bool ok = gCoreContext->SendReceiveStringList(message, true);
2233 return ok ? "" : message[1];
2234 }
2235 QStringList err = HandleRename(QString::number(im->m_id), name);
2236 return (err[0] == "OK") ? "" : err[1];
2237}
2238
2239
2246QString ImageManagerFe::CreateImages(int destId, const ImageListK &images)
2247{
2248 if (images.isEmpty())
2249 return "";
2250
2251 // Define field seperator & include it in message
2252 const QString seperator("...");
2253 QStringList imageDefs(seperator);
2254 ImageIdList ids;
2255 for (const auto& im : std::as_const(images))
2256 {
2257 ids << im->m_id;
2258
2259 // Copies preserve hide state, orientation & cover
2260 QStringList aDef;
2261 aDef << QString::number(im->m_id)
2262 << QString::number(im->m_type)
2263 << im->m_filePath
2264 << QString::number(static_cast<int>(im->m_isHidden))
2265 << QString::number(im->m_orientation)
2266 << QString::number(im->m_userThumbnail);
2267
2268 imageDefs << aDef.join(seperator);
2269 }
2270
2271 // Images are either all local or all remote
2272 if (ImageItem::IsLocalId(destId))
2273 {
2274 QStringList err = HandleDbCreate(imageDefs);
2275 return (err[0] == "OK") ? "" : err[1];
2276 }
2277 imageDefs.prepend("IMAGE_COPY");
2278 bool ok = gCoreContext->SendReceiveStringList(imageDefs, true);
2279 return ok ? "" : imageDefs[1];
2280}
2281
2282
2290QString ImageManagerFe::MoveDbImages(const ImagePtrK& destDir, ImageListK &images,
2291 const QString &srcPath)
2292{
2293 QStringList idents;
2294 for (const auto& im : std::as_const(images))
2295 idents << QString::number(im->m_id);
2296
2297 // Images are either all local or all remote
2298 if (destDir->IsLocal())
2299 {
2300 QStringList err = HandleDbMove(idents.join(","), srcPath,
2301 destDir->m_filePath);
2302 return (err[0] == "OK") ? "" : err[1];
2303 }
2304
2305 QStringList message("IMAGE_MOVE");
2306 message << idents.join(",") << srcPath << destDir->m_filePath;
2307 bool ok = gCoreContext->SendReceiveStringList(message, true);
2308 return ok ? "" : message[1];
2309}
2310
2311
2318{
2320
2321 QString result = "";
2322 if (!lists.second.isEmpty())
2323 {
2324 QStringList message("IMAGE_DELETE");
2325 message << lists.second;
2326
2327 bool ok = gCoreContext->SendReceiveStringList(message, true);
2328 if (!ok)
2329 result = message[1];
2330 }
2331 if (!lists.first.isEmpty())
2332 {
2333 QStringList err = HandleDelete(lists.first);
2334 if (err[0] != "OK")
2335 result = err[1];
2336 }
2337 return result;
2338}
2339
2340
2348{
2349 if (im->m_id == GALLERY_DB_ID)
2350 return "";
2351
2352 std::chrono::seconds secs = 0s;
2354
2355 if (im->m_date > 0s)
2356 {
2357 secs = im->m_date;
2358 format |= MythDate::kTime;
2359 }
2360 else
2361 {
2362 secs = im->m_modTime;
2363 }
2364
2365 return MythDate::toString(QDateTime::fromSecsSinceEpoch(secs.count()), format);
2366}
2367
2368
2376{
2377 if (im->m_id == GALLERY_DB_ID)
2378 return "";
2379
2380 std::chrono::seconds secs(im->m_date > 0s ? im->m_date : im->m_modTime);
2381 return QDateTime::fromSecsSinceEpoch(secs.count()).date().toString(m_dateFormat);
2382}
2383
2384
2391{
2392 if (im.m_id == GALLERY_DB_ID)
2393 return tr("Gallery");
2394 if (im.m_id == PHOTO_DB_ID)
2395 return tr("Photographs");
2396 return im.IsLocal() ? DeviceName(im.m_device)
2398}
2399
2400
2408QString ImageManagerFe::CrumbName(ImageItemK &im, bool getPath) const
2409{
2410 if (im.IsDevice())
2411 return DeviceCaption(im);
2412
2413 if (!getPath)
2414 return im.m_baseName;
2415
2416 QString dev;
2417 QString path(im.m_filePath);
2418
2419 if (im.IsLocal())
2420 {
2421 // Replace local mount path with device name
2422 path.remove(0, DeviceMount(im.m_device).size());
2423 dev = DeviceName(im.m_device);
2424 }
2425 return dev + path.replace("/", " > ");
2426}
2427
2428
2429void ImageManagerFe::CloseDevices(int devId, bool eject)
2430{
2431 QString reason { "DEVICE REMOVE" };
2432 if (devId == DEVICE_INVALID)
2433 reason = "DEVICE CLOSE ALL";
2434 else if (eject)
2435 reason = "DEVICE EJECT";
2436 HandleScanRequest(reason, devId);
2437}
2438
2439
2445{
2447 if (!monitor)
2448 return false;
2449
2450 // Detect all local media
2451 QList<MythMediaDevice*> devices
2453
2454 for (auto *dev : std::as_const(devices))
2455 {
2456 if (monitor->ValidateAndLock(dev) && dev->isUsable())
2457 OpenDevice(dev->getDeviceModel(), dev->getMountPath(), dev);
2458 else
2459 monitor->Unlock(dev);
2460 }
2461
2462 if (DeviceCount() > 0)
2463 {
2464 // Close devices that are no longer present
2465 QList absentees = GetAbsentees();
2466 for (int devId : std::as_const(absentees))
2467 CloseDevices(devId);
2468
2469 // Start local scan
2470 QString err = ScanImagesAction(true, true);
2471 if (!err.isEmpty())
2472 LOG(VB_GENERAL, LOG_ERR, LOC + err);
2473 }
2474 return DeviceCount() > 0;
2475}
2476
2477
2483{
2485
2486 if (!event || !monitor)
2487 return;
2488
2489 MythMediaDevice *dev = event->getDevice();
2490 if (!dev)
2491 return;
2492
2494 MythMediaStatus status = dev->getStatus();
2495
2496 LOG(VB_FILE, LOG_DEBUG, LOC +
2497 QString("Media event for %1 (%2) at %3, type %4, status %5 (was %6)")
2498 .arg(dev->getDeviceModel(), dev->getVolumeID(), dev->getMountPath())
2499 .arg(type).arg(status).arg(event->getOldStatus()));
2500
2502 {
2503 LOG(VB_FILE, LOG_DEBUG, LOC +
2504 QString("Ignoring event - wrong type %1").arg(type));
2505 return;
2506 }
2507
2508 if (status == MEDIASTAT_USEABLE || status == MEDIASTAT_MOUNTED)
2509 {
2510 // New device. Lock it & scan
2511 if (monitor->ValidateAndLock(dev))
2512 {
2513 OpenDevice(dev->getDeviceModel(), dev->getMountPath(), dev);
2514 ScanImagesAction(true, true);
2515 }
2516 else
2517 {
2518 monitor->Unlock(dev);
2519 }
2520 return;
2521 }
2522
2523 // Device has disappeared
2524 int devId = LocateMount(dev->getMountPath());
2525 if (devId != DEVICE_INVALID)
2526 CloseDevices(devId);
2527}
2528
2529
2531{
2532 auto *tmp = new QTemporaryDir(QDir::tempPath() % "/" % IMPORTDIR % "-XXXXXX");
2533 if (!tmp->isValid())
2534 {
2535 delete tmp;
2536 return "";
2537 }
2538
2539 QString time(QDateTime::currentDateTime().toString("mm:ss"));
2540 OpenDevice("Import " + time, tmp->path(), nullptr, tmp);
2541 return tmp->path();
2542}
2543
2544
2545// Must define the valid template implementations to generate code for the
2546// instantiations (as they are defined in the cpp rather than header).
2547// Otherwise the linker will fail with undefined references...
2548template class ImageDb<ImageAdapterSg>;
2549template class ImageDb<ImageAdapterLocal>;
2550template class ImageHandler<ImageDbSg>;
2551template 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