~rjarry/aerc

d57aa9e582f1644e2ee260c5c18fbf974dcc03ee — Tim Culverhouse 19 days ago b17e8ae
lib: return a new Header from LimitHeaders

The LimitHeaders function is used to optionally reduce memory usage of
aerc by only keeping certain headers in memory for the message list. The
function properly deletes header keys and values from the underlying
object, however the underlying data structure has a map and a slice -
which do not get resized after deletion: resulting in no actual memory
savings.

Create a new header and add only the headers we want to it. Return this
value and use in the MessageInfo struct.

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
4 files changed, 16 insertions(+), 18 deletions(-)

M worker/lib/parse.go
M worker/maildir/worker.go
M worker/mbox/worker.go
M worker/notmuch/worker.go
M worker/lib/parse.go => worker/lib/parse.go +10 -12
@@ 303,7 303,7 @@ func MessageInfo(raw RawMessage) (*models.MessageInfo, error) {
		Flags:         flags,
		Labels:        labels,
		InternalDate:  recDate,
		RFC822Headers: &mail.Header{Header: msg.Header},
		RFC822Headers: h,
		Size:          0,
		Uid:           raw.UID(),
		Error:         parseErr,


@@ 312,26 312,24 @@ func MessageInfo(raw RawMessage) (*models.MessageInfo, error) {

// LimitHeaders returns a new Header with the specified headers included or
// excluded
func LimitHeaders(hdr *mail.Header, fields []string, exclude bool) {
func LimitHeaders(hdr *mail.Header, fields []string, exclude bool) *mail.Header {
	fieldMap := make(map[string]struct{}, len(fields))
	for _, f := range fields {
		fieldMap[strings.ToLower(f)] = struct{}{}
	}
	nh := &mail.Header{}
	curFields := hdr.Fields()
	for curFields.Next() {
		key := strings.ToLower(curFields.Key())
		_, ok := fieldMap[key]
		switch {
		case exclude && ok:
			curFields.Del()
		case exclude && !ok:
			// No-op: exclude but we didn't find it
		case !exclude && ok:
			// No-op: include and we found it
		case !exclude && !ok:
			curFields.Del()
		_, present := fieldMap[key]
		// XOR exclude and present. When they are equal, it means we
		// should not add the header to the new header struct
		if exclude == present {
			continue
		}
		nh.Add(key, curFields.Value())
	}
	return nh
}

// MessageHeaders populates a models.MessageInfo struct for the message.

M worker/maildir/worker.go => worker/maildir/worker.go +2 -2
@@ 667,9 667,9 @@ func (w *Worker) handleFetchMessageHeaders(
		}
		switch {
		case len(w.headersExclude) > 0:
			lib.LimitHeaders(info.RFC822Headers, w.headersExclude, true)
			info.RFC822Headers = lib.LimitHeaders(info.RFC822Headers, w.headersExclude, true)
		case len(w.headers) > 0:
			lib.LimitHeaders(info.RFC822Headers, w.headers, false)
			info.RFC822Headers = lib.LimitHeaders(info.RFC822Headers, w.headers, false)
		}
		w.worker.PostMessage(&types.MessageInfo{
			Message: types.RespondTo(msg),

M worker/mbox/worker.go => worker/mbox/worker.go +2 -2
@@ 174,9 174,9 @@ func (w *mboxWorker) handleMessage(msg types.WorkerMessage) error {
			} else {
				switch {
				case len(w.headersExclude) > 0:
					lib.LimitHeaders(msgInfo.RFC822Headers, w.headersExclude, true)
					msgInfo.RFC822Headers = lib.LimitHeaders(msgInfo.RFC822Headers, w.headersExclude, true)
				case len(w.headers) > 0:
					lib.LimitHeaders(msgInfo.RFC822Headers, w.headers, false)
					msgInfo.RFC822Headers = lib.LimitHeaders(msgInfo.RFC822Headers, w.headers, false)
				}
				w.worker.PostMessage(&types.MessageInfo{
					Message: types.RespondTo(msg),

M worker/notmuch/worker.go => worker/notmuch/worker.go +2 -2
@@ 678,9 678,9 @@ func (w *worker) emitMessageInfo(m *Message,
	}
	switch {
	case len(w.headersExclude) > 0:
		lib.LimitHeaders(info.RFC822Headers, w.headersExclude, true)
		info.RFC822Headers = lib.LimitHeaders(info.RFC822Headers, w.headersExclude, true)
	case len(w.headers) > 0:
		lib.LimitHeaders(info.RFC822Headers, w.headers, false)
		info.RFC822Headers = lib.LimitHeaders(info.RFC822Headers, w.headers, false)
	}
	switch parent {
	case nil: