~rjarry/aerc-devel

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
7 4

[PATCH aerc v1] jmap: fetch entire threads

Details
Message ID
<20240709011710.26346-2-tristan@partin.io>
DKIM signature
pass
Download raw message
Patch: +141 -36
Fetch an email's entire thread in the JMAP backend.

Changelog-added: Fetch entire threads in the JMAP backend.
Signed-off-by: Tristan Partin <tristan@partin.io>
---
 worker/jmap/cache/state.go | 13 ++++++
 worker/jmap/fetch.go       | 74 ++++++++++++++++++----------------
 worker/jmap/push.go        |  8 +++-
 worker/jmap/threads.go     | 82 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 141 insertions(+), 36 deletions(-)
 create mode 100644 worker/jmap/threads.go

diff --git a/worker/jmap/cache/state.go b/worker/jmap/cache/state.go
index 5fec5034..e7771075 100644
--- a/worker/jmap/cache/state.go
+++ b/worker/jmap/cache/state.go
@@ -24,7 +24,20 @@ func (c *JMAPCache) PutEmailState(state string) error {
	return c.put(emailStateKey, []byte(state))
}

func (c *JMAPCache) GetThreadState() (string, error) {
	buf, err := c.get(threadStateKey)
	if err != nil {
		return "", err
	}
	return string(buf), nil
}

func (c *JMAPCache) PutThreadState(state string) error {
	return c.put(threadStateKey, []byte(state))
}

const (
	mailboxStateKey = "state/mailbox"
	emailStateKey   = "state/email"
	threadStateKey  = "state/thread"
)
diff --git a/worker/jmap/fetch.go b/worker/jmap/fetch.go
index 5d88e6a1..bbef1bb5 100644
--- a/worker/jmap/fetch.go
+++ b/worker/jmap/fetch.go
@@ -16,6 +16,7 @@ import (
var headersProperties = []string{
	"id",
	"blobId",
	"threadId",
	"mailboxIds",
	"keywords",
	"size",
@@ -34,9 +35,8 @@ var headersProperties = []string{
}

func (w *JMAPWorker) handleFetchMessageHeaders(msg *types.FetchMessageHeaders) error {
	var req jmap.Request

	ids := make([]jmap.ID, 0, len(msg.Uids))
	emailIdsToFetch := make([]jmap.ID, 0, len(msg.Uids))
	currentEmails := make([]*email.Email, 0, len(msg.Uids))
	for _, uid := range msg.Uids {
		id, ok := w.uidStore.GetKey(uid)
		if !ok {
@@ -45,47 +45,51 @@ func (w *JMAPWorker) handleFetchMessageHeaders(msg *types.FetchMessageHeaders) e
		jid := jmap.ID(id)
		m, err := w.cache.GetEmail(jid)
		if err == nil {
			w.w.PostMessage(&types.MessageInfo{
				Message: types.RespondTo(msg),
				Info:    w.translateMsgInfo(m),
			}, nil)
			continue
			currentEmails = append(currentEmails, m)
		} else {
			emailIdsToFetch = append(emailIdsToFetch, jid)
		}
		ids = append(ids, jid)
	}

	if len(ids) == 0 {
		return nil
	if len(emailIdsToFetch) != 0 {
		var req jmap.Request

		req.Invoke(&email.Get{
			Account:    w.AccountId(),
			IDs:        emailIdsToFetch,
			Properties: []string{"threadId"},
		})

		resp, err := w.Do(&req)
		if err != nil {
			return err
		}

		for _, inv := range resp.Responses {
			switch r := inv.Args.(type) {
			case *email.GetResponse:
				if err = w.cache.PutEmailState(r.State); err != nil {
					w.w.Warnf("PutEmailState: %s", err)
				}
				currentEmails = append(currentEmails, r.List...)
			case *jmap.MethodError:
				return wrapMethodError(r)
			}
		}
	}

	req.Invoke(&email.Get{
		Account:    w.AccountId(),
		IDs:        ids,
		Properties: headersProperties,
	})

	resp, err := w.Do(&req)
	allEmails, err := w.fetchEntireThreads(currentEmails)
	if err != nil {
		return err
	}

	for _, inv := range resp.Responses {
		switch r := inv.Args.(type) {
		case *email.GetResponse:
			for _, m := range r.List {
				w.w.PostMessage(&types.MessageInfo{
					Message: types.RespondTo(msg),
					Info:    w.translateMsgInfo(m),
				}, nil)
				if err := w.cache.PutEmail(m.ID, m); err != nil {
					w.w.Warnf("PutEmail: %s", err)
				}
			}
			if err = w.cache.PutEmailState(r.State); err != nil {
				w.w.Warnf("PutEmailState: %s", err)
			}
		case *jmap.MethodError:
			return wrapMethodError(r)
	for _, m := range allEmails {
		w.w.PostMessage(&types.MessageInfo{
			Message: types.RespondTo(msg),
			Info:    w.translateMsgInfo(m),
		}, nil)
		if err := w.cache.PutEmail(m.ID, m); err != nil {
			w.w.Warnf("PutEmail: %s", err)
		}
	}

diff --git a/worker/jmap/push.go b/worker/jmap/push.go
index 54e6375e..0a85332e 100644
--- a/worker/jmap/push.go
+++ b/worker/jmap/push.go
@@ -253,7 +253,13 @@ func (w *JMAPWorker) refresh(newState jmap.TypeState) error {
					selectedIds[id] = true
				}
			}
			for _, m := range r.List {

			emails, err := w.fetchEntireThreads(r.List)
			if err != nil {
				return err
			}

			for _, m := range emails {
				err = w.cache.PutEmail(m.ID, m)
				if err != nil {
					w.w.Warnf("PutEmail: %s", err)
diff --git a/worker/jmap/threads.go b/worker/jmap/threads.go
new file mode 100644
index 00000000..b967e8cd
--- /dev/null
+++ b/worker/jmap/threads.go
@@ -0,0 +1,82 @@
package jmap

import (
	"git.sr.ht/~rockorager/go-jmap"
	"git.sr.ht/~rockorager/go-jmap/mail/email"
	"git.sr.ht/~rockorager/go-jmap/mail/thread"
)

func (w *JMAPWorker) fetchEntireThreads(emailIds []*email.Email) ([]*email.Email, error) {
	var req jmap.Request

	if len(emailIds) == 0 {
		return emailIds, nil
	}

	threadsToFetch := make([]jmap.ID, 0, len(emailIds))
	for _, m := range emailIds {
		threadsToFetch = append(threadsToFetch, m.ThreadID)
	}

	req.Invoke(&thread.Get{
		Account: w.AccountId(),
		IDs:     threadsToFetch,
	})

	resp, err := w.Do(&req)
	if err != nil {
		return nil, err
	}

	emailsToFetch := make([]jmap.ID, 0)
	emailsToReturn := make([]*email.Email, 0)
	for _, inv := range resp.Responses {
		switch r := inv.Args.(type) {
		case *thread.GetResponse:
			for _, t := range r.List {
				for _, emailId := range t.EmailIDs {
					m, err := w.cache.GetEmail(emailId)
					if err == nil || m == nil {
						emailsToFetch = append(emailsToFetch, emailId)
					} else {
						emailsToReturn = append(emailsToReturn, m)
					}
				}
			}
			if err = w.cache.PutThreadState(r.State); err != nil {
				w.w.Warnf("PutThreadState: %s", err)
			}
		case *jmap.MethodError:
			return nil, wrapMethodError(r)
		}
	}

	if len(emailsToFetch) == 0 {
		return emailsToReturn, nil
	}

	req.Invoke(&email.Get{
		Account:    w.AccountId(),
		IDs:        emailsToFetch,
		Properties: headersProperties,
	})

	resp, err = w.Do(&req)
	if err != nil {
		return nil, err
	}

	for _, inv := range resp.Responses {
		switch r := inv.Args.(type) {
		case *email.GetResponse:
			emailsToReturn = append(emailsToReturn, r.List...)
			if err = w.cache.PutEmailState(r.State); err != nil {
				w.w.Warnf("PutEmailState: %s", err)
			}
		case *jmap.MethodError:
			return nil, wrapMethodError(r)
		}
	}

	return emailsToReturn, nil
}
-- 
Tristan Partin
https://tristan.partin.io

[aerc/patches] build success

builds.sr.ht <builds@sr.ht>
Details
Message ID
<D2KM33LX3DZT.27AC24LMAYVCE@fra02>
In-Reply-To
<20240709011710.26346-2-tristan@partin.io> (view parent)
DKIM signature
missing
Download raw message
aerc/patches: SUCCESS in 2m0s

[jmap: fetch entire threads][0] from [Tristan Partin][1]

[0]: https://lists.sr.ht/~rjarry/aerc-devel/patches/53768
[1]: tristan@partin.io

✓ #1271435 SUCCESS aerc/patches/openbsd.yml     https://builds.sr.ht/~rjarry/job/1271435
✓ #1271434 SUCCESS aerc/patches/alpine-edge.yml https://builds.sr.ht/~rjarry/job/1271434
Details
Message ID
<D2L2PKEY1F70.3L9X22FJG0UO9@timculverhouse.com>
In-Reply-To
<20240709011710.26346-2-tristan@partin.io> (view parent)
DKIM signature
pass
Download raw message
On Mon Jul 8, 2024 at 8:17 PM CDT, Tristan Partin wrote:
> Fetch an email's entire thread in the JMAP backend.
>
> Changelog-added: Fetch entire threads in the JMAP backend.
> Signed-off-by: Tristan Partin <tristan@partin.io>
> ---

Hey Tristan -

I wasn't able to test this...I applied the patch and get this error in a loop at
startup:

  DEBUG 2024/07/09 09:19:49.812608 connect.go:94: [jmap] >1214> POST
  DEBUG 2024/07/09 09:19:49.812637 connect.go:99: [jmap] <1214< json: error calling MarshalJSON for type *jmap.Invocation: json: error calling MarshalJSON for type jmap.ID: invalid ID: too short

-- 
Tim
Details
Message ID
<D2L37BBF3FOP.18EH2NM1O02JI@timculverhouse.com>
In-Reply-To
<20240709011710.26346-2-tristan@partin.io> (view parent)
DKIM signature
pass
Download raw message
On Mon Jul 8, 2024 at 8:17 PM CDT, Tristan Partin wrote:
> Fetch an email's entire thread in the JMAP backend.
>
> Changelog-added: Fetch entire threads in the JMAP backend.
> Signed-off-by: Tristan Partin <tristan@partin.io>
> ---

Took your IRC advice and deleted my cache.

Works great and much appreciated to have the full thread!

Tested-by: Tim Culverhouse <tim@timculverhouse.com>

Applied: [PATCH aerc v1] jmap: fetch entire threads

Details
Message ID
<172270895925.29060.5565171842822583856@ringo.local>
In-Reply-To
<20240709011710.26346-2-tristan@partin.io> (view parent)
DKIM signature
pass
Download raw message
Tristan Partin <tristan@partin.io> wrote:
> Fetch an email's entire thread in the JMAP backend.
>
> Changelog-added: Fetch entire threads in the JMAP backend.
> Signed-off-by: Tristan Partin <tristan@partin.io>
> ---

Acked-by: Robin Jarry <robin@jarry.cc>

Applied, thanks.

To git@git.sr.ht:~rjarry/aerc
   d20c578de426..cd92da0e893a  master -> master

Re: Applied: [PATCH aerc v1] jmap: fetch entire threads

Details
Message ID
<D373J8UIB3BX.3TE4J0AXJPLZ4@bitquabit.com>
In-Reply-To
<172270895925.29060.5565171842822583856@ringo.local> (view parent)
DKIM signature
pass
Download raw message
On Sat Aug 3, 2024 at 2:23 PM EDT, Robin Jarry wrote:
> Tristan Partin <tristan@partin.io> wrote:
> > Fetch an email's entire thread in the JMAP backend.
> >
> > Changelog-added: Fetch entire threads in the JMAP backend.
> > Signed-off-by: Tristan Partin <tristan@partin.io>
> > ---
>
> Acked-by: Robin Jarry <robin@jarry.cc>
>
> Applied, thanks.

Unfortunately, since this got applied, aerc is unable to load my Fastmail
account.  The logs are filled with:

json: error calling MarshalJSON for type *jmap.Invocation: json: error calling MarshalJSON for type jmap.ID: invalid ID: too short

I did confirm that this patch specifically is the source of the problem.

Re: Applied: [PATCH aerc v1] jmap: fetch entire threads

Details
Message ID
<1becf6e3-8b29-4ee9-835e-1801b3adf1dc@app.fastmail.com>
In-Reply-To
<D373J8UIB3BX.3TE4J0AXJPLZ4@bitquabit.com> (view parent)
DKIM signature
pass
Download raw message
Benjamin Pollack, Aug 04 2024:
> Unfortunately, since this got applied, aerc is unable to load my Fastmail
> account.  The logs are filled with:
>
> json: error calling MarshalJSON for type *jmap.Invocation: json: error 
> calling MarshalJSON for type jmap.ID: invalid ID: too short
>
> I did confirm that this patch specifically is the source of the problem.

You will need to delete your cache.

rm -rf ~/.cache/$account_name

Re: Applied: [PATCH aerc v1] jmap: fetch entire threads

Details
Message ID
<D3743XPMDWXM.2Y0NCWIPAVJQ1@bitquabit.com>
In-Reply-To
<1becf6e3-8b29-4ee9-835e-1801b3adf1dc@app.fastmail.com> (view parent)
DKIM signature
pass
Download raw message
On Sun Aug 4, 2024 at 7:41 AM EDT, Robin Jarry wrote:
> Benjamin Pollack, Aug 04 2024:
> > Unfortunately, since this got applied, aerc is unable to load my Fastmail
> > account.  The logs are filled with:
> >
> > json: error calling MarshalJSON for type *jmap.Invocation: json: error 
> > calling MarshalJSON for type jmap.ID: invalid ID: too short
> >
> > I did confirm that this patch specifically is the source of the problem.
>
> You will need to delete your cache.
>
> rm -rf ~/.cache/$account_name

Oh bah, and I see that was already covered upthread.  Sorry for the false
alarm.  Thanks for the fix, and I can confirm that works.
Reply to thread Export thread (mbox)