File indexing completed on 2024-05-05 16:45:01
0001 /* 0002 SPDX-FileCopyrightText: 2010-2012, 2020 Friedrich W. H. Kossebau <kossebau@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "checksetselectionmanager.h" 0008 0009 // plugin 0010 #include "checksetselectionlock.h" 0011 #include "debug.h" 0012 // KF 0013 #include <KConfigGroup> 0014 #include <KConfig> 0015 #include <KDirWatch> 0016 // Qt 0017 #include <QFileInfo> 0018 #include <QDir> 0019 #include <QUuid> 0020 0021 namespace Clazy { 0022 0023 QStringList checkSetSelectionFileNameFilter() { return QStringList { QStringLiteral("*.kdevczcs"), QStringLiteral("*.kdevlock") }; } 0024 inline QLatin1String checkSetSelectionFileSuffix() { return QLatin1String(".kdevczcs"); } 0025 inline QLatin1String checkSetSelectionDirSubPath() { return QLatin1String("/kdevclazy/checksetselections"); } 0026 inline QLatin1String defaultCheckSetSelectionFileSubPath() { return QLatin1String("/kdevclazy/defaultchecksetselection"); } 0027 0028 0029 QVector<QString> lockedCheckSetSelectionIds(const CheckSetSelectionFileInfoLookup& checkSetSelectionFileInfoLookup) 0030 { 0031 QVector<QString> result; 0032 0033 for (auto it = checkSetSelectionFileInfoLookup.constBegin(), end = checkSetSelectionFileInfoLookup.constEnd(); 0034 it != end; ++it) { 0035 if (it.value().isLocked()) { 0036 result.append(it.key()); 0037 } 0038 } 0039 0040 return result; 0041 } 0042 0043 void updateLockStatus(CheckSetSelectionFileInfoLookup& checkSetSelectionFileInfoLookup, 0044 const QVector<QString>& lockedCheckSetSelectionIds, 0045 const QVector<QString>& unlockedCheckSetSelectionIds) 0046 { 0047 if (lockedCheckSetSelectionIds.isEmpty() && unlockedCheckSetSelectionIds.isEmpty()) { 0048 return; 0049 } 0050 0051 for (auto it = checkSetSelectionFileInfoLookup.begin(), end = checkSetSelectionFileInfoLookup.end(); 0052 it != end; ++it) { 0053 bool isLocked; 0054 0055 if (lockedCheckSetSelectionIds.contains(it.key())) { 0056 isLocked = true; 0057 } else if (unlockedCheckSetSelectionIds.contains(it.key())) { 0058 isLocked = false; 0059 } else { 0060 continue; 0061 } 0062 0063 it.value().setLocked(isLocked); 0064 } 0065 } 0066 0067 QString defaultCheckSetSelectionFilePath() 0068 { 0069 return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + defaultCheckSetSelectionFileSubPath(); 0070 } 0071 0072 QString checkSetSelectionFilePath(const QString& checkSetSelectionId) 0073 { 0074 return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) 0075 + checkSetSelectionDirSubPath() + QLatin1Char('/') + checkSetSelectionId + checkSetSelectionFileSuffix(); 0076 } 0077 0078 QString checkSetSelectionFileName(const QString& checkSetSelectionId) 0079 { 0080 return checkSetSelectionId + checkSetSelectionFileSuffix(); 0081 } 0082 0083 // TODO: add global lock 0084 // TODO: make calls async 0085 // TODO: only load checkset setlections on demand 0086 CheckSetSelectionManager::CheckSetSelectionManager() 0087 : m_checkSetSelectionFileWatcher(new KDirWatch(this)) 0088 { 0089 connect(m_checkSetSelectionFileWatcher, &KDirWatch::dirty, 0090 this, &CheckSetSelectionManager::onCheckSetSelectionsFolderChanged); 0091 0092 // get all folder where checkSetSelections could be stored 0093 const QStringList dataFolderPaths = 0094 QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); 0095 0096 for (const QString& dataFolderPath : dataFolderPaths) { 0097 const QString checkSetSelectionFolderPath = dataFolderPath + checkSetSelectionDirSubPath(); 0098 // watch folder for changes 0099 m_checkSetSelectionFileWatcher->addDir(checkSetSelectionFolderPath, KDirWatch::WatchDirOnly); 0100 0101 // read current files 0102 onCheckSetSelectionsFolderChanged(checkSetSelectionFolderPath); 0103 } 0104 0105 // default checkset selection 0106 // While there is no proper config syncing offer in the used frameworks, use a 0107 // single file with the id as content as workaround and watch for it changing 0108 auto* defaultCheckSetSelectionWatcher = new KDirWatch(this); 0109 connect(defaultCheckSetSelectionWatcher, &KDirWatch::created, 0110 this, &CheckSetSelectionManager::onDefaultCheckSetSelectionChanged); 0111 connect(defaultCheckSetSelectionWatcher, &KDirWatch::dirty, 0112 this, &CheckSetSelectionManager::onDefaultCheckSetSelectionChanged); 0113 const QString _defaultCheckSetSelectionFilePath = defaultCheckSetSelectionFilePath(); 0114 0115 defaultCheckSetSelectionWatcher->addFile(_defaultCheckSetSelectionFilePath); 0116 0117 onDefaultCheckSetSelectionChanged(_defaultCheckSetSelectionFilePath); 0118 0119 // report any problems with existing checkset selections? 0120 } 0121 0122 CheckSetSelectionManager::~CheckSetSelectionManager() = default; 0123 0124 int CheckSetSelectionManager::checkSetSelectionsCount() const 0125 { 0126 return m_checkSetSelections.count(); 0127 } 0128 0129 QVector<CheckSetSelection> CheckSetSelectionManager::checkSetSelections() const 0130 { 0131 return m_checkSetSelections; 0132 } 0133 0134 CheckSetSelection CheckSetSelectionManager::checkSetSelection(const QString& checkSetSelectionId) const 0135 { 0136 CheckSetSelection result; 0137 0138 for (const CheckSetSelection& checkSetSelection : m_checkSetSelections) { 0139 if (checkSetSelection.id() == checkSetSelectionId) { 0140 result = checkSetSelection; 0141 break; 0142 } 0143 } 0144 0145 return result; 0146 } 0147 0148 QString CheckSetSelectionManager::defaultCheckSetSelectionId() const 0149 { 0150 return m_defaultCheckSetSelectionId; 0151 } 0152 0153 CheckSetSelection CheckSetSelectionManager::defaultCheckSetSelection() const 0154 { 0155 return checkSetSelection(m_defaultCheckSetSelectionId); 0156 } 0157 0158 bool CheckSetSelectionManager::isCheckSetSelectionLocked(const QString& checkSetSelectionId) const 0159 { 0160 bool result = false; 0161 0162 // search in all folders for the info 0163 for (const CheckSetSelectionFileInfoLookup& checkSetSelectionFileInfoLookup : m_checkSetSelectionFileInfoLookupPerFolder) { 0164 CheckSetSelectionFileInfoLookup::ConstIterator it = 0165 checkSetSelectionFileInfoLookup.find(checkSetSelectionId); 0166 if (it != checkSetSelectionFileInfoLookup.constEnd()) { 0167 result = it->isLocked(); 0168 break; 0169 } 0170 } 0171 0172 return result; 0173 } 0174 0175 void CheckSetSelectionManager::saveCheckSetSelections(QVector<CheckSetSelection>& checkSetSelections) 0176 { 0177 // TODO: do not save if locked by someone else -> needs passing of our lock? or just registering our own and check? 0178 // create and set unique id 0179 std::for_each(checkSetSelections.begin(), checkSetSelections.end(), [this](CheckSetSelection& checkSetSelection) { 0180 const QString checkSetSelectionId = checkSetSelection.id(); 0181 0182 bool needsId = true; 0183 if (!checkSetSelectionId.isEmpty()) { 0184 // already existing? 0185 auto hasCheckSetSelectionId = [&checkSetSelectionId] (const CheckSetSelection& existingProfile) { 0186 return (checkSetSelectionId == existingProfile.id()); 0187 }; 0188 if (std::any_of(m_checkSetSelections.constBegin(), m_checkSetSelections.constEnd(), hasCheckSetSelectionId)) { 0189 needsId = false; 0190 } 0191 } 0192 0193 // set new uuid for non-existing 0194 if (needsId) { 0195 checkSetSelection.setId(QUuid::createUuid().toString()); 0196 } 0197 0198 saveCheckSetSelection(checkSetSelection); 0199 }); 0200 } 0201 0202 void CheckSetSelectionManager::removeCheckSetSelections(const QVector<QString>& checkSetSelectionIds) 0203 { 0204 for (const QString& checkSetSelectionId : checkSetSelectionIds) { 0205 removeCheckSetSelection(checkSetSelectionId); 0206 } 0207 } 0208 0209 void CheckSetSelectionManager::setDefaultCheckSetSelection(const QString& checkSetSelectionId) 0210 { 0211 QFile defaultCheckSetSelectionFile(defaultCheckSetSelectionFilePath()); 0212 defaultCheckSetSelectionFile.open(QIODevice::WriteOnly); 0213 0214 defaultCheckSetSelectionFile.write(checkSetSelectionId.toUtf8()); 0215 defaultCheckSetSelectionFile.close(); 0216 } 0217 0218 CheckSetSelectionLock CheckSetSelectionManager::createLock(const QString& checkSetSelectionId) 0219 { 0220 const QString checkSetSelectionFilePath = filePathOfCheckSetSelection(checkSetSelectionId); 0221 0222 return CheckSetSelectionLock(checkSetSelectionFilePath, checkSetSelectionId); 0223 } 0224 0225 CheckSetSelection CheckSetSelectionManager::loadCheckSetSelection(const QString& absoluteFilePath) const 0226 { 0227 CheckSetSelection result; 0228 0229 KConfig configFile(absoluteFilePath, KConfig::SimpleConfig); 0230 0231 // check version 0232 KConfigGroup formatConfigGroup = configFile.group("KDEVCZCS"); 0233 const QString formatVersion = formatConfigGroup.readEntry("Version"); 0234 if (!formatVersion.startsWith(QLatin1String("1."))) { 0235 return result; 0236 } 0237 0238 result.setId(QFileInfo(absoluteFilePath).baseName()); 0239 0240 KConfigGroup generalConfigGroup = configFile.group("General"); 0241 result.setName(generalConfigGroup.readEntry("Name")); 0242 0243 KConfigGroup layoutConfigGroup = configFile.group("Checks"); 0244 result.setSelection(layoutConfigGroup.readEntry("Selection", QString())); 0245 0246 return result; 0247 } 0248 0249 void CheckSetSelectionManager::saveCheckSetSelection(const CheckSetSelection& checkSetSelection) const 0250 { 0251 const QString fileName = checkSetSelectionFilePath(checkSetSelection.id()); 0252 KConfig configFile(fileName, KConfig::SimpleConfig); 0253 0254 KConfigGroup formatConfigGroup = configFile.group("KDEVCZCS"); 0255 formatConfigGroup.writeEntry("Version", "1.0"); 0256 0257 KConfigGroup generalConfigGroup = configFile.group("General"); 0258 generalConfigGroup.writeEntry("Name", checkSetSelection.name()); 0259 0260 KConfigGroup layoutConfigGroup = configFile.group("Checks"); 0261 layoutConfigGroup.writeEntry("Selection", checkSetSelection.selectionAsString()); 0262 } 0263 0264 void CheckSetSelectionManager::removeCheckSetSelection(const QString& checkSetSelectionId) 0265 { 0266 const QString filePath = filePathOfCheckSetSelection(checkSetSelectionId); 0267 if (!filePath.isEmpty()) { 0268 QFile::remove(filePath); 0269 } 0270 } 0271 0272 QString CheckSetSelectionManager::filePathOfCheckSetSelection(const QString& checkSetSelectionId) const 0273 { 0274 QString result; 0275 0276 for (QHash<QString, CheckSetSelectionFileInfoLookup>::ConstIterator foldersIt = 0277 m_checkSetSelectionFileInfoLookupPerFolder.constBegin(); 0278 foldersIt != m_checkSetSelectionFileInfoLookupPerFolder.constEnd() && result.isEmpty(); 0279 ++foldersIt) { 0280 const CheckSetSelectionFileInfoLookup& fileInfoList = foldersIt.value(); 0281 for (CheckSetSelectionFileInfoLookup::ConstIterator folderIt = fileInfoList.constBegin(); 0282 folderIt != fileInfoList.constEnd(); 0283 ++folderIt) { 0284 if (folderIt.key() == checkSetSelectionId) { 0285 result = foldersIt.key() + QLatin1Char('/') + checkSetSelectionFileName(checkSetSelectionId); 0286 break; 0287 } 0288 } 0289 } 0290 0291 return result; 0292 } 0293 0294 void CheckSetSelectionManager::onCheckSetSelectionsFolderChanged(const QString& checkSetSelectionFolderPath) 0295 { 0296 CheckSetSelectionFileInfoLookup& checkSetSelectionFileInfoLookup = 0297 m_checkSetSelectionFileInfoLookupPerFolder[checkSetSelectionFolderPath]; 0298 0299 // TODO: reparse for new, removed and changed files 0300 // assume all are removed and unlocked in the beginning 0301 QVector<QString> removedCheckSetSelectionIds = checkSetSelectionFileInfoLookup.keys().toVector(); 0302 QVector<CheckSetSelection> newCheckSetSelections; 0303 QVector<CheckSetSelection> changedCheckSetSelections; 0304 0305 QVector<QString> newUnlockedCheckSetSelectionIds = lockedCheckSetSelectionIds(checkSetSelectionFileInfoLookup); 0306 QVector<QString> newLockedCheckSetSelectionIds; 0307 // iterate all files in folder 0308 const QFileInfoList checkSetSelectionFileInfoList = 0309 QDir(checkSetSelectionFolderPath).entryInfoList(checkSetSelectionFileNameFilter(), QDir::Files); 0310 0311 for (const QFileInfo& checkSetSelectionFileInfo : checkSetSelectionFileInfoList) { 0312 // a lock file ? 0313 if (checkSetSelectionFileInfo.suffix() == QLatin1String("kdevlock")) { 0314 const QString lockedCheckSetSelectionId = checkSetSelectionFileInfo.baseName(); 0315 // if not in old locks, is a new lock 0316 if (!newUnlockedCheckSetSelectionIds.removeOne(lockedCheckSetSelectionId)) { 0317 newLockedCheckSetSelectionIds.append(lockedCheckSetSelectionId); 0318 } 0319 continue; 0320 } 0321 0322 // not a checkset file ? 0323 if (checkSetSelectionFileInfo.suffix() != QLatin1String("kdevczcs")) { 0324 continue; 0325 } 0326 0327 // all other files assumed to be checkSetSelection files 0328 const QString checkSetSelectionId = checkSetSelectionFileInfo.baseName(); 0329 // load file 0330 const CheckSetSelection checkSetSelection = loadCheckSetSelection(checkSetSelectionFileInfo.absoluteFilePath()); 0331 // loading failed? Treat as not existing 0332 if (checkSetSelection.id().isEmpty()) { 0333 continue; 0334 } 0335 0336 const CheckSetSelectionFileInfoLookup::Iterator infoIt = 0337 checkSetSelectionFileInfoLookup.find(checkSetSelectionId); 0338 const bool isKnown = (infoIt != checkSetSelectionFileInfoLookup.end()); 0339 const QDateTime fileInfoLastModified = checkSetSelectionFileInfo.lastModified(); 0340 // is known? 0341 if (isKnown) { 0342 removedCheckSetSelectionIds.removeOne(checkSetSelectionId); 0343 0344 // check timestamp 0345 if (fileInfoLastModified == infoIt->lastModified()) { 0346 continue; 0347 } 0348 0349 // update timestamp 0350 infoIt->setLastModified(fileInfoLastModified); 0351 } else { 0352 CheckSetSelectionFileInfo info(fileInfoLastModified, false); 0353 checkSetSelectionFileInfoLookup.insert(checkSetSelectionId, info); 0354 } 0355 0356 if (isKnown) { 0357 auto it = std::find_if(m_checkSetSelections.begin(), m_checkSetSelections.end(), 0358 [&checkSetSelectionId](const CheckSetSelection& existingProfile) { 0359 return (existingProfile.id() == checkSetSelectionId); 0360 }); 0361 if (it != m_checkSetSelections.end()) { 0362 *it = checkSetSelection; 0363 } 0364 } else { 0365 newCheckSetSelections.append(checkSetSelection); 0366 } 0367 changedCheckSetSelections.append(checkSetSelection); 0368 } 0369 0370 // remove all removed checksets 0371 { 0372 auto isProfileToRemove = [&removedCheckSetSelectionIds](const CheckSetSelection& selection) { 0373 return removedCheckSetSelectionIds.contains(selection.id()); 0374 }; 0375 m_checkSetSelections.erase(std::remove_if(m_checkSetSelections.begin(), m_checkSetSelections.end(), isProfileToRemove), 0376 m_checkSetSelections.end()); 0377 } 0378 0379 for (const QString& checkSetSelectionId : qAsConst(removedCheckSetSelectionIds)) { 0380 checkSetSelectionFileInfoLookup.remove(checkSetSelectionId); 0381 if (checkSetSelectionId == m_defaultCheckSetSelectionId) { 0382 m_defaultCheckSetSelectionId.clear(); 0383 } 0384 // TODO: how to select new one? 0385 } 0386 0387 // add new checksets 0388 m_checkSetSelections.append(newCheckSetSelections); 0389 // if there was no default checkset before, set default to first 0390 const bool isDefaultCheckSetSelectionChanged = (m_defaultCheckSetSelectionId.isEmpty() && !m_checkSetSelections.isEmpty()); 0391 if (isDefaultCheckSetSelectionChanged) { 0392 m_defaultCheckSetSelectionId = m_checkSetSelections.at(0).id(); 0393 } 0394 0395 // update lock info 0396 updateLockStatus(checkSetSelectionFileInfoLookup, newLockedCheckSetSelectionIds, newUnlockedCheckSetSelectionIds); 0397 0398 // signal changes 0399 if (!changedCheckSetSelections.isEmpty()) { 0400 emit checkSetSelectionsChanged(changedCheckSetSelections); 0401 } 0402 if (!removedCheckSetSelectionIds.isEmpty()) { 0403 emit checkSetSelectionsRemoved(removedCheckSetSelectionIds); 0404 } 0405 0406 if (!newUnlockedCheckSetSelectionIds.isEmpty()) { 0407 emit checkSetSelectionsUnlocked(newUnlockedCheckSetSelectionIds); 0408 } 0409 if (!newLockedCheckSetSelectionIds.isEmpty()) { 0410 emit checkSetSelectionsLocked(newLockedCheckSetSelectionIds); 0411 } 0412 if (isDefaultCheckSetSelectionChanged) { 0413 emit defaultCheckSetSelectionChanged(m_defaultCheckSetSelectionId); 0414 } 0415 } 0416 0417 void CheckSetSelectionManager::onDefaultCheckSetSelectionChanged(const QString& path) 0418 { 0419 QFile defaultCheckSetSelectionFile(path); 0420 if (!defaultCheckSetSelectionFile.open(QIODevice::ReadOnly)) { 0421 qCDebug(KDEV_CLAZY) << "Failed to open checkset selection file " << path; 0422 return; 0423 } 0424 0425 const QByteArray fileContent = defaultCheckSetSelectionFile.readAll(); 0426 const QString checkSetSelectionId = QString::fromUtf8(fileContent); 0427 defaultCheckSetSelectionFile.close(); 0428 0429 // no id set? 0430 if (checkSetSelectionId.isEmpty()) { 0431 return; 0432 } 0433 0434 // no change? 0435 if (m_defaultCheckSetSelectionId == checkSetSelectionId) { 0436 return; 0437 } 0438 0439 bool isExisting = false; 0440 for (const CheckSetSelection& checkSetSelection : qAsConst(m_checkSetSelections)) { 0441 if (checkSetSelection.id() == checkSetSelectionId) { 0442 isExisting = true; 0443 break; 0444 } 0445 } 0446 0447 if (isExisting) { 0448 m_defaultCheckSetSelectionId = checkSetSelectionId; 0449 emit defaultCheckSetSelectionChanged(m_defaultCheckSetSelectionId); 0450 } 0451 } 0452 0453 } 0454 0455 #include "moc_checksetselectionmanager.cpp"