NEWS: update for v14.5.2
[s-mailx.git] / imap_cache.c
blob775b66e861fddc3cfd6d435dcb368f3a1830dfc5
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ A cache for IMAP.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 2004
9 * Gunnar Ritter. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by Gunnar Ritter
22 * and his contributors.
23 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 EMPTY_FILE(imap_cache)
45 #ifdef HAVE_IMAP
46 #include <dirent.h>
47 #include <fcntl.h>
49 static char *encname(struct mailbox *mp, const char *name, int same,
50 const char *box);
51 static char *encuid(struct mailbox *mp, unsigned long uid);
52 static FILE *clean(struct mailbox *mp, struct cw *cw);
53 static unsigned long *builds(long *contentelem);
54 static void purge(struct mailbox *mp, struct message *m, long mc,
55 struct cw *cw, const char *name);
56 static int longlt(const void *a, const void *b);
57 static void remve(unsigned long n);
58 static FILE *cache_queue1(struct mailbox *mp, char const *mode, char **xname);
59 static enum okay dequeue1(struct mailbox *mp);
61 static const char infofmt[] = "%c %lu %d %lu %ld";
62 #define INITSKIP 128L
63 #define USEBITS(f) \
64 ((f) & (MSAVED|MDELETED|MREAD|MBOXED|MNEW|MFLAGGED|MANSWERED|MDRAFTED))
66 static const char README1[] = "\
67 This is a cache directory maintained by " UAGENT "(1). You should not change any\n\
68 files within. Nevertheless, the structure is as follows: Each subdirectory\n\
69 of the current directory represents an IMAP account, and each subdirectory\n\
70 below that represents a mailbox. Each mailbox directory contains a file\n\
71 named UIDVALIDITY which describes the validity in relation to the version\n\
72 on the server. Other files have names corresponding to their IMAP UID.\n";
73 static const char README2[] = "\n\
74 The first 128 bytes of these files are used to store message attributes; the\n\
75 following data is equivalent to compress(1) output. So if you have to save a\n\
76 message by hand because of an emergency, throw away the first 128 bytes and\n\
77 decompress the rest, as e.g. 'dd if=MESSAGEFILE skip=1 bs=128 | zcat' does.\n";
78 static const char README3[] = "\n\
79 Files named QUEUE contain data that will be sent do the IMAP server next\n\
80 time a connection is made in online mode.\n";
81 static const char README4[] = "\n\
82 You can safely delete any file or directory here, unless it contains a QUEUE\n\
83 file that is not empty; mailx(1) will download the data again and will also\n\
84 write new cache entries if configured in this way. If you do not wish to use\n\
85 the cache anymore, delete the entire directory and unset the 'imap-cache'\n\
86 variable in mailx(1).\n";
87 static const char README5[] = "\n\
88 For more information about " UAGENT "(1), visit\n\
89 <http://sdaoden.users.sourceforge.net/code.html>.\n"; /* TODO MAGIC CONSTANT */
91 static char *
92 encname(struct mailbox *mp, const char *name, int same, const char *box)
94 char *cachedir, *eaccount, *ename, *res;
95 char const *emailbox;
96 int resz;
98 ename = urlxenc(name);
99 if (mp->mb_cache_directory && same && box == NULL) {
100 res = salloc(resz = strlen(mp->mb_cache_directory) +
101 strlen(ename) + 2);
102 snprintf(res, resz, "%s%s%s", mp->mb_cache_directory,
103 *ename ? "/" : "", ename);
104 } else {
105 if ((cachedir = ok_vlook(imap_cache)) == NULL ||
106 (cachedir = file_expand(cachedir)) == NULL)
107 return NULL;
108 eaccount = urlxenc(mp->mb_imap_account);
109 if (box)
110 emailbox = urlxenc(box);
111 else if (asccasecmp(mp->mb_imap_mailbox, "INBOX"))
112 emailbox = urlxenc(mp->mb_imap_mailbox);
113 else
114 emailbox = "INBOX";
115 res = salloc(resz = strlen(cachedir) + strlen(eaccount) +
116 strlen(emailbox) + strlen(ename) + 4);
117 snprintf(res, resz, "%s/%s/%s%s%s",
118 cachedir, eaccount, emailbox,
119 *ename ? "/" : "", ename);
121 return res;
124 static char *
125 encuid(struct mailbox *mp, unsigned long uid)
127 char buf[30];
129 snprintf(buf, sizeof buf, "%lu", uid);
130 return encname(mp, buf, 1, NULL);
133 FL enum okay
134 getcache1(struct mailbox *mp, struct message *m, enum needspec need,
135 int setflags)
137 FILE *fp;
138 long n = 0, size = 0, xsize, xtime, xlines = -1, lines = 0;
139 int lastc = EOF, i, xflag, inheader = 1;
140 char b, iob[32768];
141 off_t offset;
142 void *zp;
144 if (setflags == 0 && ((mp->mb_type != MB_IMAP &&
145 mp->mb_type != MB_CACHE) ||
146 m->m_uid == 0))
147 return STOP;
148 if ((fp = Fopen(encuid(mp, m->m_uid), "r")) == NULL)
149 return STOP;
150 (void)fcntl_lock(fileno(fp), F_RDLCK);
151 if (fscanf(fp, infofmt, &b, (unsigned long*)&xsize, &xflag,
152 (unsigned long*)&xtime, &xlines) < 4)
153 goto fail;
154 if (need != NEED_UNSPEC) {
155 switch (b) {
156 case 'H':
157 if (need == NEED_HEADER)
158 goto success;
159 goto fail;
160 case 'B':
161 if (need == NEED_HEADER || need == NEED_BODY)
162 goto success;
163 goto fail;
164 default:
165 goto fail;
168 success:
169 if (b == 'N')
170 goto flags;
171 if (fseek(fp, INITSKIP, SEEK_SET) < 0)
172 goto fail;
173 zp = zalloc(fp);
174 if (fseek(mp->mb_otf, 0L, SEEK_END) < 0) {
175 (void)zfree(zp);
176 goto fail;
178 offset = ftell(mp->mb_otf);
179 while (inheader && (n = zread(zp, iob, sizeof iob)) > 0) {
180 size += n;
181 for (i = 0; i < n; i++) {
182 if (iob[i] == '\n') {
183 lines++;
184 if (lastc == '\n')
185 inheader = 0;
187 lastc = iob[i]&0377;
189 fwrite(iob, 1, n, mp->mb_otf);
191 if (n > 0 && need == NEED_BODY) {
192 while ((n = zread(zp, iob, sizeof iob)) > 0) {
193 size += n;
194 for (i = 0; i < n; i++)
195 if (iob[i] == '\n')
196 lines++;
197 fwrite(iob, 1, n, mp->mb_otf);
200 fflush(mp->mb_otf);
201 if (zfree(zp) < 0 || n < 0 || ferror(fp) || ferror(mp->mb_otf))
202 goto fail;
203 m->m_size = size;
204 m->m_lines = lines;
205 m->m_block = mailx_blockof(offset);
206 m->m_offset = mailx_offsetof(offset);
207 flags: if (setflags) {
208 m->m_xsize = xsize;
209 m->m_time = xtime;
210 if (setflags & 2) {
211 m->m_flag = xflag | MNOFROM;
212 if (b != 'B')
213 m->m_flag |= MHIDDEN;
216 if (xlines > 0 && m->m_xlines <= 0)
217 m->m_xlines = xlines;
218 switch (b) {
219 case 'B':
220 m->m_xsize = xsize;
221 if (xflag == MREAD && xlines > 0)
222 m->m_flag |= MFULLYCACHED;
223 if (need == NEED_BODY) {
224 m->m_have |= HAVE_HEADER|HAVE_BODY;
225 if (m->m_lines > 0)
226 m->m_xlines = m->m_lines;
227 break;
229 /*FALLTHRU*/
230 case 'H':
231 m->m_have |= HAVE_HEADER;
232 break;
233 case 'N':
234 break;
236 Fclose(fp);
237 return OKAY;
238 fail:
239 Fclose(fp);
240 return STOP;
243 FL enum okay
244 getcache(struct mailbox *mp, struct message *m, enum needspec need)
246 return getcache1(mp, m, need, 0);
249 FL void
250 putcache(struct mailbox *mp, struct message *m)
252 FILE *ibuf, *obuf;
253 char *name, ob;
254 int c, oflag;
255 long n, cnt, oldoffset, osize, otime, olines = -1;
256 char iob[32768];
257 void *zp;
259 if ((mp->mb_type != MB_IMAP && mp->mb_type != MB_CACHE) ||
260 m->m_uid == 0 || m->m_time == 0 ||
261 (m->m_flag & (MTOUCH|MFULLYCACHED)) == MFULLYCACHED)
262 return;
263 if (m->m_have & HAVE_BODY)
264 c = 'B';
265 else if (m->m_have & HAVE_HEADER)
266 c = 'H';
267 else if (m->m_have == HAVE_NOTHING)
268 c = 'N';
269 else
270 return;
271 if ((oldoffset = ftell(mp->mb_itf)) < 0) /* XXX weird err hdling */
272 oldoffset = 0;
273 if ((obuf = Fopen(name = encuid(mp, m->m_uid), "r+")) == NULL) {
274 if ((obuf = Fopen(name, "w")) == NULL)
275 return;
276 (void)fcntl_lock(fileno(obuf), F_WRLCK); /* XXX err hdl */
277 } else {
278 (void)fcntl_lock(fileno(obuf), F_WRLCK); /* XXX err hdl */
279 if (fscanf(obuf, infofmt, &ob, (unsigned long*)&osize, &oflag,
280 (unsigned long*)&otime, &olines) >= 4 &&
281 ob != '\0' && (ob == 'B' ||
282 (ob == 'H' && c != 'B'))) {
283 if (m->m_xlines <= 0 && olines > 0)
284 m->m_xlines = olines;
285 if ((c != 'N' && (size_t)osize != m->m_xsize) ||
286 oflag != (int)USEBITS(m->m_flag) ||
287 otime != m->m_time ||
288 (m->m_xlines > 0 &&
289 olines != m->m_xlines)) {
290 fflush(obuf);
291 rewind(obuf);
292 fprintf(obuf, infofmt, ob,
293 (unsigned long)m->m_xsize,
294 USEBITS(m->m_flag),
295 (unsigned long)m->m_time,
296 m->m_xlines);
297 putc('\n', obuf);
299 Fclose(obuf);
300 return;
302 fflush(obuf);
303 rewind(obuf);
304 ftruncate(fileno(obuf), 0);
306 if ((ibuf = setinput(mp, m, NEED_UNSPEC)) == NULL) {
307 Fclose(obuf);
308 return;
310 if (c == 'N')
311 goto done;
312 fseek(obuf, INITSKIP, SEEK_SET);
313 zp = zalloc(obuf);
314 cnt = m->m_size;
315 while (cnt > 0) {
316 n = cnt > (long)sizeof iob ? (long)sizeof iob : cnt;
317 cnt -= n;
318 if ((size_t)n != fread(iob, 1, n, ibuf) ||
319 n != (long)zwrite(zp, iob, n)) {
320 unlink(name);
321 zfree(zp);
322 goto out;
325 if (zfree(zp) < 0) {
326 unlink(name);
327 goto out;
329 done: rewind(obuf);
330 fprintf(obuf, infofmt, c, (unsigned long)m->m_xsize,
331 USEBITS(m->m_flag),
332 (unsigned long)m->m_time,
333 m->m_xlines);
334 putc('\n', obuf);
335 if (ferror(obuf)) {
336 unlink(name);
337 goto out;
339 if (c == 'B' && USEBITS(m->m_flag) == MREAD)
340 m->m_flag |= MFULLYCACHED;
341 out: if (Fclose(obuf) != 0) {
342 m->m_flag &= ~MFULLYCACHED;
343 unlink(name);
345 (void)fseek(mp->mb_itf, oldoffset, SEEK_SET);
348 FL void
349 initcache(struct mailbox *mp)
351 char *name, *uvname;
352 FILE *uvfp;
353 unsigned long uv;
354 struct cw cw;
356 if (mp->mb_cache_directory != NULL)
357 free(mp->mb_cache_directory);
358 mp->mb_cache_directory = NULL;
359 if ((name = encname(mp, "", 1, NULL)) == NULL)
360 return;
361 mp->mb_cache_directory = sstrdup(name);
362 if ((uvname = encname(mp, "UIDVALIDITY", 1, NULL)) == NULL)
363 return;
364 if (cwget(&cw) == STOP)
365 return;
366 if ((uvfp = Fopen(uvname, "r+")) == NULL ||
367 (fcntl_lock(fileno(uvfp), F_RDLCK), 0) ||
368 fscanf(uvfp, "%lu", &uv) != 1 ||
369 uv != mp->mb_uidvalidity) {
370 if ((uvfp = clean(mp, &cw)) == NULL)
371 goto out;
372 } else {
373 fflush(uvfp);
374 rewind(uvfp);
376 fcntl_lock(fileno(uvfp), F_WRLCK);
377 fprintf(uvfp, "%lu\n", mp->mb_uidvalidity);
378 if (ferror(uvfp) || Fclose(uvfp) != 0) {
379 unlink(uvname);
380 mp->mb_uidvalidity = 0;
382 out: cwrelse(&cw);
385 FL void
386 purgecache(struct mailbox *mp, struct message *m, long mc)
388 char *name;
389 struct cw cw;
391 if ((name = encname(mp, "", 1, NULL)) == NULL)
392 return;
393 if (cwget(&cw) == STOP)
394 return;
395 purge(mp, m, mc, &cw, name);
396 cwrelse(&cw);
399 static FILE *
400 clean(struct mailbox *mp, struct cw *cw)
402 char *cachedir, *eaccount, *buf;
403 char const *emailbox;
404 int bufsz;
405 DIR *dirp;
406 struct dirent *dp;
407 FILE *fp = NULL;
409 if ((cachedir = ok_vlook(imap_cache)) == NULL ||
410 (cachedir = file_expand(cachedir)) == NULL)
411 return NULL;
412 eaccount = urlxenc(mp->mb_imap_account);
413 if (asccasecmp(mp->mb_imap_mailbox, "INBOX"))
414 emailbox = urlxenc(mp->mb_imap_mailbox);
415 else
416 emailbox = "INBOX";
417 buf = salloc(bufsz = strlen(cachedir) + strlen(eaccount) +
418 strlen(emailbox) + 40);
419 if (makedir(cachedir) != OKAY)
420 return NULL;
421 snprintf(buf, bufsz, "%s/README", cachedir);
422 if ((fp = Fopen(buf, "wx")) != NULL) {
423 fputs(README1, fp);
424 fputs(README2, fp);
425 fputs(README3, fp);
426 fputs(README4, fp);
427 fputs(README5, fp);
428 Fclose(fp);
430 snprintf(buf, bufsz, "%s/%s", cachedir, eaccount);
431 if (makedir(buf) != OKAY)
432 return NULL;
433 snprintf(buf, bufsz, "%s/%s/%s", cachedir, eaccount, emailbox);
434 if (makedir(buf) != OKAY)
435 return NULL;
436 if (chdir(buf) < 0)
437 return NULL;
438 if ((dirp = opendir(".")) == NULL)
439 goto out;
440 while ((dp = readdir(dirp)) != NULL) {
441 if (dp->d_name[0] == '.' &&
442 (dp->d_name[1] == '\0' ||
443 (dp->d_name[1] == '.' &&
444 dp->d_name[2] == '\0')))
445 continue;
446 unlink(dp->d_name);
448 closedir(dirp);
449 fp = Fopen("UIDVALIDITY", "w");
450 out: if (cwret(cw) == STOP) {
451 fputs("Fatal: Cannot change back to current directory.\n",
452 stderr);
453 abort();
455 return fp;
458 static unsigned long *
459 builds(long *contentelem)
461 unsigned long n, *contents = NULL;
462 long contentalloc = 0;
463 char *x;
464 DIR *dirp;
465 struct dirent *dp;
467 *contentelem = 0;
468 if ((dirp = opendir(".")) == NULL)
469 return NULL;
470 while ((dp = readdir(dirp)) != NULL) {
471 if (dp->d_name[0] == '.' &&
472 (dp->d_name[1] == '\0' ||
473 (dp->d_name[1] == '.' &&
474 dp->d_name[2] == '\0')))
475 continue;
476 n = strtoul(dp->d_name, &x, 10);
477 if (*x != '\0')
478 continue;
479 if (*contentelem >= contentalloc - 1)
480 contents = srealloc(contents,
481 (contentalloc += 200) * sizeof *contents);
482 contents[(*contentelem)++] = n;
484 closedir(dirp);
485 if (*contentelem > 0) {
486 contents[*contentelem] = 0;
487 qsort(contents, *contentelem, sizeof *contents, longlt);
489 return contents;
492 static void
493 purge(struct mailbox *mp, struct message *m, long mc, struct cw *cw,
494 const char *name)
496 unsigned long *contents;
497 long i, j, contentelem;
498 (void)mp;
500 if (chdir(name) < 0)
501 return;
502 contents = builds(&contentelem);
503 if (contents) {
504 i = j = 0;
505 while (j < contentelem) {
506 if (i < mc && m[i].m_uid == contents[j]) {
507 i++;
508 j++;
509 } else if (i < mc && m[i].m_uid < contents[j])
510 i++;
511 else
512 remve(contents[j++]);
515 if (cwret(cw) == STOP) {
516 fputs("Fatal: Cannot change back to current directory.\n",
517 stderr);
518 abort();
520 free(contents);
523 static int
524 longlt(const void *a, const void *b)
526 return *(const long*)a - *(const long*)b;
529 static void
530 remve(unsigned long n)
532 char buf[30];
534 snprintf(buf, sizeof buf, "%lu", n);
535 unlink(buf);
538 FL void
539 delcache(struct mailbox *mp, struct message *m)
541 char *fn;
543 fn = encuid(mp, m->m_uid);
544 if (fn && unlink(fn) == 0)
545 m->m_flag |= MUNLINKED;
548 FL enum okay
549 cache_setptr(int transparent)
551 int i;
552 struct cw cw;
553 char *name;
554 unsigned long *contents;
555 long contentelem;
556 enum okay ok = STOP;
557 struct message *omessage = NULL;
558 int omsgCount = 0;
560 if (transparent) {
561 omessage = message;
562 omsgCount = msgCount;
564 free(mb.mb_cache_directory);
565 mb.mb_cache_directory = NULL;
566 if ((name = encname(&mb, "", 1, NULL)) == NULL)
567 return STOP;
568 mb.mb_cache_directory = sstrdup(name);
569 if (cwget(&cw) == STOP)
570 return STOP;
571 if (chdir(name) < 0)
572 return STOP;
573 contents = builds(&contentelem);
574 msgCount = contentelem;
575 message = scalloc(msgCount + 1, sizeof *message);
576 if (cwret(&cw) == STOP) {
577 fputs("Fatal: Cannot change back to current directory.\n",
578 stderr);
579 abort();
581 cwrelse(&cw);
582 for (i = 0; i < msgCount; i++) {
583 message[i].m_uid = contents[i];
584 getcache1(&mb, &message[i], NEED_UNSPEC, 3);
586 ok = OKAY;
587 if (ok == OKAY) {
588 mb.mb_type = MB_CACHE;
589 mb.mb_perm = (options & OPT_R_FLAG) ? 0 : MB_DELE;
590 if (transparent)
591 transflags(omessage, omsgCount, 1);
592 else
593 setdot(message);
595 return ok;
598 FL enum okay
599 cache_list(struct mailbox *mp, const char *base, int strip, FILE *fp)
601 char *name, *cachedir, *eaccount;
602 DIR *dirp;
603 struct dirent *dp;
604 const char *cp, *bp, *sp;
605 int namesz;
607 if ((cachedir = ok_vlook(imap_cache)) == NULL ||
608 (cachedir = file_expand(cachedir)) == NULL)
609 return STOP;
610 eaccount = urlxenc(mp->mb_imap_account);
611 name = salloc(namesz = strlen(cachedir) + strlen(eaccount) + 2);
612 snprintf(name, namesz, "%s/%s", cachedir, eaccount);
613 if ((dirp = opendir(name)) == NULL)
614 return STOP;
615 while ((dp = readdir(dirp)) != NULL) {
616 if (dp->d_name[0] == '.')
617 continue;
618 cp = sp = urlxdec(dp->d_name);
619 for (bp = base; *bp && *bp == *sp; bp++)
620 sp++;
621 if (*bp)
622 continue;
623 cp = strip ? sp : cp;
624 fprintf(fp, "%s\n", *cp ? cp : "INBOX");
626 closedir(dirp);
627 return OKAY;
630 FL enum okay
631 cache_remove(const char *name)
633 struct stat st;
634 DIR *dirp;
635 struct dirent *dp;
636 char *path;
637 int pathsize, pathend, n;
638 char *dir;
640 if ((dir = encname(&mb, "", 0, imap_fileof(name))) == NULL)
641 return OKAY;
642 pathend = strlen(dir);
643 path = smalloc(pathsize = pathend + 30);
644 memcpy(path, dir, pathend);
645 path[pathend++] = '/';
646 path[pathend] = '\0';
647 if ((dirp = opendir(path)) == NULL) {
648 free(path);
649 return OKAY;
651 while ((dp = readdir(dirp)) != NULL) {
652 if (dp->d_name[0] == '.' &&
653 (dp->d_name[1] == '\0' ||
654 (dp->d_name[1] == '.' &&
655 dp->d_name[2] == '\0')))
656 continue;
657 n = strlen(dp->d_name) + 1;
658 if (pathend + n > pathsize)
659 path = srealloc(path, pathsize = pathend + n + 30);
660 memcpy(path + pathend, dp->d_name, n);
661 if (stat(path, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)
662 continue;
663 if (unlink(path) < 0) {
664 perror(path);
665 closedir(dirp);
666 free(path);
667 return STOP;
670 closedir(dirp);
671 path[pathend] = '\0';
672 rmdir(path); /* no error on failure, might contain submailboxes */
673 free(path);
674 return OKAY;
677 FL enum okay
678 cache_rename(const char *old, const char *new)
680 char *olddir, *newdir;
682 if ((olddir = encname(&mb, "", 0, imap_fileof(old))) == NULL ||
683 (newdir = encname(&mb, "",0, imap_fileof(new))) == NULL)
684 return OKAY;
685 if (rename(olddir, newdir) < 0) {
686 perror(olddir);
687 return STOP;
689 return OKAY;
692 FL unsigned long
693 cached_uidvalidity(struct mailbox *mp)
695 FILE *uvfp;
696 char *uvname;
697 unsigned long uv;
699 if ((uvname = encname(mp, "UIDVALIDITY", 1, NULL)) == NULL)
700 return 0;
701 if ((uvfp = Fopen(uvname, "r")) == NULL ||
702 (fcntl_lock(fileno(uvfp), F_RDLCK), 0) ||
703 fscanf(uvfp, "%lu", &uv) != 1)
704 uv = 0;
705 if (uvfp != NULL)
706 Fclose(uvfp);
707 return uv;
710 static FILE *
711 cache_queue1(struct mailbox *mp, char const *mode, char **xname)
713 char *name;
714 FILE *fp;
716 if ((name = encname(mp, "QUEUE", 0, NULL)) == NULL)
717 return NULL;
718 if ((fp = Fopen(name, mode)) != NULL)
719 fcntl_lock(fileno(fp), F_WRLCK);
720 if (xname)
721 *xname = name;
722 return fp;
725 FL FILE *
726 cache_queue(struct mailbox *mp)
728 FILE *fp;
730 fp = cache_queue1(mp, "a", NULL);
731 if (fp == NULL)
732 fputs("Cannot queue IMAP command. Retry when online.\n",
733 stderr);
734 return fp;
737 FL enum okay
738 cache_dequeue(struct mailbox *mp)
740 int bufsz;
741 char *cachedir, *eaccount, *buf, *oldbox;
742 DIR *dirp;
743 struct dirent *dp;
745 if ((cachedir = ok_vlook(imap_cache)) == NULL ||
746 (cachedir = file_expand(cachedir)) == NULL)
747 return OKAY;
748 eaccount = urlxenc(mp->mb_imap_account);
749 buf = salloc(bufsz = strlen(cachedir) + strlen(eaccount) + 2);
750 snprintf(buf, bufsz, "%s/%s", cachedir, eaccount);
751 if ((dirp = opendir(buf)) == NULL)
752 return OKAY;
753 oldbox = mp->mb_imap_mailbox;
754 while ((dp = readdir(dirp)) != NULL) {
755 if (dp->d_name[0] == '.')
756 continue;
757 mp->mb_imap_mailbox = urlxdec(dp->d_name);
758 dequeue1(mp);
760 closedir(dirp);
761 mp->mb_imap_mailbox = oldbox;
762 return OKAY;
765 static enum okay
766 dequeue1(struct mailbox *mp)
768 FILE *fp = NULL, *uvfp = NULL;
769 char *qname, *uvname;
770 unsigned long uv;
771 off_t is_size;
772 int is_count;
774 fp = cache_queue1(mp, "r+", &qname);
775 if (fp != NULL && fsize(fp) > 0) {
776 if (imap_select(mp, &is_size, &is_count,
777 mp->mb_imap_mailbox) != OKAY) {
778 fprintf(stderr, "Cannot select \"%s\" for dequeuing.\n",
779 mp->mb_imap_mailbox);
780 goto save;
782 if ((uvname = encname(mp, "UIDVALIDITY", 0, NULL)) == NULL ||
783 (uvfp = Fopen(uvname, "r")) == NULL ||
784 (fcntl_lock(fileno(uvfp), F_RDLCK), 0) ||
785 fscanf(uvfp, "%lu", &uv) != 1 ||
786 uv != mp->mb_uidvalidity) {
787 fprintf(stderr,
788 "Unique identifiers for \"%s\" are out of date. "
789 "Cannot commit IMAP commands.\n",
790 mp->mb_imap_mailbox);
791 save: fputs("Saving IMAP commands to dead.letter\n", stderr);
792 savedeadletter(fp, 0);
793 ftruncate(fileno(fp), 0);
794 Fclose(fp);
795 if (uvfp)
796 Fclose(uvfp);
797 return STOP;
799 Fclose(uvfp);
800 printf("Committing IMAP commands for \"%s\"\n",
801 mp->mb_imap_mailbox);
802 imap_dequeue(mp, fp);
804 if (fp) {
805 Fclose(fp);
806 unlink(qname);
808 return OKAY;
810 #endif /* HAVE_IMAP */