1 /*
2 This file is part of the KDE Baloo Project
3 SPDX-FileCopyrightText: 2014 Vishesh Handa <me@vhanda.in>
4 SPDX-FileCopyrightText: 2019 Tomaz Canabrava <tcanabrava@kde.org>
5 SPDX-FileCopyrightText: 2020 Benjamin Port <benjamin.port@enioka.com>
6
7 SPDX-License-Identifier: LGPL-2.1-or-later
8
9 */
10
11 #include "filteredfoldermodel.h"
12
13 #include <QIcon>
14
15 #include <KLocalizedString>
16 #include <QDir>
17 #include <QStringList>
18 #include <QTimer>
19 #include <QUrl>
20
21 #include "baloo/baloosettings.h"
22
23 namespace
24 {
normalizeTrailingSlashes(QString && input)25 QString normalizeTrailingSlashes(QString &&input)
26 {
27 if (!input.endsWith('/'))
28 return input + QLatin1Char('/');
29 return input;
30 }
31
addTrailingSlashes(QStringList && list)32 QStringList addTrailingSlashes(QStringList &&list)
33 {
34 for (QString &str : list) {
35 str = normalizeTrailingSlashes(std::move(str));
36 }
37 return list;
38 }
39
makeHomePretty(const QString & url)40 QString makeHomePretty(const QString &url)
41 {
42 if (url.startsWith(QDir::homePath()))
43 return QString(url).replace(0, QDir::homePath().length(), QStringLiteral("~"));
44 return url;
45 }
46 }
47
FilteredFolderModel(BalooSettings * settings,QObject * parent)48 FilteredFolderModel::FilteredFolderModel(BalooSettings *settings, QObject *parent)
49 : QAbstractListModel(parent)
50 , m_settings(settings)
51 {
52 }
53
updateDirectoryList()54 void FilteredFolderModel::updateDirectoryList()
55 {
56 beginResetModel();
57
58 const QStringList runtimeExcluded = m_runtimeConfig.excludeFolders();
59
60 QStringList settingsIncluded = addTrailingSlashes(m_settings->folders());
61 QStringList settingsExcluded = addTrailingSlashes(m_settings->excludedFolders());
62
63 const QString homePath = normalizeTrailingSlashes(QDir::homePath());
64
65 auto folderListEntry = [&homePath](const QString &url, bool include, bool fromConfig) {
66 QString displayName = url;
67 if (displayName.size() > 1) {
68 displayName.chop(1);
69 }
70 if (displayName.startsWith(homePath)) {
71 displayName.replace(0, homePath.length(), QStringLiteral("~/"));
72 }
73
74 QString icon = QStringLiteral("folder");
75 if (url == homePath) {
76 icon = QStringLiteral("user-home");
77 } else if (!fromConfig) {
78 icon = QStringLiteral("drive-harddisk");
79 }
80
81 return FolderInfo{url, displayName, icon, include, fromConfig};
82 };
83 m_folderList.clear();
84
85 for (const QString &folder : settingsIncluded) {
86 m_folderList.append(folderListEntry(folder, true, true));
87 }
88 for (const QString &folder : settingsExcluded) {
89 m_folderList.append(folderListEntry(folder, false, true));
90 }
91
92 // Add any automatically excluded mounts to the list
93 for (const QString &folder : runtimeExcluded) {
94 if (settingsIncluded.contains(folder) || settingsExcluded.contains(folder)) {
95 // Do not add any duplicates
96 continue;
97 }
98 if (m_deletedSettings.contains(folder)) {
99 // Skip entries deleted from the config
100 continue;
101 }
102 m_folderList.append(folderListEntry(folder, false, false));
103 }
104
105 std::sort(m_folderList.begin(), m_folderList.end(), [](const FolderInfo &a, const FolderInfo &b) {
106 return a.url < b.url;
107 });
108
109 endResetModel();
110 }
111
data(const QModelIndex & idx,int role) const112 QVariant FilteredFolderModel::data(const QModelIndex &idx, int role) const
113 {
114 if (!idx.isValid() || idx.row() >= m_folderList.size()) {
115 return {};
116 }
117
118 const auto entry = m_folderList.at(idx.row());
119 switch (role) {
120 case Qt::DisplayRole:
121 return entry.displayName;
122 case Qt::WhatsThisRole:
123 return entry.url;
124 case Qt::DecorationRole:
125 return entry.icon;
126 case Qt::ToolTipRole:
127 return makeHomePretty(entry.url);
128 case Url:
129 return entry.url;
130 case Folder:
131 return entry.displayName;
132 case EnableIndex:
133 return entry.enableIndex;
134 case Deletable:
135 return entry.isFromConfig && entry.url != normalizeTrailingSlashes(QDir::homePath());
136 default:
137 return {};
138 }
139 }
140
setData(const QModelIndex & idx,const QVariant & value,int role)141 bool FilteredFolderModel::setData(const QModelIndex &idx, const QVariant &value, int role)
142 {
143 if (!idx.isValid() || idx.row() >= m_folderList.size()) {
144 return false;
145 }
146 FolderInfo &entry = m_folderList[idx.row()];
147 if (role == EnableIndex) {
148 entry.enableIndex = value.toBool();
149 syncFolderConfig(entry);
150 Q_EMIT dataChanged(idx, idx);
151 return true;
152 }
153 return false;
154 }
155
rowCount(const QModelIndex & parent) const156 int FilteredFolderModel::rowCount(const QModelIndex &parent) const
157 {
158 Q_UNUSED(parent);
159 return m_folderList.count();
160 }
161
addFolder(const QString & url,const bool included=false)162 void FilteredFolderModel::addFolder(const QString &url, const bool included = false)
163 {
164 QString nUrl = normalizeTrailingSlashes(QUrl(url).toLocalFile());
165
166 auto it = std::find_if(m_folderList.begin(), m_folderList.end(), [nUrl](const FolderInfo &folder) {
167 return folder.url == nUrl;
168 });
169 if (it != m_folderList.end() && (*it).isFromConfig) {
170 return;
171 }
172 if (included) {
173 auto included = addTrailingSlashes(m_settings->folders());
174 included.append(nUrl);
175 std::sort(std::begin(included), std::end(included));
176 m_settings->setFolders(included);
177 } else {
178 auto excluded = addTrailingSlashes(m_settings->excludedFolders());
179 excluded.append(nUrl);
180 std::sort(std::begin(excluded), std::end(excluded));
181 m_settings->setExcludedFolders(excluded);
182 }
183 m_deletedSettings.removeAll(nUrl);
184 }
185
removeFolder(int row)186 void FilteredFolderModel::removeFolder(int row)
187 {
188 auto entry = m_folderList.at(row);
189 if (!entry.isFromConfig) {
190 return;
191 }
192 if (!entry.enableIndex) {
193 auto excluded = addTrailingSlashes(m_settings->excludedFolders());
194 excluded.removeAll(entry.url);
195 std::sort(std::begin(excluded), std::end(excluded));
196 m_settings->setExcludedFolders(excluded);
197 } else {
198 auto included = addTrailingSlashes(m_settings->folders());
199 included.removeAll(entry.url);
200 std::sort(std::begin(included), std::end(included));
201 m_settings->setFolders(included);
202 }
203 m_deletedSettings.append(entry.url);
204 }
205
syncFolderConfig(const FolderInfo & entry)206 void FilteredFolderModel::syncFolderConfig(const FolderInfo &entry)
207 {
208 auto excluded = addTrailingSlashes(m_settings->excludedFolders());
209 auto included = addTrailingSlashes(m_settings->folders());
210 if (entry.enableIndex) {
211 included.append(entry.url);
212 std::sort(std::begin(included), std::end(included));
213 if (excluded.removeAll(entry.url)) {
214 std::sort(std::begin(excluded), std::end(excluded));
215 m_settings->setExcludedFolders(excluded);
216 }
217 m_settings->setFolders(included);
218 } else {
219 excluded.append(entry.url);
220 std::sort(std::begin(excluded), std::end(excluded));
221 if (included.removeAll(entry.url)) {
222 std::sort(std::begin(included), std::end(included));
223 m_settings->setFolders(included);
224 }
225 m_settings->setExcludedFolders(excluded);
226 }
227 }
228
roleNames() const229 QHash<int, QByteArray> FilteredFolderModel::roleNames() const
230 {
231 return {{Url, "url"}, //
232 {Folder, "folder"},
233 {EnableIndex, "enableIndex"},
234 {Deletable, "deletable"},
235 {Qt::DecorationRole, "decoration"}};
236 }
237