Fix server file browse flow

This commit is contained in:
npc0-hue
2026-08-26 22:00:23 +08:00
parent 861ee752ed
commit 6369a8099a
22 changed files with 627 additions and 132 deletions
+79 -1
View File
@@ -1,6 +1,7 @@
package service
import (
"context"
"encoding/json"
"errors"
"fmt"
@@ -19,6 +20,7 @@ const (
serverFileTransferChannel = "run-file-transfer"
serverFileMaxInlineEditBytes = 64 * 1024
serverFileDefaultDirectoryKey = "server-root"
serverFileBrowseWait = 4 * time.Second
)
type serverFileContext struct {
@@ -188,6 +190,82 @@ func (svc *CoreService) RefreshServerFileListForSession(sessionID string, reques
return domain.CopyServerFileListResult(domain.ServerFileListResult{ServerInstanceID: ctx.Instance.ID, PluginID: ctx.Plugin.ID, DirectoryKey: request.DirectoryKey, Path: request.Path, State: "pending", Entries: entries, Job: job, Reason: "目录刷新任务已派发到 Run。"}), nil
}
func (svc *CoreService) BrowseServerFilesForSession(ctx context.Context, sessionID string, request domain.ServerFileListRequest) (domain.ServerFileListResult, error) {
request = normalizeServerFileListRequest(request)
if request.IdempotencyKey == "" {
idempotencyKey, err := svc.serverFileBrowseIdempotencyKey(request)
if err != nil {
return domain.ServerFileListResult{}, err
}
request.IdempotencyKey = idempotencyKey
}
result, err := svc.RefreshServerFileListForSession(sessionID, request)
if err != nil || result.State != "pending" || result.Job.ID == "" {
return result, err
}
timer := time.NewTimer(serverFileBrowseWait)
defer timer.Stop()
for {
current, ready, err := svc.serverFileListResultFromJob(request, result, result.Job.ID)
if err != nil || ready {
return current, err
}
waiter := svc.registerRunJobWaiter(result.Job.RunEndpointID)
current, ready, err = svc.serverFileListResultFromJob(request, result, result.Job.ID)
if err != nil || ready {
svc.unregisterRunJobWaiter(result.Job.RunEndpointID, waiter)
return current, err
}
select {
case <-ctx.Done():
svc.unregisterRunJobWaiter(result.Job.RunEndpointID, waiter)
return domain.ServerFileListResult{}, ctx.Err()
case <-waiter:
case <-timer.C:
svc.unregisterRunJobWaiter(result.Job.RunEndpointID, waiter)
current.Reason = "正在读取目录。"
return current, nil
}
}
}
func (svc *CoreService) serverFileListResultFromJob(request domain.ServerFileListRequest, fallback domain.ServerFileListResult, jobID string) (domain.ServerFileListResult, bool, error) {
job, err := svc.store.Jobs().Get(jobID)
if err != nil {
return domain.ServerFileListResult{}, true, err
}
result := domain.CopyServerFileListResult(fallback)
result.Job = job
if !isTerminalJobState(job.State) {
return result, false, nil
}
result.RefreshedAt = job.TerminalAt
if job.State != domain.JobStateSucceeded || job.ExecutionResult.Kind != "file.list" {
result.State = "failed"
result.Reason = serverFileListJobFailureReason(job)
return result, true, nil
}
entries, err := serverFileEntriesFromRunList(job.ExecutionResult.Content, request.DirectoryKey, request.Path)
if err != nil {
result.State = "failed"
result.Reason = "Run 返回的文件列表无法解析。"
return result, true, nil
}
result.State = "ready"
result.Entries = filterServerFileEntries(entries, request.Query)
result.Reason = "目录读取完成。"
return result, true, nil
}
func (svc *CoreService) serverFileBrowseIdempotencyKey(request domain.ServerFileListRequest) (string, error) {
token, err := randomToken()
if err != nil {
return "", err
}
raw := strings.Join([]string{request.ServerInstanceID, request.DirectoryKey, request.Path, request.Query, strconv.FormatBool(request.Recursive), token}, ":")
return fmt.Sprintf("file-browse:%d", stableStringNumber(raw)), nil
}
func (svc *CoreService) ReadServerFileForSession(sessionID string, request domain.ServerFileReadRequest) (domain.FileOperationDispatchResult, error) {
if err := validator.ValidateServerFileReadRequest(request); err != nil {
return domain.FileOperationDispatchResult{}, err
@@ -545,7 +623,7 @@ func (svc *CoreService) putBrowserFileArtifact(artifact domain.Artifact, payload
if err := svc.artifactStore.PutPayload(artifact.ID, payload); err != nil {
return err
}
svc.artifactPayloads[artifact.ID] = domain.CopyBytes(payload)
svc.cacheArtifactPayload(artifact.ID, payload)
return nil
}