Baloo

fileindexerconfig.cpp
1/*
2 This file is part of the KDE Project
3 SPDX-FileCopyrightText: 2008-2010 Sebastian Trueg <trueg@kde.org>
4 SPDX-FileCopyrightText: 2013-2014 Vishesh Handa <me@vhanda.in>
5 SPDX-FileCopyrightText: 2020 Benjamin Port <benjamin.port@enioka.com>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "fileindexerconfig.h"
11#include "fileexcludefilters.h"
12#include "storagedevices.h"
13#include "baloodebug.h"
14
15#include <QStringList>
16#include <QDir>
17
18#include <QStandardPaths>
19#include "baloosettings.h"
20
21namespace
22{
23QString normalizeTrailingSlashes(QString&& path)
24{
25 while (path.endsWith(QLatin1Char('/'))) {
26 path.chop(1);
27 }
28 path += QLatin1Char('/');
29 return path;
30}
31
32}
33
34namespace Baloo
35{
36
37FileIndexerConfig::FileIndexerConfig(QObject* parent)
38 : QObject(parent)
39 , m_settings(new BalooSettings(this))
40 , m_folderCacheDirty(true)
41 , m_indexHidden(false)
42 , m_devices(nullptr)
43 , m_maxUncomittedFiles(40)
44{
45 forceConfigUpdate();
46}
47
48FileIndexerConfig::~FileIndexerConfig()
49{
50}
51
52QDebug operator<<(QDebug dbg, const FileIndexerConfig::FolderConfig& entry)
53{
54 QDebugStateSaver saver(dbg);
55 dbg.nospace() << entry.path << ": "
56 << (entry.isIncluded ? "included" : "excluded");
57 return dbg;
58}
59
61{
62 const_cast<FileIndexerConfig*>(this)->buildFolderCache();
63
64 QStringList fl;
65 for (const auto& entry : m_folderCache) {
66 if (entry.isIncluded) {
67 fl << entry.path;
68 }
69 }
70 return fl;
71}
72
74{
75 const_cast<FileIndexerConfig*>(this)->buildFolderCache();
76
77 QStringList fl;
78 for (const auto& entry : m_folderCache) {
79 if (!entry.isIncluded) {
80 fl << entry.path;
81 }
82 }
83 return fl;
84}
85
86QStringList FileIndexerConfig::excludeFilters() const
87{
88 // read configured exclude filters
89 QStringList filters = m_settings->excludedFilters();
90
91 // make sure we always keep the latest default exclude filters
92 // TODO: there is one problem here. What if the user removed some of the default filters?
93 if (m_settings->excludedFiltersVersion() < defaultExcludeFilterListVersion()) {
94 filters += defaultExcludeFilterList();
95 // in case the cfg entry was empty and filters == defaultExcludeFilterList()
96 filters.removeDuplicates();
97
98 // write the config directly since the KCM does not have support for the version yet
99 m_settings->setExcludedFilters(filters);
100 m_settings->setExcludedFiltersVersion(defaultExcludeFilterListVersion());
101 }
102
103 return filters;
104}
105
106QStringList FileIndexerConfig::excludeMimetypes() const
107{
108 return QList<QString>(m_excludeMimetypes.begin(), m_excludeMimetypes.end());
109}
110
111bool FileIndexerConfig::indexHiddenFilesAndFolders() const
112{
113 return m_indexHidden;
114}
115
116bool FileIndexerConfig::onlyBasicIndexing() const
117{
118 return m_onlyBasicIndexing;
119}
120
122{
123 QFileInfo fi(folder);
124 QString path = fi.absolutePath();
125 if (!fi.isDir()) {
126 return false;
127 } else if (shouldFolderBeIndexed(path)) {
128 return true;
129 }
130
131 const_cast<FileIndexerConfig*>(this)->buildFolderCache();
132
133 // Look for included descendants
134 for (const auto& entry : m_folderCache) {
135 if (entry.isIncluded && entry.path.startsWith(path)) {
136 return true;
137 }
138 }
139
140 return false;
141}
142
144{
145 QFileInfo fi(path);
146 if (fi.isDir()) {
147 return shouldFolderBeIndexed(path);
148 } else {
149 return (shouldFolderBeIndexed(fi.absolutePath()) &&
150 (!fi.isHidden() || indexHiddenFilesAndFolders()) &&
152 }
153}
154
156{
157 QString folder;
158 auto normalizedPath = normalizeTrailingSlashes(QString(path));
159
160 if (folderInFolderList(normalizedPath, folder)) {
161 // we always index the folders in the list
162 // ignoring the name filters
163 if (folder == normalizedPath) {
164 return true;
165 }
166
167 // check the exclude filters for all components of the path
168 // after folder
169#ifndef __unix__
170 QDir d(folder);
171#endif
172
173 const QStringView trailingPath = QStringView(normalizedPath).mid(folder.size());
174 const auto pathComponents = trailingPath.split(QLatin1Char('/'), Qt::SkipEmptyParts);
175 for (const auto &c : pathComponents) {
176 if (!shouldFileBeIndexed(c.toString())) {
177 return false;
178 }
179#ifndef __unix__
180 if (!indexHiddenFilesAndFolders() ||
181 !d.cd(c.toString()) || QFileInfo(d.path()).isHidden()) {
182 return false;
183 }
184#endif
185 }
186 return true;
187 }
188
189 return false;
190}
191
193{
194 if (!indexHiddenFilesAndFolders() && fileName.startsWith(QLatin1Char('.'))) {
195 return false;
196 }
197 return !m_excludeFilterRegExpCache.exactMatch(fileName);
198}
199
201{
202 return !m_excludeMimetypes.contains(mimeType);
203}
204
206{
207 const_cast<FileIndexerConfig*>(this)->buildFolderCache();
208
209 const QString p = normalizeTrailingSlashes(QString(path));
210
211 for (const auto& entry : m_folderCache) {
212 const QString& f = entry.path;
213 if (p.startsWith(f)) {
214 folder = f;
215 return entry.isIncluded;
216 }
217 }
218 // path is not in the list, thus it should not be included
219 folder.clear();
220 return false;
221}
222
223void FileIndexerConfig::FolderCache::cleanup()
224{
225 // TODO There are two cases where "redundant" includes
226 // should be kept:
227 // 1. when the "tail" matches a path exclude filter
228 // (m_excludeFilterRegexpCache)
229 // 2. when the explicitly adds a hidden directory, and
230 // we want to index hidden dirs (m_indexHidden)
231 bool keepAllIncluded = true;
232
233 auto entry = begin();
234 while (entry != end()) {
235 if ((*entry).isIncluded && keepAllIncluded) {
236 ++entry;
237 continue;
238 }
239
240 const QString entryPath = (*entry).path;
241 auto start = entry; ++start;
242 auto parent = std::find_if(start, end(),
243 [&entryPath](const FolderConfig& _parent) {
244 return entryPath.startsWith(_parent.path);
245 });
246
247 if (parent != end()) {
248 if ((*entry).isIncluded == (*parent).isIncluded) {
249 // remove identical config
250 entry = erase(entry);
251 } else {
252 ++entry;
253 }
254 } else {
255 if (!(*entry).isIncluded) {
256 // remove excluded a topmost level (default)
257 entry = erase(entry);
258 } else {
259 ++entry;
260 }
261 }
262 }
263}
264
265bool FileIndexerConfig::FolderConfig::operator<(const FolderConfig& other) const
266{
267 return path.size() > other.path.size() ||
268 (path.size() == other.path.size() && path < other.path);
269}
270
271bool FileIndexerConfig::FolderCache::addFolderConfig(const FolderConfig& config)
272{
273 if (config.path.isEmpty()) {
274 qCDebug(BALOO) << "Trying to add folder config entry with empty path";
275 return false;
276 }
277 auto newConfig{config};
278 newConfig.path = QDir::cleanPath(config.path) + QLatin1Char('/');
279
280 auto it = std::lower_bound(cbegin(), cend(), newConfig);
281 if (it != cend() && (*it).path == newConfig.path) {
282 qCDebug(BALOO) << "Folder config entry for" << newConfig.path << "already exists";
283 return false;
284 }
285
286 it = insert(it, newConfig);
287 return true;
288}
289
290void FileIndexerConfig::buildFolderCache()
291{
292 if (!m_folderCacheDirty) {
293 return;
294 }
295
296 if (!m_devices) {
297 m_devices = new StorageDevices(this);
298 }
299
300 FolderCache cache;
301
302 const QStringList includeFolders = m_settings->folders();
303 for (const auto& folder : includeFolders) {
304 if (!cache.addFolderConfig({folder, true})) {
305 qCWarning(BALOO) << "Failed to add include folder config entry for" << folder;
306 }
307 }
308
309 const QStringList excludeFolders = m_settings->excludedFolders();
310 for (const auto& folder : excludeFolders) {
311 if (!cache.addFolderConfig({folder, false})) {
312 qCWarning(BALOO) << "Failed to add exclude folder config entry for" << folder;
313 }
314 }
315
316 // Add all removable media and network shares as ignored unless they have
317 // been explicitly added in the include list
318 const auto allMedia = m_devices->allMedia();
319 for (const auto& device: allMedia) {
320 const QString mountPath = device.mountPath();
321 if (!device.isUsable() && !mountPath.isEmpty()) {
322 if (!includeFolders.contains(mountPath)) {
323 cache.addFolderConfig({mountPath, false});
324 }
325 }
326 }
327
328 cache.cleanup();
329 qCDebug(BALOO) << "Folder cache:" << cache;
330 m_folderCache = cache;
331
332 m_folderCacheDirty = false;
333}
334
335void FileIndexerConfig::buildExcludeFilterRegExpCache()
336{
337 QStringList newFilters = excludeFilters();
338 m_excludeFilterRegExpCache.rebuildCacheFromFilterList(newFilters);
339}
340
341void FileIndexerConfig::buildMimeTypeCache()
342{
343 const QStringList excludedTypes = m_settings->excludedMimetypes();
344 m_excludeMimetypes = QSet<QString>(excludedTypes.begin(), excludedTypes.end());
345}
346
348{
349 m_settings->load();
350
351 m_folderCacheDirty = true;
352 buildExcludeFilterRegExpCache();
353 buildMimeTypeCache();
354
355 m_indexHidden = m_settings->indexHiddenFolders();
356 m_onlyBasicIndexing = m_settings->onlyBasicIndexing();
357}
358
360{
361 return m_settings->dbVersion();
362}
363
364void FileIndexerConfig::setDatabaseVersion(int version)
365{
366 m_settings->setDbVersion(version);
367 m_settings->save();
368}
369
370bool FileIndexerConfig::indexingEnabled() const
371{
372 return m_settings->indexingEnabled();
373}
374
376{
377 return m_maxUncomittedFiles;
378}
379
380} // namespace Baloo
381
382#include "moc_fileindexerconfig.cpp"
QStringList excludeFolders() const
Folders that are excluded from indexing.
bool shouldMimeTypeBeIndexed(const QString &mimeType) const
Checks if mimeType should be indexed.
bool shouldFolderBeIndexed(const QString &path) const
Check if the folder at path should be indexed.
int databaseVersion() const
Returns the internal version number of the Baloo database.
void forceConfigUpdate()
Reread the config from disk and update the configuration cache.
bool shouldBeIndexed(const QString &path) const
Check if file or folder path should be indexed taking into account the includeFolders(),...
bool folderInFolderList(const QString &path, QString &folder) const
Check if path is in the list of folders to be indexed taking include and exclude folders into account...
bool shouldFileBeIndexed(const QString &fileName) const
Check fileName for all exclude filters.
uint maxUncomittedFiles() const
Returns batch size.
QStringList includeFolders() const
Folders to search for files to index and analyze.
bool canBeSearched(const QString &folder) const
Check if folder can be searched.
Q_SCRIPTABLE QString start(QString train="")
Implements storage for docIds without any associated data Instantiated for:
Definition coding.cpp:11
int defaultExcludeFilterListVersion()
QStringList defaultExcludeFilterList()
QString path(const QString &relativePath)
KGuiItem insert()
const QList< QKeySequence > & end()
QDebug & nospace()
bool cd(const QString &dirName)
QString cleanPath(const QString &path)
QString path() const const
QString absolutePath() const const
QString fileName() const const
bool isDir() const const
bool isHidden() const const
iterator begin()
iterator end()
void chop(qsizetype n)
void clear()
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
qsizetype removeDuplicates()
QStringView mid(qsizetype start, qsizetype length) const const
QList< QStringView > split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
SkipEmptyParts
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:50:57 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.