]> git.deb.at Git - pkg/abook.git/blob - abook.c
Initial revision
[pkg/abook.git] / abook.c
1 /*
2  * abook.c
3  * by JH <jheinonen@bigfoot.com>
4  *
5  * Copyright (C) 1999, 2000 Jaakko Heinonen
6  */
7
8 #include <string.h>
9 #include <unistd.h>
10 #include <stdlib.h>
11 #include <sys/stat.h>
12 #include <signal.h>
13 #include <fcntl.h>
14 #include "abook_curses.h"
15 #ifdef HAVE_CONFIG_H
16 #       include "config.h"
17 #endif
18 #if defined(HAVE_LOCALE_H) && defined(HAVE_SETLOCALE)
19 #       include <locale.h>
20 #endif
21 #ifdef HAVE_TERMIOS_H
22 #       include <termios.h>
23 #else
24 #       ifdef HAVE_LINUX_TERMIOS_H
25 #               include <linux/termios.h>
26 #       endif
27 #endif
28 #ifdef HAVE_SYS_IOCTL_H
29 #       include <sys/ioctl.h>
30 #endif
31 #include "abook.h"
32 #include "database.h"
33 #include "list.h"
34 #include "filter.h"
35 #include "edit.h"
36 #include "misc.h"
37 #include "help.h"
38 #include "options.h"
39 #include "estr.h"
40
41 static void             init_abook();
42 static void             set_filenames();
43 static void             free_filenames();
44 static void             display_help(char **tbl);
45 static void             get_commands();
46 static void             parse_command_line(int argc, char **argv);
47 static void             show_usage();
48 static void             mutt_query(char *str);
49 static void             init_mutt_query();
50 static void             quit_mutt_query();
51 static void             launch_mutt();
52 static void             launch_lynx();
53 static void             win_changed(int dummy);
54 static void             open_datafile();
55 #ifdef SIGWINCH
56 static void             resize_abook();
57 #endif
58 static void             convert(char *srcformat, char *srcfile,
59                                 char *dstformat, char *dstfile);
60
61 int should_resize = FALSE;
62 int can_resize = FALSE;
63
64 char *datafile = NULL;
65 char *rcfile = NULL;
66
67 WINDOW *top = NULL, *bottom = NULL;
68
69 static void
70 init_windows()
71 {
72         top = newwin(LIST_TOP - 1, COLS, 0, 0);
73         
74         bottom = newwin(LINES - LIST_BOTTOM, COLS, LIST_BOTTOM, 0);
75 }
76
77 static void
78 free_windows()
79 {
80         delwin(top);
81         delwin(bottom);
82 }
83
84 static void
85 init_abook()
86 {
87         set_filenames();
88         init_options();
89
90         initscr(); cbreak(); noecho();
91         nonl();
92         intrflush(stdscr, FALSE);
93         keypad(stdscr, TRUE);
94 #ifdef DEBUG
95         fprintf(stderr, "init_abook():\n");
96         fprintf(stderr, "  COLS = %d, LINES = %d\n", COLS, LINES);
97 #endif
98         if( LINES < MIN_LINES || COLS < MIN_COLS ) {
99                 clear(); refresh(); endwin();
100                 fprintf(stderr, "Your terminal size is %dx%d\n", COLS, LINES);
101                 fprintf(stderr, "Terminal is too small. Minium terminal size "
102                                 "for abook is "
103                                 "%dx%d\n", MIN_COLS, MIN_LINES);
104                 exit(1);
105         }
106         umask(DEFAULT_UMASK);
107 #ifdef SIGWINCH
108         signal(SIGWINCH, win_changed);
109 #endif
110         signal(SIGINT, quit_abook);
111         signal(SIGKILL, quit_abook);
112         signal(SIGTERM, quit_abook);
113         
114         init_list();
115         init_windows();
116
117         /*
118          * this is very ugly for now
119          */
120         /*if( options_get_int("datafile", "autosave") )*/
121
122         if( load_database(datafile) == 2 ) {
123                 char *tmp = strconcat(getenv("HOME"),
124                                 "/" DATAFILE, NULL);
125
126                 if( safe_strcmp(tmp, datafile) ) {
127                         refresh_screen();
128                         statusline_msg("Sorry, the specified file does "
129                                 "not appear to be a valid abook addressbook");
130                         statusline_msg("Will open default addressbook...");
131                         free(datafile);
132                         datafile = tmp;
133                         load_database(datafile);
134                 } else
135                         free(tmp);
136         }
137
138         refresh_screen();
139 }
140
141 void
142 quit_abook()
143 {
144         if( options_get_int("autosave") )
145                 save_database();
146         else {
147                 statusline_addstr("Save database (y/N)");
148                 switch( getch() ) {
149                         case 'y':
150                         case 'Y':
151                                 save_database();
152                         default: break;
153                 }
154         }
155         close_config();
156         close_database();
157         close_list();
158         free_windows();
159         clear();
160         refresh();
161         endwin();
162
163         exit(0);
164 }
165
166 int
167 main(int argc, char **argv)
168 {
169 #if defined(HAVE_SETLOCALE) && defined(HAVE_LOCALE_H)
170         setlocale(LC_ALL, "" );
171 #endif
172                 
173         parse_command_line(argc, argv);
174         
175         init_abook();
176
177         get_commands(); 
178         
179         quit_abook();
180
181         return 0;
182 }
183
184 static void
185 set_filenames()
186 {
187         struct stat s;
188
189         if( (stat(getenv("HOME"), &s)) == -1 || ! S_ISDIR(s.st_mode) ) {
190                 fprintf(stderr,"%s is not a valid HOME directory\n", getenv("HOME") );
191                 exit(1);
192         }
193
194         if (!datafile)
195                 datafile = strconcat(getenv("HOME"), "/" DATAFILE, NULL);
196
197         rcfile = strconcat(getenv("HOME"), "/" RCFILE, NULL);
198
199         atexit(free_filenames);
200 }
201
202 static void
203 free_filenames()
204 {
205         my_free(rcfile);
206         my_free(datafile);
207 }
208
209 void
210 headerline(char *str)
211 {
212         werase(top);
213         
214         mvwhline(top, 1, 0, ACS_HLINE, COLS);
215         
216         mvwprintw(top, 0, 0, "%s | %s", PACKAGE " " VERSION, str);
217
218         refresh();
219         wrefresh(top);
220 }
221                 
222
223 void
224 refresh_screen()
225 {
226 #ifdef SIGWINCH
227         if( should_resize ) {
228                 resize_abook();
229                 return;
230         }
231 #endif
232         clear();
233         
234         refresh_statusline();
235         headerline(MAIN_HELPLINE);
236         list_headerline();
237
238         refresh_list();
239 }
240
241 #ifdef DEBUG
242 extern int curitem;
243 extern list_item *database;
244 static void
245 dump_item()
246 {
247         int i;
248
249         fprintf(stderr,"sizeof(list_item) = %d\n", sizeof(list_item));
250         fprintf(stderr,"--- dumping item %d ---\n", curitem);
251         
252         for(i=0; i<ITEM_FIELDS; i++)
253                 fprintf(stderr,"%d - %d\n",
254                                 i, (int)database[curitem][i]);
255
256         fprintf(stderr,"--- end of dump ---\n");
257                                 
258 }
259 #endif
260
261 extern char *selected;
262 extern int curitem;
263
264 static void
265 get_commands()
266 {
267         int ch;
268
269         for(;;) {
270                 can_resize = TRUE; /* it's safe to resize now */
271                 hide_cursor();
272                 if( should_resize )
273                         refresh_screen();
274                 ch = getch();
275                 show_cursor();
276                 can_resize = FALSE; /* it's not safe to resize anymore */
277                 switch( ch ) {
278                         case 'q': return;
279                         case '?': display_help(mainhelp); break;
280                         case 'a': add_item();           break;
281                         case '\r': edit_item(-1);       break;
282                         case KEY_DC:
283                         case 'd':
284                         case 'r': remove_items();       break;
285                         case 12: refresh_screen();      break;
286
287                         case 'k':
288                         case KEY_UP: scroll_up();       break;
289                         case 'j':
290                         case KEY_DOWN: scroll_down();   break;
291                         case 'K':
292                         case KEY_PPAGE: page_up();      break;
293                         case 'J':
294                         case KEY_NPAGE: page_down();    break;
295
296                         case 'H':
297                         case KEY_HOME: goto_home();     break;
298                         case 'E':
299                         case KEY_END: goto_end();       break;
300
301                         case 'w': save_database();
302                                   break;
303                         case 'l': read_database();      break;
304                         case 'i': import_database();    break;
305                         case 'e': export_database();    break;
306                         case 'C': clear_database();     break;
307
308                         case 'y': edit_options();       break;
309                         case 'o': open_datafile();      break;
310
311                         case 's': sort_database();      break;
312                         case 'S': sort_surname();       break;
313
314                         case '/': find(0);              break;
315                         case '\\': find(1);             break;
316
317                         case ' ': if(curitem >= 0) {
318                                    selected[curitem] = !selected[curitem];
319                                    print_number_of_items();
320                                    refresh_list();
321                                   }
322                                 break;
323                         case '+': select_all();
324                                   refresh_list();
325                                 break;
326                         case '-': select_none();
327                                   refresh_list();
328                                 break;
329                         case '*': invert_selection();
330                                   refresh_list();
331                                  break;
332                         case 'A': move_curitem(MOVE_ITEM_UP);
333                                 break;
334                         case 'Z': move_curitem(MOVE_ITEM_DOWN);
335                                 break;
336
337                         case 'm': launch_mutt(); break;
338
339                         case 'p': print_database(); break;
340
341                         case 'u': launch_lynx(); break;
342 #ifdef DEBUG
343                         case 'D': dump_item();
344 #endif
345                 }
346         }
347 }
348
349
350 static void
351 display_help(char **tbl)
352 {
353         int i, j = 3;
354
355         erase();
356         headerline("help");
357         refresh_statusline();
358         
359         for( i = 0; tbl[i] != NULL; i++) {
360                 mvaddstr(j++, 0, tbl[i]);
361                 if( ( !( (i+1) % (LINES-7) ) ) ||
362                                 (tbl[i+1] == NULL) ) {
363                         refresh();
364                         statusline_msg("Press any key to continue...");
365                         erase();
366                         refresh_statusline();
367                         headerline("help");
368                         j = 3;
369                 }
370         }
371         refresh_screen();
372 }
373
374 void
375 display_editor_help(WINDOW *w)
376 {
377         int i;
378
379         werase(w);
380
381         headerline("editor help");
382
383         for( i = 0; editorhelp[i] != NULL; i++) {
384                 waddstr(w, editorhelp[i]);
385                 if( ( !( (i+1) % (LINES-8) ) ) ||
386                         (editorhelp[i+1] == NULL) ) {
387                         refresh();
388                         wrefresh(w);
389                         statusline_msg("Press any key to continue...");
390                         wclear(w);
391                 }
392         }
393 }
394
395
396 void
397 statusline_msg(char *msg)
398 {
399         clear_statusline();
400         statusline_addstr(msg);
401         getch();
402 #ifdef DEBUG
403         fprintf(stderr, "statusline_msg(\"%s\")\n", msg);
404 #endif
405         clear_statusline();
406 }
407
408 void
409 statusline_addstr(char *str)
410 {
411         mvwaddstr(bottom, 1, 0, str);
412         refresh();
413         wrefresh(bottom);
414 }
415
416 /*
417  * function statusline_getnstr
418  *
419  * parameters:
420  *  (char *str)
421  *   if n >= 0 str is a pointer which points a place where to store
422  *   the string, else str is ingnored
423  *  (int n)
424  *   the maximum length of the string
425  *   If n < 0 function will allocate needed space for the string.
426  *   Value 0 is not allowed for n.
427  *  (int use_filesel)
428  *   if this value is nonzero the fileselector is enabled
429  *
430  *  returns (char *)
431  *   If n < 0 a pointer to a newly allocated string is returned.
432  *   If n > 0 a nonzero value is returned if user has typed a valid
433  *   string. If not NULL value is returned. Never really use the
434  *   _pointer_ if n > 0.
435  *
436  */
437
438 char *
439 statusline_getnstr(char *str, int n, int use_filesel)
440 {
441         char *buf;
442         int y, x;
443
444         getyx(bottom, y, x);
445         wmove(bottom, 1, x);
446         
447         buf = wenter_string(bottom, n,
448                         (use_filesel ? ESTR_USE_FILESEL:0) | ESTR_DONT_WRAP);
449
450         if(n < 0)
451                 return buf;
452         
453         if(buf == NULL)
454                 str[0] = 0;
455         else
456                 strncpy(str, buf, n);
457
458         str[n-1] = 0;
459
460         free(buf);
461
462         return buf;
463 }
464
465 void
466 refresh_statusline()
467 {
468         werase(bottom);
469
470         mvwhline(bottom, 0, 0, ACS_HLINE, COLS);
471         mvwhline(bottom, 2, 0, ACS_HLINE, COLS);
472
473         refresh();
474         wrefresh(bottom);
475 }
476         
477
478 char *
479 ask_filename(char *prompt, int flags)
480 {
481         char *buf = NULL;
482
483         clear_statusline();
484         
485         statusline_addstr(prompt);
486         buf = statusline_getnstr(NULL, -1, flags);
487
488         clear_statusline();
489
490         return buf;
491 }
492
493 void
494 clear_statusline()
495 {
496         wmove(bottom, 1, 0);
497         wclrtoeol(bottom);
498         wrefresh(bottom);
499         refresh();
500 }
501
502 static void
503 parse_command_line(int argc, char **argv)
504 {
505         int i;
506
507         for( i = 1; i < argc; i++ ) {
508                 if( !strcmp(argv[i], "--help") ) {
509                         show_usage();
510                         exit(1);
511                 } else
512                 if( !strcmp(argv[i], "--mutt-query") )
513                         mutt_query(argv[i + 1]);
514                 else
515                 if( !strcmp(argv[i], "--datafile") ) {
516                         if (argv[i+1]) {
517                                 if (argv[i+1][0] != '/') {
518                                         char *cwd = my_getcwd();
519                                         datafile = strconcat(cwd, "/", argv[i+1], NULL);
520                                         free(cwd);
521                                 } else {
522                                         datafile = strdup(argv[i+1]);
523                                 }
524                                 i++;
525                         } else {
526                                 show_usage();
527                                 exit(1);
528                         }
529                 } else
530                 if( !strcmp(argv[i], "--convert") ) {
531                         if( argc < 5 || argc > 6 ) {
532                                 fprintf(stderr, "incorrect number of argumets to make conversion\n");
533                                 fprintf(stderr, "try %s --help\n", argv[0]);
534                                 exit(1);
535                         }
536                         if( argv[i+4] )
537                                 convert(argv[i+1], argv[i+2],
538                                         argv[i+3], argv[i+4]);
539                         else
540                                 convert(argv[i+1], argv[i+2], argv[i+3], "-");
541                 } else {
542                         printf("option %s not recognized\n", argv[i]);
543                         printf("try %s --help\n", argv[0]);
544                         exit(1);
545                 }
546         }
547 }
548
549
550 static void
551 show_usage()
552 {
553         puts    (PACKAGE " v " VERSION "\n");
554         puts    ("      --help                          show usage");
555         puts    ("      --datafile      <filename>      use an alternative addressbook file");
556         puts    ("      --mutt-query    <string>        make a query for mutt");
557         puts    ("      --convert       <inputformat> <inputfile> "
558                  "<outputformat> <outputfile>");
559         putchar('\n');
560         puts    ("available formats for --convert option:");
561         print_filters();
562 #ifdef DEBUG
563         puts    ("\nWarning: this version compiled with DEBUG flag ON");
564 #endif
565 }
566
567 extern list_item *database;
568 extern int items;
569
570 static void
571 muttq_print_item(int item)
572 {
573         char emails[MAX_EMAILS][MAX_EMAIL_LEN];
574         int i;
575
576         split_emailstr(item, emails);
577         
578         for(i = 0; i < (options_get_int("mutt_return_all_emails") ?
579                         MAX_EMAILS : 1) ; i++)
580                 if( *emails[i] )
581                         printf("%s\t%s\t%s\n", emails[i],
582                                 database[item][NAME],
583                                 database[item][NOTES] == NULL ? " " :
584                                         database[item][NOTES]
585                                 );
586 }
587
588 static int
589 mutt_query_name(char *str)
590 {
591         int i, j;
592         char *tmp;
593
594         for(i = 0, j = 0 ; i < items; i++) {
595                 tmp = strdup(database[i][NAME]);
596                 if( strstr( strupper(tmp), strupper(str) ) != NULL ) {
597                         if( !j )
598                                 putchar('\n');
599                         muttq_print_item(i);
600                         j++;
601                 }
602                 free(tmp);
603         }
604
605         return j;
606 }
607
608 static int
609 mutt_query_email(char *str)
610 {
611         int i, j, k;
612         char *tmp, emails[MAX_EMAILS][MAX_EMAIL_LEN];
613
614         for(i = 0, j = 0; i < items; i++) {
615                 split_emailstr(i, emails);
616                 for(k = 0; k < MAX_EMAILS; k++) {
617                         if( *emails[k] ) {
618                                 tmp = strdup( emails[k] );
619                                 if( strstr( strupper(tmp), strupper(str) ) != NULL ) {
620                                         if( !j )
621                                                 putchar('\n');
622                                         j++;
623                                         if( options_get_int("mutt_return_all_emails") ) {
624                                                 muttq_print_item(i);
625                                                 free(tmp);
626                                                 break;
627                                         } else
628                                                 printf("%s\t%s\n", emails[k],
629                                                         database[i][NAME]);
630                                 }
631                                 free(tmp);
632                         }
633                 }
634         }
635
636         return j;
637 }
638
639 static void
640 mutt_query(char *str)
641 {
642         int i;
643         
644         init_mutt_query();
645
646         if( str == NULL || !strcasecmp(str, "all") ) {
647                 printf("All items\n");
648                 for(i = 0; i < items; i++)
649                         muttq_print_item(i);
650         } else {
651                 if( !mutt_query_name(str) && !mutt_query_email(str) ) {
652                         printf("Not found\n");
653                         quit_mutt_query(1);
654                 }
655         }
656
657         quit_mutt_query(0);
658 }
659
660 static void
661 init_mutt_query()
662 {
663         set_filenames();
664         init_options();
665         
666         if( load_database(datafile) ) {
667                 printf("Cannot open database\n");
668                 quit_mutt_query(1);
669                 exit(1);
670         }
671 }
672
673 static void
674 quit_mutt_query(int status)
675 {
676         close_database();
677         close_config();
678
679         exit(status);
680 }
681
682
683 static void
684 launch_mutt()
685 {
686         int i;
687         char email[MAX_EMAIL_LEN];
688         char *cmd;
689         char *tmp = options_get_str("mutt_command");
690
691         if(curitem < 0)
692                 return;
693
694         cmd = strconcat(tmp, " '", NULL );
695
696         for(i=0; i < items; i++) {
697                 if( ! selected[i] && i != curitem )
698                         continue;
699                 get_first_email(email, i);
700                 tmp = mkstr("%s \"%s\"", cmd, database[i][NAME]);
701                 my_free(cmd);
702                 if( *database[i][EMAIL] ) {
703                         cmd = mkstr("%s <%s>", tmp, email);
704                         free(tmp);
705                         tmp = cmd;
706                 }
707                 cmd = strconcat(tmp, " ", NULL);
708                 free(tmp);
709         }
710
711         tmp = mkstr("%s%c", cmd, '\'');
712         free(cmd);
713         cmd = tmp;
714 #ifdef DEBUG
715         fprintf(stderr, "cmd: %s\n", cmd);
716 #endif
717         system(cmd);    
718
719         free(cmd);
720         refresh_screen();
721 }
722
723 static void
724 launch_lynx()
725 {
726         char *cmd = NULL;
727
728         if(curitem < 0)
729                 return;
730
731         if( database[curitem][URL] )
732                 cmd = mkstr("%s '%s'",
733                                 options_get_str("www_command"),
734                                 safe_str(database[curitem][URL]));
735         else
736                 return;
737
738         if ( cmd )
739                 system(cmd);
740
741         free(cmd);
742         refresh_screen();
743 }
744
745 void *
746 abook_malloc(size_t size)
747 {
748         void *ptr;
749
750         if ( (ptr = malloc(size)) == NULL ) {
751                 if(top) /* determinate if init_abook has been called */
752                         quit_abook();
753                 perror("malloc() failed");
754                 exit(1);
755         }
756
757         return ptr;
758 }
759
760 void *
761 abook_realloc(void *ptr, size_t size)
762 {
763         ptr = realloc(ptr, size);
764
765         if( size == 0 )
766                 return NULL;
767
768         if( ptr == NULL ) {
769                 if(top) /* determinate if init_abook has been called */
770                         quit_abook();
771                 perror("realloc() failed");
772                 exit(1);
773         }
774
775         return ptr;
776 }
777
778 FILE *
779 abook_fopen (const char *path, const char *mode)
780 {       
781         struct stat s;
782         
783         if( ! strchr(mode, 'r') )
784                 return fopen(path, mode);
785         
786         if ( (stat(path, &s)) == -1 )
787                 return NULL;
788         
789         return S_ISREG(s.st_mode) ? fopen(path, mode) : NULL;
790 }
791
792
793 static void
794 win_changed(int i)
795 {
796         if( can_resize )
797                 resize_abook();
798         else
799                 should_resize = TRUE;   
800 }
801
802 #ifdef SIGWINCH
803 static void
804 resize_abook()
805 {
806 #ifdef TIOCGWINSZ
807         struct winsize winsz;
808
809         ioctl (0, TIOCGWINSZ, &winsz);
810 #ifdef DEBUG
811         if(winsz.ws_col >= MIN_COLS && winsz.ws_row >= MIN_LINES) {
812                 fprintf(stderr, "Warning: COLS=%d, LINES=%d\n", winsz.ws_col, winsz.ws_row);
813         }
814 #endif
815                 
816         if(winsz.ws_col >= MIN_COLS && winsz.ws_row >= MIN_LINES) {
817 #ifdef HAVE_RESIZETERM
818                 resizeterm(winsz.ws_row, winsz.ws_col);
819 #else
820                 COLS = winsz.ws_col;
821                 LINES = winsz.ws_row;
822 #endif
823         }
824
825         should_resize = FALSE;
826         close_list(); /* we need to recreate windows */
827         init_list();
828         free_windows();
829         init_windows();
830         refresh_screen();
831         refresh();
832 #endif /* TIOCGWINSZ */
833 }
834 #endif /* SIGWINCH */
835
836
837 static void
838 convert(char *srcformat, char *srcfile, char *dstformat, char *dstfile)
839 {
840         int ret=0;
841
842         if( !srcformat || !srcfile || !dstformat || !dstfile ) {
843                 fprintf(stderr, "too few argumets to make conversion\n");
844                 fprintf(stderr, "try --help\n");
845         }
846
847         strlower(srcformat);
848         strlower(dstformat);
849
850         if( !strcmp(srcformat, dstformat) ) {
851                 printf( "input and output formats are the same\n"
852                         "exiting...\n");
853                 exit(1);
854         }
855
856         set_filenames();
857         init_options();
858
859         switch( import(srcformat, srcfile) ) {
860                 case -1:
861                         printf("input format %s not supported\n", srcformat);
862                         ret = 1;
863                 case 1:
864                         printf("cannot read file %s\n", srcfile);
865                         ret = 1;
866         }
867
868         if(!ret)
869                 switch( export(dstformat, dstfile) ) {
870                         case -1:
871                                 printf("output format %s not supported\n",
872                                                 dstformat);
873                                 ret = 1;
874                                 break;
875                         case 1:
876                                 printf("cannot write file %s\n", dstfile);
877                                 ret = 1;
878                                 break;
879                 }
880
881         close_database();
882         close_config();
883         exit(ret);
884 }
885
886
887 static void
888 open_datafile()
889 {
890         char *filename;
891
892         filename = ask_filename("File to open: ", 1);
893
894         if( !filename ) {
895                 refresh_screen();
896                 return;
897         }
898
899         if( options_get_int("autosave") )
900                 save_database();
901         else {
902                 statusline_addstr("Save current database (y/N)");
903                 switch( getch() ) {
904                         case 'y':
905                         case 'Y':
906                                 save_database();
907                         default: break;
908                 }
909         }
910
911         close_database();
912
913         load_database(filename);
914
915         if( items == 0 ) {
916                 statusline_msg("Sorry, that specified file appears not to be a valid abook addressbook");
917                 load_database(datafile);
918         } else {
919                 free(datafile);
920                 datafile = strdup(filename);
921         }
922
923         refresh_screen();
924         free(filename);
925 }