Files
OpenFlare/internal/apps/upload/ingest/local_file.go
T
ryan 33a1c32cf8 refactor(structure): group platform, infra, and shared packages
Reorganize internal packages into platform/infra/shared layers and update
imports, docs, and seed-count tests to match current system configs.
2026-07-24 15:41:59 +08:00

116 lines
2.9 KiB
Go

// 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/infra/objectstore"
)
// 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
}
// FromLocalPath ingests a local regular file through the standard upload ingest path.
func FromLocalPath(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{})
const (
initialCandidatesCap = 8
relativePathsWeight = 4
)
candidates := make([]string, 0, initialCandidatesCap+len(req.RelativePaths)*relativePathsWeight)
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 := objectstore.LoadConfig(ctx)
if err != nil {
return nil
}
root := strings.TrimSpace(cfg.Local.Root)
if root == "" {
return nil
}
return []string{root}
}