use automake 1.11, autoconf 2.65
[abook.git] / abook.c
blobf20c45d1c6f9b0eb2f18b1e4f242b6b438b43a7b
1 /*
2 * $Id: abook.c,v 1.60 2006/09/04 18:29:24 cduval Exp $
4 * by JH <jheinonen@users.sourceforge.net>
6 * Copyright (C) Jaakko Heinonen
7 */
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"
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);
50 char *datafile = NULL;
51 static char *rcfile = NULL;
53 bool alternative_datafile = FALSE;
54 bool alternative_rcfile = FALSE;
57 static int
58 datafile_writeable()
60 FILE *f;
62 assert(datafile != NULL);
64 if( (f = fopen(datafile, "a")) == NULL)
65 return FALSE;
67 fclose(f);
69 return TRUE;
72 static void
73 check_abook_directory()
75 struct stat s;
76 char *dir;
78 assert(!is_ui_initialized());
80 if(alternative_datafile)
81 return;
83 dir = strconcat(getenv("HOME"), "/" DIR_IN_HOME, NULL);
84 assert(dir != NULL);
86 if(stat(dir, &s) == -1) {
87 if(errno != ENOENT) {
88 perror(dir);
89 free(dir);
90 exit(EXIT_FAILURE);
92 if(mkdir(dir, 0700) == -1) {
93 printf(_("Cannot create directory %s\n"), dir);
94 perror(dir);
95 free(dir);
96 exit(EXIT_FAILURE);
98 } else if(!S_ISDIR(s.st_mode)) {
99 printf(_("%s is not a directory\n"), dir);
100 free(dir);
101 exit(EXIT_FAILURE);
104 free(dir);
107 static void
108 xmalloc_error_handler(int err)
111 * We don't try to save addressbook here because we don't know
112 * if it's fully loaded to memory.
114 if(is_ui_initialized())
115 close_ui();
117 fprintf(stderr, _("Memory allocation failure: %s\n"), strerror(err));
118 exit(EXIT_FAILURE);
121 static void
122 init_abook()
124 set_filenames();
125 check_abook_directory();
126 init_opts();
127 if(load_opts(rcfile) > 0) {
128 printf(_("Press enter to continue...\n"));
129 fgetc(stdin);
131 init_default_views();
133 signal(SIGTERM, quit_abook_sig);
135 init_index();
137 if(init_ui())
138 exit(EXIT_FAILURE);
140 umask(DEFAULT_UMASK);
142 if(!datafile_writeable()) {
143 char *s = strdup_printf(_("File %s is not writeable"), datafile);
144 refresh_screen();
145 statusline_msg(s);
146 free(s);
147 if(load_database(datafile) || !statusline_ask_boolean(
148 _("If you continue all changes will "
149 "be lost. Do you want to continue?"), FALSE)) {
150 free_opts();
151 /*close_database();*/
152 close_ui();
153 exit(EXIT_FAILURE);
155 } else
156 load_database(datafile);
158 refresh_screen();
161 void
162 quit_abook(int save_db)
164 if(save_db) {
165 if(opt_get_bool(BOOL_AUTOSAVE))
166 save_database();
167 else if(statusline_ask_boolean(_("Save database"), TRUE))
168 save_database();
169 } else if(!statusline_ask_boolean(_("Quit without saving"), FALSE))
170 return;
172 free_opts();
173 close_database();
175 close_ui();
177 exit(EXIT_SUCCESS);
180 static void
181 quit_abook_sig(int i)
183 quit_abook(QUIT_SAVE);
187 main(int argc, char **argv)
189 #if defined(HAVE_SETLOCALE) && defined(HAVE_LOCALE_H)
190 setlocale(LC_ALL, "");
191 #endif
193 bindtextdomain(PACKAGE, LOCALEDIR);
194 textdomain(PACKAGE);
196 xmalloc_set_error_handler(xmalloc_error_handler);
198 prepare_database_internals();
200 parse_command_line(argc, argv);
202 init_abook();
204 get_commands();
206 quit_abook(QUIT_SAVE);
208 return 0;
211 static void
212 free_filenames()
214 xfree(rcfile);
215 xfree(datafile);
219 static void
220 set_filenames()
222 struct stat s;
224 if( (stat(getenv("HOME"), &s)) == -1 || ! S_ISDIR(s.st_mode) ) {
225 fprintf(stderr,_("%s is not a valid HOME directory\n"), getenv("HOME") );
226 exit(EXIT_FAILURE);
229 if(!datafile)
230 datafile = strconcat(getenv("HOME"), "/" DIR_IN_HOME "/"
231 DATAFILE, NULL);
233 if(!rcfile)
234 rcfile = strconcat(getenv("HOME"), "/" DIR_IN_HOME "/"
235 RCFILE, NULL);
237 atexit(free_filenames);
241 * CLI
244 enum {
245 MODE_CONT,
246 MODE_ADD_EMAIL,
247 MODE_ADD_EMAIL_QUIET,
248 MODE_QUERY,
249 MODE_CONVERT
252 static void
253 change_mode(int *current, int mode)
255 if(*current != MODE_CONT) {
256 fprintf(stderr, _("Cannot combine options --mutt-query, "
257 "--convert, "
258 "--add-email or --add-email-quiet\n"));
259 exit(EXIT_FAILURE);
262 *current = mode;
265 void
266 set_filename(char **var, char *path)
268 char *cwd;
270 assert(var != NULL);
271 assert(*var == NULL); /* or else we probably leak memory */
272 assert(path != NULL);
274 if(*path == '/') {
275 *var = xstrdup(path);
276 return;
279 cwd = my_getcwd();
281 *var = strconcat(cwd, "/", path, NULL);
283 free(cwd);
286 #define set_convert_var(X) do { if(mode != MODE_CONVERT) {\
287 fprintf(stderr, _("please use option --%s after --convert option\n"),\
288 long_options[option_index].name);\
289 exit(EXIT_FAILURE);\
290 } else\
291 X = optarg;\
292 } while(0)
294 static void
295 parse_command_line(int argc, char **argv)
297 int mode = MODE_CONT;
298 char *query_string = NULL;
299 char *informat = "abook",
300 *outformat = "text",
301 *infile = "-",
302 *outfile = "-";
303 int c;
305 for(;;) {
306 int option_index = 0;
307 enum {
308 OPT_ADD_EMAIL,
309 OPT_ADD_EMAIL_QUIET,
310 OPT_MUTT_QUERY,
311 OPT_CONVERT,
312 OPT_INFORMAT,
313 OPT_OUTFORMAT,
314 OPT_INFILE,
315 OPT_OUTFILE,
316 OPT_FORMATS
318 static struct option long_options[] = {
319 { "help", 0, 0, 'h' },
320 { "add-email", 0, 0, OPT_ADD_EMAIL },
321 { "add-email-quiet", 0, 0, OPT_ADD_EMAIL_QUIET },
322 { "datafile", 1, 0, 'f' },
323 { "mutt-query", 1, 0, OPT_MUTT_QUERY },
324 { "config", 1, 0, 'C' },
325 { "convert", 0, 0, OPT_CONVERT },
326 { "informat", 1, 0, OPT_INFORMAT },
327 { "outformat", 1, 0, OPT_OUTFORMAT },
328 { "infile", 1, 0, OPT_INFILE },
329 { "outfile", 1, 0, OPT_OUTFILE },
330 { "formats", 0, 0, OPT_FORMATS },
331 { 0, 0, 0, 0 }
334 c = getopt_long(argc, argv, "hC:",
335 long_options, &option_index);
337 if(c == -1)
338 break;
340 switch(c) {
341 case 'h':
342 show_usage();
343 exit(EXIT_SUCCESS);
344 case OPT_ADD_EMAIL:
345 change_mode(&mode, MODE_ADD_EMAIL);
346 break;
347 case OPT_ADD_EMAIL_QUIET:
348 change_mode(&mode, MODE_ADD_EMAIL_QUIET);
349 break;
350 case 'f':
351 set_filename(&datafile, optarg);
352 alternative_datafile = TRUE;
353 break;
354 case OPT_MUTT_QUERY:
355 query_string = optarg;
356 change_mode(&mode, MODE_QUERY);
357 break;
358 case 'C':
359 set_filename(&rcfile, optarg);
360 alternative_rcfile = TRUE;
361 break;
362 case OPT_CONVERT:
363 change_mode(&mode, MODE_CONVERT);
364 break;
365 case OPT_INFORMAT:
366 set_convert_var(informat);
367 break;
368 case OPT_OUTFORMAT:
369 set_convert_var(outformat);
370 break;
371 case OPT_INFILE:
372 set_convert_var(infile);
373 break;
374 case OPT_OUTFILE:
375 set_convert_var(outfile);
376 break;
377 case OPT_FORMATS:
378 print_filters();
379 exit(EXIT_SUCCESS);
380 default:
381 exit(EXIT_FAILURE);
385 if(optind < argc) {
386 fprintf(stderr, _("%s: unrecognized arguments on command line\n"),
387 argv[0]);
388 exit(EXIT_FAILURE);
391 switch(mode) {
392 case MODE_ADD_EMAIL:
393 add_email(0);
394 case MODE_ADD_EMAIL_QUIET:
395 add_email(1);
396 case MODE_QUERY:
397 mutt_query(query_string);
398 case MODE_CONVERT:
399 convert(informat, infile, outformat, outfile);
404 static void
405 show_usage()
407 puts (PACKAGE " v " VERSION "\n");
408 puts (_(" -h --help show usage"));
409 puts (_(" -C --config <file> use an alternative configuration file"));
410 puts (_(" --datafile <file> use an alternative addressbook file"));
411 puts (_(" --mutt-query <string> make a query for mutt"));
412 puts (_(" --add-email "
413 "read an e-mail message from stdin and\n"
415 "add the sender to the addressbook"));
416 puts (_(" --add-email-quiet "
417 "same as --add-email but doesn't\n"
418 " require to confirm adding"));
419 putchar('\n');
420 puts (_(" --convert convert address book files"));
421 puts (_(" options to use with --convert:"));
422 puts (_(" --informat <format> format for input file"));
423 puts (_(" (default: abook)"));
424 puts (_(" --infile <file> source file"));
425 puts (_(" (default: stdin)"));
426 puts (_(" --outformat <format> format for output file"));
427 puts (_(" (default: text)"));
428 puts (_(" --outfile <file> destination file"));
429 puts (_(" (default: stdout)"));
430 puts (_(" --formats list available formats"));
434 * end of CLI
438 static void
439 quit_mutt_query(int status)
441 close_database();
442 free_opts();
444 exit(status);
447 static void
448 muttq_print_item(FILE *file, int item)
450 abook_list *emails, *e;
451 char *tmp = db_email_get(item);
453 emails = csv_to_abook_list(tmp);
454 free(tmp);
456 for(e = emails; e; e = e->next) {
457 fprintf(file, "%s\t%s\t%s\n", e->data, db_name_get(item),
458 !db_fget(item, NOTES) ?" " :db_fget(item, NOTES)
460 if(!opt_get_bool(BOOL_MUTT_RETURN_ALL_EMAILS))
461 break;
463 abook_list_free(&emails);
466 static void
467 mutt_query(char *str)
469 init_mutt_query();
471 if( str == NULL || !strcasecmp(str, "all") ) {
472 struct db_enumerator e = init_db_enumerator(ENUM_ALL);
473 printf("All items\n");
474 db_enumerate_items(e)
475 muttq_print_item(stdout, e.item);
476 } else {
477 int search_fields[] = {NAME, EMAIL, NICK, -1};
478 int i;
479 if( (i = find_item(str, 0, search_fields)) < 0 ) {
480 printf("Not found\n");
481 quit_mutt_query(EXIT_FAILURE);
483 putchar('\n');
484 while(i >= 0) {
485 muttq_print_item(stdout, i);
486 i = find_item(str, i + 1, search_fields);
490 quit_mutt_query(EXIT_SUCCESS);
493 static void
494 init_mutt_query()
496 set_filenames();
497 init_opts();
498 load_opts(rcfile);
500 if( load_database(datafile) ) {
501 printf(_("Cannot open database\n"));
502 quit_mutt_query(EXIT_FAILURE);
503 exit(EXIT_FAILURE);
508 static char *
509 make_mailstr(int item)
511 char email[MAX_EMAIL_LEN];
512 char *ret;
513 char *name = strdup_printf("\"%s\"", db_name_get(item));
515 get_first_email(email, item);
517 ret = *email ?
518 strdup_printf("%s <%s>", name, email) :
519 xstrdup(name);
521 free(name);
523 return ret;
526 void
527 print_stderr(int item)
529 fprintf (stderr, "%c", '\n');
531 if( is_valid_item(item) )
532 muttq_print_item(stderr, item);
533 else {
534 struct db_enumerator e = init_db_enumerator(ENUM_SELECTED);
535 db_enumerate_items(e) {
536 muttq_print_item(stderr, e.item);
542 void
543 launch_mutt(int item)
545 char *cmd = NULL, *mailstr = NULL;
546 char *mutt_command = opt_get_str(STR_MUTT_COMMAND);
548 if(mutt_command == NULL || !*mutt_command)
549 return;
551 if( is_valid_item(item) )
552 mailstr = make_mailstr(item);
553 else {
554 struct db_enumerator e = init_db_enumerator(ENUM_SELECTED);
555 char *tmp = NULL;
556 db_enumerate_items(e) {
557 tmp = mailstr;
558 mailstr = tmp ?
559 strconcat(tmp, ",", make_mailstr(e.item), NULL):
560 strconcat(make_mailstr(e.item), NULL);
561 free(tmp);
565 cmd = strconcat(mutt_command, " \'", mailstr, "\'", NULL);
566 free(mailstr);
567 #ifdef DEBUG
568 fprintf(stderr, "cmd: %s\n", cmd);
569 #endif
570 system(cmd);
571 free(cmd);
574 * we need to make sure that curses settings are correct
576 ui_init_curses();
579 void
580 launch_wwwbrowser(int item)
582 char *cmd = NULL;
584 if( !is_valid_item(item) )
585 return;
587 if(db_fget(item, URL))
588 cmd = strdup_printf("%s '%s'",
589 opt_get_str(STR_WWW_COMMAND),
590 safe_str(db_fget(item, URL)));
591 else
592 return;
594 if ( cmd )
595 system(cmd);
597 free(cmd);
600 * we need to make sure that curses settings are correct
602 ui_init_curses();
605 FILE *
606 abook_fopen (const char *path, const char *mode)
608 struct stat s;
609 bool stat_ok;
611 stat_ok = (stat(path, &s) != -1);
613 if(strchr(mode, 'r'))
614 return (stat_ok && S_ISREG(s.st_mode)) ?
615 fopen(path, mode) : NULL;
616 else
617 return (stat_ok && S_ISDIR(s.st_mode)) ?
618 NULL : fopen(path, mode);
621 static void
622 convert(char *srcformat, char *srcfile, char *dstformat, char *dstfile)
624 int ret=0;
626 if( !srcformat || !srcfile || !dstformat || !dstfile ) {
627 fprintf(stderr, _("too few arguments to make conversion\n"));
628 fprintf(stderr, _("try --help\n"));
631 #ifndef DEBUG
632 if( !strcasecmp(srcformat, dstformat) ) {
633 printf( _("input and output formats are the same\n"
634 "exiting...\n"));
635 exit(EXIT_FAILURE);
637 #endif
639 set_filenames();
640 init_opts();
641 load_opts(rcfile);
642 init_standard_fields();
644 switch(import_file(srcformat, srcfile)) {
645 case -1:
646 fprintf(stderr,
647 _("input format %s not supported\n"), srcformat);
648 ret = 1;
649 break;
650 case 1:
651 fprintf(stderr, _("cannot read file %s\n"), srcfile);
652 ret = 1;
653 break;
656 if(!ret)
657 switch(export_file(dstformat, dstfile)) {
658 case -1:
659 fprintf(stderr,
660 _("output format %s not supported\n"),
661 dstformat);
662 ret = 1;
663 break;
664 case 1:
665 fprintf(stderr,
666 _("cannot write file %s\n"), dstfile);
667 ret = 1;
668 break;
671 close_database();
672 free_opts();
673 exit(ret);
677 * --add-email handling
680 static int add_email_count = 0;
682 static void
683 quit_add_email()
685 if(add_email_count > 0) {
686 if(save_database() < 0) {
687 fprintf(stderr, _("cannot open %s\n"), datafile);
688 exit(EXIT_FAILURE);
690 printf(_("%d item(s) added to %s\n"), add_email_count, datafile);
691 } else {
692 puts(_("Valid sender address not found"));
695 exit(EXIT_SUCCESS);
698 static void
699 quit_add_email_sig(int signal)
701 quit_add_email();
704 static void
705 init_add_email()
707 set_filenames();
708 check_abook_directory();
709 init_opts();
710 load_opts(rcfile);
711 atexit(free_opts);
714 * we don't actually care if loading fails or not
716 load_database(datafile);
718 atexit(close_database);
720 signal(SIGINT, quit_add_email_sig);
723 static int
724 add_email_add_item(int quiet, char *name, char *email)
726 list_item item;
728 if(opt_get_bool(BOOL_ADD_EMAIL_PREVENT_DUPLICATES)) {
729 int search_fields[] = { EMAIL, -1 };
730 if(find_item(email, 0, search_fields) >= 0) {
731 if(!quiet)
732 printf(_("Address %s already in addressbook\n"),
733 email);
734 return 0;
738 if(!quiet) {
739 FILE *in = fopen("/dev/tty", "r");
740 char c;
741 if(!in) {
742 fprintf(stderr, _("cannot open /dev/tty\n"
743 "you may want to use --add-email-quiet\n"));
744 exit(EXIT_FAILURE);
747 do {
748 printf(_("Add \"%s <%s>\" to %s? (%c/%c)\n"),
749 name,
750 email,
751 datafile,
752 *S_("keybinding for yes|y"),
753 *S_("keybinding for no|n"));
754 c = tolower(getc(in));
755 if(c == *S_("keybinding for no|n")) {
756 fclose(in);
757 return 0;
759 } while(c != *S_("keybinding for yes|y"));
760 fclose(in);
763 item = item_create();
764 item_fput(item, NAME, xstrdup(name));
765 item_fput(item, EMAIL, xstrdup(email));
766 add_item2database(item);
767 item_free(&item);
769 return 1;
772 static void
773 add_email(int quiet)
775 char *line;
776 char *name = NULL, *email = NULL;
777 struct stat s;
779 if( (fstat(fileno(stdin), &s)) == -1 || S_ISDIR(s.st_mode) ) {
780 fprintf(stderr, _("stdin is a directory or cannot stat stdin\n"));
781 exit(EXIT_FAILURE);
784 init_add_email();
786 do {
787 line = getaline(stdin);
788 if(line && !strncasecmp("From:", line, 5) ) {
789 getname(line, &name, &email);
790 add_email_count += add_email_add_item(quiet,
791 name, email);
792 xfree(name);
793 xfree(email);
795 xfree(line);
796 } while( !feof(stdin) );
798 quit_add_email();
802 * end of --add-email handling