File indexing completed on 2024-04-21 04:38:10

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"