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