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