]> git.deb.at Git - pkg/abook.git/blob - abook.c
build system: updating gnuconfig files to 2013-06-10
[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                                 if(mode != MODE_CONVERT && mode != MODE_QUERY) {
381                                   fprintf(stderr,
382                                           _("please use option --outformat after --convert or --mutt-query option\n"));
383                                   exit(EXIT_FAILURE);
384                                 }
385                                 // ascii-name is stored, it's used to traverse
386                                 // e_filters[] in MODE_CONVERT (see export_file())
387                                 outformat = optarg;
388                                 // but in case a query-compatible filter is requested
389                                 // try to guess right now which one it is, from u_filters[]
390                                 selected_item_filter = select_output_item_filter(outformat);
391                                 break;
392                         case OPT_OUTFORMAT_STR:
393                                 strncpy(custom_format, optarg, FORMAT_STRING_LEN);
394                                 custom_format[FORMAT_STRING_LEN - 1] = 0;
395                                 break;
396                         case OPT_INFILE:
397                                 set_convert_var(infile);
398                                 break;
399                         case OPT_OUTFILE:
400                                 set_convert_var(outfile);
401                                 break;
402                         case OPT_FORMATS:
403                                 print_filters();
404                                 exit(EXIT_SUCCESS);
405                         default:
406                                 exit(EXIT_FAILURE);
407                 }
408         }
409
410         // if the output format requested does not allow filtered querying
411         // (not in u_filter[]) and --convert has not been specified; bailout
412         if(! selected_item_filter.func && mode != MODE_CONVERT) {
413           printf("output format %s not supported or incompatible with --mutt-query\n", outformat);
414           exit(EXIT_FAILURE);
415         }
416         if(! selected_item_filter.func)
417                 selected_item_filter = select_output_item_filter("muttq");
418         else if (! strcmp(outformat, "custom")) {
419                 if(! *custom_format) {
420                         fprintf(stderr, _("Invalid custom format string\n"));
421                         exit(EXIT_FAILURE);
422                 }
423                 parsed_custom_format = (char *)malloc(FORMAT_STRING_LEN * sizeof(char*));
424                 custom_format_fields = (enum field_types *)malloc(FORMAT_STRING_MAX_FIELDS * sizeof(enum field_types *));
425                 parse_custom_format(custom_format, parsed_custom_format, custom_format_fields);
426         }
427         if(optind < argc) {
428                 fprintf(stderr, _("%s: unrecognized arguments on command line\n"),
429                                 argv[0]);
430                 exit(EXIT_FAILURE);
431         }
432
433         switch(mode) {
434                 case MODE_ADD_EMAIL:
435                         add_email(0);
436                 case MODE_ADD_EMAIL_QUIET:
437                         add_email(1);
438                 case MODE_QUERY:
439                         mutt_query(query_string);
440                 case MODE_CONVERT:
441                         convert(informat, infile, outformat, outfile);
442         }
443 }
444
445
446 static void
447 show_usage()
448 {
449         puts    (PACKAGE " v " VERSION "\n");
450         puts    (_("     -h     --help                          show usage"));
451         puts    (_("     -C     --config        <file>          use an alternative configuration file"));
452         puts    (_("    --datafile      <file>          use an alternative addressbook file"));
453         puts    (_("    --mutt-query    <string>        make a query for mutt"));
454         puts    (_("    --add-email                     "
455                         "read an e-mail message from stdin and\n"
456                 "                                       "
457                 "add the sender to the addressbook"));
458         puts    (_("    --add-email-quiet               "
459                 "same as --add-email but doesn't\n"
460                 "                                       require to confirm adding"));
461         putchar('\n');
462         puts    (_("    --convert                       convert address book files"));
463         puts    (_("    options to use with --convert:"));
464         puts    (_("    --informat      <format>        format for input file"));
465         puts    (_("                                    (default: abook)"));
466         puts    (_("    --infile        <file>          source file"));
467         puts    (_("                                    (default: stdin)"));
468         puts    (_("    --outformat     <format>        format for output file"));
469         puts    (_("                                    (default: text)"));
470         puts    (_("    --outfile       <file>          destination file"));
471         puts    (_("                                    (default: stdout)"));
472         puts    (_("    --outformatstr  <str>           format to use for \"custom\" --outformat"));
473         puts    (_("                                    (default: \"{nick} ({name}): {mobile}\")"));
474         puts    (_("    --formats                       list available formats"));
475 }
476
477 /*
478  * end of CLI
479  */
480
481
482 static void
483 quit_mutt_query(int status)
484 {
485         close_database();
486         free_opts();
487
488         exit(status);
489 }
490
491 static void
492 mutt_query(char *str)
493 {
494         init_mutt_query();
495
496         if( str == NULL || !strcasecmp(str, "all") ) {
497                 export_file("muttq", "-");
498         } else {
499                 int search_fields[] = {NAME, EMAIL, NICK, -1};
500                 int i;
501                 if( (i = find_item(str, 0, search_fields)) < 0 ) {
502                         printf("Not found\n");
503                         quit_mutt_query(EXIT_FAILURE);
504                 }
505                 // mutt expects a leading line containing
506                 // a message about the query.
507                 // Others output filter supporting query (vcard, custom)
508                 // don't needs this.
509                 if(!strcmp(selected_item_filter.filtname, "muttq"))
510                         putchar('\n');
511                 while(i >= 0) {
512                         e_write_item(stdout, i, selected_item_filter.func);
513                         i = find_item(str, i + 1, search_fields);
514                 }
515         }
516
517         quit_mutt_query(EXIT_SUCCESS);
518 }
519
520 static void
521 init_mutt_query()
522 {
523         set_filenames();
524         init_opts();
525         load_opts(rcfile);
526
527         if( load_database(datafile) ) {
528                 printf(_("Cannot open database\n"));
529                 quit_mutt_query(EXIT_FAILURE);
530                 exit(EXIT_FAILURE);
531         }
532 }
533
534
535 static char *
536 make_mailstr(int item)
537 {
538         char email[MAX_EMAIL_LEN];
539         char *ret;
540         char *name = strdup_printf("\"%s\"", db_name_get(item));
541
542         get_first_email(email, item);
543
544         ret = *email ?
545                 strdup_printf("%s <%s>", name, email) :
546                 xstrdup(name);
547
548         free(name);
549
550         return ret;
551 }
552
553 void
554 print_stderr(int item)
555 {
556         fprintf (stderr, "%c", '\n');
557
558         if( is_valid_item(item) )
559                 muttq_print_item(stderr, item);
560         else {
561                 struct db_enumerator e = init_db_enumerator(ENUM_SELECTED);
562                 db_enumerate_items(e) {
563                         muttq_print_item(stderr, e.item);
564                 }
565         }
566
567 }
568
569 void
570 launch_mutt(int item)
571 {
572         char *cmd = NULL, *mailstr = NULL;
573         char *mutt_command = opt_get_str(STR_MUTT_COMMAND);
574
575         if(mutt_command == NULL || !*mutt_command)
576                 return;
577
578         if( is_valid_item(item) )
579                 mailstr = make_mailstr(item);
580         else {
581                 struct db_enumerator e = init_db_enumerator(ENUM_SELECTED);
582                 char *tmp = NULL;
583                 db_enumerate_items(e) {
584                         tmp = mailstr;
585                         mailstr = tmp ?
586                                 strconcat(tmp, ",", make_mailstr(e.item), NULL):
587                                 strconcat(make_mailstr(e.item), NULL);
588                         free(tmp);
589                 }
590         }
591
592         cmd = strconcat(mutt_command, " \'", mailstr, "\'", NULL);
593         free(mailstr);
594 #ifdef DEBUG
595         fprintf(stderr, "cmd: %s\n", cmd);
596 #endif
597         system(cmd);
598         free(cmd);
599
600         /*
601          * we need to make sure that curses settings are correct
602          */
603         ui_init_curses();
604 }
605
606 void
607 launch_wwwbrowser(int item)
608 {
609         char *cmd = NULL;
610
611         if( !is_valid_item(item) )
612                 return;
613
614         if(db_fget(item, URL))
615                 cmd = strdup_printf("%s '%s'",
616                                 opt_get_str(STR_WWW_COMMAND),
617                                 safe_str(db_fget(item, URL)));
618         else
619                 return;
620
621         if ( cmd )
622                 system(cmd);
623
624         free(cmd);
625
626         /*
627          * we need to make sure that curses settings are correct
628          */
629         ui_init_curses();
630 }
631
632 FILE *
633 abook_fopen (const char *path, const char *mode)
634 {
635         struct stat s;
636         bool stat_ok;
637
638         stat_ok = (stat(path, &s) != -1);
639
640         if(strchr(mode, 'r'))
641                 return (stat_ok && S_ISREG(s.st_mode)) ?
642                         fopen(path, mode) : NULL;
643         else
644                 return (stat_ok && S_ISDIR(s.st_mode)) ?
645                         NULL : fopen(path, mode);
646 }
647
648 static void
649 convert(char *srcformat, char *srcfile, char *dstformat, char *dstfile)
650 {
651         int ret=0;
652
653         if( !srcformat || !srcfile || !dstformat || !dstfile ) {
654                 fprintf(stderr, _("too few arguments to make conversion\n"));
655                 fprintf(stderr, _("try --help\n"));
656         }
657
658 #ifndef DEBUG
659         if( !strcasecmp(srcformat, dstformat) ) {
660                 printf( _("input and output formats are the same\n"
661                         "exiting...\n"));
662                 exit(EXIT_FAILURE);
663         }
664 #endif
665
666         set_filenames();
667         init_opts();
668         load_opts(rcfile);
669         init_standard_fields();
670
671         switch(import_file(srcformat, srcfile)) {
672                 case -1:
673                         fprintf(stderr,
674                                 _("input format %s not supported\n"), srcformat);
675                         ret = 1;
676                         break;
677                 case 1:
678                         fprintf(stderr, _("cannot read file %s\n"), srcfile);
679                         ret = 1;
680                         break;
681         }
682
683         if(!ret)
684                 switch(export_file(dstformat, dstfile)) {
685                         case -1:
686                                 fprintf(stderr,
687                                         _("output format %s not supported\n"),
688                                         dstformat);
689                                 ret = 1;
690                                 break;
691                         case 1:
692                                 fprintf(stderr,
693                                         _("cannot write file %s\n"), dstfile);
694                                 ret = 1;
695                                 break;
696                 }
697
698         close_database();
699         free_opts();
700         exit(ret);
701 }
702
703 /*
704  * --add-email handling
705  */
706
707 static int add_email_count = 0;
708
709 static void
710 quit_add_email()
711 {
712         if(add_email_count > 0) {
713                 if(save_database() < 0) {
714                         fprintf(stderr, _("cannot open %s\n"), datafile);
715                         exit(EXIT_FAILURE);
716                 }
717                 printf(_("%d item(s) added to %s\n"), add_email_count, datafile);
718         } else {
719                 puts(_("Valid sender address not found"));
720         }
721
722         exit(EXIT_SUCCESS);
723 }
724
725 static void
726 quit_add_email_sig(int signal)
727 {
728         quit_add_email();
729 }
730
731 static void
732 init_add_email()
733 {
734         set_filenames();
735         check_abook_directory();
736         init_opts();
737         load_opts(rcfile);
738         init_standard_fields();
739         atexit(free_opts);
740
741         /*
742          * we don't actually care if loading fails or not
743          */
744         load_database(datafile);
745
746         atexit(close_database);
747
748         signal(SIGINT, quit_add_email_sig);
749 }
750
751 static int
752 add_email_add_item(int quiet, char *name, char *email)
753 {
754         list_item item;
755
756         if(opt_get_bool(BOOL_ADD_EMAIL_PREVENT_DUPLICATES)) {
757                 int search_fields[] = { EMAIL, -1 };
758                 if(find_item(email, 0, search_fields) >= 0) {
759                         if(!quiet)
760                                 printf(_("Address %s already in addressbook\n"),
761                                                 email);
762                         return 0;
763                 }
764         }
765
766         if(!quiet) {
767                 FILE *in = fopen("/dev/tty", "r");
768                 char c;
769                 if(!in) {
770                         fprintf(stderr, _("cannot open /dev/tty\n"
771                                 "you may want to use --add-email-quiet\n"));
772                         exit(EXIT_FAILURE);
773                 }
774
775                 do {
776                         printf(_("Add \"%s <%s>\" to %s? (%c/%c)\n"),
777                                         name,
778                                         email,
779                                         datafile,
780                                         *S_("keybinding for yes|y"),
781                                         *S_("keybinding for no|n"));
782                         c = tolower(getc(in));
783                         if(c == *S_("keybinding for no|n")) {
784                                 fclose(in);
785                                 return 0;
786                         }
787                 } while(c != *S_("keybinding for yes|y"));
788                 fclose(in);
789         }
790
791         item = item_create();
792         item_fput(item, NAME, xstrdup(name));
793         item_fput(item, EMAIL, xstrdup(email));
794         add_item2database(item);
795         item_free(&item);
796
797         return 1;
798 }
799
800 static void
801 add_email(int quiet)
802 {
803         char *line;
804         char *name = NULL, *email = NULL;
805         struct stat s;
806
807         if( (fstat(fileno(stdin), &s)) == -1 || S_ISDIR(s.st_mode) ) {
808                 fprintf(stderr, _("stdin is a directory or cannot stat stdin\n"));
809                 exit(EXIT_FAILURE);
810         }
811
812         init_add_email();
813
814         do {
815                 line = getaline(stdin);
816                 if(line && !strncasecmp("From:", line, 5) ) {
817                         getname(line, &name, &email);
818                         add_email_count += add_email_add_item(quiet,
819                                         name, email);
820                         xfree(name);
821                         xfree(email);
822                 }
823                 xfree(line);
824         } while( !feof(stdin) );
825
826         quit_add_email();
827 }
828
829 /*
830  * end of --add-email handling
831  */