Diffstat (limited to 'internal/storage/b2.go')
| -rw-r--r-- | internal/storage/b2.go | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/internal/storage/b2.go b/internal/storage/b2.go new file mode 100644 index 0000000..6697023 --- /dev/null +++ b/internal/storage/b2.go @@ -0,0 +1,110 @@ +package storage + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + "github.com/Backblaze/blazer/b2" + "github.com/SayaAndy/saya-today-article-metadata-add/config" + "github.com/SayaAndy/saya-today-article-metadata-add/internal/frontmatter" +) + +var _ StorageClient = &B2StorageClient{} + +type B2StorageClient struct { + prefix string + bucket *b2.Bucket + b2cl *b2.Client +} + +func NewB2StorageClient(cfg *config.StorageConfig) (StorageClient, error) { + if cfg.Type != "b2" { + return nil, fmt.Errorf("invalid storage type for B2InputClient") + } + b2cfg := cfg.Config.(*config.B2Config) + + b2cl, err := b2.NewClient(context.Background(), b2cfg.KeyID, b2cfg.ApplicationKey) + if err != nil { + return nil, err + } + + bucket, err := b2cl.Bucket(context.Background(), b2cfg.BucketName) + if err != nil { + return nil, err + } + + return &B2StorageClient{b2cl: b2cl, bucket: bucket, prefix: b2cfg.Prefix}, nil +} + +func (sc *B2StorageClient) GetMetadata(key string) (map[string]string, error) { + obj := sc.bucket.Object(sc.prefix + key) + if obj == nil { + return nil, fmt.Errorf("failed to reference object in B2 bucket") + } + attrs, err := obj.Attrs(context.Background()) + if err != nil { + if b2.IsNotExist(err) { + return map[string]string{}, nil + } + return nil, fmt.Errorf("get attributes of B2 object %q: %w", sc.prefix+key, err) + } + return attrs.Info, nil +} + +func (sc *B2StorageClient) Put(key string, content []byte, metadata *frontmatter.Metadata) error { + geolocationParts := strings.Split(metadata.Geolocation, " ") + if (len(geolocationParts) == 1 && geolocationParts[0] != "") || len(geolocationParts) >= 4 { + return fmt.Errorf("invalid geolocation format, expecting '{x} {y} [areaError]' or an empty string") + } + if len(geolocationParts) >= 2 { + if _, err := strconv.ParseFloat(geolocationParts[0], 64); err != nil { + return fmt.Errorf("invalid geolocation parameter, expected float for X: %w", err) + } + if _, err := strconv.ParseFloat(geolocationParts[1], 64); err != nil { + return fmt.Errorf("invalid geolocation parameter, expected float for Y: %w", err) + } + } + if len(geolocationParts) == 3 { + if _, err := strconv.ParseFloat(geolocationParts[2], 64); err != nil { + return fmt.Errorf("invalid geolocation parameter, expected float for area error: %w", err) + } + } + + medley := "" + if metadata.Medley != "" { + medley = fmt.Sprintf("%s %d", metadata.Medley, metadata.MedleyPart) + } + + attrs := &b2.Attrs{ + ContentType: "text/markdown; charset=utf-8", + Info: map[string]string{ + "title": metadata.Title, + "short-description": metadata.ShortDescription, + "action-date": metadata.ActionDate, + "published-time": metadata.PublishedTime.Format(time.RFC3339), + "thumbnail": metadata.Thumbnail, + "tags": strings.Join(metadata.Tags, ","), + "geolocation": metadata.Geolocation, + "medley": medley, + }} + + prod := sc.bucket.Object(sc.prefix + key) + if prod == nil { + return fmt.Errorf("failed to reference prod object in B2 bucket") + } + + writer := prod.NewWriter(context.Background(), b2.WithAttrsOption(attrs)) + defer writer.Close() + if _, err := writer.Write(content); err != nil { + return fmt.Errorf("failed to write an object back after attribute settings: %w", err) + } + + return nil +} + +func (sc *B2StorageClient) BuildIndex() error { + return fmt.Errorf("BuildIndex not implemented for B2 storage") +} |