]> git.deb.at Git - pkg/abook.git/blob - database.c
Imported Debian patch 0.5.6+cvs1-1
[pkg/abook.git] / database.c
1
2 /*
3  * $Id: database.c,v 1.36 2006/08/09 21:54:56 cduval Exp $
4  *
5  * by JH <jheinonen@users.sourceforge.net>
6  *
7  * Copyright (C) Jaakko Heinonen
8  */
9
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"
24
25 abook_field_list *fields_list = NULL;
26 int fields_count = 0;
27
28 list_item *database = NULL;
29 static int items = 0;
30
31 #define ITEM_SIZE (fields_count * sizeof(char *))
32 #define LAST_ITEM (items - 1)
33
34 #define INITIAL_LIST_CAPACITY   30
35 static int list_capacity = 0;
36
37 int standard_fields_indexed[ITEM_FIELDS];
38
39 /*
40  * notes about adding predefined "standard" fields:
41  *      - leave alone "name" and "email"
42  *      - reorganize the field numbers in database.h
43  */
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_DAY},    /* ANNIVERSARY */
61         {0} /* ITEM_FIELDS */
62 };
63
64
65 extern int first_list_item;
66 extern int curitem;
67 extern char *selected;
68 extern char *datafile;
69
70
71
72 static abook_field *
73 declare_standard_field(int i)
74 {
75         abook_field *f = xmalloc(sizeof(abook_field));
76
77         f = memcpy(f, &standard_fields[i], sizeof(abook_field));
78         f->name = xstrdup(gettext(f->name));
79
80         add_field(&fields_list, f);
81
82         assert(standard_fields_indexed[i] == -1);
83         standard_fields_indexed[i] = fields_count++;
84
85         return f;
86 }
87
88 abook_field *
89 find_standard_field(char *key, int do_declare)
90 {
91         int i;
92
93         for(i = 0; standard_fields[i].key; i++)
94                 if(0 == strcmp(standard_fields[i].key, key))
95                         goto found;
96
97         return NULL;
98
99 found:
100         return do_declare ? declare_standard_field(i) : &standard_fields[i];
101 }
102
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)
106 {
107         abook_field_list *cur;
108         int i;
109
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;
115                 }
116
117         if(number)
118                 *number = -1;
119
120         return NULL;
121 }
122
123 void
124 get_field_keyname(int i, char **key, char **name)
125 {
126         abook_field_list *cur = fields_list;
127         int j;
128
129         assert(i < fields_count);
130
131         for(j = 0; i >= 0 && j < i; j++, cur = cur->next)
132                 ;
133
134         if(key)
135                 *key = (i < 0) ? NULL : cur->field->key;
136         if(name)
137                 *name = (i < 0) ? NULL : cur->field->name;
138 }
139
140 void
141 add_field(abook_field_list **list, abook_field *f)
142 {
143         abook_field_list *tmp;
144
145         for(tmp = *list; tmp && tmp->next; tmp = tmp->next)
146                 ;
147
148         if(tmp) {
149                 tmp->next = xmalloc(sizeof(abook_field_list));
150                 tmp = tmp->next;
151         } else
152                 *list = tmp = xmalloc(sizeof(abook_field_list));
153
154         tmp->field = f;
155         tmp->next = NULL;
156 }
157
158 char *
159 declare_new_field(char *key, char *name, char *type, int accept_standard)
160 {
161         abook_field *f;
162
163         if(find_declared_field(key))
164                 return _("field already defined");
165
166         if(find_standard_field(key, accept_standard))
167                 return accept_standard ? NULL /* ok, added */ :
168                         _("standard field does not need to be declared");
169
170         f = xmalloc(sizeof(abook_field));
171         f->key = xstrdup(key);
172         f->name = xstrdup(name);
173
174         if(!*type || (0 == strcasecmp("string", type)))
175                 f->type = FIELD_STRING;
176         else if(0 == strcasecmp("emails", type))
177                 f->type = FIELD_EMAILS;
178         else if(0 == strcasecmp("list", type))
179                 f->type = FIELD_LIST;
180         else if(0 == strcasecmp("day", type))
181                 f->type = FIELD_DAY;
182         else
183                 return _("unknown type");
184
185         add_field(&fields_list, f);
186         fields_count++;
187
188         return NULL;
189 }
190
191 /*
192  * Declare a new field while database is already loaded
193  * making it grow accordingly
194  */
195 static void
196 declare_unknown_field(char *key)
197 {
198         int i;
199
200         declare_new_field(key, key, "string",
201                         1 /* accept to declare "standard" fields */);
202
203         if(!database)
204                 return;
205
206         for(i = 0; i < items; i++)
207                 if(database[i]) {
208                         database[i] = xrealloc(database[i], ITEM_SIZE);
209                         database[i][fields_count - 1] = NULL;
210                 }
211 }
212
213 /*
214  * Declare "standard" fields, thus preserving them while parsing a database,
215  * even if they won't be displayed.
216  */
217 void
218 init_standard_fields()
219 {
220         int i;
221
222         for(i = 0; standard_fields[i].key; i++)
223                 if(standard_fields_indexed[i] == -1)
224                         declare_standard_field(i);
225 }
226
227 /* Some initializations - Must be called _before_ load_opts() */
228 void
229 prepare_database_internals()
230 {
231         int i;
232
233         for(i = 0; i < ITEM_FIELDS; i++)
234                 standard_fields_indexed[i] = -1;
235
236         /* the only two mandatory fields */
237         declare_standard_field(NAME);
238         declare_standard_field(EMAIL);
239 }
240
241 int
242 parse_database(FILE *in)
243 {
244         char *line = NULL;
245         char *tmp;
246         int sec=0, field;
247         list_item item;
248
249         item = item_create();
250
251         for(;;) {
252                 line = getaline(in);
253                 if(feof(in)) {
254                         if(item[field_id(NAME)] && sec) {
255                                 add_item2database(item);
256                         } else {
257                                 item_empty(item);
258                         }
259                         break;
260                 }
261
262                 if(!*line || *line == '\n' || *line == '#') {
263                         goto next;
264                 } else if(*line == '[') {
265                         if(item[field_id(NAME)] && sec ) {
266                                 add_item2database(item);
267                         } else {
268                                 item_empty(item);
269                         }
270                         sec = 1;
271                         memset(item, 0, ITEM_SIZE);
272                         if(!(tmp = strchr(line, ']')))
273                                 sec = 0; /*incorrect section lines are skipped*/
274                 } else if((tmp = strchr(line, '=') ) && sec) {
275                         *tmp++ = '\0';
276                         find_field_number(line, &field);
277                         if(field != -1) {
278                                 item[field] = xstrdup(tmp);
279                                 goto next;
280                         } else if(!strcasecmp(opt_get_str(STR_PRESERVE_FIELDS),
281                                                 "all")){
282                                 declare_unknown_field(line);
283                                 item = xrealloc(item, ITEM_SIZE);
284                                 item[fields_count - 1] = xstrdup(tmp);
285                                 goto next;
286                         }
287                 }
288 next:
289                 xfree(line);
290         }
291
292         xfree(line);
293         item_free(&item);
294         return 0;
295 }
296
297 int
298 load_database(char *filename)
299 {
300         FILE *in;
301
302         if(database != NULL)
303                 close_database();
304
305         if ((in = abook_fopen(filename, "r")) == NULL)
306                 return -1;
307
308         parse_database(in);
309
310         return (items == 0) ? 2 : 0;
311 }
312
313 int
314 write_database(FILE *out, struct db_enumerator e)
315 {
316         int j;
317         int i = 0;
318         abook_field_list *cur;
319
320         fprintf(out,
321                 "# abook addressbook file\n\n"
322                 "[format]\n"
323                 "program=" PACKAGE "\n"
324                 "version=" VERSION "\n"
325                 "\n\n"
326         );
327
328         db_enumerate_items(e) {
329                 fprintf(out, "[%d]\n", i);
330
331                 for(cur = fields_list, j = 0; cur; cur = cur->next, j++) {
332                         if( database[e.item][j] != NULL &&
333                                         *database[e.item][j] )
334                                 fprintf(out, "%s=%s\n",
335                                         cur->field->key,
336                                         database[e.item][j]
337                                         );
338                 }
339
340                 fputc('\n', out);
341                 i++;
342         }
343
344         return 0;
345 }
346
347 int
348 save_database()
349 {
350         FILE *out;
351         int ret = 0;
352         struct db_enumerator e = init_db_enumerator(ENUM_ALL);
353         char *datafile_new = strconcat(datafile, ".new", NULL);
354         char *datafile_old = strconcat(datafile, "~", NULL);
355
356         if( (out = abook_fopen(datafile_new, "w")) == NULL ) {
357                 ret = -1;
358                 goto out;
359         }
360
361         if(list_is_empty()) {
362                 fclose(out);
363                 unlink(datafile);
364                 ret = 1;
365                 goto out;
366         }
367
368         /*
369          * Possibly should check if write_database failed.
370          * Currently it returns always zero.
371          */
372         write_database(out, e);
373         fclose(out);
374
375         if(access(datafile, F_OK) == 0 &&
376                         (rename(datafile, datafile_old)) == -1)
377                 ret = -1;
378         
379         if((rename(datafile_new, datafile)) == -1)
380                 ret = -1;
381
382 out:
383         free(datafile_new);
384         free(datafile_old);
385         return ret;
386 }
387
388 static void
389 db_free_item(int item)
390 {
391         item_empty(database[item]);
392 }
393
394 void
395 close_database()
396 {
397         int i;
398
399         for(i=0; i <= LAST_ITEM; i++)
400                 db_free_item(i);
401
402         xfree(database);
403         free(selected);
404
405         database = NULL;
406         selected = NULL;
407
408         items = 0;
409         first_list_item = curitem = -1;
410         list_capacity = 0;
411 }
412
413
414 static void
415 validate_item(list_item item)
416 {
417         abook_field_list *f;
418         int i, max_field_len;
419         char *tmp;
420
421         for(f = fields_list, i = 0; f; f = f->next, i++) {
422                 max_field_len = 0;
423
424                 switch(f->field->type) {
425                         case FIELD_EMAILS:
426                                 max_field_len = MAX_EMAILSTR_LEN;
427                                 if(item[i] == NULL)
428                                         item[i] = xstrdup("");
429                                 break;
430                         case FIELD_LIST:
431                                 /* TODO quote string if it contains commas */
432                                 break;
433                         case FIELD_STRING:
434                                 max_field_len = MAX_FIELD_LEN;
435                                 break;
436                         case FIELD_DAY:
437                                 break;
438                         default:
439                                 assert(0);
440                 }
441
442                 if(max_field_len && item[i] &&
443                                 ((int)strlen(item[i]) > max_field_len)) {
444                         /* truncate field */
445                         tmp = item[i];
446                         item[i][max_field_len - 1] = 0;
447                         item[i] = xstrdup(item[i]);
448                         free(tmp);
449                 }
450         }
451 }
452
453 static void
454 adjust_list_capacity()
455 {
456         if(list_capacity < 1)
457                 list_capacity = INITIAL_LIST_CAPACITY;
458         else if(items >= list_capacity)
459                 list_capacity *= 2;
460         else if(list_capacity / 2 > items)
461                 list_capacity /= 2;
462         else
463                 return;
464
465         if(database)
466                 database = xrealloc(database,sizeof(list_item) * list_capacity);
467         else /* allocate memory _and_ initialize pointers to NULL */
468                 database = xmalloc0(sizeof(list_item) * list_capacity);
469
470         selected = xrealloc(selected, list_capacity);
471 }
472
473 int
474 add_item2database(list_item item)
475 {
476         /* 'name' field is mandatory */
477         if((item[field_id(NAME)] == NULL) || ! *item[field_id(NAME)]) {
478                 item_empty(item);
479                 return 1;
480         }
481
482         if(++items > list_capacity)
483                 adjust_list_capacity();
484
485         validate_item(item);
486
487         selected[LAST_ITEM] = 0;
488
489         database[LAST_ITEM] = item_create();
490         item_copy(database[LAST_ITEM], item);
491
492         return 0;
493 }
494         
495
496 void
497 remove_selected_items()
498 {
499         int i, j;
500
501         if(list_is_empty())
502                 return;
503
504         if(!selected_items())
505                 selected[curitem] = 1;
506
507         for(j = LAST_ITEM; j >= 0; j--) {
508                 if(selected[j]) {
509                         db_free_item(j); /* added for .4 data_s_ */
510                         for(i = j; i < LAST_ITEM; i++) {
511                                 item_copy(database[i], database[i + 1]);
512                                 selected[i] = selected[i + 1];
513                         }
514                         item_free(&database[LAST_ITEM]);
515                         items--;
516                 }
517         }
518
519         if(curitem > LAST_ITEM && items > 0)
520                 curitem = LAST_ITEM;
521
522         adjust_list_capacity();
523
524         select_none();
525 }
526
527 char *
528 get_surname(char *s)
529 {
530         char *p = s + strlen(s);
531
532         assert(s != NULL);
533
534         while(p > s && *(p - 1) != ' ')
535                 p--;
536
537         return xstrdup(p);
538 }
539
540 static int
541 surnamecmp(const void *i1, const void *i2)
542 {
543         int ret, idx = field_id(NAME);
544         char *n1, *n2, *s1, *s2;
545
546         n1 = (*(list_item *)i1)[idx];
547         n2 = (*(list_item *)i2)[idx];
548         
549         s1 = get_surname(n1);
550         s2 = get_surname(n2);
551
552         if( !(ret = safe_strcoll(s1, s2)) )
553                 ret = safe_strcoll(n1, n2);
554
555         free(s1);
556         free(s2);
557
558         return ret;
559 }
560
561 static int sort_field = -1;
562
563 static int
564 namecmp(const void *i1, const void *i2)
565 {
566         char *n1, *n2;
567
568         assert(sort_field >= 0 && sort_field < fields_count);
569
570         n1 = (*(list_item *)i1)[sort_field];
571         n2 = (*(list_item *)i2)[sort_field];
572
573         return safe_strcoll(n1, n2);
574 }
575
576 void
577 sort_by_field(char *name)
578 {
579         int field;
580
581         select_none();
582
583         name = (name == NULL) ? opt_get_str(STR_SORT_FIELD) : name;
584         find_field_number(name, &field);
585
586         if(field < 0) {
587                 if(name == opt_get_str(STR_SORT_FIELD))
588                         statusline_msg(_("Invalid field value defined "
589                                 "in configuration"));
590                 else
591                         statusline_msg(_("Invalid field value for sorting"));
592
593                 return;
594         }
595
596         sort_field = field;
597
598         qsort((void *)database, items, sizeof(list_item), namecmp);
599
600         refresh_screen();
601 }
602
603 void
604 sort_surname()
605 {
606         select_none();
607
608         qsort((void *)database, items, sizeof(list_item), surnamecmp);
609
610         refresh_screen();
611 }
612
613 /* TODO implement a search based on more sophisticated patterns */
614 int
615 find_item(char *str, int start, int search_fields[])
616 {
617         int i, id;
618         char *findstr = NULL;
619         char *tmp = NULL;
620         int ret = -1; /* not found */
621         struct db_enumerator e = init_db_enumerator(ENUM_ALL);
622
623         if(list_is_empty() || !is_valid_item(start))
624                 return -2; /* error */
625
626         findstr = xstrdup(str);
627         findstr = strlower(findstr);
628
629         e.item = start - 1; /* must be "real start" - 1 */
630         db_enumerate_items(e) {
631                 for(i = 0; search_fields[i] >= 0; i++) {
632                         if((id = field_id(search_fields[i])) == -1)
633                                 continue;
634                         if(database[e.item][id] == NULL)
635                                 continue;
636                         tmp = xstrdup(database[e.item][id]);
637                         if( tmp && strstr(strlower(tmp), findstr) ) {
638                                 ret = e.item;
639                                 goto out;
640                         }
641                         xfree(tmp);
642                 }
643         }
644
645 out:
646         free(findstr);
647         free(tmp);
648         return ret;
649 }
650
651 int
652 is_selected(int item)
653 {
654         return selected[item];
655 }
656
657 int
658 is_valid_item(int item)
659 {
660         return item <= LAST_ITEM && item >= 0;
661 }
662
663 int
664 last_item()
665 {
666         return LAST_ITEM;
667 }
668
669 int
670 db_n_items()
671 {
672         return items;
673 }
674
675 int
676 real_db_enumerate_items(struct db_enumerator e)
677 {
678         int item = max(0, e.item + 1);
679         int i;
680
681         switch(e.mode) {
682 #ifdef DEBUG
683                 case ENUM_ALL:
684                         break;
685 #endif
686                 case ENUM_SELECTED:
687                         for(i = item; i <= LAST_ITEM; i++) {
688                                 if(is_selected(i)) {
689                                         item = i;
690                                         goto out;
691                                 }
692                         }
693                         return -1;
694 #ifdef DEBUG
695                 default:
696                         fprintf(stderr, "real_db_enumerate_items() "
697                                         "BUG: unknown db_enumerator mode: %d\n",
698                                         e.mode);
699                         break;
700 #endif
701         }
702 out:
703         return (item > LAST_ITEM || item < 0) ? -1 : item;
704 }
705
706 struct db_enumerator
707 init_db_enumerator(int mode)
708 {
709         struct db_enumerator e;
710
711         e.item = -1; /* important - means "start from beginning" */
712         e.mode = mode;
713
714         return e;
715 }
716
717
718 list_item
719 item_create()
720 {
721         return xmalloc0(ITEM_SIZE);
722 }
723
724 void
725 item_free(list_item *item)
726 {
727         assert(item);
728
729         xfree(*item);
730 }
731
732 void
733 item_empty(list_item item)
734 {       int i;
735
736         assert(item);
737
738         for(i = 0; i < fields_count; i++)
739                 if(item[i])
740                         xfree(item[i]);
741
742 }
743
744 void
745 item_copy(list_item dest, list_item src)
746 {
747         memmove(dest, src, ITEM_SIZE);
748 }
749
750 void
751 item_duplicate(list_item dest, list_item src)
752 {
753         int i;
754
755         for(i = 0; i < fields_count; i++)
756                 dest[i] = src[i] ? xstrdup(src[i]) : NULL;
757 }
758
759 /* 
760  * Things like item[field_id(NICK)] should never be used, since besides NAME
761  * and EMAIL, none of the standard fields can be assumed to be existing.
762  *
763  * Prefer the functions item_fput(), item_fget(), db_fput() and db_fget()
764  * to access fields in items and database.
765  */
766
767 /* quick lookup by "standard" field number */
768 inline int
769 field_id(int i)
770 {
771         assert((i >= 0) && (i < ITEM_FIELDS));
772         return standard_fields_indexed[i];
773 }
774
775 int
776 item_fput(list_item item, int i, char *val)
777 {
778         int id = field_id(i);
779
780         if(id != -1) {
781                 item[id] = val;
782                 return 1;
783         }
784
785         return 0;
786 }
787
788 char *
789 item_fget(list_item item, int i)
790 {
791         int id = field_id(i);
792
793         if(id != -1)
794                 return item[id];
795         else
796                 return NULL;
797 }
798
799 int
800 real_db_field_put(int item, int i, int std, char *val)
801 {
802         int id;
803
804         assert(database[item]);
805
806         id = std ? field_id(i) : i;
807
808         if(id != -1) {
809                 database[item][id] = val;
810                 return 1;
811         }
812
813         return 0;
814 }
815
816 char *
817 real_db_field_get(int item, int i, int std)
818 {
819         int id;
820
821         assert(database[item]);
822
823         id = std ? field_id(i) : i;
824
825         if(id != -1)
826                 return database[item][id];
827         else
828                 return NULL;
829 }
830
831 list_item
832 db_item_get(int i)
833 {
834         return database[i];
835 }
836
837 /* Fetch addresses from all fields of FIELD_EMAILS type */
838 /* Memory has to be freed by the caller */
839 char *
840 db_email_get(int item)
841 {
842         int i;
843         char *res;
844         abook_field_list *cur;
845         abook_list *emails = NULL;
846
847         for(cur = fields_list, i = 0; cur; cur = cur->next, i++)
848                 if(cur->field->type == FIELD_EMAILS && *database[item][i])
849                         abook_list_append(&emails, database[item][i]);
850
851         res = abook_list_to_csv(emails);
852         abook_list_free(&emails);
853         return res ? res : xstrdup("");
854 }
855