package fsys import ( "bytes" "context" "errors" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" "io" "net/http" "os" "path/filepath" "strings" ) var ( ErrInvalidStorageType = errors.New("invalid storage type") ErrInvalidPath = errors.New("invalid path") ErrFileNotFound = errors.New("file not found") ) type Storage struct { config *Config s3Client *minio.Client ctx context.Context } type Config struct { Type string Path string S3Endpoint string S3Location string S3Secure bool S3AccessID string S3AccessKey string S3BucketName string } func New(config *Config) (*Storage, error) { newStorage := new(Storage) newStorage.config = config newStorage.ctx = context.Background() switch config.Type { case "minio", "s3": s3Client, err := minio.New(config.S3Endpoint, &minio.Options{ Creds: credentials.NewStaticV4(config.S3AccessID, config.S3AccessKey, ""), Secure: config.S3Secure, }) if err != nil { return nil, err } bucketExists, err := s3Client.BucketExists(newStorage.ctx, config.S3BucketName) if err != nil { return nil, err } if !bucketExists { if err := s3Client.MakeBucket(newStorage.ctx, config.S3BucketName, minio.MakeBucketOptions{}); err != nil { return nil, err } } newStorage.s3Client = s3Client case "local": if err := os.MkdirAll(config.Path, 0740); err != nil { return nil, err } default: return nil, ErrInvalidStorageType } return newStorage, nil } func (s *Storage) Open(name string) (*File, error) { returnFile := new(File) returnFile.storage = s if s.s3Client != nil { object, err := s.s3Client.GetObject(s.ctx, s.config.S3BucketName, name, minio.GetObjectOptions{}) if err != nil { errResp := minio.ToErrorResponse(err) if errResp.StatusCode == http.StatusNotFound { return nil, ErrFileNotFound } else { return nil, err } } objectStat, err := object.Stat() if err != nil { errResp := minio.ToErrorResponse(err) if errResp.StatusCode == http.StatusNotFound { return nil, ErrFileNotFound } else { return nil, err } } returnFile.Name = objectStat.Key returnFile.file = object } else { if _, err := os.Stat(filepath.Join(s.config.Path, name)); err != nil && os.IsNotExist(err) { return nil, ErrFileNotFound } else if err != nil { return nil, err } file, err := os.Open(filepath.Join(s.config.Path, name)) if err != nil { return nil, err } returnFile.file = file cutStr, _ := strings.CutPrefix(file.Name(), filepath.Clean(s.config.Path)+"/") returnFile.Name = cutStr } return returnFile, nil } func (s *Storage) Read(name string) ([]byte, error) { if s.s3Client != nil { object, err := s.s3Client.GetObject(s.ctx, s.config.S3BucketName, name, minio.GetObjectOptions{}) if err != nil { errResp := minio.ToErrorResponse(err) if errResp.StatusCode == http.StatusNotFound { return nil, ErrFileNotFound } else { return nil, err } } objectBytes, err := io.ReadAll(object) if err != nil { return nil, err } return objectBytes, nil } else { return os.ReadFile(filepath.Join(s.config.Path, name)) } } type WriteOptions struct { S3Tags map[string]string } func (s *Storage) Write(name string, data []byte, opts WriteOptions) error { if s.s3Client != nil { if _, err := s.s3Client.PutObject(s.ctx, s.config.S3BucketName, name, bytes.NewReader(data), int64(len(data)), minio.PutObjectOptions{ UserTags: opts.S3Tags, }); err != nil { return err } return nil } else { return os.WriteFile(filepath.Join(s.config.Path, name), data, 0640) } }