| 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 | |