-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgbfs.go
193 lines (167 loc) · 3.53 KB
/
gbfs.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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package gbfs
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
f "github.com/marz619/gbfs-go/fields"
)
const DefaultRefreshDuration = 10 * time.Second
// Client interface is the public Client interface used to retrieve data from
// a GBFS API
type Client interface {
GBFS() (GBFS, error)
}
// AutoRefreshClient extends the Client interface providing the ability to auto
// refresh documents based on the returned TTL
type AutoRefreshClient interface {
Client
Pause() RefreshState
Resume() RefreshState
//
next(any, int)
}
// RefreshState enum
type RefreshState uint8
const (
_ RefreshState = iota
Paused
Refreshing
Errored
Noop
)
// New Client with default http.Client
func New(rootURL string) Client {
return NewClient(rootURL, nil)
}
// NewClient returns a Client
func NewClient(rootURL string, c *http.Client) Client {
if c == nil {
c = http.DefaultClient
}
return &clientImpl{
Client: c,
rootURL: rootURL,
autoRefresh: false,
}
}
// NewAutoRefreshClient returns a Client that will self update based on the
// returned TTL
func NewAutoRefreshClient(rootURL string, c *http.Client) AutoRefreshClient {
if c == nil {
c = http.DefaultClient
}
return &clientImpl{
Client: c,
rootURL: rootURL,
autoRefresh: true,
state: Paused,
ts: make(map[any]time.Ticker),
}
}
// client interface
type client interface {
get(string, any) error
set(client)
}
func setC(c client, dst any) {
if t, ok := dst.(client); ok {
t.set(c)
}
}
// internal client implementation
type clientImpl struct {
*http.Client
rootURL string
autoRefresh bool
// protected by mutex
m sync.Mutex
state RefreshState // global state
ts map[any]time.Ticker // per object ticker
}
func (c *clientImpl) set(_ client) {} // noop to satisfy interface
// get retrieves
func (c *clientImpl) get(url string, dst any) error {
defer setC(c, dst)
res, err := c.Get(url)
if err != nil {
return err
}
// check status code
if res.StatusCode != http.StatusOK {
content, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
return fmt.Errorf("HTTP<%d>: %s", res.StatusCode, string(content))
}
// try to unmarshal as json
err = json.NewDecoder(res.Body).Decode(dst)
if err != nil {
return err
}
if o, ok := dst.(Output); ok {
o.self = url
}
return nil
}
// ErrNoRootURL error
var ErrNoRootURL = errors.New("no rootURL url")
// GBFS satisfies Client interface
func (c *clientImpl) GBFS() (g GBFS, err error) {
if c.rootURL == "" {
err = ErrNoRootURL
return
}
// get the Discover doc
err = c.get(c.rootURL, &g)
return
}
func (c *clientImpl) Pause() RefreshState {
if c.state == Paused {
return Noop
}
c.m.Lock()
defer c.m.Unlock()
return c.pause()
}
func (c *clientImpl) pause() RefreshState {
if c.state == Paused {
return Noop
}
// stop all the tickers
for _, t := range c.ts {
t.Stop()
}
c.state = Paused
return c.state
}
func (c *clientImpl) Resume() RefreshState {
if c.state == Refreshing {
return Noop
}
c.m.Lock()
defer c.m.Unlock()
return c.resume()
}
func (c *clientImpl) resume() RefreshState {
if c.state == Refreshing {
return Noop
}
for _, t := range c.ts {
t.Reset(DefaultRefreshDuration)
}
c.state = Refreshing
return c.state
}
func (c *clientImpl) next(i any, ttl int) {
// based on TTL set the next timer tick for this object
}
// SystemInformation ...
func (g GBFS) SystemInformation(l f.Language) (s SystemInformation, err error) {
err = g.c.get(g.Feeds(l).URL("system_information").String(), &s)
return
}