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