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