ca7c0ae426f61c32f7a971e0c481718d575532cd
[citadel.git] / citadel / modules / autocompletion / serv_autocompletion.c
1 /*
2  * Autocompletion of email recipients, etc.
3  *
4  * Copyright (c) 1987-2015 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15
16 #include "ctdl_module.h"
17 #include "serv_autocompletion.h"
18 #include "config.h"
19
20
21 /*
22  * Convert a structured name into a friendly name.  Caller must free the
23  * returned pointer.
24  */
25 char *n_to_fn(char *value) {
26         char *nnn = NULL;
27         int i;
28
29         nnn = malloc(strlen(value) + 10);
30         strcpy(nnn, "");
31         extract_token(&nnn[strlen(nnn)] , value, 3, ';', 999);
32         strcat(nnn, " ");
33         extract_token(&nnn[strlen(nnn)] , value, 1, ';', 999);
34         strcat(nnn, " ");
35         extract_token(&nnn[strlen(nnn)] , value, 2, ';', 999);
36         strcat(nnn, " ");
37         extract_token(&nnn[strlen(nnn)] , value, 0, ';', 999);
38         strcat(nnn, " ");
39         extract_token(&nnn[strlen(nnn)] , value, 4, ';', 999);
40         strcat(nnn, " ");
41         for (i=0; i<strlen(nnn); ++i) {
42                 if (!strncmp(&nnn[i], "  ", 2)) strcpy(&nnn[i], &nnn[i+1]);
43         }
44         striplt(nnn);
45         return(nnn);
46 }
47
48
49
50
51 /*
52  * Back end for cmd_auto()
53  */
54 void hunt_for_autocomplete(long msgnum, char *search_string) {
55         struct CtdlMessage *msg;
56         struct vCard *v;
57         char *value = NULL;
58         char *value2 = NULL;
59         int i = 0;
60         char *nnn = NULL;
61
62         msg = CtdlFetchMessage(msgnum, 1, 1);
63         if (msg == NULL) return;
64
65         v = vcard_load(msg->cm_fields[eMesageText]);
66         CM_Free(msg);
67
68         /*
69          * Try to match from a friendly name (the "fn" field).  If there is
70          * a match, return the entry in the form of:
71          *     Display Name <user@domain.org>
72          */
73         value = vcard_get_prop(v, "fn", 0, 0, 0);
74         if (value != NULL) if (bmstrcasestr(value, search_string)) {
75                 value2 = vcard_get_prop(v, "email", 1, 0, 0);
76                 if (value2 == NULL) value2 = "";
77                 cprintf("%s <%s>\n", value, value2);
78                 vcard_free(v);
79                 return;
80         }
81
82         /*
83          * Try to match from a structured name (the "n" field).  If there is
84          * a match, return the entry in the form of:
85          *     Display Name <user@domain.org>
86          */
87         value = vcard_get_prop(v, "n", 0, 0, 0);
88         if (value != NULL) if (bmstrcasestr(value, search_string)) {
89
90                 value2 = vcard_get_prop(v, "email", 1, 0, 0);
91                 if (value2 == NULL) value2 = "";
92                 nnn = n_to_fn(value);
93                 cprintf("%s <%s>\n", nnn, value2);
94                 free(nnn);
95                 vcard_free(v);
96                 return;
97         }
98
99         /*
100          * Try a partial match on all listed email addresses.
101          */
102         i = 0;
103         while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
104                 if (bmstrcasestr(value, search_string)) {
105                         if (vcard_get_prop(v, "fn", 0, 0, 0)) {
106                                 cprintf("%s <%s>\n", vcard_get_prop(v, "fn", 0, 0, 0), value);
107                         }
108                         else if (vcard_get_prop(v, "n", 0, 0, 0)) {
109                                 nnn = n_to_fn(vcard_get_prop(v, "n", 0, 0, 0));
110                                 cprintf("%s <%s>\n", nnn, value);
111                                 free(nnn);
112                         
113                         }
114                         else {
115                                 cprintf("%s\n", value);
116                         }
117                         vcard_free(v);
118                         return;
119                 }
120         }
121
122         vcard_free(v);
123 }
124
125
126
127 /*
128  * Attempt to autocomplete an address based on a partial...
129  */
130 void cmd_auto(char *argbuf) {
131         char hold_rm[ROOMNAMELEN];
132         char search_string[256];
133         long *msglist = NULL;
134         int num_msgs = 0;
135         long *fts_msgs = NULL;
136         int fts_num_msgs = 0;
137         struct cdbdata *cdbfr;
138         int r = 0;
139         int i = 0;
140         int j = 0;
141         int search_match = 0;
142         char *rooms_to_try[] = { USERCONTACTSROOM, ADDRESS_BOOK_ROOM };
143                 
144         if (CtdlAccessCheck(ac_logged_in)) return;
145         extract_token(search_string, argbuf, 0, '|', sizeof search_string);
146         if (IsEmptyStr(search_string)) {
147                 cprintf("%d You supplied an empty partial.\n",
148                         ERROR + ILLEGAL_VALUE);
149                 return;
150         }
151
152         strcpy(hold_rm, CC->room.QRname);       /* save current room */
153         cprintf("%d try these:\n", LISTING_FOLLOWS);
154
155         /*
156          * Gather up message pointers in rooms containing vCards
157          */
158         for (r=0; r < (sizeof(rooms_to_try) / sizeof(char *)); ++r) {
159                 if (CtdlGetRoom(&CC->room, rooms_to_try[r]) == 0) {
160                         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
161                         if (cdbfr != NULL) {
162                                 msglist = realloc(msglist, (num_msgs * sizeof(long)) + cdbfr->len + 1);
163                                 memcpy(&msglist[num_msgs], cdbfr->ptr, cdbfr->len);
164                                 num_msgs += (cdbfr->len / sizeof(long));
165                                 cdb_free(cdbfr);
166                         }
167                 }
168         }
169
170         /*
171          * Search-reduce the results if we have the full text index available
172          */
173         if (CtdlGetConfigInt("c_enable_fulltext")) {
174                 CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, search_string, "fulltext");
175                 if (fts_msgs) {
176                         for (i=0; i<num_msgs; ++i) {
177                                 search_match = 0;
178                                 for (j=0; j<fts_num_msgs; ++j) {
179                                         if (msglist[i] == fts_msgs[j]) {
180                                                 search_match = 1;
181                                                 j = fts_num_msgs + 1;   /* end the search */
182                                         }
183                                 }
184                                 if (!search_match) {
185                                         msglist[i] = 0;         /* invalidate this result */
186                                 }
187                         }
188                         free(fts_msgs);
189                 }
190                 else {
191                         /* If no results, invalidate the whole list */
192                         free(msglist);
193                         msglist = NULL;
194                         num_msgs = 0;
195                 }
196         }
197
198         /*
199          * Now output the ones that look interesting
200          */
201         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
202                 if (msglist[i] != 0) {
203                         hunt_for_autocomplete(msglist[i], search_string);
204                 }
205         }
206         
207         cprintf("000\n");
208         if (strcmp(CC->room.QRname, hold_rm)) {
209                 CtdlGetRoom(&CC->room, hold_rm);    /* return to saved room */
210         }
211
212         if (msglist) {
213                 free(msglist);
214         }
215         
216 }
217
218
219 CTDL_MODULE_INIT(autocompletion)
220 {
221         if (!threading)
222         {
223                 CtdlRegisterProtoHook(cmd_auto, "AUTO", "Do recipient autocompletion");
224         }
225         /* return our module name for the log */
226         return "autocompletion";
227 }