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