]> git.deb.at Git - pkg/abook.git/blob - abook.c
ldif: support parsing from stdin
[pkg/abook.git] / abook.c
1 /*
2  * $Id$
3  *
4  * by JH <jheinonen@users.sourceforge.net>
5  *
6  * Copyright (C) Jaakko Heinonen
7  */
8
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <ctype.h>
12 #include <signal.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <unistd.h>
17 #include <sys/stat.h>
18 #ifdef HAVE_CONFIG_H
19 #       include "config.h"
20 #endif
21 #if defined(HAVE_LOCALE_H) && defined(HAVE_SETLOCALE)
22 #       include <locale.h>
23 #endif
24 #include <assert.h>
25 #include "abook.h"
26 #include "gettext.h"
27 #include "ui.h"
28 #include "database.h"
29 #include "list.h"
30 #include "filter.h"
31 #include "edit.h"
32 #include "misc.h"
33 #include "options.h"
34 #include "getname.h"
35 #include "getopt.h"
36 #include "views.h"
37 #include "xmalloc.h"
38
39 static void             init_abook();
40 static void             quit_abook_sig(int i);
41 static void             set_filenames();
42 static void             parse_command_line(int argc, char **argv);
43 static void             show_usage();
44 static void             mutt_query(char *str);
45 static void             init_mutt_query();
46 static void             convert(char *srcformat, char *srcfile,
47                                 char *dstformat, char *dstfile);
48 static void             add_email(int);
49
50 char *datafile = NULL;
51 static char *rcfile = NULL;
52
53 // custom formatting
54 char custom_format[FORMAT_STRING_LEN] = "{nick} ({name}): {mobile}";
55 char *parsed_custom_format = NULL;
56 enum field_types *custom_format_fields = 0;
57 struct abook_output_item_filter selected_item_filter;
58
59 bool alternative_datafile = FALSE;
60 bool alternative_rcfile = FALSE;
61
62
63 static int
64 datafile_writeable()
65 {
66         FILE *f;
67
68         assert(datafile != NULL);
69
70         if( (f = fopen(datafile, "a")) == NULL)
71                 return FALSE;
72
73         fclose(f);
74
75         return TRUE;
76 }
77
78 static void
79 check_abook_directory()
80 {
81         struct stat s;
82         char *dir;
83
84         assert(!is_ui_initialized());
85
86         if(alternative_datafile)
87                 return;
88
89         dir = strconcat(getenv("HOME"), "/" DIR_IN_HOME, NULL);
90         assert(dir != NULL);
91
92         if(stat(dir, &s) == -1) {
93                 if(errno != ENOENT) {
94                         perror(dir);
95                         free(dir);
96                         exit(EXIT_FAILURE);
97                 }
98                 if(mkdir(dir, 0700) == -1) {
99                         printf(_("Cannot create directory %s\n"), dir);
100                         perror(dir);
101                         free(dir);
102                         exit(EXIT_FAILURE);
103                 }
104         } else if(!S_ISDIR(s.st_mode)) {
105                 printf(_("%s is not a directory\n"), dir);
106                 free(dir);
107                 exit(EXIT_FAILURE);
108         }
109
110         free(dir);
111 }
112
113 static void
114 xmalloc_error_handler(int err)
115 {
116         /*
117          * We don't try to save addressbook here because we don't know
118          * if it's fully loaded to memory.
119          */
120         if(is_ui_initialized())
121                 close_ui();
122
123         fprintf(stderr, _("Memory allocation failure: %s\n"), strerror(err));
124         exit(EXIT_FAILURE);
125 }
126
127 static void
128 init_abook()
129 {
130         set_filenames();
131         check_abook_directory();
132         init_opts();
133         if(load_opts(rcfile) > 0) {
134                 printf(_("Press enter to continue...\n"));
135                 fgetc(stdin);
136         }
137         init_default_views();
138
139         signal(SIGTERM, quit_abook_sig);
140
141         init_index();
142
143         if(init_ui())
144                 exit(EXIT_FAILURE);
145
146         umask(DEFAULT_UMASK);
147
148         if(!datafile_writeable()) {
149                 char *s = strdup_printf(_("File %s is not writeable"), datafile);
150                 refresh_screen();
151                 statusline_msg(s);
152                 free(s);
153                 if(load_database(datafile) || !statusline_ask_boolean(
154                                         _("If you continue all changes will "
155                                 "be lost. Do you want to continue?"), FALSE)) {
156                         free_opts();
157                         /*close_database();*/
158                         close_ui();
159                         exit(EXIT_FAILURE);
160                 }
161         } else
162                 load_database(datafile);
163
164         refresh_screen();
165 }
166
167 void
168 quit_abook(int save_db)
169 {
170         if(save_db)  {
171                 if(opt_get_bool(BOOL_AUTOSAVE))
172                         save_database();
173                 else if(statusline_ask_boolean(_("Save database"), TRUE))
174                         save_database();
175         } else if(!statusline_ask_boolean(_("Quit without saving"), FALSE))
176                 return;
177
178         free_opts();
179         close_database();
180
181         close_ui();
182
183         exit(EXIT_SUCCESS);
184 }
185
186 static void
187 quit_abook_sig(int i)
188 {
189         quit_abook(QUIT_SAVE);
190 }
191
192 int
193 main(int argc, char **argv)
194 {
195 #if defined(HAVE_SETLOCALE) && defined(HAVE_LOCALE_H)
196         setlocale(LC_MESSAGES, "");
197         setlocale(LC_TIME, "");
198         setlocale(LC_CTYPE, "");
199         setlocale(LC_COLLATE, "");
200 #endif
201         bindtextdomain(PACKAGE, LOCALEDIR);
202         textdomain(PACKAGE);
203
204         xmalloc_set_error_handler(xmalloc_error_handler);
205
206         prepare_database_internals();
207
208         parse_command_line(argc, argv);
209
210         init_abook();
211
212         get_commands();
213
214         quit_abook(QUIT_SAVE);
215
216         return 0;
217 }
218
219 static void
220 free_filenames()
221 {
222         xfree(rcfile);
223         xfree(datafile);
224 }
225
226
227 static void
228 set_filenames()
229 {
230         struct stat s;
231
232         if( (stat(getenv("HOME"), &s)) == -1 || ! S_ISDIR(s.st_mode) ) {
233                 fprintf(stderr,_("%s is not a valid HOME directory\n"), getenv("HOME") );
234                 exit(EXIT_FAILURE);
235         }
236
237         if(!datafile)
238                 datafile = strconcat(getenv("HOME"), "/" DIR_IN_HOME "/"
239                                 DATAFILE, NULL);
240
241         if(!rcfile)
242                 rcfile = strconcat(getenv("HOME"), "/" DIR_IN_HOME "/"
243                                 RCFILE, NULL);
244
245         atexit(free_filenames);
246 }
247
248 /*
249  * CLI
250  */
251
252 enum {
253         MODE_CONT,
254         MODE_ADD_EMAIL,
255         MODE_ADD_EMAIL_QUIET,
256         MODE_QUERY,
257         MODE_CONVERT
258 };
259
260 static void
261 change_mode(int *current, int mode)
262 {
263         if(*current != MODE_CONT) {
264                 fprintf(stderr, _("Cannot combine options --mutt-query, "
265                                 "--convert, "
266                                 "--add-email or --add-email-quiet\n"));
267                 exit(EXIT_FAILURE);
268         }
269
270         *current = mode;
271 }
272
273 void
274 set_filename(char **var, char *path)
275 {
276         char *cwd;
277
278         assert(var != NULL);
279         assert(*var == NULL); /* or else we probably leak memory */
280         assert(path != NULL);
281
282         if(*path == '/') {
283                 *var = xstrdup(path);
284                 return;
285         }
286
287         cwd = my_getcwd();
288
289         *var = strconcat(cwd, "/", path, NULL);
290
291         free(cwd);
292 }
293
294 #define set_convert_var(X) do { if(mode != MODE_CONVERT) {\
295         fprintf(stderr, _("please use option --%s after --convert option\n"),\
296                         long_options[option_index].name);\
297                 exit(EXIT_FAILURE);\
298         } else\
299                 X = optarg;\
300         } while(0)
301
302 static void
303 parse_command_line(int argc, char **argv)
304 {
305         int mode = MODE_CONT;
306         char *query_string = NULL;
307         char *informat = "abook",
308                 *outformat = "text",
309                 *infile = "-",
310                 *outfile = "-";
311         int c;
312         selected_item_filter = select_output_item_filter("muttq");
313
314         for(;;) {
315                 int option_index = 0;
316                 enum {
317                         OPT_ADD_EMAIL,
318                         OPT_ADD_EMAIL_QUIET,
319                         OPT_MUTT_QUERY,
320                         OPT_CONVERT,
321                         OPT_INFORMAT,
322                         OPT_OUTFORMAT,
323                         OPT_OUTFORMAT_STR,
324                         OPT_INFILE,
325                         OPT_OUTFILE,
326                         OPT_FORMATS
327                 };
328                 static struct option long_options[] = {
329                         { "help", 0, 0, 'h' },
330                         { "add-email", 0, 0, OPT_ADD_EMAIL },
331                         { "add-email-quiet", 0, 0, OPT_ADD_EMAIL_QUIET },
332                         { "datafile", 1, 0, 'f' },
333                         { "mutt-query", 1, 0, OPT_MUTT_QUERY },
334                         { "config", 1, 0, 'C' },
335                         { "convert", 0, 0, OPT_CONVERT },
336                         { "informat", 1, 0, OPT_INFORMAT },
337                         { "outformat", 1, 0, OPT_OUTFORMAT },
338                         { "outformatstr", 1, 0, OPT_OUTFORMAT_STR },
339                         { "infile", 1, 0, OPT_INFILE },
340                         { "outfile", 1, 0, OPT_OUTFILE },
341                         { "formats", 0, 0, OPT_FORMATS },
342                         { 0, 0, 0, 0 }
343                 };
344
345                 c = getopt_long(argc, argv, "hC:",
346                                 long_options, &option_index);
347
348                 if(c == -1)
349                         break;
350
351                 switch(c) {
352                         case 'h':
353                                 show_usage();
354                                 exit(EXIT_SUCCESS);
355                         case OPT_ADD_EMAIL:
356                                 change_mode(&mode, MODE_ADD_EMAIL);
357                                 break;
358                         case OPT_ADD_EMAIL_QUIET:
359                                 change_mode(&mode, MODE_ADD_EMAIL_QUIET);
360                                 break;
361                         case 'f':
362                                 set_filename(&datafile, optarg);
363                                 alternative_datafile = TRUE;
364                                 break;
365                         case OPT_MUTT_QUERY:
366                                 query_string = optarg;
367                                 change_mode(&mode, MODE_QUERY);
368                                 break;
369                         case 'C':
370                                 set_filename(&rcfile, optarg);
371                                 alternative_rcfile = TRUE;
372                                 break;
373                         case OPT_CONVERT:
374                                 change_mode(&mode, MODE_CONVERT);
375                                 break;
376                         case OPT_INFORMAT:
377                                 set_convert_var(informat);
378                                 break;
379                         case OPT_OUTFORMAT:
380                                 if(mode != MODE_CONVERT && mode != MODE_QUERY) {
381                                   fprintf(stderr,
382                                           _("please use option --outformat after --convert or --mutt-query option\n"));
383                                   exit(EXIT_FAILURE);
384                                 }
385                                 // ascii-name is stored, it's used to traverse
386                                 // e_filters[] in MODE_CONVERT (see export_file())
387                                 outformat = optarg;
388                                 // but in case a query-compatible filter is requested
389                                 // try to guess right now which one it is, from u_filters[]
390                                 selected_item_filter = select_output_item_filter(outformat);
391                                 break;
392                         case OPT_OUTFORMAT_STR:
393                                 strncpy(custom_format, optarg, FORMAT_STRING_LEN - 1);
394                                 custom_format[FORMAT_STRING_LEN] = 0;
395                                 break;
396                         case OPT_INFILE:
397                                 set_convert_var(infile);
398                                 break;
399                         case OPT_OUTFILE:
400                                 set_convert_var(outfile);
401                                 break;
402                         case OPT_FORMATS:
403                                 print_filters();
404                                 exit(EXIT_SUCCESS);
405                         default:
406                                 exit(EXIT_FAILURE);
407                 }
408         }
409
410         // if the output format requested does not allow filtered querying
411         // (not in u_filter[]) and --convert has not been specified; bailout
412         if(! selected_item_filter.func && mode != MODE_CONVERT) {
413           printf("output format %s not supported or incompatible with --mutt-query\n", outformat);
414           exit(EXIT_FAILURE);
415         }
416         if(! selected_item_filter.func)
417                 selected_item_filter = select_output_item_filter("muttq");
418         else if (! strcmp(outformat, "custom") && *custom_format) {
419                 parsed_custom_format = (char *)malloc(FORMAT_STRING_LEN * sizeof(char*));
420                 custom_format_fields = (enum field_types *)malloc(FORMAT_STRING_MAX_FIELDS * sizeof(enum field_types *));
421                 parse_custom_format(custom_format, parsed_custom_format, custom_format_fields);
422         }
423         if(optind < argc) {
424                 fprintf(stderr, _("%s: unrecognized arguments on command line\n"),
425                                 argv[0]);
426                 exit(EXIT_FAILURE);
427         }
428
429         switch(mode) {
430                 case MODE_ADD_EMAIL:
431                         add_email(0);
432                 case MODE_ADD_EMAIL_QUIET:
433                         add_email(1);
434                 case MODE_QUERY:
435                         mutt_query(query_string);
436                 case MODE_CONVERT:
437                         convert(informat, infile, outformat, outfile);
438         }
439 }
440
441
442 static void
443 show_usage()
444 {
445         puts    (PACKAGE " v " VERSION "\n");
446         puts    (_("     -h     --help                          show usage"));
447         puts    (_("     -C     --config        <file>          use an alternative configuration file"));
448         puts    (_("    --datafile      <file>          use an alternative addressbook file"));
449         puts    (_("    --mutt-query    <string>        make a query for mutt"));
450         puts    (_("    --add-email                     "
451                         "read an e-mail message from stdin and\n"
452                 "                                       "
453                 "add the sender to the addressbook"));
454         puts    (_("    --add-email-quiet               "
455                 "same as --add-email but doesn't\n"
456                 "                                       require to confirm adding"));
457         putchar('\n');
458         puts    (_("    --convert                       convert address book files"));
459         puts    (_("    options to use with --convert:"));
460         puts    (_("    --informat      <format>        format for input file"));
461         puts    (_("                                    (default: abook)"));
462         puts    (_("    --infile        <file>          source file"));
463         puts    (_("                                    (default: stdin)"));
464         puts    (_("    --outformat     <format>        format for output file"));
465         puts    (_("                                    (default: text)"));
466         puts    (_("    --outfile       <file>          destination file"));
467         puts    (_("                                    (default: stdout)"));
468         puts    (_("    --formats                       list available formats"));
469 }
470
471 /*
472  * end of CLI
473  */
474
475
476 static void
477 quit_mutt_query(int status)
478 {
479         close_database();
480         free_opts();
481
482         exit(status);
483 }
484
485 static void
486 mutt_query(char *str)
487 {
488         init_mutt_query();
489
490         if( str == NULL || !strcasecmp(str, "all") ) {
491                 export_file("muttq", "-");
492         } else {
493                 int search_fields[] = {NAME, EMAIL, NICK, -1};
494                 int i;
495                 if( (i = find_item(str, 0, search_fields)) < 0 ) {
496                         printf("Not found\n");
497                         quit_mutt_query(EXIT_FAILURE);
498                 }
499                 putchar('\n');
500                 while(i >= 0) {
501                         e_write_item(stdout, i, selected_item_filter.func);
502                         i = find_item(str, i + 1, search_fields);
503                 }
504         }
505
506         quit_mutt_query(EXIT_SUCCESS);
507 }
508
509 static void
510 init_mutt_query()
511 {
512         set_filenames();
513         init_opts();
514         load_opts(rcfile);
515
516         if( load_database(datafile) ) {
517                 printf(_("Cannot open database\n"));
518                 quit_mutt_query(EXIT_FAILURE);
519                 exit(EXIT_FAILURE);
520         }
521 }
522
523
524 static char *
525 make_mailstr(int item)
526 {
527         char email[MAX_EMAIL_LEN];
528         char *ret;
529         char *name = strdup_printf("\"%s\"", db_name_get(item));
530
531         get_first_email(email, item);
532
533         ret = *email ?
534                 strdup_printf("%s <%s>", name, email) :
535                 xstrdup(name);
536
537         free(name);
538
539         return ret;
540 }
541
542 void
543 print_stderr(int item)
544 {
545         fprintf (stderr, "%c", '\n');
546
547         if( is_valid_item(item) )
548                 muttq_print_item(stderr, item);
549         else {
550                 struct db_enumerator e = init_db_enumerator(ENUM_SELECTED);
551                 db_enumerate_items(e) {
552                         muttq_print_item(stderr, e.item);
553                 }
554         }
555
556 }
557
558 void
559 launch_mutt(int item)
560 {
561         char *cmd = NULL, *mailstr = NULL;
562         char *mutt_command = opt_get_str(STR_MUTT_COMMAND);
563
564         if(mutt_command == NULL || !*mutt_command)
565                 return;
566
567         if( is_valid_item(item) )
568                 mailstr = make_mailstr(item);
569         else {
570                 struct db_enumerator e = init_db_enumerator(ENUM_SELECTED);
571                 char *tmp = NULL;
572                 db_enumerate_items(e) {
573                         tmp = mailstr;
574                         mailstr = tmp ?
575                                 strconcat(tmp, ",", make_mailstr(e.item), NULL):
576                                 strconcat(make_mailstr(e.item), NULL);
577                         free(tmp);
578                 }
579         }
580
581         cmd = strconcat(mutt_command, " \'", mailstr, "\'", NULL);
582         free(mailstr);
583 #ifdef DEBUG
584         fprintf(stderr, "cmd: %s\n", cmd);
585 #endif
586         system(cmd);
587         free(cmd);
588
589         /*
590          * we need to make sure that curses settings are correct
591          */
592         ui_init_curses();
593 }
594
595 void
596 launch_wwwbrowser(int item)
597 {
598         char *cmd = NULL;
599
600         if( !is_valid_item(item) )
601                 return;
602
603         if(db_fget(item, URL))
604                 cmd = strdup_printf("%s '%s'",
605                                 opt_get_str(STR_WWW_COMMAND),
606                                 safe_str(db_fget(item, URL)));
607         else
608                 return;
609
610         if ( cmd )
611                 system(cmd);
612
613         free(cmd);
614
615         /*
616          * we need to make sure that curses settings are correct
617          */
618         ui_init_curses();
619 }
620
621 FILE *
622 abook_fopen (const char *path, const char *mode)
623 {
624         struct stat s;
625         bool stat_ok;
626
627         stat_ok = (stat(path, &s) != -1);
628
629         if(strchr(mode, 'r'))
630                 return (stat_ok && S_ISREG(s.st_mode)) ?
631                         fopen(path, mode) : NULL;
632         else
633                 return (stat_ok && S_ISDIR(s.st_mode)) ?
634                         NULL : fopen(path, mode);
635 }
636
637 static void
638 convert(char *srcformat, char *srcfile, char *dstformat, char *dstfile)
639 {
640         int ret=0;
641
642         if( !srcformat || !srcfile || !dstformat || !dstfile ) {
643                 fprintf(stderr, _("too few arguments to make conversion\n"));
644                 fprintf(stderr, _("try --help\n"));
645         }
646
647 #ifndef DEBUG
648         if( !strcasecmp(srcformat, dstformat) ) {
649                 printf( _("input and output formats are the same\n"
650                         "exiting...\n"));
651                 exit(EXIT_FAILURE);
652         }
653 #endif
654
655         set_filenames();
656         init_opts();
657         load_opts(rcfile);
658         init_standard_fields();
659
660         switch(import_file(srcformat, srcfile)) {
661                 case -1:
662                         fprintf(stderr,
663                                 _("input format %s not supported\n"), srcformat);
664                         ret = 1;
665                         break;
666                 case 1:
667                         fprintf(stderr, _("cannot read file %s\n"), srcfile);
668                         ret = 1;
669                         break;
670         }
671
672         if(!ret)
673                 switch(export_file(dstformat, dstfile)) {
674                         case -1:
675                                 fprintf(stderr,
676                                         _("output format %s not supported\n"),
677                                         dstformat);
678                                 ret = 1;
679                                 break;
680                         case 1:
681                                 fprintf(stderr,
682                                         _("cannot write file %s\n"), dstfile);
683                                 ret = 1;
684                                 break;
685                 }
686
687         close_database();
688         free_opts();
689         exit(ret);
690 }
691
692 /*
693  * --add-email handling
694  */
695
696 static int add_email_count = 0;
697
698 static void
699 quit_add_email()
700 {
701         if(add_email_count > 0) {
702                 if(save_database() < 0) {
703                         fprintf(stderr, _("cannot open %s\n"), datafile);
704                         exit(EXIT_FAILURE);
705                 }
706                 printf(_("%d item(s) added to %s\n"), add_email_count, datafile);
707         } else {
708                 puts(_("Valid sender address not found"));
709         }
710
711         exit(EXIT_SUCCESS);
712 }
713
714 static void
715 quit_add_email_sig(int signal)
716 {
717         quit_add_email();
718 }
719
720 static void
721 init_add_email()
722 {
723         set_filenames();
724         check_abook_directory();
725         init_opts();
726         load_opts(rcfile);
727         init_standard_fields();
728         atexit(free_opts);
729
730         /*
731          * we don't actually care if loading fails or not
732          */
733         load_database(datafile);
734
735         atexit(close_database);
736
737         signal(SIGINT, quit_add_email_sig);
738 }
739
740 static int
741 add_email_add_item(int quiet, char *name, char *email)
742 {
743         list_item item;
744
745         if(opt_get_bool(BOOL_ADD_EMAIL_PREVENT_DUPLICATES)) {
746                 int search_fields[] = { EMAIL, -1 };
747                 if(find_item(email, 0, search_fields) >= 0) {
748                         if(!quiet)
749                                 printf(_("Address %s already in addressbook\n"),
750                                                 email);
751                         return 0;
752                 }
753         }
754
755         if(!quiet) {
756                 FILE *in = fopen("/dev/tty", "r");
757                 char c;
758                 if(!in) {
759                         fprintf(stderr, _("cannot open /dev/tty\n"
760                                 "you may want to use --add-email-quiet\n"));
761                         exit(EXIT_FAILURE);
762                 }
763
764                 do {
765                         printf(_("Add \"%s <%s>\" to %s? (%c/%c)\n"),
766                                         name,
767                                         email,
768                                         datafile,
769                                         *S_("keybinding for yes|y"),
770                                         *S_("keybinding for no|n"));
771                         c = tolower(getc(in));
772                         if(c == *S_("keybinding for no|n")) {
773                                 fclose(in);
774                                 return 0;
775                         }
776                 } while(c != *S_("keybinding for yes|y"));
777                 fclose(in);
778         }
779
780         item = item_create();
781         item_fput(item, NAME, xstrdup(name));
782         item_fput(item, EMAIL, xstrdup(email));
783         add_item2database(item);
784         item_free(&item);
785
786         return 1;
787 }
788
789 static void
790 add_email(int quiet)
791 {
792         char *line;
793         char *name = NULL, *email = NULL;
794         struct stat s;
795
796         if( (fstat(fileno(stdin), &s)) == -1 || S_ISDIR(s.st_mode) ) {
797                 fprintf(stderr, _("stdin is a directory or cannot stat stdin\n"));
798                 exit(EXIT_FAILURE);
799         }
800
801         init_add_email();
802
803         do {
804                 line = getaline(stdin);
805                 if(line && !strncasecmp("From:", line, 5) ) {
806                         getname(line, &name, &email);
807                         add_email_count += add_email_add_item(quiet,
808                                         name, email);
809                         xfree(name);
810                         xfree(email);
811                 }
812                 xfree(line);
813         } while( !feof(stdin) );
814
815         quit_add_email();
816 }
817
818 /*
819  * end of --add-email handling
820  */