feat(cache): remove segment specific caching

This commit is contained in:
Jan De Dobbeleer 2024-07-30 12:37:02 +02:00 committed by Jan De Dobbeleer
parent a45fb3991e
commit 4fe6efc94a
44 changed files with 289 additions and 982 deletions

13
src/cache/cache.go vendored
View file

@ -14,11 +14,11 @@ type Cache interface {
Close()
// Gets the value for a given key.
// Returns the value and a boolean indicating if the key was found.
// In case the ttl expired, the function returns false.
// In case the duration expired, the function returns false.
Get(key string) (string, bool)
// Sets a value for a given key.
// The ttl indicates how many minutes to cache the value.
Set(key, value string, ttl int)
// The duration indicates how many minutes to cache the value.
Set(key, value string, duration Duration)
// Deletes a key from the cache.
Delete(key string)
}
@ -45,11 +45,6 @@ const (
PROMPTCOUNTCACHE = "prompt_count_cache"
ENGINECACHE = "engine_cache"
FONTLISTCACHE = "font_list_cache"
ONEDAY = 1440
ONEWEEK = 10080
ONEMONTH = 43200
INFINITE = -1
)
type Entry struct {
@ -63,5 +58,5 @@ func (c *Entry) Expired() bool {
return false
}
return time.Now().Unix() >= (c.Timestamp + int64(c.TTL)*60)
return time.Now().Unix() >= (c.Timestamp + int64(c.TTL))
}

86
src/cache/duration.go vendored Normal file
View file

@ -0,0 +1,86 @@
package cache
import (
"strconv"
"github.com/jandedobbeleer/oh-my-posh/src/regex"
)
type Duration string
const INFINITE = Duration("infinite")
func (d Duration) Seconds() int {
if d == INFINITE {
return -1
}
re := `(?P<AMOUNT>[0-9]*)(?P<UNIT>.*)`
match := regex.FindNamedRegexMatch(re, string(d))
if len(match) < 2 {
return 0
}
amount := match["AMOUNT"]
unit := match["UNIT"]
if len(amount) == 0 {
return 0
}
amountInt, err := strconv.Atoi(amount)
if err != nil {
return 0
}
var multiplier int
switch unit {
case "second", "seconds":
multiplier = 1
case "minute", "minutes":
multiplier = 60
case "hour", "hours":
multiplier = 3600
case "day", "days":
multiplier = 86400
case "week", "weeks":
multiplier = 604800
case "month", "months":
multiplier = 2592000
}
return amountInt * multiplier
}
func (d Duration) IsEmpty() bool {
return len(d) == 0
}
func ToDuration(seconds int) Duration {
if seconds == 0 {
return ""
}
if seconds == -1 {
return "infinite"
}
if seconds%604800 == 0 {
return Duration(strconv.Itoa(seconds/604800) + "weeks")
}
if seconds%86400 == 0 {
return Duration(strconv.Itoa(seconds/86400) + "days")
}
if seconds%3600 == 0 {
return Duration(strconv.Itoa(seconds/3600) + "hours")
}
if seconds%60 == 0 {
return Duration(strconv.Itoa(seconds/60) + "minutes")
}
return Duration(strconv.Itoa(seconds) + "seconds")
}

95
src/cache/duration_test.go vendored Normal file
View file

@ -0,0 +1,95 @@
package cache
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSeconds(t *testing.T) {
cases := []struct {
Case string
Duration Duration
Expected int
}{
{
Case: "1 second",
Duration: "1second",
Expected: 1,
},
{
Case: "2 seconds",
Duration: "2seconds",
Expected: 2,
},
{
Case: "1 minute",
Duration: "1minute",
Expected: 60,
},
{
Case: "2 minutes",
Duration: "2minutes",
Expected: 120,
},
{
Case: "1 hour",
Duration: "1hour",
Expected: 3600,
},
{
Case: "2 hours",
Duration: "2hours",
Expected: 7200,
},
{
Case: "1 day",
Duration: "1day",
Expected: 86400,
},
{
Case: "2 days",
Duration: "2days",
Expected: 172800,
},
{
Case: "1 week",
Duration: "1week",
Expected: 604800,
},
{
Case: "2 weeks",
Duration: "2weeks",
Expected: 1209600,
},
{
Case: "1 month",
Duration: "1month",
Expected: 2592000,
},
{
Case: "2 months",
Duration: "2month",
Expected: 5184000,
},
{
Case: "invalid",
Duration: "foo",
Expected: 0,
},
{
Case: "1 fortnight",
Duration: "1fortnight",
Expected: 0,
},
{
Case: "infinite",
Duration: "infinite",
Expected: -1,
},
}
for _, tc := range cases {
got := tc.Duration.Seconds()
assert.Equal(t, tc.Expected, got, tc.Case)
}
}

8
src/cache/file.go vendored
View file

@ -61,7 +61,7 @@ func (fc *File) Close() {
}
// returns the value for the given key as long as
// the TTL (minutes) is not expired
// the duration is not expired
func (fc *File) Get(key string) (string, bool) {
val, found := fc.cache.Get(key)
if !found {
@ -73,12 +73,12 @@ func (fc *File) Get(key string) (string, bool) {
return "", false
}
// sets the value for the given key with a TTL (minutes)
func (fc *File) Set(key, value string, ttl int) {
// sets the value for the given key with a duration
func (fc *File) Set(key, value string, duration Duration) {
fc.cache.Set(key, &Entry{
Value: value,
Timestamp: time.Now().Unix(),
TTL: ttl,
TTL: duration.Seconds(),
})
fc.dirty = true

View file

@ -1,6 +1,9 @@
package mock
import mock "github.com/stretchr/testify/mock"
import (
"github.com/jandedobbeleer/oh-my-posh/src/cache"
mock "github.com/stretchr/testify/mock"
)
type Cache struct {
mock.Mock
@ -34,8 +37,9 @@ func (_m *Cache) Get(key string) (string, bool) {
return r0, r1
}
func (_m *Cache) Set(key, value string, ttl int) {
_m.Called(key, value, ttl)
// set provides a mock function with given fields: key, value, ttl
func (_m *Cache) Set(key, value string, duration cache.Duration) {
_m.Called(key, value, duration)
}
func (_m *Cache) Delete(key string) {

View file

@ -50,7 +50,7 @@ var toggleCmd = &cobra.Command{
newToggles = append(newToggles, segment)
}
env.Session().Set(cache.TOGGLECACHE, strings.Join(newToggles, ","), 1440)
env.Session().Set(cache.TOGGLECACHE, strings.Join(newToggles, ","), "1day")
},
}

View file

@ -57,7 +57,7 @@ type Segment struct {
Background color.Ansi `json:"background,omitempty" toml:"background,omitempty"`
Foreground color.Ansi `json:"foreground,omitempty" toml:"foreground,omitempty"`
Newline bool `json:"newline,omitempty" toml:"newline,omitempty"`
CacheDuration int `json:"cache_duration,omitempty" toml:"cache_duration,omitempty"`
CacheDuration cache.Duration `json:"cache_duration,omitempty" toml:"cache_duration,omitempty"`
Enabled bool `json:"-" toml:"-"`
@ -153,7 +153,7 @@ func (segment *Segment) isToggled() bool {
}
func (segment *Segment) restoreCache() bool {
if segment.CacheDuration <= 0 {
if segment.CacheDuration.IsEmpty() {
return false
}
@ -182,7 +182,7 @@ func (segment *Segment) restoreCache() bool {
}
func (segment *Segment) setCache() {
if segment.CacheDuration <= 0 {
if segment.CacheDuration.IsEmpty() {
return
}
@ -198,11 +198,11 @@ func (segment *Segment) setCache() {
}
func (segment *Segment) cacheKey() string {
return fmt.Sprintf("segment_cache_%s", segment.Name())
return fmt.Sprintf("segment_cache_%s_%s", segment.Name(), segment.env.Pwd())
}
func (segment *Segment) writerCacheKey() string {
return fmt.Sprintf("segment_cache_writer_%s", segment.Name())
return fmt.Sprintf("segment_cache_writer_%s_%s", segment.Name(), segment.env.Pwd())
}
func (segment *Segment) setText() {

View file

@ -78,7 +78,7 @@ func setCachedFontData(assets []*Asset) {
return
}
environment.Cache().Set(cache.FONTLISTCACHE, string(data), cache.ONEDAY)
environment.Cache().Set(cache.FONTLISTCACHE, string(data), "1day")
}
func CascadiaCode() ([]*Asset, error) {

View file

@ -5,6 +5,8 @@ import (
"fmt"
"io"
httplib "net/http"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
)
const (
@ -80,6 +82,7 @@ func (o *OAuthRequest) refreshToken(refreshToken string) (string, error) {
message: Timeout,
}
}
tokens := &tokenExchange{}
err = json.Unmarshal(body, &tokens)
if err != nil {
@ -87,17 +90,14 @@ func (o *OAuthRequest) refreshToken(refreshToken string) (string, error) {
message: TokenRefreshFailed,
}
}
// add tokens to cache
o.Env.Cache().Set(o.AccessTokenKey, tokens.AccessToken, tokens.ExpiresIn/60)
o.Env.Cache().Set(o.RefreshTokenKey, tokens.RefreshToken, 2*525960) // it should never expire unless revoked, default to 2 year
o.Env.Cache().Set(o.AccessTokenKey, tokens.AccessToken, cache.ToDuration(tokens.ExpiresIn))
o.Env.Cache().Set(o.RefreshTokenKey, tokens.RefreshToken, "2years")
return tokens.AccessToken, nil
}
func OauthResult[a any](o *OAuthRequest, url string, body io.Reader, requestModifiers ...RequestModifier) (a, error) {
if data, err := getCacheValue[a](&o.Request, url); err == nil {
return data, nil
}
accessToken, err := o.getAccessToken()
if err != nil {
var data a
@ -115,5 +115,5 @@ func OauthResult[a any](o *OAuthRequest, url string, body io.Reader, requestModi
requestModifiers = append(requestModifiers, addAuthHeader)
return do[a](&o.Request, url, body, requestModifiers...)
return Do[a](&o.Request, url, body, requestModifiers...)
}

View file

@ -29,11 +29,8 @@ func TestOauthResult(t *testing.T) {
// API response
JSONResponse string
// Cache
CacheJSONResponse string
CacheTimeout int
RefreshTokenFromCache bool
AccessTokenFromCache bool
ResponseCacheMiss bool
// Errors
Error error
// Validations
@ -95,34 +92,23 @@ func TestOauthResult(t *testing.T) {
ExpectedErrorMessage: InvalidRefreshToken,
},
{
Case: "Cache data",
CacheTimeout: 60,
CacheJSONResponse: jsonResponse,
ExpectedData: successData,
Case: "Cache data, invalid data",
RefreshToken: "REFRESH_TOKEN",
TokenResponse: tokenResponse,
JSONResponse: jsonResponse,
ExpectedData: successData,
},
{
Case: "Cache data, invalid data",
CacheTimeout: 60,
RefreshToken: "REFRESH_TOKEN",
CacheJSONResponse: "ERR",
TokenResponse: tokenResponse,
JSONResponse: jsonResponse,
ExpectedData: successData,
},
{
Case: "Cache data, no cache",
CacheTimeout: 60,
RefreshToken: "REFRESH_TOKEN",
ResponseCacheMiss: true,
TokenResponse: tokenResponse,
JSONResponse: jsonResponse,
ExpectedData: successData,
Case: "Cache data, no cache",
RefreshToken: "REFRESH_TOKEN",
TokenResponse: tokenResponse,
JSONResponse: jsonResponse,
ExpectedData: successData,
},
{
Case: "API body failure",
AccessToken: "ACCESSTOKEN",
AccessTokenFromCache: true,
ResponseCacheMiss: true,
JSONResponse: "ERR",
ExpectedErrorMessage: "invalid character 'E' looking for beginning of value",
},
@ -130,7 +116,6 @@ func TestOauthResult(t *testing.T) {
Case: "API request failure",
AccessToken: "ACCESSTOKEN",
AccessTokenFromCache: true,
ResponseCacheMiss: true,
JSONResponse: "ERR",
Error: fmt.Errorf("no response"),
ExpectedErrorMessage: "no response",
@ -143,7 +128,6 @@ func TestOauthResult(t *testing.T) {
cache := &mock.Cache{}
cache.On("Get", url).Return(tc.CacheJSONResponse, !tc.ResponseCacheMiss)
cache.On("Get", accessTokenKey).Return(tc.AccessToken, tc.AccessTokenFromCache)
cache.On("Get", refreshTokenKey).Return(tc.RefreshToken, tc.RefreshTokenFromCache)
cache.On("Set", testify_.Anything, testify_.Anything, testify_.Anything)
@ -162,14 +146,14 @@ func TestOauthResult(t *testing.T) {
AccessToken: tc.AccessToken,
RefreshToken: tc.RefreshToken,
Request: Request{
Env: env,
CacheTimeout: tc.CacheTimeout,
HTTPTimeout: 20,
Env: env,
HTTPTimeout: 20,
},
}
got, err := OauthResult[*data](oauth, url, nil)
assert.Equal(t, tc.ExpectedData, got, tc.Case)
if len(tc.ExpectedErrorMessage) == 0 {
assert.Nil(t, err, tc.Case)
} else {

View file

@ -2,7 +2,6 @@ package http
import (
"encoding/json"
"errors"
"io"
"net/http"
@ -13,9 +12,8 @@ import (
type RequestModifier func(request *http.Request)
type Request struct {
Env Environment
CacheTimeout int
HTTPTimeout int
Env Environment
HTTPTimeout int
}
type Environment interface {
@ -23,37 +21,7 @@ type Environment interface {
Cache() cache.Cache
}
func Do[a any](r *Request, url string, requestModifiers ...RequestModifier) (a, error) {
if data, err := getCacheValue[a](r, url); err == nil {
return data, nil
}
return do[a](r, url, nil, requestModifiers...)
}
func getCacheValue[a any](r *Request, key string) (a, error) {
var data a
cacheTimeout := r.CacheTimeout // r.props.GetInt(properties.CacheTimeout, 30)
if cacheTimeout <= 0 {
return data, errors.New("no cache needed")
}
if val, found := r.Env.Cache().Get(key); found {
err := json.Unmarshal([]byte(val), &data)
if err != nil {
log.Error(err)
return data, err
}
return data, nil
}
err := errors.New("no data in cache")
log.Error(err)
return data, err
}
func do[a any](r *Request, url string, body io.Reader, requestModifiers ...RequestModifier) (a, error) {
func Do[a any](r *Request, url string, body io.Reader, requestModifiers ...RequestModifier) (a, error) {
var data a
httpTimeout := r.HTTPTimeout // r.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout)
@ -69,10 +37,5 @@ func do[a any](r *Request, url string, body io.Reader, requestModifiers ...Reque
return data, err
}
cacheTimeout := r.CacheTimeout // r.props.GetInt(properties.CacheTimeout, 30)
if cacheTimeout > 0 {
r.Env.Cache().Set(url, string(responseBody), cacheTimeout)
}
return data, nil
}

View file

@ -6,7 +6,6 @@ import (
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
"github.com/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock"
@ -35,10 +34,6 @@ func TestRequestResult(t *testing.T) {
Case string
// API response
JSONResponse string
// Cache
CacheJSONResponse string
CacheTimeout int
ResponseCacheMiss bool
// Errors
Error error
// Validations
@ -50,20 +45,6 @@ func TestRequestResult(t *testing.T) {
JSONResponse: jsonResponse,
ExpectedData: successData,
},
{
Case: "Cache",
CacheJSONResponse: `{ "hello":"mom" }`,
ExpectedData: &data{Hello: "mom"},
CacheTimeout: 10,
},
{
Case: "Cache miss",
ResponseCacheMiss: true,
JSONResponse: jsonResponse,
CacheJSONResponse: `{ "hello":"mom" }`,
ExpectedData: successData,
CacheTimeout: 10,
},
{
Case: "DNS error",
Error: &net.DNSError{IsNotFound: true},
@ -77,20 +58,12 @@ func TestRequestResult(t *testing.T) {
}
for _, tc := range cases {
c := &mock.Cache{}
c.On("Get", url).Return(tc.CacheJSONResponse, !tc.ResponseCacheMiss)
c.On("Set", testify_.Anything, testify_.Anything, testify_.Anything)
env := &MockedEnvironment{}
env.On("Cache").Return(c)
env.On("HTTPRequest", url).Return([]byte(tc.JSONResponse), tc.Error)
request := &Request{
Env: env,
CacheTimeout: tc.CacheTimeout,
HTTPTimeout: 0,
Env: env,
HTTPTimeout: 0,
}
got, err := Do[*data](request, url, nil)

View file

@ -589,7 +589,7 @@ func (term *Terminal) saveTemplateCache() {
templateCache, err := json.Marshal(tmplCache)
if err == nil {
term.sessionCache.Set(cache.TEMPLATECACHE, string(templateCache), 1440)
term.sessionCache.Set(cache.TEMPLATECACHE, string(templateCache), "1day")
}
}
@ -769,7 +769,7 @@ func (term *Terminal) setPromptCount() {
// Only update the count if we're generating a primary prompt.
if term.CmdFlags.Primary {
count++
term.Session().Set(cache.PROMPTCOUNTCACHE, strconv.Itoa(count), 1440)
term.Session().Set(cache.PROMPTCOUNTCACHE, strconv.Itoa(count), "1day")
}
term.CmdFlags.PromptCount = count

View file

@ -9,6 +9,7 @@ import (
"strings"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/shirou/gopsutil/v3/host"
mem "github.com/shirou/gopsutil/v3/mem"
terminal "github.com/wayneashleyberry/terminal-dimensions"
@ -35,12 +36,15 @@ func (term *Terminal) IsWsl() bool {
term.Debug(val)
return val == "true"
}
var val bool
defer func() {
term.Cache().Set(key, strconv.FormatBool(val), -1)
term.Cache().Set(key, strconv.FormatBool(val), cache.INFINITE)
}()
val = term.HasCommand("wslpath")
term.Debug(strconv.FormatBool(val))
return val
}
@ -92,15 +96,18 @@ func (term *Terminal) Platform() string {
term.Debug(val)
return val
}
var platform string
defer func() {
term.Cache().Set(key, platform, -1)
term.Cache().Set(key, platform, cache.INFINITE)
}()
if wsl := term.Getenv("WSL_DISTRO_NAME"); len(wsl) != 0 {
platform = strings.Split(strings.ToLower(wsl), "-")[0]
term.Debug(platform)
return platform
}
platform, _, _, _ = host.PlatformInformation()
if platform == "arch" {
// validate for Manjaro
@ -109,6 +116,7 @@ func (term *Terminal) Platform() string {
platform = "manjaro"
}
}
term.Debug(platform)
return platform
}

View file

@ -198,30 +198,6 @@ func (bf *Brewfather) getBatchStatusIcon(batchStatus string) string {
}
func (bf *Brewfather) getResult() (*Batch, error) {
getFromCache := func(key string) (*Batch, error) {
val, found := bf.env.Cache().Get(key)
// we got something from the cache
if found {
var result Batch
err := json.Unmarshal([]byte(val), &result)
if err == nil {
return &result, nil
}
}
return nil, errors.New("no data in cache")
}
putToCache := func(key string, batch *Batch, cacheTimeout int) error {
cacheJSON, err := json.Marshal(batch)
if err != nil {
return err
}
bf.env.Cache().Set(key, string(cacheJSON), cacheTimeout)
return nil
}
userID := bf.props.GetString(BFUserID, "")
if len(userID) == 0 {
return nil, errors.New("missing Brewfather user id (user_id)")
@ -244,18 +220,12 @@ func (bf *Brewfather) getResult() (*Batch, error) {
batchReadingsURL := fmt.Sprintf("https://api.brewfather.app/v1/batches/%s/readings", batchID)
httpTimeout := bf.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout)
cacheTimeout := bf.props.GetInt(properties.CacheTimeout, 5)
if cacheTimeout > 0 {
if data, err := getFromCache(batchURL); err == nil {
return data, nil
}
}
// batch
addAuthHeader := func(request *http.Request) {
request.Header.Add("authorization", authHeader)
}
body, err := bf.env.HTTPRequest(batchURL, nil, httpTimeout, addAuthHeader)
if err != nil {
return nil, err
@ -294,10 +264,6 @@ func (bf *Brewfather) getResult() (*Batch, error) {
}
}
if cacheTimeout > 0 {
_ = putToCache(batchURL, &batch, cacheTimeout)
}
return &batch, nil
}

View file

@ -5,7 +5,6 @@ import (
"testing"
"time"
cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
@ -58,8 +57,6 @@ func TestBrewfatherSegment(t *testing.T) {
BatchReadingsJSONResponse string
ExpectedString string
ExpectedEnabled bool
CacheTimeout int
CacheFoundFail bool
Template string
Error error
}{
@ -148,19 +145,13 @@ func TestBrewfatherSegment(t *testing.T) {
for _, tc := range cases {
env := &mock.Environment{}
props := properties.Map{
properties.CacheTimeout: tc.CacheTimeout,
BFBatchID: BFFakeBatchID,
APIKey: "FAKE",
BFUserID: "FAKE",
BFBatchID: BFFakeBatchID,
APIKey: "FAKE",
BFUserID: "FAKE",
}
cache := &cache_.Cache{}
cache.On("Get", BFCacheKey).Return(nil, false) // cache testing later because cache is a little more complicated than just the single response.
// cache.On("Set", BFCacheKey, tc.JSONResponse, tc.CacheTimeout).Return()
env.On("HTTPRequest", BFBatchURL).Return([]byte(tc.BatchJSONResponse), tc.Error)
env.On("HTTPRequest", BFBatchReadingsURL).Return([]byte(tc.BatchReadingsJSONResponse), tc.Error)
env.On("Cache").Return(cache)
env.On("Flags").Return(&runtime.Flags{})
ns := &Brewfather{

View file

@ -77,41 +77,19 @@ func (d *CarbonIntensity) Template() string {
return " CO₂ {{ .Index.Icon }}{{ .Actual.String }} {{ .TrendIcon }} {{ .Forecast.String }} "
}
func (d *CarbonIntensity) updateCache(responseBody []byte, url string, cacheTimeoutInMinutes int) {
if cacheTimeoutInMinutes > 0 {
d.env.Cache().Set(url, string(responseBody), cacheTimeoutInMinutes)
}
}
func (d *CarbonIntensity) getResult() (*CarbonIntensityResponse, error) {
cacheTimeoutInMinutes := d.props.GetInt(properties.CacheTimeout, properties.DefaultCacheTimeout)
response := new(CarbonIntensityResponse)
url := "https://api.carbonintensity.org.uk/intensity"
if cacheTimeoutInMinutes > 0 {
cachedValue, foundInCache := d.env.Cache().Get(url)
if foundInCache {
err := json.Unmarshal([]byte(cachedValue), response)
if err == nil {
return response, nil
}
// If there was an error, just fall through to refetching
}
}
httpTimeout := d.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout)
body, err := d.env.HTTPRequest(url, nil, httpTimeout)
if err != nil {
d.updateCache(body, url, cacheTimeoutInMinutes)
return new(CarbonIntensityResponse), err
}
err = json.Unmarshal(body, &response)
if err != nil {
d.updateCache(body, url, cacheTimeoutInMinutes)
return new(CarbonIntensityResponse), err
}

View file

@ -5,7 +5,6 @@ import (
"fmt"
"testing"
cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
@ -248,25 +247,3 @@ func TestCarbonIntensitySegmentSingle(t *testing.T) {
assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, d), tc.Case)
}
}
func TestCarbonIntensitySegmentFromCache(t *testing.T) {
response := `{ "data": [ { "from": "2023-10-27T12:30Z", "to": "2023-10-27T13:00Z", "intensity": { "forecast": 199, "actual": 193, "index": "moderate" } } ] }`
expectedString := "CO₂ •193 ↗ 199"
env := &mock.Environment{}
cache := &cache_.Cache{}
d := &CarbonIntensity{
props: properties.Map{},
env: env,
}
env.On("Flags").Return(&runtime.Flags{})
cache.On("Get", CARBONINTENSITYURL).Return(response, true)
cache.On("Set").Return()
env.On("Cache").Return(cache)
assert.Nil(t, d.setStatus())
assert.Equal(t, expectedString, renderTemplate(env, d.Template(), d), "should return the cached response")
}

View file

@ -2,7 +2,6 @@ package segments
import (
"encoding/json"
"errors"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
@ -61,14 +60,6 @@ func (n *GitVersion) Enabled() bool {
return false
}
dir := n.env.Pwd()
version, err := n.getCacheValue(dir)
// only return on valid cache value
if err == nil && len(version.FullSemVer) != 0 {
n.gitVersion = *version
return true
}
response, err := n.env.RunCommand(gitversion, "-output", "json")
if err != nil {
return false
@ -76,35 +67,8 @@ func (n *GitVersion) Enabled() bool {
n.gitVersion = gitVersion{}
err = json.Unmarshal([]byte(response), &n.gitVersion)
if err != nil {
return false
}
cacheTimeout := n.props.GetInt(properties.CacheTimeout, 30)
if cacheTimeout > 0 {
n.env.Cache().Set(dir, response, cacheTimeout)
}
return true
}
func (n *GitVersion) getCacheValue(key string) (*gitVersion, error) {
var semVer = &gitVersion{}
cacheTimeout := n.props.GetInt(properties.CacheTimeout, 30)
if cacheTimeout <= 0 {
return semVer, errors.New("no cache needed")
}
if val, found := n.env.Cache().Get(key); found {
err := json.Unmarshal([]byte(val), &semVer)
if err != nil {
return semVer, err
}
return semVer, nil
}
err := errors.New("no data in cache")
return semVer, err
return err == nil
}
func (n *GitVersion) Init(props properties.Properties, env runtime.Environment) {

View file

@ -4,12 +4,10 @@ import (
"errors"
"testing"
cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/alecthomas/assert"
testify_ "github.com/stretchr/testify/mock"
)
func TestGitversion(t *testing.T) {
@ -18,12 +16,9 @@ func TestGitversion(t *testing.T) {
ExpectedEnabled bool
ExpectedString string
Response string
CacheResponse string
CacheError error
HasGitversion bool
Template string
CommandError error
CacheTimeout int
}{
{Case: "GitVersion not installed"},
{Case: "GitVersion installed, no GitVersion.yml file", HasGitversion: true, Response: "Cannot find the .git directory"},
@ -35,61 +30,23 @@ func TestGitversion(t *testing.T) {
Response: "{ \"FullSemVer\": \"0.1.0\", \"SemVer\": \"number\" }",
Template: "{{ .SemVer }}",
},
{
Case: "Cache Version",
ExpectedEnabled: true,
ExpectedString: "number2",
HasGitversion: true,
CacheResponse: "{ \"FullSemVer\": \"0.1.2\", \"SemVer\": \"number2\" }",
Response: "{ \"FullSemVer\": \"0.1.0\", \"SemVer\": \"number\" }",
Template: "{{ .SemVer }}",
},
{
Case: "No Cache enabled",
ExpectedEnabled: true,
CacheTimeout: -1,
ExpectedString: "number",
HasGitversion: true,
CacheResponse: "{ \"FullSemVer\": \"0.1.2\", \"SemVer\": \"number2\" }",
Response: "{ \"FullSemVer\": \"0.1.0\", \"SemVer\": \"number\" }",
Template: "{{ .SemVer }}",
},
{
Case: "Command Error",
HasGitversion: true,
CommandError: errors.New("error"),
},
{
Case: "Bad cache",
ExpectedEnabled: true,
ExpectedString: "number",
HasGitversion: true,
CacheResponse: "{{",
Response: "{ \"FullSemVer\": \"0.1.0\", \"SemVer\": \"number\" }",
Template: "{{ .SemVer }}",
},
}
for _, tc := range cases {
env := new(mock.Environment)
cache := &cache_.Cache{}
env.On("HasCommand", "gitversion").Return(tc.HasGitversion)
env.On("Pwd").Return("test-dir")
env.On("Cache").Return(cache)
cache.On("Get", "test-dir").Return(tc.CacheResponse, len(tc.CacheResponse) != 0)
cache.On("Set", testify_.Anything, testify_.Anything, testify_.Anything)
env.On("RunCommand", "gitversion", []string{"-output", "json"}).Return(tc.Response, tc.CommandError)
if tc.CacheTimeout == 0 {
tc.CacheTimeout = 30
}
gitversion := &GitVersion{
env: env,
props: properties.Map{
properties.CacheTimeout: tc.CacheTimeout,
},
env: env,
props: properties.Map{},
}
if len(tc.Template) == 0 {
tc.Template = gitversion.Template()

View file

@ -22,7 +22,7 @@ type ipAPI struct {
func (i *ipAPI) Get() (*ipData, error) {
url := "https://api.ipify.org?format=json"
return http.Do[*ipData](&i.Request, url)
return http.Do[*ipData](&i.Request, url, nil)
}
type IPify struct {
@ -64,9 +64,8 @@ func (i *IPify) getResult() (string, error) {
func (i *IPify) Init(props properties.Properties, env runtime.Environment) {
request := &http.Request{
Env: env,
CacheTimeout: props.GetInt(properties.CacheTimeout, 30),
HTTPTimeout: props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout),
Env: env,
HTTPTimeout: props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout),
}
i.api = &ipAPI{

View file

@ -1,8 +1,6 @@
package segments
import (
"encoding/json"
"fmt"
"path/filepath"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
@ -52,44 +50,7 @@ func (k *Kubectl) Init(props properties.Properties, env runtime.Environment) {
k.env = env
}
func (k *Kubectl) setCacheValue(timeout int) {
if !k.dirty {
return
}
cachedData, _ := json.Marshal(k)
k.env.Cache().Set(kubectlCacheKey, string(cachedData), timeout)
}
func (k *Kubectl) restoreCacheValue() error {
if val, found := k.env.Cache().Get(kubectlCacheKey); found {
err := json.Unmarshal([]byte(val), k)
if err != nil {
k.env.Error(err)
return err
}
return nil
}
return fmt.Errorf("no data in cache")
}
func (k *Kubectl) Enabled() bool {
cacheTimeout := k.props.GetInt(properties.CacheTimeout, 0)
if cacheTimeout > 0 {
if err := k.restoreCacheValue(); err == nil {
return true
}
}
defer func() {
if cacheTimeout > 0 {
k.setCacheValue(cacheTimeout)
}
}()
parseKubeConfig := k.props.GetBool(ParseKubeConfig, true)
if parseKubeConfig {

View file

@ -197,23 +197,11 @@ func (l *language) hasLanguageFolders() bool {
// setVersion parses the version string returned by the command
func (l *language) setVersion() error {
var lastError error
cacheVersion := l.props.GetBool(CacheVersion, false)
for _, command := range l.commands {
var versionStr string
var err error
versionKey := fmt.Sprintf("%s_version", command.executable)
versionURL := fmt.Sprintf("%s_version_url", command.executable)
if versionStr, OK := l.env.Cache().Get(versionKey); OK {
version, _ := command.parse(versionStr)
l.version = *version
l.version.Executable = command.executable
l.version.URL, _ = l.env.Cache().Get(versionURL)
return nil
}
if command.getVersion == nil {
if !l.env.HasCommand(command.executable) {
lastError = errors.New(noVersion)
@ -248,12 +236,6 @@ func (l *language) setVersion() error {
l.buildVersionURL()
l.version.Executable = command.executable
if cacheVersion {
timeout := l.props.GetInt(properties.CacheTimeout, 1440)
l.env.Cache().Set(versionKey, versionStr, timeout)
l.env.Cache().Set(versionURL, l.version.URL, timeout)
}
return nil
}
if lastError != nil {

View file

@ -4,7 +4,6 @@ import (
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
@ -30,7 +29,6 @@ type languageArgs struct {
properties properties.Properties
matchesVersionFile matchesVersionFile
inHome bool
cachedVersion string
}
func (l *languageArgs) hasvalue(value string, list []string) bool {
@ -69,11 +67,6 @@ func bootStrapLanguageTest(args *languageArgs) *language {
Env: make(map[string]string),
})
c := &cache_.Cache{}
c.On("Get", testify_.Anything).Return(args.cachedVersion, len(args.cachedVersion) > 0)
c.On("Set", testify_.Anything, testify_.Anything, testify_.Anything)
env.On("Cache").Return(c)
if args.properties == nil {
args.properties = properties.Map{}
}
@ -542,31 +535,6 @@ func TestLanguageHyperlinkTemplatePropertyTakesPriority(t *testing.T) {
assert.Equal(t, "https://custom/url/template/1.3", lang.version.URL)
}
func TestLanguageEnabledCachedVersion(t *testing.T) {
props := properties.Map{
properties.FetchVersion: true,
}
args := &languageArgs{
commands: []*cmd{
{
executable: "unicorn",
args: []string{"--version"},
regex: "(?P<version>.*)",
},
},
extensions: []string{uni, corn},
enabledExtensions: []string{uni, corn},
enabledCommands: []string{"unicorn"},
version: universion,
cachedVersion: "1.3.37",
properties: props,
}
lang := bootStrapLanguageTest(args)
assert.True(t, lang.Enabled())
assert.Equal(t, "1.3.37", lang.Full, "cached unicorn version is available")
assert.Equal(t, "unicorn", lang.Executable, "cached version was found")
}
type mockedLanguageParams struct {
cmd string
versionParam string
@ -586,14 +554,10 @@ func getMockedLanguageEnv(params *mockedLanguageParams) (*mock.Environment, prop
})
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("Flags").Return(&runtime.Flags{})
props := properties.Map{
properties.FetchVersion: true,
}
c := &cache_.Cache{}
c.On("Get", testify_.Anything).Return("", false)
c.On("Set", testify_.Anything, testify_.Anything, testify_.Anything)
env.On("Cache").Return(c)
return env, props
}

View file

@ -68,7 +68,6 @@ func (d *LastFM) Template() string {
}
func (d *LastFM) getResult() (*lfmDataResponse, error) {
cacheTimeout := d.props.GetInt(properties.CacheTimeout, 0)
response := new(lfmDataResponse)
apikey := d.props.GetString(APIKey, ".")
@ -77,30 +76,16 @@ func (d *LastFM) getResult() (*lfmDataResponse, error) {
url := fmt.Sprintf("https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&api_key=%s&user=%s&format=json&limit=1", apikey, username)
if cacheTimeout > 0 {
val, found := d.env.Cache().Get(url)
if found {
err := json.Unmarshal([]byte(val), response)
if err != nil {
return nil, err
}
return response, nil
}
}
body, err := d.env.HTTPRequest(url, nil, httpTimeout)
if err != nil {
return new(lfmDataResponse), err
}
err = json.Unmarshal(body, &response)
if err != nil {
return new(lfmDataResponse), err
}
if cacheTimeout > 0 {
d.env.Cache().Set(url, string(body), cacheTimeout)
}
return response, nil
}

View file

@ -4,7 +4,6 @@ import (
"errors"
"testing"
cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
@ -82,25 +81,3 @@ func TestLFMSegmentSingle(t *testing.T) {
assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, o), tc.Case)
}
}
func TestLFMSegmentFromCache(t *testing.T) {
response := `{"recenttracks":{"track":[{"artist":{"mbid":"","#text":"C.Gambino"},"streamable":"0","name":"Automatic","date":{"uts":"1699350223","#text":"07 Nov 2023, 09:43"}}]}}`
expectedString := "\uF04D"
env := &mock.Environment{}
cache := &cache_.Cache{}
o := &LastFM{
props: properties.Map{
APIKey: "key",
Username: "KibbeWater",
properties.CacheTimeout: 1,
},
env: env,
}
cache.On("Get", LFMAPIURL).Return(response, true)
cache.On("Set").Return()
env.On("Cache").Return(cache)
assert.Nil(t, o.setStatus())
assert.Equal(t, expectedString, renderTemplate(env, o.Template(), o), "should return the cached response")
}

View file

@ -152,25 +152,6 @@ func (nba *Nba) Enabled() bool {
return true
}
// Returns an empty Game Data struct with the GameStatus set to NotFound
// Helpful for caching the fact that a game was not found for a team
func (nba *Nba) getGameNotFoundData() string {
return `{
"HomeTeam":"",
"AwayTeam":"",
"Time":"",
"GameDate":"",
"StartTimeUTC":"",
"GameStatus":4,
"HomeScore":0,
"AwayScore":0,
"HomeTeamWins":0,
"HomeTeamLosses":0,
"AwayTeamWins":0,
"AwayTeamLosses":0
}`
}
// parses through a set of games from the score endpoint and looks for props.team in away or home team
func (nba *Nba) findGameScoreByTeamTricode(games []Game, teamTricode string) (*Game, error) {
for _, game := range games {
@ -308,86 +289,15 @@ func (nba *Nba) getAvailableGameData(teamName string, httpTimeout int) (*NBAData
return nil, err
}
// Gets the data from the cache if it exists
func (nba *Nba) getCacheValue(key string) (*NBAData, error) {
if val, found := nba.env.Cache().Get(key); found {
var nbaData *NBAData
err := json.Unmarshal([]byte(val), &nbaData)
if err != nil {
return nil, err
}
return nbaData, nil
}
return nil, errors.New("no data in cache")
}
// Gets the data from the cache for a scheduled game if it exists
// Checks whether the game should have started and if so, removes the cache entry
func (nba *Nba) getCachedScheduleValue(key string) (*NBAData, error) {
data, err := nba.getCacheValue(key)
if err != nil {
return nil, errors.New("no data in cache")
}
// check if the game was previously not found and we should wait to check again
if data.GameStatus == NotFound {
return data, nil
}
// check if the current time is after the start time of the game
// if so, we need to refresh the data
startTime, err := time.Parse("2006-01-02T15:04:05Z", data.StartTimeUTC)
if err != nil {
return nil, err
}
if time.Now().UTC().After(startTime) {
// remove the cache entry
nba.env.Cache().Delete(key)
return nil, errors.New("game has already started")
}
return data, nil
}
func (nba *Nba) getResult() (*NBAData, error) {
teamName := nba.props.GetString(TeamName, "")
cachedScheduleKey := fmt.Sprintf("%s%s", teamName, "schedule")
cachedScoreKey := fmt.Sprintf("%s%s", teamName, "score")
httpTimeout := nba.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout)
// How often you want to query the API to get live score information, defaults to 2 minutes
cacheScoreTimeout := nba.props.GetInt(properties.CacheTimeout, 2)
// Cache the schedule information for a day so we don't call the API too often
cacheScheduleTimeout := nba.props.GetInt(properties.CacheTimeout, 1440)
// Cache the fact a game was not found for 30 minutes so we don't call the API too often
cacheNotFoundTimeout := nba.props.GetInt(properties.CacheTimeout, 30)
nba.env.Debug("validating cache data for " + teamName)
if cacheScheduleTimeout > 0 {
if data, err := nba.getCachedScheduleValue(cachedScheduleKey); err == nil {
return data, nil
}
}
if cacheScoreTimeout > 0 {
if data, err := nba.getCacheValue(cachedScoreKey); err == nil {
return data, nil
}
}
nba.env.Debug("fetching available data for " + teamName)
data, err := nba.getAvailableGameData(teamName, httpTimeout)
if err != nil {
// cache the fact that we didn't find a game yet for the day for 30m so we don't continuously ping the endpoints
nba.env.Cache().Set(cachedScheduleKey, nba.getGameNotFoundData(), cacheNotFoundTimeout)
nba.env.Error(errors.Join(err, fmt.Errorf("unable to get data for team %s", teamName)))
return nil, err
}
@ -398,19 +308,6 @@ func (nba *Nba) getResult() (*NBAData, error) {
return nil, err
}
if cacheScheduleTimeout > 0 && data.GameStatus == Scheduled {
// persist data for team in cache
cachedData, _ := json.Marshal(data)
nba.env.Cache().Set(cachedScheduleKey, string(cachedData), cacheScheduleTimeout)
}
// if the game is in progress or finished, we can cache the score
if cacheScoreTimeout > 0 && data.GameStatus == InProgress || data.GameStatus == Finished {
// persist data for team in cache
cachedData, _ := json.Marshal(data)
nba.env.Cache().Set(cachedScoreKey, string(cachedData), cacheScoreTimeout)
}
return data, nil
}

View file

@ -6,7 +6,6 @@ import (
"testing"
"time"
cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
@ -29,8 +28,6 @@ func TestNBASegment(t *testing.T) {
JSONResponse string
ExpectedString string
ExpectedEnabled bool
CacheTimeout int
CacheFoundFail bool
TeamName string
DaysOffset int
Error error
@ -77,9 +74,8 @@ func TestNBASegment(t *testing.T) {
for _, tc := range cases {
env := &mock.Environment{}
props := properties.Map{
properties.CacheTimeout: tc.CacheTimeout,
TeamName: tc.TeamName,
DaysOffset: tc.DaysOffset,
TeamName: tc.TeamName,
DaysOffset: tc.DaysOffset,
}
env.On("Error", testify_.Anything)
@ -101,16 +97,6 @@ func TestNBASegment(t *testing.T) {
env: env,
}
cachedScheduleKey := fmt.Sprintf("%s%s", tc.TeamName, "schedule")
cachedScoreKey := fmt.Sprintf("%s%s", tc.TeamName, "score")
cache := &cache_.Cache{}
cache.On("Get", cachedScheduleKey).Return(nba.getGameNotFoundData(), tc.CacheFoundFail)
cache.On("Get", cachedScoreKey).Return(nba.getGameNotFoundData(), tc.CacheFoundFail)
cache.On("Set", cachedScheduleKey, nba.getGameNotFoundData(), tc.CacheTimeout).Return()
cache.On("Set", cachedScoreKey, nba.getGameNotFoundData(), tc.CacheTimeout).Return()
env.On("Cache").Return(cache)
enabled := nba.Enabled()
assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case)
if !enabled {

View file

@ -96,27 +96,9 @@ func (ns *Nightscout) getResult() (*NightscoutData, error) {
}
return result[0], nil
}
getCacheValue := func(key string) (*NightscoutData, error) {
val, found := ns.env.Cache().Get(key)
// we got something from the cache
if found {
if data, err := parseSingleElement([]byte(val)); err == nil {
return data, nil
}
}
return nil, errors.New("no data in cache")
}
url := ns.props.GetString(URL, "")
httpTimeout := ns.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout)
// natural and understood NS timeout is 5, anything else is unusual
cacheTimeout := ns.props.GetInt(properties.CacheTimeout, 5)
if cacheTimeout > 0 {
if data, err := getCacheValue(url); err == nil {
return data, nil
}
}
headers := ns.props.GetKeyValueMap(Headers, map[string]string{})
modifiers := func(request *http2.Request) {
@ -129,6 +111,7 @@ func (ns *Nightscout) getResult() (*NightscoutData, error) {
if err != nil {
return nil, err
}
var arr []*NightscoutData
err = json.Unmarshal(body, &arr)
if err != nil {
@ -140,10 +123,6 @@ func (ns *Nightscout) getResult() (*NightscoutData, error) {
return nil, err
}
if cacheTimeout > 0 {
// persist new sugars in cache
ns.env.Cache().Set(url, string(body), cacheTimeout)
}
return data, nil
}

View file

@ -4,7 +4,6 @@ import (
"errors"
"testing"
cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
@ -21,8 +20,6 @@ func TestNSSegment(t *testing.T) {
JSONResponse string
ExpectedString string
ExpectedEnabled bool
CacheTimeout int
CacheFoundFail bool
Template string
Error error
}{
@ -93,25 +90,6 @@ func TestNSSegment(t *testing.T) {
JSONResponse: "[]",
ExpectedEnabled: false,
},
{
Case: "DoubleDown 50 from cache",
JSONResponse: `
[{"_id":"619d6fa819696e8ded5b2206","sgv":50,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"DoubleDown","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, //nolint:lll
Template: "\ue2a1 {{.Sgv}}{{.TrendIcon}}",
ExpectedString: "\ue2a1 50↓↓",
ExpectedEnabled: true,
CacheTimeout: 10,
},
{
Case: "DoubleDown 50 from cache not found",
JSONResponse: `
[{"_id":"619d6fa819696e8ded5b2206","sgv":50,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"DoubleDown","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, //nolint:lll
Template: "\ue2a1 {{.Sgv}}{{.TrendIcon}}",
ExpectedString: "\ue2a1 50↓↓",
ExpectedEnabled: true,
CacheTimeout: 10,
CacheFoundFail: true,
},
{
Case: "Error parsing response",
JSONResponse: `
@ -119,7 +97,6 @@ func TestNSSegment(t *testing.T) {
Template: "\ue2a1 {{.Sgv}}{{.TrendIcon}}",
ExpectedString: "\ue2a1 50↓↓",
ExpectedEnabled: false,
CacheTimeout: 10,
},
{
Case: "Faulty template",
@ -128,24 +105,17 @@ func TestNSSegment(t *testing.T) {
Template: "\ue2a1 {{.Sgv}}{{.Burp}}",
ExpectedString: "<.Data.Burp>: can't evaluate field Burp in type template.Data",
ExpectedEnabled: true,
CacheTimeout: 10,
},
}
for _, tc := range cases {
env := &mock.Environment{}
props := properties.Map{
properties.CacheTimeout: tc.CacheTimeout,
URL: "FAKE",
Headers: map[string]string{"Fake-Header": "xxxxx"},
URL: "FAKE",
Headers: map[string]string{"Fake-Header": "xxxxx"},
}
cache := &cache_.Cache{}
cache.On("Get", FAKEAPIURL).Return(tc.JSONResponse, !tc.CacheFoundFail)
cache.On("Set", FAKEAPIURL, tc.JSONResponse, tc.CacheTimeout).Return()
env.On("HTTPRequest", FAKEAPIURL).Return([]byte(tc.JSONResponse), tc.Error)
env.On("Cache").Return(cache)
ns := &Nightscout{
props: props,

View file

@ -67,29 +67,14 @@ func (d *Owm) Template() string {
}
func (d *Owm) getResult() (*owmDataResponse, error) {
cacheTimeout := d.props.GetInt(properties.CacheTimeout, properties.DefaultCacheTimeout)
response := new(owmDataResponse)
if cacheTimeout > 0 {
val, found := d.env.Cache().Get(CacheKeyResponse)
if found {
err := json.Unmarshal([]byte(val), response)
if err != nil {
return nil, err
}
d.URL, _ = d.env.Cache().Get(CacheKeyURL)
return response, nil
}
}
apikey := properties.OneOf(d.props, ".", APIKey, "apiKey")
if len(apikey) == 0 {
apikey = d.env.Getenv(PoshOWMAPIKey)
}
location := d.props.GetString(Location, "De Bilt,NL")
location = url.QueryEscape(location)
if len(apikey) == 0 || len(location) == 0 {
@ -105,16 +90,12 @@ func (d *Owm) getResult() (*owmDataResponse, error) {
if err != nil {
return new(owmDataResponse), err
}
err = json.Unmarshal(body, &response)
if err != nil {
return new(owmDataResponse), err
}
if cacheTimeout > 0 {
// persist new forecasts in cache
d.env.Cache().Set(CacheKeyResponse, string(body), cacheTimeout)
d.env.Cache().Set(CacheKeyURL, d.URL, cacheTimeout)
}
return response, nil
}

View file

@ -6,7 +6,6 @@ import (
"net/url"
"testing"
cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
@ -251,50 +250,3 @@ func TestOWMSegmentIcons(t *testing.T) {
assert.Equal(t, expectedString, renderTemplate(env, "«{{.Weather}} ({{.Temperature}}{{.UnitIcon}})»({{.URL}})", o), tc.Case)
}
}
func TestOWMSegmentFromCacheByGeoName(t *testing.T) {
response := fmt.Sprintf(`{"weather":[{"icon":"%s"}],"main":{"temp":20}}`, "01d")
expectedString := fmt.Sprintf("%s (20°C)", "\ue30d")
env := &mock.Environment{}
cache := &cache_.Cache{}
o := &Owm{
props: properties.Map{
APIKey: "key",
Location: "AMSTERDAM,NL",
Units: "metric",
},
env: env,
}
cache.On("Get", "owm_response").Return(response, true)
cache.On("Get", "owm_url").Return("http://api.openweathermap.org/data/2.5/weather?q=AMSTERDAM,NL&units=metric&appid=key", true)
cache.On("Set").Return()
env.On("Cache").Return(cache)
assert.Nil(t, o.setStatus())
assert.Equal(t, expectedString, renderTemplate(env, o.Template(), o), "should return the cached response")
}
func TestOWMSegmentFromCacheWithHyperlinkByGeoName(t *testing.T) {
response := fmt.Sprintf(`{"weather":[{"icon":"%s"}],"main":{"temp":20}}`, "01d")
expectedString := fmt.Sprintf("«%s (20°C)»(http://api.openweathermap.org/data/2.5/weather?q=AMSTERDAM,NL&units=metric&appid=key)", "\ue30d")
env := &mock.Environment{}
cache := &cache_.Cache{}
o := &Owm{
props: properties.Map{
APIKey: "key",
Location: "AMSTERDAM,NL",
Units: "metric",
},
env: env,
}
cache.On("Get", "owm_response").Return(response, true)
cache.On("Get", "owm_url").Return("http://api.openweathermap.org/data/2.5/weather?q=AMSTERDAM,NL&units=metric&appid=key", true)
cache.On("Set").Return()
env.On("Cache").Return(cache)
assert.Nil(t, o.setStatus())
assert.Equal(t, expectedString, renderTemplate(env, "«{{.Weather}} ({{.Temperature}}{{.UnitIcon}})»({{.URL}})", o))
}

View file

@ -165,31 +165,6 @@ func (p *Pulumi) getPulumiAbout() {
return
}
cacheKey := "pulumi-" + p.Name + "-" + p.Stack + "-" + p.workspaceSHA1 + "-about"
getAboutCache := func(key string) (*backend, error) {
aboutBackend, OK := p.env.Cache().Get(key)
if (!OK || len(aboutBackend) == 0) || (OK && len(aboutBackend) == 0) {
return nil, fmt.Errorf("no data in cache")
}
var backend *backend
err := json.Unmarshal([]byte(aboutBackend), &backend)
if err != nil {
p.env.DebugF("unable to decode about cache: %s", aboutBackend)
p.env.Error(fmt.Errorf("pulling about cache decode error"))
return nil, err
}
return backend, nil
}
aboutBackend, err := getAboutCache(cacheKey)
if err == nil {
p.backend = *aboutBackend
return
}
aboutOutput, err := p.env.RunCommand("pulumi", "about", "--json")
if err != nil {
@ -213,8 +188,4 @@ func (p *Pulumi) getPulumiAbout() {
}
p.backend = *about.Backend
cacheTimeout := p.props.GetInt(properties.CacheTimeout, 43800)
jso, _ := json.Marshal(about.Backend)
p.env.Cache().Set(cacheKey, string(jso), cacheTimeout)
}

View file

@ -5,7 +5,6 @@ import (
"path/filepath"
"testing"
cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/stretchr/testify/assert"
@ -95,18 +94,6 @@ description: A Console App
WorkSpaceFile: `{ "stack": "1337" }`,
About: `{ "backend": { "url": "s3://test-pulumi-state-test", "user":"posh-user" } }`,
},
{
Case: "pulumi URL - cache",
ExpectedString: "\U000f0d46 1337 :: posh-user@s3://test-pulumi-state-test",
ExpectedEnabled: true,
HasCommand: true,
HasWorkspaceFolder: true,
FetchStack: true,
FetchAbout: true,
JSONConfig: `{ "name": "oh-my-posh" }`,
WorkSpaceFile: `{ "stack": "1337" }`,
AboutCache: `{ "url": "s3://test-pulumi-state-test", "user":"posh-user" }`,
},
// Error flows
{
Case: "pulumi file JSON error",
@ -134,19 +121,6 @@ description: A Console App
FetchAbout: true,
JSONConfig: `{ "name": "oh-my-posh" }`,
},
{
Case: "pulumi URL - cache error",
ExpectedString: "\U000f0d46 1337 :: posh-user@s3://test-pulumi-state-test-output",
ExpectedEnabled: true,
HasCommand: true,
HasWorkspaceFolder: true,
FetchStack: true,
FetchAbout: true,
JSONConfig: `{ "name": "oh-my-posh" }`,
WorkSpaceFile: `{ "stack": "1337" }`,
AboutCache: `{`,
About: `{ "backend": { "url": "s3://test-pulumi-state-test-output", "user":"posh-user" } }`,
},
{
Case: "pulumi URL - about error",
ExpectedString: "\U000f0d46 1337",
@ -211,12 +185,6 @@ description: A Console App
env.On("HasFilesInDir", filepath.Clean("/home/foobar/.pulumi/workspaces"), workspaceFile).Return(len(tc.WorkSpaceFile) > 0)
env.On("FileContent", filepath.Clean("/home/foobar/.pulumi/workspaces/"+workspaceFile)).Return(tc.WorkSpaceFile, nil)
cache := &cache_.Cache{}
cache.On("Get", "pulumi-oh-my-posh-1337-c62b7b6786c5c5a85896576e46a25d7c9f888e92-about").Return(tc.AboutCache, len(tc.AboutCache) > 0)
cache.On("Set", testify_.Anything, testify_.Anything, testify_.Anything)
env.On("Cache").Return(cache)
pulumi := &Pulumi{
env: env,
props: properties.Map{

View file

@ -2,20 +2,9 @@
package segments
import (
"encoding/json"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
)
const spotifyCacheKey = "spotify_music_player"
func (s *Spotify) Enabled() bool {
cacheTimeout := s.props.GetInt(properties.CacheTimeout, 0)
if cacheTimeout > 0 && s.getFromCache() {
return true
}
// Check if running
running := s.runAppleScriptCommand("application \"Spotify\" is running")
if running == "false" || running == "" {
@ -39,10 +28,6 @@ func (s *Spotify) Enabled() bool {
s.resolveIcon()
if cacheTimeout > 0 {
s.setCache(cacheTimeout)
}
return true
}
@ -50,28 +35,3 @@ func (s *Spotify) runAppleScriptCommand(command string) string {
val, _ := s.env.RunCommand("osascript", "-e", command)
return val
}
func (s *Spotify) getFromCache() bool {
str, found := s.env.Cache().Get(spotifyCacheKey)
if !found {
return false
}
var cachedMusicPlayer MusicPlayer
err := json.Unmarshal([]byte(str), &cachedMusicPlayer)
if err != nil {
return false
}
s.MusicPlayer = cachedMusicPlayer
return true
}
func (s *Spotify) setCache(cacheTimeout int) {
cache, err := json.Marshal(s.MusicPlayer)
if err != nil {
return
}
s.env.Cache().Set(spotifyCacheKey, string(cache), cacheTimeout)
}

View file

@ -136,9 +136,8 @@ func (s *Strava) Init(props properties.Properties, env runtime.Environment) {
AccessToken: s.props.GetString(properties.AccessToken, ""),
RefreshToken: s.props.GetString(properties.RefreshToken, ""),
Request: http.Request{
Env: env,
CacheTimeout: s.props.GetInt(properties.CacheTimeout, 30),
HTTPTimeout: s.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout),
Env: env,
HTTPTimeout: s.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout),
},
}

View file

@ -117,10 +117,6 @@ func (u *Unity) GetCSharpVersion() (version string, err error) {
}
func (u *Unity) GetCSharpVersionFromWeb(shortUnityVersion string) (version string, err error) {
if csharpVersion, found := u.env.Cache().Get(shortUnityVersion); found {
return csharpVersion, nil
}
url := fmt.Sprintf("https://docs.unity3d.com/%s/Documentation/Manual/CSharpCompiler.html", shortUnityVersion)
httpTimeout := u.props.GetInt(properties.HTTPTimeout, 2000)
@ -135,11 +131,9 @@ func (u *Unity) GetCSharpVersionFromWeb(shortUnityVersion string) (version strin
matches := regex.FindNamedRegexMatch(pattern, pageContent)
if matches != nil && matches["csharpVersion"] != "" {
csharpVersion := strings.TrimSuffix(matches["csharpVersion"], ".0")
u.env.Cache().Set(shortUnityVersion, csharpVersion, -1)
return csharpVersion, nil
}
u.env.Cache().Set(shortUnityVersion, "", -1)
return "", nil
}

View file

@ -2,13 +2,11 @@ package segments
import (
"errors"
"fmt"
"path/filepath"
"testing"
testify_ "github.com/stretchr/testify/mock"
cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
@ -16,17 +14,6 @@ import (
"github.com/stretchr/testify/assert"
)
type CacheGet struct {
key string
val string
found bool
}
type CacheSet struct {
key string
val string
}
type HTTPResponse struct {
body string
err error
@ -119,39 +106,16 @@ func TestUnitySegmentCSharpWebRequest(t *testing.T) {
Case string
ExpectedOutput string
VersionFileText string
CacheGet CacheGet
CacheSet CacheSet
ExpectedToBeEnabled bool
VersionFileExists bool
HTTPResponse HTTPResponse
}{
{
Case: "C# version cached",
Case: "C# version",
ExpectedOutput: "\ue721 2021.9.20 C# 10",
ExpectedToBeEnabled: true,
VersionFileExists: true,
VersionFileText: "m_EditorVersion: 2021.9.20f1\nm_EditorVersionWithRevision: 2021.9.20f1 (4016570cf34f)",
CacheGet: CacheGet{
key: "2021.9",
val: "C# 10",
found: true,
},
},
{
Case: "C# version not cached",
ExpectedOutput: "\ue721 2021.9.20 C# 10",
ExpectedToBeEnabled: true,
VersionFileExists: true,
VersionFileText: "m_EditorVersion: 2021.9.20f1\nm_EditorVersionWithRevision: 2021.9.20f1 (4016570cf34f)",
CacheGet: CacheGet{
key: "2021.9",
val: "",
found: false,
},
CacheSet: CacheSet{
key: "2021.9",
val: "C# 10",
},
HTTPResponse: HTTPResponse{
body: `<a href="https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10">C# 10.0</a>`,
err: nil,
@ -163,15 +127,6 @@ func TestUnitySegmentCSharpWebRequest(t *testing.T) {
ExpectedToBeEnabled: true,
VersionFileExists: true,
VersionFileText: "m_EditorVersion: 2021.9.20f1\nm_EditorVersionWithRevision: 2021.9.20f1 (4016570cf34f)",
CacheGet: CacheGet{
key: "2021.9",
val: "",
found: false,
},
CacheSet: CacheSet{
key: "2021.9",
val: "C# 10.1",
},
HTTPResponse: HTTPResponse{
body: `<a href="https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10-1">C# 10.1</a>`,
err: nil,
@ -183,15 +138,6 @@ func TestUnitySegmentCSharpWebRequest(t *testing.T) {
ExpectedToBeEnabled: true,
VersionFileExists: true,
VersionFileText: "m_EditorVersion: 2021.9.20f1\nm_EditorVersionWithRevision: 2021.9.20f1 (4016570cf34f)",
CacheGet: CacheGet{
key: "2021.9",
val: "",
found: false,
},
CacheSet: CacheSet{
key: "2021.9",
val: "",
},
HTTPResponse: HTTPResponse{
body: `<h1>Sorry... that page seems to be missing!</h1>`,
err: nil,
@ -203,11 +149,6 @@ func TestUnitySegmentCSharpWebRequest(t *testing.T) {
ExpectedToBeEnabled: true,
VersionFileExists: true,
VersionFileText: "m_EditorVersion: 2021.9.20f1\nm_EditorVersionWithRevision: 2021.9.20f1 (4016570cf34f)",
CacheGet: CacheGet{
key: "2021.9",
val: "",
found: false,
},
HTTPResponse: HTTPResponse{
body: "",
err: errors.New("FAIL"),
@ -235,12 +176,7 @@ func TestUnitySegmentCSharpWebRequest(t *testing.T) {
}
env.On("HasParentFilePath", "ProjectSettings", false).Return(projectDir, err)
cache := &cache_.Cache{}
cache.On("Get", tc.CacheGet.key).Return(tc.CacheGet.val, tc.CacheGet.found)
cache.On("Set", tc.CacheSet.key, tc.CacheSet.val, -1).Return()
env.On("Cache").Return(cache)
url := fmt.Sprintf("https://docs.unity3d.com/%s/Documentation/Manual/CSharpCompiler.html", tc.CacheGet.key)
url := "https://docs.unity3d.com/2021.9/Documentation/Manual/CSharpCompiler.html"
env.On("HTTPRequest", url).Return([]byte(tc.HTTPResponse.body), tc.HTTPResponse.err)
props := properties.Map{}

View file

@ -1,16 +1,13 @@
package segments
import (
"encoding/json"
"errors"
"github.com/jandedobbeleer/oh-my-posh/src/build"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/upgrade"
)
type UpgradeCache struct {
type upgradeData struct {
Latest string `json:"latest"`
Current string `json:"current"`
}
@ -22,11 +19,9 @@ type Upgrade struct {
// deprecated
Version string
UpgradeCache
upgradeData
}
const UPGRADECACHEKEY = "upgrade_segment"
func (u *Upgrade) Template() string {
return " \uf019 "
}
@ -38,59 +33,27 @@ func (u *Upgrade) Init(props properties.Properties, env runtime.Environment) {
func (u *Upgrade) Enabled() bool {
u.Current = build.Version
latest, err := u.cachedLatest(u.Current)
if err != nil {
latest, err = u.checkUpdate(u.Current)
}
latest, err := u.checkUpdate(u.Current)
if err != nil || u.Current == latest.Latest {
return false
}
u.UpgradeCache = *latest
u.upgradeData = *latest
u.Version = u.Latest
return true
}
func (u *Upgrade) cachedLatest(current string) (*UpgradeCache, error) {
data, ok := u.env.Cache().Get(UPGRADECACHEKEY)
if !ok {
return nil, errors.New("no cache data")
}
var cacheJSON UpgradeCache
err := json.Unmarshal([]byte(data), &cacheJSON)
if err != nil {
return nil, err // invalid cache data
}
if current != cacheJSON.Current {
return nil, errors.New("version changed, run the check again")
}
return &cacheJSON, nil
}
func (u *Upgrade) checkUpdate(current string) (*UpgradeCache, error) {
func (u *Upgrade) checkUpdate(current string) (*upgradeData, error) {
tag, err := upgrade.Latest(u.env)
if err != nil {
return nil, err
}
latest := tag[1:]
cacheData := &UpgradeCache{
Latest: latest,
return &upgradeData{
// strip leading v
Latest: tag[1:],
Current: current,
}
cacheJSON, err := json.Marshal(cacheData)
if err != nil {
return nil, err
}
oneWeek := 10080
cacheTimeout := u.props.GetInt(properties.CacheTimeout, oneWeek)
// update cache
u.env.Cache().Set(UPGRADECACHEKEY, string(cacheJSON), cacheTimeout)
return cacheData, nil
}, nil
}

View file

@ -6,23 +6,19 @@ import (
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/build"
cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/upgrade"
"github.com/alecthomas/assert"
testify_ "github.com/stretchr/testify/mock"
)
func TestUpgrade(t *testing.T) {
cases := []struct {
Case string
ExpectedEnabled bool
HasCache bool
CurrentVersion string
LatestVersion string
CachedVersion string
Error error
}{
{
@ -40,42 +36,21 @@ func TestUpgrade(t *testing.T) {
Case: "Error on update check",
Error: errors.New("error"),
},
{
Case: "On previous, from cache",
HasCache: true,
CurrentVersion: "1.0.2",
LatestVersion: "1.0.3",
CachedVersion: "1.0.2",
ExpectedEnabled: true,
},
{
Case: "On latest, version changed",
HasCache: true,
CurrentVersion: "1.0.2",
LatestVersion: "1.0.2",
CachedVersion: "1.0.1",
},
{
Case: "On previous, version changed",
HasCache: true,
CurrentVersion: "1.0.2",
LatestVersion: "1.0.3",
CachedVersion: "1.0.1",
ExpectedEnabled: true,
},
}
for _, tc := range cases {
env := new(mock.Environment)
cache := &cache_.Cache{}
env.On("Cache").Return(cache)
if len(tc.CachedVersion) == 0 {
tc.CachedVersion = tc.CurrentVersion
}
cacheData := fmt.Sprintf(`{"latest":"%s", "current": "%s"}`, tc.LatestVersion, tc.CachedVersion)
cache.On("Get", UPGRADECACHEKEY).Return(cacheData, tc.HasCache)
cache.On("Set", testify_.Anything, testify_.Anything, testify_.Anything)
build.Version = tc.CurrentVersion

View file

@ -40,17 +40,6 @@ func (w *Wakatime) setAPIData() error {
if err != nil {
return err
}
cacheTimeout := w.props.GetInt(properties.CacheTimeout, properties.DefaultCacheTimeout)
if cacheTimeout > 0 {
// check if data stored in cache
if val, found := w.env.Cache().Get(url); found {
err := json.Unmarshal([]byte(val), &w.wtData)
if err != nil {
return err
}
return nil
}
}
httpTimeout := w.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout)
@ -58,14 +47,12 @@ func (w *Wakatime) setAPIData() error {
if err != nil {
return err
}
err = json.Unmarshal(body, &w.wtData)
if err != nil {
return err
}
if cacheTimeout > 0 {
w.env.Cache().Set(url, string(body), cacheTimeout)
}
return nil
}

View file

@ -6,7 +6,6 @@ import (
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
@ -17,13 +16,11 @@ import (
func TestWTTrackedTime(t *testing.T) {
cases := []struct {
Case string
Seconds int
Expected string
Template string
CacheTimeout int
CacheFoundFail bool
Error error
Case string
Seconds int
Expected string
Template string
Error error
}{
{
Case: "nothing tracked",
@ -51,25 +48,15 @@ func TestWTTrackedTime(t *testing.T) {
Expected: "2h 45m",
},
{
Case: "cache 2h 45m",
Seconds: 9900,
Expected: "2h 45m",
CacheTimeout: 20,
Case: "no cache 2h 45m",
Seconds: 9900,
Expected: "2h 45m",
},
{
Case: "no cache 2h 45m",
Seconds: 9900,
Expected: "2h 45m",
CacheTimeout: 20,
CacheFoundFail: true,
},
{
Case: "api error",
Seconds: 2,
Expected: "0s",
CacheTimeout: 20,
CacheFoundFail: true,
Error: errors.New("api error"),
Case: "api error",
Seconds: 2,
Expected: "0s",
Error: errors.New("api error"),
},
}
@ -79,10 +66,6 @@ func TestWTTrackedTime(t *testing.T) {
env.On("HTTPRequest", FAKEAPIURL).Return([]byte(response), tc.Error)
mockedCache := &cache_.Cache{}
mockedCache.On("Get", FAKEAPIURL).Return(response, !tc.CacheFoundFail)
mockedCache.On("Set", FAKEAPIURL, response, tc.CacheTimeout).Return()
env.On("Cache").Return(mockedCache)
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("Flags").Return(&runtime.Flags{})
@ -92,8 +75,7 @@ func TestWTTrackedTime(t *testing.T) {
w := &Wakatime{
props: properties.Map{
properties.CacheTimeout: tc.CacheTimeout,
URL: FAKEAPIURL,
URL: FAKEAPIURL,
},
env: env,
}

View file

@ -233,9 +233,8 @@ func (w *Withings) Init(props properties.Properties, env runtime.Environment) {
AccessToken: w.props.GetString(properties.AccessToken, ""),
RefreshToken: w.props.GetString(properties.RefreshToken, ""),
Request: http.Request{
Env: env,
CacheTimeout: w.props.GetInt(properties.CacheTimeout, 30),
HTTPTimeout: w.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout),
Env: env,
HTTPTimeout: w.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout),
},
}

View file

@ -83,8 +83,7 @@ func Notice(env runtime.Environment, force bool) (string, bool) {
return "", false
}
oneWeek := 10080
env.Cache().Set(CACHEKEY, latest, oneWeek)
env.Cache().Set(CACHEKEY, latest, "1week")
version := fmt.Sprintf("v%s", build.Version)
if latest == version {