5 * Copyright (C) 2009-2010 Intel Corporation
6 * Copyright (C) 2007-2010 Marcel Holtmann <marcel@holtmann.org>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
36 #include <sys/types.h>
40 #include <libical/ical.h>
41 #include <libical/vobject.h>
42 #include <libical/vcc.h>
45 #include "phonebook.h"
47 typedef void (*vcard_func_t) (const char *file, VObject *vo, void *user_data);
52 const struct apparam_field *apparams;
59 phonebook_entry_cb entry_cb;
60 phonebook_cache_ready_cb ready_cb;
65 static char *root_folder = NULL;
67 static void dummy_free(void *user_data)
69 struct dummy_data *dummy = user_data;
74 g_free(dummy->folder);
78 static void query_free(void *user_data)
80 struct cache_query *query = user_data;
88 int phonebook_init(void)
93 /* FIXME: It should NOT be hard-coded */
94 root_folder = g_build_filename(getenv("HOME"), "phonebook", NULL);
99 void phonebook_exit(void)
105 static int handle_cmp(gconstpointer a, gconstpointer b)
111 if (sscanf(f1, "%u.vcf", &i1) != 1)
114 if (sscanf(f2, "%u.vcf", &i2) != 1)
120 static int foreach_vcard(DIR *dp, vcard_func_t func, uint16_t offset,
121 uint16_t maxlistcount, void *user_data, uint16_t *count)
124 GSList *sorted = NULL, *l;
127 int err, fd, folderfd;
130 folderfd = dirfd(dp);
133 error("dirfd(): %s(%d)", strerror(err), err);
138 * Sorting vcards by file name. versionsort is a GNU extension.
139 * The simple sorting function implemented on handle_cmp address
140 * vcards handle only(handle is always a number). This sort function
141 * doesn't address filename started by "0".
143 while ((ep = readdir(dp))) {
146 if (ep->d_name[0] == '.')
149 filename = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL);
150 if (filename == NULL) {
151 error("g_filename_to_utf8: invalid filename");
155 if (!g_str_has_suffix(filename, ".vcf")) {
160 sorted = g_slist_insert_sorted(sorted, filename, handle_cmp);
164 * Filtering only the requested vCards attributes. Offset
165 * shall be based on the first entry of the phonebook.
167 for (l = g_slist_nth(sorted, offset);
168 l && n < maxlistcount; l = l->next) {
169 const char *filename = l->data;
171 fd = openat(folderfd, filename, O_RDONLY);
174 error("openat(%s): %s(%d)", filename, strerror(err), err);
178 fp = fdopen(fd, "r");
179 v = Parse_MIME_FromFile(fp);
181 func(filename, v, user_data);
189 g_slist_free_full(sorted, g_free);
197 static void entry_concat(const char *filename, VObject *v, void *user_data)
199 GString *buffer = user_data;
204 * VObject API uses len for IN and OUT
205 * Written bytes is also returned in the len variable
210 writeMemVObject(tmp, &len, v);
212 /* FIXME: only the requested fields must be added */
213 g_string_append_len(buffer, tmp, len);
216 static gboolean read_dir(void *user_data)
218 struct dummy_data *dummy = user_data;
221 uint16_t count = 0, max, offset;
223 buffer = g_string_new("");
225 dp = opendir(dummy->folder);
228 DBG("opendir(): %s(%d)", strerror(err), err);
233 * For PullPhoneBook function, the decision of returning the size
234 * or contacts is made in the PBAP core. When MaxListCount is ZERO,
235 * PCE wants to know the size of a given folder, PSE shall ignore all
236 * other applicattion parameters that may be present in the request.
238 if (dummy->apparams->maxlistcount == 0) {
242 max = dummy->apparams->maxlistcount;
243 offset = dummy->apparams->liststartoffset;
246 foreach_vcard(dp, entry_concat, offset, max, buffer, &count);
250 /* FIXME: Missing vCards fields filtering */
251 dummy->cb(buffer->str, buffer->len, count, 0, TRUE, dummy->user_data);
253 g_string_free(buffer, TRUE);
258 static void entry_notify(const char *filename, VObject *v, void *user_data)
260 struct cache_query *query = user_data;
261 VObject *property, *subproperty;
264 long unsigned int handle;
266 property = isAPropertyOf(v, VCNameProp);
270 if (sscanf(filename, "%lu.vcf", &handle) != 1)
273 if (handle > UINT32_MAX)
276 /* LastName; FirstName; MiddleName; Prefix; Suffix */
278 name = g_string_new("");
279 subproperty = isAPropertyOf(property, VCFamilyNameProp);
281 g_string_append(name,
282 fakeCString(vObjectUStringZValue(subproperty)));
285 subproperty = isAPropertyOf(property, VCGivenNameProp);
287 g_string_append_printf(name, ";%s",
288 fakeCString(vObjectUStringZValue(subproperty)));
290 subproperty = isAPropertyOf(property, VCAdditionalNamesProp);
292 g_string_append_printf(name, ";%s",
293 fakeCString(vObjectUStringZValue(subproperty)));
295 subproperty = isAPropertyOf(property, VCNamePrefixesProp);
297 g_string_append_printf(name, ";%s",
298 fakeCString(vObjectUStringZValue(subproperty)));
301 subproperty = isAPropertyOf(property, VCNameSuffixesProp);
303 g_string_append_printf(name, ";%s",
304 fakeCString(vObjectUStringZValue(subproperty)));
306 property = isAPropertyOf(v, VCTelephoneProp);
308 tel = property ? fakeCString(vObjectUStringZValue(property)) : NULL;
310 query->entry_cb(filename, handle, name->str, NULL, tel,
312 g_string_free(name, TRUE);
315 static gboolean create_cache(void *user_data)
317 struct cache_query *query = user_data;
320 * MaxListCount and ListStartOffset shall not be used
321 * when creating the cache. All entries shall be fetched.
322 * PBAP core is responsible for consider these application
323 * parameters before reply the entries.
325 foreach_vcard(query->dp, entry_notify, 0, 0xffff, query, NULL);
327 query->ready_cb(query->user_data);
332 static gboolean read_entry(void *user_data)
334 struct dummy_data *dummy = user_data;
338 memset(buffer, 0, sizeof(buffer));
339 count = read(dummy->fd, buffer, sizeof(buffer));
343 error("read(): %s(%d)", strerror(err), err);
347 /* FIXME: Missing vCards fields filtering */
349 dummy->cb(buffer, count, 1, 0, TRUE, dummy->user_data);
354 static gboolean is_dir(const char *dir)
358 if (stat(dir, &st) < 0) {
360 error("stat(%s): %s (%d)", dir, strerror(err), err);
364 return S_ISDIR(st.st_mode);
367 char *phonebook_set_folder(const char *current_folder,
368 const char *new_folder, uint8_t flags, int *err)
370 gboolean root, child;
371 char *tmp1, *tmp2, *base, *absolute, *relative = NULL;
374 root = (g_strcmp0("/", current_folder) == 0);
375 child = (new_folder && strlen(new_folder) != 0);
379 /* Go back to root */
381 relative = g_strdup("/");
385 relative = g_build_filename(current_folder, new_folder, NULL);
396 * Removing one level of the current folder. Current folder
397 * contains AT LEAST one level since it is not at root folder.
398 * Use glib utility functions to handle invalid chars in the
399 * folder path properly.
401 tmp1 = g_path_get_basename(current_folder);
402 tmp2 = g_strrstr(current_folder, tmp1);
403 len = tmp2 - (current_folder + 1);
408 base = g_strdup("/");
410 base = g_strndup(current_folder, len);
412 /* Return: one level only */
418 relative = g_build_filename(base, new_folder, NULL);
435 absolute = g_build_filename(root_folder, relative, NULL);
436 if (!is_dir(absolute)) {
450 void phonebook_req_finalize(void *request)
452 struct dummy_data *dummy = request;
454 /* dummy_data will be cleaned when request will be finished via
456 if (dummy && dummy->id)
457 g_source_remove(dummy->id);
460 void *phonebook_pull(const char *name, const struct apparam_field *params,
461 phonebook_cb cb, void *user_data, int *err)
463 struct dummy_data *dummy;
464 char *filename, *folder;
467 * Main phonebook objects will be created dinamically based on the
468 * folder content. All vcards inside the given folder will be appended
469 * in the "virtual" main phonebook object.
472 filename = g_build_filename(root_folder, name, NULL);
474 if (!g_str_has_suffix(filename, ".vcf")) {
481 folder = g_strndup(filename, strlen(filename) - 4);
483 if (!is_dir(folder)) {
490 dummy = g_new0(struct dummy_data, 1);
492 dummy->user_data = user_data;
493 dummy->apparams = params;
494 dummy->folder = folder;
503 int phonebook_pull_read(void *request)
505 struct dummy_data *dummy = request;
510 dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, read_dir, dummy,
516 void *phonebook_get_entry(const char *folder, const char *id,
517 const struct apparam_field *params, phonebook_cb cb,
518 void *user_data, int *err)
520 struct dummy_data *dummy;
525 filename = g_build_filename(root_folder, folder, id, NULL);
527 fd = open(filename, O_RDONLY);
529 DBG("open(): %s(%d)", strerror(errno), errno);
535 dummy = g_new0(struct dummy_data, 1);
537 dummy->user_data = user_data;
538 dummy->apparams = params;
541 ret = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, read_entry, dummy,
547 return GINT_TO_POINTER(ret);
550 void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
551 phonebook_cache_ready_cb ready_cb, void *user_data, int *err)
553 struct cache_query *query;
558 foldername = g_build_filename(root_folder, name, NULL);
559 dp = opendir(foldername);
563 DBG("opendir(): %s(%d)", strerror(errno), errno);
569 query = g_new0(struct cache_query, 1);
570 query->entry_cb = entry_cb;
571 query->ready_cb = ready_cb;
572 query->user_data = user_data;
575 ret = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, create_cache, query,
581 return GINT_TO_POINTER(ret);