update for upstream 52eca667bbd1cf5a929d3f2352c3c1ee8e91625d
[libmaildir.git] / maildir.myr
blob88915e8267aeaaa3e4bc54b9050436babea4a373
1 use fileutil
2 use std
3 use sys
5 pkg maildir =
6         /* http://cr.yp.to/proto/maildir.html */
7         type info = struct
8                 draft : bool
9                 flagged : bool
10                 passed : bool
11                 replied : bool
12                 seen : bool
13                 trashed : bool
14         ;;
16         /* Sanely change the info of a message */
17         const changeinfo : (msg : byte[:], i : info -> std.result(byte[:], byte[:]))
19         /* Unlink old messages in tmp/, messages marked 'T' */
20         const cleanfs : (dirs : byte[:][:] -> std.result(void, byte[:]))
22         /* Get the info flags from the info field of a filename */
23         const info : (msg : byte[:] -> info)
25         /* List maildirs (rel paths) under dir. Result must be deep-free'd */
26         const maildirs : (dir : byte[:] -> std.result(byte[:][:], byte[:]))
28         /* List messages (rel paths) under dir. Result must be deep-free'd */
29         const messages : (dir : byte[:] -> std.result(byte[:][:], byte[:]))
31         /* Sanely move a message around */
32         const movemessage : (msg : byte[:], dir : byte[:] -> std.result(void, byte[:]))
35 const infofmt = {s : std.strbuf#, ap : std.valist#, opts : (byte[:], byte[:])[:] -> void
36         var i : info = std.vanext(ap)
38         if i.draft
39                 std.sbfmt(s, "D")
40         ;;
42         if i.flagged
43                 std.sbfmt(s, "F")
44         ;;
46         if i.passed
47                 std.sbfmt(s, "P")
48         ;;
50         if i.replied
51                 std.sbfmt(s, "R")
52         ;;
54         if i.seen
55                 std.sbfmt(s, "S")
56         ;;
58         if i.trashed
59                 std.sbfmt(s, "T")
60         ;;
63 const __init__ = {
64         var i : info = [.passed = true]
65         std.fmtinstall(std.typeof(i), infofmt)
68 const ismaildir = {dir : byte[:] -> bool
69         var s : std.strbuf# = std.mksb()
70         var m : bool = true
71         std.sbfmt(s, "{}", dir)
73         std.sbfmt(s, "/cur")
74         m = m && std.fisdir(std.sbpeek(s))
76         std.sbtrim(s, dir.len)
77         std.sbfmt(s, "/new")
78         m = m && std.fisdir(std.sbpeek(s))
80         std.sbtrim(s, dir.len)
81         std.sbfmt(s, "/tmp")
82         m = m && std.fisdir(std.sbpeek(s))
84         std.sbfree(s)
85         -> m
87 /* Sanely change the info of a message */
88 const changeinfo = {msg : byte[:], i : info
89         var j : std.size = 0
90         var base : byte[:] = [][:]
91         var newname : byte[:] = [][:]
92         var s : std.strbuf# = std.mksb()
93         var err : std.strbuf# = std.mksb()
95         for j = msg.len - 1; j >= 0; --j
96                 if msg[j] == (':' : byte)
97                         break
98                 ;;
100                 if msg[j] == ('/' : byte)
101                         j = 0
102                         break
103                 ;;
104         ;;
106         if j <= 0
107                 j = msg.len
108         ;;
110         base = msg[0:j]
111         std.sbfmt(s, "{}:2,{}", base, i)
112         newname = std.sbpeek(s)
114         match sys.link(sys.cstring(msg), sys.cstring(newname))
115         | 0:
116         | e:
117                 std.sbfmt(err, "link(\"{}\", \"{}\"): {}", msg, newname, e)
118                 goto done
119         ;;
121         match sys.unlink(msg)
122         | 0:
123         | e:
124                 std.sbfmt(err, "unlink(\"{}\"): {}", msg, e)
125                 goto done
126         ;;
128 :done
129         match err.len
130         | 0:
131                 std.sbfree(err)
132                 -> `std.Ok std.sbfin(s)
133         | _:
134                 std.sbfree(s)
135                 -> `std.Err std.sbfin(err)
136         ;;
140 /* Cleanup messages older than 36 hrs in tmp,  */
141 const cleanfs = { dirs : byte[:][:]
142         var err : std.strbuf# = std.mksb()
143         var thisdir : byte[:] = [][:]
144         var thisfile : byte[:] = [][:]
145         var tokill : byte[:][:] = std.slalloc(0)
146         var cutoff : std.time = std.now() - (36 * 60 * 60 * std.Sec)
148         for p : dirs
149                 if !ismaildir(p)
150                         continue
151                 ;;
153                 thisdir = std.pathcat(p, "tmp")
155                 match std.diropen(thisdir)
156                 | `std.Err e:
157                         std.sbfmt(err, "cannot open {}: {}", thisdir, e)
158                         goto done
159                 | `std.Ok d:
160                         while true
161                                 match std.dirread(d)
162                                 | `std.None:
163                                         std.dirclose(d)
164                                         break
165                                 | `std.Some f:
166                                         if std.eq(f, ".") || std.eq(f, "..")
167                                                 std.slfree(f)
168                                                 continue
169                                         ;;
171                                         thisfile = std.pathcat(thisdir, f)
172                                         std.slfree(f)
173                                         match std.fmtime(thisfile)
174                                         | `std.Ok t:
175                                                 if t * std.Msec < cutoff
176                                                         std.slpush(&tokill, thisfile)
177                                                         thisfile = [][:]
178                                                 ;;
179                                         | `std.Err e:
180                                                 std.sbfmt(err, "cannot stat {}: {}", thisfile, e)
181                                                 goto done
182                                         ;;
183                                 ;;
184                         ;;
185                 ;;
187                 std.slfree(thisdir)
188                 thisdir = std.pathcat(p, "cur")
190                 match std.diropen(thisdir)
191                 | `std.Err e:
192                         std.sbfmt(err, "cannot open {}: {}", thisdir, e)
193                         goto done
194                 | `std.Ok d:
195                         while true
196                                 match std.dirread(d)
197                                 | `std.None:
198                                         std.dirclose(d)
199                                         break
200                                 | `std.Some f:
201                                         std.slfree(thisfile)
202                                         thisfile = std.pathcat(thisdir, f)
203                                         std.slfree(f)
204                                         if info(thisfile).trashed
205                                                 std.slpush(&tokill, thisfile)
206                                                 thisfile = [][:]
207                                         ;;
208                                 ;;
209                         ;;
210                 ;;
212                 std.slfree(thisdir)
213                 thisdir = [][:]
214         ;;
216         for k : tokill
217                 sys.unlink(k)
218         ;;
220 :done
221         std.slfree(thisdir)
222         std.slfree(thisfile)
223         for k : tokill
224                 std.slfree(k)
225         ;;
226         std.slfree(tokill)
228         match err.len
229         | 0:
230                 std.sbfree(err)
231                 -> `std.Ok void
232         | _: -> `std.Err std.sbfin(err)
233         ;;
236 /* Parse a message filename for info flags */
237 const info = {msg : byte[:]
238         var j : std.size = 0
239         var i : info = [
240                 .passed = false,
241                 .replied = false,
242                 .seen = false,
243                 .trashed = false,
244                 .draft = false,
245                 .flagged = false
246         ]
248         for j = msg.len - 1; j >= 0; --j
249                 if (msg[j] : char) == ':'
250                         break
251                 ;;
252         ;;
254         if j >= msg.len - 3 || j <= 0
255                 -> i
256         ;;
258         if !std.sleq(msg[j:j+3], ":2,")
259                 -> i
260         ;;
262         for c : msg[j+3:]
263                 match (c : char)
264                 | 'P': i.passed = true
265                 | 'R': i.replied = true
266                 | 'S': i.seen = true
267                 | 'T': i.trashed = true
268                 | 'D': i.draft = true
269                 | 'F': i.flagged = true
270                 | _:
271                 ;;
272         ;;
274         -> i
277 /* Returns immediate subdirs that meet the maildir(5) format */
278 const maildirs = {dir : byte[:] -> std.result(byte[:][:], byte[:])
279         var m : byte[:][:] = std.slalloc(0)
280         var p : byte[:] = [][:]
281         var err : std.strbuf# = std.mksb()
283         match std.diropen(dir)
284         | `std.Err e:
285                 std.sbfmt(err, "cannot open {}: {}", dir, e)
286                 goto done
287         | `std.Ok d:
288                 while true
289                         match std.dirread(d)
290                         | `std.None:
291                                 std.dirclose(d)
292                                 goto done
293                         | `std.Some e:
294                                 p = std.pathcat(dir, e)
295                                 std.slfree(e)
296                                 if ismaildir(p)
297                                         std.slpush(&m, p)
298                                 else
299                                         std.slfree(p)
300                                 ;;
301                         ;;
302                 ;;
303         ;;
305 :done
306         match err.len
307         | 0:
308                 std.sbfree(err)
309                 -> `std.Ok std.sort(m, std.strcmp)
310         | _:
311                 for mm : m
312                         std.slfree(mm)
313                 ;;
315                 std.slfree(m)
316                 -> `std.Err std.sbfin(err)
317         ;;
320 /* Returns things that might be messages */
321 const messages = {dir : byte[:] -> std.result(byte[:][:], byte[:])
322         var m : byte[:][:] = std.slalloc(0)
323         var err : std.strbuf# = std.mksb()
324         var thisdir : byte[:] = [][:]
326         if !ismaildir(dir)
327                 std.sbfmt(err, "{} is not a maildir", dir)
328                 goto done
329         ;;
331         for subdir : [ "cur", "new" ][:]
332                 std.slfree(thisdir)
333                 thisdir = std.pathcat(dir, subdir)
335                 match std.diropen(thisdir)
336                 | `std.Err e:
337                         std.sbfmt(err, "cannot open {}: {}", thisdir, e)
338                         goto done
339                 | `std.Ok d:
340                         while true
341                                 match std.dirread(d)
342                                 | `std.None:
343                                         break
344                                 | `std.Some e:
345                                         if e.len > 0 && e[0] == ('.' : byte)
346                                                 std.slfree(e)
347                                                 continue
348                                         ;;
350                                         std.slpush(&m, std.pathcat(thisdir, e))
351                                         std.slfree(e)
352                                 ;;
353                         ;;
355                         std.dirclose(d)
356                 ;;
357         ;;
359 :done
360         std.slfree(thisdir)
362         match err.len
363         | 0:
364                 std.sbfree(err)
365                 -> `std.Ok std.sort(m, std.strcmp)
366         | _:
367                 for mm : m
368                         std.slfree(mm)
369                 ;;
371                 std.slfree(m)
372                 -> `std.Err std.sbfin(err)
373         ;;
376 const movemessage = {msg : byte[:], dir : byte[:]
377         var err : std.strbuf# = std.mksb()
378         var basename : byte[:] = msg
379         var newname : byte[:] = [][:]
380         var j : std.size = 0
382         if !ismaildir(dir)
383                 std.sbfmt(err, "{} is not a maildir", dir)
384                 goto done
385         ;;
387         for j = msg.len - 1; j >= 0; --j
388                 if msg[j] == ('/' : byte)
389                         basename = msg[j+1:]
390                         break
391                 ;;
392         ;;
394         newname = std.pathjoin([dir, "cur", basename][:])
396         /* TODO: once the syscalls get redone, this can be simpler */
397         match sys.link(sys.cstring(msg), sys.cstring(newname))
398         | 0:
399         | e:
400                 std.sbfmt(err, "link(\"{}\", \"{}\"): {}", msg, newname, e)
401                 goto done
402         ;;
404         match sys.unlink(msg)
405         | 0:
406         | e:
407                 std.sbfmt(err, "unlink(\"{}\"): {}", msg, e)
408                 goto done
409         ;;
411 :done
412         std.slfree(newname)
413         match err.len
414         | 0:
415                 std.sbfree(err)
416                 -> `std.Ok void
417         | _: -> `std.Err std.sbfin(err)
418         ;;