Commit 9c7b5f8a authored by Sandro Knauß's avatar Sandro Knauß
Browse files

Implement ldap search in calendar selection

KOLAB: #3543
(cherry picked from commit bf819e0eb26fba226b79fbcb0f38a0f977b4210c)
parent a509f859
......@@ -581,6 +581,16 @@ AkonadiCollectionView::AkonadiCollectionView( CalendarView *view, bool hasContex
topLayout->addWidget( mStackedWidget );
KMessageWidget *msgWidget = new KMessageWidget(this);
msgWidget->setCloseButtonVisible(false);
msgWidget->setMessageType(KMessageWidget::Positive);
msgWidget->setObjectName(QLatin1String("msgwidget"));
msgWidget->setVisible(false);
msgWidget->setText(i18n("searching..."));
connect(mController, SIGNAL(searching(bool)),
msgWidget, SLOT(setVisible(bool)));
topLayout->addWidget(msgWidget);
connect( mBaseModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(rowsInserted(QModelIndex,int,int)) );
......
......@@ -28,6 +28,7 @@
#include <Akonadi/CollectionFetchScope>
#include <Akonadi/AttributeFactory>
#include <KIcon>
#include <KLocale>
#include <KCalCore/Event>
#include <KCalCore/Journal>
#include <KCalCore/Todo>
......@@ -80,7 +81,7 @@ QVariant CollectionNode::data(int role) const
return mCheckState;
}
if (role == Qt::ToolTipRole) {
return QString(QLatin1String("Collection: ") + mCollection.name() + QString::number(mCollection.id()));
return QString(i18n("Collection: ") + mCollection.name() + QLatin1String(" ") + QString::number(mCollection.id()));
}
if (role == IsSearchResultRole) {
return isSearchNode;
......@@ -128,7 +129,7 @@ bool PersonNode::operator==(const Node &node) const
{
const PersonNode *personNode = dynamic_cast<const PersonNode*>(&node);
if (personNode) {
return (personNode->mPerson.name == mPerson.name);
return (personNode->mPerson.uid == mPerson.uid);
}
return false;
}
......@@ -145,7 +146,11 @@ void PersonNode::setChecked(bool enabled)
QVariant PersonNode::data(int role) const
{
if (role == Qt::DisplayRole) {
return mPerson.name;
QString name = mPerson.name;
if (!mPerson.ou.isEmpty()) {
name += QLatin1String(" (") + mPerson.ou + QLatin1String(")");
}
return name;
}
if (role == Qt::DecorationRole) {
return KIcon(QLatin1String("meeting-participant"));
......@@ -157,7 +162,14 @@ QVariant PersonNode::data(int role) const
return mCheckState;
}
if (role == Qt::ToolTipRole) {
return QString(QLatin1String("Person: ") + mPerson.name);
QString tooltip = i18n("Person: ") + mPerson.name;
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);
......@@ -214,6 +226,9 @@ void PersonNodeManager::checkSourceIndex(const QModelIndex &sourceIndex)
kDebug() << "Found user folder, creating person node " << col.displayName();
Person person;
person.name = col.displayName();
person.mail = QString::fromUtf8(attr->mail());
person.ou = QString::fromUtf8(attr->ou());
person.uid = col.name();
person.rootCollection = col.id();
model.addNode(ReparentingModel::Node::Ptr(new PersonNode(model, person)));
......@@ -231,6 +246,9 @@ void PersonNodeManager::checkSourceIndexRemoval(const QModelIndex &sourceIndex)
kDebug() << "Found user folder, removing person node " << col.displayName();
Person person;
person.name = col.displayName();
person.mail = QString::fromUtf8(attr->mail());
person.ou = QString::fromUtf8(attr->ou());
person.uid = col.name();
person.rootCollection = col.id();
model.removeNode(PersonNode(model, person));
}
......@@ -312,6 +330,7 @@ static Akonadi::Collection replaceParent(Akonadi::Collection col, const Akonadi:
Q_FOREACH (const Akonadi::Collection &c, ancestors) {
if (col == c) {
col = c;
break;
}
}
col.setParentCollection(parent);
......@@ -343,6 +362,22 @@ PersonSearchJob::PersonSearchJob(const QString& searchString, QObject* parent)
: KJob(parent),
mSearchString(searchString)
{
connect(&mLdapSearch, SIGNAL(searchData(const QList<KLDAP::LdapResultObject> &)),
SLOT(onLDAPSearchData(const QList<KLDAP::LdapResultObject> &)));
connect(&mLdapSearch, SIGNAL(searchDone()),
SLOT(onLDAPSearchDone()));
}
PersonSearchJob::~PersonSearchJob()
{
mLdapSearch.cancelSearch();
}
bool PersonSearchJob::kill(KJob::KillVerbosity verbosity)
{
mLdapSearch.cancelSearch();
return KJob::kill(verbosity);
}
void PersonSearchJob::start()
......@@ -358,9 +393,14 @@ void PersonSearchJob::start()
}
kDebug() << "Found persons " << collections.size();
mCollectionSearchDone = false;
mLdapSearchDone = false;
mLdapSearch.startSearch(QLatin1String("*") + mSearchString);
if (collections.isEmpty()) {
//We didn't find anything
emitResult();
mCollectionSearchDone = true;
return;
}
......@@ -369,34 +409,176 @@ void PersonSearchJob::start()
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*)));
//TODO query ldap for available persons and their folders.
//TODO identify imap folders as person folders and list them here (after indexing them in baloo).
//
//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.
}
void PersonSearchJob::onLDAPSearchData(const QList< KLDAP::LdapResultObject > &list)
{
QList<Person> persons;
Q_FOREACH(const KLDAP::LdapResultObject &item, list) {
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()
{
mLdapSearchDone = true;
if (mCollectionSearchDone) {
emitResult();
}
}
void PersonSearchJob::onCollectionsReceived(const Akonadi::Collection::List &list)
{
QList<Person> persons;
Q_FOREACH(const Akonadi::Collection &col, list) {
Person person;
person.name = col.displayName();
const QString &uid = col.name();
const CollectionIdentificationAttribute *const attr = col.attribute<CollectionIdentificationAttribute>();
const Akonadi::EntityDisplayAttribute *const displayname = col.attribute<Akonadi::EntityDisplayAttribute>();
person.rootCollection = col.id();
mMatches << person;
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<CollectionIdentificationAttribute>(Akonadi::Entity::AddIfMissing);
if (person.updateDisplayName) {
Akonadi::EntityDisplayAttribute *displayname = c.attribute<Akonadi::EntityDisplayAttribute >(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();
}
emitResult();
mCollectionSearchDone = true;
if (mLdapSearchDone) {
emitResult();
}
}
QList<Person> PersonSearchJob::matches() const
{
return mMatches;
return mMatches.values();
}
void PersonSearchJob::modifyResult(KJob *job)
{
if (job->error()) {
kWarning() << job->errorString();
return;
}
const Akonadi::CollectionModifyJob *modifyJob = static_cast<Akonadi::CollectionModifyJob*>(job);
const Akonadi::Collection &col = modifyJob->collection();
const CollectionIdentificationAttribute *const attr = col.attribute<CollectionIdentificationAttribute>();
const Akonadi::EntityDisplayAttribute *const displayname = col.attribute<Akonadi::EntityDisplayAttribute>();
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);
}
......@@ -426,10 +608,15 @@ void Controller::setSearchString(const QString &searchString)
mSearchModel->clear();
emit searchIsActive(!searchString.isEmpty());
if (searchString.size() < 2) {
emit searching(false);
return;
}
emit searching(true);
mPersonSearchJob = new PersonSearchJob(searchString, this);
connect(mPersonSearchJob, SIGNAL(personsFound(QList<Person>)), this, SLOT(onPersonsFound(QList<Person>)));
connect(mPersonSearchJob, SIGNAL(personUpdate(Person)), this, SLOT(onPersonUpdate(Person)));
connect(mPersonSearchJob, SIGNAL(result(KJob*)), this, SLOT(onPersonsFound(KJob*)));
mPersonSearchJob->start();
......@@ -440,6 +627,9 @@ void Controller::setSearchString(const QString &searchString)
void Controller::onCollectionsFound(KJob* job)
{
if (!mPersonSearchJob) {
emit searching(false);
}
if (job->error()) {
kWarning() << job->errorString();
mCollectionSearchJob = 0;
......@@ -456,21 +646,34 @@ void Controller::onCollectionsFound(KJob* job)
mCollectionSearchJob = 0;
}
void Controller::onPersonsFound(KJob* job)
void Controller::onPersonsFound(const QList<Person> &persons)
{
if (job->error()) {
kWarning() << job->errorString();
mPersonSearchJob = 0;
return;
}
Q_ASSERT(mPersonSearchJob == static_cast<PersonSearchJob*>(job));
Q_FOREACH(const Person &p, mPersonSearchJob->matches()) {
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)
{
if (!mCollectionSearchJob) {
emit searching(false);
}
if (job->error()) {
kWarning() << job->errorString();
mPersonSearchJob = 0;
return;
}
mPersonSearchJob = 0;
}
......@@ -536,19 +739,44 @@ void Controller::onPersonCollectionsFetched(KJob* job)
void Controller::addPerson(const Person &person)
{
kDebug() << person.name;
kDebug() << person.uid << person.name << person.rootCollection;
if (person.rootCollection == -1) {
Baloo::PIM::CollectionQuery query;
query.setNamespace(QStringList() << QLatin1String("usertoplevel"));
query.pathMatches(QLatin1String("/Other Users/")+person.uid);
query.setLimit(200);
Baloo::PIM::ResultIterator it = query.exec();
Akonadi::Collection::List collections;
while (it.next()) {
collections << Akonadi::Collection(it.id());
}
kDebug() << "Found collections " << collections.size() << "for" << person.name;
//TODO: use the found collection and update attribute
}
PersonNode *personNode = new PersonNode(*mPersonModel, person);
personNode->setChecked(true);
mPersonModel->addNode(ReparentingModel::Node::Ptr(personNode));
setCollectionState(Akonadi::Collection(person.rootCollection), Referenced, true);
if (person.rootCollection > -1) {
setCollectionState(Akonadi::Collection(person.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.name;
kDebug() << person.uid << person.name << person.rootCollection;
mPersonModel->removeNode(PersonNode(*mPersonModel, person));
setCollectionState(Akonadi::Collection(person.rootCollection), Disabled, true);
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
}
}
......@@ -29,6 +29,8 @@
#include <Akonadi/Collection>
#include "reparentingmodel.h"
#include <libkdepim/ldap/ldapclientsearch.h>
enum DataRoles {
PersonRole = Akonadi::EntityTreeModel::UserRole + 1,
IsSearchResultRole,
......@@ -45,8 +47,12 @@ enum NodeTypeRoles {
struct Person
{
Person(): rootCollection(-1){};
Person(): rootCollection(-1), updateDisplayName(false) {};
QString name;
QString uid;
QString ou;
QString mail;
bool updateDisplayName;
Akonadi::Collection::Id rootCollection;
//FIXME not sure we actually require those two
......@@ -156,18 +162,33 @@ class PersonSearchJob : public KJob
Q_OBJECT
public:
explicit PersonSearchJob(const QString &searchString, QObject* parent = 0);
virtual ~PersonSearchJob();
virtual void start();
QList<Person> matches() const;
Q_SIGNALS:
void personsFound(const QList<Person> &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<KLDAP::LdapResultObject> &);
void onLDAPSearchDone();
void updatePersonCollection(const Person &person);
void modifyResult(KJob *job);
private:
QString mSearchString;
QList<Person> mMatches;
QHash<QString, Person> mMatches;
KLDAP::LdapClientSearch mLdapSearch;
bool mCollectionSearchDone;
bool mLdapSearchDone;
};
/**
......@@ -195,12 +216,15 @@ public:
Q_SIGNALS:
void searchIsActive(bool);
void searching(bool);
public Q_SLOTS:
void setSearchString(const QString &);
private Q_SLOTS:
void onCollectionsFound(KJob *job);
void onPersonsFound(const QList<Person> &persons);
void onPersonUpdate(const Person &person);
void onPersonsFound(KJob *job);
void onPersonCollectionsFetched(KJob *job);
......
......@@ -274,6 +274,23 @@ void ReparentingModel::doAddNode(const Node::Ptr &node)
}
}
void ReparentingModel::updateNode(const ReparentingModel::Node::Ptr &node)
{
Q_FOREACH(const ReparentingModel::Node::Ptr &existing, mProxyNodes) {
if (*existing == *node) {
node->parent = existing->parent;
int r = row(existing.data());
existing->parent->children.replace(r, node);
const QModelIndex i = index(node.data());
Q_ASSERT(i.row() == r);
emit dataChanged(i, i);
return;
}
}
kWarning() << "no node to update";
}
void ReparentingModel::removeNode(const ReparentingModel::Node& node)
{
//If there is an addNode in progress for that node, abort it.
......@@ -761,21 +778,30 @@ Qt::ItemFlags ReparentingModel::flags(const QModelIndex& index) const
return sourceModel()->flags(mapToSource(index));
}
QModelIndex ReparentingModel::index(Node *node) const
int ReparentingModel::row(ReparentingModel::Node *node) const
{
Q_ASSERT(node);
if (node == &mRootNode) {
return QModelIndex();
return -1;
}
Q_ASSERT(validateNode(node));
int row = 0;
Q_FOREACH(const Node::Ptr &c, node->parent->children) {
if (c.data() == node) {
break;
return row;
}
row++;
}
return createIndex(row, 0, node);
return -1;
}
QModelIndex ReparentingModel::index(Node *node) const
{
const int r = row(node);
if (r < 0) {
return QModelIndex();
}
return createIndex(r, 0, node);
}
QModelIndex ReparentingModel::parent(const QModelIndex& child) const
......
......@@ -87,6 +87,7 @@ public:
void setNodeManager(const NodeManager::Ptr &nodeManager);
void addNode(const Node::Ptr &node);
void updateNode(const Node::Ptr &node);
void removeNode(const Node &node);
void setNodes(const QList<Node::Ptr> &nodes);
void clear();
......@@ -126,6 +127,7 @@ private:
void reparentSourceNodes(const Node::Ptr &proxyNode);
void rebuildAll();
QModelIndex index(Node *node) const;
int row(Node *node) const;
Node *getReparentNode(const QModelIndex &sourceIndex);
Node *getParentNode(const QModelIndex &sourceIndex);
bool validateNode(const Node *node) const;
......
......@@ -30,9 +30,10 @@
class DummyNode : public ReparentingModel::Node
{
public:
DummyNode(ReparentingModel &personModel, const QString &name)
DummyNode(ReparentingModel &personModel, const QString &name, const QString &data=QString())
: ReparentingModel::Node(personModel),
mName(name)
mName(name),
mData(data)
{}
virtual ~DummyNode(){};
......@@ -49,10 +50,14 @@ private:
virtual QVariant data(int role) const {