processing inbox
This commit is contained in:
parent
1d69a1fbce
commit
e1758db30e
|
@ -0,0 +1,83 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.sr.ht/~ewintr/gte/internal/task"
|
||||||
|
"git.sr.ht/~ewintr/gte/pkg/mstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config := &mstore.EmailConfiguration{
|
||||||
|
IMAPURL: os.Getenv("IMAP_URL"),
|
||||||
|
IMAPUsername: os.Getenv("IMAP_USERNAME"),
|
||||||
|
IMAPPassword: os.Getenv("IMAP_PASSWORD"),
|
||||||
|
}
|
||||||
|
if !config.Valid() {
|
||||||
|
log.Fatal("please set MAIL_USER, MAIL_PASSWORD, etc environment variables")
|
||||||
|
}
|
||||||
|
|
||||||
|
mailStore, err := mstore.EmailConnect(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer mailStore.Disconnect()
|
||||||
|
|
||||||
|
taskRepo := task.NewRepository(mailStore)
|
||||||
|
tasks, err := taskRepo.FindAll("INBOX")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, t := range tasks {
|
||||||
|
fmt.Printf("processing: %s... ", t.Action)
|
||||||
|
|
||||||
|
if t.Dirty() {
|
||||||
|
if err := taskRepo.Update(t); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("updated.")
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
folders, err := mailStore.FolderNames()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, f := range folders {
|
||||||
|
fmt.Println(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mailStore.Select("Today"); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
messages, err := mailStore.Messages()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, m := range messages {
|
||||||
|
fmt.Printf("%d: %s\n", m.Uid, m.Subject)
|
||||||
|
}
|
||||||
|
if len(messages) == 0 {
|
||||||
|
log.Fatal("no messages")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mailStore.Remove(messages[0].Uid); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := NewBody(`From: todo <process@erikwinter.nl>
|
||||||
|
Subject: the subject
|
||||||
|
|
||||||
|
And here comes the body`)
|
||||||
|
|
||||||
|
if err := mailStore.Append("INBOX", imap.Literal(body)); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
|
@ -1,91 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.sr.ht/~ewintr/gte/pkg/mstore"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Body struct {
|
|
||||||
reader io.Reader
|
|
||||||
length int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBody(msg string) *Body {
|
|
||||||
return &Body{
|
|
||||||
reader: strings.NewReader(msg),
|
|
||||||
length: len([]byte(msg)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Body) Read(p []byte) (int, error) {
|
|
||||||
return b.reader.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Body) Len() int {
|
|
||||||
return b.length
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
config := &mstore.EmailConfiguration{
|
|
||||||
IMAPURL: os.Getenv("IMAP_URL"),
|
|
||||||
IMAPUsername: os.Getenv("IMAP_USERNAME"),
|
|
||||||
IMAPPassword: os.Getenv("IMAP_PASSWORD"),
|
|
||||||
}
|
|
||||||
if !config.Valid() {
|
|
||||||
fmt.Printf("conf: %v\n", config)
|
|
||||||
log.Fatal("please set MAIL_USER, MAIL_PASSWORD, etc environment variables")
|
|
||||||
}
|
|
||||||
//fmt.Printf("conf: %+v\n", config)
|
|
||||||
|
|
||||||
mailStore, err := mstore.EmailConnect(config)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer mailStore.Disconnect()
|
|
||||||
|
|
||||||
/*
|
|
||||||
folders, err := mailStore.FolderNames()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, f := range folders {
|
|
||||||
fmt.Println(f)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
if err := mailStore.Select("Today"); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
messages, err := mailStore.Messages()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, m := range messages {
|
|
||||||
fmt.Printf("%d: %s\n", m.Uid, m.Subject)
|
|
||||||
}
|
|
||||||
if len(messages) == 0 {
|
|
||||||
log.Fatal("no messages")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := mailStore.Remove(messages[0].Uid); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
body := NewBody(`From: todo <process@erikwinter.nl>
|
|
||||||
Subject: the subject
|
|
||||||
|
|
||||||
And here comes the body`)
|
|
||||||
|
|
||||||
if err := mailStore.Append("INBOX", imap.Literal(body)); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
4
go.mod
4
go.mod
|
@ -5,6 +5,6 @@ go 1.14
|
||||||
require (
|
require (
|
||||||
git.sr.ht/~ewintr/go-kit v0.0.0-20201229104230-4d7958f8de04
|
git.sr.ht/~ewintr/go-kit v0.0.0-20201229104230-4d7958f8de04
|
||||||
github.com/emersion/go-imap v1.0.6
|
github.com/emersion/go-imap v1.0.6
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||||
github.com/emersion/go-smtp v0.14.0
|
github.com/google/uuid v1.2.0
|
||||||
)
|
)
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -53,8 +53,6 @@ github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b h1:uhWtEWBHgop1rq
|
||||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-smtp v0.14.0 h1:RYW203p+EcPjL8Z/ZpT9lZ6iOc8MG1MQzEx1UKEkXlA=
|
|
||||||
github.com/emersion/go-smtp v0.14.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
@ -92,6 +90,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||||
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
|
|
@ -2,21 +2,9 @@ package task
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type Date time.Time
|
|
||||||
|
|
||||||
func (d *Date) Weekday() Weekday {
|
|
||||||
return d.Weekday()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Weekday time.Weekday
|
type Weekday time.Weekday
|
||||||
|
|
||||||
type Period int
|
type Period int
|
||||||
|
|
||||||
type Task struct {
|
|
||||||
Action string
|
|
||||||
Due Date
|
|
||||||
}
|
|
||||||
|
|
||||||
type Recurrer interface {
|
type Recurrer interface {
|
||||||
FirstAfter(date Date) Date
|
FirstAfter(date Date) Date
|
||||||
}
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.sr.ht/~ewintr/gte/pkg/mstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrMStoreError = errors.New("mstore gave error response")
|
||||||
|
)
|
||||||
|
|
||||||
|
type TaskRepo struct {
|
||||||
|
mstore mstore.MStorer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRepository(ms mstore.MStorer) *TaskRepo {
|
||||||
|
return &TaskRepo{
|
||||||
|
mstore: ms,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *TaskRepo) FindAll(folder string) ([]*Task, error) {
|
||||||
|
msgs, err := tr.mstore.Messages(folder)
|
||||||
|
if err != nil {
|
||||||
|
return []*Task{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks := []*Task{}
|
||||||
|
for _, msg := range msgs {
|
||||||
|
if msg.Valid() {
|
||||||
|
tasks = append(tasks, NewFromMessage(msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tasks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *TaskRepo) Update(t *Task) error {
|
||||||
|
if !t.Current {
|
||||||
|
return ErrOutdatedTask
|
||||||
|
}
|
||||||
|
if !t.Dirty() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new
|
||||||
|
if err := tr.mstore.Add(t.Folder, t.Subject(), t.Body()); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrMStoreError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove old
|
||||||
|
if err := tr.mstore.Remove(t.Message); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrMStoreError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Current = false
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.sr.ht/~ewintr/gte/pkg/mstore"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrOutdatedTask = errors.New("task is outdated")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Date time.Time
|
||||||
|
|
||||||
|
func (d *Date) Weekday() Weekday {
|
||||||
|
return d.Weekday()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Task struct {
|
||||||
|
Id string
|
||||||
|
Folder string
|
||||||
|
Action string
|
||||||
|
Due Date
|
||||||
|
Message *mstore.Message
|
||||||
|
Current bool
|
||||||
|
Simplified bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFromMessage(msg *mstore.Message) *Task {
|
||||||
|
fmt.Println(msg.Subject)
|
||||||
|
id := FieldFromBody("id", msg.Body)
|
||||||
|
if id == "" {
|
||||||
|
id = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
action := FieldFromBody("action", msg.Body)
|
||||||
|
if action == "" {
|
||||||
|
action = FieldFromSubject("action", msg.Subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
folder := msg.Folder
|
||||||
|
if folder == "INBOX" {
|
||||||
|
folder = "New"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Task{
|
||||||
|
Id: id,
|
||||||
|
Action: action,
|
||||||
|
Folder: folder,
|
||||||
|
Message: msg,
|
||||||
|
Current: true,
|
||||||
|
Simplified: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dirty checks if the task has unsaved changes
|
||||||
|
func (t *Task) Dirty() bool {
|
||||||
|
mBody := t.Message.Body
|
||||||
|
mSubject := t.Message.Subject
|
||||||
|
|
||||||
|
if t.Id != FieldFromBody("id", mBody) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Folder != t.Message.Folder {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Action != FieldFromBody("action", mBody) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if t.Action != FieldFromSubject("action", mSubject) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Task) Subject() string {
|
||||||
|
return t.Action
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Task) Body() string {
|
||||||
|
body := fmt.Sprintf("id: %s\n", t.Id)
|
||||||
|
body += fmt.Sprintf("action: %s\n", t.Action)
|
||||||
|
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
func FieldFromBody(field, body string) string {
|
||||||
|
lines := strings.Split(body, "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
parts := strings.SplitN(line, ":", 2)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.ToLower(parts[0]) == field {
|
||||||
|
return strings.TrimSpace(parts[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func FieldFromSubject(field, subject string) string {
|
||||||
|
if field == "action" {
|
||||||
|
return strings.ToLower(subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
|
@ -2,12 +2,35 @@ package mstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/emersion/go-imap"
|
"github.com/emersion/go-imap"
|
||||||
"github.com/emersion/go-imap/client"
|
"github.com/emersion/go-imap/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
reader io.Reader
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBody(msg string) *Body {
|
||||||
|
|
||||||
|
return &Body{
|
||||||
|
reader: strings.NewReader(msg),
|
||||||
|
length: len([]byte(msg)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Body) Read(p []byte) (int, error) {
|
||||||
|
return b.reader.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Body) Len() int {
|
||||||
|
return b.length
|
||||||
|
}
|
||||||
|
|
||||||
type EmailConfiguration struct {
|
type EmailConfiguration struct {
|
||||||
IMAPURL string
|
IMAPURL string
|
||||||
IMAPUsername string
|
IMAPUsername string
|
||||||
|
@ -48,7 +71,7 @@ func (es *Email) Disconnect() {
|
||||||
es.imap.Logout()
|
es.imap.Logout()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Email) FolderNames() ([]string, error) {
|
func (es *Email) Folders() ([]string, error) {
|
||||||
boxes, done := make(chan *imap.MailboxInfo), make(chan error)
|
boxes, done := make(chan *imap.MailboxInfo), make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
done <- es.imap.List("", "*", boxes)
|
done <- es.imap.List("", "*", boxes)
|
||||||
|
@ -66,21 +89,20 @@ func (es *Email) FolderNames() ([]string, error) {
|
||||||
return folders, nil
|
return folders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Email) Select(folder string) error {
|
func (es *Email) selectFolder(folder string) error {
|
||||||
status, err := es.imap.Select(folder, false)
|
status, err := es.imap.Select(folder, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("status: %+v\n", status)
|
|
||||||
|
|
||||||
es.mboxStatus = status
|
es.mboxStatus = status
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Email) Messages() ([]*Message, error) {
|
func (es *Email) Messages(folder string) ([]*Message, error) {
|
||||||
if es.mboxStatus == nil {
|
if err := es.selectFolder(folder); err != nil {
|
||||||
return []*Message{}, fmt.Errorf("no mailbox selected")
|
return []*Message{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if es.mboxStatus.Messages == 0 {
|
if es.mboxStatus.Messages == 0 {
|
||||||
|
@ -97,9 +119,9 @@ func (es *Email) Messages() ([]*Message, error) {
|
||||||
|
|
||||||
messages := []*Message{}
|
messages := []*Message{}
|
||||||
for m := range imsg {
|
for m := range imsg {
|
||||||
//fmt.Printf("%+v\n", m)
|
|
||||||
messages = append(messages, &Message{
|
messages = append(messages, &Message{
|
||||||
Uid: m.Uid,
|
Uid: m.Uid,
|
||||||
|
Folder: folder,
|
||||||
Subject: m.Envelope.Subject,
|
Subject: m.Envelope.Subject,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -111,21 +133,29 @@ func (es *Email) Messages() ([]*Message, error) {
|
||||||
return messages, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Email) Append(mbox string, msg imap.Literal) error {
|
func (es *Email) Add(folder, subject, body string) error {
|
||||||
return es.imap.Append(mbox, nil, time.Time{}, msg)
|
msgStr := fmt.Sprintf(`From: todo <process@erikwinter.nl>
|
||||||
|
Subject: %s
|
||||||
|
|
||||||
|
%s`, subject, body)
|
||||||
|
|
||||||
|
msg := NewBody(msgStr)
|
||||||
|
|
||||||
|
return es.imap.Append(folder, nil, time.Time{}, imap.Literal(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Email) Remove(uid uint32) error {
|
func (es *Email) Remove(msg *Message) error {
|
||||||
if uid == 0 {
|
if !msg.Valid() {
|
||||||
return fmt.Errorf("invalid uid: %d", uid)
|
return ErrInvalidMessage
|
||||||
}
|
}
|
||||||
if es.mboxStatus == nil {
|
|
||||||
return fmt.Errorf("no mailbox selected")
|
if err := es.selectFolder(msg.Folder); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// set deleted flag
|
// set deleted flag
|
||||||
seqset := new(imap.SeqSet)
|
seqset := new(imap.SeqSet)
|
||||||
seqset.AddRange(uid, uid)
|
seqset.AddRange(msg.Uid, msg.Uid)
|
||||||
storeItem := imap.FormatFlagsOp(imap.SetFlags, true)
|
storeItem := imap.FormatFlagsOp(imap.SetFlags, true)
|
||||||
err := es.imap.UidStore(seqset, storeItem, imap.FormatStringList([]string{imap.DeletedFlag}), nil)
|
err := es.imap.UidStore(seqset, storeItem, imap.FormatStringList([]string{imap.DeletedFlag}), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -37,7 +37,7 @@ func NewMemory(folders []string) (*Memory, error) {
|
||||||
|
|
||||||
func (mem *Memory) Folders() ([]string, error) {
|
func (mem *Memory) Folders() ([]string, error) {
|
||||||
folders := []string{}
|
folders := []string{}
|
||||||
for f, _ := range mem.messages {
|
for f := range mem.messages {
|
||||||
folders = append(folders, f)
|
folders = append(folders, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,15 +56,15 @@ func (mem *Memory) Select(folder string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mem *Memory) Add(subject, body string) error {
|
func (mem *Memory) Add(folder, subject, body string) error {
|
||||||
if subject == "" {
|
if subject == "" {
|
||||||
return ErrInvalidMessage
|
return ErrInvalidMessage
|
||||||
}
|
}
|
||||||
if mem.Selected == "" {
|
if _, ok := mem.messages[folder]; !ok {
|
||||||
return ErrNoFolderSelected
|
return ErrFolderDoesNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
mem.messages[mem.Selected] = append(mem.messages[mem.Selected], &Message{
|
mem.messages[folder] = append(mem.messages[folder], &Message{
|
||||||
Uid: mem.nextUid,
|
Uid: mem.nextUid,
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
Body: body,
|
Body: body,
|
||||||
|
|
|
@ -101,12 +101,6 @@ func TestMemorySelect(t *testing.T) {
|
||||||
func TestMemoryAdd(t *testing.T) {
|
func TestMemoryAdd(t *testing.T) {
|
||||||
folder := "folder"
|
folder := "folder"
|
||||||
|
|
||||||
t.Run("no folder selected", func(t *testing.T) {
|
|
||||||
mem, err := mstore.NewMemory([]string{folder})
|
|
||||||
test.OK(t, err)
|
|
||||||
test.Equals(t, mstore.ErrNoFolderSelected, mem.Add("subject", ""))
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
subject string
|
subject string
|
||||||
|
@ -124,8 +118,7 @@ func TestMemoryAdd(t *testing.T) {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
mem, err := mstore.NewMemory([]string{folder})
|
mem, err := mstore.NewMemory([]string{folder})
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
mem.Select(folder)
|
test.Equals(t, tc.exp, mem.Add(folder, tc.subject, ""))
|
||||||
test.Equals(t, tc.exp, mem.Add(tc.subject, ""))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,7 +161,6 @@ func TestMemoryMessages(t *testing.T) {
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
|
|
||||||
expMessages := []*mstore.Message{}
|
expMessages := []*mstore.Message{}
|
||||||
test.OK(t, mem.Select(folderA))
|
|
||||||
for i := 1; i <= tc.amount; i++ {
|
for i := 1; i <= tc.amount; i++ {
|
||||||
m := &mstore.Message{
|
m := &mstore.Message{
|
||||||
Uid: uint32(i),
|
Uid: uint32(i),
|
||||||
|
@ -178,7 +170,7 @@ func TestMemoryMessages(t *testing.T) {
|
||||||
if tc.folder == folderA {
|
if tc.folder == folderA {
|
||||||
expMessages = append(expMessages, m)
|
expMessages = append(expMessages, m)
|
||||||
}
|
}
|
||||||
test.OK(t, mem.Add(m.Subject, m.Body))
|
test.OK(t, mem.Add(folderA, m.Subject, m.Body))
|
||||||
}
|
}
|
||||||
|
|
||||||
test.OK(t, mem.Select(tc.folder))
|
test.OK(t, mem.Select(tc.folder))
|
||||||
|
@ -200,9 +192,8 @@ func TestMemoryRemove(t *testing.T) {
|
||||||
|
|
||||||
mem, err := mstore.NewMemory([]string{folderA, folderB})
|
mem, err := mstore.NewMemory([]string{folderA, folderB})
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
test.OK(t, mem.Select(folderA))
|
|
||||||
for i := 1; i <= 3; i++ {
|
for i := 1; i <= 3; i++ {
|
||||||
test.OK(t, mem.Add(fmt.Sprintf("subject-%d", i), ""))
|
test.OK(t, mem.Add(folderA, fmt.Sprintf("subject-%d", i), ""))
|
||||||
}
|
}
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
@ -12,18 +12,18 @@ var (
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Uid uint32
|
Uid uint32
|
||||||
|
Folder string
|
||||||
Subject string
|
Subject string
|
||||||
Body string
|
Body string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) Valid() bool {
|
func (m *Message) Valid() bool {
|
||||||
return m.Uid != 0 && m.Subject != ""
|
return m.Uid != 0 && m.Subject != "" && m.Folder != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
type MStorer interface {
|
type MStorer interface {
|
||||||
Folders() ([]string, error)
|
Folders() ([]string, error)
|
||||||
Select(folder string) error
|
Messages(folder string) ([]*Message, error)
|
||||||
Messages() ([]*Message, error)
|
Add(folder, subject, body string) error
|
||||||
Add(message *Message) error
|
Remove(msg *Message) error
|
||||||
Remove(uid uint32) error
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,22 +18,47 @@ func TestMessageValid(t *testing.T) {
|
||||||
message: &mstore.Message{},
|
message: &mstore.Message{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no uid",
|
name: "no uid",
|
||||||
message: &mstore.Message{Subject: "subject", Body: "body"},
|
message: &mstore.Message{
|
||||||
|
Subject: "subject",
|
||||||
|
Folder: "folder",
|
||||||
|
Body: "body",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no subject",
|
name: "no folder",
|
||||||
message: &mstore.Message{Uid: 1, Body: "body"},
|
message: &mstore.Message{
|
||||||
|
Uid: 1,
|
||||||
|
Subject: "subject",
|
||||||
|
Body: "body",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no body",
|
name: "no subject",
|
||||||
message: &mstore.Message{Uid: 1, Subject: "subject"},
|
message: &mstore.Message{
|
||||||
exp: true,
|
Uid: 1,
|
||||||
|
Folder: "folder",
|
||||||
|
Body: "body",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all present",
|
name: "no body",
|
||||||
message: &mstore.Message{Uid: 1, Subject: "subject", Body: "body"},
|
message: &mstore.Message{
|
||||||
exp: true,
|
Uid: 1,
|
||||||
|
Folder: "folder",
|
||||||
|
Subject: "subject",
|
||||||
|
},
|
||||||
|
exp: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all present",
|
||||||
|
message: &mstore.Message{
|
||||||
|
Uid: 1,
|
||||||
|
Folder: "folder",
|
||||||
|
Subject: "subject",
|
||||||
|
Body: "body",
|
||||||
|
},
|
||||||
|
exp: true,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue