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