diff --git a/src/file/fileindexerconfig.cpp b/src/file/fileindexerconfig.cpp index 9367df84..13610fde 100644 --- a/src/file/fileindexerconfig.cpp +++ b/src/file/fileindexerconfig.cpp @@ -1,397 +1,399 @@ /* This file is part of the KDE Project Copyright (c) 2008-2010 Sebastian Trueg Copyright (c) 2013-2014 Vishesh Handa This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "fileindexerconfig.h" #include "fileexcludefilters.h" #include "storagedevices.h" #include #include #include #include namespace { /// recursively check if a folder is hidden bool isDirHidden(const QDir& dir) { #ifdef __unix__ return dir.absolutePath().contains(QLatin1String("/.")); #else QDir d = dir; do { if (QFileInfo(d.path()).isHidden()) return true; } while (d.cdUp()); return false; #endif } -QString stripTrailingSlash(const QString& path) +QString normalizeTrailingSlashes(QString&& path) { - return path.endsWith(QLatin1Char('/')) ? path.mid(0, path.length()-1) : path; + while (path.endsWith(QLatin1Char('/'))) { + path.chop(1); + } + path += QLatin1Char('/'); + return path; } } using namespace Baloo; FileIndexerConfig::FileIndexerConfig(QObject* parent) : QObject(parent) , m_config(QStringLiteral("baloofilerc")) , m_folderCacheDirty(true) , m_indexHidden(false) , m_devices(nullptr) , m_maxUncomittedFiles(40) { forceConfigUpdate(); } FileIndexerConfig::~FileIndexerConfig() { } QStringList FileIndexerConfig::includeFolders() const { const_cast(this)->buildFolderCache(); QStringList fl; for (int i = 0; i < m_folderCache.count(); ++i) { if (m_folderCache[i].second) fl << m_folderCache[i].first; } return fl; } QStringList FileIndexerConfig::excludeFolders() const { const_cast(this)->buildFolderCache(); QStringList fl; for (int i = 0; i < m_folderCache.count(); ++i) { if (!m_folderCache[i].second) fl << m_folderCache[i].first; } return fl; } QStringList FileIndexerConfig::excludeFilters() const { KConfigGroup cfg = m_config.group("General"); // read configured exclude filters QSet filters = cfg.readEntry("exclude filters", defaultExcludeFilterList()).toSet(); // make sure we always keep the latest default exclude filters // TODO: there is one problem here. What if the user removed some of the default filters? if (cfg.readEntry("exclude filters version", 0) < defaultExcludeFilterListVersion()) { filters += defaultExcludeFilterList().toSet(); // write the config directly since the KCM does not have support for the version yet // TODO: make this class public and use it in the KCM KConfig config(m_config.name()); KConfigGroup cfg = config.group("General"); cfg.writeEntry("exclude filters", QStringList::fromSet(filters)); cfg.writeEntry("exclude filters version", defaultExcludeFilterListVersion()); } // remove duplicates return QStringList::fromSet(filters); } QStringList FileIndexerConfig::excludeMimetypes() const { return QStringList::fromSet(m_excludeMimetypes); } bool FileIndexerConfig::indexHiddenFilesAndFolders() const { return m_indexHidden; } bool FileIndexerConfig::onlyBasicIndexing() const { return m_onlyBasicIndexing; } bool FileIndexerConfig::isInitialRun() const { return m_config.group("General").readEntry("first run", true); } bool FileIndexerConfig::canBeSearched(const QString& folder) const { QFileInfo fi(folder); QString path = fi.absolutePath(); if (!fi.isDir()) { return false; } else if (shouldFolderBeIndexed(path)) { return true; } const_cast(this)->buildFolderCache(); // Look for included descendants for (const QPair& fld: m_folderCache) { if (fld.second && fld.first.startsWith(path)) { return true; } } return false; } bool FileIndexerConfig::shouldBeIndexed(const QString& path) const { QFileInfo fi(path); if (fi.isDir()) { return shouldFolderBeIndexed(path); } else { return (shouldFolderBeIndexed(fi.absolutePath()) && (!fi.isHidden() || indexHiddenFilesAndFolders()) && shouldFileBeIndexed(fi.fileName())); } } bool FileIndexerConfig::shouldFolderBeIndexed(const QString& path) const { QString folder; if (folderInFolderList(path, folder)) { // we always index the folders in the list // ignoring the name filters if (folder == path) return true; // check for hidden folders QDir dir(path); if (!indexHiddenFilesAndFolders() && isDirHidden(dir)) return false; // check the exclude filters for all components of the path // after folder const QStringList pathComponents = path.mid(folder.count()).split(QLatin1Char('/'), QString::SkipEmptyParts); Q_FOREACH (const QString& c, pathComponents) { if (!shouldFileBeIndexed(c)) { return false; } } return true; } else { return false; } } bool FileIndexerConfig::shouldFileBeIndexed(const QString& fileName) const { if (!indexHiddenFilesAndFolders() && fileName.startsWith(QLatin1Char('.'))) { return false; } return !m_excludeFilterRegExpCache.exactMatch(fileName); } bool FileIndexerConfig::shouldMimeTypeBeIndexed(const QString& mimeType) const { return !m_excludeMimetypes.contains(mimeType); } bool FileIndexerConfig::folderInFolderList(const QString& path) { QString str; return folderInFolderList(path, str); } bool FileIndexerConfig::folderInFolderList(const QString& path, QString& folder) const { const_cast(this)->buildFolderCache(); - const QString p = stripTrailingSlash(path); + const QString p = normalizeTrailingSlashes(QString(path)); // we traverse the list backwards to catch all exclude folders int i = m_folderCache.count(); while (--i >= 0) { const QString& f = m_folderCache[i].first; const bool include = m_folderCache[i].second; if (p.startsWith(f)) { folder = f; return include; } } // path is not in the list, thus it should not be included folder.clear(); return false; } namespace { /** * Returns true if the specified folder f would already be excluded using the list * folders. */ bool alreadyExcluded(const QList >& folders, const QString& f) { bool included = false; for (int i = 0; i < folders.count(); ++i) { QString path = folders[i].first; - if (!path.endsWith(QLatin1Char('/'))) - path.append(QLatin1Char('/')); if (f != folders[i].first && f.startsWith(path)) { included = folders[i].second; } } return !included; } /** * Simple insertion sort */ void insertSortFolders(const QStringList& folders, bool include, QList >& result) { - Q_FOREACH (const QString& f, folders) { + Q_FOREACH (QString path, folders) { int pos = 0; - QString path = stripTrailingSlash(f); + path = normalizeTrailingSlashes(std::move(path)); while (result.count() > pos && result[pos].first < path) ++pos; result.insert(pos, qMakePair(path, include)); } } /** * Remove useless exclude entries which would confuse the folderInFolderList algo. * This makes sure all top-level items are include folders. * This runs in O(n^2) and could be optimized but what for. */ void cleanupList(QList >& result) { int i = 0; while (i < result.count()) { if (result[i].first.isEmpty() || (!result[i].second && alreadyExcluded(result, result[i].first))) result.removeAt(i); else ++i; } } } void FileIndexerConfig::buildFolderCache() { if (!m_folderCacheDirty) { return; } if (!m_devices) { m_devices = new StorageDevices(this); } KConfigGroup group = m_config.group("General"); QStringList includeFoldersPlain = group.readPathEntry("folders", QStringList() << QDir::homePath()); QStringList excludeFoldersPlain = group.readPathEntry("exclude folders", QStringList()); // Add all removable media and network shares as ignored unless they have // been explicitly added in the include list const auto allMedia = m_devices->allMedia(); for (const auto& device: allMedia) { const QString mountPath = device.mountPath(); if (!device.isUsable() && !mountPath.isEmpty()) { if (!includeFoldersPlain.contains(mountPath)) { excludeFoldersPlain << mountPath; } } } m_folderCache.clear(); insertSortFolders(includeFoldersPlain, true, m_folderCache); insertSortFolders(excludeFoldersPlain, false, m_folderCache); cleanupList(m_folderCache); m_folderCacheDirty = false; } void FileIndexerConfig::buildExcludeFilterRegExpCache() { QStringList newFilters = excludeFilters(); m_excludeFilterRegExpCache.rebuildCacheFromFilterList(newFilters); } void FileIndexerConfig::buildMimeTypeCache() { m_excludeMimetypes = m_config.group("General").readPathEntry("exclude mimetypes", defaultExcludeMimetypes()).toSet(); } void FileIndexerConfig::forceConfigUpdate() { m_config.reparseConfiguration(); m_folderCacheDirty = true; buildExcludeFilterRegExpCache(); buildMimeTypeCache(); m_indexHidden = m_config.group("General").readEntry("index hidden folders", false); m_onlyBasicIndexing = m_config.group("General").readEntry("only basic indexing", false); } void FileIndexerConfig::setInitialRun(bool isInitialRun) { m_config.group("General").writeEntry("first run", isInitialRun); m_config.sync(); } bool FileIndexerConfig::initialUpdateDisabled() const { return m_config.group("General").readEntry("disable initial update", true); } int FileIndexerConfig::databaseVersion() const { return m_config.group("General").readEntry("dbVersion", 0); } void FileIndexerConfig::setDatabaseVersion(int version) { m_config.group("General").writeEntry("dbVersion", version); m_config.sync(); } bool FileIndexerConfig::indexingEnabled() const { return m_config.group("Basic Settings").readEntry("Indexing-Enabled", true); } uint FileIndexerConfig::maxUncomittedFiles() const { return m_maxUncomittedFiles; } diff --git a/src/tools/balooctl/configcommand.cpp b/src/tools/balooctl/configcommand.cpp index f8455d15..da633e27 100644 --- a/src/tools/balooctl/configcommand.cpp +++ b/src/tools/balooctl/configcommand.cpp @@ -1,443 +1,444 @@ /* * This file is part of the KDE Baloo project. * Copyright (C) 2015 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "configcommand.h" #include "indexerconfig.h" #include #include #include #include using namespace Baloo; /* * TODO: remove code duplication, we are performing similar operations * for excludeFolders, includeFolders and excludeFilters just using different * setters/getters. Figure out a way to unify the code while still keeping it * readable. */ namespace { QString normalizeTrailingSlashes(QString&& path) { while (path.endsWith(QLatin1Char('/'))) { path.chop(1); } + path += QLatin1Char('/'); return path; } } QString ConfigCommand::command() { return QStringLiteral("config"); } QString ConfigCommand::description() { return i18n("Manipulate the Baloo configuration"); } int ConfigCommand::exec(const QCommandLineParser& parser) { QStringList args = parser.positionalArguments(); args.removeFirst(); QTextStream out(stdout); auto printCommand = [&out](const QString& command, const QString& description) { out << " "; out.setFieldWidth(25); out.setFieldAlignment(QTextStream::AlignLeft); out << command; out.setFieldWidth(0); out.setFieldAlignment(QTextStream::AlignLeft); out << description << endl; }; const QString command = args.isEmpty() ? QStringLiteral("help") : args.takeFirst(); if (command == QStringLiteral("help")) { // Show help out << i18n("The config command can be used to manipulate the Baloo Configuration") << endl; out << i18n("Usage: balooctl config ") << endl << endl; out << i18n("Possible Commands:") << endl; printCommand(QStringLiteral("add"), i18n("Add a value to config parameter")); printCommand(QStringLiteral("rm | remove"), i18n("Remove a value from a config parameter")); printCommand(QStringLiteral("list | ls | show"), i18n("Show the value of a config parameter")); printCommand(QStringLiteral("set"), i18n("Set the value of a config parameter")); printCommand(QStringLiteral("help"), i18n("Display this help menu")); return 0; } if (command == QStringLiteral("list") || command == QStringLiteral("ls") || command == QStringLiteral("show")) { if (args.isEmpty()) { out << i18n("The following configuration options may be listed:") << endl << endl; printCommand(QStringLiteral("hidden"), i18n("Controls if Baloo indexes hidden files and folders")); printCommand(QStringLiteral("contentIndexing"), i18n("Controls if Baloo indexes file content")); printCommand(QStringLiteral("includeFolders"), i18n("The list of folders which Baloo indexes")); printCommand(QStringLiteral("excludeFolders"), i18n("The list of folders which Baloo will never index")); printCommand(QStringLiteral("excludeFilters"), i18n("The list of filters which are used to exclude files")); printCommand(QStringLiteral("excludeMimetypes"), i18n("The list of mimetypes which are used to exclude files")); return 0; } IndexerConfig config; QString value = args.takeFirst(); if (value.compare(QLatin1String("hidden"), Qt::CaseInsensitive) == 0) { if (config.indexHidden()) { out << "yes" << endl; } else { out << "no" << endl; } return 0; } if (value.compare(QStringLiteral("contentIndexing"), Qt::CaseInsensitive) == 0) { if (config.onlyBasicIndexing()) { out << "no" << endl; } else { out << "yes" << endl; } return 0; } auto printList = [&out](const QStringList& list) { for (const QString& item: list) { out << item << endl; } }; if (value.compare(QLatin1String("includeFolders"), Qt::CaseInsensitive) == 0) { printList(config.includeFolders()); return 0; } if (value.compare(QLatin1String("excludeFolders"), Qt::CaseInsensitive) == 0) { printList(config.excludeFolders()); return 0; } if (value.compare(QStringLiteral("excludeFilters"), Qt::CaseInsensitive) == 0) { printList(config.excludeFilters()); return 0; } if (value.compare(QStringLiteral("excludeMimetypes"), Qt::CaseInsensitive) == 0) { printList(config.excludeMimetypes()); return 0; } out << i18n("Config parameter could not be found") << endl; return 1; } if (command == QStringLiteral("rm") || command == QStringLiteral("remove")) { if (args.isEmpty()) { out << i18n("The following configuration options may be modified:") << endl << endl; printCommand(QStringLiteral("includeFolders"), i18n("The list of folders which Baloo indexes")); printCommand(QStringLiteral("excludeFolders"), i18n("The list of folders which Baloo will never index")); printCommand(QStringLiteral("excludeFilters"), i18n("The list of filters which are used to exclude files")); printCommand(QStringLiteral("excludeMimetypes"), i18n("The list of mimetypes which are used to exclude files")); return 0; } IndexerConfig config; QString value = args.takeFirst(); if (value.compare(QLatin1String("includeFolders"), Qt::CaseInsensitive) == 0) { if (args.isEmpty()) { out << i18n("A folder must be provided") << endl; return 1; } QString path = args.takeFirst().replace(QStringLiteral("$HOME"), QDir::homePath()); path = normalizeTrailingSlashes(std::move(path)); QStringList folders = config.includeFolders(); if (!folders.contains(path)) { out << i18n("%1 is not in the list of include folders", path) << endl; return 1; } folders.removeAll(path); config.setIncludeFolders(folders); return 0; } if (value.compare(QLatin1String("excludeFolders"), Qt::CaseInsensitive) == 0) { if (args.isEmpty()) { out << i18n("A folder must be provided") << endl; return 1; } QString path = args.takeFirst().replace(QStringLiteral("$HOME"), QDir::homePath()); path = normalizeTrailingSlashes(std::move(path)); QStringList folders = config.excludeFolders(); if (!folders.contains(path)) { out << i18n("%1 is not in the list of exclude folders", path) << endl; return 1; } folders.removeAll(path); config.setExcludeFolders(folders); return 0; } if (value.compare(QStringLiteral("excludeFilters"), Qt::CaseInsensitive) == 0) { if (args.isEmpty()) { out << i18n("A filter must be provided") << endl; return 1; } QStringList filters = config.excludeFilters(); if (!filters.contains(args.first())) { out << i18n("%1 is not in list of exclude filters", args.first()) << endl; return 1; } filters.removeAll(args.first()); config.setExcludeFilters(filters); return 0; } if (value.compare(QStringLiteral("excludeMimetypes"), Qt::CaseInsensitive) == 0) { if (args.isEmpty()) { out << i18n("A mimetype must be provided") << endl; return 1; } QStringList mimetypes = config.excludeMimetypes(); if (!mimetypes.contains(args.first())) { out << i18n("%1 is not in list of exclude mimetypes", args.first()) << endl; return 1; } mimetypes.removeAll(args.first()); config.setExcludeMimetypes(mimetypes); return 0; } out << i18n("Config parameter could not be found") << endl; return 1; } if (command == QStringLiteral("add")) { if (args.isEmpty()) { out << i18n("The following configuration options may be modified:") << endl << endl; printCommand(QStringLiteral("includeFolders"), i18n("The list of folders which Baloo indexes")); printCommand(QStringLiteral("excludeFolders"), i18n("The list of folders which Baloo will never index")); printCommand(QStringLiteral("excludeFilters"), i18n("The list of filters which are used to exclude files")); printCommand(QStringLiteral("excludeMimetypes"), i18n("The list of mimetypes which are used to exclude files")); return 0; } IndexerConfig config; QString value = args.takeFirst(); if (value.compare(QLatin1String("includeFolders"), Qt::CaseInsensitive) == 0) { if (args.isEmpty()) { out << i18n("A folder must be provided") << endl; return 1; } auto fileInfo = QFileInfo(args.takeFirst()); if (!fileInfo.exists()) { out << i18n("Path does not exist") << endl; return 1; } if (!fileInfo.isDir()) { out << i18n("Path is not a directory") << endl; return 1; } auto path = normalizeTrailingSlashes(fileInfo.absoluteFilePath()); QStringList folders = config.includeFolders(); if (folders.contains(path)) { out << i18n("%1 is already in the list of include folders", path) << endl; return 1; } if (config.excludeFolders().contains(path)) { out << i18n("%1 is in the list of exclude folders", path) << endl; return 1; } folders.append(path); config.setIncludeFolders(folders); return 0; } if (value.compare(QLatin1String("excludeFolders"), Qt::CaseInsensitive) == 0) { if (args.isEmpty()) { out << i18n("A folder must be provided") << endl; return 1; } auto fileInfo = QFileInfo(args.takeFirst()); if (!fileInfo.exists()) { out << i18n("Path does not exist") << endl; return 1; } if (!fileInfo.isDir()) { out << i18n("Path is not a directory") << endl; return 1; } auto path = normalizeTrailingSlashes(fileInfo.absoluteFilePath()); QStringList folders = config.excludeFolders(); if (folders.contains(path)) { out << i18n("%1 is already in the list of exclude folders", path) << endl; return 1; } if (config.includeFolders().contains(path)) { out << i18n("%1 is in the list of exclude folders", path) << endl; return 1; } folders.append(path); config.setExcludeFolders(folders); return 0; } if (value.compare(QStringLiteral("excludeFilters"), Qt::CaseInsensitive) == 0) { if (args.empty()) { out << i18n("A filter must be provided") << endl; return 1; } QStringList filters = config.excludeFilters(); if (filters.contains(args.first())) { out << i18n("Exclude filter is already in the list") << endl; return 1; } filters.append(args.first()); config.setExcludeFilters(filters); return 0; } if (value.compare(QStringLiteral("excludeMimetypes"), Qt::CaseInsensitive) == 0) { if (args.empty()) { out << i18n("A mimetype must be provided") << endl; return 1; } QStringList mimetypes = config.excludeMimetypes(); if (mimetypes.contains(args.first())) { out << i18n("Exclude mimetype is already in the list") << endl; return 1; } mimetypes.append(args.first()); config.setExcludeMimetypes(mimetypes); return 0; } out << i18n("Config parameter could not be found") << endl; return 1; } if (command == QStringLiteral("set")) { if (args.isEmpty()) { out << i18n("The following configuration options may be modified:") << endl << endl; printCommand(QStringLiteral("hidden"), i18n("Controls if Baloo indexes hidden files and folders")); printCommand(QStringLiteral("contentIndexing"), i18n("Controls if Baloo indexes file content")); return 0; } IndexerConfig config; QString configParam = args.takeFirst(); if (configParam == QStringLiteral("hidden")) { if (args.isEmpty()) { out << i18n("A value must be provided") << endl; return 1; } QString value = args.takeFirst(); if (value.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0 || value.compare(QLatin1String("y"), Qt::CaseInsensitive) == 0 || value.compare(QLatin1String("yes"), Qt::CaseInsensitive) == 0 || value.compare(QLatin1String("1")) == 0) { config.setIndexHidden(true); return 0; } if (value.compare(QLatin1String("false"), Qt::CaseInsensitive) == 0 || value.compare(QLatin1String("n"), Qt::CaseInsensitive) == 0 || value.compare(QLatin1String("no"), Qt::CaseInsensitive) == 0 || value.compare(QLatin1String("0")) == 0) { config.setIndexHidden(false); return 0; } out << i18n("Invalid value") << endl; return 1; } if (configParam.compare(QStringLiteral("contentIndexing"), Qt::CaseInsensitive) == 0) { if (args.isEmpty()) { out << i18n("A value must be provided") << endl; return 1; } QString value = args.takeFirst(); if (value.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0 || value.compare(QLatin1String("y"), Qt::CaseInsensitive) == 0 || value.compare(QLatin1String("yes"), Qt::CaseInsensitive) == 0 || value.compare(QLatin1String("1")) == 0) { config.setOnlyBasicIndexing(false); return 0; } if (value.compare(QLatin1String("false"), Qt::CaseInsensitive) == 0 || value.compare(QLatin1String("n"), Qt::CaseInsensitive) == 0 || value.compare(QLatin1String("no"), Qt::CaseInsensitive) == 0 || value.compare(QLatin1String("0")) == 0) { config.setOnlyBasicIndexing(true); return 0; } out << i18n("Invalid value") << endl; return 1; } out << i18n("Config parameter could not be found") << endl; return 1; } return 0; }