mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 07:36:37 +08:00
refactor(arch): decouple private imports, enforce contracts and comply with cordis architecture
This commit is contained in:
@@ -13,9 +13,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"Wavelet/core/contracts"
|
||||
waveletupload "Wavelet/plugins/domain/upload"
|
||||
"Wavelet/plugins/domain/upload/models"
|
||||
"Wavelet/plugins/infra/database"
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
)
|
||||
|
||||
// ReservedPagesDeploymentType is managed exclusively by the Pages domain.
|
||||
@@ -23,40 +21,77 @@ const ReservedPagesDeploymentType = "openflare_pages_deployment"
|
||||
|
||||
const (
|
||||
// PolicyCreate always stores a new object and creates a new upload record.
|
||||
PolicyCreate = waveletupload.PolicyCreate
|
||||
PolicyCreate = 1
|
||||
// PolicyDedupNewRecord reuses an existing object path on hash match but creates a new record.
|
||||
PolicyDedupNewRecord = waveletupload.PolicyDedupNewRecord
|
||||
PolicyDedupNewRecord = 2
|
||||
// PolicyResolveExisting returns an existing upload on hash match.
|
||||
PolicyResolveExisting = waveletupload.PolicyResolveExisting
|
||||
PolicyResolveExisting = 3
|
||||
)
|
||||
|
||||
type (
|
||||
// IngestRequest is the programmatic upload ingest payload.
|
||||
IngestRequest = waveletupload.IngestRequest
|
||||
// IngestResult reports ingest side effects.
|
||||
IngestResult = waveletupload.IngestResult
|
||||
// IngestPolicy controls hash-collision behavior during ingest.
|
||||
IngestPolicy = waveletupload.IngestPolicy
|
||||
)
|
||||
// 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 (
|
||||
storageMu sync.RWMutex
|
||||
svcMu sync.RWMutex
|
||||
storageSvc contracts.StorageService
|
||||
uploadSvc contracts.UploadService
|
||||
)
|
||||
|
||||
// SetStorage injects the platform StorageService used to open stored objects.
|
||||
func SetStorage(s contracts.StorageService) {
|
||||
storageMu.Lock()
|
||||
defer storageMu.Unlock()
|
||||
svcMu.Lock()
|
||||
defer svcMu.Unlock()
|
||||
storageSvc = s
|
||||
}
|
||||
|
||||
func currentStorage() contracts.StorageService {
|
||||
storageMu.RLock()
|
||||
defer storageMu.RUnlock()
|
||||
// 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)
|
||||
@@ -79,55 +114,87 @@ func IngestFromLocalPath(ctx context.Context, localPath string, req IngestReques
|
||||
if req.Size <= 0 {
|
||||
req.Size = info.Size()
|
||||
}
|
||||
req.Reader = file
|
||||
return waveletupload.Ingest(ctx, req)
|
||||
|
||||
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) (models.Upload, error) {
|
||||
conn := database.DB(ctx)
|
||||
if conn == nil {
|
||||
return models.Upload{}, errors.New("database not initialized")
|
||||
func GetActiveUpload(ctx context.Context, id uint64) (contracts.UploadDTO, error) {
|
||||
svc := currentUpload()
|
||||
if svc == nil {
|
||||
return contracts.UploadDTO{}, errors.New("upload service not available")
|
||||
}
|
||||
var upload models.Upload
|
||||
err := conn.Where("id = ? AND status <> ?", id, models.UploadStatusDeleted).First(&upload).Error
|
||||
return upload, err
|
||||
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 models.Upload
|
||||
Upload contracts.UploadDTO
|
||||
Body io.ReadCloser
|
||||
ContentType string
|
||||
ContentLength int64
|
||||
}
|
||||
|
||||
// OpenStoredUpload opens the stored object for an active upload via StorageService.
|
||||
// OpenStoredUpload opens the stored object for an active upload via UploadService.
|
||||
func OpenStoredUpload(ctx context.Context, id uint64) (*OpenedUploadObject, error) {
|
||||
upload, err := GetActiveUpload(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
svc := currentStorage()
|
||||
svc := currentUpload()
|
||||
if svc == nil {
|
||||
return nil, errors.New("storage service not available")
|
||||
return nil, errors.New("upload service not available")
|
||||
}
|
||||
obj, err := svc.Get(ctx, upload.FilePath)
|
||||
obj, err := svc.OpenStoredUpload(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &OpenedUploadObject{
|
||||
Upload: upload,
|
||||
Upload: obj.Upload,
|
||||
Body: obj.Body,
|
||||
ContentType: obj.ContentType,
|
||||
ContentLength: obj.ContentLength,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LocalFileCandidateRequest describes filesystem locations that may host a legacy blob.
|
||||
type LocalFileCandidateRequest struct {
|
||||
StoredPath string
|
||||
RelativePaths []string
|
||||
// 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.
|
||||
@@ -147,7 +214,17 @@ func ResolveLocalFile(_ context.Context, req LocalFileCandidateRequest) (string,
|
||||
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 {
|
||||
return waveletupload.RebuildUploadStats(ctx)
|
||||
svc := currentUpload()
|
||||
if svc == nil {
|
||||
return errors.New("upload service not available")
|
||||
}
|
||||
return svc.RebuildStats(ctx)
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package ofupload
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"Wavelet/plugins/domain/upload/cache"
|
||||
"Wavelet/plugins/domain/upload/models"
|
||||
uploadrepo "Wavelet/plugins/domain/upload/repository"
|
||||
uploadstats "Wavelet/plugins/domain/upload/stats"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RemoveLockedTx performs the idempotent active-to-deleted transition for a row
|
||||
// that the caller has already locked in its surrounding transaction.
|
||||
func RemoveLockedTx(tx *gorm.DB, upload *models.Upload) (bool, error) {
|
||||
if upload == nil {
|
||||
return false, nil
|
||||
}
|
||||
if upload.Status == models.UploadStatusDeleted {
|
||||
return false, nil
|
||||
}
|
||||
snapshot := *upload
|
||||
if err := uploadrepo.SoftDeleteUploadTx(tx, upload); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := uploadstats.ApplyUploadStatsDeltaTx(tx, &snapshot, -1); err != nil {
|
||||
return false, err
|
||||
}
|
||||
upload.Status = models.UploadStatusDeleted
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// InvalidateUploadMetaCache evicts cached upload metadata.
|
||||
func InvalidateUploadMetaCache(ctx context.Context, id uint64) {
|
||||
cache.EvictUploadMeta(ctx, id)
|
||||
}
|
||||
Reference in New Issue
Block a user