blob: e8018b13072fd66324c467945cdb98a465ff2016 [file] [log] [blame]
/*
* Copyright (C) 2015 Savoir-Faire Linux Inc.
* Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
*
* 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 3 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.
*
* Additional permission under GNU GPL version 3 section 7:
*
* If you modify this program, or any covered work, by linking or
* combining it with the OpenSSL project's OpenSSL library (or a
* modified version of that library), containing parts covered by the
* terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
* grants you additional permission to convey the resulting work.
* Corresponding Source for a non-source form of such a combination
* shall include the source code for the parts of OpenSSL used as well
* as that of the covered work.
*/
#include "edscontactbackend.h"
#include <glib/gi18n.h>
#include <person.h>
#include <personmodel.h>
#include <contactmethod.h>
#include <collectioneditor.h>
#include <memory>
static void
client_cb(G_GNUC_UNUSED ESource *source, GAsyncResult *result, G_GNUC_UNUSED gpointer user_data)
{
GError *error = NULL;
EClient *client = e_book_client_connect_finish(result, &error);
if (!client) {
g_warning("%s", error->message);
g_clear_error(&error);
} else {
/* got a client for this addressbook, add as backend */
PersonModel::instance()->addCollection<EdsContactBackend, EClient *>(
client, LoadOptions::FORCE_ENABLED);
}
}
static void
registry_cb(G_GNUC_UNUSED GObject *source, GAsyncResult *result, GCancellable *cancellable)
{
GError *error = NULL;
ESourceRegistry *registry = e_source_registry_new_finish(result, &error);
if(!registry) {
g_critical("Unable to create EDS registry: %s", error->message);
g_clear_error(&error);
return;
} else {
GList *list = e_source_registry_list_enabled(registry, E_SOURCE_EXTENSION_ADDRESS_BOOK);
for (GList *l = list ; l; l = l->next) {
ESource *source = E_SOURCE(l->data);
/* try to connect to each source ansynch */
#if EDS_CHECK_VERSION(3,16,0)
e_book_client_connect(source,
EdsContactBackend::WAIT_FOR_CONNECTED_SECONDS,
cancellable,
(GAsyncReadyCallback)client_cb,
NULL);
#else
e_book_client_connect(source, cancellable, (GAsyncReadyCallback)client_cb, NULL);
#endif
}
g_list_free_full(list, g_object_unref);
}
}
void load_eds_sources(GCancellable *cancellable)
{
/* load the registery asynchronously, and then each source asynch as well
* pass the cancellable as a param so that the loading of each source can
* also be cancelled
*/
e_source_registry_new(cancellable, (GAsyncReadyCallback)registry_cb, cancellable);
}
class EdsContactEditor : public CollectionEditor<Person>
{
public:
EdsContactEditor(CollectionMediator<Person>* m, EdsContactBackend* parent);
~EdsContactEditor();
virtual bool save ( const Person* item ) override;
virtual bool remove ( const Person* item ) override;
virtual bool edit ( Person* item ) override;
virtual bool addNew ( Person* item ) override;
virtual bool addExisting( const Person* item ) override;
private:
virtual QVector<Person*> items() const override;
QVector<Person*> items_;
EdsContactBackend* collection_;
};
EdsContactEditor::EdsContactEditor(CollectionMediator<Person>* m, EdsContactBackend* parent) :
CollectionEditor<Person>(m),collection_(parent)
{
}
EdsContactEditor::~EdsContactEditor()
{
}
bool EdsContactEditor::save(const Person* item)
{
return collection_->savePerson(item);
}
bool EdsContactEditor::remove(const Person* item)
{
mediator()->removeItem(item);
return true;
}
bool EdsContactEditor::edit( Person* item)
{
Q_UNUSED(item)
return false;
}
bool EdsContactEditor::addNew(Person* item)
{
bool ret = collection_->addNewPerson(item);
if (ret) {
items_ << item;
mediator()->addItem(item);
}
return ret;
}
bool EdsContactEditor::addExisting(const Person* item)
{
items_ << const_cast<Person*>(item);
mediator()->addItem(item);
return true;
}
QVector<Person*> EdsContactEditor::items() const
{
return items_;
}
static void free_client_view(EBookClientView *client_view) {
g_return_if_fail(client_view);
GError *error = NULL;
e_book_client_view_stop(client_view, &error);
if (error) {
g_warning("error stopping EBookClientView: %s", error->message);
g_clear_error(&error);
}
g_object_unref(client_view);
}
static void
free_object_list(GSList *list)
{
g_slist_free_full(list, g_object_unref);
};
static void
free_string_list(GSList *list)
{
g_slist_free_full(list, g_free);
};
EdsContactBackend::EdsContactBackend(CollectionMediator<Person>* mediator, EClient *client, CollectionInterface* parent)
: CollectionInterface(new EdsContactEditor(mediator,this), parent)
, mediator_(mediator)
, client_(client, g_object_unref)
, cancellable_(g_cancellable_new(), g_object_unref)
, client_view_(nullptr, &free_client_view)
{
// cache the name
if (client_) {
auto source = e_client_get_source(client_.get());
auto extension = (ESourceAddressBook *)e_source_get_extension(source, E_SOURCE_EXTENSION_ADDRESS_BOOK);
auto backend = e_source_backend_get_backend_name(E_SOURCE_BACKEND(extension));
auto addressbook = e_source_get_display_name(source);
gchar *name = g_strdup_printf("%s (%s)", addressbook, backend);
name_ = name;
g_free(name);
} else
name_ = _("Unknown EDS addressbook");
}
EdsContactBackend::~EdsContactBackend()
{
/* cancel any cancellable operations */
g_cancellable_cancel(cancellable_.get());
/* cancel contact loading timeout source, if its not finished */
if (add_contacts_source_id != 0)
g_source_remove(add_contacts_source_id);
}
QString EdsContactBackend::name() const
{
return name_;
}
QString EdsContactBackend::category() const
{
return C_("Backend type", "Contacts");
}
bool EdsContactBackend::isEnabled() const
{
return true;
}
static void
contacts_added(G_GNUC_UNUSED EBookClientView *client_view, const GSList *objects, EdsContactBackend *self)
{
std::unique_ptr<GSList,void(*)(GSList *)> contacts(
g_slist_copy_deep((GSList *)objects, (GCopyFunc)g_object_ref, NULL ), &free_object_list);
self->addContacts(std::move(contacts));
}
static void
contacts_modified(EBookClientView *client_view, const GSList *objects, EdsContactBackend *self)
{
/* The parseContact function will check if the contacts we're "adding" have
* the same URI as any existing ones and if so will update those instead */
contacts_added(client_view, objects, self);
}
static void
contacts_removed(G_GNUC_UNUSED EBookClientView *client_view, const GSList *uids, EdsContactBackend *self)
{
std::unique_ptr<GSList,void(*)(GSList *)> contact_uids(
g_slist_copy_deep((GSList *)uids, (GCopyFunc)g_strdup, NULL ), &free_string_list);
self->removeContacts(std::move(contact_uids));
}
static void
client_view_cb(EBookClient *client, GAsyncResult *result, EdsContactBackend *self)
{
g_return_if_fail(E_IS_BOOK_CLIENT(client));
EBookClientView *client_view = NULL;
GError *error = NULL;
if(!e_book_client_get_view_finish(client, result, &client_view, &error)) {
g_critical("Unable to get client view: %s", error->message);
g_clear_error(&error);
return;
} else {
/* we want the EBookClientView to have the same life cycle as the backend */
std::unique_ptr<EBookClientView, void(*)(EBookClientView *)> client_view_ptr(
client_view, &free_client_view);
self->addClientView(std::move(client_view_ptr));
}
}
void EdsContactBackend::parseContact(EContact *contact)
{
/* Check if the photo is in-line or a URI, in the case that it is a URI,
* try to make it inline so that the lrc vcard parser is able to get the
* photo. Note that this will only work on local URIs
*/
EContactPhoto *photo = (EContactPhoto *)e_contact_get(contact, E_CONTACT_PHOTO);
if (photo) {
if (photo->type == E_CONTACT_PHOTO_TYPE_URI) {
GError *error = NULL;
if (!e_contact_inline_local_photos(contact, &error)) {
g_warning("could not inline photo from vcard URI: %s", error->message);
g_clear_error(&error);
}
}
}
e_contact_photo_free(photo);
EVCard *vcard = E_VCARD(contact);
gchar *vcard_str = e_vcard_to_string(vcard, EVC_FORMAT_VCARD_30);
/* check if this person already exists */
Person *existing = nullptr;
gchar *uid = (gchar *)e_contact_get(contact, E_CONTACT_UID);
if (uid) {
// g_warning("got uid: %s", uid);
existing = PersonModel::instance()->getPersonByUid(uid);
g_free(uid);
}
if (existing) {
/* update existing person */
existing->updateFromVCard(vcard_str);
} else {
Person *p = new Person(vcard_str, Person::Encoding::vCard, this);
editor<Person>()->addExisting(p);
}
g_free(vcard_str);
}
typedef struct AddContactsData_
{
EdsContactBackend* backend;
GSList *next;
std::unique_ptr<GSList, void(*)(GSList *)> contacts;
} AddContactsData;
void
free_add_contacts_data(AddContactsData *data)
{
g_return_if_fail(data && data->contacts);
data->contacts.reset();
g_free(data);
}
static gboolean
add_contacts(AddContactsData *data)
{
for (int i = 0; i < data->backend->CONTACT_ADD_LIMIT && data->next; i++) {
data->backend->parseContact(E_CONTACT(data->next->data));
data->next = data->next->next;
}
if (!data->next) {
data->backend->lastContactAdded();
return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE;
}
void EdsContactBackend::lastContactAdded()
{
/* Sets the source id to 0 to make sure we don't try to remove this source
* after it has already finished */
add_contacts_source_id = 0;
}
void EdsContactBackend::addClientView(std::unique_ptr<EBookClientView, void(*)(EBookClientView *)> client_view)
{
client_view_ = std::move(client_view);
/* connect signals for adding, removing, and modifying contacts */
g_signal_connect(client_view_.get(), "objects-added", G_CALLBACK(contacts_added), this);
g_signal_connect(client_view_.get(), "objects-modified", G_CALLBACK(contacts_modified), this);
g_signal_connect(client_view_.get(), "objects-removed", G_CALLBACK(contacts_removed), this);
/* start processing the signals */
GError *error = NULL;
e_book_client_view_start(client_view_.get(), &error);
if (error) {
g_critical("Unable to get start client view: %s", error->message);
g_clear_error(&error);
}
}
void EdsContactBackend::addContacts(std::unique_ptr<GSList, void(*)(GSList *)> contacts)
{
/* add CONTACT_ADD_LIMIT # of contacts every CONTACT_ADD_INTERVAL miliseconds */
AddContactsData *data = g_new0(AddContactsData, 1);
data->backend = this;
data->contacts = std::move(contacts);
data->next = data->contacts.get();
g_timeout_add_full(G_PRIORITY_DEFAULT,
CONTACT_ADD_INTERVAL,
(GSourceFunc)add_contacts,
data,
(GDestroyNotify)free_add_contacts_data);
}
void EdsContactBackend::removeContacts(std::unique_ptr<GSList, void(*)(GSList *)> contact_uids)
{
GSList *next = contact_uids.get();
while(next) {
gchar *uid = (gchar *)next->data;
if (uid) {
Person *p = PersonModel::instance()->getPersonByUid(uid);
if (p) {
g_debug("removing: %s", p->formattedName().toUtf8().constData());
deactivate(p);
editor<Person>()->remove(p);
} else {
g_warning("person with given UID doesn't exist: %s", uid);
}
} else {
g_warning("null UID in list");
}
next = g_slist_next(next);
}
}
bool EdsContactBackend::load()
{
/**
* load the contacts by querying for them,
* we want the contact to have some kind of name
*/
EBookQuery *queries[4];
int idx = 0;
queries[idx++] = e_book_query_field_exists(E_CONTACT_NAME_OR_ORG);
queries[idx++] = e_book_query_field_exists(E_CONTACT_GIVEN_NAME);
queries[idx++] = e_book_query_field_exists(E_CONTACT_FAMILY_NAME);
queries[idx++] = e_book_query_field_exists(E_CONTACT_NICKNAME);
EBookQuery *name_query = e_book_query_or(idx, queries, TRUE);
gchar *query_str = e_book_query_to_string(name_query);
e_book_query_unref(name_query);
/* test */
e_book_client_get_view(E_BOOK_CLIENT(client_.get()),
query_str,
cancellable_.get(),
(GAsyncReadyCallback)client_view_cb,
this);
g_free(query_str);
return true;
}
bool EdsContactBackend::reload()
{
return false;
}
FlagPack<CollectionInterface::SupportedFeatures> EdsContactBackend::supportedFeatures() const
{
return (CollectionInterface::SupportedFeatures::NONE |
CollectionInterface::SupportedFeatures::LOAD |
CollectionInterface::SupportedFeatures::ADD |
CollectionInterface::SupportedFeatures::SAVE );
}
bool EdsContactBackend::clear()
{
return false;
}
QByteArray EdsContactBackend::id() const
{
if (client_)
return e_source_get_uid(e_client_get_source(client_.get()));
return "edscb";
}
bool EdsContactBackend::addNewPerson(Person *item)
{
g_return_val_if_fail(client_.get(), false);
auto contact = e_contact_new_from_vcard(item->toVCard().constData());
gchar *uid = NULL;
GError *error = NULL;
/* FIXME: this methods returns True for a google addressbook, but it never
* actually adds the new contact... not clear if this is possible */
bool ret = e_book_client_add_contact_sync(
E_BOOK_CLIENT(client_.get()),
contact,
&uid,
cancellable_.get(),
&error
);
if (!ret) {
if (error) {
g_warning("could not add contact to collection: %s", error->message);
g_clear_error(&error);
} else {
g_warning("could not add contact to collection");
}
} else {
item->setUid(uid);
}
g_free(uid);
g_object_unref(contact);
return ret;
}
bool EdsContactBackend::savePerson(const Person *item)
{
g_return_val_if_fail(client_.get(), false);
g_debug("saving person");
auto contact = e_contact_new_from_vcard(item->toVCard().constData());
GError *error = NULL;
/* FIXME: this methods fails for a google addressbook, not clear if it is
* possible to edit a google address book... gnome contacts simply creates
* a local contact with the same uid */
bool ret = e_book_client_modify_contact_sync(
E_BOOK_CLIENT(client_.get()),
contact,
cancellable_.get(),
&error
);
if (!ret) {
if (error) {
g_warning("could not modify contact: %s", error->message);
g_clear_error(&error);
} else {
g_warning("could not modify contact");
}
}
g_object_unref(contact);
return ret;
}