2024-01-15 04:52:54 +00:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2024-01-19 20:51:31 +00:00
|
|
|
"context"
|
2024-01-25 14:50:17 +00:00
|
|
|
"fmt"
|
2024-02-16 01:55:21 +00:00
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
|
2024-02-24 15:34:49 +00:00
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
|
|
|
|
|
|
awsConfig "github.com/aws/aws-sdk-go-v2/config"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
|
|
|
2024-02-22 08:27:48 +00:00
|
|
|
"git.lumeweb.com/LumeWeb/portal/config"
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
"go.uber.org/fx"
|
2024-02-16 01:55:21 +00:00
|
|
|
|
2024-01-25 13:37:15 +00:00
|
|
|
"go.sia.tech/renterd/api"
|
2024-02-17 03:00:53 +00:00
|
|
|
|
|
|
|
"git.lumeweb.com/LumeWeb/portal/metadata"
|
|
|
|
|
2024-01-19 20:51:31 +00:00
|
|
|
"go.uber.org/zap"
|
2024-02-17 03:00:53 +00:00
|
|
|
|
|
|
|
"git.lumeweb.com/LumeWeb/portal/bao"
|
|
|
|
|
2024-01-28 07:20:59 +00:00
|
|
|
"gorm.io/gorm"
|
2024-02-17 03:00:53 +00:00
|
|
|
|
|
|
|
"git.lumeweb.com/LumeWeb/portal/renter"
|
2024-01-15 04:52:54 +00:00
|
|
|
)
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
const PROOF_EXTENSION = ".obao"
|
2024-01-28 07:20:59 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
var _ StorageService = (*StorageServiceDefault)(nil)
|
|
|
|
|
|
|
|
type FileNameEncoderFunc func([]byte) string
|
|
|
|
|
|
|
|
type StorageProtocol interface {
|
|
|
|
Name() string
|
|
|
|
EncodeFileName([]byte) string
|
2024-01-28 07:20:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var Module = fx.Module("storage",
|
|
|
|
fx.Provide(
|
2024-02-17 08:21:27 +00:00
|
|
|
fx.Annotate(
|
|
|
|
NewStorageService,
|
|
|
|
fx.As(new(StorageService)),
|
|
|
|
),
|
2024-01-28 07:20:59 +00:00
|
|
|
),
|
2024-01-15 04:52:54 +00:00
|
|
|
)
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
type StorageService interface {
|
|
|
|
UploadObject(ctx context.Context, protocol StorageProtocol, data io.ReadSeeker, muParams *renter.MultiPartUploadParams, proof *bao.Result) (*metadata.UploadMetadata, error)
|
|
|
|
UploadObjectProof(ctx context.Context, protocol StorageProtocol, data io.ReadSeeker, proof *bao.Result) error
|
|
|
|
HashObject(ctx context.Context, data io.Reader) (*bao.Result, error)
|
|
|
|
DownloadObject(ctx context.Context, protocol StorageProtocol, objectHash []byte, start int64) (io.ReadCloser, error)
|
|
|
|
DownloadObjectProof(ctx context.Context, protocol StorageProtocol, objectHash []byte) (io.ReadCloser, error)
|
|
|
|
DeleteObject(ctx context.Context, protocol StorageProtocol, objectHash []byte) error
|
|
|
|
DeleteObjectProof(ctx context.Context, protocol StorageProtocol, objectHash []byte) error
|
2024-02-24 15:34:49 +00:00
|
|
|
S3Client(ctx context.Context) (*s3.Client, error)
|
2024-02-17 03:00:53 +00:00
|
|
|
}
|
|
|
|
|
2024-02-01 01:29:27 +00:00
|
|
|
type StorageServiceDefault struct {
|
2024-02-22 08:27:48 +00:00
|
|
|
config *config.Manager
|
2024-02-01 02:27:38 +00:00
|
|
|
db *gorm.DB
|
|
|
|
renter *renter.RenterDefault
|
2024-02-17 03:00:53 +00:00
|
|
|
logger *zap.Logger
|
|
|
|
metadata metadata.MetadataService
|
2024-01-19 20:51:31 +00:00
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
type StorageServiceParams struct {
|
2024-02-17 08:33:44 +00:00
|
|
|
fx.In
|
2024-02-22 08:27:48 +00:00
|
|
|
Config *config.Manager
|
2024-02-17 03:00:53 +00:00
|
|
|
Db *gorm.DB
|
|
|
|
Renter *renter.RenterDefault
|
|
|
|
Logger *zap.Logger
|
2024-02-17 08:35:51 +00:00
|
|
|
Metadata metadata.MetadataService
|
2024-01-19 21:51:41 +00:00
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
func NewStorageService(params StorageServiceParams) *StorageServiceDefault {
|
|
|
|
return &StorageServiceDefault{
|
2024-02-17 08:36:18 +00:00
|
|
|
config: params.Config,
|
|
|
|
db: params.Db,
|
|
|
|
renter: params.Renter,
|
|
|
|
logger: params.Logger,
|
|
|
|
metadata: params.Metadata,
|
2024-02-17 03:00:53 +00:00
|
|
|
}
|
2024-01-19 20:51:31 +00:00
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
func (s StorageServiceDefault) UploadObject(ctx context.Context, protocol StorageProtocol, data io.ReadSeeker, muParams *renter.MultiPartUploadParams, proof *bao.Result) (*metadata.UploadMetadata, error) {
|
|
|
|
readers := make([]io.ReadCloser, 0)
|
|
|
|
defer func() {
|
|
|
|
for _, reader := range readers {
|
|
|
|
err := reader.Close()
|
|
|
|
if err != nil {
|
|
|
|
s.logger.Error("error closing reader", zap.Error(err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2024-02-01 02:28:31 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
getReader := func() (io.Reader, error) {
|
|
|
|
if muParams != nil {
|
|
|
|
muReader, err := muParams.ReaderFactory(0, uint(muParams.Size))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-02-01 02:28:31 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
found := false
|
|
|
|
for _, reader := range readers {
|
|
|
|
if reader == muReader {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2024-01-15 04:52:54 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
if !found {
|
|
|
|
readers = append(readers, muReader)
|
|
|
|
}
|
2024-02-18 07:38:17 +00:00
|
|
|
|
|
|
|
return muReader, nil
|
2024-02-17 03:00:53 +00:00
|
|
|
}
|
2024-02-16 01:55:21 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
_, err := data.Seek(0, io.SeekStart)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-02-16 01:55:21 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
return data, nil
|
2024-02-16 01:55:21 +00:00
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
reader, err := getReader()
|
2024-02-16 01:55:21 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
if proof == nil {
|
|
|
|
hashResult, err := s.HashObject(ctx, reader)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-02-16 01:55:21 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
reader, err = getReader()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-01-15 04:52:54 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
proof = hashResult
|
2024-01-15 04:52:54 +00:00
|
|
|
}
|
|
|
|
|
2024-02-18 03:54:44 +00:00
|
|
|
meta, err := s.metadata.GetUpload(ctx, proof.Hash)
|
|
|
|
if err == nil {
|
|
|
|
return &meta, nil
|
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
mimeBytes := make([]byte, 512)
|
2024-02-18 07:44:57 +00:00
|
|
|
|
|
|
|
reader, err = getReader()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
_, err = io.ReadFull(reader, mimeBytes)
|
2024-01-15 19:25:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
reader, err = getReader()
|
2024-01-15 04:52:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
mimeType := http.DetectContentType(mimeBytes)
|
2024-02-16 01:55:21 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
protocolName := protocol.Name()
|
2024-01-19 20:51:31 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
err = s.renter.CreateBucketIfNotExists(protocolName)
|
2024-01-19 20:51:31 +00:00
|
|
|
if err != nil {
|
2024-02-09 20:23:33 +00:00
|
|
|
return nil, err
|
2024-01-19 20:51:31 +00:00
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
filename := protocol.EncodeFileName(proof.Hash)
|
|
|
|
|
|
|
|
err = s.UploadObjectProof(ctx, protocol, nil, proof)
|
2024-01-19 20:51:31 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2024-02-17 03:00:53 +00:00
|
|
|
return nil, err
|
2024-01-19 20:51:31 +00:00
|
|
|
}
|
|
|
|
|
2024-02-18 03:59:34 +00:00
|
|
|
uploadMeta := &metadata.UploadMetadata{
|
|
|
|
Protocol: protocolName,
|
|
|
|
Hash: proof.Hash,
|
|
|
|
MimeType: mimeType,
|
|
|
|
Size: uint64(proof.Length),
|
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
if muParams != nil {
|
|
|
|
muParams.FileName = filename
|
|
|
|
muParams.Bucket = protocolName
|
2024-01-19 20:51:31 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
err = s.renter.UploadObjectMultipart(ctx, muParams)
|
2024-01-19 20:51:31 +00:00
|
|
|
if err != nil {
|
2024-02-17 03:00:53 +00:00
|
|
|
return nil, err
|
2024-01-19 20:51:31 +00:00
|
|
|
}
|
|
|
|
|
2024-02-18 03:59:34 +00:00
|
|
|
return uploadMeta, nil
|
2024-02-09 20:23:33 +00:00
|
|
|
}
|
2024-01-16 05:48:06 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
err = s.renter.UploadObject(ctx, reader, protocolName, filename)
|
2024-01-19 20:51:31 +00:00
|
|
|
if err != nil {
|
2024-02-17 03:00:53 +00:00
|
|
|
return nil, err
|
2024-01-19 20:51:31 +00:00
|
|
|
}
|
|
|
|
|
2024-02-18 03:59:34 +00:00
|
|
|
return uploadMeta, nil
|
2024-01-19 20:51:31 +00:00
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
func (s StorageServiceDefault) UploadObjectProof(ctx context.Context, protocol StorageProtocol, data io.ReadSeeker, proof *bao.Result) error {
|
|
|
|
if proof == nil {
|
|
|
|
hashResult, err := s.HashObject(ctx, data)
|
2024-02-01 07:03:04 +00:00
|
|
|
if err != nil {
|
2024-02-17 03:00:53 +00:00
|
|
|
return err
|
2024-02-01 07:03:04 +00:00
|
|
|
}
|
2024-01-19 20:51:31 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
proof = hashResult
|
2024-01-28 21:26:15 +00:00
|
|
|
}
|
2024-01-19 20:51:31 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
protocolName := protocol.Name()
|
2024-01-19 20:51:31 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
err := s.renter.CreateBucketIfNotExists(protocolName)
|
2024-01-19 20:51:31 +00:00
|
|
|
|
2024-01-28 21:26:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-01-19 20:51:31 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
return s.renter.UploadObject(ctx, bytes.NewReader(proof.Proof), protocolName, s.getProofPath(protocol, proof.Hash))
|
|
|
|
}
|
2024-01-20 17:26:31 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
func (s StorageServiceDefault) HashObject(ctx context.Context, data io.Reader) (*bao.Result, error) {
|
|
|
|
result, err := bao.Hash(data)
|
2024-01-26 00:05:52 +00:00
|
|
|
|
2024-01-28 21:26:15 +00:00
|
|
|
if err != nil {
|
2024-02-17 03:00:53 +00:00
|
|
|
return nil, err
|
2024-01-28 21:26:15 +00:00
|
|
|
}
|
2024-01-26 00:05:52 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
return result, nil
|
|
|
|
}
|
2024-01-26 00:05:52 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
func (s StorageServiceDefault) DownloadObject(ctx context.Context, protocol StorageProtocol, objectHash []byte, start int64) (io.ReadCloser, error) {
|
|
|
|
var partialRange api.DownloadRange
|
2024-01-26 00:05:52 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
upload, err := s.metadata.GetUpload(ctx, objectHash)
|
2024-02-01 07:03:04 +00:00
|
|
|
if err != nil {
|
2024-02-17 03:00:53 +00:00
|
|
|
return nil, err
|
2024-02-01 07:03:04 +00:00
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
if start > 0 {
|
|
|
|
partialRange = api.DownloadRange{
|
|
|
|
Offset: start,
|
|
|
|
Length: int64(upload.Size) - start + 1,
|
|
|
|
Size: int64(upload.Size),
|
|
|
|
}
|
2024-01-28 21:26:15 +00:00
|
|
|
}
|
2024-01-26 00:05:52 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
object, err := s.renter.GetObject(ctx, protocol.Name(), protocol.EncodeFileName(objectHash), api.DownloadObjectOptions{Range: partialRange})
|
2024-01-28 21:26:15 +00:00
|
|
|
if err != nil {
|
2024-02-17 03:00:53 +00:00
|
|
|
return nil, err
|
2024-01-28 21:26:15 +00:00
|
|
|
}
|
2024-01-19 20:51:31 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
return object.Content, nil
|
|
|
|
}
|
2024-02-09 20:23:33 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
func (s StorageServiceDefault) DownloadObjectProof(ctx context.Context, protocol StorageProtocol, objectHash []byte) (io.ReadCloser, error) {
|
|
|
|
object, err := s.renter.GetObject(ctx, protocol.Name(), protocol.EncodeFileName(objectHash)+".bao", api.DownloadObjectOptions{})
|
2024-02-09 20:23:33 +00:00
|
|
|
if err != nil {
|
2024-02-17 03:00:53 +00:00
|
|
|
return nil, err
|
2024-02-09 20:23:33 +00:00
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
return object.Content, nil
|
|
|
|
}
|
2024-01-22 23:25:11 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
func (s StorageServiceDefault) DeleteObject(ctx context.Context, protocol StorageProtocol, objectHash []byte) error {
|
|
|
|
err := s.renter.DeleteObject(ctx, protocol.Name(), protocol.EncodeFileName(objectHash))
|
2024-01-28 21:26:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-01-22 23:25:11 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
return nil
|
|
|
|
}
|
2024-01-23 00:08:56 +00:00
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
func (s StorageServiceDefault) DeleteObjectProof(ctx context.Context, protocol StorageProtocol, objectHash []byte) error {
|
|
|
|
err := s.renter.DeleteObject(ctx, protocol.Name(), s.getProofPath(protocol, objectHash))
|
2024-01-28 21:26:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-01-19 20:51:31 +00:00
|
|
|
|
2024-01-28 21:26:15 +00:00
|
|
|
return nil
|
2024-01-19 20:51:31 +00:00
|
|
|
}
|
|
|
|
|
2024-02-24 15:34:49 +00:00
|
|
|
func (s StorageServiceDefault) S3Client(ctx context.Context) (*s3.Client, error) {
|
|
|
|
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
|
|
|
|
if service == s3.ServiceID {
|
|
|
|
return aws.Endpoint{
|
|
|
|
URL: s.config.Config().Core.Storage.S3.Endpoint,
|
|
|
|
SigningRegion: s.config.Config().Core.Storage.S3.Region,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
return aws.Endpoint{}, &aws.EndpointNotFoundError{}
|
|
|
|
})
|
|
|
|
cfg, err := awsConfig.LoadDefaultConfig(ctx,
|
|
|
|
awsConfig.WithRegion("us-east-1"),
|
|
|
|
awsConfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
|
|
|
|
s.config.Config().Core.Storage.S3.AccessKey,
|
|
|
|
s.config.Config().Core.Storage.S3.SecretKey,
|
|
|
|
"",
|
|
|
|
)),
|
|
|
|
awsConfig.WithEndpointResolverWithOptions(customResolver),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s3.NewFromConfig(cfg), nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-02-17 03:00:53 +00:00
|
|
|
func (s StorageServiceDefault) getProofPath(protocol StorageProtocol, objectHash []byte) string {
|
2024-02-18 08:07:14 +00:00
|
|
|
return fmt.Sprintf("%s%s", protocol.EncodeFileName(objectHash), PROOF_EXTENSION)
|
2024-01-25 21:31:05 +00:00
|
|
|
}
|