diff --git a/korganizer/views/collectionview/controller.cpp b/korganizer/views/collectionview/controller.cpp index 251291fb6d..a7b03d6636 100644 --- a/korganizer/views/collectionview/controller.cpp +++ b/korganizer/views/collectionview/controller.cpp @@ -1,476 +1,482 @@ /* Copyright (C) 2014 Christian Mollekopf This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "controller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include CollectionNode::CollectionNode(ReparentingModel& personModel, const Akonadi::Collection& col) : Node(personModel), mCollection(col), mCheckState(Qt::Unchecked), isSearchNode(false) { } CollectionNode::~CollectionNode() { } bool CollectionNode::operator==(const ReparentingModel::Node &node) const { const CollectionNode *collectionNode = dynamic_cast(&node); if (collectionNode) { return (collectionNode->mCollection == mCollection); } return false; } QVariant CollectionNode::data(int role) const { if (role == Qt::DisplayRole) { QStringList path; Akonadi::Collection c = mCollection; while (c.isValid()) { path.prepend(c.name()); c = c.parentCollection(); } return path.join(QLatin1String("/")); } if (role == Qt::DecorationRole) { if (mCollection.hasAttribute()) { return mCollection.attribute()->icon(); } return QVariant(); } if (role == Qt::CheckStateRole) { if (isSearchNode) { return QVariant(); } return mCheckState; } if (role == Qt::ToolTipRole) { return QString(i18n("Collection: ") + mCollection.name() + QLatin1String(" ") + QString::number(mCollection.id())); } if (role == IsSearchResultRole) { return isSearchNode; } if (role == CollectionRole || role == Akonadi::EntityTreeModel::CollectionRole) { return QVariant::fromValue(mCollection); } if (role == NodeTypeRole) { return CollectionNodeRole; } return QVariant(); } bool CollectionNode::setData(const QVariant& value, int role) { if (role == Qt::CheckStateRole) { mCheckState = static_cast(value.toInt()); emitter.emitEnabled(mCheckState == Qt::Checked, mCollection); return true; } return false; } bool CollectionNode::isDuplicateOf(const QModelIndex& sourceIndex) { return (sourceIndex.data(Akonadi::EntityTreeModel::CollectionIdRole).value() == mCollection.id()); } PersonNode::PersonNode(ReparentingModel& personModel, const Person& person) : Node(personModel), mPerson(person), mCheckState(Qt::Unchecked), isSearchNode(false) { } PersonNode::~PersonNode() { } bool PersonNode::operator==(const Node &node) const { const PersonNode *personNode = dynamic_cast(&node); if (personNode) { return (personNode->mPerson.uid == mPerson.uid); } return false; } void PersonNode::setChecked(bool enabled) { if (enabled) { mCheckState = Qt::Checked; } else { mCheckState = Qt::Unchecked; } } QVariant PersonNode::data(int role) const { if (role == Qt::DisplayRole) { QString name = mPerson.name; + if (!mPerson.group.isEmpty()) { + name = mPerson.group + QLatin1String(" - ") + name; + } if (!mPerson.ou.isEmpty()) { name += QLatin1String(" (") + mPerson.ou + QLatin1String(")"); } return name; } if (role == Qt::DecorationRole) { return KIcon(QLatin1String("meeting-participant")); } if (role == Qt::CheckStateRole) { if (isSearchNode) { return QVariant(); } return mCheckState; } if (role == Qt::ToolTipRole) { QString tooltip = i18n("Person: ") + mPerson.name; + if (!mPerson.group.isEmpty()) { + tooltip += QLatin1String("\n") + i18n("Group: ") + mPerson.group; + } if (!mPerson.mail.isEmpty()) { tooltip += QLatin1String("\n") + i18n("Mail: ") + mPerson.mail; } if (!mPerson.ou.isEmpty()) { tooltip += QLatin1String("\n") + i18n("Organization Unit: ") + mPerson.ou; } return tooltip; } if (role == PersonRole) { return QVariant::fromValue(mPerson); } if (role == IsSearchResultRole) { return isSearchNode; } if (role == NodeTypeRole) { return PersonNodeRole; } if (role == CollectionRole || role == Akonadi::EntityTreeModel::CollectionRole) { return QVariant::fromValue(Akonadi::Collection(mPerson.rootCollection)); } return QVariant(); } bool PersonNode::setData(const QVariant& value, int role) { if (role == Qt::CheckStateRole) { mCheckState = static_cast(value.toInt()); emitter.emitEnabled(mCheckState == Qt::Checked, mPerson); return true; } return false; } bool PersonNode::adopts(const QModelIndex& sourceIndex) { const Akonadi::Collection &parent = sourceIndex.data(Akonadi::EntityTreeModel::ParentCollectionRole).value(); if (parent.id() == mPerson.rootCollection) { return true; } const Akonadi::Collection &col = sourceIndex.data(Akonadi::EntityTreeModel::CollectionRole).value(); // kDebug() << col.displayName(); //FIXME: we need a way to compare the path we get from LDAP to the folder in akonadi. //TODO: get it from the folder attribute if ((col.isValid() && mPerson.folderPaths.contains(col.displayName())) || mPerson.collections.contains(col.id())) { // kDebug() << "reparenting " << col.displayName() << " to " << mPerson.name; return true; } return false; } bool PersonNode::isDuplicateOf(const QModelIndex& sourceIndex) { return (sourceIndex.data(PersonRole).value().uid == mPerson.uid); } void PersonNode::update(const Node::Ptr &node) { mPerson = node.staticCast()->mPerson; } Person PersonNodeManager::person(const QModelIndex &sourceIndex) { Person person; const Akonadi::Collection col = sourceIndex.data(Akonadi::EntityTreeModel::CollectionRole).value(); if (col.isValid()) { CollectionIdentificationAttribute *attr = col.attribute(); if (attr && attr->collectionNamespace() == "usertoplevel") { person.name = col.displayName(); person.mail = QString::fromUtf8(attr->mail()); person.ou = QString::fromUtf8(attr->ou()); person.uid = col.name(); person.rootCollection = col.id(); } } return person; } void PersonNodeManager::checkSourceIndex(const QModelIndex &sourceIndex) { const Person &p = person(sourceIndex); if (p.rootCollection > -1) { model.addNode(ReparentingModel::Node::Ptr(new PersonNode(model, p))); } } void PersonNodeManager::updateSourceIndex(const QModelIndex &sourceIndex) { const Person &p = person(sourceIndex); if (p.rootCollection > -1) { model.updateNode(ReparentingModel::Node::Ptr(new PersonNode(model, p))); } } void PersonNodeManager::checkSourceIndexRemoval(const QModelIndex &sourceIndex) { const Person &p = person(sourceIndex); if (p.rootCollection > -1) { model.removeNode(PersonNode(model, p)); } } Controller::Controller(ReparentingModel* personModel, ReparentingModel* searchModel, QObject* parent) : QObject(parent), mPersonModel(personModel), mSearchModel(searchModel), mCollectionSearchJob(0), mPersonSearchJob(0) { Akonadi::AttributeFactory::registerAttribute(); } void Controller::setSearchString(const QString &searchString) { if (mCollectionSearchJob) { disconnect(mCollectionSearchJob, 0, this, 0); mCollectionSearchJob->kill(KJob::Quietly); mCollectionSearchJob = 0; } if (mPersonSearchJob) { disconnect(mPersonSearchJob, 0, this, 0); mPersonSearchJob->kill(KJob::Quietly); mPersonSearchJob = 0; } //TODO: Delay and abort when results are found mSearchModel->clear(); emit searchIsActive(!searchString.isEmpty()); const bool showAllPersonalFolders = (searchString == QLatin1String("*")); if (searchString.size() < 2 && !showAllPersonalFolders) { emit searching(false); return; } if (!showAllPersonalFolders) { emit searching(true); mPersonSearchJob = new PersonSearchJob(searchString, this); connect(mPersonSearchJob, SIGNAL(personsFound(QList)), this, SLOT(onPersonsFound(QList))); connect(mPersonSearchJob, SIGNAL(personUpdate(Person)), this, SLOT(onPersonUpdate(Person))); connect(mPersonSearchJob, SIGNAL(result(KJob*)), this, SLOT(onPersonsFound(KJob*))); mPersonSearchJob->start(); } mCollectionSearchJob = new CollectionSearchJob(searchString, QStringList() << QLatin1String("text/calendar") << KCalCore::Event::eventMimeType() << KCalCore::Todo::todoMimeType(), this); connect(mCollectionSearchJob, SIGNAL(result(KJob*)), this, SLOT(onCollectionsFound(KJob*))); mCollectionSearchJob->start(); } void Controller::onCollectionsFound(KJob* job) { mCollectionSearchJob = 0; if (!mPersonSearchJob) { emit searching(false); } if (job->error()) { kWarning() << job->errorString(); return; } Q_FOREACH(const Akonadi::Collection &col, static_cast(job)->matchingCollections()) { CollectionNode *collectionNode = new CollectionNode(*mSearchModel, col); collectionNode->isSearchNode = true; //toggled by the checkbox, results in collection getting monitored // connect(&collectionNode->emitter, SIGNAL(enabled(bool, Akonadi::Collection)), this, SLOT(onCollectionEnabled(bool, Akonadi::Collection))); mSearchModel->addNode(ReparentingModel::Node::Ptr(collectionNode)); } } void Controller::onPersonsFound(const QList &persons) { Q_FOREACH(const Person &p, persons) { PersonNode *personNode = new PersonNode(*mSearchModel, p); personNode->isSearchNode = true; //toggled by the checkbox, results in person getting added to main model // connect(&personNode->emitter, SIGNAL(enabled(bool, Person)), this, SLOT(onPersonEnabled(bool, Person))); mSearchModel->addNode(ReparentingModel::Node::Ptr(personNode)); } } void Controller::onPersonUpdate(const Person &person) { PersonNode *personNode = new PersonNode(*mSearchModel, person); personNode->isSearchNode = true; mSearchModel->updateNode(ReparentingModel::Node::Ptr(personNode)); } void Controller::onPersonsFound(KJob* job) { mPersonSearchJob = 0; if (!mCollectionSearchJob) { emit searching(false); } if (job->error()) { kWarning() << job->errorString(); return; } } static Akonadi::EntityTreeModel *findEtm(QAbstractItemModel *model) { QAbstractProxyModel *proxyModel; while (model) { proxyModel = qobject_cast(model); if (proxyModel && proxyModel->sourceModel()) { model = proxyModel->sourceModel(); } else { break; } } return qobject_cast(model); } void Controller::setCollectionState(const Akonadi::Collection &collection, CollectionState collectionState, bool recursive) { //We removed the children first, so the children in the tree are removed before the parents if (recursive) { //We have to include all mimetypes since mimetypes are not available yet (they will be synced once the collectoins are referenced) Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(collection, Akonadi::CollectionFetchJob::Recursive, this); fetchJob->setProperty("collectionState", static_cast(collectionState)); fetchJob->fetchScope().setListFilter(Akonadi::CollectionFetchScope::NoFilter); connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onPersonCollectionsFetched(KJob*))); } { Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(collection, Akonadi::CollectionFetchJob::Base, this); fetchJob->setProperty("collectionState", static_cast(collectionState)); fetchJob->fetchScope().setListFilter(Akonadi::CollectionFetchScope::NoFilter); connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onPersonCollectionsFetched(KJob*))); } } void Controller::onPersonCollectionsFetched(KJob* job) { if (job->error()) { kWarning() << "Failed to fetch collections " << job->errorString(); return; } Akonadi::EntityTreeModel *etm = findEtm(mPersonModel); if (!etm) { kWarning() << "Couldn't find etm"; return; } const CollectionState collectionState = static_cast(job->property("collectionState").toInt()); Q_FOREACH(const Akonadi::Collection &col, static_cast(job)->collections()) { // kDebug() << col.displayName() << "do enable " << enabled; Akonadi::Collection modifiedCollection = col; if (collectionState == Enabled) { modifiedCollection.setShouldList(Akonadi::Collection::ListDisplay, true); } if (collectionState == Disabled) { modifiedCollection.setShouldList(Akonadi::Collection::ListDisplay, false); } //HACK: We have no way of getting to the correct session as used by the etm, //and two concurrent jobs end up overwriting the enabled state of each other. etm->setCollectionReferenced(modifiedCollection, collectionState == Referenced); } } void Controller::addPerson(const Person &person) { kDebug() << person.uid << person.name << person.rootCollection; Person p = person; if (person.rootCollection == -1) { Baloo::PIM::CollectionQuery query; query.setNamespace(QStringList() << QLatin1String("usertoplevel")); query.pathMatches(QLatin1String("/Other Users/")+p.uid); query.setLimit(1); Baloo::PIM::ResultIterator it = query.exec(); Akonadi::Collection::List collections; while (it.next()) { collections << Akonadi::Collection(it.id()); } kDebug() << "Found collections " << collections.size() << "for" << p.name; if (collections.size() == 1) { kDebug() << "Set rootCollection=" << collections.at(0).id(); p.rootCollection = collections.at(0).id(); } } PersonNode *personNode = new PersonNode(*mPersonModel, p); personNode->setChecked(true); mPersonModel->addNode(ReparentingModel::Node::Ptr(personNode)); if (p.rootCollection > -1) { setCollectionState(Akonadi::Collection(p.rootCollection), Referenced, true); } else { kDebug() << "well this only a ldap search object without a collection"; //TODO: use freebusy data into calendar } } void Controller::removePerson(const Person &person) { kDebug() << person.uid << person.name << person.rootCollection; mPersonModel->removeNode(PersonNode(*mPersonModel, person)); if (person.rootCollection > -1) { setCollectionState(Akonadi::Collection(person.rootCollection), Disabled, true); } else { kDebug() << "well this only a ldap search object without a collection"; //TODO: delete freebusy data from calendar } } diff --git a/libkdepim/job/person.h b/libkdepim/job/person.h index e11729b92e..32316ac9da 100644 --- a/libkdepim/job/person.h +++ b/libkdepim/job/person.h @@ -1,48 +1,49 @@ /* Copyright (C) 2014 Christian Mollekopf This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef KORG_PERSON_H #define KORG_PERSON_H #include "kdepim_export.h" #include #include struct KDEPIM_EXPORT Person { Person(): rootCollection(-1), updateDisplayName(false) {}; QString name; QString uid; QString ou; QString mail; + QString group; Akonadi::Collection::Id rootCollection; bool updateDisplayName; //FIXME not sure we actually require those two QStringList folderPaths; QList collections; }; Q_DECLARE_METATYPE(Person); #endif diff --git a/libkdepim/job/personsearchjob.cpp b/libkdepim/job/personsearchjob.cpp index 39838a2785..82c8d0b065 100644 --- a/libkdepim/job/personsearchjob.cpp +++ b/libkdepim/job/personsearchjob.cpp @@ -1,253 +1,427 @@ /* Copyright (C) 2014 Christian Mollekopf This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "personsearchjob.h" +#include #include #include #include #include +#include +#include #include #include #include +class ExpandGroupJob : public QObject +{ + Q_OBJECT +public: + ExpandGroupJob(const KLDAP::LdapObject &groupObject, const KLDAP::LdapServer &server, const QStringList& attributes); + virtual ~ExpandGroupJob(); + + typedef QSharedPointer Ptr; + + virtual void start(); + const QString &groupName() const; + +Q_SIGNALS: + void groupMembers(const QString groupName, const QList &objects); + +private Q_SLOTS: + void groupResult(KLDAP::LdapSearch *ldapSearch, KLDAP::LdapObject object); + void onResult(KLDAP::LdapSearch *); + +private: + void searchNext(KLDAP::LdapSearch *ldapSearch); + KLDAP::LdapConnection mConnection; + KLDAP::LdapAttrValue mGroupMembersDN; + QString mGroupName; + QStringList mAttributes; + QList mResults; +}; + +ExpandGroupJob::ExpandGroupJob(const KLDAP::LdapObject &groupObject, const KLDAP::LdapServer &server, const QStringList &attributes) +: QObject() +, mConnection(server) +, mAttributes(attributes) +, mGroupMembersDN(groupObject.values(QLatin1String("uniqueMember"))) +, mGroupName(QString::fromUtf8(groupObject.value(QLatin1String("cn")))) +{ +} + +ExpandGroupJob::~ExpandGroupJob() +{ + mConnection.close(); +} + +void ExpandGroupJob::start() +{ + mConnection.connect(); + + KLDAP::LdapSearch *search(new KLDAP::LdapSearch(mConnection)); + connect(search, SIGNAL(data(KLDAP::LdapSearch*,KLDAP::LdapObject)), SLOT(groupResult(KLDAP::LdapSearch*,KLDAP::LdapObject))); + connect(search, SIGNAL(result(KLDAP::LdapSearch*)), SLOT(onResult(KLDAP::LdapSearch*))); + searchNext(search); +} + +const QString &ExpandGroupJob::groupName() const +{ + return mGroupName; +} + +void ExpandGroupJob::groupResult(KLDAP::LdapSearch *ldapSearch, KLDAP::LdapObject object) +{ + mResults << object; +} + +void ExpandGroupJob::onResult(KLDAP::LdapSearch *ldapSearch) +{ + if (mGroupMembersDN.isEmpty()) { + emit groupMembers(groupName(), mResults); + } else { + searchNext(ldapSearch); + } +} + +void ExpandGroupJob::searchNext(KLDAP::LdapSearch *ldapSearch) +{ + ldapSearch->search(KLDAP::LdapDN(QString::fromLatin1(mGroupMembersDN.takeFirst())), KLDAP::LdapUrl::Base, QString(), mAttributes); +} + PersonSearchJob::PersonSearchJob(const QString& searchString, QObject* parent) : KJob(parent), mSearchString(searchString) { + QStringList attrs = mLdapSearch.attributes(); + attrs.append(QLatin1String("uniqueMember")); + mLdapSearch.setAttributes(attrs); connect(&mLdapSearch, SIGNAL(searchData(const QList &)), SLOT(onLDAPSearchData(const QList &))); connect(&mLdapSearch, SIGNAL(searchDone()), SLOT(onLDAPSearchDone())); } PersonSearchJob::~PersonSearchJob() { mLdapSearch.cancelSearch(); } bool PersonSearchJob::kill(KJob::KillVerbosity verbosity) { + Q_FOREACH(ExpandGroupJob::Ptr job, mExpandGroupJobs) { + disconnect(job.data()); + } + mExpandGroupJobs.clear(); + mLdapSearch.cancelSearch(); return KJob::kill(verbosity); } void PersonSearchJob::start() { Baloo::PIM::CollectionQuery query; query.setNamespace(QStringList() << QLatin1String("usertoplevel")); query.nameMatches(mSearchString); query.setLimit(200); Baloo::PIM::ResultIterator it = query.exec(); Akonadi::Collection::List collections; while (it.next()) { collections << Akonadi::Collection(it.id()); } kDebug() << "Found persons " << collections.size(); mCollectionSearchDone = false; mLdapSearchDone = false; if (collections.isEmpty()) { //We didn't find anything mCollectionSearchDone = true; } + Q_FOREACH(ExpandGroupJob::Ptr job, mExpandGroupJobs) { + disconnect(job.data()); + } + + mExpandGroupJobs.clear(); + mLdapSearch.startSearch(QLatin1String("*") + mSearchString); if (!collections.isEmpty()) { Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(collections, Akonadi::CollectionFetchJob::Base, this); fetchJob->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); fetchJob->fetchScope().setListFilter(Akonadi::CollectionFetchScope::NoFilter); connect(fetchJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), this, SLOT(onCollectionsReceived(Akonadi::Collection::List))); connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(onCollectionsFetched(KJob*))); } //The IMAP resource should add a "Person" attribute to the collections in the person namespace, //the ldap query can then be used to update the name (entitydisplayattribute) for the person. } +bool PersonSearchJob::isGroup(KLDAP::LdapObject object) const +{ + const KLDAP::LdapAttrValue objectClasses = object.values(QLatin1String("objectClass")); + if (objectClasses.contains(QLatin1String("groupofuniquenames").latin1()) + || objectClasses.contains(QLatin1String("kolabgroupofuniquenames").latin1())) { + return true; + } + + return false; +} + +void PersonSearchJob::onGroupFound(QString name, QList< KLDAP::LdapObject > members) +{ + Q_ASSERT(mExpandGroupJobs.contains(name)); + ExpandGroupJob::Ptr job = mExpandGroupJobs.take(name); + Q_ASSERT(job); + + QList persons; + Q_FOREACH(const KLDAP::LdapObject &item, members) { + Person person; + person.group = name; + person.name = QString::fromUtf8(item.value(QLatin1String("cn"))); + person.mail = QString::fromUtf8(item.value(QLatin1String("mail"))); + + const int depth = item.dn().depth(); + for ( int i = 0; i < depth; ++i ) { + const QString rdnStr = item.dn().rdnString(i); + if ( rdnStr.startsWith(QLatin1String("ou="), Qt::CaseInsensitive) ) { + person.ou = rdnStr.mid(3); + break; + } + } + const QStringList &parts = person.mail.split(QLatin1Char('@')); + if (parts.count() == 2) { + const QString &uid = parts.at(0); + person.uid = uid; + if (mMatches.contains(uid)) { + const Person &p = mMatches.value(uid); + if (p.mail != person.mail ) { + if (p.rootCollection > -1) { + person.rootCollection = p.rootCollection; + person.updateDisplayName = p.updateDisplayName; + updatePersonCollection(person); + mMatches.insert(uid, person); + } else { + kWarning() << "That should not happen: we found two times persons with the same uid ("<< uid << "), but differnet name:" << p.name << "vs" << person.name; + } + } + } else { //New person found + mMatches.insert(uid, person); + persons << person; + } + } else { + kWarning() << item.dn().toString() << ": invalid email address" << person.mail; + } + } + + if (persons.count() > 0) { + emit personsFound(persons); + } + + onLDAPSearchDone(); +} + void PersonSearchJob::onLDAPSearchData(const QList< KLDAP::LdapResultObject > &list) { QList persons; Q_FOREACH(const KLDAP::LdapResultObject &item, list) { + if (isGroup(item.object)) { + ExpandGroupJob::Ptr job(new ExpandGroupJob(item.object, item.client->server(), mLdapSearch.attributes())); + connect(job.data(), SIGNAL(groupMembers(QString,QList)), SLOT(onGroupFound(QString,QList))); + + if (mExpandGroupJobs.contains(job->groupName())) { + ExpandGroupJob::Ptr j2 = mExpandGroupJobs.take(job->groupName()); + disconnect(j2.data()); + j2->deleteLater(); + } + mExpandGroupJobs.insert(job->groupName(), job); + job->start(); + continue; + } Person person; person.name = QString::fromUtf8(item.object.value(QLatin1String("cn"))); person.mail = QString::fromUtf8(item.object.value(QLatin1String("mail"))); const int depth = item.object.dn().depth(); for ( int i = 0; i < depth; ++i ) { const QString rdnStr = item.object.dn().rdnString(i); if ( rdnStr.startsWith(QLatin1String("ou="), Qt::CaseInsensitive) ) { person.ou = rdnStr.mid(3); break; } } const QStringList &parts = person.mail.split(QLatin1Char('@')); if (parts.count() == 2) { const QString &uid = parts.at(0); person.uid = uid; if (mMatches.contains(uid)) { const Person &p = mMatches.value(uid); if (p.mail != person.mail ) { if (p.rootCollection > -1) { person.rootCollection = p.rootCollection; person.updateDisplayName = p.updateDisplayName; updatePersonCollection(person); mMatches.insert(uid, person); } else { kWarning() << "That should not happen: we found two times persons with the same uid ("<< uid << "), but differnet name:" << p.name << "vs" << person.name; } } } else { //New person found mMatches.insert(uid, person); persons << person; } } else { kWarning() << item.object.dn().toString() << ": invalid email address" << person.mail; } } if (persons.count() > 0) { emit personsFound(persons); } } void PersonSearchJob::onLDAPSearchDone() { + if (mExpandGroupJobs.count() > 0) { + return; + } mLdapSearchDone = true; if (mCollectionSearchDone) { emitResult(); } } void PersonSearchJob::onCollectionsReceived(const Akonadi::Collection::List &list) { QList persons; Q_FOREACH(const Akonadi::Collection &col, list) { Person person; const QString &uid = col.name(); const CollectionIdentificationAttribute *const attr = col.attribute(); const Akonadi::EntityDisplayAttribute *const displayname = col.attribute(); person.rootCollection = col.id(); person.uid = uid; if (attr) { person.ou = QString::fromUtf8(attr->ou()); person.mail = QString::fromUtf8(attr->mail()); person.name = QString::fromUtf8(attr->identifier()); if (!displayname || displayname->displayName().isEmpty() || displayname->displayName() == person.name) { person.updateDisplayName = true; } } else { person.name = col.displayName(); if (!displayname || displayname->displayName().isEmpty()) { person.updateDisplayName = true; } } if (mMatches.contains(uid)) { Person p = mMatches.value(uid); if (p.rootCollection > -1) { //two collection with the same uid ?! kWarning() << "Two collections match to same person" << p.rootCollection << person.rootCollection; } else if (p.mail != person.mail) { p.rootCollection = person.rootCollection; p.updateDisplayName = person.updateDisplayName; updatePersonCollection(p); } else { mMatches.insert(uid, person); emit personUpdate(person); } } else { mMatches.insert(uid, person); persons << person; } } if (persons.count() > 0) { emit personsFound(persons); } } void PersonSearchJob::updatePersonCollection(const Person &person) { Akonadi::Collection c(person.rootCollection); CollectionIdentificationAttribute *identification = c.attribute(Akonadi::Entity::AddIfMissing); if (person.updateDisplayName) { Akonadi::EntityDisplayAttribute *displayname = c.attribute(Akonadi::Entity::AddIfMissing); displayname->setDisplayName(person.name); } //identification->setIdentifier("Other Users/" + person.uid); identification->setIdentifier(person.name.toUtf8()); identification->setName(person.name.toUtf8()); identification->setCollectionNamespace("usertoplevel"); identification->setMail(person.mail.toUtf8()); identification->setOu(person.ou.toUtf8()); Akonadi::CollectionModifyJob *job = new Akonadi::CollectionModifyJob( c, this ); connect(job, SIGNAL(result(KJob*)), this, SLOT(modifyResult(KJob*))); } void PersonSearchJob::onCollectionsFetched(KJob *job) { if (job->error()) { kWarning() << job->errorString(); } mCollectionSearchDone = true; if (mLdapSearchDone) { emitResult(); } } QList PersonSearchJob::matches() const { return mMatches.values(); } void PersonSearchJob::modifyResult(KJob *job) { if (job->error()) { kWarning() << job->errorString(); return; } const Akonadi::CollectionModifyJob *modifyJob = static_cast(job); const Akonadi::Collection &col = modifyJob->collection(); const CollectionIdentificationAttribute *const attr = col.attribute(); const Akonadi::EntityDisplayAttribute *const displayname = col.attribute(); const QString &uid = col.name(); Person &person = mMatches[col.name()]; person.rootCollection = col.id(); person.uid = uid; if (attr) { person.ou = QString::fromUtf8(attr->ou()); person.mail = QString::fromUtf8(attr->mail()); person.name = QString::fromUtf8(attr->identifier()); if (!displayname || displayname->displayName().isEmpty() || displayname->displayName() == person.name) { person.updateDisplayName = true; } } kDebug() << "modified person to" << person.uid << person.name << person.rootCollection; mMatches.insert(person.uid, person); emit personUpdate(person); } +#include "personsearchjob.moc" \ No newline at end of file diff --git a/libkdepim/job/personsearchjob.h b/libkdepim/job/personsearchjob.h index 8924e83745..170e2e9ddc 100644 --- a/libkdepim/job/personsearchjob.h +++ b/libkdepim/job/personsearchjob.h @@ -1,67 +1,77 @@ /* Copyright (C) 2014 Christian Mollekopf This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef KORG_PERSONSEARCHJOB_H #define KORG_PERSONSEARCHJOB_H #include "kdepim_export.h" #include #include #include #include "person.h" +#include + +class ExpandGroupJob; +typedef QSharedPointer ExpandGroupJobPtr; + class KDEPIM_EXPORT PersonSearchJob : public KJob { Q_OBJECT public: explicit PersonSearchJob(const QString &searchString, QObject* parent = 0); virtual ~PersonSearchJob(); virtual void start(); QList matches() const; Q_SIGNALS: void personsFound(const QList &persons); void personUpdate(const Person &person); public Q_SLOTS: bool kill(KillVerbosity verbosity=Quietly); private Q_SLOTS: void onCollectionsReceived(const Akonadi::Collection::List &); void onCollectionsFetched(KJob *); void onLDAPSearchData(const QList &); void onLDAPSearchDone(); void updatePersonCollection(const Person &person); void modifyResult(KJob *job); + void onGroupFound(QString name, QList members); + +private: + bool isGroup(KLDAP::LdapObject object) const; private: QString mSearchString; QHash mMatches; KLDAP::LdapClientSearch mLdapSearch; bool mCollectionSearchDone; bool mLdapSearchDone; + QHash mExpandGroupJobs; }; #endif