Messagelib

threadingcache.cpp
1/******************************************************************************
2 *
3 * SPDX-FileCopyrightText: 2016 Daniel Vrátil <dvratil@kde.org>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 *
7 *******************************************************************************/
8
9#include "threadingcache.h"
10#include "aggregation.h"
11#include "messagelist_debug.h"
12
13#include <QDebug>
14#include <QDir>
15#include <QFile>
16#include <QStandardPaths>
17
18using namespace MessageList::Core;
19
20namespace
21{
22struct CacheHeader {
23 int version;
24 Aggregation::Grouping grouping;
25 Aggregation::Threading threading;
26 Aggregation::ThreadLeader threadLeader;
27 int cacheSize;
28};
29
30QDebug operator<<(QDebug d, const CacheHeader &t)
31{
32 d << " grouping " << t.grouping;
33 d << " threading " << t.threading;
34 d << " threadLeader " << t.threadLeader;
35 d << " grouping " << t.grouping;
36 d << " cacheSize " << t.cacheSize;
37 return d;
38}
39
40QDataStream &operator<<(QDataStream &stream, const CacheHeader &header)
41{
42 return stream << header.version << (qint8)header.grouping << (qint8)header.threading << (qint8)header.threadLeader << header.cacheSize;
43}
44
45QDataStream &operator>>(QDataStream &stream, CacheHeader &header)
46{
47 return stream >> header.version >> (qint8 &)header.grouping >> (qint8 &)header.threading >> (qint8 &)header.threadLeader >> header.cacheSize;
48}
49}
50
51ThreadingCache::ThreadingCache() = default;
52
53ThreadingCache::~ThreadingCache()
54{
55 if (mEnabled && !mCacheId.isEmpty()) {
56 save();
57 }
58}
59
60bool ThreadingCache::isEnabled() const
61{
62 return mEnabled;
63}
64
65void ThreadingCache::setEnabled(bool enabled)
66{
67 mEnabled = enabled;
68}
69
70void ThreadingCache::load(const QString &id, const Aggregation *aggregation)
71{
72 mParentCache.clear();
73 mItemCache.clear();
74
75 mCacheId = id;
76 mThreading = aggregation->threading();
77 mThreadLeader = aggregation->threadLeader();
78 mGrouping = aggregation->grouping();
79 mEnabled = true;
80
81 if (id.isEmpty()) {
82 qCDebug(MESSAGELIST_LOG) << "Invalid collection: id is empty";
83 return;
84 }
85
86 const QString cacheFileName =
87 QStandardPaths::locate(QStandardPaths::CacheLocation, QStringLiteral("messagelist/threading/%1").arg(id), QStandardPaths::LocateFile);
88 if (cacheFileName.isEmpty()) {
89 qCDebug(MESSAGELIST_LOG) << "No threading cache file for collection" << id;
90 return;
91 }
92 qCDebug(MESSAGELIST_LOG) << "Loading threading cache file" << cacheFileName;
93
94 QFile cacheFile(cacheFileName);
95 if (!cacheFile.open(QIODevice::ReadOnly)) {
96 qCWarning(MESSAGELIST_LOG) << "Failed to open cache file" << cacheFileName << ":" << cacheFile.errorString();
97 return;
98 }
99
100 QDataStream stream(&cacheFile);
101 stream.setVersion(QDataStream::Qt_5_15);
102 CacheHeader cacheHeader = {};
103 stream >> cacheHeader;
104
105 if (cacheHeader.version != 1) {
106 // Unknown version
107 qCDebug(MESSAGELIST_LOG) << "\tCache file unusable, unknown version";
108 cacheFile.close();
109 cacheFile.remove();
110 return;
111 }
112
113 if (cacheHeader.grouping != mGrouping || cacheHeader.threading != mThreading || cacheHeader.threadLeader != mThreadLeader) {
114 // The cache is valid, but for a different grouping/threading configuration.
115 qCDebug(MESSAGELIST_LOG) << "\tCache file unusable, threading configuration mismatch";
116 cacheFile.close();
117 cacheFile.remove();
118 return;
119 }
120 const qsizetype values = cacheHeader.cacheSize;
121 mItemCache.reserve(values);
122 mParentCache.reserve(values);
123
124 for (int i = 0; i < values; ++i) {
125 qint64 child;
126 qint64 parent;
127 stream >> child >> parent;
128 if (stream.status() != QDataStream::Ok) {
129 // Suspect corrupted cache
130 qCDebug(MESSAGELIST_LOG) << "\tCache file unusable, data truncated";
131 cacheFile.close();
132 cacheFile.remove();
133 mParentCache.clear();
134 return;
135 }
136
137 mParentCache.insert(child, parent);
138 }
139
140 qCDebug(MESSAGELIST_LOG) << "Loaded" << values << "entries from threading cache";
141}
142
143void ThreadingCache::save()
144{
145 if (mCacheId.isEmpty()) {
146 return;
147 }
149 if (!cacheDir.exists(QStringLiteral("messagelist/threading"))) {
150 if (!cacheDir.mkpath(QStringLiteral("messagelist/threading"))) {
151 qCWarning(MESSAGELIST_LOG) << "Failed to create cache directory.";
152 return;
153 }
154 }
155
156 QFile cacheFile(cacheDir.filePath(QStringLiteral("messagelist/threading/%1").arg(mCacheId)));
157 if (!cacheFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
158 qCWarning(MESSAGELIST_LOG) << "Failed to create cache file:" << cacheFile.errorString() << " mCacheId " << mCacheId;
159 return;
160 }
161
162 qCDebug(MESSAGELIST_LOG) << "Saving threading cache to" << cacheFile.fileName();
163
164 QDataStream stream(&cacheFile);
165 stream.setVersion(QDataStream::Qt_5_15);
166 CacheHeader cacheHeader{1, // version
167 mGrouping,
168 mThreading,
169 mThreadLeader,
170 static_cast<int>(mParentCache.size())};
171 stream << cacheHeader;
172 cacheFile.flush();
173
174 for (auto iter = mParentCache.cbegin(), end = mParentCache.cend(); iter != end; ++iter) {
175 stream << iter.key() << iter.value();
176 }
177 qCDebug(MESSAGELIST_LOG) << "Saved" << mParentCache.count() << "cache entries";
178}
179
180void ThreadingCache::addItemToCache(MessageItem *mi)
181{
182 if (mEnabled) {
183 mItemCache.insert(mi->itemId(), mi);
184 }
185}
186
187void ThreadingCache::updateParent(MessageItem *mi, MessageItem *parent)
188{
189 if (mEnabled) {
190 mParentCache.insert(mi->itemId(), parent ? parent->itemId() : 0);
191 }
192}
193
194MessageItem *ThreadingCache::parentForItem(MessageItem *mi, qint64 &parentId) const
195{
196 if (mEnabled) {
197 parentId = mParentCache.value(mi->itemId(), -1);
198 if (parentId > -1) {
199 return mItemCache.value(parentId, nullptr);
200 } else {
201 return nullptr;
202 }
203 } else {
204 return nullptr;
205 }
206}
207
208void ThreadingCache::expireParent(MessageItem *item)
209{
210 if (mEnabled) {
211 mParentCache.remove(item->itemId());
212 mItemCache.remove(item->itemId());
213 }
214}
A set of aggregation options that can be applied to the MessageList::Model in a single shot.
Definition aggregation.h:29
Threading
The available threading methods.
Definition aggregation.h:65
Grouping grouping() const
Returns the currently set Grouping option.
Threading threading() const
Returns the current threading method.
ThreadLeader
The available thread leading options.
Definition aggregation.h:78
ThreadLeader threadLeader() const
Returns the current thread leader determination method.
The MessageItem class.
Definition messageitem.h:35
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
KDB_EXPORT KDbVersionInfo version()
const QList< QKeySequence > & end()
The implementation independent part of the MessageList library.
Definition aggregation.h:22
QDebug operator<<(QDebug dbg, const PerceptualColor::LchaDouble &value)
bool exists() const const
QString filePath(const QString &fileName) const const
bool mkpath(const QString &dirPath) const const
QHash::const_iterator cbegin() const const
QHash::const_iterator cend() const const
void clear()
int count(const Key &key) const const
QHash::iterator insert(const Key &key, const T &value)
int remove(const Key &key)
void reserve(int size)
int size() const const
const T value(const Key &key) const const
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
QString writableLocation(QStandardPaths::StandardLocation type)
bool isEmpty() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sun Feb 25 2024 18:37:31 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.