-
Notifications
You must be signed in to change notification settings - Fork 1
/
sheetdb.go
113 lines (102 loc) · 3.13 KB
/
sheetdb.go
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
111
112
113
// Package sheetdb is a package for using Google spreadsheets as a database
// instead of the actual database management system.
// This package is used with the code generated by the `sheetdb-modeler` tool.
// For details, please refer to usage of README.md.
package sheetdb
import (
"context"
"errors"
"fmt"
"github.com/takuoki/gsheets"
"golang.org/x/sync/errgroup"
)
var modelSets = map[string][]model{}
type model struct {
name string
sheetName string
loadFunc func(data *gsheets.Sheet) error
}
// RegisterModel registers the model specified as an argument into a model set.
// This function is usually called from generated code.
func RegisterModel(modelSetName, modelName, sheetName string, loadFunc func(data *gsheets.Sheet) error) {
m := model{
name: modelName,
sheetName: sheetName,
loadFunc: loadFunc,
}
if s, ok := modelSets[modelSetName]; ok {
modelSets[modelSetName] = append(s, m)
} else {
modelSets[modelSetName] = []model{m}
}
}
// Client is a client of this package.
// Create a new client with the `New` function.
type Client struct {
gsClient *gsheets.Client
spreadsheetID string
modelSetName string
}
// New creates and returns a new client.
func New(ctx context.Context, credentials, token, spreadsheetID string, opts ...ClientOption) (*Client, error) {
gsClient, err := gsheets.New(ctx, credentials, token, gsheets.ClientWritable())
if err != nil {
return nil, fmt.Errorf("Unable to create gsheets client: %v", err)
}
client := &Client{
gsClient: gsClient,
spreadsheetID: spreadsheetID,
modelSetName: "default",
}
for _, opt := range opts {
client = opt(client)
}
return client, nil
}
// LoadData loads data from a spreadsheet into cache.
// This function calls the load functions of models registered in advance
// into the model set in order.
func (c *Client) LoadData(ctx context.Context) error {
if c.gsClient == nil {
return errors.New("The client has not been created correctly")
}
eg, ctx := errgroup.WithContext(ctx)
for _, m := range modelSets[c.modelSetName] {
m := m
eg.Go(func() (er error) {
defer func() {
if e := recover(); e != nil {
er = fmt.Errorf("Panic recovered in goroutine: %v", e)
}
}()
data, err := c.gsClient.GetSheet(ctx, c.spreadsheetID, m.sheetName)
if err != nil {
return err
}
logger.Infof("Loading from '%s' model", m.name)
if err := m.loadFunc(data); err != nil {
return fmt.Errorf("Unable to load '%s' data: %v", m.name, err)
}
return nil
})
}
return eg.Wait()
}
// AsyncUpdate applies updates to s spreadsheet asynchronously.
// This function is usually called from generated code.
func (c *Client) AsyncUpdate(data []gsheets.UpdateValue) error {
if c.gsClient == nil {
return errors.New("The client has not been created correctly")
}
go func() {
defer func() {
if e := recover(); e != nil {
logger.Errorf("Data could not be reflected on the sheet because an error occurred (err=%v, data=%+v)", e, data)
}
}()
if err := c.gsClient.BatchUpdate(context.Background(), c.spreadsheetID, data...); err != nil {
panic(fmt.Sprintf("Unable to update spreadsheet: %v", err))
}
}()
return nil
}