mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 10:22:50 +00:00
533 lines
18 KiB
C++
533 lines
18 KiB
C++
/*
|
|
Copyright (c) 2007 Volker Krause <vkrause@kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU Library General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or (at your
|
|
option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
|
|
License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to the
|
|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
02110-1301, USA.
|
|
*/
|
|
|
|
#include "changerecorder_p.h"
|
|
|
|
#include <QtCore/QFile>
|
|
#include <QtCore/QDir>
|
|
#include <QtCore/QSettings>
|
|
#include <QtCore/QFileInfo>
|
|
|
|
using namespace Akonadi;
|
|
|
|
ChangeRecorderPrivate::ChangeRecorderPrivate(ChangeNotificationDependenciesFactory *dependenciesFactory_,
|
|
ChangeRecorder *parent)
|
|
: MonitorPrivate(dependenciesFactory_, parent)
|
|
, settings(0)
|
|
, enableChangeRecording(true)
|
|
, m_lastKnownNotificationsCount(0)
|
|
, m_startOffset(0)
|
|
, m_needFullSave(true)
|
|
{
|
|
}
|
|
|
|
int ChangeRecorderPrivate::pipelineSize() const
|
|
{
|
|
if (enableChangeRecording) {
|
|
return 0; // we fill the pipeline ourselves when using change recording
|
|
}
|
|
return MonitorPrivate::pipelineSize();
|
|
}
|
|
|
|
void ChangeRecorderPrivate::slotNotify(const Akonadi::NotificationMessageV3::List &msgs)
|
|
{
|
|
Q_Q(ChangeRecorder);
|
|
const int oldChanges = pendingNotifications.size();
|
|
// with change recording disabled this will automatically take care of dispatching notification messages and saving
|
|
MonitorPrivate::slotNotify(msgs);
|
|
if (enableChangeRecording && pendingNotifications.size() != oldChanges) {
|
|
emit q->changesAdded();
|
|
}
|
|
}
|
|
|
|
// The QSettings object isn't actually used anymore, except for migrating old data
|
|
// and it gives us the base of the filename to use. This is all historical.
|
|
QString ChangeRecorderPrivate::notificationsFileName() const
|
|
{
|
|
return settings->fileName() + QLatin1String("_changes.dat");
|
|
}
|
|
|
|
void ChangeRecorderPrivate::loadNotifications()
|
|
{
|
|
pendingNotifications.clear();
|
|
Q_ASSERT(pipeline.isEmpty());
|
|
pipeline.clear();
|
|
|
|
const QString changesFileName = notificationsFileName();
|
|
|
|
/**
|
|
* In an older version we recorded changes inside the settings object, however
|
|
* for performance reasons we changed that to store them in a separated file.
|
|
* If this file doesn't exists, it means we run the new version the first time,
|
|
* so we have to read in the legacy list of changes first.
|
|
*/
|
|
if (!QFile::exists(changesFileName)) {
|
|
QStringList list;
|
|
settings->beginGroup(QLatin1String("ChangeRecorder"));
|
|
const int size = settings->beginReadArray(QLatin1String("change"));
|
|
|
|
for (int i = 0; i < size; ++i) {
|
|
settings->setArrayIndex(i);
|
|
NotificationMessageV3 msg;
|
|
msg.setSessionId(settings->value(QLatin1String("sessionId")).toByteArray());
|
|
msg.setType((NotificationMessageV2::Type)settings->value(QLatin1String("type")).toInt());
|
|
msg.setOperation((NotificationMessageV2::Operation)settings->value(QLatin1String("op")).toInt());
|
|
msg.addEntity(settings->value(QLatin1String("uid")).toLongLong(),
|
|
settings->value(QLatin1String("rid")).toString(),
|
|
QString(),
|
|
settings->value(QLatin1String("mimeType")).toString());
|
|
msg.setResource(settings->value(QLatin1String("resource")).toByteArray());
|
|
msg.setParentCollection(settings->value(QLatin1String("parentCol")).toLongLong());
|
|
msg.setParentDestCollection(settings->value(QLatin1String("parentDestCol")).toLongLong());
|
|
list = settings->value(QLatin1String("itemParts")).toStringList();
|
|
QSet<QByteArray> itemParts;
|
|
Q_FOREACH (const QString &entry, list) {
|
|
itemParts.insert(entry.toLatin1());
|
|
}
|
|
msg.setItemParts(itemParts);
|
|
pendingNotifications << msg;
|
|
}
|
|
|
|
settings->endArray();
|
|
|
|
// save notifications to the new file...
|
|
saveNotifications();
|
|
|
|
// ...delete the legacy list...
|
|
settings->remove(QString());
|
|
settings->endGroup();
|
|
|
|
// ...and continue as usually
|
|
}
|
|
|
|
QFile file(changesFileName);
|
|
if (file.open(QIODevice::ReadOnly)) {
|
|
m_needFullSave = false;
|
|
pendingNotifications = loadFrom(&file);
|
|
} else {
|
|
m_needFullSave = true;
|
|
}
|
|
notificationsLoaded();
|
|
}
|
|
|
|
static const quint64 s_currentVersion = Q_UINT64_C(0x000300000000);
|
|
static const quint64 s_versionMask = Q_UINT64_C(0xFFFF00000000);
|
|
static const quint64 s_sizeMask = Q_UINT64_C(0x0000FFFFFFFF);
|
|
|
|
QQueue<NotificationMessageV3> ChangeRecorderPrivate::loadFrom(QIODevice *device)
|
|
{
|
|
QDataStream stream(device);
|
|
stream.setVersion(QDataStream::Qt_4_6);
|
|
|
|
QByteArray sessionId, resource, destinationResource;
|
|
int type, operation, entityCnt;
|
|
quint64 uid, parentCollection, parentDestCollection;
|
|
QString remoteId, mimeType, remoteRevision;
|
|
QSet<QByteArray> itemParts, addedFlags, removedFlags;
|
|
QSet<qint64> addedTags, removedTags;
|
|
|
|
QQueue<NotificationMessageV3> list;
|
|
|
|
quint64 sizeAndVersion;
|
|
stream >> sizeAndVersion;
|
|
|
|
const quint64 size = sizeAndVersion & s_sizeMask;
|
|
const quint64 version = (sizeAndVersion & s_versionMask) >> 32;
|
|
|
|
quint64 startOffset = 0;
|
|
if (version >= 1) {
|
|
stream >> startOffset;
|
|
}
|
|
|
|
// If we skip the first N items, then we'll need to rewrite the file on saving.
|
|
// Also, if the file is old, it needs to be rewritten.
|
|
m_needFullSave = startOffset > 0 || version == 0;
|
|
|
|
for (quint64 i = 0; i < size && !stream.atEnd(); ++i) {
|
|
NotificationMessageV2 msg;
|
|
|
|
if (version == 1) {
|
|
stream >> sessionId;
|
|
stream >> type;
|
|
stream >> operation;
|
|
stream >> uid;
|
|
stream >> remoteId;
|
|
stream >> resource;
|
|
stream >> parentCollection;
|
|
stream >> parentDestCollection;
|
|
stream >> mimeType;
|
|
stream >> itemParts;
|
|
|
|
if (i < startOffset) {
|
|
continue;
|
|
}
|
|
|
|
msg.setSessionId(sessionId);
|
|
msg.setType(static_cast<NotificationMessageV2::Type>(type));
|
|
msg.setOperation(static_cast<NotificationMessageV2::Operation>(operation));
|
|
msg.addEntity(uid, remoteId, QString(), mimeType);
|
|
msg.setResource(resource);
|
|
msg.setParentCollection(parentCollection);
|
|
msg.setParentDestCollection(parentDestCollection);
|
|
msg.setItemParts(itemParts);
|
|
|
|
} else if (version == 2 || version == 3) {
|
|
|
|
NotificationMessageV3 msg;
|
|
|
|
stream >> sessionId;
|
|
stream >> type;
|
|
stream >> operation;
|
|
stream >> entityCnt;
|
|
for (int j = 0; j < entityCnt; ++j) {
|
|
stream >> uid;
|
|
stream >> remoteId;
|
|
stream >> remoteRevision;
|
|
stream >> mimeType;
|
|
msg.addEntity(uid, remoteId, remoteRevision, mimeType);
|
|
}
|
|
stream >> resource;
|
|
stream >> destinationResource;
|
|
stream >> parentCollection;
|
|
stream >> parentDestCollection;
|
|
stream >> itemParts;
|
|
stream >> addedFlags;
|
|
stream >> removedFlags;
|
|
if (version == 3) {
|
|
stream >> addedTags;
|
|
stream >> removedTags;
|
|
}
|
|
|
|
if (i < startOffset) {
|
|
continue;
|
|
}
|
|
|
|
msg.setSessionId(sessionId);
|
|
msg.setType(static_cast<NotificationMessageV2::Type>(type));
|
|
msg.setOperation(static_cast<NotificationMessageV2::Operation>(operation));
|
|
msg.setResource(resource);
|
|
msg.setDestinationResource(destinationResource);
|
|
msg.setParentCollection(parentCollection);
|
|
msg.setParentDestCollection(parentDestCollection);
|
|
msg.setItemParts(itemParts);
|
|
msg.setAddedFlags(addedFlags);
|
|
msg.setRemovedFlags(removedFlags);
|
|
msg.setAddedTags(addedTags);
|
|
msg.setRemovedTags(removedTags);
|
|
|
|
list << msg;
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
QString ChangeRecorderPrivate::dumpNotificationListToString() const
|
|
{
|
|
if (!settings) {
|
|
return QString::fromLatin1("No settings set in ChangeRecorder yet.");
|
|
}
|
|
QString result;
|
|
const QString changesFileName = notificationsFileName();
|
|
QFile file(changesFileName);
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
return QString::fromLatin1("Error reading ") + changesFileName;
|
|
}
|
|
|
|
QDataStream stream(&file);
|
|
stream.setVersion(QDataStream::Qt_4_6);
|
|
|
|
QByteArray sessionId, resource, destResource;
|
|
int type, operation, entityCnt;
|
|
quint64 parentCollection, parentDestCollection;
|
|
QString remoteId, remoteRevision, mimeType;
|
|
QSet<QByteArray> itemParts, addedFlags, removedFlags;
|
|
QSet<qint64> addedTags, removedTags;
|
|
QVariantList items;
|
|
|
|
QStringList list;
|
|
|
|
quint64 sizeAndVersion;
|
|
stream >> sizeAndVersion;
|
|
|
|
const quint64 size = sizeAndVersion & s_sizeMask;
|
|
const quint64 version = (sizeAndVersion & s_versionMask) >> 32;
|
|
|
|
quint64 startOffset = 0;
|
|
if (version >= 1) {
|
|
stream >> startOffset;
|
|
}
|
|
|
|
for (quint64 i = 0; i < size && !stream.atEnd(); ++i) {
|
|
stream >> sessionId;
|
|
stream >> type;
|
|
stream >> operation;
|
|
stream >> entityCnt;
|
|
for (int j = 0; j < entityCnt; ++j) {
|
|
QVariantMap map;
|
|
stream >> map[QLatin1String("uid")];
|
|
stream >> map[QLatin1String("remoteId")];
|
|
stream >> map[QLatin1String("remoteRevision")];
|
|
stream >> map[QLatin1String("mimeType")];
|
|
items << map;
|
|
}
|
|
stream >> resource;
|
|
stream >> destResource;
|
|
stream >> parentCollection;
|
|
stream >> parentDestCollection;
|
|
stream >> itemParts;
|
|
stream >> addedFlags;
|
|
stream >> removedFlags;
|
|
if (version == 3) {
|
|
stream >> addedTags;
|
|
stream >> removedTags;
|
|
}
|
|
|
|
if (i < startOffset) {
|
|
continue;
|
|
}
|
|
|
|
QString typeString;
|
|
switch (type) {
|
|
case NotificationMessageV2::Collections:
|
|
typeString = QLatin1String("Collections");
|
|
break;
|
|
case NotificationMessageV2::Items:
|
|
typeString = QLatin1String("Items");
|
|
break;
|
|
case NotificationMessageV2::Tags:
|
|
typeString = QLatin1String("Tags");
|
|
break;
|
|
default:
|
|
typeString = QLatin1String("InvalidType");
|
|
break;
|
|
};
|
|
|
|
QString operationString;
|
|
switch (operation) {
|
|
case NotificationMessageV2::Add:
|
|
operationString = QLatin1String("Add");
|
|
break;
|
|
case NotificationMessageV2::Modify:
|
|
operationString = QLatin1String("Modify");
|
|
break;
|
|
case NotificationMessageV2::ModifyFlags:
|
|
operationString = QLatin1String("ModifyFlags");
|
|
break;
|
|
case NotificationMessageV2::ModifyTags:
|
|
operationString = QLatin1String("ModifyTags");
|
|
break;
|
|
case NotificationMessageV2::Move:
|
|
operationString = QLatin1String("Move");
|
|
break;
|
|
case NotificationMessageV2::Remove:
|
|
operationString = QLatin1String("Remove");
|
|
break;
|
|
case NotificationMessageV2::Link:
|
|
operationString = QLatin1String("Link");
|
|
break;
|
|
case NotificationMessageV2::Unlink:
|
|
operationString = QLatin1String("Unlink");
|
|
break;
|
|
case NotificationMessageV2::Subscribe:
|
|
operationString = QLatin1String("Subscribe");
|
|
break;
|
|
case NotificationMessageV2::Unsubscribe:
|
|
operationString = QLatin1String("Unsubscribe");
|
|
break;
|
|
default:
|
|
operationString = QLatin1String("InvalidOp");
|
|
break;
|
|
};
|
|
|
|
QStringList itemPartsList, addedFlagsList, removedFlagsList, addedTagsList, removedTagsList;
|
|
foreach (const QByteArray &b, itemParts) {
|
|
itemPartsList.push_back(QString::fromLatin1(b));
|
|
}
|
|
foreach (const QByteArray &b, addedFlags) {
|
|
addedFlagsList.push_back(QString::fromLatin1(b));
|
|
}
|
|
foreach (const QByteArray &b, removedFlags) {
|
|
removedFlagsList.push_back(QString::fromLatin1(b));
|
|
}
|
|
foreach (qint64 id, addedTags) {
|
|
addedTagsList.push_back(QString::number(id));
|
|
}
|
|
foreach (qint64 id, removedTags) {
|
|
removedTagsList.push_back(QString::number(id)) ;
|
|
}
|
|
|
|
const QString entry = QString::fromLatin1("session=%1 type=%2 operation=%3 items=%4 resource=%5 destResource=%6 parentCollection=%7 parentDestCollection=%8 itemParts=%9 addedFlags=%10 removedFlags=%11 addedTags=%12 removedTags=%13")
|
|
.arg(QString::fromLatin1(sessionId))
|
|
.arg(typeString)
|
|
.arg(operationString)
|
|
.arg(QVariant(items).toString())
|
|
.arg(QString::fromLatin1(resource))
|
|
.arg(QString::fromLatin1(destResource))
|
|
.arg(parentCollection)
|
|
.arg(parentDestCollection)
|
|
.arg(itemPartsList.join(QLatin1String(", ")))
|
|
.arg(addedFlagsList.join(QLatin1String(", ")))
|
|
.arg(removedFlagsList.join(QLatin1String(", ")))
|
|
.arg(addedTagsList.join(QLatin1String(", ")))
|
|
.arg(removedTagsList.join(QLatin1String(", ")));
|
|
|
|
result += entry + QLatin1Char('\n');
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void ChangeRecorderPrivate::addToStream(QDataStream &stream, const NotificationMessageV3 &msg)
|
|
{
|
|
stream << msg.sessionId();
|
|
stream << int(msg.type());
|
|
stream << int(msg.operation());
|
|
stream << msg.entities().count();
|
|
Q_FOREACH (const NotificationMessageV2::Entity &entity, msg.entities()) {
|
|
stream << quint64(entity.id);
|
|
stream << entity.remoteId;
|
|
stream << entity.remoteRevision;
|
|
stream << entity.mimeType;
|
|
}
|
|
stream << msg.resource();
|
|
stream << msg.destinationResource();
|
|
stream << quint64(msg.parentCollection());
|
|
stream << quint64(msg.parentDestCollection());
|
|
stream << msg.itemParts();
|
|
stream << msg.addedFlags();
|
|
stream << msg.removedFlags();
|
|
stream << msg.addedTags();
|
|
stream << msg.removedTags();
|
|
}
|
|
|
|
void ChangeRecorderPrivate::writeStartOffset()
|
|
{
|
|
if (!settings) {
|
|
return;
|
|
}
|
|
|
|
QFile file(notificationsFileName());
|
|
if (!file.open(QIODevice::ReadWrite)) {
|
|
qWarning() << "Could not update notifications in file" << file.fileName();
|
|
return;
|
|
}
|
|
|
|
// Skip "countAndVersion"
|
|
file.seek(8);
|
|
|
|
//kDebug() << "Writing start offset=" << m_startOffset;
|
|
|
|
QDataStream stream(&file);
|
|
stream.setVersion(QDataStream::Qt_4_6);
|
|
stream << static_cast<quint64>(m_startOffset);
|
|
|
|
// Everything else stays unchanged
|
|
}
|
|
|
|
void ChangeRecorderPrivate::saveNotifications()
|
|
{
|
|
if (!settings) {
|
|
return;
|
|
}
|
|
|
|
QFile file(notificationsFileName());
|
|
QFileInfo info(file);
|
|
if (!QFile::exists(info.absolutePath())) {
|
|
QDir dir;
|
|
dir.mkpath(info.absolutePath());
|
|
}
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
|
qWarning() << "Could not save notifications to file" << file.fileName();
|
|
return;
|
|
}
|
|
saveTo(&file);
|
|
m_needFullSave = false;
|
|
m_startOffset = 0;
|
|
}
|
|
|
|
void ChangeRecorderPrivate::saveTo(QIODevice *device)
|
|
{
|
|
// Version 0 of this file format was writing a quint64 count, followed by the notifications.
|
|
// Version 1 bundles a version number into that quint64, to be able to detect a version number at load time.
|
|
|
|
const quint64 countAndVersion = static_cast<quint64>(pendingNotifications.count()) | s_currentVersion;
|
|
|
|
QDataStream stream(device);
|
|
stream.setVersion(QDataStream::Qt_4_6);
|
|
|
|
stream << countAndVersion;
|
|
stream << quint64(0); // no start offset
|
|
|
|
//kDebug() << "Saving" << pendingNotifications.count() << "notifications (full save)";
|
|
|
|
for (int i = 0; i < pendingNotifications.count(); ++i) {
|
|
const NotificationMessageV3 msg = pendingNotifications.at(i);
|
|
addToStream(stream, msg);
|
|
}
|
|
}
|
|
|
|
void ChangeRecorderPrivate::notificationsEnqueued(int count)
|
|
{
|
|
// Just to ensure the contract is kept, and these two methods are always properly called.
|
|
if (enableChangeRecording) {
|
|
m_lastKnownNotificationsCount += count;
|
|
if (m_lastKnownNotificationsCount != pendingNotifications.count()) {
|
|
kWarning() << this << "The number of pending notifications changed without telling us! Expected"
|
|
<< m_lastKnownNotificationsCount << "but got" << pendingNotifications.count()
|
|
<< "Caller just added" << count;
|
|
Q_ASSERT(pendingNotifications.count() == m_lastKnownNotificationsCount);
|
|
}
|
|
|
|
saveNotifications();
|
|
}
|
|
}
|
|
|
|
void ChangeRecorderPrivate::dequeueNotification()
|
|
{
|
|
pendingNotifications.dequeue();
|
|
|
|
if (enableChangeRecording) {
|
|
|
|
Q_ASSERT(pendingNotifications.count() == m_lastKnownNotificationsCount - 1);
|
|
--m_lastKnownNotificationsCount;
|
|
|
|
if (m_needFullSave || pendingNotifications.isEmpty()) {
|
|
saveNotifications();
|
|
} else {
|
|
++m_startOffset;
|
|
writeStartOffset();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ChangeRecorderPrivate::notificationsErased()
|
|
{
|
|
if (enableChangeRecording) {
|
|
m_lastKnownNotificationsCount = pendingNotifications.count();
|
|
m_needFullSave = true;
|
|
saveNotifications();
|
|
}
|
|
}
|
|
|
|
void ChangeRecorderPrivate::notificationsLoaded()
|
|
{
|
|
m_lastKnownNotificationsCount = pendingNotifications.count();
|
|
m_startOffset = 0;
|
|
}
|