feat: Add cache cmd
Implements: https://todo.sr.ht/~timharek/yr/4 Signed-off-by: Tim Hårek Andreassen <tim@harek.no>
This commit is contained in:
parent
04059d8af6
commit
985f8a53d3
4 changed files with 155 additions and 10 deletions
64
cmd/cache.go
Normal file
64
cmd/cache.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.sr.ht/~timharek/yr/yr"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cacheCmd = &cobra.Command{
|
||||
Use: "cache",
|
||||
Short: "Get details about your cached locations",
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
Run: caches,
|
||||
}
|
||||
var cacheClearCmd = &cobra.Command{
|
||||
Use: "clear",
|
||||
Short: "Clear cache",
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
Run: cachesClear,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(cacheCmd)
|
||||
cacheCmd.AddCommand(cacheClearCmd)
|
||||
}
|
||||
|
||||
func caches(cmd *cobra.Command, args []string) {
|
||||
c, err := yr.New()
|
||||
cobra.CheckErr(err)
|
||||
|
||||
list, err := c.Cache.List()
|
||||
cobra.CheckErr(err)
|
||||
|
||||
if len(list) == 0 {
|
||||
fmt.Println("No location is cached")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// NOTE: Both location name and coordinates are used to store files
|
||||
fmt.Println("Cached locations")
|
||||
for _, item := range list {
|
||||
fmt.Println("\t" + item.Filename)
|
||||
}
|
||||
}
|
||||
|
||||
func cachesClear(cmd *cobra.Command, args []string) {
|
||||
c, err := yr.New()
|
||||
cobra.CheckErr(err)
|
||||
|
||||
list, err := c.Cache.List()
|
||||
cobra.CheckErr(err)
|
||||
|
||||
if len(list) == 0 {
|
||||
fmt.Println("No location is cached")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
err = c.Cache.Clear()
|
||||
cobra.CheckErr(err)
|
||||
|
||||
fmt.Printf("Cleared %d locations from cache\n", len(list))
|
||||
}
|
72
pkg/cache/cache.go
vendored
72
pkg/cache/cache.go
vendored
|
@ -7,6 +7,7 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -33,7 +34,7 @@ func (c *Cache[T]) Add(name string, data any) error {
|
|||
return fmt.Errorf("Failed to ensure cache-dir is present.")
|
||||
}
|
||||
|
||||
filePath := filepath.Join(c.Path, fmt.Sprintf("%s.json", name))
|
||||
filePath := filepath.Join(c.Path, filename(name))
|
||||
d := []byte(j)
|
||||
err = os.WriteFile(filePath, d, 0646)
|
||||
if err != nil {
|
||||
|
@ -45,7 +46,7 @@ func (c *Cache[T]) Add(name string, data any) error {
|
|||
|
||||
// Get data from JSON-file from cache-dir, returns nil if no result
|
||||
func (c *Cache[T]) Get(name string) (*T, error) {
|
||||
filePath := filepath.Join(c.Path, fmt.Sprintf("%s.json", name))
|
||||
filePath := filepath.Join(c.Path, filename(name))
|
||||
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -70,6 +71,73 @@ func (c *Cache[T]) Get(name string) (*T, error) {
|
|||
return &result, nil
|
||||
}
|
||||
|
||||
type Saved[T any] struct {
|
||||
Filename string
|
||||
Item T
|
||||
}
|
||||
|
||||
func (c *Cache[T]) List() ([]Saved[T], error) {
|
||||
var result []Saved[T]
|
||||
filenames, err := list(c.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to read from cache path")
|
||||
}
|
||||
|
||||
if len(filenames) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
for _, f := range filenames {
|
||||
cached, err := c.Get(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, Saved[T]{Filename: f, Item: *(cached)})
|
||||
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func list(path string) ([]string, error) {
|
||||
dir, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to read from path")
|
||||
}
|
||||
var filenames []string
|
||||
|
||||
for _, item := range dir {
|
||||
if !item.IsDir() && strings.Contains(item.Name(), prefix) {
|
||||
tmp := strings.ReplaceAll(strings.ReplaceAll(item.Name(), ".json", ""), prefix, "")
|
||||
filenames = append(filenames, tmp)
|
||||
}
|
||||
}
|
||||
return filenames, nil
|
||||
|
||||
}
|
||||
|
||||
func (c *Cache[T]) Clear() error {
|
||||
files, err := list(c.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range files {
|
||||
filePath := filepath.Join(c.Path, filename(f))
|
||||
|
||||
err = os.Remove(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var prefix = "yr-"
|
||||
|
||||
func filename(name string) string {
|
||||
return fmt.Sprintf("%s%s.json", prefix, name)
|
||||
}
|
||||
|
||||
// Returns if t is expired
|
||||
func IsExpired(expire time.Time, now *time.Time) bool {
|
||||
if now == nil {
|
||||
|
|
15
pkg/cache/cache_test.go
vendored
15
pkg/cache/cache_test.go
vendored
|
@ -17,14 +17,27 @@ func TestCache(t *testing.T) {
|
|||
assert := assert.New(t)
|
||||
c := New[testType]("")
|
||||
assert.Equal(os.TempDir(), c.Path)
|
||||
err := c.Clear()
|
||||
assert.NoError(err)
|
||||
|
||||
err := c.Add("something", &testType{Prop1: "test1", Prop2: "test2"})
|
||||
err = c.Add("something", &testType{Prop1: "test1", Prop2: "test2"})
|
||||
assert.NoError(err)
|
||||
|
||||
result, err := c.Get("something")
|
||||
assert.NoError(err)
|
||||
assert.Equal("test1", result.Prop1)
|
||||
assert.Equal("test2", result.Prop2)
|
||||
|
||||
list, err := c.List()
|
||||
assert.Equal(1, len(list))
|
||||
assert.NoError(err)
|
||||
|
||||
err = c.Add("something else", &testType{Prop1: "test1", Prop2: "test2"})
|
||||
assert.NoError(err)
|
||||
|
||||
list, err = c.List()
|
||||
assert.NoError(err)
|
||||
assert.Equal(2, len(list))
|
||||
}
|
||||
|
||||
func TestIsExpired(t *testing.T) {
|
||||
|
|
14
yr/yr.go
14
yr/yr.go
|
@ -15,7 +15,7 @@ import (
|
|||
type Client struct {
|
||||
met met.Met
|
||||
nom nominatim.Nominatim
|
||||
cache cache.Cache[ForecastResult]
|
||||
Cache cache.Cache[ForecastResult]
|
||||
}
|
||||
|
||||
// New default Yr client
|
||||
|
@ -31,7 +31,7 @@ func New() (*Client, error) {
|
|||
}
|
||||
cache := cache.New[ForecastResult]("")
|
||||
|
||||
return &Client{met: *met, nom: *nom, cache: *cache}, nil
|
||||
return &Client{met: *met, nom: *nom, Cache: *cache}, nil
|
||||
}
|
||||
|
||||
type wind struct {
|
||||
|
@ -89,7 +89,7 @@ type ForecastResult struct {
|
|||
|
||||
// Current forecast for q
|
||||
func (c *Client) Now(q string) (*ForecastResult, error) {
|
||||
cacheResult, err := c.cache.Get(q)
|
||||
cacheResult, err := c.Cache.Get(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ func (c *Client) NowCoords(coords *nominatim.Coordinates, location *string) (*Fo
|
|||
|
||||
// Forecast for q
|
||||
func (c *Client) Forecast(q string) (*ForecastResult, error) {
|
||||
cacheResult, err := c.cache.Get(q)
|
||||
cacheResult, err := c.Cache.Get(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ func (c *Client) Forecast(q string) (*ForecastResult, error) {
|
|||
// Forecast for coords
|
||||
func (c *Client) ForecastCoords(coords *nominatim.Coordinates, location *string) (*ForecastResult, error) {
|
||||
coordCacheName := fmt.Sprintf("%.4f_%.4f", coords.Latitude, coords.Longitude)
|
||||
cacheResult, err := c.cache.Get(coordCacheName)
|
||||
cacheResult, err := c.Cache.Get(coordCacheName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -220,11 +220,11 @@ func (c *Client) ForecastCoords(coords *nominatim.Coordinates, location *string)
|
|||
Forecast: forecasts,
|
||||
}
|
||||
|
||||
err = c.cache.Add(*location, result)
|
||||
err = c.Cache.Add(*location, result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.cache.Add(coordCacheName, result)
|
||||
err = c.Cache.Add(coordCacheName, result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue