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 }