package service import ( "context" "fmt" "io" "gcy_hpc_server/internal/model" "gcy_hpc_server/internal/server" "gcy_hpc_server/internal/storage" "gcy_hpc_server/internal/store" "go.uber.org/zap" ) // DownloadService handles file downloads with streaming and Range support. type DownloadService struct { storage storage.ObjectStorage blobStore *store.BlobStore fileStore *store.FileStore bucket string logger *zap.Logger } // NewDownloadService creates a new DownloadService. func NewDownloadService(storage storage.ObjectStorage, blobStore *store.BlobStore, fileStore *store.FileStore, bucket string, logger *zap.Logger) *DownloadService { return &DownloadService{ storage: storage, blobStore: blobStore, fileStore: fileStore, bucket: bucket, logger: logger, } } // Download returns a stream reader for the given file, optionally limited to a byte range. // Returns (reader, file, blob, start, end, error). func (s *DownloadService) Download(ctx context.Context, fileID int64, rangeHeader string) (io.ReadCloser, *model.File, *model.FileBlob, int64, int64, error) { file, err := s.fileStore.GetByID(ctx, fileID) if err != nil { return nil, nil, nil, 0, 0, fmt.Errorf("get file: %w", err) } if file == nil { return nil, nil, nil, 0, 0, fmt.Errorf("file not found") } blob, err := s.blobStore.GetBySHA256(ctx, file.BlobSHA256) if err != nil { return nil, nil, nil, 0, 0, fmt.Errorf("get blob: %w", err) } if blob == nil { return nil, nil, nil, 0, 0, fmt.Errorf("blob not found") } var start, end int64 if rangeHeader != "" { start, end, err = server.ParseRange(rangeHeader, blob.FileSize) if err != nil { return nil, nil, nil, 0, 0, fmt.Errorf("parse range: %w", err) } } else { start = 0 end = blob.FileSize - 1 } opts := storage.GetOptions{ Start: &start, End: &end, } reader, _, err := s.storage.GetObject(ctx, s.bucket, blob.MinioKey, opts) if err != nil { return nil, nil, nil, 0, 0, fmt.Errorf("get object: %w", err) } return reader, file, blob, start, end, nil } // GetFileMetadata returns the file and its associated blob metadata. func (s *DownloadService) GetFileMetadata(ctx context.Context, fileID int64) (*model.File, *model.FileBlob, error) { file, err := s.fileStore.GetByID(ctx, fileID) if err != nil { return nil, nil, fmt.Errorf("get file: %w", err) } if file == nil { return nil, nil, fmt.Errorf("file not found") } blob, err := s.blobStore.GetBySHA256(ctx, file.BlobSHA256) if err != nil { return nil, nil, fmt.Errorf("get blob: %w", err) } if blob == nil { return nil, nil, fmt.Errorf("blob not found") } return file, blob, nil }