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