]> git.deb.at Git - pkg/abook.git/blob - database.c
Merge branch 'dh'
[pkg/abook.git] / database.c
1
2 /*
3  * $Id: database.c,v 1.39 2006/09/04 18:29:25 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_DATE},   /* 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_info(int i, char **key, char **name, int *type)
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         if(type)
139                 *type = (i < 0) ? -1 : cur->field->type;
140 }
141
142 void
143 add_field(abook_field_list **list, abook_field *f)
144 {
145         abook_field_list *tmp;
146
147         for(tmp = *list; tmp && tmp->next; tmp = tmp->next)
148                 ;
149
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));
155
156         tmp->field = f;
157         tmp->next = NULL;
158 }
159
160 char *
161 declare_new_field(char *key, char *name, char *type, int accept_standard)
162 {
163         abook_field *f;
164
165         if(find_declared_field(key))
166                 return _("field already defined");
167
168         if(find_standard_field(key, accept_standard))
169                 return accept_standard ? NULL /* ok, added */ :
170                         _("standard field does not need to be declared");
171
172         f = xmalloc(sizeof(abook_field));
173         f->key = xstrdup(key);
174         f->name = xstrdup(name);
175
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");
186
187         add_field(&fields_list, f);
188         fields_count++;
189
190         return NULL;
191 }
192
193 /*
194  * Declare a new field while database is already loaded
195  * making it grow accordingly
196  */
197 static void
198 declare_unknown_field(char *key)
199 {
200         int i;
201
202         declare_new_field(key, key, "string",
203                         1 /* accept to declare "standard" fields */);
204
205         if(!database)
206                 return;
207
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;
212                 }
213 }
214
215 /*
216  * Declare "standard" fields, thus preserving them while parsing a database,
217  * even if they won't be displayed.
218  */
219 void
220 init_standard_fields()
221 {
222         int i;
223
224         for(i = 0; standard_fields[i].key; i++)
225                 if(standard_fields_indexed[i] == -1)
226                         declare_standard_field(i);
227 }
228
229 /* Some initializations - Must be called _before_ load_opts() */
230 void
231 prepare_database_internals()
232 {
233         int i;
234
235         for(i = 0; i < ITEM_FIELDS; i++)
236                 standard_fields_indexed[i] = -1;
237
238         /* the only two mandatory fields */
239         declare_standard_field(NAME);
240         declare_standard_field(EMAIL);
241 }
242
243 int
244 parse_database(FILE *in)
245 {
246         char *line = NULL;
247         char *tmp;
248         int sec=0, field;
249         list_item item;
250
251         item = item_create();
252
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);
260                         }
261                         break;
262                 }
263
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);
271                         }
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;
288                         }
289                 }
290 next:
291                 xfree(line);
292         }
293
294         xfree(line);
295         item_free(&item);
296         return 0;
297 }
298
299 int
300 load_database(char *filename)
301 {
302         FILE *in;
303
304         if(database != NULL)
305                 close_database();
306
307         if ((in = abook_fopen(filename, "r")) == NULL)
308                 return -1;
309
310         parse_database(in);
311
312         return (items == 0) ? 2 : 0;
313 }
314
315 int
316 write_database(FILE *out, struct db_enumerator e)
317 {
318         int j;
319         int i = 0;
320         abook_field_list *cur;
321
322         fprintf(out,
323                 "# abook addressbook file\n\n"
324                 "[format]\n"
325                 "program=" PACKAGE "\n"
326                 "version=" VERSION "\n"
327                 "\n\n"
328         );
329
330         db_enumerate_items(e) {
331                 fprintf(out, "[%d]\n", i);
332
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]
339                                         );
340                 }
341
342                 fputc('\n', out);
343                 i++;
344         }
345
346         return 0;
347 }
348
349 int
350 save_database()
351 {
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);
357
358         if( (out = abook_fopen(datafile_new, "w")) == NULL ) {
359                 ret = -1;
360                 goto out;
361         }
362
363         if(!list_is_empty())
364                 /*
365                  * Possibly should check if write_database failed.
366                  * Currently it returns always zero.
367                  */
368                 write_database(out, e);
369
370         fclose(out);
371
372         if(access(datafile, F_OK) == 0 &&
373                         (rename(datafile, datafile_old)) == -1)
374                 ret = -1;
375         
376         if((rename(datafile_new, datafile)) == -1)
377                 ret = -1;
378
379 out:
380         free(datafile_new);
381         free(datafile_old);
382         return ret;
383 }
384
385 static void
386 db_free_item(int item)
387 {
388         item_empty(database[item]);
389 }
390
391 void
392 close_database()
393 {
394         int i;
395
396         for(i=0; i <= LAST_ITEM; i++)
397                 db_free_item(i);
398
399         xfree(database);
400         free(selected);
401
402         database = NULL;
403         selected = NULL;
404
405         items = 0;
406         first_list_item = curitem = -1;
407         list_capacity = 0;
408 }
409
410
411 static void
412 validate_item(list_item item)
413 {
414         abook_field_list *f;
415         int i, max_field_len;
416         char *tmp;
417
418         for(f = fields_list, i = 0; f; f = f->next, i++) {
419                 max_field_len = 0;
420
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);
437                 }
438
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);
446                 }
447         }
448 }
449
450 static void
451 adjust_list_capacity()
452 {
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;
461
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);
466
467         selected = xrealloc(selected, list_capacity);
468 }
469
470 int
471 add_item2database(list_item item)
472 {
473         /* 'name' field is mandatory */
474         if((item[field_id(NAME)] == NULL) || ! *item[field_id(NAME)]) {
475                 item_empty(item);
476                 return 1;
477         }
478
479         if(++items > list_capacity)
480                 adjust_list_capacity();
481
482         validate_item(item);
483
484         selected[LAST_ITEM] = 0;
485
486         database[LAST_ITEM] = item_create();
487         item_copy(database[LAST_ITEM], item);
488
489         return 0;
490 }
491         
492
493 void
494 remove_selected_items()
495 {
496         int i, j;
497
498         if(list_is_empty())
499                 return;
500
501         if(!selected_items())
502                 selected[curitem] = 1;
503
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];
510                         }
511                         item_free(&database[LAST_ITEM]);
512                         items--;
513                 }
514         }
515
516         if(curitem > LAST_ITEM && items > 0)
517                 curitem = LAST_ITEM;
518
519         adjust_list_capacity();
520
521         select_none();
522 }
523
524 char *
525 get_surname(char *s)
526 {
527         char *p = s + strlen(s);
528
529         assert(s != NULL);
530
531         while(p > s && *(p - 1) != ' ')
532                 p--;
533
534         return xstrdup(p);
535 }
536
537 static int
538 surnamecmp(const void *i1, const void *i2)
539 {
540         int ret, idx = field_id(NAME);
541         char *n1, *n2, *s1, *s2;
542
543         n1 = (*(list_item *)i1)[idx];
544         n2 = (*(list_item *)i2)[idx];
545         
546         s1 = get_surname(n1);
547         s2 = get_surname(n2);
548
549         if( !(ret = safe_strcoll(s1, s2)) )
550                 ret = safe_strcoll(n1, n2);
551
552         free(s1);
553         free(s2);
554
555         return ret;
556 }
557
558 static int sort_field = -1;
559
560 static int
561 namecmp(const void *i1, const void *i2)
562 {
563         char *n1, *n2;
564
565         assert(sort_field >= 0 && sort_field < fields_count);
566
567         n1 = (*(list_item *)i1)[sort_field];
568         n2 = (*(list_item *)i2)[sort_field];
569
570         return safe_strcoll(n1, n2);
571 }
572
573 void
574 sort_by_field(char *name)
575 {
576         int field;
577
578         select_none();
579
580         name = (name == NULL) ? opt_get_str(STR_SORT_FIELD) : name;
581         find_field_number(name, &field);
582
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"));
589
590                 return;
591         }
592
593         sort_field = field;
594
595         qsort((void *)database, items, sizeof(list_item), namecmp);
596
597         refresh_screen();
598 }
599
600 void
601 sort_surname()
602 {
603         select_none();
604
605         qsort((void *)database, items, sizeof(list_item), surnamecmp);
606
607         refresh_screen();
608 }
609
610 /* TODO implement a search based on more sophisticated patterns */
611 int
612 find_item(char *str, int start, int search_fields[])
613 {
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);
619
620         if(list_is_empty() || !is_valid_item(start))
621                 return -2; /* error */
622
623         findstr = xstrdup(str);
624         findstr = strlower(findstr);
625
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;
637                         }
638                         xfree(tmp);
639                 }
640         }
641
642 out:
643         free(findstr);
644         free(tmp);
645         return ret;
646 }
647
648 int
649 is_selected(int item)
650 {
651         return selected[item];
652 }
653
654 int
655 is_valid_item(int item)
656 {
657         return item <= LAST_ITEM && item >= 0;
658 }
659
660 int
661 last_item()
662 {
663         return LAST_ITEM;
664 }
665
666 int
667 db_n_items()
668 {
669         return items;
670 }
671
672 int
673 real_db_enumerate_items(struct db_enumerator e)
674 {
675         int item = max(0, e.item + 1);
676         int i;
677
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;
688                                 }
689                         }
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
698         }
699 out:
700         return (item > LAST_ITEM || item < 0) ? -1 : item;
701 }
702
703 struct db_enumerator
704 init_db_enumerator(int mode)
705 {
706         struct db_enumerator e;
707
708         e.item = -1; /* important - means "start from beginning" */
709         e.mode = mode;
710
711         return e;
712 }
713
714
715 list_item
716 item_create()
717 {
718         return xmalloc0(ITEM_SIZE);
719 }
720
721 void
722 item_free(list_item *item)
723 {
724         assert(item);
725
726         xfree(*item);
727 }
728
729 void
730 item_empty(list_item item)
731 {       int i;
732
733         assert(item);
734
735         for(i = 0; i < fields_count; i++)
736                 if(item[i])
737                         xfree(item[i]);
738
739 }
740
741 void
742 item_copy(list_item dest, list_item src)
743 {
744         memmove(dest, src, ITEM_SIZE);
745 }
746
747 void
748 item_duplicate(list_item dest, list_item src)
749 {
750         int i;
751
752         for(i = 0; i < fields_count; i++)
753                 dest[i] = src[i] ? xstrdup(src[i]) : NULL;
754 }
755
756 /* 
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.
759  *
760  * Prefer the functions item_fput(), item_fget(), db_fput() and db_fget()
761  * to access fields in items and database.
762  */
763
764 /* quick lookup by "standard" field number */
765 inline int
766 field_id(int i)
767 {
768         assert((i >= 0) && (i < ITEM_FIELDS));
769         return standard_fields_indexed[i];
770 }
771
772 int
773 item_fput(list_item item, int i, char *val)
774 {
775         int id = field_id(i);
776
777         if(id != -1) {
778                 item[id] = val;
779                 return 1;
780         }
781
782         return 0;
783 }
784
785 char *
786 item_fget(list_item item, int i)
787 {
788         int id = field_id(i);
789
790         if(id != -1)
791                 return item[id];
792         else
793                 return NULL;
794 }
795
796 int
797 real_db_field_put(int item, int i, int std, char *val)
798 {
799         int id;
800
801         assert(database[item]);
802
803         id = std ? field_id(i) : i;
804
805         if(id != -1) {
806                 database[item][id] = val;
807                 return 1;
808         }
809
810         return 0;
811 }
812
813 char *
814 real_db_field_get(int item, int i, int std)
815 {
816         int id;
817
818         assert(database[item]);
819
820         id = std ? field_id(i) : i;
821
822         if(id != -1)
823                 return database[item][id];
824         else
825                 return NULL;
826 }
827
828 list_item
829 db_item_get(int i)
830 {
831         return database[i];
832 }
833
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)
838 {
839         int i;
840         char *res;
841         abook_field_list *cur;
842         abook_list *emails = NULL;
843
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]);
847
848         res = abook_list_to_csv(emails);
849         abook_list_free(&emails);
850         return res ? res : xstrdup("");
851 }
852