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