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
60QStringList FileIndexerConfig::includeFolders() const
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
73QStringList FileIndexerConfig::excludeFolders() const
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
121bool FileIndexerConfig::canBeSearched(const QString& folder) const
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
143bool FileIndexerConfig::shouldBeIndexed(const QString& path) const
144{
145 QFileInfo fi(path);
146 if (fi.isDir()) {
147 return shouldFolderBeIndexed(path);
148 } else {
149 return (shouldFolderBeIndexed(fi.absolutePath()) &&
150 (!fi.isHidden() || indexHiddenFilesAndFolders()) &&
151 shouldFileBeIndexed(fi.fileName()));
152 }
153}
154
155bool FileIndexerConfig::shouldFolderBeIndexed(const QString& path) const
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
192bool FileIndexerConfig::shouldFileBeIndexed(const QString& fileName) const
193{
194 if (!indexHiddenFilesAndFolders() && fileName.startsWith(QLatin1Char('.'))) {
195 return false;
196 }
197 return !m_excludeFilterRegExpCache.exactMatch(fileName);
198}
199
200bool FileIndexerConfig::shouldMimeTypeBeIndexed(const QString& mimeType) const
201{
202 return !m_excludeMimetypes.contains(mimeType);
203}
204
205bool FileIndexerConfig::folderInFolderList(const QString& path, QString& folder) const
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
347void FileIndexerConfig::forceConfigUpdate()
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
359int FileIndexerConfig::databaseVersion() const
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
375uint FileIndexerConfig::maxUncomittedFiles() const
376{
377 return m_maxUncomittedFiles;
378}
379
380} // namespace Baloo
381
382#include "moc_fileindexerconfig.cpp"
Active config class which emits signals if the config was changed, for example if the KCM saved the c...
Q_SCRIPTABLE Q_NOREPLY void start()
Implements storage for docIds without any associated data Instantiated for:
Definition coding.cpp:11
int defaultExcludeFilterListVersion()
QStringList defaultExcludeFilterList()
KCALENDARCORE_EXPORT QDataStream & operator<<(QDataStream &out, const KCalendarCore::Alarm::Ptr &)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
QString path(const QString &relativePath)
KGuiItem insert()
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
bool contains(QLatin1StringView str, 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-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:52:28 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.