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