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

228 lines
6.9 KiB
Go

//go:build darwin
package workspace
import (
"bufio"
"context"
"io"
"os/exec"
"path/filepath"
"sort"
"strings"
"time"
workspacefiles "github.com/tutti-os/tutti/packages/workspace/files"
)
// mdlsPathBatchSize keeps the argv for each metadata lookup comfortably below
// the platform limit while still avoiding one process per candidate.
const mdlsPathBatchSize = 200
// recentLookupTimeout bounds the external Spotlight calls so a slow index never
// stalls the picker.
const recentLookupTimeout = 4 * time.Second
// finderRecentSpotlightQuery mirrors Finder's Recents scope: files that
// LaunchServices has recorded as opened, excluding folders and incomplete
// Safari downloads. Finder keeps older opened documents in Recents, so this
// intentionally has no rolling date window.
const finderRecentSpotlightQuery = "kMDItemLastUsedDate == * && kMDItemContentTypeTree != 'public.folder' && kMDItemFSName != '*.download'cd"
// mdlsLastUsedDateLayout matches `mdls -name kMDItemLastUsedDate` output, e.g.
// "kMDItemLastUsedDate = 2026-06-17 06:35:10 +0000".
const mdlsLastUsedDateLayout = "2006-01-02 15:04:05 -0700"
// ListRecent enumerates the workspace's recently accessed files (most-recent
// first) via Spotlight, scoped to the workspace root so results never escape it.
// Folders are excluded to match Finder's "Recents".
func (LocalFilesAdapter) ListRecent(
ctx context.Context,
root workspacefiles.WorkspaceRoot,
limit int,
) (workspacefiles.DirectoryListing, error) {
limit = workspacefiles.NormalizeRecentLimit(limit)
rootPath := strings.TrimSpace(root.PhysicalRoot)
listing := workspacefiles.DirectoryListing{
WorkspaceID: root.WorkspaceID,
Root: workspacefiles.NormalizeLogicalRoot(root.LogicalRoot),
DirectoryPath: workspacefiles.NormalizeLogicalRoot(root.LogicalRoot),
Entries: []workspacefiles.FileEntry{},
}
if rootPath == "" {
return listing, nil
}
lookupCtx, cancel := context.WithTimeout(ctx, recentLookupTimeout)
defer cancel()
candidates, err := spotlightRecentCandidates(lookupCtx, rootPath)
if err != nil || len(candidates) == 0 {
// A Spotlight failure (index disabled, sandboxed, etc.) degrades to an
// empty recent list rather than failing the whole picker.
return listing, nil
}
dated := spotlightLastUsedDates(lookupCtx, candidates)
sort.SliceStable(dated, func(left, right int) bool {
return dated[left].lastUsed.After(dated[right].lastUsed)
})
entries := make([]workspacefiles.FileEntry, 0, limit)
for _, candidate := range dated {
if len(entries) >= limit {
break
}
logicalPath, ok := logicalPathWithinRoot(root, candidate.physicalPath)
if !ok {
continue
}
// Skip the workspace root itself; it is reachable via the "个人" entry.
if workspacefiles.IsLogicalRoot(logicalPath, root.LogicalRoot) {
continue
}
entry, err := localFileEntry(root, logicalPath)
if err != nil {
continue
}
// Recents shows files only, matching Finder. The Spotlight query already
// drops `public.folder`, but packages (.app, etc.) stat as directories;
// skip anything that resolves to a directory so it never renders as an
// expandable folder in the picker.
if entry.Kind == workspacefiles.EntryKindDirectory {
continue
}
lastUsedMs := candidate.lastUsed.UnixMilli()
entry.LastOpenedMs = &lastUsedMs
entries = append(entries, entry)
}
listing.Entries = entries
return listing, nil
}
type recentCandidate struct {
physicalPath string
lastUsed time.Time
}
// spotlightRecentCandidates returns Finder-compatible recent, non-folder paths
// under rootPath. Spotlight does not rank these results by last-used date, so
// every candidate must survive until spotlightLastUsedDates sorts them.
func spotlightRecentCandidates(ctx context.Context, rootPath string) ([]string, error) {
cmd := exec.CommandContext(
ctx,
"mdfind",
"-onlyin", rootPath,
finderRecentSpotlightQuery,
)
output, err := cmd.Output()
if err != nil {
return nil, err
}
return parseSpotlightRecentCandidates(strings.NewReader(string(output)))
}
func parseSpotlightRecentCandidates(reader io.Reader) ([]string, error) {
paths := make([]string, 0)
scanner := bufio.NewScanner(reader)
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
for scanner.Scan() {
path := strings.TrimSpace(scanner.Text())
if path == "" {
continue
}
paths = append(paths, path)
}
if err := scanner.Err(); err != nil {
return nil, err
}
return paths, nil
}
// spotlightLastUsedDates reads kMDItemLastUsedDate for each path via a single
// `mdls` call. Output is one line per input path, in input order; entries
// without a parseable date are dropped.
func spotlightLastUsedDates(ctx context.Context, paths []string) []recentCandidate {
if len(paths) == 0 {
return nil
}
dated := make([]recentCandidate, 0, len(paths))
for start := 0; start < len(paths); start += mdlsPathBatchSize {
end := min(start+mdlsPathBatchSize, len(paths))
dated = append(dated, spotlightLastUsedDatesBatch(ctx, paths[start:end])...)
if ctx.Err() != nil {
break
}
}
return dated
}
func spotlightLastUsedDatesBatch(ctx context.Context, paths []string) []recentCandidate {
args := append([]string{"-name", "kMDItemLastUsedDate"}, paths...)
output, err := exec.CommandContext(ctx, "mdls", args...).Output()
if err != nil {
return nil
}
dated := make([]recentCandidate, 0, len(paths))
index := 0
scanner := bufio.NewScanner(strings.NewReader(string(output)))
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
for scanner.Scan() {
if index >= len(paths) {
break
}
line := scanner.Text()
value, ok := parseMdlsValue(line)
path := paths[index]
index++
if !ok {
continue
}
lastUsed, parseErr := time.Parse(mdlsLastUsedDateLayout, value)
if parseErr != nil {
continue
}
dated = append(dated, recentCandidate{physicalPath: path, lastUsed: lastUsed})
}
return dated
}
// parseMdlsValue extracts the value from an `mdls` line such as
// "kMDItemLastUsedDate = 2026-06-17 06:35:10 +0000". Returns ok=false for null
// values or lines without an assignment.
func parseMdlsValue(line string) (string, bool) {
separator := strings.Index(line, " = ")
if separator < 0 {
return "", false
}
value := strings.TrimSpace(line[separator+len(" = "):])
if value == "" || value == "(null)" {
return "", false
}
return value, true
}
// logicalPathWithinRoot converts an absolute physical path into a logical path
// scoped to root, returning ok=false when the path escapes the root.
func logicalPathWithinRoot(
root workspacefiles.WorkspaceRoot,
physicalPath string,
) (workspacefiles.LogicalPath, bool) {
relative, err := filepath.Rel(root.PhysicalRoot, physicalPath)
if err != nil {
return "", false
}
relative = filepath.ToSlash(relative)
if relative == ".." || strings.HasPrefix(relative, "../") {
return "", false
}
logicalPath, err := workspacefiles.NormalizeLogicalPathWithinRoot(relative, root.LogicalRoot)
if err != nil {
return "", false
}
return logicalPath, true
}