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