File indexing completed on 2024-04-28 15:39:58
0001 // SPDX-FileCopyrightText: 2003 - 2022 Jesper K. Pedersen <jesper.pedersen@kdab.com> 0002 // SPDX-FileCopyrightText: 2003 David Faure <faure@kde.org> 0003 // SPDX-FileCopyrightText: 2005 - 2007 Dirk Mueller <mueller@kde.org> 0004 // SPDX-FileCopyrightText: 2006 - 2007 Tuomas Suutari <tuomas@nepnep.net> 0005 // SPDX-FileCopyrightText: 2007 - 2008 Laurent Montel <montel@kde.org> 0006 // SPDX-FileCopyrightText: 2007 - 2010 Jan Kundrát <jkt@flaska.net> 0007 // SPDX-FileCopyrightText: 2008 - 2009 Henner Zeller <h.zeller@acm.org> 0008 // SPDX-FileCopyrightText: 2013 - 2024 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0009 // SPDX-FileCopyrightText: 2018 - 2022 Tobias Leupold <tl@stonemx.de> 0010 // SPDX-FileCopyrightText: 2018 Robert Krawitz <rlk@alum.mit.edu> 0011 // SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de> 0012 // 0013 // SPDX-License-Identifier: GPL-2.0-or-later 0014 0015 #include "MemberMap.h" 0016 0017 #include "Category.h" 0018 0019 #include <kpabase/Logging.h> 0020 0021 using namespace DB; 0022 0023 MemberMap::MemberMap() 0024 : QObject(nullptr) 0025 , m_dirty(true) 0026 , m_loading(false) 0027 { 0028 } 0029 0030 MemberMap::MemberMap(const MemberMap &other) 0031 : QObject(nullptr) 0032 , m_members(other.memberMap()) 0033 , m_dirty(true) 0034 , m_loading(false) 0035 { 0036 } 0037 0038 MemberMap &MemberMap::operator=(const MemberMap &other) 0039 { 0040 if (this != &other) { 0041 m_members = other.memberMap(); 0042 m_dirty = true; 0043 } 0044 return *this; 0045 } 0046 0047 QStringList MemberMap::groups(const QString &category) const 0048 { 0049 return QStringList(m_members[category].keys()); 0050 } 0051 0052 bool MemberMap::contains(const QString &category, const QString &item) const 0053 { 0054 return m_flatMembers.contains(category) && m_flatMembers[category].contains(item); 0055 } 0056 0057 void MemberMap::markDirty(const QString &category) 0058 { 0059 if (m_loading) 0060 regenerateFlatList(category); 0061 else 0062 Q_EMIT dirty(); 0063 } 0064 0065 void MemberMap::deleteGroup(const QString &category, const QString &groupName) 0066 { 0067 if (!m_members.contains(category)) 0068 return; 0069 0070 if (m_members[category].remove(groupName) > 0) { 0071 m_dirty = true; 0072 markDirty(category); 0073 } 0074 } 0075 0076 QStringList MemberMap::members(const QString &category, const QString &memberGroup, bool closure) const 0077 { 0078 if (!m_members.contains(category)) { 0079 return {}; 0080 } 0081 if (closure) { 0082 if (m_dirty) { 0083 calculate(); 0084 } 0085 const auto &members = m_closureMembers[category][memberGroup]; 0086 return QStringList(members.begin(), members.end()); 0087 } else { 0088 const auto &members = m_members[category][memberGroup]; 0089 return QStringList(members.begin(), members.end()); 0090 } 0091 } 0092 0093 void MemberMap::setMembers(const QString &category, const QString &memberGroup, const QStringList &members) 0094 { 0095 Q_ASSERT(!category.isEmpty()); 0096 Q_ASSERT(!memberGroup.isEmpty()); 0097 StringSet allowedMembers(members.begin(), members.end()); 0098 0099 for (QStringList::const_iterator i = members.begin(); i != members.end(); ++i) 0100 if (!canAddMemberToGroup(category, memberGroup, *i)) 0101 allowedMembers.remove(*i); 0102 0103 m_members[category][memberGroup] = allowedMembers; 0104 m_dirty = true; 0105 markDirty(category); 0106 } 0107 0108 bool MemberMap::isEmpty() const 0109 { 0110 return m_members.empty(); 0111 } 0112 0113 bool MemberMap::isGroup(const QString &category, const QString &item) const 0114 { 0115 return m_members.contains(category) && m_members[category].contains(item); 0116 } 0117 0118 QMap<QString, StringSet> MemberMap::groupMap(const QString &category) const 0119 { 0120 if (!m_members.contains(category)) 0121 return {}; 0122 0123 if (m_dirty) 0124 calculate(); 0125 0126 return m_closureMembers[category]; 0127 } 0128 0129 QStringList MemberMap::calculateClosure(QMap<QString, StringSet> &resultSoFar, const QString &category, const QString &group) const 0130 { 0131 resultSoFar[group] = StringSet(); // Prevent against cycles. 0132 const StringSet members = m_members[category][group]; 0133 StringSet result = members; 0134 for (const auto &member : members) { 0135 if (resultSoFar.contains(member)) { 0136 result += resultSoFar[member]; 0137 } else if (isGroup(category, member)) { 0138 const auto closure = calculateClosure(resultSoFar, category, member); 0139 const StringSet closureSet(closure.begin(), closure.end()); 0140 result += closureSet; 0141 } 0142 } 0143 0144 resultSoFar[group] = result; 0145 return QStringList(result.begin(), result.end()); 0146 } 0147 0148 void MemberMap::calculate() const 0149 { 0150 m_closureMembers.clear(); 0151 // run through all categories 0152 for (QMap<QString, QMap<QString, StringSet>>::ConstIterator categoryIt = m_members.begin(); 0153 categoryIt != m_members.end(); ++categoryIt) { 0154 0155 QString category = categoryIt.key(); 0156 QMap<QString, StringSet> groupMap = categoryIt.value(); 0157 0158 // Run through each of the groups for the given categories 0159 for (QMap<QString, StringSet>::const_iterator groupIt = groupMap.constBegin(); groupIt != groupMap.constEnd(); ++groupIt) { 0160 QString group = groupIt.key(); 0161 if (m_closureMembers[category].find(group) == m_closureMembers[category].end()) { 0162 (void)calculateClosure(m_closureMembers[category], category, group); 0163 } 0164 } 0165 } 0166 m_dirty = false; 0167 } 0168 0169 void MemberMap::renameGroup(const QString &category, const QString &oldName, const QString &newName) 0170 { 0171 const auto sanitizedNewName = newName.trimmed(); 0172 if (!m_members.contains(category)) 0173 return; 0174 if (!m_members[category].contains(oldName)) 0175 return; 0176 // Don't allow overwriting to avoid creating cycles 0177 if (m_members[category].contains(sanitizedNewName)) 0178 return; 0179 0180 m_dirty = true; 0181 markDirty(category); 0182 QMap<QString, StringSet> &groupMap = m_members[category]; 0183 groupMap.insert(sanitizedNewName, m_members[category][oldName]); 0184 groupMap.remove(oldName); 0185 for (StringSet &set : groupMap) { 0186 if (set.contains(oldName)) { 0187 set.remove(oldName); 0188 set.insert(sanitizedNewName); 0189 } 0190 } 0191 } 0192 0193 void MemberMap::deleteItem(DB::Category *category, const QString &name) 0194 { 0195 Q_ASSERT(category != nullptr); 0196 const auto categoryName = category->name(); 0197 if (!m_members.contains(categoryName)) 0198 return; 0199 0200 int removed = 0; 0201 QMap<QString, StringSet> &groupMap = m_members[categoryName]; 0202 for (StringSet &items : groupMap) { 0203 removed += items.remove(name); 0204 } 0205 removed += m_members[categoryName].remove(name); 0206 0207 if (removed > 0) { 0208 m_dirty = true; 0209 markDirty(categoryName); 0210 } 0211 } 0212 0213 void MemberMap::renameItem(DB::Category *category, const QString &oldName, const QString &newName) 0214 { 0215 Q_ASSERT(category != nullptr); 0216 const auto categoryName = category->name(); 0217 const auto sanitizedNewName = newName.trimmed(); 0218 if (!m_members.contains(categoryName)) 0219 return; 0220 if (oldName == sanitizedNewName) 0221 return; 0222 0223 bool changed = false; 0224 QMap<QString, StringSet> &groupMap = m_members[categoryName]; 0225 for (StringSet &items : groupMap) { 0226 if (items.contains(oldName)) { 0227 changed = true; 0228 items.remove(oldName); 0229 items.insert(sanitizedNewName); 0230 } 0231 } 0232 if (groupMap.contains(oldName)) { 0233 changed = true; 0234 groupMap[sanitizedNewName] = groupMap[oldName]; 0235 groupMap.remove(oldName); 0236 } 0237 0238 if (changed) { 0239 m_dirty = true; 0240 markDirty(categoryName); 0241 } 0242 } 0243 0244 void MemberMap::regenerateFlatList(const QString &category) 0245 { 0246 if (!m_members.contains(category)) 0247 return; 0248 0249 m_flatMembers[category].clear(); 0250 for (const auto &group : qAsConst(m_members[category])) { 0251 for (const auto &tag : group) { 0252 m_flatMembers[category].insert(tag); 0253 } 0254 } 0255 } 0256 0257 void MemberMap::addMemberToGroup(const QString &category, const QString &group, const QString &item) 0258 { 0259 // Only test for cycles after database is already loaded 0260 if (!m_loading && !canAddMemberToGroup(category, group, item)) { 0261 qCWarning(DBLog, "Inserting item %s into group %s/%s would create a cycle. Ignoring...", qPrintable(item), qPrintable(category), qPrintable(group)); 0262 return; 0263 } 0264 0265 if (item.isEmpty()) { 0266 qCWarning(DBLog, "Tried to insert null item into group %s/%s. Ignoring...", qPrintable(category), qPrintable(group)); 0267 return; 0268 } 0269 0270 m_members[category][group].insert(item); 0271 m_flatMembers[category].insert(item); 0272 0273 if (m_loading) { 0274 m_dirty = true; 0275 } else if (!m_dirty) { 0276 // Update _closureMembers to avoid marking it dirty 0277 0278 QMap<QString, StringSet> &categoryClosure = m_closureMembers[category]; 0279 0280 categoryClosure[group].insert(item); 0281 0282 QMap<QString, StringSet>::const_iterator 0283 closureOfItem 0284 = categoryClosure.constFind(item); 0285 const StringSet *closureOfItemPtr(nullptr); 0286 if (closureOfItem != categoryClosure.constEnd()) { 0287 closureOfItemPtr = &(*closureOfItem); 0288 categoryClosure[group] += *closureOfItem; 0289 } 0290 0291 for (QMap<QString, StringSet>::iterator i = categoryClosure.begin(); 0292 i != categoryClosure.end(); ++i) 0293 if ((*i).contains(group)) { 0294 (*i).insert(item); 0295 if (closureOfItemPtr) 0296 (*i) += *closureOfItemPtr; 0297 } 0298 } 0299 0300 // If we are loading, we do *not* want to regenerate the list! 0301 if (!m_loading) 0302 Q_EMIT dirty(); 0303 } 0304 0305 void MemberMap::removeMemberFromGroup(const QString &category, const QString &group, const QString &item) 0306 { 0307 if (!m_members.contains(category)) 0308 return; 0309 if (!m_members[category].contains(group)) 0310 return; 0311 0312 if (m_members[category][group].remove(item) > 0) { 0313 // We shouldn't be doing this very often, so just regenerate 0314 // the flat list 0315 regenerateFlatList(category); 0316 Q_EMIT dirty(); 0317 } 0318 } 0319 0320 void MemberMap::addGroup(const QString &category, const QString &group) 0321 { 0322 const auto sanitizedGroup = group.trimmed(); 0323 if (sanitizedGroup.isEmpty()) 0324 return; 0325 0326 if (!m_members[category].contains(sanitizedGroup)) { 0327 m_members[category].insert(sanitizedGroup, StringSet()); 0328 markDirty(category); 0329 } 0330 } 0331 0332 void MemberMap::renameCategory(const QString &oldName, const QString &newName) 0333 { 0334 const auto sanitizedNewName = newName.trimmed(); 0335 if (!m_members.contains(oldName)) 0336 return; 0337 if (oldName == sanitizedNewName) 0338 return; 0339 if (m_members.contains(sanitizedNewName)) 0340 return; 0341 0342 m_members[sanitizedNewName] = m_members[oldName]; 0343 m_members.remove(oldName); 0344 m_closureMembers[sanitizedNewName] = m_closureMembers[oldName]; 0345 m_closureMembers.remove(oldName); 0346 if (!m_loading) 0347 Q_EMIT dirty(); 0348 } 0349 0350 void MemberMap::deleteCategory(const QString &category) 0351 { 0352 if (!m_members.contains(category)) 0353 return; 0354 0355 m_members.remove(category); 0356 m_closureMembers.remove(category); 0357 markDirty(category); 0358 } 0359 0360 QMap<QString, StringSet> DB::MemberMap::inverseMap(const QString &category) const 0361 { 0362 QMap<QString, StringSet> res; 0363 const QMap<QString, StringSet> &map = m_members[category]; 0364 0365 for (QMap<QString, StringSet>::ConstIterator mapIt = map.begin(); mapIt != map.end(); ++mapIt) { 0366 QString group = mapIt.key(); 0367 const StringSet members = mapIt.value(); 0368 for (const auto &member : members) { 0369 res[member].insert(group); 0370 } 0371 } 0372 return res; 0373 } 0374 0375 bool DB::MemberMap::hasPath(const QString &category, const QString &from, const QString &to) const 0376 { 0377 if (from == to) 0378 return true; 0379 else if (!m_members[category].contains(from)) 0380 // Try to avoid calculate(), which is quite time consuming. 0381 return false; 0382 else { 0383 // return members(category, from, true).contains(to); 0384 if (m_dirty) 0385 calculate(); 0386 return m_closureMembers[category][from].contains(to); 0387 } 0388 } 0389 0390 void DB::MemberMap::setLoading(bool isLoading) 0391 { 0392 if (m_loading && !isLoading) { 0393 // TODO: Remove possible loaded cycles. 0394 } 0395 m_loading = isLoading; 0396 } 0397 0398 #include "moc_MemberMap.cpp" 0399 0400 // vi:expandtab:tabstop=4 shiftwidth=4: