package workspace import ( "os" "path/filepath" "codespace/internal/util" ) // Manager manages workspace lifecycle. type Manager interface { Create(id string) (*Workspace, error) Get(id string) (*Workspace, error) Delete(id string) 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 := os.RemoveAll(root); err != nil { return util.Wrap(util.CodeInternal, "failed to delete workspace", err) } return nil }