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