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
+150 -13
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"sort"
@@ -22,6 +23,8 @@ type ArtifactBodyStore interface {
LoadTransfers() ([]domain.ArtifactTransferSession, error)
PutPayload(string, []byte) error
GetPayload(string) ([]byte, error)
ReadPayloadRange(string, int64, int) ([]byte, error)
CommitTransferPayload(domain.ArtifactTransferSession) error
}
type MemoryArtifactBodyStore struct {
@@ -73,6 +76,40 @@ func (store *MemoryArtifactBodyStore) GetPayload(artifactID string) ([]byte, err
return domain.CopyBytes(payload), nil
}
func (store *MemoryArtifactBodyStore) ReadPayloadRange(artifactID string, offset int64, length int) ([]byte, error) {
store.mu.Lock()
defer store.mu.Unlock()
payload, exists := store.payloads[artifactID]
if !exists {
return nil, repo.ErrNotFound
}
if offset < 0 || offset > int64(len(payload)) || length < 0 || int64(length) > int64(len(payload))-offset {
return nil, fmt.Errorf("artifact range is invalid")
}
return domain.CopyBytes(payload[int(offset) : int(offset)+length]), nil
}
func (store *MemoryArtifactBodyStore) CommitTransferPayload(session domain.ArtifactTransferSession) error {
store.mu.Lock()
defer store.mu.Unlock()
payload := make([]byte, 0, int(session.SizeBytes))
for index := 0; index < session.TotalChunks; index++ {
record, exists := session.ReceivedChunks[index]
if !exists {
return validationError("artifact transfer has missing chunks")
}
payload = append(payload, record.Payload...)
}
if int64(len(payload)) != session.SizeBytes {
return validationError("artifact transfer size does not match metadata")
}
if checksum := validator.BytesChecksum(payload); checksum != session.Checksum {
return validationError("artifact transfer checksum does not match metadata")
}
store.payloads[session.ArtifactID] = payload
return nil
}
type FileArtifactBodyStore struct {
mu sync.Mutex
rootDir string
@@ -101,12 +138,14 @@ func (store *FileArtifactBodyStore) SaveTransfer(session domain.ArtifactTransfer
}
manifest := domain.CopyArtifactTransferSession(session)
for index, record := range manifest.ReceivedChunks {
payload := domain.CopyBytes(record.Payload)
if len(payload) != record.SizeBytes || validator.BytesChecksum(payload) != record.Checksum {
return validationError("artifact chunk does not match durable manifest")
}
if err := writeAtomicFile(filepath.Join(dir, fmt.Sprintf("chunk-%08d.bin", index)), payload, 0o600); err != nil {
return err
if record.Payload != nil {
payload := domain.CopyBytes(record.Payload)
if len(payload) != record.SizeBytes || validator.BytesChecksum(payload) != record.Checksum {
return validationError("artifact chunk does not match durable manifest")
}
if err := writeAtomicFile(filepath.Join(dir, fmt.Sprintf("chunk-%08d.bin", index)), payload, 0o600); err != nil {
return err
}
}
record.Payload = nil
manifest.ReceivedChunks[index] = record
@@ -148,14 +187,11 @@ func (store *FileArtifactBodyStore) LoadTransfers() ([]domain.ArtifactTransferSe
return nil, fmt.Errorf("artifact transfer manifest identity mismatch")
}
for index, record := range session.ReceivedChunks {
payload, err := os.ReadFile(filepath.Join(dir, fmt.Sprintf("chunk-%08d.bin", index)))
if err != nil {
return nil, fmt.Errorf("read artifact transfer chunk: %w", err)
chunkPath := filepath.Join(dir, fmt.Sprintf("chunk-%08d.bin", index))
if _, err := os.Stat(chunkPath); err != nil {
return nil, fmt.Errorf("stat artifact transfer chunk: %w", err)
}
if len(payload) != record.SizeBytes || validator.BytesChecksum(payload) != record.Checksum {
return nil, validationError("durable artifact chunk checksum mismatch")
}
record.Payload = payload
record.Payload = nil
session.ReceivedChunks[index] = record
}
out = append(out, domain.CopyArtifactTransferSession(session))
@@ -182,6 +218,107 @@ func (store *FileArtifactBodyStore) GetPayload(artifactID string) ([]byte, error
return payload, nil
}
func (store *FileArtifactBodyStore) ReadPayloadRange(artifactID string, offset int64, length int) ([]byte, error) {
if offset < 0 || length < 0 {
return nil, fmt.Errorf("artifact range is invalid")
}
store.mu.Lock()
defer store.mu.Unlock()
file, err := os.Open(store.payloadPath(artifactID))
if errors.Is(err, os.ErrNotExist) {
return nil, repo.ErrNotFound
}
if err != nil {
return nil, fmt.Errorf("open artifact payload: %w", err)
}
defer file.Close()
payload := make([]byte, length)
read, err := file.ReadAt(payload, offset)
if err != nil && !(errors.Is(err, io.ErrUnexpectedEOF) && read == length) {
return nil, fmt.Errorf("read artifact payload range: %w", err)
}
if read != length {
return nil, fmt.Errorf("artifact payload range is shorter than requested")
}
return payload, nil
}
func (store *FileArtifactBodyStore) CommitTransferPayload(session domain.ArtifactTransferSession) error {
store.mu.Lock()
defer store.mu.Unlock()
transferDir := store.transferDir(session.TransferID)
payloadPath := store.payloadPath(session.ArtifactID)
if err := os.MkdirAll(filepath.Dir(payloadPath), 0o700); err != nil {
return fmt.Errorf("create artifact payload directory: %w", err)
}
tmp := payloadPath + ".tmp"
out, err := os.OpenFile(tmp, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600)
if err != nil {
return fmt.Errorf("open artifact payload temporary file: %w", err)
}
hash := sha256.New()
written := int64(0)
for index := 0; index < session.TotalChunks; index++ {
record, exists := session.ReceivedChunks[index]
if !exists {
_ = out.Close()
_ = os.Remove(tmp)
return validationError("artifact transfer has missing chunks")
}
chunkPath := filepath.Join(transferDir, fmt.Sprintf("chunk-%08d.bin", index))
chunk, err := os.Open(chunkPath)
if err != nil {
_ = out.Close()
_ = os.Remove(tmp)
return fmt.Errorf("open artifact transfer chunk: %w", err)
}
chunkHash := sha256.New()
count, copyErr := io.Copy(io.MultiWriter(out, hash, chunkHash), chunk)
closeErr := chunk.Close()
if copyErr != nil {
_ = out.Close()
_ = os.Remove(tmp)
return fmt.Errorf("copy artifact transfer chunk: %w", copyErr)
}
if closeErr != nil {
_ = out.Close()
_ = os.Remove(tmp)
return fmt.Errorf("close artifact transfer chunk: %w", closeErr)
}
if int(count) != record.SizeBytes || "sha256:"+hex.EncodeToString(chunkHash.Sum(nil)) != record.Checksum {
_ = out.Close()
_ = os.Remove(tmp)
return validationError("durable artifact chunk checksum mismatch")
}
written += count
}
if written != session.SizeBytes {
_ = out.Close()
_ = os.Remove(tmp)
return validationError("artifact transfer size does not match metadata")
}
if checksum := "sha256:" + hex.EncodeToString(hash.Sum(nil)); checksum != session.Checksum {
_ = out.Close()
_ = os.Remove(tmp)
return validationError("artifact transfer checksum does not match metadata")
}
if err := out.Sync(); err != nil {
_ = out.Close()
_ = os.Remove(tmp)
return fmt.Errorf("sync artifact payload: %w", err)
}
if err := out.Close(); err != nil {
_ = os.Remove(tmp)
return fmt.Errorf("close artifact payload: %w", err)
}
if err := os.Rename(tmp, payloadPath); err != nil {
_ = os.Remove(tmp)
return fmt.Errorf("commit artifact payload: %w", err)
}
return nil
}
func (store *FileArtifactBodyStore) transferDir(transferID string) string {
return filepath.Join(store.rootDir, "transfers", stableStorageKey(transferID))
}