1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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")
}
|