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