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