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