File indexing completed on 2024-12-15 04:54:42
0001 /****************************************************************************** 0002 * 0003 * SPDX-FileCopyrightText: 2016 Daniel Vrátil <dvratil@kde.org> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 * 0007 *******************************************************************************/ 0008 0009 #include "threadingcache.h" 0010 #include "aggregation.h" 0011 #include "messagelist_debug.h" 0012 0013 #include <QDebug> 0014 #include <QDir> 0015 #include <QFile> 0016 #include <QStandardPaths> 0017 0018 using namespace MessageList::Core; 0019 0020 namespace 0021 { 0022 struct CacheHeader { 0023 int version; 0024 Aggregation::Grouping grouping; 0025 Aggregation::Threading threading; 0026 Aggregation::ThreadLeader threadLeader; 0027 int cacheSize; 0028 }; 0029 0030 QDebug operator<<(QDebug d, const CacheHeader &t) 0031 { 0032 d << " grouping " << t.grouping; 0033 d << " threading " << t.threading; 0034 d << " threadLeader " << t.threadLeader; 0035 d << " grouping " << t.grouping; 0036 d << " cacheSize " << t.cacheSize; 0037 return d; 0038 } 0039 0040 QDataStream &operator<<(QDataStream &stream, const CacheHeader &header) 0041 { 0042 return stream << header.version << (qint8)header.grouping << (qint8)header.threading << (qint8)header.threadLeader << header.cacheSize; 0043 } 0044 0045 QDataStream &operator>>(QDataStream &stream, CacheHeader &header) 0046 { 0047 return stream >> header.version >> (qint8 &)header.grouping >> (qint8 &)header.threading >> (qint8 &)header.threadLeader >> header.cacheSize; 0048 } 0049 } 0050 0051 ThreadingCache::ThreadingCache() = default; 0052 0053 ThreadingCache::~ThreadingCache() 0054 { 0055 if (mEnabled && !mCacheId.isEmpty()) { 0056 save(); 0057 } 0058 } 0059 0060 bool ThreadingCache::isEnabled() const 0061 { 0062 return mEnabled; 0063 } 0064 0065 void ThreadingCache::setEnabled(bool enabled) 0066 { 0067 mEnabled = enabled; 0068 } 0069 0070 void ThreadingCache::load(const QString &id, const Aggregation *aggregation) 0071 { 0072 mParentCache.clear(); 0073 mItemCache.clear(); 0074 0075 mCacheId = id; 0076 mThreading = aggregation->threading(); 0077 mThreadLeader = aggregation->threadLeader(); 0078 mGrouping = aggregation->grouping(); 0079 mEnabled = true; 0080 0081 if (id.isEmpty()) { 0082 qCDebug(MESSAGELIST_LOG) << "Invalid collection: id is empty"; 0083 return; 0084 } 0085 0086 const QString cacheFileName = 0087 QStandardPaths::locate(QStandardPaths::CacheLocation, QStringLiteral("messagelist/threading/%1").arg(id), QStandardPaths::LocateFile); 0088 if (cacheFileName.isEmpty()) { 0089 qCDebug(MESSAGELIST_LOG) << "No threading cache file for collection" << id; 0090 return; 0091 } 0092 qCDebug(MESSAGELIST_LOG) << "Loading threading cache file" << cacheFileName; 0093 0094 QFile cacheFile(cacheFileName); 0095 if (!cacheFile.open(QIODevice::ReadOnly)) { 0096 qCWarning(MESSAGELIST_LOG) << "Failed to open cache file" << cacheFileName << ":" << cacheFile.errorString(); 0097 return; 0098 } 0099 0100 QDataStream stream(&cacheFile); 0101 stream.setVersion(QDataStream::Qt_5_15); 0102 CacheHeader cacheHeader = {}; 0103 stream >> cacheHeader; 0104 0105 if (cacheHeader.version != 1) { 0106 // Unknown version 0107 qCDebug(MESSAGELIST_LOG) << "\tCache file unusable, unknown version"; 0108 cacheFile.close(); 0109 cacheFile.remove(); 0110 return; 0111 } 0112 0113 if (cacheHeader.grouping != mGrouping || cacheHeader.threading != mThreading || cacheHeader.threadLeader != mThreadLeader) { 0114 // The cache is valid, but for a different grouping/threading configuration. 0115 qCDebug(MESSAGELIST_LOG) << "\tCache file unusable, threading configuration mismatch"; 0116 cacheFile.close(); 0117 cacheFile.remove(); 0118 return; 0119 } 0120 const qsizetype values = cacheHeader.cacheSize; 0121 mItemCache.reserve(values); 0122 mParentCache.reserve(values); 0123 0124 for (int i = 0; i < values; ++i) { 0125 qint64 child; 0126 qint64 parent; 0127 stream >> child >> parent; 0128 if (stream.status() != QDataStream::Ok) { 0129 // Suspect corrupted cache 0130 qCDebug(MESSAGELIST_LOG) << "\tCache file unusable, data truncated"; 0131 cacheFile.close(); 0132 cacheFile.remove(); 0133 mParentCache.clear(); 0134 return; 0135 } 0136 0137 mParentCache.insert(child, parent); 0138 } 0139 0140 qCDebug(MESSAGELIST_LOG) << "Loaded" << values << "entries from threading cache"; 0141 } 0142 0143 void ThreadingCache::save() 0144 { 0145 if (mCacheId.isEmpty()) { 0146 return; 0147 } 0148 const QDir cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); 0149 if (!cacheDir.exists(QStringLiteral("messagelist/threading"))) { 0150 if (!cacheDir.mkpath(QStringLiteral("messagelist/threading"))) { 0151 qCWarning(MESSAGELIST_LOG) << "Failed to create cache directory."; 0152 return; 0153 } 0154 } 0155 0156 QFile cacheFile(cacheDir.filePath(QStringLiteral("messagelist/threading/%1").arg(mCacheId))); 0157 if (!cacheFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { 0158 qCWarning(MESSAGELIST_LOG) << "Failed to create cache file:" << cacheFile.errorString() << " mCacheId " << mCacheId; 0159 return; 0160 } 0161 0162 qCDebug(MESSAGELIST_LOG) << "Saving threading cache to" << cacheFile.fileName(); 0163 0164 QDataStream stream(&cacheFile); 0165 stream.setVersion(QDataStream::Qt_5_15); 0166 CacheHeader cacheHeader{1, // version 0167 mGrouping, 0168 mThreading, 0169 mThreadLeader, 0170 static_cast<int>(mParentCache.size())}; 0171 stream << cacheHeader; 0172 cacheFile.flush(); 0173 0174 for (auto iter = mParentCache.cbegin(), end = mParentCache.cend(); iter != end; ++iter) { 0175 stream << iter.key() << iter.value(); 0176 } 0177 qCDebug(MESSAGELIST_LOG) << "Saved" << mParentCache.count() << "cache entries"; 0178 } 0179 0180 void ThreadingCache::addItemToCache(MessageItem *mi) 0181 { 0182 if (mEnabled) { 0183 mItemCache.insert(mi->itemId(), mi); 0184 } 0185 } 0186 0187 void ThreadingCache::updateParent(MessageItem *mi, MessageItem *parent) 0188 { 0189 if (mEnabled) { 0190 mParentCache.insert(mi->itemId(), parent ? parent->itemId() : 0); 0191 } 0192 } 0193 0194 MessageItem *ThreadingCache::parentForItem(MessageItem *mi, qint64 &parentId) const 0195 { 0196 if (mEnabled) { 0197 parentId = mParentCache.value(mi->itemId(), -1); 0198 if (parentId > -1) { 0199 return mItemCache.value(parentId, nullptr); 0200 } else { 0201 return nullptr; 0202 } 0203 } else { 0204 return nullptr; 0205 } 0206 } 0207 0208 void ThreadingCache::expireParent(MessageItem *item) 0209 { 0210 if (mEnabled) { 0211 mParentCache.remove(item->itemId()); 0212 mItemCache.remove(item->itemId()); 0213 } 0214 }