b9a4003233ea4b3b2be44232809fda8cb0cc45be
[apps/madmutt.git] / query.c
1 /*
2  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
3  * 
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
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  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
17  */
18
19 #if HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22
23 #include "mutt.h"
24 #include "mutt_menu.h"
25 #include "mutt_idna.h"
26 #include "mapping.h"
27 #include "sort.h"
28
29 #include <string.h>
30 #include <stdlib.h>
31 #include <ctype.h>
32
33 typedef struct query {
34   ADDRESS *addr;
35   char *name;
36   char *other;
37   struct query *next;
38 } QUERY;
39
40 typedef struct entry {
41   int tagged;
42   QUERY *data;
43 } ENTRY;
44
45 static struct mapping_t QueryHelp[] = {
46   {N_("Exit"), OP_EXIT},
47   {N_("Mail"), OP_MAIL},
48   {N_("New Query"), OP_QUERY},
49   {N_("Make Alias"), OP_CREATE_ALIAS},
50   {N_("Search"), OP_SEARCH},
51   {N_("Help"), OP_HELP},
52   {NULL}
53 };
54
55 /* Variables for outsizing output format */
56 static int FirstColumn;
57 static int SecondColumn;
58
59 static void query_menu (char *buf, size_t buflen, QUERY * results,
60                         int retbuf);
61
62 static ADDRESS *result_to_addr (QUERY * r)
63 {
64   static ADDRESS *tmp;
65
66   tmp = rfc822_cpy_adr (r->addr);
67
68   if (!tmp->next && !tmp->personal)
69     tmp->personal = safe_strdup (r->name);
70
71   mutt_addrlist_to_idna (tmp, NULL);
72   return tmp;
73 }
74
75 static QUERY *run_query (char *s, int quiet)
76 {
77   FILE *fp;
78   QUERY *first = NULL;
79   QUERY *cur = NULL;
80   char cmd[_POSIX_PATH_MAX];
81   char *buf = NULL;
82   size_t buflen;
83   int dummy = 0;
84   char msg[STRING];
85   char *p;
86   pid_t thepid;
87   int l;
88
89
90   mutt_expand_file_fmt (cmd, sizeof (cmd), QueryCmd, s);
91
92   if ((thepid = mutt_create_filter (cmd, NULL, &fp, NULL)) < 0) {
93     dprint (1, (debugfile, "unable to fork command: %s", cmd));
94     return 0;
95   }
96   if (!quiet)
97     mutt_message _("Waiting for response...");
98
99   fgets (msg, sizeof (msg), fp);
100   if ((p = strrchr (msg, '\n')))
101     *p = '\0';
102   while ((buf = mutt_read_line (buf, &buflen, fp, &dummy)) != NULL) {
103     if ((p = strtok (buf, "\t\n"))) {
104       if (first == NULL) {
105         FirstColumn = 0;
106         SecondColumn = 0;
107         first = (QUERY *) safe_calloc (1, sizeof (QUERY));
108         cur = first;
109       }
110       else {
111         cur->next = (QUERY *) safe_calloc (1, sizeof (QUERY));
112         cur = cur->next;
113       }
114
115       l = mutt_strwidth (p);
116       if (l > SecondColumn)
117         SecondColumn = l;
118
119       cur->addr = rfc822_parse_adrlist (cur->addr, p);
120       p = strtok (NULL, "\t\n");
121       if (p) {
122         l = mutt_strwidth (p);
123         if (l > FirstColumn)
124           FirstColumn = l;
125         cur->name = safe_strdup (p);
126         p = strtok (NULL, "\t\n");
127         if (p) {
128           cur->other = safe_strdup (p);
129         }
130       }
131     }
132   }
133   FREE (&buf);
134   fclose (fp);
135   if (mutt_wait_filter (thepid)) {
136     dprint (1, (debugfile, "Error: %s\n", msg));
137     if (!quiet)
138       mutt_error ("%s", msg);
139   }
140   else {
141     if (!quiet)
142       mutt_message ("%s", msg);
143   }
144
145   return first;
146 }
147
148 static int query_search (MUTTMENU * m, regex_t * re, int n)
149 {
150   ENTRY *table = (ENTRY *) m->data;
151
152   if (table[n].data->name && !regexec (re, table[n].data->name, 0, NULL, 0))
153     return 0;
154   if (table[n].data->other && !regexec (re, table[n].data->other, 0, NULL, 0))
155     return 0;
156   if (table[n].data->addr) {
157     if (table[n].data->addr->personal &&
158         !regexec (re, table[n].data->addr->personal, 0, NULL, 0))
159       return 0;
160     if (table[n].data->addr->mailbox &&
161         !regexec (re, table[n].data->addr->mailbox, 0, NULL, 0))
162       return 0;
163 #ifdef EXACT_ADDRESS
164     if (table[n].data->addr->val &&
165         !regexec (re, table[n].data->addr->val, 0, NULL, 0))
166       return 0;
167 #endif
168   }
169
170   return REG_NOMATCH;
171 }
172
173 /* This is the callback routine from mutt_menuLoop() which is used to generate
174  * a menu entry for the requested item number.
175  */
176 #define QUERY_MIN_COLUMN_LENGHT 20      /* Must be < 70/2 */
177 static void query_entry (char *s, size_t slen, MUTTMENU * m, int num)
178 {
179   ENTRY *table = (ENTRY *) m->data;
180   char buf2[SHORT_STRING], buf[SHORT_STRING] = "";
181
182   /* need a query format ... hard coded constants are not good */
183   while (FirstColumn + SecondColumn > 70) {
184     FirstColumn = FirstColumn * 3 / 4;
185     SecondColumn = SecondColumn * 3 / 4;
186     if (FirstColumn < QUERY_MIN_COLUMN_LENGHT)
187       FirstColumn = QUERY_MIN_COLUMN_LENGHT;
188     if (SecondColumn < QUERY_MIN_COLUMN_LENGHT)
189       SecondColumn = QUERY_MIN_COLUMN_LENGHT;
190   }
191
192   rfc822_write_address (buf, sizeof (buf), table[num].data->addr, 1);
193
194   mutt_format_string (buf2, sizeof (buf2),
195                       FirstColumn + 2, FirstColumn + 2,
196                       0, ' ', table[num].data->name,
197                       mutt_strlen (table[num].data->name), 0);
198
199   snprintf (s, slen, " %c %3d %s %-*.*s %s",
200             table[num].tagged ? '*' : ' ',
201             num + 1,
202             buf2,
203             SecondColumn + 2,
204             SecondColumn + 2, buf, NONULL (table[num].data->other));
205 }
206
207 static int query_tag (MUTTMENU * menu, int n, int m)
208 {
209   ENTRY *cur = &((ENTRY *) menu->data)[n];
210   int ot = cur->tagged;
211
212   cur->tagged = m >= 0 ? m : !cur->tagged;
213   return cur->tagged - ot;
214 }
215
216 int mutt_query_complete (char *buf, size_t buflen)
217 {
218   QUERY *results = NULL;
219   ADDRESS *tmpa;
220
221   if (!QueryCmd) {
222     mutt_error _("Query command not defined.");
223
224     return 0;
225   }
226
227   results = run_query (buf, 1);
228   if (results) {
229     /* only one response? */
230     if (results->next == NULL) {
231       tmpa = result_to_addr (results);
232       mutt_addrlist_to_local (tmpa);
233       buf[0] = '\0';
234       rfc822_write_address (buf, buflen, tmpa, 0);
235       rfc822_free_address (&tmpa);
236       mutt_clear_error ();
237       return (0);
238     }
239     /* multiple results, choose from query menu */
240     query_menu (buf, buflen, results, 1);
241   }
242   return (0);
243 }
244
245 void mutt_query_menu (char *buf, size_t buflen)
246 {
247   if (!QueryCmd) {
248     mutt_error _("Query command not defined.");
249
250     return;
251   }
252
253   if (buf == NULL) {
254     char buffer[STRING] = "";
255
256     query_menu (buffer, sizeof (buffer), NULL, 0);
257   }
258   else {
259     query_menu (buf, buflen, NULL, 1);
260   }
261 }
262
263 static void query_menu (char *buf, size_t buflen, QUERY * results, int retbuf)
264 {
265   MUTTMENU *menu;
266   HEADER *msg = NULL;
267   ENTRY *QueryTable = NULL;
268   QUERY *queryp = NULL;
269   int i, done = 0;
270   int op;
271   char helpstr[SHORT_STRING];
272   char title[STRING];
273
274   snprintf (title, sizeof (title), _("Query")); /* FIXME */
275
276   menu = mutt_new_menu ();
277   menu->make_entry = query_entry;
278   menu->search = query_search;
279   menu->tag = query_tag;
280   menu->menu = MENU_QUERY;
281   menu->title = title;
282   menu->help =
283     mutt_compile_help (helpstr, sizeof (helpstr), MENU_QUERY, QueryHelp);
284
285   if (results == NULL) {
286     /* Prompt for Query */
287     if (mutt_get_field (_("Query: "), buf, buflen, 0) == 0 && buf[0]) {
288       results = run_query (buf, 0);
289     }
290   }
291
292   if (results) {
293     snprintf (title, sizeof (title), _("Query '%s'"), buf);
294
295     /* count the number of results */
296     for (queryp = results; queryp; queryp = queryp->next)
297       menu->max++;
298
299     menu->data = QueryTable =
300       (ENTRY *) safe_calloc (menu->max, sizeof (ENTRY));
301
302     for (i = 0, queryp = results; queryp; queryp = queryp->next, i++)
303       QueryTable[i].data = queryp;
304
305     while (!done) {
306       switch ((op = mutt_menuLoop (menu))) {
307       case OP_QUERY_APPEND:
308       case OP_QUERY:
309         if (mutt_get_field (_("Query: "), buf, buflen, 0) == 0 && buf[0]) {
310           QUERY *newresults = NULL;
311
312           newresults = run_query (buf, 0);
313
314           menu->redraw = REDRAW_FULL;
315           if (newresults) {
316             snprintf (title, sizeof (title), _("Query '%s'"), buf);
317
318             if (op == OP_QUERY) {
319               queryp = results;
320               while (queryp) {
321                 rfc822_free_address (&queryp->addr);
322                 FREE (&queryp->name);
323                 FREE (&queryp->other);
324                 results = queryp->next;
325                 FREE (&queryp);
326                 queryp = results;
327               }
328               results = newresults;
329               FREE (&QueryTable);
330             }
331             else {
332               /* append */
333               for (queryp = results; queryp->next; queryp = queryp->next);
334
335               queryp->next = newresults;
336             }
337
338
339             menu->current = 0;
340             mutt_menuDestroy (&menu);
341             menu = mutt_new_menu ();
342             menu->make_entry = query_entry;
343             menu->search = query_search;
344             menu->tag = query_tag;
345             menu->menu = MENU_QUERY;
346             menu->title = title;
347             menu->help =
348               mutt_compile_help (helpstr, sizeof (helpstr), MENU_QUERY,
349                                  QueryHelp);
350
351             /* count the number of results */
352             for (queryp = results; queryp; queryp = queryp->next)
353               menu->max++;
354
355             if (op == OP_QUERY) {
356               menu->data = QueryTable =
357                 (ENTRY *) safe_calloc (menu->max, sizeof (ENTRY));
358
359               for (i = 0, queryp = results; queryp;
360                    queryp = queryp->next, i++)
361                 QueryTable[i].data = queryp;
362             }
363             else {
364               int clear = 0;
365
366               /* append */
367               safe_realloc (&QueryTable, menu->max * sizeof (ENTRY));
368
369               menu->data = QueryTable;
370
371               for (i = 0, queryp = results; queryp;
372                    queryp = queryp->next, i++) {
373                 /* once we hit new entries, clear/init the tag */
374                 if (queryp == newresults)
375                   clear = 1;
376
377                 QueryTable[i].data = queryp;
378                 if (clear)
379                   QueryTable[i].tagged = 0;
380               }
381             }
382           }
383         }
384         break;
385
386       case OP_CREATE_ALIAS:
387         if (menu->tagprefix) {
388           ADDRESS *naddr = NULL;
389
390           for (i = 0; i < menu->max; i++)
391             if (QueryTable[i].tagged) {
392               ADDRESS *a = result_to_addr (QueryTable[i].data);
393
394               rfc822_append (&naddr, a);
395               rfc822_free_address (&a);
396             }
397
398           mutt_create_alias (NULL, naddr);
399         }
400         else {
401           ADDRESS *a = result_to_addr (QueryTable[menu->current].data);
402
403           mutt_create_alias (NULL, a);
404           rfc822_free_address (&a);
405         }
406         break;
407
408       case OP_GENERIC_SELECT_ENTRY:
409         if (retbuf) {
410           done = 2;
411           break;
412         }
413         /* fall through to OP_MAIL */
414
415       case OP_MAIL:
416         msg = mutt_new_header ();
417         msg->env = mutt_new_envelope ();
418         if (!menu->tagprefix) {
419           msg->env->to = result_to_addr (QueryTable[menu->current].data);
420         }
421         else {
422           for (i = 0; i < menu->max; i++)
423             if (QueryTable[i].tagged) {
424               ADDRESS *a = result_to_addr (QueryTable[i].data);
425
426               rfc822_append (&msg->env->to, a);
427               rfc822_free_address (&a);
428             }
429         }
430         ci_send_message (0, msg, NULL, Context, NULL);
431         menu->redraw = REDRAW_FULL;
432         break;
433
434       case OP_EXIT:
435         done = 1;
436         break;
437       }
438     }
439
440     /* if we need to return the selected entries */
441     if (retbuf && (done == 2)) {
442       int tagged = 0;
443       size_t curpos = 0;
444
445       memset (buf, 0, buflen);
446
447       /* check for tagged entries */
448       for (i = 0; i < menu->max; i++) {
449         if (QueryTable[i].tagged) {
450           if (curpos == 0) {
451             ADDRESS *tmpa = result_to_addr (QueryTable[i].data);
452
453             mutt_addrlist_to_local (tmpa);
454             tagged = 1;
455             rfc822_write_address (buf, buflen, tmpa, 0);
456             curpos = mutt_strlen (buf);
457             rfc822_free_address (&tmpa);
458           }
459           else if (curpos + 2 < buflen) {
460             ADDRESS *tmpa = result_to_addr (QueryTable[i].data);
461
462             mutt_addrlist_to_local (tmpa);
463             strcat (buf, ", "); /* __STRCAT_CHECKED__ */
464             rfc822_write_address ((char *) buf + curpos + 1,
465                                   buflen - curpos - 1, tmpa, 0);
466             curpos = mutt_strlen (buf);
467             rfc822_free_address (&tmpa);
468           }
469         }
470       }
471       /* then enter current message */
472       if (!tagged) {
473         ADDRESS *tmpa = result_to_addr (QueryTable[menu->current].data);
474
475         mutt_addrlist_to_local (tmpa);
476         rfc822_write_address (buf, buflen, tmpa, 0);
477         rfc822_free_address (&tmpa);
478       }
479
480     }
481
482     queryp = results;
483     while (queryp) {
484       rfc822_free_address (&queryp->addr);
485       FREE (&queryp->name);
486       FREE (&queryp->other);
487       results = queryp->next;
488       FREE (&queryp);
489       queryp = results;
490     }
491     FREE (&QueryTable);
492
493     /* tell whoever called me to redraw the screen when I return */
494     set_option (OPTNEEDREDRAW);
495   }
496
497   mutt_menuDestroy (&menu);
498 }