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