Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

554 lines
16 KiB
Go

package workspace
import (
"context"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
workspacefiles "github.com/tutti-os/tutti/packages/workspace/files"
)
type LocalFilesAdapter struct {
MaxSearchCandidates int
searchProvider localFileSearchProvider
}
func (LocalFilesAdapter) ListDirectory(
ctx context.Context,
root workspacefiles.WorkspaceRoot,
logicalPath workspacefiles.LogicalPath,
includeHidden bool,
) (workspacefiles.DirectoryListing, error) {
physicalPath, err := existingPhysicalPath(root, logicalPath)
if err != nil {
return workspacefiles.DirectoryListing{}, err
}
info, err := os.Stat(physicalPath)
if err != nil {
return workspacefiles.DirectoryListing{}, fileError(err, logicalPath)
}
if !info.IsDir() {
return workspacefiles.DirectoryListing{}, fmt.Errorf("%w: %s", workspacefiles.ErrInvalidEntryKind, logicalPath)
}
dirEntries, err := os.ReadDir(physicalPath)
if err != nil {
return workspacefiles.DirectoryListing{}, fileError(err, logicalPath)
}
entries := make([]workspacefiles.FileEntry, 0, len(dirEntries))
for _, dirEntry := range dirEntries {
if err := ctx.Err(); err != nil {
return workspacefiles.DirectoryListing{}, err
}
if !includeHidden && shouldHideWorkspaceEntry(physicalPath, dirEntry) {
continue
}
childLogicalPath, err := workspacefiles.NormalizeLogicalPathWithinRoot(
logicalPath.String()+"/"+dirEntry.Name(),
root.LogicalRoot,
)
if err != nil {
return workspacefiles.DirectoryListing{}, err
}
entry, err := localFileEntry(root, childLogicalPath)
if err != nil {
if errors.Is(err, workspacefiles.ErrPathEscapesRoot) || errors.Is(err, workspacefiles.ErrEntryNotFound) {
continue
}
return workspacefiles.DirectoryListing{}, err
}
entries = append(entries, entry)
}
sortFileEntries(entries)
return workspacefiles.DirectoryListing{
WorkspaceID: root.WorkspaceID,
Root: workspacefiles.NormalizeLogicalRoot(root.LogicalRoot),
DirectoryPath: logicalPath,
Entries: entries,
}, nil
}
func (LocalFilesAdapter) ShouldPrefetchDirectory(
root workspacefiles.WorkspaceRoot,
logicalPath workspacefiles.LogicalPath,
) bool {
return !isMacOSProtectedDirectory(root, logicalPath)
}
func (LocalFilesAdapter) CreateFile(
_ context.Context,
root workspacefiles.WorkspaceRoot,
logicalPath workspacefiles.LogicalPath,
) (workspacefiles.FileEntry, error) {
physicalPath, err := creatablePhysicalPath(root, logicalPath)
if err != nil {
return workspacefiles.FileEntry{}, err
}
file, err := os.OpenFile(physicalPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
if err != nil {
return workspacefiles.FileEntry{}, fileError(err, logicalPath)
}
if err := file.Close(); err != nil {
return workspacefiles.FileEntry{}, fmt.Errorf("close created workspace file: %w", err)
}
return localFileEntry(root, logicalPath)
}
func (LocalFilesAdapter) ReadFile(
_ context.Context,
root workspacefiles.WorkspaceRoot,
logicalPath workspacefiles.LogicalPath,
maxBytes int64,
) (workspacefiles.FileContent, error) {
physicalPath, err := existingPhysicalPath(root, logicalPath)
if err != nil {
return workspacefiles.FileContent{}, err
}
info, err := os.Stat(physicalPath)
if err != nil {
return workspacefiles.FileContent{}, fileError(err, logicalPath)
}
if info.IsDir() {
return workspacefiles.FileContent{}, fmt.Errorf("%w: %s", workspacefiles.ErrInvalidEntryKind, logicalPath)
}
if maxBytes > 0 && info.Size() > maxBytes {
return workspacefiles.FileContent{}, fmt.Errorf("%w: %s", workspacefiles.ErrFileTooLarge, logicalPath)
}
bytes, err := os.ReadFile(physicalPath)
if err != nil {
return workspacefiles.FileContent{}, fileError(err, logicalPath)
}
if maxBytes > 0 && int64(len(bytes)) > maxBytes {
return workspacefiles.FileContent{}, fmt.Errorf("%w: %s", workspacefiles.ErrFileTooLarge, logicalPath)
}
return workspacefiles.FileContent{
Path: logicalPath,
Name: workspacefiles.LogicalPathBase(logicalPath),
Bytes: bytes,
SizeBytes: int64(len(bytes)),
}, nil
}
func (LocalFilesAdapter) WriteTextFile(
_ context.Context,
root workspacefiles.WorkspaceRoot,
logicalPath workspacefiles.LogicalPath,
content string,
) (workspacefiles.FileEntry, error) {
physicalPath, err := existingPhysicalPath(root, logicalPath)
if err != nil {
return workspacefiles.FileEntry{}, err
}
info, err := os.Stat(physicalPath)
if err != nil {
return workspacefiles.FileEntry{}, fileError(err, logicalPath)
}
if info.IsDir() {
return workspacefiles.FileEntry{}, fmt.Errorf("%w: %s", workspacefiles.ErrInvalidEntryKind, logicalPath)
}
if err := os.WriteFile(physicalPath, []byte(content), 0o644); err != nil {
return workspacefiles.FileEntry{}, fileError(err, logicalPath)
}
return localFileEntry(root, logicalPath)
}
func (LocalFilesAdapter) CreateDirectory(
_ context.Context,
root workspacefiles.WorkspaceRoot,
logicalPath workspacefiles.LogicalPath,
) (workspacefiles.FileEntry, error) {
physicalPath, err := creatablePhysicalPath(root, logicalPath)
if err != nil {
return workspacefiles.FileEntry{}, err
}
if err := os.Mkdir(physicalPath, 0o755); err != nil {
return workspacefiles.FileEntry{}, fileError(err, logicalPath)
}
return localFileEntry(root, logicalPath)
}
func (LocalFilesAdapter) DeleteEntry(
_ context.Context,
root workspacefiles.WorkspaceRoot,
logicalPath workspacefiles.LogicalPath,
kind workspacefiles.EntryKind,
) error {
physicalPath, err := existingPhysicalPath(root, logicalPath)
if err != nil {
return err
}
info, err := os.Stat(physicalPath)
if err != nil {
return fileError(err, logicalPath)
}
switch kind {
case workspacefiles.EntryKindFile:
if info.IsDir() {
return fmt.Errorf("%w: %s", workspacefiles.ErrInvalidEntryKind, logicalPath)
}
case workspacefiles.EntryKindDirectory:
if !info.IsDir() {
return fmt.Errorf("%w: %s", workspacefiles.ErrInvalidEntryKind, logicalPath)
}
}
if err := os.RemoveAll(physicalPath); err != nil {
return fileError(err, logicalPath)
}
return nil
}
func (LocalFilesAdapter) MoveEntry(
_ context.Context,
root workspacefiles.WorkspaceRoot,
logicalPath workspacefiles.LogicalPath,
targetDirectoryPath workspacefiles.LogicalPath,
) (workspacefiles.FileEntry, error) {
sourcePhysicalPath, err := existingPhysicalPath(root, logicalPath)
if err != nil {
return workspacefiles.FileEntry{}, err
}
targetDirectoryPhysicalPath, err := existingPhysicalPath(root, targetDirectoryPath)
if err != nil {
return workspacefiles.FileEntry{}, err
}
targetDirectoryInfo, err := os.Stat(targetDirectoryPhysicalPath)
if err != nil {
return workspacefiles.FileEntry{}, fileError(err, targetDirectoryPath)
}
if !targetDirectoryInfo.IsDir() {
return workspacefiles.FileEntry{}, fmt.Errorf("%w: %s", workspacefiles.ErrInvalidEntryKind, targetDirectoryPath)
}
targetLogicalPath, err := workspacefiles.NormalizeLogicalPathWithinRoot(
targetDirectoryPath.String()+"/"+workspacefiles.LogicalPathBase(logicalPath),
root.LogicalRoot,
)
if err != nil {
return workspacefiles.FileEntry{}, err
}
if targetLogicalPath == logicalPath {
return localFileEntry(root, logicalPath)
}
targetPhysicalPath, err := creatablePhysicalPath(root, targetLogicalPath)
if err != nil {
return workspacefiles.FileEntry{}, err
}
if _, err := os.Stat(targetPhysicalPath); err == nil {
return workspacefiles.FileEntry{}, fmt.Errorf("%w: %s", workspacefiles.ErrEntryAlreadyExists, targetLogicalPath)
} else if !errors.Is(err, os.ErrNotExist) {
return workspacefiles.FileEntry{}, fileError(err, targetLogicalPath)
}
if err := os.Rename(sourcePhysicalPath, targetPhysicalPath); err != nil {
return workspacefiles.FileEntry{}, fileError(err, logicalPath)
}
return localFileEntry(root, targetLogicalPath)
}
func (LocalFilesAdapter) RenameEntry(
_ context.Context,
root workspacefiles.WorkspaceRoot,
logicalPath workspacefiles.LogicalPath,
newName string,
) (workspacefiles.FileEntry, error) {
sourcePhysicalPath, err := existingPhysicalPath(root, logicalPath)
if err != nil {
return workspacefiles.FileEntry{}, err
}
parentDirectoryPath := workspacefiles.LogicalPathDir(logicalPath)
targetLogicalPath, err := workspacefiles.NormalizeLogicalPathWithinRoot(
parentDirectoryPath.String()+"/"+strings.TrimSpace(newName),
root.LogicalRoot,
)
if err != nil {
return workspacefiles.FileEntry{}, err
}
if targetLogicalPath == logicalPath {
return localFileEntry(root, logicalPath)
}
targetPhysicalPath, err := creatablePhysicalPath(root, targetLogicalPath)
if err != nil {
return workspacefiles.FileEntry{}, err
}
if _, err := os.Stat(targetPhysicalPath); err == nil {
return workspacefiles.FileEntry{}, fmt.Errorf("%w: %s", workspacefiles.ErrEntryAlreadyExists, targetLogicalPath)
} else if !errors.Is(err, os.ErrNotExist) {
return workspacefiles.FileEntry{}, fileError(err, targetLogicalPath)
}
if err := os.Rename(sourcePhysicalPath, targetPhysicalPath); err != nil {
return workspacefiles.FileEntry{}, fileError(err, logicalPath)
}
return localFileEntry(root, targetLogicalPath)
}
func (LocalFilesAdapter) CopyEntry(
ctx context.Context,
root workspacefiles.WorkspaceRoot,
logicalPath workspacefiles.LogicalPath,
) (workspacefiles.FileEntry, error) {
sourcePhysicalPath, err := existingPhysicalPath(root, logicalPath)
if err != nil {
return workspacefiles.FileEntry{}, err
}
parentDirectoryPath := workspacefiles.LogicalPathDir(logicalPath)
parentPhysicalPath, err := existingPhysicalPath(root, parentDirectoryPath)
if err != nil {
return workspacefiles.FileEntry{}, err
}
existingNames, err := directoryEntryNames(parentPhysicalPath)
if err != nil {
return workspacefiles.FileEntry{}, err
}
copyName := duplicateEntryName(
workspacefiles.LogicalPathBase(logicalPath),
existingNames,
)
targetLogicalPath, err := workspacefiles.NormalizeLogicalPathWithinRoot(
parentDirectoryPath.String()+"/"+copyName,
root.LogicalRoot,
)
if err != nil {
return workspacefiles.FileEntry{}, err
}
targetPhysicalPath, err := creatablePhysicalPath(root, targetLogicalPath)
if err != nil {
return workspacefiles.FileEntry{}, err
}
sourceInfo, err := os.Stat(sourcePhysicalPath)
if err != nil {
return workspacefiles.FileEntry{}, fileError(err, logicalPath)
}
if sourceInfo.IsDir() {
if err := copyPhysicalDirectory(ctx, sourcePhysicalPath, targetPhysicalPath); err != nil {
return workspacefiles.FileEntry{}, fileError(err, targetLogicalPath)
}
} else if err := copyPhysicalFile(sourcePhysicalPath, targetPhysicalPath, sourceInfo.Mode()); err != nil {
return workspacefiles.FileEntry{}, fileError(err, targetLogicalPath)
}
return localFileEntry(root, targetLogicalPath)
}
func localFileEntry(
root workspacefiles.WorkspaceRoot,
logicalPath workspacefiles.LogicalPath,
) (workspacefiles.FileEntry, error) {
physicalPath, err := existingPhysicalPath(root, logicalPath)
if err != nil {
return workspacefiles.FileEntry{}, err
}
info, err := os.Stat(physicalPath)
if err != nil {
return workspacefiles.FileEntry{}, fileError(err, logicalPath)
}
kind := workspacefiles.EntryKindFile
var sizeBytes *int64
hasChildren := false
if info.IsDir() {
kind = workspacefiles.EntryKindDirectory
if isMacOSProtectedDirectory(root, logicalPath) {
hasChildren = true
} else {
hasChildren = directoryHasChildren(physicalPath)
}
} else if info.Mode().IsRegular() {
size := info.Size()
sizeBytes = &size
} else {
kind = workspacefiles.EntryKindUnknown
}
mtimeMs := info.ModTime().UnixMilli()
createdTimeMs, lastOpenedMs := localFileTimeMetadata(info)
return workspacefiles.FileEntry{
Path: logicalPath,
Name: workspacefiles.LogicalPathBase(logicalPath),
Kind: kind,
HasChildren: hasChildren,
SizeBytes: sizeBytes,
MtimeMs: &mtimeMs,
CreatedTimeMs: createdTimeMs,
LastOpenedMs: lastOpenedMs,
}, nil
}
func existingPhysicalPath(root workspacefiles.WorkspaceRoot, logicalPath workspacefiles.LogicalPath) (string, error) {
physicalPath, err := workspacefiles.JoinPhysicalPath(root, logicalPath)
if err != nil {
return "", err
}
if _, err := workspacefiles.EvaluatePhysicalPathWithinRoot(root.PhysicalRoot, physicalPath); err != nil {
return "", fileError(err, logicalPath)
}
return physicalPath, nil
}
func creatablePhysicalPath(root workspacefiles.WorkspaceRoot, logicalPath workspacefiles.LogicalPath) (string, error) {
physicalPath, err := workspacefiles.JoinPhysicalPath(root, logicalPath)
if err != nil {
return "", err
}
parent := filepath.Dir(physicalPath)
if _, err := workspacefiles.EvaluatePhysicalPathWithinRoot(root.PhysicalRoot, parent); err != nil {
return "", fileError(err, logicalPath)
}
return physicalPath, nil
}
func directoryHasChildren(physicalPath string) bool {
entries, err := os.ReadDir(physicalPath)
return err == nil && len(entries) > 0
}
func entryKind(mode fs.FileMode) workspacefiles.EntryKind {
if mode.IsDir() {
return workspacefiles.EntryKindDirectory
}
if mode.IsRegular() {
return workspacefiles.EntryKindFile
}
return workspacefiles.EntryKindUnknown
}
func sortFileEntries(entries []workspacefiles.FileEntry) {
sort.SliceStable(entries, func(i, j int) bool {
if entries[i].Kind != entries[j].Kind {
return entries[i].Kind == workspacefiles.EntryKindDirectory
}
return strings.ToLower(entries[i].Name) < strings.ToLower(entries[j].Name)
})
}
func fileError(err error, logicalPath workspacefiles.LogicalPath) error {
if err == nil {
return nil
}
if errors.Is(err, os.ErrExist) {
return fmt.Errorf("%w: %s", workspacefiles.ErrEntryAlreadyExists, logicalPath)
}
if errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("%w: %s", workspacefiles.ErrEntryNotFound, logicalPath)
}
if errors.Is(err, workspacefiles.ErrPathEscapesRoot) {
return err
}
return err
}
func directoryEntryNames(physicalDirectoryPath string) (map[string]struct{}, error) {
dirEntries, err := os.ReadDir(physicalDirectoryPath)
if err != nil {
return nil, err
}
names := make(map[string]struct{}, len(dirEntries))
for _, dirEntry := range dirEntries {
names[dirEntry.Name()] = struct{}{}
}
return names, nil
}
func duplicateEntryName(originalName string, existingNames map[string]struct{}) string {
extension := filepath.Ext(originalName)
baseName := strings.TrimSuffix(originalName, extension)
candidate := baseName + " copy" + extension
if _, exists := existingNames[candidate]; !exists {
return candidate
}
for index := 2; ; index++ {
candidate = fmt.Sprintf("%s copy %d%s", baseName, index, extension)
if _, exists := existingNames[candidate]; !exists {
return candidate
}
}
}
func copyPhysicalFile(sourcePhysicalPath, targetPhysicalPath string, mode fs.FileMode) error {
sourceFile, err := os.Open(sourcePhysicalPath)
if err != nil {
return err
}
defer sourceFile.Close()
targetFile, err := os.OpenFile(targetPhysicalPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, mode.Perm())
if err != nil {
return err
}
defer targetFile.Close()
if _, err := io.Copy(targetFile, sourceFile); err != nil {
_ = os.Remove(targetPhysicalPath)
return err
}
return nil
}
func copyPhysicalDirectory(ctx context.Context, sourcePhysicalPath, targetPhysicalPath string) error {
sourceInfo, err := os.Stat(sourcePhysicalPath)
if err != nil {
return err
}
if err := os.MkdirAll(targetPhysicalPath, sourceInfo.Mode().Perm()); err != nil {
return err
}
return filepath.WalkDir(sourcePhysicalPath, func(walkedPath string, dirEntry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if err := ctx.Err(); err != nil {
return err
}
relativePath, err := filepath.Rel(sourcePhysicalPath, walkedPath)
if err != nil {
return err
}
if relativePath == "." {
return nil
}
targetPath := filepath.Join(targetPhysicalPath, relativePath)
if dirEntry.IsDir() {
info, err := dirEntry.Info()
if err != nil {
return err
}
return os.MkdirAll(targetPath, info.Mode().Perm())
}
info, err := dirEntry.Info()
if err != nil {
return err
}
return copyPhysicalFile(walkedPath, targetPath, info.Mode())
})
}