Citadel API clean up.
[citadel.git] / citadel / modules / autocompletion / serv_autocompletion.c
1 /*
2  * $Id$
3  *
4  * Autocompletion of email recipients, etc.
5  *
6  * Copyright (c) 1987-2009 by the citadel.org team
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 3 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 #include "sysdep.h"
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <stdio.h>
27 #include <fcntl.h>
28 #include <ctype.h>
29 #include <signal.h>
30 #include <pwd.h>
31 #include <errno.h>
32 #include <sys/types.h>
33
34 #if TIME_WITH_SYS_TIME
35 # include <sys/time.h>
36 # include <time.h>
37 #else
38 # if HAVE_SYS_TIME_H
39 #  include <sys/time.h>
40 # else
41 #  include <time.h>
42 # endif
43 #endif
44
45 #include <sys/wait.h>
46 #include <string.h>
47 #include <limits.h>
48 #include <libcitadel.h>
49 #include "citadel.h"
50 #include "server.h"
51 #include "citserver.h"
52 #include "support.h"
53 #include "config.h"
54 #include "msgbase.h"
55 #include "user_ops.h"
56 #include "database.h"
57 #include "serv_autocompletion.h"
58
59 #include "ctdl_module.h"
60
61
62 #ifndef HAVE_SNPRINTF
63 #include "snprintf.h"
64 #endif
65
66
67
68 /*
69  * Convert a structured name into a friendly name.  Caller must free the
70  * returned pointer.
71  */
72 char *n_to_fn(char *value) {
73         char *nnn = NULL;
74         int i;
75
76         nnn = malloc(strlen(value) + 10);
77         strcpy(nnn, "");
78         extract_token(&nnn[strlen(nnn)] , value, 3, ';', 999);
79         strcat(nnn, " ");
80         extract_token(&nnn[strlen(nnn)] , value, 1, ';', 999);
81         strcat(nnn, " ");
82         extract_token(&nnn[strlen(nnn)] , value, 2, ';', 999);
83         strcat(nnn, " ");
84         extract_token(&nnn[strlen(nnn)] , value, 0, ';', 999);
85         strcat(nnn, " ");
86         extract_token(&nnn[strlen(nnn)] , value, 4, ';', 999);
87         strcat(nnn, " ");
88         for (i=0; i<strlen(nnn); ++i) {
89                 if (!strncmp(&nnn[i], "  ", 2)) strcpy(&nnn[i], &nnn[i+1]);
90         }
91         striplt(nnn);
92         return(nnn);
93 }
94
95
96
97
98 /*
99  * Back end for cmd_auto()
100  */
101 void hunt_for_autocomplete(long msgnum, char *search_string) {
102         struct CtdlMessage *msg;
103         struct vCard *v;
104         char *value = NULL;
105         char *value2 = NULL;
106         int i = 0;
107         char *nnn = NULL;
108
109         msg = CtdlFetchMessage(msgnum, 1);
110         if (msg == NULL) return;
111
112         v = vcard_load(msg->cm_fields['M']);
113         CtdlFreeMessage(msg);
114
115         /*
116          * Try to match from a friendly name (the "fn" field).  If there is
117          * a match, return the entry in the form of:
118          *     Display Name <user@domain.org>
119          */
120         value = vcard_get_prop(v, "fn", 0, 0, 0);
121         if (value != NULL) if (bmstrcasestr(value, search_string)) {
122                 value2 = vcard_get_prop(v, "email", 1, 0, 0);
123                 if (value2 == NULL) value2 = "";
124                 cprintf("%s <%s>\n", value, value2);
125                 vcard_free(v);
126                 return;
127         }
128
129         /*
130          * Try to match from a structured name (the "n" field).  If there is
131          * a match, return the entry in the form of:
132          *     Display Name <user@domain.org>
133          */
134         value = vcard_get_prop(v, "n", 0, 0, 0);
135         if (value != NULL) if (bmstrcasestr(value, search_string)) {
136
137                 value2 = vcard_get_prop(v, "email", 1, 0, 0);
138                 if (value2 == NULL) value2 = "";
139                 nnn = n_to_fn(value);
140                 cprintf("%s <%s>\n", nnn, value2);
141                 free(nnn);
142                 vcard_free(v);
143                 return;
144         }
145
146         /*
147          * Try a partial match on all listed email addresses.
148          */
149         i = 0;
150         while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
151                 if (bmstrcasestr(value, search_string)) {
152                         if (vcard_get_prop(v, "fn", 0, 0, 0)) {
153                                 cprintf("%s <%s>\n", vcard_get_prop(v, "fn", 0, 0, 0), value);
154                         }
155                         else if (vcard_get_prop(v, "n", 0, 0, 0)) {
156                                 nnn = n_to_fn(vcard_get_prop(v, "n", 0, 0, 0));
157                                 cprintf("%s <%s>\n", nnn, value);
158                                 free(nnn);
159                         
160                         }
161                         else {
162                                 cprintf("%s\n", value);
163                         }
164                         vcard_free(v);
165                         return;
166                 }
167         }
168
169         vcard_free(v);
170 }
171
172
173
174 /*
175  * Attempt to autocomplete an address based on a partial...
176  */
177 void cmd_auto(char *argbuf) {
178         char hold_rm[ROOMNAMELEN];
179         char search_string[256];
180         long *msglist = NULL;
181         int num_msgs = 0;
182         long *fts_msgs = NULL;
183         int fts_num_msgs = 0;
184         struct cdbdata *cdbfr;
185         int r = 0;
186         int i = 0;
187         int j = 0;
188         int search_match = 0;
189         char *rooms_to_try[] = { USERCONTACTSROOM, ADDRESS_BOOK_ROOM };
190                 
191         if (CtdlAccessCheck(ac_logged_in)) return;
192         extract_token(search_string, argbuf, 0, '|', sizeof search_string);
193         if (IsEmptyStr(search_string)) {
194                 cprintf("%d You supplied an empty partial.\n",
195                         ERROR + ILLEGAL_VALUE);
196                 return;
197         }
198
199         strcpy(hold_rm, CC->room.QRname);       /* save current room */
200         cprintf("%d try these:\n", LISTING_FOLLOWS);
201
202         /*
203          * Gather up message pointers in rooms containing vCards
204          */
205         for (r=0; r < (sizeof(rooms_to_try) / sizeof(char *)); ++r) {
206                 if (CtdlGetRoom(&CC->room, rooms_to_try[r]) == 0) {
207                         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
208                         if (cdbfr != NULL) {
209                                 msglist = realloc(msglist, (num_msgs * sizeof(long)) + cdbfr->len + 1);
210                                 memcpy(&msglist[num_msgs], cdbfr->ptr, cdbfr->len);
211                                 num_msgs += (cdbfr->len / sizeof(long));
212                                 cdb_free(cdbfr);
213                         }
214                 }
215         }
216
217         /*
218          * Search-reduce the results if we have the full text index available
219          */
220         if (config.c_enable_fulltext) {
221                 CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, search_string, "fulltext");
222                 if (fts_msgs) {
223                         for (i=0; i<num_msgs; ++i) {
224                                 search_match = 0;
225                                 for (j=0; j<fts_num_msgs; ++j) {
226                                         if (msglist[i] == fts_msgs[j]) {
227                                                 search_match = 1;
228                                                 j = fts_num_msgs + 1;   /* end the search */
229                                         }
230                                 }
231                                 if (!search_match) {
232                                         msglist[i] = 0;         /* invalidate this result */
233                                 }
234                         }
235                         free(fts_msgs);
236                 }
237                 else {
238                         /* If no results, invalidate the whole list */
239                         free(msglist);
240                         msglist = NULL;
241                         num_msgs = 0;
242                 }
243         }
244
245         /*
246          * Now output the ones that look interesting
247          */
248         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
249                 if (msglist[i] != 0) {
250                         hunt_for_autocomplete(msglist[i], search_string);
251                 }
252         }
253         
254         cprintf("000\n");
255         if (strcmp(CC->room.QRname, hold_rm)) {
256                 CtdlGetRoom(&CC->room, hold_rm);    /* return to saved room */
257         }
258
259         if (msglist) {
260                 free(msglist);
261         }
262         
263 }
264
265
266 CTDL_MODULE_INIT(autocompletion) {
267         if (!threading)
268         {
269                 CtdlRegisterProtoHook(cmd_auto, "AUTO", "Do recipient autocompletion");
270         }
271         /* return our Subversion id for the Log */
272         return "$Id$";
273 }