mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-24 10:52:52 +00:00
359 lines
11 KiB
C++
359 lines
11 KiB
C++
/*
|
|
Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
|
|
|
|
Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
|
|
Author: Sergio Martins, <sergio.martins@kdab.com>
|
|
|
|
Copyright (C) 2010-2012 Sérgio Martins <iamsergio@gmail.com>
|
|
|
|
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.
|
|
*/
|
|
#ifndef AKONADI_INCIDENCECHANGER_P_H
|
|
#define AKONADI_INCIDENCECHANGER_P_H
|
|
|
|
#include "incidencechanger.h"
|
|
#include "itiphandlerhelper_p.h"
|
|
#include "history.h"
|
|
|
|
#include <akonadi/item.h>
|
|
#include <akonadi/collection.h>
|
|
#include <akonadi/transactionsequence.h>
|
|
|
|
#include <QSet>
|
|
#include <QObject>
|
|
#include <QPointer>
|
|
#include <QVector>
|
|
|
|
class KJob;
|
|
#include <QWidget>
|
|
|
|
namespace Akonadi {
|
|
|
|
class TransactionSequence;
|
|
class CollectionFetchJob;
|
|
|
|
class Change {
|
|
|
|
public:
|
|
typedef QSharedPointer<Change> Ptr;
|
|
typedef QList<Ptr> List;
|
|
Change(IncidenceChanger *incidenceChanger, int changeId,
|
|
IncidenceChanger::ChangeType changeType, uint operationId,
|
|
QWidget *parent) : id(changeId)
|
|
, type(changeType)
|
|
, recordToHistory(incidenceChanger->historyEnabled())
|
|
, parentWidget(parent)
|
|
, atomicOperationId(operationId)
|
|
, resultCode(Akonadi::IncidenceChanger::ResultCodeSuccess)
|
|
, completed(false)
|
|
, queuedModification(false)
|
|
, useGroupwareCommunication(incidenceChanger->groupwareCommunication())
|
|
, changer(incidenceChanger)
|
|
{
|
|
}
|
|
|
|
virtual ~Change()
|
|
{
|
|
if (parentChange) {
|
|
parentChange->childAboutToDie(this);
|
|
}
|
|
}
|
|
|
|
virtual void childAboutToDie(Change *child)
|
|
{
|
|
Q_UNUSED(child);
|
|
}
|
|
|
|
virtual void emitCompletionSignal() = 0;
|
|
|
|
const int id;
|
|
const IncidenceChanger::ChangeType type;
|
|
const bool recordToHistory;
|
|
const QPointer<QWidget> parentWidget;
|
|
uint atomicOperationId;
|
|
|
|
// If this change is internal, i.e. not initiated by the user, mParentChange will
|
|
// contain the non-internal change.
|
|
QSharedPointer<Change> parentChange;
|
|
|
|
Akonadi::Item::List originalItems;
|
|
Akonadi::Item newItem;
|
|
|
|
QString errorString;
|
|
IncidenceChanger::ResultCode resultCode;
|
|
bool completed;
|
|
bool queuedModification;
|
|
bool useGroupwareCommunication;
|
|
protected:
|
|
IncidenceChanger *const changer;
|
|
};
|
|
|
|
class ModificationChange : public Change
|
|
{
|
|
public:
|
|
typedef QSharedPointer<ModificationChange> Ptr;
|
|
ModificationChange(IncidenceChanger *changer, int id, uint atomicOperationId,
|
|
QWidget *parent) : Change(changer, id,
|
|
IncidenceChanger::ChangeTypeModify,
|
|
atomicOperationId, parent)
|
|
{
|
|
}
|
|
|
|
~ModificationChange()
|
|
{
|
|
if (!parentChange)
|
|
emitCompletionSignal();
|
|
}
|
|
|
|
/**reimp*/
|
|
void emitCompletionSignal();
|
|
};
|
|
|
|
class CreationChange : public Change
|
|
{
|
|
public:
|
|
typedef QSharedPointer<CreationChange> Ptr;
|
|
CreationChange(IncidenceChanger *changer, int id, uint atomicOperationId,
|
|
QWidget *parent) : Change(changer, id, IncidenceChanger::ChangeTypeCreate,
|
|
atomicOperationId, parent)
|
|
{
|
|
}
|
|
|
|
~CreationChange()
|
|
{
|
|
//kDebug() << "CreationChange::~ will emit signal with " << resultCode;
|
|
if (!parentChange)
|
|
emitCompletionSignal();
|
|
}
|
|
|
|
/**reimp*/
|
|
void emitCompletionSignal();
|
|
|
|
Akonadi::Collection mUsedCol1lection;
|
|
};
|
|
|
|
class DeletionChange : public Change
|
|
{
|
|
public:
|
|
typedef QSharedPointer<DeletionChange> Ptr;
|
|
DeletionChange(IncidenceChanger *changer, int id, uint atomicOperationId,
|
|
QWidget *parent) : Change(changer, id, IncidenceChanger::ChangeTypeDelete,
|
|
atomicOperationId, parent)
|
|
{
|
|
}
|
|
|
|
~DeletionChange()
|
|
{
|
|
//kDebug() << "DeletionChange::~ will emit signal with " << resultCode;
|
|
if (!parentChange)
|
|
emitCompletionSignal();
|
|
}
|
|
|
|
/**reimp*/
|
|
void emitCompletionSignal();
|
|
|
|
QVector<Akonadi::Item::Id> mItemIds;
|
|
};
|
|
|
|
class AtomicOperation {
|
|
public:
|
|
uint m_id;
|
|
|
|
// To make sure they are not repeated
|
|
QSet<Akonadi::Item::Id> m_itemIdsInOperation;
|
|
|
|
// After endAtomicOperation() is called we don't accept more changes
|
|
bool m_endCalled;
|
|
|
|
// Number of completed changes(jobs)
|
|
int m_numCompletedChanges;
|
|
QString m_description;
|
|
bool m_transactionCompleted;
|
|
|
|
AtomicOperation(IncidenceChanger::Private *icp, uint ident);
|
|
|
|
~AtomicOperation()
|
|
{
|
|
//kDebug() << "AtomicOperation::~ " << wasRolledback << changes.count();
|
|
if (m_wasRolledback) {
|
|
for (int i=0; i<m_changes.count(); ++i) {
|
|
// When a job that can finish successfully is aborted because the transaction failed
|
|
// because of some other job, akonadi is returning an Unknown error
|
|
// which isnt very specific
|
|
if (m_changes[i]->completed &&
|
|
(m_changes[i]->resultCode == IncidenceChanger::ResultCodeSuccess ||
|
|
(m_changes[i]->resultCode == IncidenceChanger::ResultCodeJobError &&
|
|
m_changes[i]->errorString == QLatin1String("Unknown error.")))) {
|
|
m_changes[i]->resultCode = IncidenceChanger::ResultCodeRolledback;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Did all jobs return ?
|
|
bool pendingJobs() const
|
|
{
|
|
return m_changes.count() > m_numCompletedChanges;
|
|
}
|
|
|
|
void setRolledback()
|
|
{
|
|
//kDebug() << "AtomicOperation::setRolledBack()";
|
|
m_wasRolledback = true;
|
|
transaction()->rollback();
|
|
}
|
|
|
|
bool rolledback() const
|
|
{
|
|
return m_wasRolledback;
|
|
}
|
|
|
|
void addChange(const Change::Ptr &change)
|
|
{
|
|
if (change->type == IncidenceChanger::ChangeTypeDelete) {
|
|
DeletionChange::Ptr deletion = change.staticCast<DeletionChange>();
|
|
foreach(Akonadi::Item::Id id, deletion->mItemIds) {
|
|
Q_ASSERT(!m_itemIdsInOperation.contains(id));
|
|
m_itemIdsInOperation.insert(id);
|
|
}
|
|
} else if (change->type == IncidenceChanger::ChangeTypeModify) {
|
|
Q_ASSERT(!m_itemIdsInOperation.contains(change->newItem.id()));
|
|
m_itemIdsInOperation.insert(change->newItem.id());
|
|
}
|
|
|
|
m_changes << change;
|
|
}
|
|
|
|
Akonadi::TransactionSequence *transaction();
|
|
|
|
private:
|
|
QVector<Change::Ptr> m_changes;
|
|
bool m_wasRolledback;
|
|
Akonadi::TransactionSequence *m_transaction; // constructed in first use
|
|
IncidenceChanger::Private *m_incidenceChangerPrivate;
|
|
};
|
|
|
|
class IncidenceChanger::Private : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
explicit Private(bool enableHistory, IncidenceChanger *mIncidenceChanger);
|
|
~Private();
|
|
|
|
void loadCollections(); // async-loading of list of writable collections
|
|
bool isLoadingCollections() const;
|
|
Collection::List collectionsForMimeType(const QString &mimeType, const Collection::List &collections);
|
|
|
|
// steps for the async operation:
|
|
void step1DetermineDestinationCollection(const Change::Ptr &change, const Collection &collection);
|
|
void step2CreateIncidence(const Change::Ptr &change, const Collection &collection);
|
|
|
|
|
|
|
|
/**
|
|
Returns true if, for a specific item, an ItemDeleteJob is already running,
|
|
or if one already run successfully.
|
|
*/
|
|
bool deleteAlreadyCalled(Akonadi::Item::Id id) const;
|
|
|
|
QString showErrorDialog(Akonadi::IncidenceChanger::ResultCode, QWidget *parent);
|
|
|
|
void setChangeInternal(int changeId);
|
|
|
|
void adjustRecurrence(const KCalCore::Incidence::Ptr &originalIncidence,
|
|
const KCalCore::Incidence::Ptr &incidence);
|
|
|
|
bool hasRights(const Akonadi::Collection &collection, IncidenceChanger::ChangeType) const;
|
|
void queueModification(Change::Ptr);
|
|
void performModification(Change::Ptr);
|
|
bool atomicOperationIsValid(uint atomicOperationId) const;
|
|
Akonadi::Job* parentJob(const Change::Ptr &) const;
|
|
void cancelTransaction();
|
|
void cleanupTransaction();
|
|
bool allowAtomicOperation(int atomicOperationId, const Change::Ptr &change) const;
|
|
|
|
bool handleInvitationsBeforeChange(const Change::Ptr &change);
|
|
bool handleInvitationsAfterChange(const Change::Ptr &change);
|
|
static bool myAttendeeStatusChanged(const KCalCore::Incidence::Ptr &newIncidence,
|
|
const KCalCore::Incidence::Ptr &oldIncidence,
|
|
const QStringList &myEmails);
|
|
|
|
public Q_SLOTS:
|
|
void handleCreateJobResult(KJob*);
|
|
void handleModifyJobResult(KJob*);
|
|
void handleDeleteJobResult(KJob*);
|
|
void handleTransactionJobResult(KJob*);
|
|
void performNextModification(Akonadi::Item::Id id);
|
|
void onCollectionsLoaded(KJob*);
|
|
|
|
public:
|
|
int mLatestChangeId;
|
|
QHash<const KJob*,Change::Ptr> mChangeForJob;
|
|
bool mShowDialogsOnError;
|
|
Akonadi::Collection mDefaultCollection;
|
|
DestinationPolicy mDestinationPolicy;
|
|
QVector<Akonadi::Item::Id> mDeletedItemIds;
|
|
Change::List mPendingCreations; // Creations waiting for collections to be loaded
|
|
|
|
History *mHistory;
|
|
bool mUseHistory;
|
|
|
|
/**
|
|
Queue modifications by ID. We can only send a modification to akonadi when the previous
|
|
one ended.
|
|
|
|
The container doesn't look like a queue because of an optimization: if there's a modification
|
|
A in progress, a modification B waiting (queued), and then a new one C comes in,
|
|
we just discard B, and queue C. The queue always has 1 element max.
|
|
*/
|
|
QHash<Akonadi::Item::Id,Change::Ptr> mQueuedModifications;
|
|
|
|
/**
|
|
So we know if there's already a modification in progress
|
|
*/
|
|
QHash<Akonadi::Item::Id,Change::Ptr> mModificationsInProgress;
|
|
|
|
QHash<int,Change::Ptr> mChangeById;
|
|
|
|
/**
|
|
Indexed by atomic operation id.
|
|
*/
|
|
QHash<uint,AtomicOperation*> mAtomicOperations;
|
|
|
|
bool mRespectsCollectionRights;
|
|
bool mGroupwareCommunication;
|
|
|
|
QHash<Akonadi::TransactionSequence*, uint> mAtomicOperationByTransaction;
|
|
QHash<uint,ITIPHandlerHelper::SendResult> mInvitationStatusByAtomicOperation;
|
|
|
|
uint mLatestAtomicOperationId;
|
|
bool mBatchOperationInProgress;
|
|
Akonadi::Collection mLastCollectionUsed;
|
|
bool mAutoAdjustRecurrence;
|
|
|
|
Akonadi::CollectionFetchJob *m_collectionFetchJob;
|
|
|
|
QMap<KJob *, QSet<KCalCore::IncidenceBase::Field> > mDirtyFieldsByJob;
|
|
|
|
IncidenceChanger::InvitationPolicy m_invitationPolicy;
|
|
|
|
private:
|
|
IncidenceChanger *q;
|
|
};
|
|
|
|
}
|
|
|
|
#endif
|