mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2026-08-24 10:14:12 -05:00
feat: implement async timeout for segments with cache support
Co-authored-by: JanDeDobbeleer <2492783+JanDeDobbeleer@users.noreply.github.com>
This commit is contained in:
co-authored by
JanDeDobbeleer
parent
6638dfc2df
commit
46eee055cb
Vendored
+80
@@ -0,0 +1,80 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AsyncSegmentData represents cached data for async segments
|
||||||
|
type AsyncSegmentData struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
Duration Duration `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncSegmentCache manages async segment caching
|
||||||
|
type AsyncSegmentCache struct {
|
||||||
|
cache Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAsyncSegmentCache creates a new async segment cache
|
||||||
|
func NewAsyncSegmentCache(cache Cache) *AsyncSegmentCache {
|
||||||
|
return &AsyncSegmentCache{
|
||||||
|
cache: cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSegmentData retrieves cached segment data
|
||||||
|
func (a *AsyncSegmentCache) GetSegmentData(segmentName, cacheKey string) (*AsyncSegmentData, bool) {
|
||||||
|
key := fmt.Sprintf("async_segment_%s_%s", segmentName, cacheKey)
|
||||||
|
data, found := a.cache.Get(key)
|
||||||
|
if !found {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var segmentData AsyncSegmentData
|
||||||
|
if err := json.Unmarshal([]byte(data), &segmentData); err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return &segmentData, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSegmentData stores segment data in cache
|
||||||
|
func (a *AsyncSegmentCache) SetSegmentData(segmentName, cacheKey string, data *AsyncSegmentData) {
|
||||||
|
key := fmt.Sprintf("async_segment_%s_%s", segmentName, cacheKey)
|
||||||
|
jsonData, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.cache.Set(key, string(jsonData), data.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSegmentData removes cached segment data
|
||||||
|
func (a *AsyncSegmentCache) DeleteSegmentData(segmentName, cacheKey string) {
|
||||||
|
key := fmt.Sprintf("async_segment_%s_%s", segmentName, cacheKey)
|
||||||
|
a.cache.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAsyncProcessRunning checks if an async process is currently running for a segment
|
||||||
|
func (a *AsyncSegmentCache) IsAsyncProcessRunning(segmentName, cacheKey string) bool {
|
||||||
|
key := fmt.Sprintf("async_process_%s_%s", segmentName, cacheKey)
|
||||||
|
_, found := a.cache.Get(key)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAsyncProcessRunning marks an async process as running
|
||||||
|
func (a *AsyncSegmentCache) SetAsyncProcessRunning(segmentName, cacheKey string) {
|
||||||
|
key := fmt.Sprintf("async_process_%s_%s", segmentName, cacheKey)
|
||||||
|
// Set a short TTL to prevent stale process markers
|
||||||
|
a.cache.Set(key, "running", Duration("5m"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAsyncProcessRunning removes the async process marker
|
||||||
|
func (a *AsyncSegmentCache) ClearAsyncProcessRunning(segmentName, cacheKey string) {
|
||||||
|
key := fmt.Sprintf("async_process_%s_%s", segmentName, cacheKey)
|
||||||
|
a.cache.Delete(key)
|
||||||
|
}
|
||||||
+78
-2
@@ -2,16 +2,21 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jandedobbeleer/oh-my-posh/src/cache"
|
"github.com/jandedobbeleer/oh-my-posh/src/cache"
|
||||||
|
"github.com/jandedobbeleer/oh-my-posh/src/config"
|
||||||
|
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
|
||||||
|
"github.com/jandedobbeleer/oh-my-posh/src/shell"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getCmd represents the get command
|
// getCmd represents the get command
|
||||||
var getCache = &cobra.Command{
|
var getCache = &cobra.Command{
|
||||||
Use: "cache [path|clear|edit]",
|
Use: "cache [path|clear|edit|refresh-segment]",
|
||||||
Short: "Interact with the oh-my-posh cache",
|
Short: "Interact with the oh-my-posh cache",
|
||||||
Long: `Interact with the oh-my-posh cache.
|
Long: `Interact with the oh-my-posh cache.
|
||||||
|
|
||||||
@@ -19,11 +24,13 @@ You can do the following:
|
|||||||
|
|
||||||
- path: list cache path
|
- path: list cache path
|
||||||
- clear: remove all cache values
|
- clear: remove all cache values
|
||||||
- edit: edit cache values`,
|
- edit: edit cache values
|
||||||
|
- refresh-segment: refresh a specific segment cache`,
|
||||||
ValidArgs: []string{
|
ValidArgs: []string{
|
||||||
"path",
|
"path",
|
||||||
"clear",
|
"clear",
|
||||||
"edit",
|
"edit",
|
||||||
|
"refresh-segment",
|
||||||
},
|
},
|
||||||
Args: NoArgsOrOneValidArg,
|
Args: NoArgsOrOneValidArg,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
@@ -48,10 +55,79 @@ You can do the following:
|
|||||||
case "edit":
|
case "edit":
|
||||||
cacheFilePath := filepath.Join(cache.Path(), cache.FileName)
|
cacheFilePath := filepath.Join(cache.Path(), cache.FileName)
|
||||||
exitcode = editFileWithEditor(cacheFilePath)
|
exitcode = editFileWithEditor(cacheFilePath)
|
||||||
|
case "refresh-segment":
|
||||||
|
refreshSegmentCache(cmd)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refreshSegmentCache refreshes the cache for a specific segment
|
||||||
|
func refreshSegmentCache(cmd *cobra.Command) {
|
||||||
|
segmentName, _ := cmd.Flags().GetString("segment")
|
||||||
|
cacheKey, _ := cmd.Flags().GetString("cache-key")
|
||||||
|
workingDir, _ := cmd.Flags().GetString("working-dir")
|
||||||
|
|
||||||
|
if segmentName == "" || cacheKey == "" || workingDir == "" {
|
||||||
|
fmt.Println("Error: segment, cache-key, and working-dir are required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change to the working directory
|
||||||
|
if err := os.Chdir(workingDir); err != nil {
|
||||||
|
fmt.Printf("Error changing directory: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create runtime environment
|
||||||
|
flags := &runtime.Flags{
|
||||||
|
SaveCache: true,
|
||||||
|
}
|
||||||
|
env := &runtime.Terminal{}
|
||||||
|
env.Init(flags)
|
||||||
|
defer env.Close()
|
||||||
|
|
||||||
|
// Load configuration
|
||||||
|
cfg, _ := config.Load(configFlag, shell.GENERIC, false)
|
||||||
|
|
||||||
|
// Find the segment configuration
|
||||||
|
var segment *config.Segment
|
||||||
|
for _, block := range cfg.Blocks {
|
||||||
|
for _, s := range block.Segments {
|
||||||
|
if s.Name() == segmentName || (s.Alias != "" && s.Alias == segmentName) {
|
||||||
|
segment = s
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if segment != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if segment == nil {
|
||||||
|
fmt.Printf("Error: segment %s not found in configuration\n", segmentName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the segment to get fresh data
|
||||||
|
segment.Execute(env)
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
if segment.Enabled {
|
||||||
|
asyncCache := cache.NewAsyncSegmentCache(env.Cache())
|
||||||
|
asyncData := &cache.AsyncSegmentData{
|
||||||
|
Text: segment.Text(),
|
||||||
|
Enabled: segment.Enabled,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Duration: cache.Duration("5m"), // Default 5 minutes
|
||||||
|
}
|
||||||
|
asyncCache.SetSegmentData(segmentName, cacheKey, asyncData)
|
||||||
|
fmt.Printf("Async cache refreshed for segment: %s\n", segmentName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
getCache.Flags().StringP("segment", "s", "", "segment name for refresh-segment command")
|
||||||
|
getCache.Flags().StringP("cache-key", "k", "", "cache key for refresh-segment command")
|
||||||
|
getCache.Flags().StringP("working-dir", "w", "", "working directory for refresh-segment command")
|
||||||
RootCmd.AddCommand(getCache)
|
RootCmd.AddCommand(getCache)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAsyncTimeoutFromConfig(t *testing.T) {
|
||||||
|
jsonConfig := `{
|
||||||
|
"version": 3,
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"type": "prompt",
|
||||||
|
"segments": [
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"style": "plain",
|
||||||
|
"async_timeout": 100,
|
||||||
|
"properties": {
|
||||||
|
"fetch_status": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
// Create a temporary file for the config
|
||||||
|
tmpFile := "/tmp/test_config.json"
|
||||||
|
err := os.WriteFile(tmpFile, []byte(jsonConfig), 0644)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer os.Remove(tmpFile)
|
||||||
|
|
||||||
|
// Load the config
|
||||||
|
cfg, _ := Load(tmpFile, "generic", false)
|
||||||
|
assert.NotNil(t, cfg)
|
||||||
|
|
||||||
|
// Verify the async timeout is loaded correctly
|
||||||
|
assert.Len(t, cfg.Blocks, 1)
|
||||||
|
assert.Len(t, cfg.Blocks[0].Segments, 1)
|
||||||
|
|
||||||
|
gitSegment := cfg.Blocks[0].Segments[0]
|
||||||
|
assert.Equal(t, "git", string(gitSegment.Type))
|
||||||
|
assert.Equal(t, 100*time.Nanosecond, gitSegment.AsyncTimeout)
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jandedobbeleer/oh-my-posh/src/cache"
|
||||||
|
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAsyncTimeoutConfiguration(t *testing.T) {
|
||||||
|
segment := &Segment{
|
||||||
|
Type: "git",
|
||||||
|
AsyncTimeout: 100 * time.Millisecond,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, 100*time.Millisecond, segment.AsyncTimeout)
|
||||||
|
assert.Equal(t, "git", string(segment.Type))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAsyncCacheKey(t *testing.T) {
|
||||||
|
env := &mock.Environment{}
|
||||||
|
env.On("Pwd").Return("/home/user/test")
|
||||||
|
env.On("Getenv", "GIT_DIR").Return("")
|
||||||
|
|
||||||
|
segment := &Segment{
|
||||||
|
Type: "git",
|
||||||
|
env: env,
|
||||||
|
}
|
||||||
|
|
||||||
|
key := segment.generateAsyncCacheKey()
|
||||||
|
expected := "git_/home/user/test"
|
||||||
|
assert.Equal(t, expected, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAsyncCache(t *testing.T) {
|
||||||
|
cacheFile := &cache.File{}
|
||||||
|
cacheFile.Init("/tmp/test_cache", false)
|
||||||
|
defer cacheFile.Close()
|
||||||
|
|
||||||
|
asyncCache := cache.NewAsyncSegmentCache(cacheFile)
|
||||||
|
|
||||||
|
// Test setting and getting async data
|
||||||
|
data := &cache.AsyncSegmentData{
|
||||||
|
Text: "test output",
|
||||||
|
Enabled: true,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Duration: cache.Duration("5m"),
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncCache.SetSegmentData("git", "test_key", data)
|
||||||
|
|
||||||
|
retrieved, found := asyncCache.GetSegmentData("git", "test_key")
|
||||||
|
assert.True(t, found)
|
||||||
|
assert.Equal(t, "test output", retrieved.Text)
|
||||||
|
assert.True(t, retrieved.Enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAsyncProcessMarker(t *testing.T) {
|
||||||
|
cacheFile := &cache.File{}
|
||||||
|
cacheFile.Init("/tmp/test_cache", false)
|
||||||
|
defer cacheFile.Close()
|
||||||
|
|
||||||
|
asyncCache := cache.NewAsyncSegmentCache(cacheFile)
|
||||||
|
|
||||||
|
// Test process marker
|
||||||
|
assert.False(t, asyncCache.IsAsyncProcessRunning("git", "test_key"))
|
||||||
|
|
||||||
|
asyncCache.SetAsyncProcessRunning("git", "test_key")
|
||||||
|
assert.True(t, asyncCache.IsAsyncProcessRunning("git", "test_key"))
|
||||||
|
|
||||||
|
asyncCache.ClearAsyncProcessRunning("git", "test_key")
|
||||||
|
assert.False(t, asyncCache.IsAsyncProcessRunning("git", "test_key"))
|
||||||
|
}
|
||||||
+176
-1
@@ -3,6 +3,8 @@ package config
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -67,6 +69,7 @@ type Segment struct {
|
|||||||
MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty" yaml:"min_width,omitempty"`
|
MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty" yaml:"min_width,omitempty"`
|
||||||
MaxWidth int `json:"max_width,omitempty" toml:"max_width,omitempty" yaml:"max_width,omitempty"`
|
MaxWidth int `json:"max_width,omitempty" toml:"max_width,omitempty" yaml:"max_width,omitempty"`
|
||||||
Timeout time.Duration `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty"`
|
Timeout time.Duration `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty"`
|
||||||
|
AsyncTimeout time.Duration `json:"async_timeout,omitempty" toml:"async_timeout,omitempty" yaml:"async_timeout,omitempty"`
|
||||||
Duration time.Duration `json:"-" toml:"-" yaml:"-"`
|
Duration time.Duration `json:"-" toml:"-" yaml:"-"`
|
||||||
NameLength int `json:"-" toml:"-" yaml:"-"`
|
NameLength int `json:"-" toml:"-" yaml:"-"`
|
||||||
Interactive bool `json:"interactive,omitempty" toml:"interactive,omitempty" yaml:"interactive,omitempty"`
|
Interactive bool `json:"interactive,omitempty" toml:"interactive,omitempty" yaml:"interactive,omitempty"`
|
||||||
@@ -124,8 +127,10 @@ func (segment *Segment) Execute(env runtime.Environment) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if segment.Timeout == 0 {
|
if segment.Timeout == 0 && segment.AsyncTimeout == 0 {
|
||||||
segment.Enabled = segment.writer.Enabled()
|
segment.Enabled = segment.writer.Enabled()
|
||||||
|
} else if segment.AsyncTimeout > 0 {
|
||||||
|
segment.executeWithAsyncTimeout()
|
||||||
} else {
|
} else {
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -147,6 +152,176 @@ func (segment *Segment) Execute(env runtime.Environment) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// executeWithAsyncTimeout executes the segment with async timeout behavior
|
||||||
|
func (segment *Segment) executeWithAsyncTimeout() {
|
||||||
|
// Generate a cache key for this segment
|
||||||
|
cacheKey := segment.generateAsyncCacheKey()
|
||||||
|
|
||||||
|
// Get async cache instance
|
||||||
|
asyncCache := segment.getAsyncCache()
|
||||||
|
if asyncCache == nil {
|
||||||
|
log.Debugf("async cache not available for segment: %s", segment.Name())
|
||||||
|
segment.Enabled = segment.writer.Enabled()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have cached data
|
||||||
|
if cachedData, found := asyncCache.GetSegmentData(segment.Name(), cacheKey); found {
|
||||||
|
// Use cached data if available
|
||||||
|
segment.Enabled = cachedData.Enabled
|
||||||
|
if cachedData.Enabled {
|
||||||
|
segment.writer.SetText(cachedData.Text)
|
||||||
|
}
|
||||||
|
log.Debugf("using cached data for segment: %s", segment.Name())
|
||||||
|
|
||||||
|
// Check if we should refresh the cache (if not already running)
|
||||||
|
if !asyncCache.IsAsyncProcessRunning(segment.Name(), cacheKey) {
|
||||||
|
go segment.refreshAsyncCache(cacheKey, asyncCache)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// No cached data, execute with timeout
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
segment.Enabled = segment.writer.Enabled()
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
// Completed before async timeout, cache the result
|
||||||
|
if segment.Enabled {
|
||||||
|
asyncData := &cache.AsyncSegmentData{
|
||||||
|
Text: segment.writer.Text(),
|
||||||
|
Enabled: segment.Enabled,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Duration: segment.getCacheDuration(),
|
||||||
|
}
|
||||||
|
asyncCache.SetSegmentData(segment.Name(), cacheKey, asyncData)
|
||||||
|
}
|
||||||
|
case <-time.After(segment.AsyncTimeout * time.Millisecond):
|
||||||
|
log.Debugf("async timeout after %dms for segment: %s", segment.AsyncTimeout, segment.Name())
|
||||||
|
|
||||||
|
// Start async process to update cache
|
||||||
|
go segment.refreshAsyncCache(cacheKey, asyncCache)
|
||||||
|
|
||||||
|
// Return without enabling segment (no cached data available)
|
||||||
|
segment.Enabled = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateAsyncCacheKey generates a unique cache key for async segments
|
||||||
|
func (segment *Segment) generateAsyncCacheKey() string {
|
||||||
|
// Include working directory and relevant segment properties
|
||||||
|
cwd := segment.env.Pwd()
|
||||||
|
segmentType := string(segment.Type)
|
||||||
|
|
||||||
|
// For git segments, include the git directory
|
||||||
|
if segmentType == "git" {
|
||||||
|
if gitDir := segment.env.Getenv("GIT_DIR"); gitDir != "" {
|
||||||
|
return fmt.Sprintf("%s_%s_%s", segmentType, cwd, gitDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s_%s", segmentType, cwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAsyncCache returns the async cache instance
|
||||||
|
func (segment *Segment) getAsyncCache() *cache.AsyncSegmentCache {
|
||||||
|
if segment.env.Cache() == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return cache.NewAsyncSegmentCache(segment.env.Cache())
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCacheDuration returns the cache duration for the segment
|
||||||
|
func (segment *Segment) getCacheDuration() cache.Duration {
|
||||||
|
if segment.Cache != nil {
|
||||||
|
return segment.Cache.Duration
|
||||||
|
}
|
||||||
|
// Default cache duration for async segments (5 minutes)
|
||||||
|
return cache.Duration("5m")
|
||||||
|
}
|
||||||
|
|
||||||
|
// refreshAsyncCache refreshes the cache in the background
|
||||||
|
func (segment *Segment) refreshAsyncCache(cacheKey string, asyncCache *cache.AsyncSegmentCache) {
|
||||||
|
segmentName := segment.Name()
|
||||||
|
|
||||||
|
// Mark as running to prevent multiple concurrent refreshes
|
||||||
|
asyncCache.SetAsyncProcessRunning(segmentName, cacheKey)
|
||||||
|
defer asyncCache.ClearAsyncProcessRunning(segmentName, cacheKey)
|
||||||
|
|
||||||
|
log.Debugf("refreshing async cache for segment: %s", segmentName)
|
||||||
|
|
||||||
|
// Create a fresh segment instance for background execution
|
||||||
|
// This is necessary because the original segment might be modified
|
||||||
|
if segment.env.Flags().Debug {
|
||||||
|
// For debug, run in the same process
|
||||||
|
segment.executeAsyncRefresh(cacheKey, asyncCache)
|
||||||
|
} else {
|
||||||
|
// For production, spawn a background process
|
||||||
|
segment.spawnAsyncRefreshProcess(cacheKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeAsyncRefresh executes the refresh in the current process
|
||||||
|
func (segment *Segment) executeAsyncRefresh(cacheKey string, asyncCache *cache.AsyncSegmentCache) {
|
||||||
|
// Execute the segment without timeout
|
||||||
|
enabled := segment.writer.Enabled()
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
asyncData := &cache.AsyncSegmentData{
|
||||||
|
Text: segment.writer.Text(),
|
||||||
|
Enabled: enabled,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Duration: segment.getCacheDuration(),
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncCache.SetSegmentData(segment.Name(), cacheKey, asyncData)
|
||||||
|
log.Debugf("async cache updated for segment: %s", segment.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// spawnAsyncRefreshProcess spawns a background process to refresh the cache
|
||||||
|
func (segment *Segment) spawnAsyncRefreshProcess(cacheKey string) {
|
||||||
|
// Get the current executable path
|
||||||
|
execPath, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to get executable path for async refresh: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare command arguments for async cache refresh
|
||||||
|
args := []string{
|
||||||
|
"cache", "refresh-segment",
|
||||||
|
"--segment", segment.Name(),
|
||||||
|
"--cache-key", cacheKey,
|
||||||
|
"--working-dir", segment.env.Pwd(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add segment-specific properties
|
||||||
|
if segment.Type == "git" {
|
||||||
|
args = append(args, "--segment-type", "git")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start background process
|
||||||
|
cmd := exec.Command(execPath, args...)
|
||||||
|
cmd.Dir = segment.env.Pwd()
|
||||||
|
|
||||||
|
// Set environment variables
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
|
||||||
|
// Start the process without waiting for it to complete
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to start async refresh process: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("started async refresh process for segment: %s", segment.Name())
|
||||||
|
}
|
||||||
|
|
||||||
func (segment *Segment) Render(index int, force bool) bool {
|
func (segment *Segment) Render(index int, force bool) bool {
|
||||||
if !segment.Enabled && !force {
|
if !segment.Enabled && !force {
|
||||||
return false
|
return false
|
||||||
|
|||||||
Reference in New Issue
Block a user