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