Skip to content

Commit

Permalink
Merge pull request #1 from purwandi/feat/lru-cache-in-memory-store
Browse files Browse the repository at this point in the history
implement lru backed repository
  • Loading branch information
purwandi authored Dec 12, 2020
2 parents 607bab7 + 2d1a079 commit 6ce0b48
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 4 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ MAIL_PASSWORD='password'
MAIL_TLS=true
MAIL_TLS_CERT=./cert/certificate.crt
MAIL_TLS_KEY=./cert/certificate.key
LRU_CACHE_SIZE=10000
14 changes: 13 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"os/signal"
"runtime"
"strconv"
"syscall"

"github.com/joho/godotenv"
Expand All @@ -16,6 +17,7 @@ import (
)

var (
cacheSize = 10000
shutdowns []func() error
auth *mail.Auth
tls *mail.TLS
Expand Down Expand Up @@ -49,8 +51,18 @@ func run() {
httpPort = os.Getenv("HTTP_PORT")
}

if cs := os.Getenv("LRU_CACHE_SIZE"); cs != "" {
i, err := strconv.Atoi(cs)
if err == nil {
cacheSize = i
}
}

// TODO implement multiple data store sqlite/postgresql
db := repository.NewMessageInMemory(repository.NewMessageInMemoryStore())
db, err := repository.NewLRUCacheStore(cacheSize, logger)
if err != nil {
panic(err)
}

if os.Getenv("MAIL_AUTH") == "true" {
auth = &mail.Auth{
Expand Down
8 changes: 5 additions & 3 deletions domain/attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ func CreateAttachment(filename, contentType string, content []byte) (Attachment,
}

// write into file
f, err := os.Create(fmt.Sprintf("%s/%s%s", mail.AssetFilePath, fid, filepath.Ext(filename)))
f, err := os.Create(fmt.Sprintf("%s/%s", mail.AssetFilePath, attch.Filepath))
if err != nil {
return Attachment{}, err
}
defer f.Close()
_, err = f.Write(content)
if err != nil {
return Attachment{}, err
}

f.Write(content)

return attch, nil
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.14
require (
github.com/emersion/go-smtp v0.14.0
github.com/gorilla/sessions v1.2.1
github.com/hashicorp/golang-lru v0.5.4
github.com/jhillyerd/enmime v0.8.3
github.com/joho/godotenv v1.3.0
github.com/labstack/echo-contrib v0.9.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43 h1:jTkyeF7NZ5oIr0ESmcrpiDgAfoidCBF4F5kJhjtaRwE=
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jhillyerd/enmime v0.8.3 h1:aZGfnnvl4m4FJhvoCrCeAwJWEb3GNm+WhaQ9Nr0Oec8=
Expand Down
156 changes: 156 additions & 0 deletions repository/lrucache_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package repository

import (
"context"
"errors"
"fmt"
"os"
"sort"
"time"

lru "github.com/hashicorp/golang-lru"
"github.com/purwandi/mail"
"github.com/purwandi/mail/domain"
"github.com/segmentio/ksuid"
"go.uber.org/zap"
)

type lruCache struct {
cache *lru.Cache
}

func evictionFn(logger *zap.Logger) func(key interface{}, value interface{}) {
return func(key interface{}, value interface{}) {
m, ok := value.(domain.Message)
if !ok {
return
}
for _, att := range m.Attachments {
fullPath := fmt.Sprintf("%s/%s", mail.AssetFilePath, att.Filepath)
err := os.Remove(fullPath)
if err != nil {
msg := fmt.Sprintf("failed deleting file: %s from message id: %s", fullPath, key)
logger.Error(msg, zap.Error(err))
}
}
}

}

func NewLRUCacheStore(size int, logger *zap.Logger) (MessageRepository, error) {
c, err := lru.NewWithEvict(size, evictionFn(logger))
if err != nil {
return nil, err
}
m1 := ksuid.New().String()
m2 := ksuid.New().String()
c.Add(m1, domain.Message{
ID: m1,
Subject: "7 Quotes by Albert Einstein That Will Change How You Think ",
Sender: "[email protected]",
From: []domain.Contact{
{Email: "[email protected]", Name: "Medium"},
},
To: []domain.Contact{
{Email: "[email protected]", Name: "Fooabar"},
},
TextBody: "We’ve almost made it to 2021. Have you built any particularly cool projects this year? We’d love to hear about them.",
HTMLBody: "<p>hello world</p>",
Attachments: []domain.Attachment{
{ID: "1lNigjr8fsntbweehfBLpkQRoMh", Filename: "attachment.txt", Filepath: "1lNigjr8fsntbweehfBLpkQRoMh.txt"},
{ID: "1lNip3BucJnkDGo2uAChhgib20T", Filename: "attachment.png", Filepath: "1lNip3BucJnkDGo2uAChhgib20T.png"},
},
Date: time.Now().Add(-20 * time.Minute),
})
c.Add(m2, domain.Message{
ID: m2,
Subject: "Change How You Think | Sinem Günel in Age of Awareness",
Sender: "[email protected]",
From: []domain.Contact{
{Email: "[email protected]", Name: "google"},
},
To: []domain.Contact{
{Email: "[email protected]", Name: "Foobar"},
},
TextBody: "Collaborating with multiple people can be difficult, especially with lots of back and forth across email and chat.",
HTMLBody: "<p>hello world</p>",
Attachments: []domain.Attachment{
{ID: "1lOs2EOqStjiMlDINR44KnMV9De", Filename: "attachment.txt", Filepath: "1lOs2EOqStjiMlDINR44KnMV9D.txt"},
{ID: "1lOs60waIw6LoEqWNPmL7LRR06M", Filename: "attachment.png", Filepath: "1lOs60waIw6LoEqWNPmL7LRR06M.png"},
},
Date: time.Now(),
})
return &lruCache{
cache: c,
}, nil
}

func (c *lruCache) Save(ctx context.Context, m *domain.Message) <-chan error {
ch := make(chan error)
go func() {
c.cache.Add(m.ID, *m)
ch <- nil
close(ch)
}()

return ch
}
func (c *lruCache) Delete(ctx context.Context, id string) <-chan error {
ch := make(chan error)
go func() {
present := c.cache.Remove(id)
if present {
ch <- nil
close(ch)
return
}
ch <- errors.New("Message not found")
close(ch)
}()

return ch
}
func (c *lruCache) Reset(ctx context.Context) <-chan error {
ch := make(chan error)
go func() {
c.cache.Purge()
ch <- nil
close(ch)
}()

return ch
}
func (c *lruCache) Find(ctx context.Context, id string) <-chan QueryResult {
ch := make(chan QueryResult)
go func() {
var qr QueryResult
inf, ok := c.cache.Get(id)
if !ok {
qr.Error = errors.New("Message not found")
ch <- qr
return
}
qr.Result = inf
ch <- qr
close(ch)
}()
return ch
}
func (c *lruCache) FindAll(context.Context) <-chan QueryResult {
ch := make(chan QueryResult)
go func() {
var (
qr QueryResult
msgs []domain.Message
)
for _, k := range c.cache.Keys() {
inf, _ := c.cache.Get(k)
msgs = append(msgs, inf.(domain.Message))
}
sort.Sort(ByDateDesc(msgs))
qr.Result = msgs
ch <- qr
close(ch)
}()
return ch
}

0 comments on commit 6ce0b48

Please sign in to comment.