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