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_indexFinishedFiles, 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 m_indexPendingFiles = m_provider.size();
169 m_indexFinishedFiles = 0;
170 if (m_indexPendingFiles) {
171 m_threadPool.start(runnable: m_contentIndexer);
172 m_indexerState = ContentIndexing;
173 Q_EMIT stateChanged(state: m_indexerState);
174 return;
175 }
176
177 if (m_checkUnindexedFiles) {
178 auto runnable = new UnindexedFileIndexer(m_db, m_config);
179 connect(sender: runnable, signal: &UnindexedFileIndexer::done, context: this, slot: &FileIndexScheduler::runnerFinished);
180
181 m_threadPool.start(runnable);
182 m_checkUnindexedFiles = false;
183 m_indexerState = UnindexedFileCheck;
184 Q_EMIT stateChanged(state: m_indexerState);
185 return;
186 }
187
188 if (m_indexerState != Idle) {
189 m_indexerState = Idle;
190 Q_EMIT stateChanged(state: m_indexerState);
191 }
192}
193
194static void removeStartsWith(QStringList& list, const QString& dir)
195{
196 const auto tail = std::remove_if(first: list.begin(), last: list.end(),
197 pred: [&dir](const QString& file) {
198 return file.startsWith(s: dir);
199 });
200 list.erase(abegin: tail, aend: list.end());
201}
202
203static void removeShouldNotIndex(QStringList& list, FileIndexerConfig* config)
204{
205 const auto tail = std::remove_if(first: list.begin(), last: list.end(),
206 pred: [config](const QString& file) {
207 return !config->shouldBeIndexed(path: file);
208 });
209 list.erase(abegin: tail, aend: list.end());
210}
211
212void FileIndexScheduler::updateConfig()
213{
214 // Interrupt content indexer, to avoid indexing files that should
215 // not be indexed (bug 373430)
216 if (m_indexerState == ContentIndexing) {
217 m_contentIndexer->quit();
218 }
219 removeShouldNotIndex(list&: m_newFiles, config: m_config);
220 removeShouldNotIndex(list&: m_modifiedFiles, config: m_config);
221 removeShouldNotIndex(list&: m_xattrFiles, config: m_config);
222 m_checkStaleIndexEntries = true;
223 m_checkUnindexedFiles = true;
224 scheduleIndexing();
225}
226
227void FileIndexScheduler::handleFileRemoved(const QString& file)
228{
229 if (!file.endsWith(c: QLatin1Char('/'))) {
230 m_newFiles.removeOne(t: file);
231 m_modifiedFiles.removeOne(t: file);
232 m_xattrFiles.removeOne(t: file);
233 }
234 else {
235 removeStartsWith(list&: m_newFiles, dir: file);
236 removeStartsWith(list&: m_modifiedFiles, dir: file);
237 removeStartsWith(list&: m_xattrFiles, dir: file);
238 }
239}
240
241void FileIndexScheduler::powerManagementStatusChanged(bool isOnBattery)
242{
243 qCDebug(BALOO) << "Power state changed - onBattery:" << isOnBattery;
244 if (isOnBattery && m_indexerState == ContentIndexing) {
245 qCDebug(BALOO) << "On battery, stopping content indexer";
246 m_contentIndexer->quit();
247 } else {
248 scheduleIndexing();
249 }
250}
251
252void FileIndexScheduler::setSuspend(bool suspend)
253{
254 m_isSuspended = suspend;
255 if (suspend) {
256 qCDebug(BALOO) << "Suspending";
257 if (m_indexerState == ContentIndexing) {
258 m_contentIndexer->quit();
259 } else {
260 scheduleIndexing();
261 }
262 } else {
263 qCDebug(BALOO) << "Resuming";
264 // No need to emit here we'll be emitting in scheduling
265 scheduleIndexing();
266 }
267}
268
269uint FileIndexScheduler::getRemainingTime()
270{
271 if (m_indexerState != ContentIndexing) {
272 return 0;
273 }
274 uint remainingFiles = m_indexPendingFiles - m_indexFinishedFiles;
275 return m_timeEstimator.calculateTimeLeft(filesLeft: remainingFiles);
276}
277
278void FileIndexScheduler::scheduleCheckUnindexedFiles()
279{
280 m_checkUnindexedFiles = true;
281}
282
283void FileIndexScheduler::checkUnindexedFiles()
284{
285 m_checkUnindexedFiles = true;
286 scheduleIndexing();
287}
288
289void FileIndexScheduler::scheduleCheckStaleIndexEntries()
290{
291 m_checkStaleIndexEntries = true;
292}
293
294void FileIndexScheduler::checkStaleIndexEntries()
295{
296 m_checkStaleIndexEntries = true;
297 scheduleIndexing();
298}
299
300uint FileIndexScheduler::getBatchSize()
301{
302 return m_config->maxUncomittedFiles();
303}
304
305#include "moc_fileindexscheduler.cpp"
306

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