File indexing completed on 2024-11-10 04:40:37
0001 /* 0002 SPDX-FileCopyrightText: 2007 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "changerecorder_p.h" 0008 #include "akonadicore_debug.h" 0009 #include "changerecorderjournal_p.h" 0010 0011 #include <QDataStream> 0012 #include <QDir> 0013 #include <QFile> 0014 #include <QFileInfo> 0015 #include <QSettings> 0016 0017 using namespace Akonadi; 0018 0019 ChangeRecorderPrivate::ChangeRecorderPrivate(ChangeNotificationDependenciesFactory *dependenciesFactory_, ChangeRecorder *parent) 0020 : MonitorPrivate(dependenciesFactory_, parent) 0021 { 0022 } 0023 0024 int ChangeRecorderPrivate::pipelineSize() const 0025 { 0026 if (enableChangeRecording) { 0027 return 0; // we fill the pipeline ourselves when using change recording 0028 } 0029 return MonitorPrivate::pipelineSize(); 0030 } 0031 0032 void ChangeRecorderPrivate::slotNotify(const Protocol::ChangeNotificationPtr &msg) 0033 { 0034 Q_Q(ChangeRecorder); 0035 const int oldChanges = pendingNotifications.size(); 0036 // with change recording disabled this will automatically take care of dispatching notification messages and saving 0037 MonitorPrivate::slotNotify(msg); 0038 if (enableChangeRecording && pendingNotifications.size() != oldChanges) { 0039 Q_EMIT q->changesAdded(); 0040 } 0041 } 0042 0043 // The QSettings object isn't actually used anymore, except for migrating old data 0044 // and it gives us the base of the filename to use. This is all historical. 0045 QString ChangeRecorderPrivate::notificationsFileName() const 0046 { 0047 return settings->fileName() + QStringLiteral("_changes.dat"); 0048 } 0049 0050 void ChangeRecorderPrivate::loadNotifications() 0051 { 0052 pendingNotifications.clear(); 0053 Q_ASSERT(pipeline.isEmpty()); 0054 pipeline.clear(); 0055 0056 const QString changesFileName = notificationsFileName(); 0057 0058 /** 0059 * In an older version we recorded changes inside the settings object, however 0060 * for performance reasons we changed that to store them in a separated file. 0061 * If this file doesn't exists, it means we run the new version the first time, 0062 * so we have to read in the legacy list of changes first. 0063 */ 0064 if (!QFile::exists(changesFileName)) { 0065 settings->beginGroup(QStringLiteral("ChangeRecorder")); 0066 const int size = settings->beginReadArray(QStringLiteral("change")); 0067 0068 for (int i = 0; i < size; ++i) { 0069 settings->setArrayIndex(i); 0070 auto msg = ChangeRecorderJournalReader::loadQSettingsNotification(settings); 0071 if (msg->isValid()) { 0072 pendingNotifications << msg; 0073 } 0074 } 0075 0076 settings->endArray(); 0077 0078 // save notifications to the new file... 0079 saveNotifications(); 0080 0081 // ...delete the legacy list... 0082 settings->remove(QString()); 0083 settings->endGroup(); 0084 0085 // ...and continue as usually 0086 } 0087 0088 QFile file(changesFileName); 0089 if (file.open(QIODevice::ReadOnly)) { 0090 m_needFullSave = false; 0091 pendingNotifications = ChangeRecorderJournalReader::loadFrom(&file, m_needFullSave); 0092 } else { 0093 m_needFullSave = true; 0094 } 0095 notificationsLoaded(); 0096 } 0097 0098 QString ChangeRecorderPrivate::dumpNotificationListToString() const 0099 { 0100 if (!settings) { 0101 return QStringLiteral("No settings set in ChangeRecorder yet."); 0102 } 0103 const QString changesFileName = notificationsFileName(); 0104 QFile file(changesFileName); 0105 0106 if (!file.open(QIODevice::ReadOnly)) { 0107 return QLatin1StringView("Error reading ") + changesFileName; 0108 } 0109 0110 QString result; 0111 bool dummy; 0112 const auto notifications = ChangeRecorderJournalReader::loadFrom(&file, dummy); 0113 for (const auto &n : notifications) { 0114 result += Protocol::debugString(n) + QLatin1Char('\n'); 0115 } 0116 return result; 0117 } 0118 0119 void ChangeRecorderPrivate::writeStartOffset() const 0120 { 0121 if (!settings) { 0122 return; 0123 } 0124 0125 QFile file(notificationsFileName()); 0126 if (!file.open(QIODevice::ReadWrite)) { 0127 qCWarning(AKONADICORE_LOG) << "Could not update notifications in file" << file.fileName(); 0128 return; 0129 } 0130 0131 // Skip "countAndVersion" 0132 file.seek(8); 0133 0134 // qCDebug(AKONADICORE_LOG) << "Writing start offset=" << m_startOffset; 0135 0136 QDataStream stream(&file); 0137 stream.setVersion(QDataStream::Qt_4_6); 0138 stream << static_cast<quint64>(m_startOffset); 0139 0140 // Everything else stays unchanged 0141 } 0142 0143 void ChangeRecorderPrivate::saveNotifications() 0144 { 0145 if (!settings) { 0146 return; 0147 } 0148 0149 QFile file(notificationsFileName()); 0150 QFileInfo info(file); 0151 if (!QFile::exists(info.absolutePath())) { 0152 QDir dir; 0153 dir.mkpath(info.absolutePath()); 0154 } 0155 if (!file.open(QIODevice::WriteOnly)) { 0156 qCWarning(AKONADICORE_LOG) << "Could not save notifications to file" << file.fileName(); 0157 return; 0158 } 0159 ChangeRecorderJournalWriter::saveTo(pendingNotifications, &file); 0160 m_needFullSave = false; 0161 m_startOffset = 0; 0162 } 0163 0164 void ChangeRecorderPrivate::notificationsEnqueued(int count) 0165 { 0166 // Just to ensure the contract is kept, and these two methods are always properly called. 0167 if (enableChangeRecording) { 0168 m_lastKnownNotificationsCount += count; 0169 if (m_lastKnownNotificationsCount != pendingNotifications.count()) { 0170 qCWarning(AKONADICORE_LOG) << this << "The number of pending notifications changed without telling us! Expected" << m_lastKnownNotificationsCount 0171 << "but got" << pendingNotifications.count() << "Caller just added" << count; 0172 Q_ASSERT(pendingNotifications.count() == m_lastKnownNotificationsCount); 0173 } 0174 0175 saveNotifications(); 0176 } 0177 } 0178 0179 void ChangeRecorderPrivate::dequeueNotification() 0180 { 0181 if (pendingNotifications.isEmpty()) { 0182 return; 0183 } 0184 0185 pendingNotifications.dequeue(); 0186 if (enableChangeRecording) { 0187 Q_ASSERT(pendingNotifications.count() == m_lastKnownNotificationsCount - 1); 0188 --m_lastKnownNotificationsCount; 0189 0190 if (m_needFullSave || pendingNotifications.isEmpty()) { 0191 saveNotifications(); 0192 } else { 0193 ++m_startOffset; 0194 writeStartOffset(); 0195 } 0196 } 0197 } 0198 0199 void ChangeRecorderPrivate::notificationsErased() 0200 { 0201 if (enableChangeRecording) { 0202 m_lastKnownNotificationsCount = pendingNotifications.count(); 0203 m_needFullSave = true; 0204 saveNotifications(); 0205 } 0206 } 0207 0208 void ChangeRecorderPrivate::notificationsLoaded() 0209 { 0210 m_lastKnownNotificationsCount = pendingNotifications.count(); 0211 m_startOffset = 0; 0212 } 0213 0214 bool ChangeRecorderPrivate::emitNotification(const Protocol::ChangeNotificationPtr &msg) 0215 { 0216 const bool someoneWasListening = MonitorPrivate::emitNotification(msg); 0217 if (!someoneWasListening && enableChangeRecording) { 0218 // If no signal was emitted (e.g. because no one was connected to it), no one is going to call changeProcessed, so we help ourselves. 0219 dequeueNotification(); 0220 QMetaObject::invokeMethod(q_ptr, "replayNext", Qt::QueuedConnection); 0221 } 0222 return someoneWasListening; 0223 }