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 | |
24 | using namespace Baloo; |
25 | |
26 | FileIndexScheduler::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 | |
64 | FileIndexScheduler::~FileIndexScheduler() |
65 | { |
66 | m_contentIndexer->quit(); |
67 | m_threadPool.waitForDone(msecs: 0); // wait 0 msecs |
68 | } |
69 | |
70 | void FileIndexScheduler::startupFinished() { |
71 | m_inStartup = false; |
72 | QTimer::singleShot(interval: 0, receiver: this, slot: &FileIndexScheduler::scheduleIndexing); |
73 | } |
74 | |
75 | void 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 | |
194 | static 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 | |
203 | static 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 | |
212 | void 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 | |
227 | void 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 | |
241 | void 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 | |
252 | void 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 | |
269 | uint 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 | |
278 | void FileIndexScheduler::scheduleCheckUnindexedFiles() |
279 | { |
280 | m_checkUnindexedFiles = true; |
281 | } |
282 | |
283 | void FileIndexScheduler::checkUnindexedFiles() |
284 | { |
285 | m_checkUnindexedFiles = true; |
286 | scheduleIndexing(); |
287 | } |
288 | |
289 | void FileIndexScheduler::scheduleCheckStaleIndexEntries() |
290 | { |
291 | m_checkStaleIndexEntries = true; |
292 | } |
293 | |
294 | void FileIndexScheduler::checkStaleIndexEntries() |
295 | { |
296 | m_checkStaleIndexEntries = true; |
297 | scheduleIndexing(); |
298 | } |
299 | |
300 | uint FileIndexScheduler::getBatchSize() |
301 | { |
302 | return m_config->maxUncomittedFiles(); |
303 | } |
304 | |
305 | #include "moc_fileindexscheduler.cpp" |
306 | |