收敛 Pages 部署包读取路径

This commit is contained in:
ryan
2026-06-21 12:04:54 +08:00
parent 13a375e042
commit 6ed3c0c81f
10 changed files with 274 additions and 159 deletions
+16 -7
View File
@@ -31,13 +31,22 @@ var (
// Programmatic ingest API
var (
Ingest = ingest.Ingest
Remove = ingest.Remove
RemoveOwned = ingest.RemoveOwned
FindByHash = ingest.FindByHash
GetActiveUpload = ingest.GetActive
OpenStoredUpload = ingest.OpenActive
ActiveUploadHash = ingest.ActiveHash
Ingest = ingest.Ingest
Remove = ingest.Remove
RemoveOwned = ingest.RemoveOwned
FindByHash = ingest.FindByHash
GetActiveUpload = ingest.GetActive
OpenStoredUpload = ingest.OpenActiveObject
ActiveUploadHash = ingest.ActiveHash
ResolveLocalFile = ingest.ResolveLocalFile
IngestFromLocalPath = ingest.IngestFromLocalPath
)
type (
// OpenedUploadObject is the upload-domain view of a stored object stream.
OpenedUploadObject = ingest.OpenedObject
// LocalFileCandidateRequest describes filesystem locations that may host a legacy blob.
LocalFileCandidateRequest = ingest.LocalFileCandidateRequest
)
// Ingest policy constants
+10 -6
View File
@@ -11,7 +11,6 @@ import (
uploadcache "github.com/Rain-kl/Wavelet/internal/apps/upload/cache"
uploadstorage "github.com/Rain-kl/Wavelet/internal/apps/upload/storage"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/storage"
)
// GetActive loads an active upload record by ID through the upload metadata cache path.
@@ -22,17 +21,22 @@ func GetActive(ctx context.Context, uploadID uint64) (model.Upload, error) {
return uploadcache.GetUploadByID(ctx, uploadID)
}
// OpenActive opens the stored object for an active upload record.
func OpenActive(ctx context.Context, uploadID uint64) (*storage.Object, model.Upload, error) {
// OpenActiveObject opens the stored object for an active upload record.
func OpenActiveObject(ctx context.Context, uploadID uint64) (OpenedObject, error) {
record, err := GetActive(ctx, uploadID)
if err != nil {
return nil, model.Upload{}, err
return OpenedObject{}, err
}
obj, err := uploadstorage.OpenStoredObject(ctx, &record)
if err != nil {
return nil, model.Upload{}, err
return OpenedObject{}, err
}
return obj, record, nil
return OpenedObject{
Body: obj.Body,
ContentType: obj.ContentType,
ContentLength: obj.ContentLength,
Upload: record,
}, nil
}
// ActiveHash returns the SHA-256 hash recorded for an active upload.
+111
View File
@@ -0,0 +1,111 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package ingest
import (
"context"
"errors"
"os"
"path/filepath"
"strings"
"github.com/Rain-kl/Wavelet/internal/storage"
)
// LocalFileCandidateRequest describes filesystem locations that may host a legacy blob.
type LocalFileCandidateRequest struct {
StoredPath string
RelativePaths []string
}
// ResolveLocalFile returns the first existing regular file among managed candidate paths.
func ResolveLocalFile(ctx context.Context, req LocalFileCandidateRequest) (string, int64, error) {
for _, candidate := range buildLocalFileCandidates(ctx, req) {
info, err := os.Stat(candidate) //nolint:gosec // candidate is resolved from managed legacy metadata
if err != nil || info.IsDir() {
continue
}
return candidate, info.Size(), nil
}
return "", 0, os.ErrNotExist
}
// IngestFromLocalPath ingests a local regular file through the standard upload ingest path.
func IngestFromLocalPath(ctx context.Context, localPath string, req Request) (Result, error) {
localPath = strings.TrimSpace(localPath)
if localPath == "" {
return Result{}, errors.New("local path is required")
}
file, err := os.Open(localPath) //nolint:gosec // localPath is resolved from managed legacy metadata
if err != nil {
return Result{}, err
}
defer func() { _ = file.Close() }()
info, err := file.Stat()
if err != nil {
return Result{}, err
}
if info.IsDir() {
return Result{}, errors.New("local path must be a regular file")
}
if req.Size <= 0 {
req.Size = info.Size()
}
req.Reader = file
return Ingest(ctx, req)
}
func buildLocalFileCandidates(ctx context.Context, req LocalFileCandidateRequest) []string {
seen := make(map[string]struct{})
candidates := make([]string, 0, 8+len(req.RelativePaths)*4)
add := func(raw string) {
value := strings.TrimSpace(raw)
if value == "" {
return
}
if _, ok := seen[value]; ok {
return
}
seen[value] = struct{}{}
candidates = append(candidates, value)
}
storedPath := strings.TrimSpace(req.StoredPath)
add(storedPath)
if storedPath != "" {
add(filepath.Clean(storedPath))
add(strings.ReplaceAll(storedPath, "/data/data/", "/data/"))
add(strings.ReplaceAll(
filepath.Clean(storedPath),
string(filepath.Separator)+string(filepath.Separator),
string(filepath.Separator),
))
}
for _, relativePath := range req.RelativePaths {
add(relativePath)
}
for _, root := range localStorageRoots(ctx) {
if storedPath != "" && !filepath.IsAbs(storedPath) {
add(filepath.Join(root, storedPath))
}
for _, relativePath := range req.RelativePaths {
add(filepath.Join(root, relativePath))
}
}
return candidates
}
func localStorageRoots(ctx context.Context) []string {
cfg, err := storage.LoadConfig(ctx)
if err != nil {
return nil
}
root := strings.TrimSpace(cfg.Local.Root)
if root == "" {
return nil
}
return []string{root}
}
@@ -0,0 +1,36 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package ingest
import (
"context"
"os"
"path/filepath"
"testing"
)
func TestResolveLocalFileFindsStoredPath(t *testing.T) {
dir := t.TempDir()
artifactPath := filepath.Join(dir, "legacy.zip")
if err := os.WriteFile(artifactPath, []byte("legacy"), 0o644); err != nil {
t.Fatalf("write artifact: %v", err)
}
path, size, err := ResolveLocalFile(context.Background(), LocalFileCandidateRequest{
StoredPath: artifactPath,
})
if err != nil {
t.Fatalf("ResolveLocalFile failed: %v", err)
}
if path != artifactPath || size != int64(len("legacy")) {
t.Fatalf("unexpected resolve result: path=%q size=%d", path, size)
}
}
func TestIngestFromLocalPathRequiresPath(t *testing.T) {
_, err := IngestFromLocalPath(context.Background(), "", Request{})
if err == nil {
t.Fatal("expected error for empty local path")
}
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package ingest
import (
"io"
"github.com/Rain-kl/Wavelet/internal/model"
)
// OpenedObject is the upload-domain view of a stored object stream.
type OpenedObject struct {
Body io.ReadCloser
ContentType string
ContentLength int64
Upload model.Upload
}
// Close closes the object body when present.
func (o OpenedObject) Close() error {
if o.Body == nil {
return nil
}
return o.Body.Close()
}