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