Files
orca/src/main/workspace-space-remote-scan.ts
Neil c4988fa81c refactor workspace lifecycle modules (#16334)
* refactor(workspaces): split lifecycle modules

* preserve workspace cleanup consent contract

* restore workspace delete shortcut hint in context menu view

* restore host-qualified visit recency and viewed-candidate predicate

* test(cleanup): pin the viewed-mark upgrade path and host-qualified visit reads

Two invariants a refactor broke in this PR, both silent:

- viewed marks are persisted, so gating `shouldPreserveCleanupInspection` on any
  newer field voids the grace period for every entry written by an older build
- visits are stamped under `${hostId}|${worktreeId}` whenever the host is known
  (the normal case, including 'local'), so a bare map[worktreeId] read misses
  every modern entry and yields 0, disabling the recent-visible-context blocker

Verified discriminating: reintroducing each bug fails exactly its own test.
2026-08-24 22:44:08 -07:00

108 lines
3.5 KiB
TypeScript

import type { DirEntry } from '../shared/filesystem-entry-types'
import type { Repo } from '../shared/repo-types'
import type { Worktree } from '../shared/worktree/types'
import type { WorkspaceSpaceWorktree } from '../shared/workspace-space-types'
import { compactWorkspaceSpaceItems } from '../shared/workspace-space-compaction'
import { scanWorkspaceSpaceEntryTree } from '../shared/workspace-space-entry-traversal'
import type { IFilesystemProvider } from './providers/types'
import {
WorkspaceSpaceScanCancelledError,
classifyWorkspaceSpaceError,
isWorkspaceSpaceAbortError,
isWorkspaceSpaceRelayMethodMissing,
throwIfWorkspaceSpaceScanAborted,
type AsyncLimiter
} from './workspace-space-scan-control'
import {
basenameWorkspaceFilesystemPath,
createScannedWorkspaceSpaceRow,
createUnavailableWorkspaceSpaceRow,
joinWorkspaceFilesystemPath,
toWorkspaceSpaceItem
} from './workspace-space-worktree-row'
const REMOTE_FS_CONCURRENCY = 10
async function scanRemoteEntry(
entryPath: string,
name: string,
provider: IFilesystemProvider,
signal?: AbortSignal
) {
return scanWorkspaceSpaceEntryTree<DirEntry>({
rootPath: entryPath,
rootName: name,
concurrency: REMOTE_FS_CONCURRENCY,
signal,
entryName: (entry) => entry.name,
joinPath: joinWorkspaceFilesystemPath,
classifyEntry: async (path, sourceEntry) => {
if (sourceEntry?.isSymlink) {
return { kind: 'symlink', sizeBytes: 0 }
}
const stats = await provider.stat(path)
throwIfWorkspaceSpaceScanAborted(signal)
if (stats.type === 'symlink') {
return { kind: 'symlink', sizeBytes: stats.size }
}
return stats.type === 'directory'
? { kind: 'directory', sizeBytes: stats.size }
: { kind: 'file', sizeBytes: stats.size }
},
readDirectory: (path) => provider.readDir(path),
checkCancelled: () => throwIfWorkspaceSpaceScanAborted(signal),
createCancellationError: () => new WorkspaceSpaceScanCancelledError(),
isCancellationError: (error) => error instanceof WorkspaceSpaceScanCancelledError
})
}
export async function scanRemoteWorkspaceSpaceWorktree(
repo: Repo,
worktree: Worktree,
scannedAt: number,
provider: IFilesystemProvider,
fallbackTraversalLimit: AsyncLimiter,
signal?: AbortSignal
): Promise<WorkspaceSpaceWorktree> {
try {
if (provider.scanWorkspaceSpace) {
try {
const scan = await provider.scanWorkspaceSpace(worktree.path, { signal })
return createScannedWorkspaceSpaceRow(repo, worktree, scannedAt, scan)
} catch (error) {
if (isWorkspaceSpaceAbortError(error)) {
throw new WorkspaceSpaceScanCancelledError()
}
if (!isWorkspaceSpaceRelayMethodMissing(error)) {
throw error
}
}
}
const root = await fallbackTraversalLimit(() =>
scanRemoteEntry(
worktree.path,
basenameWorkspaceFilesystemPath(worktree.path),
provider,
signal
)
)
return createScannedWorkspaceSpaceRow(repo, worktree, scannedAt, {
sizeBytes: root.sizeBytes,
skippedEntryCount: root.skippedEntryCount,
...compactWorkspaceSpaceItems((root.children ?? []).map(toWorkspaceSpaceItem))
})
} catch (error) {
if (error instanceof WorkspaceSpaceScanCancelledError) {
throw error
}
const classified = classifyWorkspaceSpaceError(error)
return createUnavailableWorkspaceSpaceRow(
repo,
worktree,
scannedAt,
classified.status,
classified.message
)
}
}