This repository has been archived on 2026-07-17. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
codespace/internal/workspace/manager.go
T
2026-07-03 14:50:37 +08:00

121 lines
3.2 KiB
Go

package workspace
import (
"os"
"path/filepath"
"sort"
"codespace/internal/util"
)
// forceRemove chmods every file and directory under root to writable before
// removing the tree. It ignores chmod and walk errors so that partial or
// concurrent removals do not abort the delete.
func forceRemove(root string) error {
_ = filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil {
return nil
}
mode := os.FileMode(0o644)
if d.IsDir() {
mode = 0o755
}
_ = os.Chmod(path, mode)
return nil
})
return os.RemoveAll(root)
}
// Manager manages workspace lifecycle.
type Manager interface {
Create(id string) (*Workspace, error)
Get(id string) (*Workspace, error)
Delete(id string) error
List() ([]Workspace, error)
}
// LocalManager implements Manager using the local filesystem.
type LocalManager struct {
root string
}
// NewLocalManager creates a LocalManager rooted at root. The root is made
// absolute and cleaned if possible.
func NewLocalManager(root string) *LocalManager {
abs, err := filepath.Abs(root)
if err != nil {
abs = filepath.Clean(root)
}
return &LocalManager{root: abs}
}
// Create creates a workspace directory and returns the workspace.
func (m *LocalManager) Create(id string) (*Workspace, error) {
if !ValidID(id) {
return nil, util.New(util.CodeBadRequest, "invalid workspace id")
}
root := RootFor(m.root, id)
if err := os.MkdirAll(root, 0o755); err != nil {
return nil, util.Wrap(util.CodeInternal, "failed to create workspace", err)
}
return &Workspace{ID: id, Root: root}, nil
}
// Get returns the workspace if its directory exists.
func (m *LocalManager) Get(id string) (*Workspace, error) {
if !ValidID(id) {
return nil, util.New(util.CodeBadRequest, "invalid workspace id")
}
root := RootFor(m.root, id)
info, err := os.Stat(root)
if err != nil {
if os.IsNotExist(err) {
return nil, util.New(util.CodeNotFound, "workspace not found")
}
return nil, util.Wrap(util.CodeInternal, "failed to stat workspace", err)
}
if !info.IsDir() {
return nil, util.New(util.CodeNotFound, "workspace not found")
}
return &Workspace{ID: id, Root: root}, nil
}
// Delete removes the workspace directory recursively.
func (m *LocalManager) Delete(id string) error {
if !ValidID(id) {
return util.New(util.CodeBadRequest, "invalid workspace id")
}
root := RootFor(m.root, id)
if err := forceRemove(root); err != nil {
return util.Wrap(util.CodeInternal, "failed to delete workspace", err)
}
return nil
}
// List returns all workspaces under the manager root, sorted by ID.
// Only directories whose names pass ValidID are included.
func (m *LocalManager) List() ([]Workspace, error) {
if err := os.MkdirAll(m.root, 0o755); err != nil {
return nil, util.Wrap(util.CodeInternal, "failed to ensure workspace root", err)
}
entries, err := os.ReadDir(m.root)
if err != nil {
return nil, util.Wrap(util.CodeInternal, "failed to read workspace root", err)
}
var result []Workspace
for _, e := range entries {
if !e.IsDir() {
continue
}
if !ValidID(e.Name()) {
continue
}
result = append(result, Workspace{ID: e.Name(), Root: RootFor(m.root, e.Name())})
}
sort.Slice(result, func(i, j int) bool {
return result[i].ID < result[j].ID
})
return result, nil
}