1 // SPDX-FileCopyrightText: 2003-2020 The KPhotoAlbum Development Team
2 // SPDX-FileCopyrightText: 2021 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
3 //
4 // SPDX-License-Identifier: GPL-2.0-or-later
5
6 #include "ImageDB.h"
7
8 #include "CategoryCollection.h"
9 #include "MediaCount.h"
10 #include "NewImageFinder.h"
11
12 #include <XMLDB/Database.h>
13 #include <kpabase/FileName.h>
14 #include <kpabase/SettingsData.h>
15 #include <kpabase/UIDelegate.h>
16
17 #include <KLocalizedString>
18 #include <QApplication>
19 #include <QFileInfo>
20 #include <QProgressDialog>
21
22 using namespace DB;
23
24 ImageDB *ImageDB::s_instance = nullptr;
25
instance()26 ImageDB *DB::ImageDB::instance()
27 {
28 if (s_instance == nullptr)
29 exit(0); // Either we are closing down or ImageDB::instance was called before ImageDB::setup
30 return s_instance;
31 }
32
setupXMLDB(const QString & configFile,UIDelegate & delegate)33 void ImageDB::setupXMLDB(const QString &configFile, UIDelegate &delegate)
34 {
35 if (s_instance)
36 qFatal("ImageDB::setupXMLDB: Setup must be called only once.");
37 s_instance = new XMLDB::Database(configFile, delegate);
38 connectSlots();
39 }
40
deleteInstance()41 void ImageDB::deleteInstance()
42 {
43 delete s_instance;
44 s_instance = nullptr;
45 }
46
connectSlots()47 void ImageDB::connectSlots()
48 {
49 connect(Settings::SettingsData::instance(), QOverload<bool, bool>::of(&Settings::SettingsData::locked), s_instance, &ImageDB::lockDB);
50 connect(&s_instance->memberMap(), &MemberMap::dirty, s_instance, &ImageDB::markDirty);
51 }
52
NONE()53 QString ImageDB::NONE()
54 {
55 static QString none = QString::fromLatin1("**NONE**");
56 return none;
57 }
58
currentScope(bool requireOnDisk) const59 DB::FileNameList ImageDB::currentScope(bool requireOnDisk) const
60 {
61 return search(m_currentScope, requireOnDisk).files();
62 }
63
markDirty()64 void ImageDB::markDirty()
65 {
66 emit dirty();
67 }
68
setDateRange(const ImageDate & range,bool includeFuzzyCounts)69 void ImageDB::setDateRange(const ImageDate &range, bool includeFuzzyCounts)
70 {
71 m_selectionRange = range;
72 m_includeFuzzyCounts = includeFuzzyCounts;
73 }
74
clearDateRange()75 void ImageDB::clearDateRange()
76 {
77 m_selectionRange = ImageDate();
78 }
79
slotRescan()80 void ImageDB::slotRescan()
81 {
82 bool newImages = NewImageFinder().findImages();
83 if (newImages)
84 markDirty();
85
86 emit totalChanged(totalCount());
87 }
88
slotRecalcCheckSums(const DB::FileNameList & inputList)89 void ImageDB::slotRecalcCheckSums(const DB::FileNameList &inputList)
90 {
91 DB::FileNameList list = inputList;
92 if (list.isEmpty()) {
93 list = files();
94 md5Map()->clear();
95 }
96
97 bool d = NewImageFinder().calculateMD5sums(list, md5Map());
98 if (d)
99 markDirty();
100
101 emit totalChanged(totalCount());
102 }
103
imagesWithMD5Changed()104 DB::FileNameSet DB::ImageDB::imagesWithMD5Changed()
105 {
106 MD5Map map;
107 bool wasCanceled;
108 NewImageFinder().calculateMD5sums(files(), &map, &wasCanceled);
109 if (wasCanceled)
110 return DB::FileNameSet();
111
112 return md5Map()->diff(map);
113 }
114
uiDelegate() const115 UIDelegate &DB::ImageDB::uiDelegate() const
116 {
117 return m_UI;
118 }
119
ImageDB(UIDelegate & delegate)120 ImageDB::ImageDB(UIDelegate &delegate)
121 : m_UI(delegate)
122 , m_exifDB(std::make_unique<Exif::Database>(::Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1("/exif-info.db"), delegate))
123 {
124 }
125
count(const ImageSearchInfo & searchInfo)126 DB::MediaCount ImageDB::count(const ImageSearchInfo &searchInfo)
127 {
128 uint images = 0;
129 uint videos = 0;
130 for (const auto &imageInfo : search(searchInfo)) {
131 if (imageInfo->mediaType() == Image)
132 ++images;
133 else
134 ++videos;
135 }
136 return MediaCount(images, videos);
137 }
138
slotReread(const DB::FileNameList & list,DB::ExifMode mode)139 void ImageDB::slotReread(const DB::FileNameList &list, DB::ExifMode mode)
140 {
141 // Do here a reread of the exif info and change the info correctly in the database without loss of previous added data
142 QProgressDialog dialog(i18n("Loading information from images"),
143 i18n("Cancel"), 0, list.count());
144
145 uint count = 0;
146 for (DB::FileNameList::ConstIterator it = list.begin(); it != list.end(); ++it, ++count) {
147 if (count % 10 == 0) {
148 dialog.setValue(count); // ensure to call setProgress(0)
149 qApp->processEvents(QEventLoop::AllEvents);
150
151 if (dialog.wasCanceled())
152 return;
153 }
154
155 QFileInfo fi((*it).absolute());
156
157 if (fi.exists())
158 info(*it)->readExif(*it, mode);
159 markDirty();
160 }
161 }
162
setCurrentScope(const ImageSearchInfo & info)163 void ImageDB::setCurrentScope(const ImageSearchInfo &info)
164 {
165 m_currentScope = info;
166 }
167
findFirstItemInRange(const DB::FileNameList & images,const ImageDate & range,bool includeRanges) const168 DB::FileName ImageDB::findFirstItemInRange(const DB::FileNameList &images,
169 const ImageDate &range,
170 bool includeRanges) const
171 {
172 DB::FileName candidate;
173 Utilities::FastDateTime candidateDateStart;
174 for (const DB::FileName &fileName : images) {
175 ImageInfoPtr iInfo = info(fileName);
176
177 ImageDate::MatchType match = iInfo->date().isIncludedIn(range);
178 if (match == DB::ImageDate::ExactMatch || (includeRanges && match == DB::ImageDate::RangeMatch)) {
179 if (candidate.isNull() || iInfo->date().start() < candidateDateStart) {
180 candidate = fileName;
181 // Looking at this, can't this just be iInfo->date().start()?
182 // Just in the middle of refactoring other stuff, so leaving
183 // this alone now. TODO(hzeller): revisit.
184 candidateDateStart = info(candidate)->date().start();
185 }
186 }
187 }
188 return candidate;
189 }
190
untaggedCategoryFeatureConfigured() const191 bool ImageDB::untaggedCategoryFeatureConfigured() const
192 {
193 const auto untaggedCategory = Settings::SettingsData::instance()->untaggedCategory();
194 const auto untaggedTag = Settings::SettingsData::instance()->untaggedTag();
195 return categoryCollection()->categoryNames().contains(untaggedCategory)
196 && categoryCollection()->categoryForName(untaggedCategory)->items().contains(untaggedTag);
197 }
198
exifDB() const199 Exif::Database *ImageDB::exifDB() const
200 {
201 return m_exifDB.get();
202 }
203
204 /** \fn void ImageDB::renameCategory( const QString& oldName, const QString newName )
205 * \brief Rename category in media items stored in database.
206 */
207
208 // vi:expandtab:tabstop=4 shiftwidth=4:
209