Files
cc-connect/core/projectstate.go
2026-06-02 23:14:41 +08:00

118 lines
2.8 KiB
Go

package core
import (
"encoding/json"
"log/slog"
"os"
"path/filepath"
"sync"
)
type projectStateData struct {
WorkDirOverride string `json:"work_dir_override,omitempty"`
WorkspaceDirOverrides map[string]string `json:"workspace_dir_overrides,omitempty"`
}
// ProjectStateStore persists lightweight runtime state for one project.
type ProjectStateStore struct {
mu sync.RWMutex
storePath string
state projectStateData
}
func NewProjectStateStore(path string) *ProjectStateStore {
ps := &ProjectStateStore{storePath: path}
if path != "" {
ps.load()
}
return ps
}
func (ps *ProjectStateStore) WorkDirOverride() string {
ps.mu.RLock()
defer ps.mu.RUnlock()
return ps.state.WorkDirOverride
}
func (ps *ProjectStateStore) SetWorkDirOverride(dir string) {
ps.mu.Lock()
defer ps.mu.Unlock()
ps.state.WorkDirOverride = dir
}
func (ps *ProjectStateStore) WorkspaceDirOverride(workspace string) string {
ps.mu.RLock()
defer ps.mu.RUnlock()
if ps.state.WorkspaceDirOverrides == nil {
return ""
}
return ps.state.WorkspaceDirOverrides[workspace]
}
func (ps *ProjectStateStore) SetWorkspaceDirOverride(workspace, dir string) {
ps.mu.Lock()
defer ps.mu.Unlock()
if ps.state.WorkspaceDirOverrides == nil {
ps.state.WorkspaceDirOverrides = make(map[string]string)
}
ps.state.WorkspaceDirOverrides[workspace] = dir
}
func (ps *ProjectStateStore) ClearWorkspaceDirOverride(workspace string) {
ps.mu.Lock()
defer ps.mu.Unlock()
if ps.state.WorkspaceDirOverrides == nil {
return
}
delete(ps.state.WorkspaceDirOverrides, workspace)
if len(ps.state.WorkspaceDirOverrides) == 0 {
ps.state.WorkspaceDirOverrides = nil
}
}
func (ps *ProjectStateStore) ClearWorkDirOverride() {
ps.SetWorkDirOverride("")
}
func (ps *ProjectStateStore) Save() {
ps.mu.RLock()
defer ps.mu.RUnlock()
ps.saveLocked()
}
func (ps *ProjectStateStore) saveLocked() {
if ps.storePath == "" {
return
}
data, err := json.MarshalIndent(ps.state, "", " ")
if err != nil {
slog.Error("project_state: failed to marshal", "error", err)
return
}
if err := os.MkdirAll(filepath.Dir(ps.storePath), 0o755); err != nil {
slog.Error("project_state: failed to create dir", "path", ps.storePath, "error", err)
return
}
if err := AtomicWriteFile(ps.storePath, data, 0o644); err != nil {
slog.Error("project_state: failed to write", "path", ps.storePath, "error", err)
}
}
func (ps *ProjectStateStore) load() {
data, err := os.ReadFile(ps.storePath)
if err != nil {
if !os.IsNotExist(err) {
slog.Error("project_state: failed to read", "path", ps.storePath, "error", err)
}
return
}
var state projectStateData
if err := json.Unmarshal(data, &state); err != nil {
slog.Error("project_state: failed to unmarshal", "path", ps.storePath, "error", err)
return
}
ps.state = state
}