]> code.citadel.org Git - citadel.git/blob - citadel/serv_fulltext.c
*** empty log message ***
[citadel.git] / citadel / serv_fulltext.c
1 /*
2  * $Id$
3  *
4  * This module handles fulltext indexing of the message base.
5  *
6  */
7
8
9 #include "sysdep.h"
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <stdio.h>
13 #include <fcntl.h>
14 #include <signal.h>
15 #include <pwd.h>
16 #include <errno.h>
17 #include <sys/types.h>
18
19 #if TIME_WITH_SYS_TIME
20 # include <sys/time.h>
21 # include <time.h>
22 #else
23 # if HAVE_SYS_TIME_H
24 #  include <sys/time.h>
25 # else
26 #  include <time.h>
27 # endif
28 #endif
29
30 #include <sys/wait.h>
31 #include <string.h>
32 #include <limits.h>
33 #include "citadel.h"
34 #include "server.h"
35 #include "sysdep_decls.h"
36 #include "citserver.h"
37 #include "support.h"
38 #include "config.h"
39 #include "serv_extensions.h"
40 #include "database.h"
41 #include "msgbase.h"
42 #include "control.h"
43 #include "room_ops.h"
44 #include "tools.h"
45 #include "serv_fulltext.h"
46 #include "ft_wordbreaker.h"
47
48
49 long ft_newhighest = 0L;
50 long *ft_newmsgs = NULL;
51 int ft_num_msgs = 0;
52 int ft_num_alloc = 0;
53
54
55 /*
56  * Index or de-index a message.  (op == 1 to index, 0 to de-index)
57  */
58 void ft_index_message(long msgnum, int op) {
59         struct CtdlMessage *msg;
60         int num_tokens = 0;
61         int *tokens = NULL;
62         int i;
63         struct cdbdata *cdb_bucket;
64         int num_msgs;
65         long *msgs;
66
67         msg = CtdlFetchMessage(msgnum, 1);
68         if (msg == NULL) return;
69
70         if (msg->cm_fields['M'] != NULL) {
71                 wordbreaker(msg->cm_fields['M'], &num_tokens, &tokens);
72         }
73         CtdlFreeMessage(msg);
74
75         if (num_tokens > 0) {
76                 for (i=0; i<num_tokens; ++i) {
77
78                         /* Add the message to the relevant token bucket */
79
80                         /* FIXME do "if op=1" ... */
81
82                         /* FIXME lock the file */
83                         cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tokens[i], sizeof(int));
84                         if (cdb_bucket == NULL) {
85                                 cdb_bucket = malloc(sizeof(struct cdbdata));
86                                 cdb_bucket->len = 0;
87                                 cdb_bucket->ptr = malloc(sizeof(long));
88                                 num_msgs = 0;
89                         }
90                         else {
91                                 num_msgs = cdb_bucket->len / sizeof(long);
92                         }
93
94                         ++num_msgs;
95                         cdb_bucket->ptr = realloc(cdb_bucket->ptr, num_msgs*sizeof(long) );
96                         msgs = (long *) cdb_bucket->ptr;
97                         msgs[num_msgs - 1] = msgnum;
98
99                         cdb_store(CDB_FULLTEXT, &tokens[i], sizeof(int),
100                                 cdb_bucket->ptr, num_msgs*sizeof(long) );
101
102                         cdb_free(cdb_bucket);
103
104                         /* FIXME unlock the file */
105                 }
106
107                 free(tokens);
108         }
109 }
110
111
112
113 /*
114  * Add a message to the list of those to be indexed.
115  */
116 void ft_index_msg(long msgnum, void *userdata) {
117
118         if ((msgnum > CitControl.MMfulltext) && (msgnum <= ft_newhighest)) {
119                 ++ft_num_msgs;
120                 if (ft_num_msgs > ft_num_alloc) {
121                         ft_num_alloc += 1024;
122                         ft_newmsgs = realloc(ft_newmsgs,
123                                 (ft_num_alloc * sizeof(long)));
124                 }
125                 ft_newmsgs[ft_num_msgs - 1] = msgnum;
126         }
127
128 }
129
130 /*
131  * Scan a room for messages to index.
132  */
133 void ft_index_room(struct ctdlroom *qrbuf, void *data)
134 {
135         getroom(&CC->room, qrbuf->QRname);
136         CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, ft_index_msg, NULL);
137 }
138
139
140 /*
141  * Compare function
142  */
143 int longcmp(const void *rec1, const void *rec2) {
144         long i1, i2;
145
146         i1 = *(const long *)rec1;
147         i2 = *(const long *)rec2;
148
149         if (i1 > i2) return(1);
150         if (i1 < i2) return(-1);
151         return(0);
152 }
153
154
155 /*
156  * Begin the fulltext indexing process.  (Called as an EVT_TIMER event)
157  */
158 void do_fulltext_indexing(void) {
159         int i;
160         static time_t last_index = 0L;
161
162         /*
163          * Make sure we don't run the indexer too frequently.
164          * FIXME move the setting into config
165          */
166         if ( (time(NULL) - last_index) < 300L) {
167                 return;
168         }
169
170         /*
171          * Check to see whether the fulltext index is up to date; if there
172          * are no messages to index, don't waste any more time trying.
173          */
174         lprintf(CTDL_DEBUG, "CitControl.MMhighest  = %ld\n",
175                 CitControl.MMhighest);
176         lprintf(CTDL_DEBUG, "CitControl.MMfulltext = %ld\n",
177                 CitControl.MMfulltext);
178         if (CitControl.MMfulltext >= CitControl.MMhighest) {
179                 /* nothing to do! */
180                 return;
181         }
182
183         lprintf(CTDL_DEBUG, "do_fulltext_indexing() started\n");
184         
185         /*
186          * If we've switched wordbreaker modules, burn the index and start
187          * over.  FIXME write this...
188          */
189         if (CitControl.fulltext_wordbreaker != FT_WORDBREAKER_ID) {
190                 lprintf(CTDL_INFO, "(re)initializing full text index\n");
191                 cdb_trunc(CDB_FULLTEXT);
192                 CitControl.MMfulltext = 0L;
193                 put_control();
194         }
195
196         /*
197          * Now go through each room and find messages to index.
198          */
199         ft_newhighest = CitControl.MMhighest;
200         ForEachRoom(ft_index_room, NULL);       /* load all msg pointers */
201
202         if (ft_num_msgs > 0) {
203                 qsort(ft_newmsgs, ft_num_msgs, sizeof(long), longcmp);
204                 if (i>1) for (i=0; i<(ft_num_msgs-1); ++i) { /* purge dups */
205                         if (ft_newmsgs[i] == ft_newmsgs[i+1]) {
206                                 memmove(&ft_newmsgs[i], &ft_newmsgs[i+1],
207                                         ((ft_num_msgs - i)*sizeof(long)));
208                                 --ft_num_msgs;
209                         }
210                 }
211
212                 /* Here it is ... do each message! */
213                 for (i=0; i<ft_num_msgs; ++i) {
214                         ft_index_message(ft_newmsgs[i], 1);
215                 }
216
217                 free(ft_newmsgs);
218                 ft_num_msgs = 0;
219                 ft_num_alloc = 0;
220                 ft_newmsgs = NULL;
221         }
222
223         /* Save our place so we don't have to do this again */
224         CitControl.MMfulltext = ft_newhighest;
225         CitControl.fulltext_wordbreaker = FT_WORDBREAKER_ID;
226         put_control();
227         last_index = time(NULL);
228
229         lprintf(CTDL_DEBUG, "do_fulltext_indexing() finished\n");
230         return;
231 }
232
233
234 /*
235  * Tentative form of our search command
236  */
237 void cmd_srch(char *argbuf) {
238         char search_string[256];
239         int num_tokens = 0;
240         int *tokens = NULL;
241         int i, j;
242         struct cdbdata *cdb_bucket;
243         int num_msgs;
244         long *msgs;
245
246         if (CtdlAccessCheck(ac_logged_in)) return;
247         extract_token(search_string, argbuf, 0, '|', sizeof search_string);
248         wordbreaker(search_string, &num_tokens, &tokens);
249
250         cprintf("%d msgs matching search words:\n", LISTING_FOLLOWS);
251         if (num_tokens > 0) {
252                 for (i=0; i<num_tokens; ++i) {
253
254                         /* search for tokens[i] */
255                         cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tokens[i], sizeof(int));
256                         if (cdb_bucket != NULL) {
257                                 num_msgs = cdb_bucket->len / sizeof(long);
258                                 msgs = (long *)cdb_bucket->ptr;
259                                 cdb_free(cdb_bucket);
260                                 for (j=0; j<num_msgs; ++j) {
261                                         cprintf("Token <%d> is in msg <%ld>\n", tokens[i], msgs[j]);
262                                 }
263                         }
264
265                 }
266                 free(tokens);
267         }
268         cprintf("000\n");
269 }
270
271
272 /*****************************************************************************/
273
274 char *serv_fulltext_init(void)
275 {
276         CtdlRegisterSessionHook(do_fulltext_indexing, EVT_TIMER);
277         CtdlRegisterProtoHook(cmd_srch, "SRCH", "Full text search");
278         return "$Id$";
279 }