]> code.citadel.org Git - citadel.git/blob - citadel/server/modules/autocompletion/serv_autocompletion.c
DRY fetch of msglists.
[citadel.git] / citadel / server / modules / autocompletion / serv_autocompletion.c
1 // Autocompletion of email recipients, etc.
2 //
3 // Copyright (c) 1987-2023 by the citadel.org team
4 //
5 // This program is open source software.  Use, duplication, or disclosure
6 // are subject to the terms of the GNU General Public License version 3.
7
8 #include "../../ctdl_module.h"
9 #include "serv_autocompletion.h"
10 #include "../../config.h"
11 #include "../../room_ops.h"
12
13
14 // Convert a structured name into a friendly name.  Caller must free the
15 // returned pointer.
16 char *n_to_fn(char *value) {
17         char *nnn = NULL;
18         int i;
19
20         nnn = malloc(strlen(value) + 10);
21         strcpy(nnn, "");
22         extract_token(&nnn[strlen(nnn)] , value, 3, ';', 999);
23         strcat(nnn, " ");
24         extract_token(&nnn[strlen(nnn)] , value, 1, ';', 999);
25         strcat(nnn, " ");
26         extract_token(&nnn[strlen(nnn)] , value, 2, ';', 999);
27         strcat(nnn, " ");
28         extract_token(&nnn[strlen(nnn)] , value, 0, ';', 999);
29         strcat(nnn, " ");
30         extract_token(&nnn[strlen(nnn)] , value, 4, ';', 999);
31         strcat(nnn, " ");
32         for (i=0; i<strlen(nnn); ++i) {
33                 if (!strncmp(&nnn[i], "  ", 2)) strcpy(&nnn[i], &nnn[i+1]);
34         }
35         string_trim(nnn);
36         return(nnn);
37 }
38
39
40 // Back end for cmd_auto()
41 void hunt_for_autocomplete(long msgnum, char *search_string) {
42         struct CtdlMessage *msg;
43         struct vCard *v;
44         char *value = NULL;
45         char *value2 = NULL;
46         int i = 0;
47         char *nnn = NULL;
48
49         msg = CtdlFetchMessage(msgnum, 1);
50         if (msg == NULL) return;
51
52         v = vcard_load(msg->cm_fields[eMesageText]);
53         CM_Free(msg);
54
55         // Try to match from a friendly name (the "fn" field).  If there is
56         // a match, return the entry in the form of:
57         //     Display Name <user@domain.org>
58         value = vcard_get_prop(v, "fn", 0, 0, 0);
59         if (value != NULL) if (bmstrcasestr(value, search_string)) {
60                 value2 = vcard_get_prop(v, "email", 1, 0, 0);
61                 if (value2 == NULL) value2 = "";
62                 cprintf("%s <%s>\n", value, value2);
63                 vcard_free(v);
64                 return;
65         }
66
67         // Try to match from a structured name (the "n" field).  If there is
68         // a match, return the entry in the form of:
69         //     Display Name <user@domain.org>
70         value = vcard_get_prop(v, "n", 0, 0, 0);
71         if (value != NULL) if (bmstrcasestr(value, search_string)) {
72
73                 value2 = vcard_get_prop(v, "email", 1, 0, 0);
74                 if (value2 == NULL) value2 = "";
75                 nnn = n_to_fn(value);
76                 cprintf("%s <%s>\n", nnn, value2);
77                 free(nnn);
78                 vcard_free(v);
79                 return;
80         }
81
82         // Try a partial match on all listed email addresses.
83         i = 0;
84         while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) {
85                 if (bmstrcasestr(value, search_string)) {
86                         if (vcard_get_prop(v, "fn", 0, 0, 0)) {
87                                 cprintf("%s <%s>\n", vcard_get_prop(v, "fn", 0, 0, 0), value);
88                         }
89                         else if (vcard_get_prop(v, "n", 0, 0, 0)) {
90                                 nnn = n_to_fn(vcard_get_prop(v, "n", 0, 0, 0));
91                                 cprintf("%s <%s>\n", nnn, value);
92                                 free(nnn);
93                         }
94                         else {
95                                 cprintf("%s\n", value);
96                         }
97                         vcard_free(v);
98                         return;
99                 }
100         }
101
102         vcard_free(v);
103 }
104
105
106 // Attempt to autocomplete an address based on a partial...
107 void cmd_auto(char *argbuf) {
108         char hold_rm[ROOMNAMELEN];
109         char search_string[256];
110         long *msglist = NULL;
111         int num_msgs = 0;
112         long *fts_msgs = NULL;
113         int fts_num_msgs = 0;
114         int r = 0;
115         int i = 0;
116         int j = 0;
117         int search_match = 0;
118         char *rooms_to_try[] = { USERCONTACTSROOM, ADDRESS_BOOK_ROOM };
119                 
120         if (CtdlAccessCheck(ac_logged_in)) return;
121         extract_token(search_string, argbuf, 0, '|', sizeof search_string);
122         if (IsEmptyStr(search_string)) {
123                 cprintf("%d You supplied an empty partial.\n", ERROR + ILLEGAL_VALUE);
124                 return;
125         }
126
127         strcpy(hold_rm, CC->room.QRname);                        // save where we were so we can go back
128         cprintf("%d try these:\n", LISTING_FOLLOWS);
129
130         // Gather up message pointers in rooms containing vCards
131         for (r=0; r < (sizeof(rooms_to_try) / sizeof(char *)); ++r) {
132                 if (CtdlGetRoom(&CC->room, rooms_to_try[r]) == 0) {
133                         num_msgs = CtdlFetchMsgList(CC->room.QRnumber, &msglist);
134
135                         // Search-reduce the results if we have the full text index available
136                         if (CtdlGetConfigInt("c_enable_fulltext")) {
137                                 CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, search_string, "fulltext");
138                                 if (fts_msgs) {
139                                         for (i=0; i<num_msgs; ++i) {
140                                                 search_match = 0;
141                                                 for (j=0; j<fts_num_msgs; ++j) {
142                                                         if (msglist[i] == fts_msgs[j]) {
143                                                                 search_match = 1;
144                                                                 j = fts_num_msgs + 1;   // end the search 
145                                                         }
146                                                 }
147                                                 if (!search_match) {
148                                                         msglist[i] = 0;                 // invalidate this result
149                                                 }
150                                         }
151                                         free(fts_msgs);
152                                 }
153                                 else {
154                                         // If no results, invalidate the whole list
155                                         free(msglist);
156                                         msglist = NULL;
157                                         num_msgs = 0;
158                                 }
159                         }
160                 
161                         // Now output the ones that look interesting
162                         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
163                                 if (msglist[i] != 0) {
164                                         hunt_for_autocomplete(msglist[i], search_string);
165                                 }
166                         }
167                 }
168         }
169         
170         cprintf("000\n");
171         if (strcmp(CC->room.QRname, hold_rm)) {
172                 CtdlGetRoom(&CC->room, hold_rm);                                        // return to saved room
173         }
174
175         if (msglist) {
176                 free(msglist);
177         }
178         
179 }
180
181
182 // Initialization function, called from modules_init.c
183 char *ctdl_module_init_autocompletion(void) {
184         if (!threading) {
185                 CtdlRegisterProtoHook(cmd_auto, "AUTO", "Do recipient autocompletion");
186         }
187         // return our module name for the log
188         return "autocompletion";
189 }