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