aboutsummaryrefslogtreecommitdiff
path: root/mu4web/maildir.py
blob: 8b01f6d9d1772522d0a88864fe2dbbb0a3ee9c9f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
from dataclasses import dataclass
import os.path

from html_render import HTML
from util import find

from urllib.parse import urlencode

from typing import (
    Union
)

try:
    from natsort import natsorted
except ModuleNotFoundError:
    natsorted = sorted


@dataclass
class MaildirEntry:
    """A single maildir, used by find_maildirs."""
    name: str


@dataclass
class MaildirGroup:
    """A group of maildir, which isn't a maildir in itself."""
    name: str
    children: list[Union[MaildirEntry, 'MaildirGroup']]


def _build_tree(items: list[list[str]]) -> MaildirGroup:
    groups: dict[str, list[list[str]]] = {}
    direct: list[MaildirEntry] = []
    for key, *rest in items:
        if rest:
            groups.setdefault(key, []).append(rest)
        else:
            direct.append(MaildirEntry(key))

    node = MaildirGroup('root', [])
    for key, values in groups.items():
        next = _build_tree(values)
        next.name = key
        node.children.append(next)
    node.children.extend(direct)
    return node


def find_maildirs(basedir) -> MaildirGroup:
    """
    Find all maildirs located under basedir.

    A maildir is defined as any directory which contains a `cur`
    directory.

    Returns a MaildirGroup of the root, whose children will be all the
    maildirs under basedir. Note that if a directory is both a maildir
    and a group of maildirs then it will have two separate entries.
    """
    basedir = basedir.rstrip('/')
    files = find(basedir, type='d', name='cur')
    # + 1 removes leading slash
    # - 4 removes '/cur'
    dirs = [entry[len(basedir) + 1:-4].decode('UTF-8').split(os.path.sep)
            for entry in files]

    return _build_tree(dirs)


def serialize_maildir(maildir: MaildirGroup, path: list[str] = []) -> HTML:
    """Build a (recursive) list from a maildir node."""
    entries: list[HTML] = []
    for node in natsorted(maildir.children, key=lambda n: n.name):
        if isinstance(node, MaildirEntry):
            parts = '/'.join(path + [node.name])
            url = 'search?' + urlencode({'q': f'maildir:"/{parts}"'})
            entry = ('li', ('a', {'href': url},
                            node.name or ('i', 'root')))
        else:
            entry = ('li',
                     ('details',
                      ('summary', node.name),
                      serialize_maildir(node, path + [node.name])))
        entries.append(entry)

    return ('ul', *entries)