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_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 | |
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 | 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 | |
193 | static 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 | |
202 | static 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 | |
211 | void 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 | |
226 | void 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 | |
240 | void 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 | |
251 | void 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 | |
268 | uint FileIndexScheduler::getRemainingTime() |
269 | { |
270 | if (m_indexerState != ContentIndexing) { |
271 | return 0; |
272 | } |
273 | return m_timeEstimator.calculateTimeLeft(); |
274 | } |
275 | |
276 | void FileIndexScheduler::scheduleCheckUnindexedFiles() |
277 | { |
278 | m_checkUnindexedFiles = true; |
279 | } |
280 | |
281 | void FileIndexScheduler::checkUnindexedFiles() |
282 | { |
283 | m_checkUnindexedFiles = true; |
284 | scheduleIndexing(); |
285 | } |
286 | |
287 | void FileIndexScheduler::scheduleCheckStaleIndexEntries() |
288 | { |
289 | m_checkStaleIndexEntries = true; |
290 | } |
291 | |
292 | void FileIndexScheduler::checkStaleIndexEntries() |
293 | { |
294 | m_checkStaleIndexEntries = true; |
295 | scheduleIndexing(); |
296 | } |
297 | |
298 | uint FileIndexScheduler::getBatchSize() |
299 | { |
300 | return m_config->maxUncomittedFiles(); |
301 | } |
302 | |
303 | #include "moc_fileindexscheduler.cpp" |
304 | |