Messagelib

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

KDE's Doxygen guidelines are available online.