~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
83 lines · 1.8 KB Raw History
package componentcore

import (
	"context"
	"slices"
	"strings"
	"sync"
	"time"
)

const (
	scopeCacheTTL = 10 * time.Second

	scopeRefreshMin = 500 * time.Millisecond

	scopeLookupTimeout = 10 * time.Second
)

type workspaceScope struct {
	static []string

	dynamic func(context.Context) []string

	mu sync.Mutex

	ctx context.Context

	cached []string

	at time.Time
}

func newWorkspaceScope(static []string, dynamic func(context.Context) []string) *workspaceScope {
	out := make([]string, 0, len(static))
	for _, workspace := range static {
		if workspace = strings.TrimSpace(workspace); workspace != "" && !slices.Contains(out, workspace) {
			out = append(out, workspace)
		}
	}
	return &workspaceScope{static: out, dynamic: dynamic}
}

func (s *workspaceScope) start(ctx context.Context) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.ctx, s.cached, s.at = ctx, nil, time.Time{}
}

func (s *workspaceScope) base() string {
	if len(s.static) == 0 {
		return DefaultWorkspace
	}
	return s.static[0]
}

func (s *workspaceScope) Base(workspace string) bool {
	return strings.TrimSpace(workspace) == s.base()
}

func (s *workspaceScope) Contains(workspace string) bool {
	workspace = strings.TrimSpace(workspace)
	if slices.Contains(s.static, workspace) {
		return true
	}
	if s.dynamic == nil {
		return false
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	age := time.Since(s.at)
	fresh := s.cached != nil && age <= scopeCacheTTL
	if fresh && slices.Contains(s.cached, workspace) {
		return true
	}
	if s.ctx == nil || s.ctx.Err() != nil || (fresh && age < scopeRefreshMin) {
		return slices.Contains(s.cached, workspace)
	}
	ctx, cancel := context.WithTimeout(s.ctx, scopeLookupTimeout)
	s.cached = append([]string{}, s.dynamic(ctx)...)
	cancel()
	s.at = time.Now()
	return slices.Contains(s.cached, workspace)
}