diff --git a/cmd/cache.go b/cmd/cache.go new file mode 100644 index 0000000..66280dd --- /dev/null +++ b/cmd/cache.go @@ -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)) +} diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 0896ce5..6bfbda0 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -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 { diff --git a/pkg/cache/cache_test.go b/pkg/cache/cache_test.go index 491c82b..ef6f45d 100644 --- a/pkg/cache/cache_test.go +++ b/pkg/cache/cache_test.go @@ -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) { diff --git a/yr/yr.go b/yr/yr.go index e9d156d..6e049fa 100644 --- a/yr/yr.go +++ b/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 }