1 /* SPDX-FileCopyrightText: 2003-2020 The KPhotoAlbum Development Team
2
3 SPDX-License-Identifier: GPL-2.0-or-later
4 */
5
6 #include "ThumbnailBuilder.h"
7
8 #include "AsyncLoader.h"
9 #include "PreloadRequest.h"
10
11 #include <DB/ImageDB.h>
12 #include <DB/ImageInfoPtr.h>
13 #include <DB/OptimizedFileList.h>
14 #include <MainWindow/StatusBar.h>
15 #include <ThumbnailView/CellGeometry.h>
16 #include <kpabase/Logging.h>
17 #include <kpabase/SettingsData.h>
18 #include <kpathumbnails/ThumbnailCache.h>
19
20 #include <KLocalizedString>
21 #include <QLoggingCategory>
22 #include <QMessageBox>
23 #include <QTimer>
24
25 namespace
26 {
27 /**
28 * @brief Thumbnail size for storage.
29 * @see ThumbnailView::CellGeometry::preferredIconSize()
30 * @return
31 */
preferredThumbnailSize()32 QSize preferredThumbnailSize()
33 {
34 int width = Settings::SettingsData::instance()->thumbnailSize();
35 return QSize(width, width);
36 }
37 }
38
39 ImageManager::ThumbnailBuilder *ImageManager::ThumbnailBuilder::s_instance = nullptr;
40
ThumbnailBuilder(MainWindow::StatusBar * statusBar,QObject * parent,ThumbnailCache * thumbnailCache)41 ImageManager::ThumbnailBuilder::ThumbnailBuilder(MainWindow::StatusBar *statusBar, QObject *parent, ThumbnailCache *thumbnailCache)
42 : QObject(parent)
43 , m_statusBar(statusBar)
44 , m_thumbnailCache(thumbnailCache)
45 , m_count(0)
46 , m_isBuilding(false)
47 , m_loadedCount(0)
48 , m_preloadQueue(nullptr)
49 , m_scout(nullptr)
50 {
51 connect(m_statusBar, &MainWindow::StatusBar::cancelRequest, this, &ThumbnailBuilder::cancelRequests);
52 s_instance = this;
53
54 m_startBuildTimer = new QTimer(this);
55 m_startBuildTimer->setSingleShot(true);
56 connect(m_startBuildTimer, &QTimer::timeout, this, &ThumbnailBuilder::doThumbnailBuild);
57 }
58
cancelRequests()59 void ImageManager::ThumbnailBuilder::cancelRequests()
60 {
61 ImageManager::AsyncLoader::instance()->stop(this, ImageManager::StopAll);
62 m_isBuilding = false;
63 m_statusBar->setProgressBarVisible(false);
64 m_startBuildTimer->stop();
65 }
66
terminateScout()67 void ImageManager::ThumbnailBuilder::terminateScout()
68 {
69 if (m_scout) {
70 delete m_scout;
71 m_scout = nullptr;
72 }
73 if (m_preloadQueue) {
74 delete m_preloadQueue;
75 m_preloadQueue = nullptr;
76 }
77 }
78
pixmapLoaded(ImageManager::ImageRequest * request,const QImage &)79 void ImageManager::ThumbnailBuilder::pixmapLoaded(ImageManager::ImageRequest *request, const QImage & /*image*/)
80 {
81 const DB::FileName fileName = request->databaseFileName();
82 const QSize fullSize = request->fullSize();
83 DB::ImageInfoPtr info = DB::ImageDB::instance()->info(fileName);
84
85 // We probably shouldn't do this at all, since the "full size"
86 // of the request could be the size of the embedded thumbnail
87 // or even a scaled-down such. But if this hasn't been
88 // set orrectly earlier, we have nothing else to go on.
89 if (fullSize.width() != -1 && info->size().width() == -1) {
90 info->setSize(fullSize);
91 }
92 m_loadedCount++;
93 m_statusBar->setProgress(++m_count);
94 if (m_count >= m_expectedThumbnails) {
95 terminateScout();
96 }
97 }
98
buildAll(ThumbnailBuildStart when)99 void ImageManager::ThumbnailBuilder::buildAll(ThumbnailBuildStart when)
100 {
101 QMessageBox msgBox;
102 msgBox.setText(i18n("Building all thumbnails may take a long time."));
103 msgBox.setInformativeText(i18n("Do you want to rebuild all of your thumbnails?"));
104 msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
105 msgBox.setDefaultButton(QMessageBox::No);
106 int ret = msgBox.exec();
107 if (ret == QMessageBox::Yes) {
108 m_thumbnailCache->flush();
109 scheduleThumbnailBuild(DB::ImageDB::instance()->files(), when);
110 }
111 }
112
instance()113 ImageManager::ThumbnailBuilder *ImageManager::ThumbnailBuilder::instance()
114 {
115 Q_ASSERT(s_instance);
116 return s_instance;
117 }
118
~ThumbnailBuilder()119 ImageManager::ThumbnailBuilder::~ThumbnailBuilder()
120 {
121 terminateScout();
122 }
123
buildMissing()124 void ImageManager::ThumbnailBuilder::buildMissing()
125 {
126 const DB::FileNameList images = DB::ImageDB::instance()->files();
127 DB::FileNameList needed;
128 for (const DB::FileName &fileName : images) {
129 if (!m_thumbnailCache->contains(fileName))
130 needed.append(fileName);
131 }
132 scheduleThumbnailBuild(needed, StartDelayed);
133 }
134
scheduleThumbnailBuild(const DB::FileNameList & list,ThumbnailBuildStart when)135 void ImageManager::ThumbnailBuilder::scheduleThumbnailBuild(const DB::FileNameList &list, ThumbnailBuildStart when)
136 {
137 if (list.count() == 0)
138 return;
139
140 if (m_isBuilding)
141 cancelRequests();
142
143 DB::OptimizedFileList files(list);
144 m_thumbnailsToBuild = files.optimizedDbFiles();
145 m_startBuildTimer->start(when == StartNow ? 0 : 5000);
146 }
147
buildOneThumbnail(const DB::ImageInfoPtr & info)148 void ImageManager::ThumbnailBuilder::buildOneThumbnail(const DB::ImageInfoPtr &info)
149 {
150 ImageManager::ImageRequest *request
151 = new ImageManager::PreloadRequest(info->fileName(),
152 preferredThumbnailSize(), info->angle(),
153 this, m_thumbnailCache);
154 request->setIsThumbnailRequest(true);
155 request->setPriority(ImageManager::BuildThumbnails);
156 ImageManager::AsyncLoader::instance()->load(request);
157 }
158
doThumbnailBuild()159 void ImageManager::ThumbnailBuilder::doThumbnailBuild()
160 {
161 m_isBuilding = true;
162 int numberOfThumbnailsToBuild = 0;
163
164 terminateScout();
165
166 m_count = 0;
167 m_loadedCount = 0;
168 m_preloadQueue = new DB::ImageScoutQueue;
169 for (const DB::FileName &fileName : m_thumbnailsToBuild) {
170 m_preloadQueue->enqueue(fileName);
171 }
172 qCDebug(ImageManagerLog) << "thumbnail builder starting scout";
173 m_scout = new DB::ImageScout(*m_preloadQueue, m_loadedCount, Settings::SettingsData::instance()->getThumbnailPreloadThreadCount());
174 m_scout->setMaxSeekAhead(10);
175 m_scout->setReadLimit(10 * 1048576);
176 m_scout->start();
177 m_statusBar->startProgress(i18n("Building thumbnails"), qMax(m_thumbnailsToBuild.size() - 1, 1));
178 // We'll update this later. Meanwhile, we want to make sure that the scout
179 // isn't prematurely terminated because the expected number of thumbnails
180 // is less than (i. e. zero) the number of thumbnails actually built.
181 m_expectedThumbnails = m_thumbnailsToBuild.size();
182 for (const DB::FileName &fileName : m_thumbnailsToBuild) {
183 const auto info = DB::ImageDB::instance()->info(fileName);
184 if (ImageManager::AsyncLoader::instance()->isExiting()) {
185 cancelRequests();
186 break;
187 }
188 if (info->isNull()) {
189 m_loadedCount++;
190 m_count++;
191 continue;
192 }
193
194 ImageManager::ImageRequest *request
195 = new ImageManager::PreloadRequest(fileName,
196 preferredThumbnailSize(), info->angle(),
197 this, m_thumbnailCache);
198 request->setIsThumbnailRequest(true);
199 request->setPriority(ImageManager::BuildThumbnails);
200 if (ImageManager::AsyncLoader::instance()->load(request))
201 ++numberOfThumbnailsToBuild;
202 }
203 m_expectedThumbnails = numberOfThumbnailsToBuild;
204 if (numberOfThumbnailsToBuild == 0) {
205 m_statusBar->setProgressBarVisible(false);
206 terminateScout();
207 }
208 }
209
save()210 void ImageManager::ThumbnailBuilder::save()
211 {
212 m_thumbnailCache->save();
213 }
214
requestCanceled()215 void ImageManager::ThumbnailBuilder::requestCanceled()
216 {
217 m_statusBar->setProgress(++m_count);
218 m_loadedCount++;
219 if (m_count >= m_expectedThumbnails) {
220 terminateScout();
221 }
222 }
223
224 // vi:expandtab:tabstop=4 shiftwidth=4:
225