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