File indexing completed on 2024-05-12 04:38:06

0001 /*
0002     SPDX-FileCopyrightText: 2008 David Nolden <david.nolden.kdevelop@art-master.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "modificationrevision.h"
0008 
0009 #include <QString>
0010 #include <QFileInfo>
0011 #include <QMutex>
0012 #include <QMutexLocker>
0013 
0014 #include <serialization/indexedstring.h>
0015 #include "modificationrevisionset.h"
0016 
0017 /// @todo Listen to filesystem changes (together with the project manager)
0018 /// and call fileModificationCache().clear(...) when a file has changed
0019 
0020 const int KDevelop::cacheModificationTimesForSeconds = 30;
0021 
0022 using namespace KDevelop;
0023 namespace
0024 {
0025 struct FileModificationCache
0026 {
0027     QDateTime m_readTime;
0028     QDateTime m_modificationTime;
0029 };
0030 }
0031 Q_DECLARE_TYPEINFO(FileModificationCache, Q_MOVABLE_TYPE);
0032 
0033 namespace
0034 {
0035 using FileModificationMap = QHash<IndexedString, FileModificationCache>;
0036 using OpenDocumentRevisionsMap = QHash<IndexedString, int>;
0037 
0038 // data protected by the mutex in the StaticCacheData below
0039 struct CacheData {
0040     FileModificationMap fileModificationCache;
0041     OpenDocumentRevisionsMap openRevisionsCache;
0042 
0043     QDateTime fileModificationTimeCached(const IndexedString& fileName)
0044     {
0045         const auto currentTime = QDateTime::currentDateTimeUtc();
0046 
0047         auto it = fileModificationCache.constFind(fileName);
0048         if (it != fileModificationCache.constEnd()) {
0049             /// Use the cache for X seconds
0050             if (it.value().m_readTime.secsTo(currentTime) < cacheModificationTimesForSeconds) {
0051                 return it.value().m_modificationTime;
0052             }
0053         }
0054 
0055         QFileInfo fileInfo(fileName.str());
0056         FileModificationCache data = { currentTime, fileInfo.lastModified() };
0057         fileModificationCache.insert(fileName, data);
0058         return data.m_modificationTime;
0059     }
0060 
0061     ModificationRevision revisionForFile(const IndexedString& url)
0062     {
0063         ModificationRevision ret(fileModificationTimeCached(url));
0064 
0065         OpenDocumentRevisionsMap::const_iterator it = openRevisionsCache.constFind(url);
0066         if (it != openRevisionsCache.constEnd()) {
0067             ret.revision = it.value();
0068         }
0069 
0070         return ret;
0071     }
0072 };
0073 
0074 // protects CacheData with a mutex and only allows thread safe access
0075 class StaticCacheData
0076 {
0077 public:
0078     template <typename Op>
0079     auto op(Op&& op)
0080     {
0081         QMutexLocker lock(&m_mutex);
0082         return op(m_cacheData);
0083     }
0084 
0085 private:
0086     QMutex m_mutex;
0087     CacheData m_cacheData;
0088 };
0089 
0090 StaticCacheData& cacheData()
0091 {
0092     static StaticCacheData cacheData;
0093     return cacheData;
0094 }
0095 }
0096 
0097 void ModificationRevision::clearModificationCache(const IndexedString& fileName)
0098 {
0099     ///@todo Make the cache management more clever (don't clear the whole)
0100     ModificationRevisionSet::clearCache();
0101 
0102     cacheData().op([&fileName](CacheData& data) { data.fileModificationCache.remove(fileName); });
0103 }
0104 
0105 ModificationRevision ModificationRevision::revisionForFile(const IndexedString& url)
0106 {
0107     return cacheData().op([&url](CacheData& data) { return data.revisionForFile(url); });
0108 }
0109 
0110 void ModificationRevision::clearEditorRevisionForFile(const IndexedString& url)
0111 {
0112     ModificationRevisionSet::clearCache(); ///@todo Make the cache management more clever (don't clear the whole)
0113 
0114     return cacheData().op([&url](CacheData& data) { data.openRevisionsCache.remove(url); });
0115 }
0116 
0117 void ModificationRevision::setEditorRevisionForFile(const IndexedString& url, int revision)
0118 {
0119     ModificationRevisionSet::clearCache(); ///@todo Make the cache management more clever (don't clear the whole)
0120 
0121     return cacheData().op([&url, revision](CacheData& data) {
0122         data.openRevisionsCache.insert(url, revision);
0123         Q_ASSERT(data.revisionForFile(url).revision == revision);
0124     });
0125 }
0126 
0127 ModificationRevision::ModificationRevision(const QDateTime& modTime, int revision_)
0128     : modificationTime(modTime.toSecsSinceEpoch())
0129     , revision(revision_)
0130 {
0131 }
0132 
0133 bool ModificationRevision::operator <(const ModificationRevision& rhs) const
0134 {
0135     return modificationTime < rhs.modificationTime ||
0136            (modificationTime == rhs.modificationTime && revision < rhs.revision);
0137 }
0138 
0139 bool ModificationRevision::operator ==(const ModificationRevision& rhs) const
0140 {
0141     return modificationTime == rhs.modificationTime && revision == rhs.revision;
0142 }
0143 
0144 bool ModificationRevision::operator !=(const ModificationRevision& rhs) const
0145 {
0146     return modificationTime != rhs.modificationTime || revision != rhs.revision;
0147 }
0148 
0149 QString ModificationRevision::toString() const
0150 {
0151     return QStringLiteral("%1 (rev %2)").arg(QDateTime::fromSecsSinceEpoch(modificationTime, Qt::LocalTime).time().toString()).arg(revision);
0152 }