mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 14:06:36 +08:00
231 lines
5.9 KiB
Go
231 lines
5.9 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Package ofupload wraps Wavelet upload ingest with Pages-specific helpers.
|
|
package ofupload
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
|
|
"Wavelet/core/contracts"
|
|
"Wavelet/openflare/plugins/server/kernel/model"
|
|
)
|
|
|
|
// ReservedPagesDeploymentType is managed exclusively by the Pages domain.
|
|
const ReservedPagesDeploymentType = "openflare_pages_deployment"
|
|
|
|
const (
|
|
// PolicyCreate always stores a new object and creates a new upload record.
|
|
PolicyCreate = 1
|
|
// PolicyDedupNewRecord reuses an existing object path on hash match but creates a new record.
|
|
PolicyDedupNewRecord = 2
|
|
// PolicyResolveExisting returns an existing upload on hash match.
|
|
PolicyResolveExisting = 3
|
|
)
|
|
|
|
// IngestRequest is the programmatic upload ingest payload.
|
|
type IngestRequest struct {
|
|
UserID uint64
|
|
Type string
|
|
FileName string
|
|
MimeType string
|
|
Extension string
|
|
Size int64
|
|
Policy int
|
|
Hash string
|
|
Reader io.Reader
|
|
AccessMode *int
|
|
SkipExtensionCheck bool
|
|
Metadata model.UploadMetadata
|
|
}
|
|
|
|
// IngestResult reports ingest side effects.
|
|
type IngestResult struct {
|
|
Upload contracts.UploadDTO
|
|
Created bool
|
|
Stored bool
|
|
Resolved bool
|
|
}
|
|
|
|
// IngestPolicy controls hash-collision behavior during ingest.
|
|
type IngestPolicy = int
|
|
|
|
var (
|
|
svcMu sync.RWMutex
|
|
storageSvc contracts.StorageService
|
|
uploadSvc contracts.UploadService
|
|
)
|
|
|
|
// SetStorage injects the platform StorageService used to open stored objects.
|
|
func SetStorage(s contracts.StorageService) {
|
|
svcMu.Lock()
|
|
defer svcMu.Unlock()
|
|
storageSvc = s
|
|
}
|
|
|
|
// SetUploadService injects the platform UploadService.
|
|
func SetUploadService(s contracts.UploadService) {
|
|
svcMu.Lock()
|
|
defer svcMu.Unlock()
|
|
uploadSvc = s
|
|
}
|
|
|
|
// CurrentStorage returns the currently registered storage service.
|
|
func CurrentStorage() contracts.StorageService {
|
|
svcMu.RLock()
|
|
defer svcMu.RUnlock()
|
|
return storageSvc
|
|
}
|
|
|
|
func currentStorage() contracts.StorageService {
|
|
return CurrentStorage()
|
|
}
|
|
|
|
func currentUpload() contracts.UploadService {
|
|
svcMu.RLock()
|
|
defer svcMu.RUnlock()
|
|
return uploadSvc
|
|
}
|
|
|
|
// IngestFromLocalPath ingests a local regular file through Wavelet upload ingest.
|
|
func IngestFromLocalPath(ctx context.Context, localPath string, req IngestRequest) (IngestResult, error) {
|
|
localPath = strings.TrimSpace(localPath)
|
|
if localPath == "" {
|
|
return IngestResult{}, errors.New("local path is required")
|
|
}
|
|
file, err := os.Open(localPath) //nolint:gosec // localPath is resolved from managed Pages artifacts
|
|
if err != nil {
|
|
return IngestResult{}, err
|
|
}
|
|
defer func() { _ = file.Close() }()
|
|
|
|
info, err := file.Stat()
|
|
if err != nil {
|
|
return IngestResult{}, err
|
|
}
|
|
if info.IsDir() {
|
|
return IngestResult{}, errors.New("local path must be a regular file")
|
|
}
|
|
if req.Size <= 0 {
|
|
req.Size = info.Size()
|
|
}
|
|
|
|
storage := currentStorage()
|
|
if storage == nil {
|
|
return IngestResult{}, errors.New("storage service not available")
|
|
}
|
|
res, err := storage.Ingest(ctx, file, contracts.IngestOptions{
|
|
UserID: req.UserID,
|
|
Type: req.Type,
|
|
FileName: req.FileName,
|
|
MimeType: req.MimeType,
|
|
Extension: req.Extension,
|
|
Size: req.Size,
|
|
Policy: req.Policy,
|
|
Metadata: req.Metadata.Extra,
|
|
})
|
|
if err != nil {
|
|
return IngestResult{}, err
|
|
}
|
|
|
|
uploadRecord, err := GetActiveUpload(ctx, res.ID)
|
|
if err != nil {
|
|
return IngestResult{}, err
|
|
}
|
|
|
|
return IngestResult{
|
|
Upload: uploadRecord,
|
|
Created: res.Created,
|
|
Stored: res.Stored,
|
|
Resolved: res.Resolved,
|
|
}, nil
|
|
}
|
|
|
|
// GetActiveUpload loads an active (non-deleted) upload by ID.
|
|
func GetActiveUpload(ctx context.Context, id uint64) (contracts.UploadDTO, error) {
|
|
svc := currentUpload()
|
|
if svc == nil {
|
|
return contracts.UploadDTO{}, errors.New("upload service not available")
|
|
}
|
|
u, err := svc.GetByID(ctx, id)
|
|
if err != nil {
|
|
return contracts.UploadDTO{}, err
|
|
}
|
|
if u == nil {
|
|
return contracts.UploadDTO{}, errors.New("upload not found")
|
|
}
|
|
return *u, nil
|
|
}
|
|
|
|
// OpenedUploadObject is a stored object stream plus the upload record.
|
|
type OpenedUploadObject struct {
|
|
Upload contracts.UploadDTO
|
|
Body io.ReadCloser
|
|
ContentType string
|
|
ContentLength int64
|
|
}
|
|
|
|
// OpenStoredUpload opens the stored object for an active upload via UploadService.
|
|
func OpenStoredUpload(ctx context.Context, id uint64) (*OpenedUploadObject, error) {
|
|
svc := currentUpload()
|
|
if svc == nil {
|
|
return nil, errors.New("upload service not available")
|
|
}
|
|
obj, err := svc.OpenStoredUpload(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &OpenedUploadObject{
|
|
Upload: obj.Upload,
|
|
Body: obj.Body,
|
|
ContentType: obj.ContentType,
|
|
ContentLength: obj.ContentLength,
|
|
}, nil
|
|
}
|
|
|
|
// Remove removes an upload by ID.
|
|
func Remove(ctx context.Context, id uint64) error {
|
|
svc := currentUpload()
|
|
if svc == nil {
|
|
return errors.New("upload service not available")
|
|
}
|
|
return svc.Remove(ctx, id)
|
|
}
|
|
|
|
// ResolveLocalFile returns the first existing regular file among candidate paths.
|
|
func ResolveLocalFile(_ context.Context, req LocalFileCandidateRequest) (string, int64, error) {
|
|
candidates := append([]string{req.StoredPath}, req.RelativePaths...)
|
|
for _, candidate := range candidates {
|
|
candidate = strings.TrimSpace(candidate)
|
|
if candidate == "" {
|
|
continue
|
|
}
|
|
info, err := os.Stat(candidate) //nolint:gosec // candidate is resolved from managed Pages metadata
|
|
if err != nil || info.IsDir() {
|
|
continue
|
|
}
|
|
return candidate, info.Size(), nil
|
|
}
|
|
return "", 0, os.ErrNotExist
|
|
}
|
|
|
|
// LocalFileCandidateRequest describes filesystem locations that may host a legacy blob.
|
|
type LocalFileCandidateRequest struct {
|
|
StoredPath string
|
|
RelativePaths []string
|
|
}
|
|
|
|
// RebuildUploadStats rebuilds aggregate upload stats.
|
|
func RebuildUploadStats(ctx context.Context) error {
|
|
svc := currentUpload()
|
|
if svc == nil {
|
|
return errors.New("upload service not available")
|
|
}
|
|
return svc.RebuildStats(ctx)
|
|
}
|