pick.c - rohrpost - A commandline mail client to change the world as we see it.
(HTM) git clone git://r-36.net/rohrpost
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
pick.c (9149B)
---
1 /*
2 * Copy me if you can.
3 * by 20h
4 */
5
6 #include <unistd.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <strings.h>
10 #include <string.h>
11 #include <ctype.h>
12
13 #include "ind.h"
14 #include "arg.h"
15 #include "cfg.h"
16 #include "llist.h"
17 #include "imap.h"
18 #include "mark.h"
19 #include "pick.h"
20
21 enum {
22 PICK_NONE = 'O',
23 PICK_STR = 'S',
24 PICK_DATE = 'D',
25 PICK_NUM = 'N',
26 PICK_EXPR = 'E',
27 PICK_KEY = 'K',
28 PICK_SEQ = 'Q',
29 PICK_HEADERPREP = 'H',
30 PICK_END = '\0'
31 };
32 static char *desc[] = {
33 [PICK_NONE] = "None",
34 [PICK_STR] = "String",
35 [PICK_DATE] = "Date",
36 [PICK_NUM] = "Number",
37 [PICK_EXPR] = "Expression",
38 [PICK_KEY] = "Keyword",
39 [PICK_SEQ] = "Sequence (also: marks)",
40 [PICK_HEADERPREP] = "Header prepend"
41 };
42
43 typedef struct expression_t expression_t;
44 struct expression_t {
45 char *expr;
46 char *syntax;
47 };
48
49 static expression_t expressions[] = {
50 {"ANSWERED", ""},
51 {"BCC", "S"},
52 {"BEFORE", "D"},
53 {"BODY", "S"},
54 {"CC", "S"},
55 {"DELETED", ""},
56 {"DRAFT", ""},
57 {"FLAGGED", ""},
58 {"FROM", "HS"},
59 {"HEADER", "SS"},
60 {"KEYWORD", "K"},
61 {"LARGER", "N"},
62 {"NEW", ""},
63 {"NOT", "E"},
64 {"OLD", ""},
65 {"ON", "D"},
66 {"OR", "EE"},
67 {"RECENT", ""},
68 {"SEEN", ""},
69 {"SENTBEFORE", "D"},
70 {"SENTON", "D"},
71 {"SENTSINCE", "D"},
72 {"SEQ", "Q"},
73 {"SINCE", "D"},
74 {"SMALLER", "N"},
75 {"SUBJECT", "HS"},
76 {"TEXT", "S"},
77 {"TO", "HS"},
78 {"UID", "Q"},
79 {"UNANSWERED", ""},
80 {"UNDELETED", ""},
81 {"UNDRAFTED", ""},
82 {"UNFLAGGED", ""},
83 {"UNKEYWORD", ""},
84 {"UNSEEN", ""}
85 };
86
87 static char *sortcriteria[] = {
88 "ARRIVAL",
89 "CC",
90 "DATE",
91 "FROM",
92 "REVERSE",
93 "SIZE",
94 "SUBJECT",
95 "TO"
96 };
97
98 /*
99 * The order of this array is the order, in which the algorithm
100 * for threading is selected.
101 */
102 static char *threadalgorithms[] = {
103 "REFERENCES",
104 "REFS",
105 "ORDEREDSUBJECT"
106 };
107
108 char *
109 pick_mksearchstring(char *cfgn, char *mailbox, char **argv[])
110 {
111 int j;
112 expression_t *expr;
113 char *rstr, *estr, *nestr, *astr, *idss;
114 llist_t *ids;
115
116 rstr = NULL;
117 estr = NULL;
118
119 do {
120 if ((*argv)[0][0] != ':')
121 die("Missing ':' at %s\n", (*argv)[0]);
122
123 expr = NULL;
124 for (j = 0; j < nelem(expressions); j++) {
125 if (!strcasecmp((*argv)[0]+1, expressions[j].expr)) {
126 expr = &expressions[j];
127 break;
128 }
129 }
130 if (expr == NULL)
131 die("Expression '%s' unknown.\n", (*argv)[0]);
132
133 switch (expr->syntax[0]) {
134 case PICK_HEADERPREP:
135 nestr = smprintf("HEADER %s", expr->expr);
136 break;
137 case PICK_SEQ:
138 nestr = smprintf("");
139 break;
140 default:
141 nestr = smprintf("%s", expr->expr);
142 break;
143 }
144 if (estr == NULL) {
145 estr = nestr;
146 } else {
147 estr = smprintf("%s %s", estr, nestr);
148 }
149
150 *argv = &(*argv)[1];
151 for (j = 0; expr->syntax[j] != PICK_END; j++) {
152 if (!(*argv)[0]) {
153 die("Expected argument of type %s at '%s'\n",
154 desc[(int)expr->syntax[j]],
155 (*argv)[-1]);
156 }
157
158 astr = estr;
159 switch (expr->syntax[j]) {
160 case PICK_HEADERPREP:
161 continue;
162 case PICK_STR:
163 estr = smprintf("%s \"%s\"", estr, (*argv)[0]);
164 *argv = &(*argv)[1];
165 break;
166 case PICK_SEQ:
167 ids = imap_str2ids(cfgn, mailbox, (*argv)[0]);
168 idss = imap_ids2str(ids);
169 estr = smprintf("%s %s", estr, idss);
170 free(idss);
171 llist_free(ids);
172 *argv = &(*argv)[1];
173 break;
174 case PICK_NUM:
175 case PICK_KEY:
176 case PICK_DATE:
177 estr = smprintf("%s %s", estr, (*argv)[0]);
178 *argv = &(*argv)[1];
179 break;
180 case PICK_EXPR:
181 rstr = pick_mksearchstring(cfgn, mailbox, argv);
182 estr = smprintf("%s %s", estr, rstr);
183 free(rstr);
184 free(astr);
185 continue;
186 default:
187 continue;
188 }
189 free(astr);
190 }
191 } while((*argv)[0] != NULL);
192
193 return estr;
194 }
195
196 llist_t *
197 pick_sanitizesort(llist_t *sortl)
198 {
199 llistelem_t *elem;
200 int i, found, isreverse;
201
202 isreverse = 0;
203 forllist(sortl, elem) {
204 for (i = 0; i < strlen(elem->key); i++) {
205 if (islower(elem->key[i]))
206 elem->key[i] = toupper(elem->key[i]) & 0xFF;
207 }
208
209 found = 0;
210 for (i = 0; i < nelem(sortcriteria); i++) {
211 if (!strcmp(elem->key, sortcriteria[i]))
212 found = 1;
213 }
214 if (!found)
215 die("Sort criterion '%s' not supported.\n", elem->key);
216
217 if (!strcasecmp(elem->key, "REVERSE")) {
218 if (isreverse) {
219 die("You want to 'REVERSE REVERSE', which"
220 " is not possible.\n");
221 }
222 isreverse = 1;
223 } else {
224 isreverse = 0;
225 }
226 }
227
228 if (isreverse) {
229 die("Your last search criterion is 'REVERSE', which is"
230 " not possible.\n");
231 }
232
233 return sortl;
234 }
235
236 char *
237 pick_threadalgorithm(imap_t *imap)
238 {
239 llistelem_t *cap;
240 int i;
241
242 forllist(imap->caps, cap) {
243 if (!strncmp(cap->key, "THREAD", 6)) {
244 for (i = 0; i < nelem(threadalgorithms); i++) {
245 if (!strcmp(cap->key+7, threadalgorithms[i]))
246 return threadalgorithms[i];
247 }
248 }
249 }
250
251 return NULL;
252 }
253
254 void
255 pick_printsearchsyntax(void)
256 {
257 int i, j;
258
259 printf("SYNTAX:\n"
260 "The search syntax is a small layer above the "
261 "specification in IMAP4v1 (RFC3501).\n"
262 "Every expression begins with the search keyword, "
263 "prepended by a colon.\n"
264 "\n"
265 "Example: :or :text \"Hello World\" :unseen\n"
266 "\n"
267 "ARGUMENTS:\n"
268 "String: UTF-8 string\n"
269 "Date: e.g. \"5-JUL-2010\" RFC 2822 Section 3.3\n"
270 "Number: decimal number\n"
271 "Expression: an expression\n"
272 "Keyword: String\n"
273 "Sequence: e.g. 1,2,3,5:7,300:320\n"
274 "\n"
275 "EXPRESSIONS:\n"
276 "Expression: arguments\n");
277 for (i = 0; i < nelem(expressions); i++) {
278 printf("%s:", expressions[i].expr);
279 for (j = 0; expressions[i].syntax[j] != PICK_END; j++) {
280 if (expressions[i].syntax[j] == PICK_HEADERPREP)
281 continue;
282 printf(" %s", desc[(int)expressions[i].syntax[j]]);
283 }
284 printf("\n");
285 }
286 }
287
288 void
289 pick_printsortsyntax(void)
290 {
291 int i;
292
293 printf("\nSORT:\n"
294 "The -o flag is taking as an argument a list of "
295 "criteria.\n"
296 "They can be specified as upper- or lowercase.\n"
297 "\n"
298 "CRITERIA:\n");
299 for (i = 0; i < nelem(sortcriteria); i++)
300 printf("%s\n", sortcriteria[i]);
301 }
302
303 void
304 pick_printthreadsyntax(void)
305 {
306 int i;
307
308 printf("\nTHREAD:\n"
309 "The -t flag will trigger the thread structure of "
310 "the given search results to be written linearly "
311 "into the results sequence.\n"
312 "Following thread algorithms are defined in various "
313 "RFCs. The list given is the order in which on an "
314 "algorithm is decided.\n\n");
315 for (i = 0; i < nelem(threadalgorithms); i++)
316 printf("%s\n", threadalgorithms[i]);
317 }
318
319 void
320 pick_printsyntax(void)
321 {
322 pick_printsearchsyntax();
323 pick_printsortsyntax();
324 pick_printthreadsyntax();
325 }
326
327 void
328 pickusage(char *argv0)
329 {
330 die("usage: %s [-sqdht] [-c cfg] [-m folder] [-o sort criteria]"
331 " [-e seq] [search syntax]\n"
332 "See -s for syntax help.\n", argv0);
333 }
334
335 int
336 pickmain(int argc, char *argv[])
337 {
338 config_t *cfg;
339 imap_t *imap;
340 int status;
341 char *user, *pass, *netspec, *addseq, *sstr, *pstr, *selected,
342 *sorts, *talg, *cfgn, *argv0;
343 llist_t *results, *sortl;
344 mark_t *marks;
345
346 enum {
347 BEQUIET = 0x01,
348 DRYRUN = 0x02,
349 PRINTSYNTAX = 0x04,
350 DOTHREAD = 0x08,
351
352 NOARGS = 0x10
353 };
354
355 status = 0;
356 addseq = "p";
357 sorts = NULL;
358 selected = NULL;
359 cfgn = NULL;
360
361 ARGBEGIN(argv0) {
362 case 'c':
363 cfgn = EARGF(pickusage(argv0));
364 break;
365 case 'd':
366 status |= DRYRUN;
367 break;
368 case 'e':
369 addseq = EARGF(pickusage(argv0));
370 break;
371 case 'm':
372 selected = EARGF(pickusage(argv0));
373 break;
374 case 'o':
375 sorts = EARGF(pickusage(argv0));
376 break;
377 case 'q':
378 status |= BEQUIET;
379 break;
380 case 's':
381 status |= PRINTSYNTAX;
382 break;
383 case 't':
384 status |= DOTHREAD;
385 break;
386 default:
387 pickusage(argv0);
388 } ARGEND;
389
390 if (status & PRINTSYNTAX) {
391 pick_printsyntax();
392 return 0;
393 }
394
395 if (argc < 1)
396 pickusage(argv0);
397
398 cfg = config_init(cfgn);
399 user = config_checkgetstr(cfg, "imapuser");
400 pass = config_checkgetstr(cfg, "imappass");
401 netspec = config_checkgetstr(cfg, "imapnet");
402 if (selected == NULL) {
403 selected = config_checkgetstr(cfg, "selected");
404 } else {
405 selected = memdups(selected);
406 }
407 if (cfg->name != NULL) {
408 cfgn = memdups(cfg->name);
409 } else {
410 cfgn = memdups(cfgn);
411 }
412
413 marks = mark_init(cfg->name, selected);
414 if (marks == NULL)
415 die("Could not initialize marks for %s.\n", addseq);
416
417 imap = imap_new(netspec, user, pass);
418
419 if (imap_init(imap))
420 imap_die(imap, "imap_init");
421 if (imap_select(imap, selected))
422 imap_die(imap, "imap_select");
423 config_free(cfg);
424
425 sstr = pick_mksearchstring(cfgn, selected, &argv);
426 free(cfgn);
427 if (status & DOTHREAD) {
428 talg = pick_threadalgorithm(imap);
429 if (talg == NULL) {
430 die("Could not find a supported threading "
431 "algorithm.\n");
432 }
433
434 results = imap_thread(imap, talg, sstr);
435 } else {
436 if (sorts != NULL) {
437 sortl = llist_splitstr(sorts, " ,");
438 sortl = pick_sanitizesort(sortl);
439 sorts = llist_joinstr(sortl, " ");
440 llist_free(sortl);
441 results = imap_sort(imap, sorts, sstr);
442 free(sorts);
443 } else {
444 results = imap_search(imap, sstr);
445 }
446 }
447 free(sstr);
448
449 if (results == NULL) {
450 printf("No results found.\n");
451 mark_stop(marks);
452 imap_close(imap);
453 imap_free(imap);
454 return 1;
455 }
456
457 pstr = llist_joinstr(results, " ");
458 llist_free(results);
459 mark_set(marks, addseq, pstr);
460 if (!(status & BEQUIET))
461 printf("%s\n", pstr);
462 free(pstr);
463
464 mark_stop(marks);
465 imap_close(imap);
466 imap_free(imap);
467 return 0;
468 }
469