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