9 "github.com/mjl-/bstore"
10 "github.com/mjl-/mox/store"
13// LIST command, for listing mailboxes with various attributes, including about subscriptions and children.
14// We don't have flags Marked, Unmarked, NoSelect and NoInferiors and we don't have REMOTE mailboxes.
16// State: Authenticated and selected.
17func (c *conn) cmdList(tag, cmd string, p *parser) {
24 var listSubscribed bool
25 var listRecursive bool
29 selectOptions := map[string]bool{}
32 if len(selectOptions) > 0 {
36 W := strings.ToUpper(w)
39 case "RECURSIVEMATCH":
46 xsyntaxErrorf("bad list selection option %q", w)
49 selectOptions[W] = true
51 if listRecursive && nbase == 0 {
53 xsyntaxErrorf("cannot have RECURSIVEMATCH selection option without other (base) selection option")
57 reference := p.xmailbox()
59 patterns, isList := p.xmboxOrPat()
60 isExtended = isExtended || isList
61 var retSubscribed, retChildren bool
62 var retStatusAttrs []string
63 if p.take(" RETURN (") {
73 W := strings.ToUpper(w)
82 // We always include special-use mailbox flags. Mac OS X Mail 16.0 (sept 2023) does
88 retStatusAttrs = []string{p.xstatusAtt()}
90 retStatusAttrs = append(retStatusAttrs, p.xstatusAtt())
95 xsyntaxErrorf("bad list return option %q", w)
101 if !isExtended && reference == "" && patterns[0] == "" {
103 c.bwritelinef(`* LIST () "/" ""`)
110 n := make([]string, 0, len(patterns))
111 for _, p := range patterns {
118 re := xmailboxPatternMatcher(reference, patterns)
119 var responseLines []string
121 c.account.WithRLock(func() {
122 c.xdbread(func(tx *bstore.Tx) {
124 mailbox *store.Mailbox
127 names := map[string]info{}
128 hasSubscribedChild := map[string]bool{}
129 hasChild := map[string]bool{}
130 var nameList []string
132 q := bstore.QueryTx[store.Mailbox](tx)
133 err := q.ForEach(func(mb store.Mailbox) error {
134 names[mb.Name] = info{mailbox: &mb}
135 nameList = append(nameList, mb.Name)
136 for p := path.Dir(mb.Name); p != "."; p = path.Dir(p) {
141 xcheckf(err, "listing mailboxes")
143 qs := bstore.QueryTx[store.Subscription](tx)
144 err = qs.ForEach(func(sub store.Subscription) error {
145 info, ok := names[sub.Name]
146 info.subscribed = true
147 names[sub.Name] = info
149 nameList = append(nameList, sub.Name)
151 for p := path.Dir(sub.Name); p != "."; p = path.Dir(p) {
152 hasSubscribedChild[p] = true
156 xcheckf(err, "listing subscriptions")
158 sort.Strings(nameList) // For predictable order in tests.
160 for _, name := range nameList {
161 if !re.MatchString(name) {
167 var extended listspace
168 if listRecursive && hasSubscribedChild[name] {
169 extended = listspace{bare("CHILDINFO"), listspace{dquote("SUBSCRIBED")}}
171 if listSubscribed && info.subscribed {
172 flags = append(flags, bare(`\Subscribed`))
173 if info.mailbox == nil {
174 flags = append(flags, bare(`\NonExistent`))
177 if (info.mailbox == nil || listSubscribed) && flags == nil && extended == nil {
188 flags = append(flags, bare(f))
190 if !listSubscribed && retSubscribed && info.subscribed {
191 flags = append(flags, bare(`\Subscribed`))
193 if info.mailbox != nil {
194 if info.mailbox.Archive {
195 flags = append(flags, bare(`\Archive`))
197 if info.mailbox.Draft {
198 flags = append(flags, bare(`\Drafts`))
200 if info.mailbox.Junk {
201 flags = append(flags, bare(`\Junk`))
203 if info.mailbox.Sent {
204 flags = append(flags, bare(`\Sent`))
206 if info.mailbox.Trash {
207 flags = append(flags, bare(`\Trash`))
213 extStr = " " + extended.pack(c)
215 line := fmt.Sprintf(`* LIST %s "/" %s%s`, flags.pack(c), astring(c.encodeMailbox(name)).pack(c), extStr)
216 responseLines = append(responseLines, line)
218 if retStatusAttrs != nil && info.mailbox != nil {
219 responseLines = append(responseLines, c.xstatusLine(tx, *info.mailbox, retStatusAttrs))
225 for _, line := range responseLines {
226 c.bwritelinef("%s", line)