mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-05 15:26:36 +08:00
收敛 Pages 部署包读取路径
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user