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