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"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"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.")
|
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)
|
d := []byte(j)
|
||||||
err = os.WriteFile(filePath, d, 0646)
|
err = os.WriteFile(filePath, d, 0646)
|
||||||
if err != nil {
|
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
|
// Get data from JSON-file from cache-dir, returns nil if no result
|
||||||
func (c *Cache[T]) Get(name string) (*T, error) {
|
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) {
|
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -70,6 +71,73 @@ func (c *Cache[T]) Get(name string) (*T, error) {
|
||||||
return &result, nil
|
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
|
// Returns if t is expired
|
||||||
func IsExpired(expire time.Time, now *time.Time) bool {
|
func IsExpired(expire time.Time, now *time.Time) bool {
|
||||||
if now == nil {
|
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)
|
assert := assert.New(t)
|
||||||
c := New[testType]("")
|
c := New[testType]("")
|
||||||
assert.Equal(os.TempDir(), c.Path)
|
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)
|
assert.NoError(err)
|
||||||
|
|
||||||
result, err := c.Get("something")
|
result, err := c.Get("something")
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal("test1", result.Prop1)
|
assert.Equal("test1", result.Prop1)
|
||||||
assert.Equal("test2", result.Prop2)
|
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) {
|
func TestIsExpired(t *testing.T) {
|
||||||
|
|
14
yr/yr.go
14
yr/yr.go
|
@ -15,7 +15,7 @@ import (
|
||||||
type Client struct {
|
type Client struct {
|
||||||
met met.Met
|
met met.Met
|
||||||
nom nominatim.Nominatim
|
nom nominatim.Nominatim
|
||||||
cache cache.Cache[ForecastResult]
|
Cache cache.Cache[ForecastResult]
|
||||||
}
|
}
|
||||||
|
|
||||||
// New default Yr client
|
// New default Yr client
|
||||||
|
@ -31,7 +31,7 @@ func New() (*Client, error) {
|
||||||
}
|
}
|
||||||
cache := cache.New[ForecastResult]("")
|
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 {
|
type wind struct {
|
||||||
|
@ -89,7 +89,7 @@ type ForecastResult struct {
|
||||||
|
|
||||||
// Current forecast for q
|
// Current forecast for q
|
||||||
func (c *Client) Now(q string) (*ForecastResult, error) {
|
func (c *Client) Now(q string) (*ForecastResult, error) {
|
||||||
cacheResult, err := c.cache.Get(q)
|
cacheResult, err := c.Cache.Get(q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ func (c *Client) NowCoords(coords *nominatim.Coordinates, location *string) (*Fo
|
||||||
|
|
||||||
// Forecast for q
|
// Forecast for q
|
||||||
func (c *Client) Forecast(q string) (*ForecastResult, error) {
|
func (c *Client) Forecast(q string) (*ForecastResult, error) {
|
||||||
cacheResult, err := c.cache.Get(q)
|
cacheResult, err := c.Cache.Get(q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ func (c *Client) Forecast(q string) (*ForecastResult, error) {
|
||||||
// Forecast for coords
|
// Forecast for coords
|
||||||
func (c *Client) ForecastCoords(coords *nominatim.Coordinates, location *string) (*ForecastResult, error) {
|
func (c *Client) ForecastCoords(coords *nominatim.Coordinates, location *string) (*ForecastResult, error) {
|
||||||
coordCacheName := fmt.Sprintf("%.4f_%.4f", coords.Latitude, coords.Longitude)
|
coordCacheName := fmt.Sprintf("%.4f_%.4f", coords.Latitude, coords.Longitude)
|
||||||
cacheResult, err := c.cache.Get(coordCacheName)
|
cacheResult, err := c.Cache.Get(coordCacheName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -220,11 +220,11 @@ func (c *Client) ForecastCoords(coords *nominatim.Coordinates, location *string)
|
||||||
Forecast: forecasts,
|
Forecast: forecasts,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.cache.Add(*location, result)
|
err = c.Cache.Add(*location, result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = c.cache.Add(coordCacheName, result)
|
err = c.Cache.Add(coordCacheName, result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue