Baloo

fileindexerconfig.cpp
1 /*
2  This file is part of the KDE Project
3  SPDX-FileCopyrightText: 2008-2010 Sebastian Trueg <[email protected]>
4  SPDX-FileCopyrightText: 2013-2014 Vishesh Handa <[email protected]>
5  SPDX-FileCopyrightText: 2020 Benjamin Port <[email protected]>
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 
21 namespace
22 {
23 QString normalizeTrailingSlashes(QString&& path)
24 {
25  while (path.endsWith(QLatin1Char('/'))) {
26  path.chop(1);
27  }
28  path += QLatin1Char('/');
29  return path;
30 }
31 
32 }
33 
34 namespace Baloo
35 {
36 
37 FileIndexerConfig::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 
48 FileIndexerConfig::~FileIndexerConfig()
49 {
50 }
51 
52 QDebug 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 
86 QStringList 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 
106 QStringList FileIndexerConfig::excludeMimetypes() const
107 {
108  return QList<QString>(m_excludeMimetypes.begin(), m_excludeMimetypes.end());
109 }
110 
111 bool FileIndexerConfig::indexHiddenFilesAndFolders() const
112 {
113  return m_indexHidden;
114 }
115 
116 bool FileIndexerConfig::onlyBasicIndexing() const
117 {
118  return m_onlyBasicIndexing;
119 }
120 
121 bool 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 
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 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
174  const QStringView trailingPath = QStringView(normalizedPath).mid(folder.size());
175 #else
176  const auto trailingPath = normalizedPath.midRef(folder.size());
177 #endif
178  const auto pathComponents = trailingPath.split(QLatin1Char('/'), Qt::SkipEmptyParts);
179  for (const auto &c : pathComponents) {
180  if (!shouldFileBeIndexed(c.toString())) {
181  return false;
182  }
183 #ifndef __unix__
184  if (!indexHiddenFilesAndFolders() ||
185  !d.cd(c.toString()) || QFileInfo(d.path()).isHidden()) {
186  return false;
187  }
188 #endif
189  }
190  return true;
191  }
192 
193  return false;
194 }
195 
197 {
198  if (!indexHiddenFilesAndFolders() && fileName.startsWith(QLatin1Char('.'))) {
199  return false;
200  }
201  return !m_excludeFilterRegExpCache.exactMatch(fileName);
202 }
203 
205 {
206  return !m_excludeMimetypes.contains(mimeType);
207 }
208 
209 bool FileIndexerConfig::folderInFolderList(const QString& path, QString& folder) const
210 {
211  const_cast<FileIndexerConfig*>(this)->buildFolderCache();
212 
213  const QString p = normalizeTrailingSlashes(QString(path));
214 
215  for (const auto& entry : m_folderCache) {
216  const QString& f = entry.path;
217  if (p.startsWith(f)) {
218  folder = f;
219  return entry.isIncluded;
220  }
221  }
222  // path is not in the list, thus it should not be included
223  folder.clear();
224  return false;
225 }
226 
227 void FileIndexerConfig::FolderCache::cleanup()
228 {
229  // TODO There are two cases where "redundant" includes
230  // should be kept:
231  // 1. when the "tail" matches a path exclude filter
232  // (m_excludeFilterRegexpCache)
233  // 2. when the explicitly adds a hidden directory, and
234  // we want to index hidden dirs (m_indexHidden)
235  bool keepAllIncluded = true;
236 
237  auto entry = begin();
238  while (entry != end()) {
239  if ((*entry).isIncluded && keepAllIncluded) {
240  ++entry;
241  continue;
242  }
243 
244  const QString entryPath = (*entry).path;
245  auto start = entry; ++start;
246  auto parent = std::find_if(start, end(),
247  [&entryPath](const FolderConfig& _parent) {
248  return entryPath.startsWith(_parent.path);
249  });
250 
251  if (parent != end()) {
252  if ((*entry).isIncluded == (*parent).isIncluded) {
253  // remove identical config
254  entry = erase(entry);
255  } else {
256  ++entry;
257  }
258  } else {
259  if (!(*entry).isIncluded) {
260  // remove excluded a topmost level (default)
261  entry = erase(entry);
262  } else {
263  ++entry;
264  }
265  }
266  }
267 }
268 
269 bool FileIndexerConfig::FolderConfig::operator<(const FolderConfig& other) const
270 {
271  return path.size() > other.path.size() ||
272  (path.size() == other.path.size() && path < other.path);
273 }
274 
275 bool FileIndexerConfig::FolderCache::addFolderConfig(const FolderConfig& config)
276 {
277  if (config.path.isEmpty()) {
278  qCDebug(BALOO) << "Trying to add folder config entry with empty path";
279  return false;
280  }
281  auto newConfig{config};
282  newConfig.path = QDir::cleanPath(config.path) + QLatin1Char('/');
283 
284  auto it = std::lower_bound(cbegin(), cend(), newConfig);
285  if (it != cend() && (*it).path == newConfig.path) {
286  qCDebug(BALOO) << "Folder config entry for" << newConfig.path << "already exists";
287  return false;
288  }
289 
290  it = insert(it, newConfig);
291  return true;
292 }
293 
294 void FileIndexerConfig::buildFolderCache()
295 {
296  if (!m_folderCacheDirty) {
297  return;
298  }
299 
300  if (!m_devices) {
301  m_devices = new StorageDevices(this);
302  }
303 
304  FolderCache cache;
305 
306  const QStringList includeFolders = m_settings->folders();
307  for (const auto& folder : includeFolders) {
308  if (!cache.addFolderConfig({folder, true})) {
309  qCWarning(BALOO) << "Failed to add include folder config entry for" << folder;
310  }
311  }
312 
313  const QStringList excludeFolders = m_settings->excludedFolders();
314  for (const auto& folder : excludeFolders) {
315  if (!cache.addFolderConfig({folder, false})) {
316  qCWarning(BALOO) << "Failed to add exclude folder config entry for" << folder;
317  }
318  }
319 
320  // Add all removable media and network shares as ignored unless they have
321  // been explicitly added in the include list
322  const auto allMedia = m_devices->allMedia();
323  for (const auto& device: allMedia) {
324  const QString mountPath = device.mountPath();
325  if (!device.isUsable() && !mountPath.isEmpty()) {
326  if (!includeFolders.contains(mountPath)) {
327  cache.addFolderConfig({mountPath, false});
328  }
329  }
330  }
331 
332  cache.cleanup();
333  qCDebug(BALOO) << "Folder cache:" << cache;
334  m_folderCache = cache;
335 
336  m_folderCacheDirty = false;
337 }
338 
339 void FileIndexerConfig::buildExcludeFilterRegExpCache()
340 {
341  QStringList newFilters = excludeFilters();
342  m_excludeFilterRegExpCache.rebuildCacheFromFilterList(newFilters);
343 }
344 
345 void FileIndexerConfig::buildMimeTypeCache()
346 {
347  const QStringList excludedTypes = m_settings->excludedMimetypes();
348  m_excludeMimetypes = QSet<QString>(excludedTypes.begin(), excludedTypes.end());
349 }
350 
352 {
353  m_settings->load();
354 
355  m_folderCacheDirty = true;
356  buildExcludeFilterRegExpCache();
357  buildMimeTypeCache();
358 
359  m_indexHidden = m_settings->indexHiddenFolders();
360  m_onlyBasicIndexing = m_settings->onlyBasicIndexing();
361 }
362 
364 {
365  return m_settings->dbVersion();
366 }
367 
368 void FileIndexerConfig::setDatabaseVersion(int version)
369 {
370  m_settings->setDbVersion(version);
371  m_settings->save();
372 }
373 
374 bool FileIndexerConfig::indexingEnabled() const
375 {
376  return m_settings->indexingEnabled();
377 }
378 
380 {
381  return m_maxUncomittedFiles;
382 }
383 
384 } // namespace Baloo
385 
386 #include "moc_fileindexerconfig.cpp"
int databaseVersion() const
Returns the internal version number of the Baloo database.
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
KCALENDARCORE_EXPORT QDataStream & operator<<(QDataStream &out, const KCalendarCore::Alarm::Ptr &)
int size() const const
QStringList includeFolders() const
Folders to search for files to index and analyze.
bool isDir() const const
QDebug & nospace()
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
void clear()
uint maxUncomittedFiles() const
Returns batch size.
QStringView mid(qsizetype start) const const
Q_SCRIPTABLE Q_NOREPLY void start()
void chop(int n)
bool insert(Part *part, qint64 *insertId=nullptr)
QStringList defaultExcludeFilterList()
int defaultExcludeFilterListVersion()
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(),...
SkipEmptyParts
Implements storage for docIds without any associated data Instantiated for:
Definition: coding.cpp:11
bool isEmpty() const const
Active config class which emits signals if the config was changed, for example if the KCM saved the c...
QSet::iterator begin()
KSharedConfigPtr config()
QString fileName() const const
QString path() const const
bool contains(const T &value) const const
QString absolutePath() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool isHidden() const const
QString cleanPath(const QString &path)
int removeDuplicates()
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...
QString path(const QString &relativePath)
bool shouldFileBeIndexed(const QString &fileName) const
Check fileName for all exclude filters.
QSet::iterator end()
QList::iterator begin()
bool shouldMimeTypeBeIndexed(const QString &mimeType) const
Checks if mimeType should be indexed.
QList::iterator end()
bool canBeSearched(const QString &folder) const
Check if folder can be searched.
QStringList excludeFolders() const
Folders that are excluded from indexing.
bool cd(const QString &dirName)
const QList< QKeySequence > & end()
bool shouldFolderBeIndexed(const QString &path) const
Check if the folder at path should be indexed.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Nov 29 2023 03:56:26 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.