1/*
2 SPDX-FileCopyrightText: 2015 Vishesh Handa <vhanda@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-or-later
5*/
6
7#include "fileindexscheduler.h"
8
9#include "baloodebug.h"
10#include "firstrunindexer.h"
11#include "newfileindexer.h"
12#include "modifiedfileindexer.h"
13#include "xattrindexer.h"
14#include "filecontentindexer.h"
15#include "unindexedfileindexer.h"
16#include "indexcleaner.h"
17
18#include "fileindexerconfig.h"
19
20#include <memory>
21
22#include <QDBusConnection>
23
24using namespace Baloo;
25
26FileIndexScheduler::FileIndexScheduler(Database* db, FileIndexerConfig* config, bool firstRun, QObject* parent)
27 : QObject(parent)
28 , m_db(db)
29 , m_config(config)
30 , m_provider(db)
31 , m_contentIndexer(nullptr)
32 , m_indexerState(Startup)
33 , m_checkUnindexedFiles(false)
34 , m_checkStaleIndexEntries(false)
35 , m_isGoingIdle(false)
36 , m_isSuspended(false)
37 , m_isFirstRun(firstRun)
38 , m_inStartup(true)
39{
40 Q_ASSERT(db);
41 Q_ASSERT(config);
42
43 m_threadPool.setMaxThreadCount(1);
44
45 connect(sender: &m_powerMonitor, signal: &PowerStateMonitor::powerManagementStatusChanged,
46 context: this, slot: &FileIndexScheduler::powerManagementStatusChanged);
47
48 if (m_powerMonitor.isOnBattery()) {
49 m_indexerState = LowPowerIdle;
50 }
51
52 m_contentIndexer = new FileContentIndexer(m_config->maxUncomittedFiles(), &m_provider, m_timeEstimator, this);
53 m_contentIndexer->setAutoDelete(false);
54 connect(sender: m_contentIndexer, signal: &FileContentIndexer::done, context: this,
55 slot: &FileIndexScheduler::runnerFinished);
56 connect(sender: m_contentIndexer, signal: &FileContentIndexer::committedBatch, slot: [this](uint time, uint batchSize) {
57 this->m_timeEstimator.handleNewBatchTime(time, batchSize);
58 });
59
60 QDBusConnection::sessionBus().registerObject(QStringLiteral("/scheduler"),
61 object: this, options: QDBusConnection::ExportScriptableContents);
62}
63
64FileIndexScheduler::~FileIndexScheduler()
65{
66 m_contentIndexer->quit();
67 m_threadPool.waitForDone(msecs: 0); // wait 0 msecs
68}
69
70void FileIndexScheduler::startupFinished() {
71 m_inStartup = false;
72 QTimer::singleShot(interval: 0, receiver: this, slot: &FileIndexScheduler::scheduleIndexing);
73}
74
75void FileIndexScheduler::scheduleIndexing()
76{
77 if (!isIndexerIdle()) {
78 return;
79 }
80 m_isGoingIdle = false;
81
82 if (m_isSuspended) {
83 if (m_indexerState != Suspended) {
84 m_indexerState = Suspended;
85 Q_EMIT stateChanged(state: m_indexerState);
86 }
87 return;
88 }
89
90 if (m_isFirstRun) {
91 if (m_inStartup) {
92 return;
93 }
94
95 m_isFirstRun = false;
96 auto runnable = new FirstRunIndexer(m_db, m_config, m_config->includeFolders());
97 connect(sender: runnable, signal: &FirstRunIndexer::done, context: this, slot: &FileIndexScheduler::runnerFinished);
98
99 m_threadPool.start(runnable);
100 m_indexerState = FirstRun;
101 Q_EMIT stateChanged(state: m_indexerState);
102 return;
103 }
104
105 if (!m_newFiles.isEmpty()) {
106 auto runnable = new NewFileIndexer(m_db, m_config, m_newFiles);
107 connect(sender: runnable, signal: &NewFileIndexer::done, context: this, slot: &FileIndexScheduler::runnerFinished);
108
109 m_threadPool.start(runnable);
110 m_newFiles.clear();
111 m_indexerState = NewFiles;
112 Q_EMIT stateChanged(state: m_indexerState);
113 return;
114 }
115
116 if (!m_modifiedFiles.isEmpty()) {
117 auto runnable = new ModifiedFileIndexer(m_db, m_config, m_modifiedFiles);
118 connect(sender: runnable, signal: &ModifiedFileIndexer::done, context: this, slot: &FileIndexScheduler::runnerFinished);
119
120 m_threadPool.start(runnable);
121 m_modifiedFiles.clear();
122 m_indexerState = ModifiedFiles;
123 Q_EMIT stateChanged(state: m_indexerState);
124 return;
125 }
126
127 if (!m_xattrFiles.isEmpty()) {
128 auto runnable = new XAttrIndexer(m_db, m_config, m_xattrFiles);
129 connect(sender: runnable, signal: &XAttrIndexer::done, context: this, slot: &FileIndexScheduler::runnerFinished);
130
131 m_threadPool.start(runnable);
132 m_xattrFiles.clear();
133 m_indexerState = XAttrFiles;
134 Q_EMIT stateChanged(state: m_indexerState);
135 return;
136 }
137
138 // No housekeeping, no content indexing
139 if (m_powerMonitor.isOnBattery()) {
140 if (m_indexerState != LowPowerIdle) {
141 m_indexerState = LowPowerIdle;
142 Q_EMIT stateChanged(state: m_indexerState);
143 }
144 return;
145 }
146
147 if (m_inStartup) {
148 if (m_indexerState != Startup) {
149 m_indexerState = Startup;
150 Q_EMIT stateChanged(state: m_indexerState);
151 }
152 return;
153 }
154
155 // This has to be above content indexing, because there can be files that
156 // should not be indexed in the DB (i.e. if config was changed)
157 if (m_checkStaleIndexEntries) {
158 auto runnable = new IndexCleaner(m_db, m_config);
159 connect(sender: runnable, signal: &IndexCleaner::done, context: this, slot: &FileIndexScheduler::runnerFinished);
160
161 m_threadPool.start(runnable);
162 m_checkStaleIndexEntries = false;
163 m_indexerState = StaleIndexEntriesClean;
164 Q_EMIT stateChanged(state: m_indexerState);
165 return;
166 }
167
168 if (auto remainingCount = m_provider.size(); remainingCount > 0) {
169 m_timeEstimator.setProgress(remainingCount);
170 m_threadPool.start(runnable: m_contentIndexer);
171 m_indexerState = ContentIndexing;
172 Q_EMIT stateChanged(state: m_indexerState);
173 return;
174 }
175
176 if (m_checkUnindexedFiles) {
177 auto runnable = new UnindexedFileIndexer(m_db, m_config);
178 connect(sender: runnable, signal: &UnindexedFileIndexer::done, context: this, slot: &FileIndexScheduler::runnerFinished);
179
180 m_threadPool.start(runnable);
181 m_checkUnindexedFiles = false;
182 m_indexerState = UnindexedFileCheck;
183 Q_EMIT stateChanged(state: m_indexerState);
184 return;
185 }
186
187 if (m_indexerState != Idle) {
188 m_indexerState = Idle;
189 Q_EMIT stateChanged(state: m_indexerState);
190 }
191}
192
193static void removeStartsWith(QStringList& list, const QString& dir)
194{
195 const auto tail = std::remove_if(first: list.begin(), last: list.end(),
196 pred: [&dir](const QString& file) {
197 return file.startsWith(s: dir);
198 });
199 list.erase(abegin: tail, aend: list.end());
200}
201
202static void removeShouldNotIndex(QStringList& list, FileIndexerConfig* config)
203{
204 const auto tail = std::remove_if(first: list.begin(), last: list.end(),
205 pred: [config](const QString& file) {
206 return !config->shouldBeIndexed(path: file);
207 });
208 list.erase(abegin: tail, aend: list.end());
209}
210
211void FileIndexScheduler::updateConfig()
212{
213 // Interrupt content indexer, to avoid indexing files that should
214 // not be indexed (bug 373430)
215 if (m_indexerState == ContentIndexing) {
216 m_contentIndexer->quit();
217 }
218 removeShouldNotIndex(list&: m_newFiles, config: m_config);
219 removeShouldNotIndex(list&: m_modifiedFiles, config: m_config);
220 removeShouldNotIndex(list&: m_xattrFiles, config: m_config);
221 m_checkStaleIndexEntries = true;
222 m_checkUnindexedFiles = true;
223 scheduleIndexing();
224}
225
226void FileIndexScheduler::handleFileRemoved(const QString& file)
227{
228 if (!file.endsWith(c: QLatin1Char('/'))) {
229 m_newFiles.removeOne(t: file);
230 m_modifiedFiles.removeOne(t: file);
231 m_xattrFiles.removeOne(t: file);
232 }
233 else {
234 removeStartsWith(list&: m_newFiles, dir: file);
235 removeStartsWith(list&: m_modifiedFiles, dir: file);
236 removeStartsWith(list&: m_xattrFiles, dir: file);
237 }
238}
239
240void FileIndexScheduler::powerManagementStatusChanged(bool isOnBattery)
241{
242 qCDebug(BALOO) << "Power state changed - onBattery:" << isOnBattery;
243 if (isOnBattery && m_indexerState == ContentIndexing) {
244 qCDebug(BALOO) << "On battery, stopping content indexer";
245 m_contentIndexer->quit();
246 } else {
247 scheduleIndexing();
248 }
249}
250
251void FileIndexScheduler::setSuspend(bool suspend)
252{
253 m_isSuspended = suspend;
254 if (suspend) {
255 qCDebug(BALOO) << "Suspending";
256 if (m_indexerState == ContentIndexing) {
257 m_contentIndexer->quit();
258 } else {
259 scheduleIndexing();
260 }
261 } else {
262 qCDebug(BALOO) << "Resuming";
263 // No need to emit here we'll be emitting in scheduling
264 scheduleIndexing();
265 }
266}
267
268uint FileIndexScheduler::getRemainingTime()
269{
270 if (m_indexerState != ContentIndexing) {
271 return 0;
272 }
273 return m_timeEstimator.calculateTimeLeft();
274}
275
276void FileIndexScheduler::scheduleCheckUnindexedFiles()
277{
278 m_checkUnindexedFiles = true;
279}
280
281void FileIndexScheduler::checkUnindexedFiles()
282{
283 m_checkUnindexedFiles = true;
284 scheduleIndexing();
285}
286
287void FileIndexScheduler::scheduleCheckStaleIndexEntries()
288{
289 m_checkStaleIndexEntries = true;
290}
291
292void FileIndexScheduler::checkStaleIndexEntries()
293{
294 m_checkStaleIndexEntries = true;
295 scheduleIndexing();
296}
297
298uint FileIndexScheduler::getBatchSize()
299{
300 return m_config->maxUncomittedFiles();
301}
302
303#include "moc_fileindexscheduler.cpp"
304

source code of baloo/src/file/fileindexscheduler.cpp