Repository: JanDeDobbeleer/oh-my-posh
Pull request #990 (2021-09-23 21:47:23)

feat: add caching for owm segment
default timeout of 10 minutes
Original concept by boarder2

### Prerequisites

- [x] I have read and understood the `CONTRIBUTING` guide
- [x] The commit message follows the [conventional commits][cc] guidelines
- [x] Tests for the changes have been added (for bug fixes/features)
- [x] Docs have been added / updated (for bug fixes/features)

### Description

Original pull by @boarder2 (#946). Relies on the new global cache by @JanDeDobbeleer.

<!---

Tips:

If you're not comfortable with working with Git, we're working a guide (https://ohmyposh.dev/docs/contributing_git) to help you out.
Oh My Posh advises GitKraken (https://www.gitkraken.com/invite/nQmDPR9D) as your preferred cross platform Git GUI power tool.

-->

[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary



Patch:
diff --git a/docs/docs/segment-owm.md b/docs/docs/segment-owm.md
index cbcac9a7..296ff0a3 100644
--- a/docs/docs/segment-owm.md
+++ b/docs/docs/segment-owm.md
@@ -29,7 +29,8 @@ The free tier for *Current weather and forecasts collection* is sufficient.
     "location": "AMSTERDAM,NL",
     "units": "metric",
     "enable_hyperlink" : false,
-    "http_timeout": 20
+    "http_timeout": 20,
+    "cache_timeout": 10
   }
 }
 ```
@@ -44,3 +45,4 @@ The free tier for *Current weather and forecasts collection* is sufficient.
                     Available values are standard (kelvin), metric (celsius), and imperial (fahrenheit) - defaults to `standard`
 - enable_hyperlink: `bool` - Displays an hyperlink to get openweathermap data
 - http_timeout: `int` - The default timeout for http request is 20ms.
+- cache_timeout: `int` - The default timeout for request caching is 10m. A value of 0 disables the cache.
diff --git a/src/config.go b/src/config.go
index 9d2f6eaf..f593c6cd 100644
--- a/src/config.go
+++ b/src/config.go
@@ -44,6 +44,8 @@ const (
 	HTTPTimeout Property = "http_timeout"
 	// DefaultHTTPTimeout default timeout used when executing http request
 	DefaultHTTPTimeout = 20
+	// DefaultCacheTimeout default timeout used when caching data
+	DefaultCacheTimeout = 10
 )
 
 func printConfigError(err error, eval bool) {
diff --git a/src/segment_owm.go b/src/segment_owm.go
index 75843104..3adef037 100644
--- a/src/segment_owm.go
+++ b/src/segment_owm.go
@@ -21,6 +21,10 @@ const (
 	LOCATION Property = "location"
 	// UNITS openweathermap units
 	UNITS Property = "units"
+	// CACHETimeout cache timeout
+	CACHETimeout Property = "cache_timeout"
+	// CACHEKey key used when caching the response
+	CACHEKey string = "owm_response"
 )
 
 type weather struct {
@@ -61,22 +65,57 @@ func (d *owm) string() string {
 	return text
 }
 
-func (d *owm) setStatus() error {
+func (d *owm) getResult() (*OWMDataResponse, error) {
 	apikey := d.props.getString(APIKEY, ".")
 	location := d.props.getString(LOCATION, "De Bilt,NL")
 	units := d.props.getString(UNITS, "standard")
-	timeout := d.props.getInt(HTTPTimeout, DefaultHTTPTimeout)
+	httpTimeout := d.props.getInt(HTTPTimeout, DefaultHTTPTimeout)
+	cacheTimeout := int64(d.props.getInt(CACHETimeout, DefaultCacheTimeout))
+
+	d.url = fmt.Sprintf("http://api.openweathermap.org/data/2.5/weather?q=%s&units=%s&appid=%s", location, units, apikey)
+
+	if cacheTimeout > 0 {
+		// check if data stored in cache
+		val, found := d.env.cache().get(CACHEKey)
+		// we got something from te cache
+		if found {
+			q := new(OWMDataResponse)
+			err := json.Unmarshal([]byte(val), q)
+			if err != nil {
+				return nil, err
+			}
+			return q, nil
+		}
+	}
 
-	url := fmt.Sprintf("http://api.openweathermap.org/data/2.5/weather?q=%s&units=%s&appid=%s", location, units, apikey)
-	body, err := d.env.doGet(url, timeout)
+	body, err := d.env.doGet(d.url, httpTimeout)
 	if err != nil {
-		return err
+		return new(OWMDataResponse), err
 	}
 	q := new(OWMDataResponse)
 	err = json.Unmarshal(body, &q)
+	if err != nil {
+		return new(OWMDataResponse), err
+	}
+
+	if cacheTimeout > 0 {
+		// persist new forecasts in cache
+		d.env.cache().set(CACHEKey, string(body), cacheTimeout)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return q, nil
+}
+
+func (d *owm) setStatus() error {
+	units := d.props.getString(UNITS, "standard")
+
+	q, err := d.getResult()
 	if err != nil {
 		return err
 	}
+
 	d.temperature = q.temperature.Value
 	icon := ""
 	switch q.Data[0].TypeID {
@@ -118,7 +157,6 @@ func (d *owm) setStatus() error {
 		icon = "\ue313"
 	}
 	d.weather = icon
-	d.url = url
 	d.units = units
 	return nil
 }
diff --git a/themes/schema.json b/themes/schema.json
index b467872b..666b3a69 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -1589,6 +1589,12 @@
                   },
                   "http_timeout": {
                     "$ref": "#/definitions/http_timeout"
+                  },
+                  "cache_timeout": {
+                    "type": "integer",
+                    "title": "cache timeout",
+                    "description": "The number of minutes the response is cached. A value of 0 disables the cache.",
+                    "default": 10
                   }
                 }
               }


Test patch:
diff --git a/src/segment_owm_test.go b/src/segment_owm_test.go
index 513e2cde..faa2faf9 100644
--- a/src/segment_owm_test.go
+++ b/src/segment_owm_test.go
@@ -8,6 +8,10 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+const (
+	OWMAPIURL = "http://api.openweathermap.org/data/2.5/weather?q=AMSTERDAM,NL&units=metric&appid=key"
+)
+
 func TestOWMSegmentSingle(t *testing.T) {
 	cases := []struct {
 		Case            string
@@ -34,15 +38,14 @@ func TestOWMSegmentSingle(t *testing.T) {
 		env := &MockedEnvironment{}
 		props := &properties{
 			values: map[Property]interface{}{
-				APIKEY:   "key",
-				LOCATION: "AMSTERDAM,NL",
-				UNITS:    "metric",
+				APIKEY:       "key",
+				LOCATION:     "AMSTERDAM,NL",
+				UNITS:        "metric",
+				CACHETimeout: 0,
 			},
 		}
 
-		url := "http://api.openweathermap.org/data/2.5/weather?q=AMSTERDAM,NL&units=metric&appid=key"
-
-		env.On("doGet", url).Return([]byte(tc.JSONResponse), tc.Error)
+		env.On("doGet", OWMAPIURL).Return([]byte(tc.JSONResponse), tc.Error)
 
 		o := &owm{
 			props: props,
@@ -162,17 +165,17 @@ func TestOWMSegmentIcons(t *testing.T) {
 		env := &MockedEnvironment{}
 		props := &properties{
 			values: map[Property]interface{}{
-				APIKEY:   "key",
-				LOCATION: "AMSTERDAM,NL",
-				UNITS:    "metric",
+				APIKEY:       "key",
+				LOCATION:     "AMSTERDAM,NL",
+				UNITS:        "metric",
+				CACHETimeout: 0,
 			},
 		}
 
-		url := "http://api.openweathermap.org/data/2.5/weather?q=AMSTERDAM,NL&units=metric&appid=key"
 		response := fmt.Sprintf(`{"weather":[{"icon":"%s"}],"main":{"temp":20}}`, tc.IconID)
 		expectedString := fmt.Sprintf("%s (20°C)", tc.ExpectedIconString)
 
-		env.On("doGet", url).Return([]byte(response), nil)
+		env.On("doGet", OWMAPIURL).Return([]byte(response), nil)
 
 		o := &owm{
 			props: props,
@@ -183,3 +186,27 @@ func TestOWMSegmentIcons(t *testing.T) {
 		assert.Equal(t, expectedString, o.string(), tc.Case)
 	}
 }
+func TestOWMSegmentFromCache(t *testing.T) {
+	response := fmt.Sprintf(`{"weather":[{"icon":"%s"}],"main":{"temp":20}}`, "01d")
+	expectedString := fmt.Sprintf("%s (20°C)", "\ufa98")
+
+	env := &MockedEnvironment{}
+	cache := &MockedCache{}
+	props := &properties{
+		values: map[Property]interface{}{
+			APIKEY:   "key",
+			LOCATION: "AMSTERDAM,NL",
+			UNITS:    "metric",
+		},
+	}
+	o := &owm{
+		props: props,
+		env:   env,
+	}
+	cache.On("get", "owm_response").Return(response, true)
+	cache.On("set").Return()
+	env.On("cache", nil).Return(cache)
+
+	assert.Nil(t, o.setStatus())
+	assert.Equal(t, expectedString, o.string())
+}