mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 10:22:50 +00:00
333 lines
13 KiB
C++
333 lines
13 KiB
C++
/*
|
|
* Copyright (c) 2011 Christian Mollekopf <chrigi_1@fastmail.fm>
|
|
*
|
|
* 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 "trashrestorejob.h"
|
|
|
|
#include "collection.h"
|
|
#include "entitydeletedattribute.h"
|
|
#include "item.h"
|
|
#include "job_p.h"
|
|
#include "trashsettings.h"
|
|
|
|
#include <KLocalizedString>
|
|
|
|
#include <akonadi/itemdeletejob.h>
|
|
#include <akonadi/collectiondeletejob.h>
|
|
#include <akonadi/itemmovejob.h>
|
|
#include <akonadi/collectionmovejob.h>
|
|
#include <akonadi/itemmodifyjob.h>
|
|
#include <akonadi/collectionmodifyjob.h>
|
|
#include <akonadi/collectionfetchjob.h>
|
|
#include <akonadi/itemfetchjob.h>
|
|
#include <akonadi/collectionfetchscope.h>
|
|
#include <akonadi/itemfetchscope.h>
|
|
|
|
#include <QHash>
|
|
|
|
using namespace Akonadi;
|
|
|
|
class TrashRestoreJob::TrashRestoreJobPrivate : public JobPrivate
|
|
{
|
|
public:
|
|
TrashRestoreJobPrivate(TrashRestoreJob *parent)
|
|
: JobPrivate(parent)
|
|
{
|
|
}
|
|
|
|
void selectResult(KJob *job);
|
|
|
|
//Called when the target collection was fetched,
|
|
//will issue the move and the removal of the attributes if collection is valid
|
|
void targetCollectionFetched(KJob *job);
|
|
|
|
void removeAttribute(const Akonadi::Item::List &list);
|
|
void removeAttribute(const Akonadi::Collection::List &list);
|
|
|
|
//Called after initial fetch of items, issues fetch of target collection or removes attributes for in place restore
|
|
void itemsReceived(const Akonadi::Item::List &items);
|
|
void collectionsReceived(const Akonadi::Collection::List &collections);
|
|
|
|
Q_DECLARE_PUBLIC(TrashRestoreJob)
|
|
|
|
Item::List mItems;
|
|
Collection mCollection;
|
|
Collection mTargetCollection;
|
|
QHash<Collection, Item::List> restoreCollections; //groups items to target restore collections
|
|
|
|
};
|
|
|
|
void TrashRestoreJob::TrashRestoreJobPrivate::selectResult(KJob *job)
|
|
{
|
|
Q_Q(TrashRestoreJob);
|
|
if (job->error()) {
|
|
kWarning() << job->errorString();
|
|
return; // KCompositeJob takes care of errors
|
|
}
|
|
|
|
if (!q->hasSubjobs() || (q->subjobs().contains(static_cast<KJob *>(q->sender())) && q->subjobs().size() == 1)) {
|
|
//kWarning() << "trash restore finished";
|
|
q->emitResult();
|
|
}
|
|
}
|
|
|
|
void TrashRestoreJob::TrashRestoreJobPrivate::targetCollectionFetched(KJob *job)
|
|
{
|
|
Q_Q(TrashRestoreJob);
|
|
|
|
CollectionFetchJob *fetchJob = qobject_cast<CollectionFetchJob *> (job);
|
|
Q_ASSERT(fetchJob);
|
|
const Collection::List &list = fetchJob->collections();
|
|
|
|
if (list.isEmpty() || !list.first().isValid() || list.first().hasAttribute<Akonadi::EntityDeletedAttribute>()) { //target collection is invalid/not existing
|
|
|
|
const QString res = fetchJob->property("Resource").toString();
|
|
if (res.isEmpty()) { //There is no fallback
|
|
q->setError(Job::Unknown);
|
|
q->setErrorText(i18n("Could not find restore collection and restore resource is not available"));
|
|
q->emitResult();
|
|
//FAIL
|
|
kWarning() << "restore collection not available";
|
|
return;
|
|
}
|
|
|
|
//Try again with the root collection of the resource as fallback
|
|
CollectionFetchJob *resRootFetch = new CollectionFetchJob(Collection::root(), CollectionFetchJob::FirstLevel, q);
|
|
resRootFetch->fetchScope().setResource(res);
|
|
const QVariant &var = fetchJob->property("Items");
|
|
if (var.isValid()) {
|
|
resRootFetch->setProperty("Items", var.toInt());
|
|
}
|
|
q->connect(resRootFetch, SIGNAL(result(KJob*)), SLOT(targetCollectionFetched(KJob*)));
|
|
q->connect(resRootFetch, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)));
|
|
return;
|
|
}
|
|
Q_ASSERT(list.size() == 1);
|
|
//SUCCESS
|
|
//We know where to move the entity, so remove the attributes and move them to the right location
|
|
if (!mItems.isEmpty()) {
|
|
const QVariant &var = fetchJob->property("Items");
|
|
Q_ASSERT(var.isValid());
|
|
const Item::List &items = restoreCollections[Collection(var.toInt())];
|
|
|
|
//store removed attribute if destination collection is valid or the item doesn't have a restore collection
|
|
//TODO only remove the attribute if the move job was successful (although it is unlikely that it fails since we already fetched the collection)
|
|
removeAttribute(items);
|
|
if (items.first().parentCollection() != list.first()) {
|
|
ItemMoveJob *job = new ItemMoveJob(items, list.first(), q);
|
|
q->connect(job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)));
|
|
}
|
|
} else {
|
|
Q_ASSERT(mCollection.isValid());
|
|
//TODO only remove the attribute if the move job was successful
|
|
removeAttribute(Collection::List() << mCollection);
|
|
CollectionFetchJob *collectionFetchJob = new CollectionFetchJob(mCollection, CollectionFetchJob::Recursive, q);
|
|
q->connect(collectionFetchJob, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)));
|
|
q->connect(collectionFetchJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(removeAttribute(Akonadi::Collection::List)));
|
|
|
|
if (mCollection.parentCollection() != list.first()) {
|
|
CollectionMoveJob *job = new CollectionMoveJob(mCollection, list.first(), q);
|
|
q->connect(job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void TrashRestoreJob::TrashRestoreJobPrivate::itemsReceived(const Akonadi::Item::List &items)
|
|
{
|
|
Q_Q(TrashRestoreJob);
|
|
if (items.isEmpty()) {
|
|
q->setError(Job::Unknown);
|
|
q->setErrorText(i18n("Invalid items passed"));
|
|
q->emitResult();
|
|
return;
|
|
}
|
|
mItems = items;
|
|
|
|
//Sort by restore collection
|
|
foreach (const Item &item, mItems) {
|
|
if (!item.hasAttribute<Akonadi::EntityDeletedAttribute>()) {
|
|
continue;
|
|
}
|
|
//If the restore collection is invalid we restore the item in place, so we don't need to know its restore resource => we can put those cases in the same list
|
|
restoreCollections[item.attribute<Akonadi::EntityDeletedAttribute>()->restoreCollection()].append(item);
|
|
}
|
|
|
|
foreach (const Collection &col, restoreCollections.keys()) { //krazy:exclude=foreach
|
|
const Item &first = restoreCollections.value(col).first();
|
|
//Move the items to the correct collection if available
|
|
Collection targetCollection = col;
|
|
const QString restoreResource = first.attribute<Akonadi::EntityDeletedAttribute>()->restoreResource();
|
|
|
|
//Restore in place if no restore collection is set
|
|
if (!targetCollection.isValid()) {
|
|
removeAttribute(restoreCollections.value(col));
|
|
return;
|
|
}
|
|
|
|
//Explicit target overrides the resource
|
|
if (mTargetCollection.isValid()) {
|
|
targetCollection = mTargetCollection;
|
|
}
|
|
|
|
//Try to fetch the target resource to see if it is available
|
|
CollectionFetchJob *fetchJob = new CollectionFetchJob(targetCollection, Akonadi::CollectionFetchJob::Base, q);
|
|
if (!mTargetCollection.isValid()) { //explicit targets don't have a fallback
|
|
fetchJob->setProperty("Resource", restoreResource);
|
|
}
|
|
fetchJob->setProperty("Items", col.id()); //to find the items in restore collections again
|
|
q->connect(fetchJob, SIGNAL(result(KJob*)), SLOT(targetCollectionFetched(KJob*)));
|
|
}
|
|
}
|
|
|
|
void TrashRestoreJob::TrashRestoreJobPrivate::collectionsReceived(const Akonadi::Collection::List &collections)
|
|
{
|
|
Q_Q(TrashRestoreJob);
|
|
if (collections.isEmpty()) {
|
|
q->setError(Job::Unknown);
|
|
q->setErrorText(i18n("Invalid collection passed"));
|
|
q->emitResult();
|
|
return;
|
|
}
|
|
Q_ASSERT(collections.size() == 1);
|
|
mCollection = collections.first();
|
|
|
|
if (!mCollection.hasAttribute<Akonadi::EntityDeletedAttribute>()) {
|
|
return;
|
|
}
|
|
|
|
const QString restoreResource = mCollection.attribute<Akonadi::EntityDeletedAttribute>()->restoreResource();
|
|
Collection targetCollection = mCollection.attribute<EntityDeletedAttribute>()->restoreCollection();
|
|
|
|
//Restore in place if no restore collection/resource is set
|
|
if (!targetCollection.isValid()) {
|
|
removeAttribute(Collection::List() << mCollection);
|
|
CollectionFetchJob *collectionFetchJob = new CollectionFetchJob(mCollection, CollectionFetchJob::Recursive, q);
|
|
q->connect(collectionFetchJob, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)));
|
|
q->connect(collectionFetchJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(removeAttribute(Akonadi::Collection::List)));
|
|
return;
|
|
}
|
|
|
|
//Explicit target overrides the resource/configured restore collection
|
|
if (mTargetCollection.isValid()) {
|
|
targetCollection = mTargetCollection;
|
|
}
|
|
|
|
//Fetch the target collection to check if it's valid
|
|
CollectionFetchJob *fetchJob = new CollectionFetchJob(targetCollection, CollectionFetchJob::Base, q);
|
|
if (!mTargetCollection.isValid()) { //explicit targets don't have a fallback
|
|
fetchJob->setProperty("Resource", restoreResource);
|
|
}
|
|
q->connect(fetchJob, SIGNAL(result(KJob*)), SLOT(targetCollectionFetched(KJob*)));
|
|
}
|
|
|
|
void TrashRestoreJob::TrashRestoreJobPrivate::removeAttribute(const Akonadi::Collection::List &list)
|
|
{
|
|
Q_Q(TrashRestoreJob);
|
|
QListIterator<Collection> i(list);
|
|
while (i.hasNext()) {
|
|
Collection col = i.next();
|
|
col.removeAttribute<EntityDeletedAttribute>();
|
|
|
|
CollectionModifyJob *job = new CollectionModifyJob(col, q);
|
|
q->connect(job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)));
|
|
|
|
ItemFetchJob *itemFetchJob = new ItemFetchJob(col, q);
|
|
itemFetchJob->fetchScope().fetchAttribute<EntityDeletedAttribute> (true);
|
|
q->connect(itemFetchJob, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)));
|
|
q->connect(itemFetchJob, SIGNAL(itemsReceived(Akonadi::Item::List)), SLOT(removeAttribute(Akonadi::Item::List)));
|
|
}
|
|
}
|
|
|
|
void TrashRestoreJob::TrashRestoreJobPrivate::removeAttribute(const Akonadi::Item::List &list)
|
|
{
|
|
Q_Q(TrashRestoreJob);
|
|
Item::List items = list;
|
|
QMutableListIterator<Item> i(items);
|
|
while (i.hasNext()) {
|
|
Item &item = i.next();
|
|
item.removeAttribute<EntityDeletedAttribute>();
|
|
ItemModifyJob *job = new ItemModifyJob(item, q);
|
|
job->setIgnorePayload(true);
|
|
q->connect(job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)));
|
|
}
|
|
//For some reason it is not possible to apply this change to multiple items at once
|
|
//ItemModifyJob *job = new ItemModifyJob(items, q);
|
|
//q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );
|
|
}
|
|
|
|
TrashRestoreJob::TrashRestoreJob(const Item &item, QObject *parent)
|
|
: Job(new TrashRestoreJobPrivate(this), parent)
|
|
{
|
|
Q_D(TrashRestoreJob);
|
|
d->mItems << item;
|
|
}
|
|
|
|
TrashRestoreJob::TrashRestoreJob(const Item::List &items, QObject *parent)
|
|
: Job(new TrashRestoreJobPrivate(this), parent)
|
|
{
|
|
Q_D(TrashRestoreJob);
|
|
d->mItems = items;
|
|
}
|
|
|
|
TrashRestoreJob::TrashRestoreJob(const Collection &collection, QObject *parent)
|
|
: Job(new TrashRestoreJobPrivate(this), parent)
|
|
{
|
|
Q_D(TrashRestoreJob);
|
|
d->mCollection = collection;
|
|
}
|
|
|
|
TrashRestoreJob::~TrashRestoreJob()
|
|
{
|
|
}
|
|
|
|
void TrashRestoreJob::setTargetCollection(const Akonadi::Collection collection)
|
|
{
|
|
Q_D(TrashRestoreJob);
|
|
d->mTargetCollection = collection;
|
|
}
|
|
|
|
Item::List TrashRestoreJob::items() const
|
|
{
|
|
Q_D(const TrashRestoreJob);
|
|
return d->mItems;
|
|
}
|
|
|
|
void TrashRestoreJob::doStart()
|
|
{
|
|
Q_D(TrashRestoreJob);
|
|
|
|
//We always have to fetch the entities to ensure that the EntityDeletedAttribute is available
|
|
if (!d->mItems.isEmpty()) {
|
|
ItemFetchJob *job = new ItemFetchJob(d->mItems, this);
|
|
job->fetchScope().setCacheOnly(true);
|
|
job->fetchScope().fetchAttribute<EntityDeletedAttribute> (true);
|
|
connect(job, SIGNAL(itemsReceived(Akonadi::Item::List)), this, SLOT(itemsReceived(Akonadi::Item::List)));
|
|
} else if (d->mCollection.isValid()) {
|
|
CollectionFetchJob *job = new CollectionFetchJob(d->mCollection, CollectionFetchJob::Base, this);
|
|
connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), this, SLOT(collectionsReceived(Akonadi::Collection::List)));
|
|
} else {
|
|
kWarning() << "No valid collection or empty itemlist";
|
|
setError(Job::Unknown);
|
|
setErrorText(i18n("No valid collection or empty itemlist"));
|
|
emitResult();
|
|
}
|
|
|
|
}
|
|
|
|
#include "moc_trashrestorejob.cpp"
|