Initialize with abook-0.6.0pre2
[abook.git] / database.c
blob7c47ab6d85a60fd5afbbbd69eb20b1565983856e
2 /*
3 * $Id: database.c,v 1.39 2006/09/04 18:29:25 cduval Exp $
5 * by JH <jheinonen@users.sourceforge.net>
7 * Copyright (C) Jaakko Heinonen
8 */
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <unistd.h>
14 #include <assert.h>
15 #ifdef HAVE_CONFIG_H
16 # include "config.h"
17 #endif
18 #include "abook.h"
19 #include "database.h"
20 #include "gettext.h"
21 #include "list.h"
22 #include "misc.h"
23 #include "xmalloc.h"
25 abook_field_list *fields_list = NULL;
26 int fields_count = 0;
28 list_item *database = NULL;
29 static int items = 0;
31 #define ITEM_SIZE (fields_count * sizeof(char *))
32 #define LAST_ITEM (items - 1)
34 #define INITIAL_LIST_CAPACITY 30
35 static int list_capacity = 0;
37 int standard_fields_indexed[ITEM_FIELDS];
40 * notes about adding predefined "standard" fields:
41 * - leave alone "name" and "email"
42 * - reorganize the field numbers in database.h
44 abook_field standard_fields[] = {
45 {"name", N_("Name"), FIELD_STRING}, /* NAME */
46 {"email", N_("E-mail addresses"), FIELD_EMAILS}, /* EMAIL */
47 {"address", N_("Address"), FIELD_STRING}, /* ADDRESS */
48 {"address2", N_("Address2"), FIELD_STRING}, /* ADDRESS2 */
49 {"city", N_("City"), FIELD_STRING}, /* CITY */
50 {"state", N_("State/Province"), FIELD_STRING}, /* STATE */
51 {"zip", N_("ZIP/Postal Code"), FIELD_STRING}, /* ZIP */
52 {"country", N_("Country"), FIELD_STRING}, /* COUNTRY */
53 {"phone", N_("Home Phone"), FIELD_STRING}, /* PHONE */
54 {"workphone", N_("Work Phone"), FIELD_STRING}, /* WORKPHONE */
55 {"fax", N_("Fax"), FIELD_STRING}, /* FAX */
56 {"mobile", N_("Mobile"), FIELD_STRING}, /* MOBILEPHONE */
57 {"nick", N_("Nickname/Alias"), FIELD_STRING}, /* NICK */
58 {"url", N_("URL"), FIELD_STRING}, /* URL */
59 {"notes", N_("Notes"), FIELD_STRING}, /* NOTES */
60 {"anniversary", N_("Anniversary day"), FIELD_DATE}, /* ANNIVERSARY */
61 {0} /* ITEM_FIELDS */
65 extern int first_list_item;
66 extern int curitem;
67 extern char *selected;
68 extern char *datafile;
72 static abook_field *
73 declare_standard_field(int i)
75 abook_field *f = xmalloc(sizeof(abook_field));
77 f = memcpy(f, &standard_fields[i], sizeof(abook_field));
78 f->name = xstrdup(gettext(f->name));
80 add_field(&fields_list, f);
82 assert(standard_fields_indexed[i] == -1);
83 standard_fields_indexed[i] = fields_count++;
85 return f;
88 abook_field *
89 find_standard_field(char *key, int do_declare)
91 int i;
93 for(i = 0; standard_fields[i].key; i++)
94 if(0 == strcmp(standard_fields[i].key, key))
95 goto found;
97 return NULL;
99 found:
100 return do_declare ? declare_standard_field(i) : &standard_fields[i];
103 /* Search for a field. Use the list of declared fields if no list specified. */
104 abook_field *
105 real_find_field(char *key, abook_field_list *list, int *number)
107 abook_field_list *cur;
108 int i;
110 for(cur = (list ? list : fields_list), i = 0; cur; cur = cur->next, i++)
111 if(0 == strcmp(cur->field->key, key)) {
112 if(number)
113 *number = i;
114 return cur->field;
117 if(number)
118 *number = -1;
120 return NULL;
123 void
124 get_field_info(int i, char **key, char **name, int *type)
126 abook_field_list *cur = fields_list;
127 int j;
129 assert(i < fields_count);
131 for(j = 0; i >= 0 && j < i; j++, cur = cur->next)
134 if(key)
135 *key = (i < 0) ? NULL : cur->field->key;
136 if(name)
137 *name = (i < 0) ? NULL : cur->field->name;
138 if(type)
139 *type = (i < 0) ? -1 : cur->field->type;
142 void
143 add_field(abook_field_list **list, abook_field *f)
145 abook_field_list *tmp;
147 for(tmp = *list; tmp && tmp->next; tmp = tmp->next)
150 if(tmp) {
151 tmp->next = xmalloc(sizeof(abook_field_list));
152 tmp = tmp->next;
153 } else
154 *list = tmp = xmalloc(sizeof(abook_field_list));
156 tmp->field = f;
157 tmp->next = NULL;
160 char *
161 declare_new_field(char *key, char *name, char *type, int accept_standard)
163 abook_field *f;
165 if(find_declared_field(key))
166 return _("field already defined");
168 if(find_standard_field(key, accept_standard))
169 return accept_standard ? NULL /* ok, added */ :
170 _("standard field does not need to be declared");
172 f = xmalloc(sizeof(abook_field));
173 f->key = xstrdup(key);
174 f->name = xstrdup(name);
176 if(!*type || (0 == strcasecmp("string", type)))
177 f->type = FIELD_STRING;
178 else if(0 == strcasecmp("emails", type))
179 f->type = FIELD_EMAILS;
180 else if(0 == strcasecmp("list", type))
181 f->type = FIELD_LIST;
182 else if(0 == strcasecmp("date", type))
183 f->type = FIELD_DATE;
184 else
185 return _("unknown type");
187 add_field(&fields_list, f);
188 fields_count++;
190 return NULL;
194 * Declare a new field while database is already loaded
195 * making it grow accordingly
197 static void
198 declare_unknown_field(char *key)
200 int i;
202 declare_new_field(key, key, "string",
203 1 /* accept to declare "standard" fields */);
205 if(!database)
206 return;
208 for(i = 0; i < items; i++)
209 if(database[i]) {
210 database[i] = xrealloc(database[i], ITEM_SIZE);
211 database[i][fields_count - 1] = NULL;
216 * Declare "standard" fields, thus preserving them while parsing a database,
217 * even if they won't be displayed.
219 void
220 init_standard_fields()
222 int i;
224 for(i = 0; standard_fields[i].key; i++)
225 if(standard_fields_indexed[i] == -1)
226 declare_standard_field(i);
229 /* Some initializations - Must be called _before_ load_opts() */
230 void
231 prepare_database_internals()
233 int i;
235 for(i = 0; i < ITEM_FIELDS; i++)
236 standard_fields_indexed[i] = -1;
238 /* the only two mandatory fields */
239 declare_standard_field(NAME);
240 declare_standard_field(EMAIL);
244 parse_database(FILE *in)
246 char *line = NULL;
247 char *tmp;
248 int sec=0, field;
249 list_item item;
251 item = item_create();
253 for(;;) {
254 line = getaline(in);
255 if(feof(in)) {
256 if(item[field_id(NAME)] && sec) {
257 add_item2database(item);
258 } else {
259 item_empty(item);
261 break;
264 if(!*line || *line == '\n' || *line == '#') {
265 goto next;
266 } else if(*line == '[') {
267 if(item[field_id(NAME)] && sec ) {
268 add_item2database(item);
269 } else {
270 item_empty(item);
272 sec = 1;
273 memset(item, 0, ITEM_SIZE);
274 if(!(tmp = strchr(line, ']')))
275 sec = 0; /*incorrect section lines are skipped*/
276 } else if((tmp = strchr(line, '=') ) && sec) {
277 *tmp++ = '\0';
278 find_field_number(line, &field);
279 if(field != -1) {
280 item[field] = xstrdup(tmp);
281 goto next;
282 } else if(!strcasecmp(opt_get_str(STR_PRESERVE_FIELDS),
283 "all")){
284 declare_unknown_field(line);
285 item = xrealloc(item, ITEM_SIZE);
286 item[fields_count - 1] = xstrdup(tmp);
287 goto next;
290 next:
291 xfree(line);
294 xfree(line);
295 item_free(&item);
296 return 0;
300 load_database(char *filename)
302 FILE *in;
304 if(database != NULL)
305 close_database();
307 if ((in = abook_fopen(filename, "r")) == NULL)
308 return -1;
310 parse_database(in);
312 return (items == 0) ? 2 : 0;
316 write_database(FILE *out, struct db_enumerator e)
318 int j;
319 int i = 0;
320 abook_field_list *cur;
322 fprintf(out,
323 "# abook addressbook file\n\n"
324 "[format]\n"
325 "program=" PACKAGE "\n"
326 "version=" VERSION "\n"
327 "\n\n"
330 db_enumerate_items(e) {
331 fprintf(out, "[%d]\n", i);
333 for(cur = fields_list, j = 0; cur; cur = cur->next, j++) {
334 if( database[e.item][j] != NULL &&
335 *database[e.item][j] )
336 fprintf(out, "%s=%s\n",
337 cur->field->key,
338 database[e.item][j]
342 fputc('\n', out);
343 i++;
346 return 0;
350 save_database()
352 FILE *out;
353 int ret = 0;
354 struct db_enumerator e = init_db_enumerator(ENUM_ALL);
355 char *datafile_new = strconcat(datafile, ".new", NULL);
356 char *datafile_old = strconcat(datafile, "~", NULL);
358 if( (out = abook_fopen(datafile_new, "w")) == NULL ) {
359 ret = -1;
360 goto out;
363 if(!list_is_empty())
365 * Possibly should check if write_database failed.
366 * Currently it returns always zero.
368 write_database(out, e);
370 fclose(out);
372 if(access(datafile, F_OK) == 0 &&
373 (rename(datafile, datafile_old)) == -1)
374 ret = -1;
376 if((rename(datafile_new, datafile)) == -1)
377 ret = -1;
379 out:
380 free(datafile_new);
381 free(datafile_old);
382 return ret;
385 static void
386 db_free_item(int item)
388 item_empty(database[item]);
391 void
392 close_database()
394 int i;
396 for(i=0; i <= LAST_ITEM; i++)
397 db_free_item(i);
399 xfree(database);
400 free(selected);
402 database = NULL;
403 selected = NULL;
405 items = 0;
406 first_list_item = curitem = -1;
407 list_capacity = 0;
411 static void
412 validate_item(list_item item)
414 abook_field_list *f;
415 int i, max_field_len;
416 char *tmp;
418 for(f = fields_list, i = 0; f; f = f->next, i++) {
419 max_field_len = 0;
421 switch(f->field->type) {
422 case FIELD_EMAILS:
423 max_field_len = MAX_EMAILSTR_LEN;
424 if(item[i] == NULL)
425 item[i] = xstrdup("");
426 break;
427 case FIELD_LIST:
428 /* TODO quote string if it contains commas */
429 break;
430 case FIELD_STRING:
431 max_field_len = MAX_FIELD_LEN;
432 break;
433 case FIELD_DATE:
434 break;
435 default:
436 assert(0);
439 if(max_field_len && item[i] &&
440 ((int)strlen(item[i]) > max_field_len)) {
441 /* truncate field */
442 tmp = item[i];
443 item[i][max_field_len - 1] = 0;
444 item[i] = xstrdup(item[i]);
445 free(tmp);
450 static void
451 adjust_list_capacity()
453 if(list_capacity < 1)
454 list_capacity = INITIAL_LIST_CAPACITY;
455 else if(items >= list_capacity)
456 list_capacity *= 2;
457 else if(list_capacity / 2 > items)
458 list_capacity /= 2;
459 else
460 return;
462 if(database)
463 database = xrealloc(database,sizeof(list_item) * list_capacity);
464 else /* allocate memory _and_ initialize pointers to NULL */
465 database = xmalloc0(sizeof(list_item) * list_capacity);
467 selected = xrealloc(selected, list_capacity);
471 add_item2database(list_item item)
473 /* 'name' field is mandatory */
474 if((item[field_id(NAME)] == NULL) || ! *item[field_id(NAME)]) {
475 item_empty(item);
476 return 1;
479 if(++items > list_capacity)
480 adjust_list_capacity();
482 validate_item(item);
484 selected[LAST_ITEM] = 0;
486 database[LAST_ITEM] = item_create();
487 item_copy(database[LAST_ITEM], item);
489 return 0;
493 void
494 remove_selected_items()
496 int i, j;
498 if(list_is_empty())
499 return;
501 if(!selected_items())
502 selected[curitem] = 1;
504 for(j = LAST_ITEM; j >= 0; j--) {
505 if(selected[j]) {
506 db_free_item(j); /* added for .4 data_s_ */
507 for(i = j; i < LAST_ITEM; i++) {
508 item_copy(database[i], database[i + 1]);
509 selected[i] = selected[i + 1];
511 item_free(&database[LAST_ITEM]);
512 items--;
516 if(curitem > LAST_ITEM && items > 0)
517 curitem = LAST_ITEM;
519 adjust_list_capacity();
521 select_none();
524 char *
525 get_surname(char *s)
527 char *p = s + strlen(s);
529 assert(s != NULL);
531 while(p > s && *(p - 1) != ' ')
532 p--;
534 return xstrdup(p);
537 static int
538 surnamecmp(const void *i1, const void *i2)
540 int ret, idx = field_id(NAME);
541 char *n1, *n2, *s1, *s2;
543 n1 = (*(list_item *)i1)[idx];
544 n2 = (*(list_item *)i2)[idx];
546 s1 = get_surname(n1);
547 s2 = get_surname(n2);
549 if( !(ret = safe_strcoll(s1, s2)) )
550 ret = safe_strcoll(n1, n2);
552 free(s1);
553 free(s2);
555 return ret;
558 static int sort_field = -1;
560 static int
561 namecmp(const void *i1, const void *i2)
563 char *n1, *n2;
565 assert(sort_field >= 0 && sort_field < fields_count);
567 n1 = (*(list_item *)i1)[sort_field];
568 n2 = (*(list_item *)i2)[sort_field];
570 return safe_strcoll(n1, n2);
573 void
574 sort_by_field(char *name)
576 int field;
578 select_none();
580 name = (name == NULL) ? opt_get_str(STR_SORT_FIELD) : name;
581 find_field_number(name, &field);
583 if(field < 0) {
584 if(name == opt_get_str(STR_SORT_FIELD))
585 statusline_msg(_("Invalid field value defined "
586 "in configuration"));
587 else
588 statusline_msg(_("Invalid field value for sorting"));
590 return;
593 sort_field = field;
595 qsort((void *)database, items, sizeof(list_item), namecmp);
597 refresh_screen();
600 void
601 sort_surname()
603 select_none();
605 qsort((void *)database, items, sizeof(list_item), surnamecmp);
607 refresh_screen();
610 /* TODO implement a search based on more sophisticated patterns */
612 find_item(char *str, int start, int search_fields[])
614 int i, id;
615 char *findstr = NULL;
616 char *tmp = NULL;
617 int ret = -1; /* not found */
618 struct db_enumerator e = init_db_enumerator(ENUM_ALL);
620 if(list_is_empty() || !is_valid_item(start))
621 return -2; /* error */
623 findstr = xstrdup(str);
624 findstr = strlower(findstr);
626 e.item = start - 1; /* must be "real start" - 1 */
627 db_enumerate_items(e) {
628 for(i = 0; search_fields[i] >= 0; i++) {
629 if((id = field_id(search_fields[i])) == -1)
630 continue;
631 if(database[e.item][id] == NULL)
632 continue;
633 tmp = xstrdup(database[e.item][id]);
634 if( tmp && strstr(strlower(tmp), findstr) ) {
635 ret = e.item;
636 goto out;
638 xfree(tmp);
642 out:
643 free(findstr);
644 free(tmp);
645 return ret;
649 is_selected(int item)
651 return selected[item];
655 is_valid_item(int item)
657 return item <= LAST_ITEM && item >= 0;
661 last_item()
663 return LAST_ITEM;
667 db_n_items()
669 return items;
673 real_db_enumerate_items(struct db_enumerator e)
675 int item = max(0, e.item + 1);
676 int i;
678 switch(e.mode) {
679 #ifdef DEBUG
680 case ENUM_ALL:
681 break;
682 #endif
683 case ENUM_SELECTED:
684 for(i = item; i <= LAST_ITEM; i++) {
685 if(is_selected(i)) {
686 item = i;
687 goto out;
690 return -1;
691 #ifdef DEBUG
692 default:
693 fprintf(stderr, "real_db_enumerate_items() "
694 "BUG: unknown db_enumerator mode: %d\n",
695 e.mode);
696 break;
697 #endif
699 out:
700 return (item > LAST_ITEM || item < 0) ? -1 : item;
703 struct db_enumerator
704 init_db_enumerator(int mode)
706 struct db_enumerator e;
708 e.item = -1; /* important - means "start from beginning" */
709 e.mode = mode;
711 return e;
715 list_item
716 item_create()
718 return xmalloc0(ITEM_SIZE);
721 void
722 item_free(list_item *item)
724 assert(item);
726 xfree(*item);
729 void
730 item_empty(list_item item)
731 { int i;
733 assert(item);
735 for(i = 0; i < fields_count; i++)
736 if(item[i])
737 xfree(item[i]);
741 void
742 item_copy(list_item dest, list_item src)
744 memmove(dest, src, ITEM_SIZE);
747 void
748 item_duplicate(list_item dest, list_item src)
750 int i;
752 for(i = 0; i < fields_count; i++)
753 dest[i] = src[i] ? xstrdup(src[i]) : NULL;
757 * Things like item[field_id(NICK)] should never be used, since besides NAME
758 * and EMAIL, none of the standard fields can be assumed to be existing.
760 * Prefer the functions item_fput(), item_fget(), db_fput() and db_fget()
761 * to access fields in items and database.
764 /* quick lookup by "standard" field number */
765 inline int
766 field_id(int i)
768 assert((i >= 0) && (i < ITEM_FIELDS));
769 return standard_fields_indexed[i];
773 item_fput(list_item item, int i, char *val)
775 int id = field_id(i);
777 if(id != -1) {
778 item[id] = val;
779 return 1;
782 return 0;
785 char *
786 item_fget(list_item item, int i)
788 int id = field_id(i);
790 if(id != -1)
791 return item[id];
792 else
793 return NULL;
797 real_db_field_put(int item, int i, int std, char *val)
799 int id;
801 assert(database[item]);
803 id = std ? field_id(i) : i;
805 if(id != -1) {
806 database[item][id] = val;
807 return 1;
810 return 0;
813 char *
814 real_db_field_get(int item, int i, int std)
816 int id;
818 assert(database[item]);
820 id = std ? field_id(i) : i;
822 if(id != -1)
823 return database[item][id];
824 else
825 return NULL;
828 list_item
829 db_item_get(int i)
831 return database[i];
834 /* Fetch addresses from all fields of FIELD_EMAILS type */
835 /* Memory has to be freed by the caller */
836 char *
837 db_email_get(int item)
839 int i;
840 char *res;
841 abook_field_list *cur;
842 abook_list *emails = NULL;
844 for(cur = fields_list, i = 0; cur; cur = cur->next, i++)
845 if(cur->field->type == FIELD_EMAILS && *database[item][i])
846 abook_list_append(&emails, database[item][i]);
848 res = abook_list_to_csv(emails);
849 abook_list_free(&emails);
850 return res ? res : xstrdup("");