Skip to content

Commit

Permalink
Use short-lived in-memory cache to cache previewed feeds (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nemoden authored Jan 28, 2023
1 parent e6e78c8 commit 5bb44e0
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 111 deletions.
12 changes: 6 additions & 6 deletions cmd/nom/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ type Options struct {
var ErrNotEnoughArgs = errors.New("not enough args")

func run(args []string, opts Options) error {
if len(opts.PreviewFeeds) > 0 {
// Don't mess up the cache of configured feeds in the preview mode.
opts.NoCache = true
}

cfg, err := config.New(opts.ConfigPath, opts.Pager, opts.NoCache, opts.PreviewFeeds)
if err != nil {
return err
Expand All @@ -38,7 +33,12 @@ func run(args []string, opts Options) error {
return err
}

cash := cache.New(cache.DefaultPath, cache.DefaultExpiry)
var cash cache.CacheInterface
if cfg.IsPreviewMode() {
cash = cache.NewMemoryCache()
} else {
cash = cache.NewFileCache(cache.DefaultPath, cache.DefaultExpiry)
}

cmds := commands.New(cfg, cash)

Expand Down
109 changes: 109 additions & 0 deletions internal/cache/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package cache

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"

"github.com/guyfedwards/nom/internal/rss"
)

var DefaultPath = filepath.Join(os.TempDir(), "nom")

type FileCache struct {
expiry time.Duration
path string
content CacheContent
}

// Write writes content to a file at the location specified in the cache
func (c *FileCache) Write(key string, content rss.RSS) error {
err := createCacheIfNotExists(c.path)
if err != nil {
return fmt.Errorf("createcache: %w", err)
}

data, err := os.ReadFile(filepath.Join(c.path, "cache.json"))
if err != nil {
return fmt.Errorf("cache Write: %w", err)
}

var cc CacheContent

err = json.Unmarshal(data, &cc)
if err != nil {
return fmt.Errorf("cache Write json unmarshal: %w", err)
}

cc[key] = content

str, err := json.Marshal(cc)
if err != nil {
return fmt.Errorf("cache write marshal json: %w", err)
}

err = os.WriteFile(filepath.Join(c.path, "cache.json"), str, 0655)
if err != nil {
return fmt.Errorf("cache Write: %w", err)
}

return nil
}

// Read reads from the cache, returning a ErrFileCacheMiss if nothing found or
// if the cache is older than the expiry
func (c *FileCache) Read(key string) (rss.RSS, error) {
err := createCacheIfNotExists(c.path)
if err != nil {
return rss.RSS{}, fmt.Errorf("cache read: %w", err)
}

data, err := os.ReadFile(filepath.Join(c.path, "cache.json"))
if err != nil {
return rss.RSS{}, fmt.Errorf("cache read file: %w", err)
}

var cc CacheContent

err = json.Unmarshal(data, &cc)
if err != nil {
return rss.RSS{}, fmt.Errorf("cache read unmarshal: %w", err)
}

if _, ok := cc[key]; !ok {
return rss.RSS{}, ErrCacheMiss
}

return cc[key], nil
}

func createCacheIfNotExists(path string) error {
cachePath := filepath.Join(path, "cache.json")
info, _ := os.Stat(cachePath)
if info != nil {
return nil
}

fmt.Println("No existing cache found, creating")

err := os.MkdirAll(path, 0755)
if err != nil {
return fmt.Errorf("createDirIfNotExists: %w", err)
}

var cc = make(CacheContent)

str, err := json.Marshal(cc)
if err != nil {
return fmt.Errorf("createDirIfNotExists: %w", err)
}

err = os.WriteFile(cachePath, []byte(str), 0655)
if err != nil {
return fmt.Errorf("createDirIfNotExists: %w", err)
}

return nil
}
6 changes: 3 additions & 3 deletions internal/cache/main_test.go → internal/cache/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func TestReadReturnError(t *testing.T) {
c := New("../test/data", 0)
c := NewFileCache("../test/data", 0)

_, err := c.Read("foo")
if !errors.Is(err, ErrCacheMiss) {
Expand All @@ -17,7 +17,7 @@ func TestReadReturnError(t *testing.T) {
}

func TestWrite(t *testing.T) {
c := New("../test/data/", 0)
c := NewFileCache("../test/data/", 0)

err := c.Write("keytomyheart", rss.RSS{
Channel: rss.Channel{
Expand All @@ -34,7 +34,7 @@ func TestWrite(t *testing.T) {
}

func TestRead(t *testing.T) {
c := New("../test/data/", 0)
c := NewFileCache("../test/data/", 0)

_ = c.Write("cashin", rss.RSS{
Channel: rss.Channel{
Expand Down
106 changes: 9 additions & 97 deletions internal/cache/main.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package cache

import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"time"

"github.com/guyfedwards/nom/internal/rss"
Expand All @@ -14,6 +10,11 @@ import (
// key is feedurl
type CacheContent = map[string]rss.RSS

type CacheInterface interface {
Write(key string, content rss.RSS) error
Read(key string) (rss.RSS, error)
}

type Cache struct {
expiry time.Duration
path string
Expand All @@ -26,102 +27,13 @@ var ErrCacheMiss = errors.New("cache miss")
// 24hrs
const DefaultExpiry time.Duration = 24 * 60 * 60 * 1000 * 1000 * 1000

var DefaultPath = filepath.Join(os.TempDir(), "nom")

// creates a new cache
func New(path string, expiry time.Duration) Cache {
return Cache{
func NewFileCache(path string, expiry time.Duration) CacheInterface {
return &FileCache{
expiry: expiry,
path: path,
}
}

// Write writes content to a file at the location specified in the cache
func (c *Cache) Write(key string, content rss.RSS) error {
err := createCacheIfNotExists(c.path)
if err != nil {
return fmt.Errorf("createcache: %w", err)
}

data, err := os.ReadFile(filepath.Join(c.path, "cache.json"))
if err != nil {
return fmt.Errorf("cache Write: %w", err)
}

var cc CacheContent

err = json.Unmarshal(data, &cc)
if err != nil {
return fmt.Errorf("cache Write json unmarshal: %w", err)
}

cc[key] = content

str, err := json.Marshal(cc)
if err != nil {
return fmt.Errorf("cache write marshal json: %w", err)
}

err = os.WriteFile(filepath.Join(c.path, "cache.json"), str, 0655)
if err != nil {
return fmt.Errorf("cache Write: %w", err)
}

return nil
}

// Read reads from the cache, returning a ErrCacheMiss if nothing found or
// if the cache is older than the expiry
func (c *Cache) Read(key string) (rss.RSS, error) {
err := createCacheIfNotExists(c.path)
if err != nil {
return rss.RSS{}, fmt.Errorf("cache read: %w", err)
}

data, err := os.ReadFile(filepath.Join(c.path, "cache.json"))
if err != nil {
return rss.RSS{}, fmt.Errorf("cache read file: %w", err)
}

var cc CacheContent

err = json.Unmarshal(data, &cc)
if err != nil {
return rss.RSS{}, fmt.Errorf("cache read unmarshal: %w", err)
}

if _, ok := cc[key]; !ok {
return rss.RSS{}, ErrCacheMiss
}

return cc[key], nil
}

func createCacheIfNotExists(path string) error {
cachePath := filepath.Join(path, "cache.json")
info, _ := os.Stat(cachePath)
if info != nil {
return nil
}

fmt.Println("No existing cache found, creating")

err := os.MkdirAll(path, 0755)
if err != nil {
return fmt.Errorf("createDirIfNotExists: %w", err)
}

var cc = make(CacheContent)

str, err := json.Marshal(cc)
if err != nil {
return fmt.Errorf("createDirIfNotExists: %w", err)
}

err = os.WriteFile(cachePath, []byte(str), 0655)
if err != nil {
return fmt.Errorf("createDirIfNotExists: %w", err)
}

return nil
func NewMemoryCache() CacheInterface {
return &MemoryCache{}
}
23 changes: 23 additions & 0 deletions internal/cache/memory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package cache

import "github.com/guyfedwards/nom/internal/rss"

type MemoryCache struct {
content CacheContent
}

func (c *MemoryCache) Write(key string, content rss.RSS) error {
if c.content == nil {
c.content = make(CacheContent)
}
c.content[key] = content
return nil
}

func (c *MemoryCache) Read(key string) (rss.RSS, error) {
if _, ok := c.content[key]; !ok {
return rss.RSS{}, ErrCacheMiss
}

return c.content[key], nil
}
4 changes: 2 additions & 2 deletions internal/commands/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import (

type Commands struct {
config config.Config
cache cache.Cache
cache cache.CacheInterface
}

func New(config config.Config, cache cache.Cache) Commands {
func New(config config.Config, cache cache.CacheInterface) Commands {
return Commands{config, cache}
}

Expand Down
6 changes: 3 additions & 3 deletions internal/config/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ func TestPreviewFeedsOverrideFeedsFromConfigFile(t *testing.T) {
feeds := c.GetFeeds()
test.Equal(t, 3, len(feeds), "Incorrect feeds number")
test.Equal(t, "cattle", feeds[0].URL, "First feed in a config must be cattle")
test.Equal(t, "bird", feeds[1].URL, "First feed in a config must be bird")
test.Equal(t, "bird", feeds[1].URL, "Second feed in a config must be bird")
test.Equal(t, "dog", feeds[2].URL, "Third feed in a config must be dog")

c, _ = New(configFixturePath, "", false, []string{"pumpkin", "raddish"})
c, _ = New(configFixturePath, "", false, []string{"pumpkin", "radish"})
c.Load()
feeds = c.GetFeeds()
test.Equal(t, 2, len(feeds), "Incorrect feeds number")
test.Equal(t, "pumpkin", feeds[0].URL, "First feed in a config must be pumpkin")
test.Equal(t, "raddish", feeds[1].URL, "First feed in a config must be raddish")
test.Equal(t, "radish", feeds[1].URL, "Second feed in a config must be radish")
}

func TestConfigLoad(t *testing.T) {
Expand Down

0 comments on commit 5bb44e0

Please sign in to comment.