aerc

Working clone of aerc-mail.org
git clone git://git.danielmoch.com/aerc.git
Log | Files | Refs | README | LICENSE

worker.go (4529B)


      1 package imap
      2 
      3 import (
      4 	"crypto/tls"
      5 	"fmt"
      6 	"net/url"
      7 	"strings"
      8 
      9 	"github.com/emersion/go-imap"
     10 	"github.com/emersion/go-imap-idle"
     11 	"github.com/emersion/go-imap/client"
     12 
     13 	"git.sr.ht/~sircmpwn/aerc/worker/types"
     14 )
     15 
     16 var errUnsupported = fmt.Errorf("unsupported command")
     17 
     18 type imapClient struct {
     19 	*client.Client
     20 	idle *idle.IdleClient
     21 }
     22 
     23 type IMAPWorker struct {
     24 	config struct {
     25 		scheme   string
     26 		insecure bool
     27 		addr     string
     28 		user     *url.Userinfo
     29 	}
     30 
     31 	client   *imapClient
     32 	idleStop chan struct{}
     33 	idleDone chan error
     34 	selected imap.MailboxStatus
     35 	updates  chan client.Update
     36 	worker   *types.Worker
     37 	// Map of sequence numbers to UIDs, index 0 is seq number 1
     38 	seqMap []uint32
     39 }
     40 
     41 func NewIMAPWorker(worker *types.Worker) *IMAPWorker {
     42 	return &IMAPWorker{
     43 		idleDone: make(chan error),
     44 		updates:  make(chan client.Update, 50),
     45 		worker:   worker,
     46 	}
     47 }
     48 
     49 func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
     50 	if w.idleStop != nil {
     51 		close(w.idleStop)
     52 		if err := <-w.idleDone; err != nil {
     53 			w.worker.PostMessage(&types.Error{Error: err}, nil)
     54 		}
     55 	}
     56 
     57 	switch msg := msg.(type) {
     58 	case *types.Unsupported:
     59 		// No-op
     60 	case *types.Configure:
     61 		u, err := url.Parse(msg.Config.Source)
     62 		if err != nil {
     63 			return err
     64 		}
     65 
     66 		w.config.scheme = u.Scheme
     67 		if strings.HasSuffix(w.config.scheme, "+insecure") {
     68 			w.config.scheme = strings.TrimSuffix(w.config.scheme, "+insecure")
     69 			w.config.insecure = true
     70 		}
     71 
     72 		w.config.addr = u.Host
     73 		if !strings.ContainsRune(w.config.addr, ':') {
     74 			w.config.addr += ":" + w.config.scheme
     75 		}
     76 
     77 		w.config.user = u.User
     78 	case *types.Connect:
     79 		var (
     80 			c   *client.Client
     81 			err error
     82 		)
     83 		switch w.config.scheme {
     84 		case "imap":
     85 			c, err = client.Dial(w.config.addr)
     86 			if err != nil {
     87 				return err
     88 			}
     89 
     90 			if !w.config.insecure {
     91 				if err := c.StartTLS(&tls.Config{}); err != nil {
     92 					return err
     93 				}
     94 			}
     95 		case "imaps":
     96 			c, err = client.DialTLS(w.config.addr, &tls.Config{})
     97 			if err != nil {
     98 				return err
     99 			}
    100 		default:
    101 			return fmt.Errorf("Unknown IMAP scheme %s", w.config.scheme)
    102 		}
    103 
    104 		if w.config.user != nil {
    105 			username := w.config.user.Username()
    106 			password, hasPassword := w.config.user.Password()
    107 			if !hasPassword {
    108 				// TODO: ask password
    109 			}
    110 			if err := c.Login(username, password); err != nil {
    111 				return err
    112 			}
    113 		}
    114 
    115 		c.SetDebug(w.worker.Logger.Writer())
    116 
    117 		if _, err := c.Select(imap.InboxName, false); err != nil {
    118 			return err
    119 		}
    120 
    121 		c.Updates = w.updates
    122 		w.client = &imapClient{c, idle.NewClient(c)}
    123 		w.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
    124 	case *types.ListDirectories:
    125 		w.handleListDirectories(msg)
    126 	case *types.OpenDirectory:
    127 		w.handleOpenDirectory(msg)
    128 	case *types.FetchDirectoryContents:
    129 		w.handleFetchDirectoryContents(msg)
    130 	case *types.FetchMessageHeaders:
    131 		w.handleFetchMessageHeaders(msg)
    132 	case *types.FetchMessageBodyPart:
    133 		w.handleFetchMessageBodyPart(msg)
    134 	case *types.FetchFullMessages:
    135 		w.handleFetchFullMessages(msg)
    136 	case *types.DeleteMessages:
    137 		w.handleDeleteMessages(msg)
    138 	case *types.CopyMessages:
    139 		w.handleCopyMessages(msg)
    140 	case *types.AppendMessage:
    141 		w.handleAppendMessage(msg)
    142 	default:
    143 		return errUnsupported
    144 	}
    145 
    146 	if w.idleStop != nil {
    147 		w.idleStop = make(chan struct{})
    148 		go func() {
    149 			w.idleDone <- w.client.idle.IdleWithFallback(w.idleStop, 0)
    150 		}()
    151 	}
    152 	return nil
    153 }
    154 
    155 func (w *IMAPWorker) handleImapUpdate(update client.Update) {
    156 	w.worker.Logger.Printf("(= %T", update)
    157 	switch update := update.(type) {
    158 	case *client.MailboxUpdate:
    159 		status := update.Mailbox
    160 		if w.selected.Name == status.Name {
    161 			w.selected = *status
    162 		}
    163 		w.worker.PostMessage(&types.DirectoryInfo{
    164 			Flags:    status.Flags,
    165 			Name:     status.Name,
    166 			ReadOnly: status.ReadOnly,
    167 
    168 			Exists: int(status.Messages),
    169 			Recent: int(status.Recent),
    170 			Unseen: int(status.Unseen),
    171 		}, nil)
    172 	case *client.ExpungeUpdate:
    173 		i := update.SeqNum - 1
    174 		uid := w.seqMap[i]
    175 		w.seqMap = append(w.seqMap[:i], w.seqMap[i+1:]...)
    176 		w.worker.PostMessage(&types.MessagesDeleted{
    177 			Uids: []uint32{uid},
    178 		}, nil)
    179 	}
    180 }
    181 
    182 func (w *IMAPWorker) Run() {
    183 	for {
    184 		select {
    185 		case msg := <-w.worker.Actions:
    186 			msg = w.worker.ProcessAction(msg)
    187 			if err := w.handleMessage(msg); err == errUnsupported {
    188 				w.worker.PostMessage(&types.Unsupported{
    189 					Message: types.RespondTo(msg),
    190 				}, nil)
    191 			} else if err != nil {
    192 				w.worker.PostMessage(&types.Error{
    193 					Message: types.RespondTo(msg),
    194 					Error:   err,
    195 				}, nil)
    196 			}
    197 		case update := <-w.updates:
    198 			w.handleImapUpdate(update)
    199 		}
    200 	}
    201 }