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