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