Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up| /* | |
| * | |
| * oFono - Open Source Telephony | |
| * | |
| * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License version 2 as | |
| * published by the Free Software Foundation. | |
| * | |
| * 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 St, Fifth Floor, Boston, MA 02110-1301 USA | |
| * | |
| */ | |
| #ifdef HAVE_CONFIG_H | |
| #include <config.h> | |
| #endif | |
| #include <string.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <ctype.h> | |
| #include <errno.h> | |
| #include <glib.h> | |
| #include <gdbus.h> | |
| #include "ofono.h" | |
| #include "common.h" | |
| #define LEN_MAX 128 | |
| #define TYPE_INTERNATIONAL 145 | |
| #define PHONEBOOK_FLAG_CACHED 0x1 | |
| static GSList *g_drivers = NULL; | |
| enum phonebook_number_type { | |
| TEL_TYPE_HOME, | |
| TEL_TYPE_MOBILE, | |
| TEL_TYPE_FAX, | |
| TEL_TYPE_WORK, | |
| TEL_TYPE_OTHER, | |
| }; | |
| struct ofono_phonebook { | |
| DBusMessage *pending; | |
| int storage_index; /* go through all supported storage */ | |
| int flags; | |
| GString *vcards; /* entries with vcard 3.0 format */ | |
| GSList *merge_list; /* cache the entries that may need a merge */ | |
| const struct ofono_phonebook_driver *driver; | |
| void *driver_data; | |
| struct ofono_atom *atom; | |
| }; | |
| struct phonebook_number { | |
| char *number; | |
| int type; | |
| enum phonebook_number_type category; | |
| }; | |
| struct phonebook_person { | |
| GSList *number_list; /* one person may have more than one numbers */ | |
| char *text; | |
| int hidden; | |
| char *group; | |
| char *email; | |
| char *sip_uri; | |
| }; | |
| static const char *storage_support[] = { "SM", "ME", NULL }; | |
| static void export_phonebook(struct ofono_phonebook *pb); | |
| /* according to RFC 2425, the output string may need folding */ | |
| static void vcard_printf(GString *str, const char *fmt, ...) | |
| { | |
| char buf[1024]; | |
| va_list ap; | |
| int len_temp, line_number, i; | |
| unsigned int line_delimit = 75; | |
| va_start(ap, fmt); | |
| vsnprintf(buf, sizeof(buf), fmt, ap); | |
| va_end(ap); | |
| line_number = strlen(buf) / line_delimit + 1; | |
| for (i = 0; i < line_number; i++) { | |
| len_temp = MIN(line_delimit, strlen(buf) - line_delimit * i); | |
| g_string_append_len(str, buf + line_delimit * i, len_temp); | |
| if (i != line_number - 1) | |
| g_string_append(str, "\r\n "); | |
| } | |
| g_string_append(str, "\r\n"); | |
| } | |
| /* | |
| * According to RFC 2426, we need escape following characters: | |
| * '\n', '\r', ';', ',', '\'. | |
| */ | |
| static void add_slash(char *dest, const char *src, int len_max, int len) | |
| { | |
| int i, j; | |
| for (i = 0, j = 0; i < len && j < len_max; i++, j++) { | |
| switch (src[i]) { | |
| case '\n': | |
| dest[j++] = '\\'; | |
| dest[j] = 'n'; | |
| break; | |
| case '\r': | |
| dest[j++] = '\\'; | |
| dest[j] = 'r'; | |
| break; | |
| case '\\': | |
| case ';': | |
| case ',': | |
| dest[j++] = '\\'; | |
| /* fall through */ | |
| default: | |
| dest[j] = src[i]; | |
| break; | |
| } | |
| } | |
| dest[j] = 0; | |
| return; | |
| } | |
| static void vcard_printf_begin(GString *vcards) | |
| { | |
| vcard_printf(vcards, "BEGIN:VCARD"); | |
| vcard_printf(vcards, "VERSION:3.0"); | |
| } | |
| static void vcard_printf_text(GString *vcards, const char *text) | |
| { | |
| char field[LEN_MAX]; | |
| add_slash(field, text, LEN_MAX, strlen(text)); | |
| vcard_printf(vcards, "FN:%s", field); | |
| } | |
| static void vcard_printf_number(GString *vcards, const char *number, int type, | |
| enum phonebook_number_type category) | |
| { | |
| char *pref = "", *intl = "", *category_string = ""; | |
| char buf[128]; | |
| if (number == NULL || !strlen(number) || !type) | |
| return; | |
| switch (category) { | |
| case TEL_TYPE_HOME: | |
| category_string = "HOME,VOICE"; | |
| break; | |
| case TEL_TYPE_MOBILE: | |
| category_string = "CELL,VOICE"; | |
| break; | |
| case TEL_TYPE_FAX: | |
| category_string = "FAX"; | |
| break; | |
| case TEL_TYPE_WORK: | |
| category_string = "WORK,VOICE"; | |
| break; | |
| case TEL_TYPE_OTHER: | |
| category_string = "VOICE"; | |
| break; | |
| } | |
| if ((type == TYPE_INTERNATIONAL) && (number[0] != '+')) | |
| intl = "+"; | |
| snprintf(buf, sizeof(buf), "TEL;TYPE=\%s%s:\%s\%s", pref, | |
| category_string, intl, number); | |
| vcard_printf(vcards, buf, number); | |
| } | |
| static void vcard_printf_group(GString *vcards, const char *group) | |
| { | |
| int len = 0; | |
| if (group) | |
| len = strlen(group); | |
| if (len) { | |
| char field[LEN_MAX]; | |
| add_slash(field, group, LEN_MAX, len); | |
| vcard_printf(vcards, "CATEGORIES:%s", field); | |
| } | |
| } | |
| static void vcard_printf_email(GString *vcards, const char *email) | |
| { | |
| int len = 0; | |
| if (email) | |
| len = strlen(email); | |
| if (len) { | |
| char field[LEN_MAX]; | |
| add_slash(field, email, LEN_MAX, len); | |
| vcard_printf(vcards, | |
| "EMAIL;TYPE=INTERNET:%s", field); | |
| } | |
| } | |
| static void vcard_printf_sip_uri(GString *vcards, const char *sip_uri) | |
| { | |
| int len = 0; | |
| if (sip_uri) | |
| len = strlen(sip_uri); | |
| if (len) { | |
| char field[LEN_MAX]; | |
| add_slash(field, sip_uri, LEN_MAX, len); | |
| vcard_printf(vcards, "IMPP;TYPE=SIP:%s", field); | |
| } | |
| } | |
| static void vcard_printf_end(GString *vcards) | |
| { | |
| vcard_printf(vcards, "END:VCARD"); | |
| vcard_printf(vcards, ""); | |
| } | |
| static void print_number(gpointer pointer, gpointer user_data) | |
| { | |
| struct phonebook_number *pn = pointer; | |
| GString *vcards = user_data; | |
| vcard_printf_number(vcards, pn->number, pn->type, pn->category); | |
| } | |
| static void destroy_number(gpointer pointer) | |
| { | |
| struct phonebook_number *pn = pointer; | |
| g_free(pn->number); | |
| g_free(pn); | |
| } | |
| static void print_merged_entry(gpointer pointer, gpointer user_data) | |
| { | |
| struct phonebook_person *person = pointer; | |
| GString *vcards = user_data; | |
| vcard_printf_begin(vcards); | |
| vcard_printf_text(vcards, person->text); | |
| g_slist_foreach(person->number_list, print_number, vcards); | |
| vcard_printf_group(vcards, person->group); | |
| vcard_printf_email(vcards, person->email); | |
| vcard_printf_sip_uri(vcards, person->sip_uri); | |
| vcard_printf_end(vcards); | |
| } | |
| static void destroy_merged_entry(gpointer pointer) | |
| { | |
| struct phonebook_person *person = pointer; | |
| g_free(person->text); | |
| g_free(person->group); | |
| g_free(person->email); | |
| g_free(person->sip_uri); | |
| g_slist_free_full(person->number_list, destroy_number); | |
| g_free(person); | |
| } | |
| static DBusMessage *generate_export_entries_reply(struct ofono_phonebook *pb, | |
| DBusMessage *msg) | |
| { | |
| DBusMessage *reply; | |
| DBusMessageIter iter; | |
| reply = dbus_message_new_method_return(msg); | |
| if (reply == NULL) | |
| return NULL; | |
| dbus_message_iter_init_append(reply, &iter); | |
| dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, pb->vcards); | |
| return reply; | |
| } | |
| static gboolean need_merge(const char *text) | |
| { | |
| int len; | |
| char c; | |
| if (text == NULL) | |
| return FALSE; | |
| len = strlen(text); | |
| if (len < 2) | |
| return FALSE; | |
| c = tolower(text[len-1]); | |
| if ((text[len-2] == '/') && | |
| ((c == 'w') || (c == 'h') || (c == 'm') || (c == 'o'))) | |
| return TRUE; | |
| return FALSE; | |
| } | |
| static void merge_field_generic(char **str1, const char *str2) | |
| { | |
| if ((*str1 == NULL) && (str2 != NULL) && (strlen(str2) != 0)) | |
| *str1 = g_strdup(str2); | |
| } | |
| static void merge_field_number(GSList **l, const char *number, int type, char c) | |
| { | |
| struct phonebook_number *pn = g_new0(struct phonebook_number, 1); | |
| enum phonebook_number_type category; | |
| pn->number = g_strdup(number); | |
| pn->type = type; | |
| switch (tolower(c)) { | |
| case 'w': | |
| category = TEL_TYPE_WORK; | |
| break; | |
| case 'h': | |
| category = TEL_TYPE_HOME; | |
| break; | |
| case 'm': | |
| category = TEL_TYPE_MOBILE; | |
| break; | |
| case 'f': | |
| category = TEL_TYPE_FAX; | |
| break; | |
| case 'o': | |
| default: | |
| category = TEL_TYPE_OTHER; | |
| break; | |
| } | |
| pn->category = category; | |
| *l = g_slist_append(*l, pn); | |
| } | |
| void ofono_phonebook_entry(struct ofono_phonebook *phonebook, int index, | |
| const char *number, int type, | |
| const char *text, int hidden, | |
| const char *group, | |
| const char *adnumber, int adtype, | |
| const char *secondtext, const char *email, | |
| const char *sip_uri, const char *tel_uri) | |
| { | |
| /* There's really nothing to do */ | |
| if ((number == NULL || number[0] == '\0') && | |
| (text == NULL || text[0] == '\0')) | |
| return; | |
| /* | |
| * We need to collect all the entries that belong to one person, | |
| * so that only one vCard will be generated at last. | |
| * Entries only differ with '/w', '/h', '/m', etc. in field text | |
| * are deemed as entries of one person. | |
| */ | |
| if (need_merge(text)) { | |
| GSList *l; | |
| size_t len_text = strlen(text) - 2; | |
| struct phonebook_person *person; | |
| for (l = phonebook->merge_list; l; l = l->next) { | |
| person = l->data; | |
| if (!strncmp(text, person->text, len_text) && | |
| (strlen(person->text) == len_text)) | |
| break; | |
| } | |
| if (l == NULL) { | |
| person = g_new0(struct phonebook_person, 1); | |
| phonebook->merge_list = | |
| g_slist_prepend(phonebook->merge_list, person); | |
| person->text = g_strndup(text, len_text); | |
| } | |
| merge_field_number(&(person->number_list), number, type, | |
| text[len_text + 1]); | |
| merge_field_number(&(person->number_list), adnumber, adtype, | |
| text[len_text + 1]); | |
| merge_field_generic(&(person->group), group); | |
| merge_field_generic(&(person->email), email); | |
| merge_field_generic(&(person->sip_uri), sip_uri); | |
| return; | |
| } | |
| vcard_printf_begin(phonebook->vcards); | |
| if (text == NULL || text[0] == '\0') | |
| vcard_printf_text(phonebook->vcards, number); | |
| else | |
| vcard_printf_text(phonebook->vcards, text); | |
| vcard_printf_number(phonebook->vcards, number, type, TEL_TYPE_OTHER); | |
| vcard_printf_number(phonebook->vcards, adnumber, adtype, | |
| TEL_TYPE_OTHER); | |
| vcard_printf_group(phonebook->vcards, group); | |
| vcard_printf_email(phonebook->vcards, email); | |
| vcard_printf_sip_uri(phonebook->vcards, sip_uri); | |
| vcard_printf_end(phonebook->vcards); | |
| } | |
| static void export_phonebook_cb(const struct ofono_error *error, void *data) | |
| { | |
| struct ofono_phonebook *phonebook = data; | |
| if (error->type != OFONO_ERROR_TYPE_NO_ERROR) | |
| ofono_error("export_entries_one_storage_cb with %s failed", | |
| storage_support[phonebook->storage_index]); | |
| /* convert the collected entries that are already merged to vcard */ | |
| phonebook->merge_list = g_slist_reverse(phonebook->merge_list); | |
| g_slist_foreach(phonebook->merge_list, print_merged_entry, | |
| phonebook->vcards); | |
| g_slist_free_full(phonebook->merge_list, destroy_merged_entry); | |
| phonebook->merge_list = NULL; | |
| phonebook->storage_index++; | |
| export_phonebook(phonebook); | |
| return; | |
| } | |
| static void export_phonebook(struct ofono_phonebook *phonebook) | |
| { | |
| DBusMessage *reply; | |
| const char *pb = storage_support[phonebook->storage_index]; | |
| if (pb) { | |
| phonebook->driver->export_entries(phonebook, pb, | |
| export_phonebook_cb, phonebook); | |
| return; | |
| } | |
| reply = generate_export_entries_reply(phonebook, phonebook->pending); | |
| if (reply == NULL) { | |
| dbus_message_unref(phonebook->pending); | |
| return; | |
| } | |
| __ofono_dbus_pending_reply(&phonebook->pending, reply); | |
| phonebook->flags |= PHONEBOOK_FLAG_CACHED; | |
| } | |
| static DBusMessage *import_entries(DBusConnection *conn, DBusMessage *msg, | |
| void *data) | |
| { | |
| struct ofono_phonebook *phonebook = data; | |
| DBusMessage *reply; | |
| if (phonebook->pending) { | |
| reply = __ofono_error_busy(phonebook->pending); | |
| g_dbus_send_message(conn, reply); | |
| return NULL; | |
| } | |
| if (phonebook->flags & PHONEBOOK_FLAG_CACHED) { | |
| reply = generate_export_entries_reply(phonebook, msg); | |
| g_dbus_send_message(conn, reply); | |
| return NULL; | |
| } | |
| g_string_set_size(phonebook->vcards, 0); | |
| phonebook->storage_index = 0; | |
| phonebook->pending = dbus_message_ref(msg); | |
| export_phonebook(phonebook); | |
| return NULL; | |
| } | |
| static const GDBusMethodTable phonebook_methods[] = { | |
| { GDBUS_ASYNC_METHOD("Import", | |
| NULL, GDBUS_ARGS({ "entries", "s" }), | |
| import_entries) }, | |
| { } | |
| }; | |
| static const GDBusSignalTable phonebook_signals[] = { | |
| { } | |
| }; | |
| int ofono_phonebook_driver_register(const struct ofono_phonebook_driver *d) | |
| { | |
| DBG("driver: %p, name: %s", d, d->name); | |
| if (d->probe == NULL) | |
| return -EINVAL; | |
| g_drivers = g_slist_prepend(g_drivers, (void *) d); | |
| return 0; | |
| } | |
| void ofono_phonebook_driver_unregister(const struct ofono_phonebook_driver *d) | |
| { | |
| DBG("driver: %p, name: %s", d, d->name); | |
| g_drivers = g_slist_remove(g_drivers, (void *) d); | |
| } | |
| static void phonebook_unregister(struct ofono_atom *atom) | |
| { | |
| struct ofono_phonebook *pb = __ofono_atom_get_data(atom); | |
| const char *path = __ofono_atom_get_path(pb->atom); | |
| DBusConnection *conn = ofono_dbus_get_connection(); | |
| struct ofono_modem *modem = __ofono_atom_get_modem(pb->atom); | |
| ofono_modem_remove_interface(modem, OFONO_PHONEBOOK_INTERFACE); | |
| g_dbus_unregister_interface(conn, path, OFONO_PHONEBOOK_INTERFACE); | |
| } | |
| static void phonebook_remove(struct ofono_atom *atom) | |
| { | |
| struct ofono_phonebook *pb = __ofono_atom_get_data(atom); | |
| DBG("atom: %p", atom); | |
| if (pb == NULL) | |
| return; | |
| if (pb->driver && pb->driver->remove) | |
| pb->driver->remove(pb); | |
| g_string_free(pb->vcards, TRUE); | |
| g_free(pb); | |
| } | |
| struct ofono_phonebook *ofono_phonebook_create(struct ofono_modem *modem, | |
| unsigned int vendor, | |
| const char *driver, void *data) | |
| { | |
| struct ofono_phonebook *pb; | |
| GSList *l; | |
| if (driver == NULL) | |
| return NULL; | |
| pb = g_try_new0(struct ofono_phonebook, 1); | |
| if (pb == NULL) | |
| return NULL; | |
| pb->vcards = g_string_new(NULL); | |
| pb->atom = __ofono_modem_add_atom(modem, OFONO_ATOM_TYPE_PHONEBOOK, | |
| phonebook_remove, pb); | |
| for (l = g_drivers; l; l = l->next) { | |
| const struct ofono_phonebook_driver *drv = l->data; | |
| if (g_strcmp0(drv->name, driver)) | |
| continue; | |
| if (drv->probe(pb, vendor, data) < 0) | |
| continue; | |
| pb->driver = drv; | |
| break; | |
| } | |
| return pb; | |
| } | |
| void ofono_phonebook_register(struct ofono_phonebook *pb) | |
| { | |
| DBusConnection *conn = ofono_dbus_get_connection(); | |
| const char *path = __ofono_atom_get_path(pb->atom); | |
| struct ofono_modem *modem = __ofono_atom_get_modem(pb->atom); | |
| if (!g_dbus_register_interface(conn, path, OFONO_PHONEBOOK_INTERFACE, | |
| phonebook_methods, phonebook_signals, | |
| NULL, pb, NULL)) { | |
| ofono_error("Could not create %s interface", | |
| OFONO_PHONEBOOK_INTERFACE); | |
| return; | |
| } | |
| ofono_modem_add_interface(modem, OFONO_PHONEBOOK_INTERFACE); | |
| __ofono_atom_register(pb->atom, phonebook_unregister); | |
| } | |
| void ofono_phonebook_remove(struct ofono_phonebook *pb) | |
| { | |
| __ofono_atom_free(pb->atom); | |
| } | |
| void ofono_phonebook_set_data(struct ofono_phonebook *pb, void *data) | |
| { | |
| pb->driver_data = data; | |
| } | |
| void *ofono_phonebook_get_data(struct ofono_phonebook *pb) | |
| { | |
| return pb->driver_data; | |
| } |