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