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

KDE's Doxygen guidelines are available online.