forked from corestoreio/pkg
-
Notifications
You must be signed in to change notification settings - Fork 0
/
storage.go
313 lines (280 loc) · 10.2 KB
/
storage.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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
// Copyright 2015 CoreStore Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package store
import (
"sync"
"github.com/corestoreio/csfw/config"
"github.com/corestoreio/csfw/storage/csdb"
"github.com/corestoreio/csfw/storage/dbr"
"github.com/juju/errgo"
)
type (
// Storager implements the requirements to get new websites, groups and store views.
// This interface is used in the StoreManager
Storager interface {
// Website creates a new Website pointer from an ID or code including all of its
// groups and all related stores. It panics when the integrity is incorrect.
// If ID and code are available then the non-empty code has precedence.
Website(Retriever) (*Website, error)
// Websites creates a slice containing all pointers to Websites with its associated
// groups and stores. It panics when the integrity is incorrect.
Websites() (WebsiteSlice, error)
// Group creates a new Group which contains all related stores and its website.
// Only the argument ID can be used to get a specific Group.
Group(Retriever) (*Group, error)
// Groups creates a slice containing all pointers to Groups with its associated
// stores and websites. It panics when the integrity is incorrect.
Groups() (GroupSlice, error)
// Store creates a new Store containing its group and its website.
// If ID and code are available then the non-empty code has precedence.
Store(Retriever) (*Store, error)
// Stores creates a new store slice. Can return an error when the website or
// the group cannot be found.
Stores() (StoreSlice, error)
// DefaultStoreView traverses through the websites to find the default website and gets
// the default group which has the default store id assigned to. Only one website can be the default one.
DefaultStoreView() (*Store, error)
// ReInit reloads the websites, groups and stores from the database.
ReInit(dbr.SessionRunner, ...csdb.DbrSelectCb) error
}
// Storage contains a mutex and the raw slices from the database. @todo maybe make private?
Storage struct {
cr config.Reader
mu sync.RWMutex
websites TableWebsiteSlice
groups TableGroupSlice
stores TableStoreSlice
}
// StorageOption option func for NewStorage()
StorageOption func(*Storage)
// Retriever implements how to get the ID. If Retriever implements CodeRetriever
// then CodeRetriever has precedence. ID can be any of the website, group or store IDs.
Retriever interface {
ID() int64
}
// CodeRetriever implements how to get an object by Code which can be website or store code.
// Groups doesn't have codes.
CodeRetriever interface {
Code() string
}
// ID is convenience helper to satisfy the interface IDRetriever.
ID int64
// Code is convenience helper to satisfy the interface CodeRetriever and IDRetriever.
Code string
)
// check if interface has been implemented
var _ Storager = (*Storage)(nil)
// ID is convenience helper to satisfy the interface Retriever
func (i ID) ID() int64 { return int64(i) }
// ID is a noop method receiver to satisfy the interface Retriever
func (c Code) ID() int64 { return int64(0) }
// Code is convenience helper to satisfy the interface CodeRetriever
func (c Code) Code() string { return string(c) }
// SetStorageWebsites adds the TableWebsiteSlice to the Storage. By default, the slice is nil.
func SetStorageWebsites(tws ...*TableWebsite) StorageOption {
return func(s *Storage) { s.websites = TableWebsiteSlice(tws) }
}
// SetStorageGroups adds the TableGroupSlice to the Storage. By default, the slice is nil.
func SetStorageGroups(tgs ...*TableGroup) StorageOption {
return func(s *Storage) { s.groups = TableGroupSlice(tgs) }
}
// SetStorageStores adds the TableStoreSlice to the Storage. By default, the slice is nil.
func SetStorageStores(tss ...*TableStore) StorageOption {
return func(s *Storage) { s.stores = TableStoreSlice(tss) }
}
// SetStorageConfig sets the configuration Reader. Optional.
// Default reader is config.DefaultManager
func SetStorageConfig(cr config.Reader) StorageOption {
return func(s *Storage) { s.cr = cr }
}
// NewStorage creates a new storage object from three slice types. All three arguments can be nil
// but then you call ReInit()
func NewStorage(opts ...StorageOption) *Storage {
s := &Storage{
cr: config.DefaultManager,
mu: sync.RWMutex{},
}
for _, opt := range opts {
if opt != nil {
opt(s)
}
}
return s
}
// NewStorageOption sames as NewStorage() but returns a function to be used in NewManager()
func NewStorageOption(opts ...StorageOption) ManagerOption {
return func(m *Manager) { m.storage = NewStorage(opts...) }
}
// website returns a TableWebsite by using either id or code to find it. If id and code are
// available then the non-empty code has precedence.
func (st *Storage) website(r Retriever) (*TableWebsite, error) {
if r == nil {
return nil, ErrWebsiteNotFound
}
if c, ok := r.(CodeRetriever); ok && c.Code() != "" {
return st.websites.FindByCode(c.Code())
}
return st.websites.FindByID(r.ID())
}
// Website creates a new Website according to the interface definition.
func (st *Storage) Website(r Retriever) (*Website, error) {
w, err := st.website(r)
if err != nil {
return nil, err
}
return NewWebsite(w).SetGroupsStores(st.groups, st.stores), nil
}
// Websites creates a slice of Website pointers according to the interface definition.
func (st *Storage) Websites() (WebsiteSlice, error) {
websites := make(WebsiteSlice, len(st.websites), len(st.websites))
for i, w := range st.websites {
websites[i] = NewWebsite(w).SetGroupsStores(st.groups, st.stores)
}
return websites, nil
}
// group returns a TableGroup by using a group id as argument. If no argument or more than
// one has been supplied it returns an error.
func (st *Storage) group(r Retriever) (*TableGroup, error) {
if r == nil {
return nil, ErrGroupNotFound
}
return st.groups.FindByID(r.ID())
}
// Group creates a new Group which contains all related stores and its website according to the
// interface definition.
func (st *Storage) Group(id Retriever) (*Group, error) {
g, err := st.group(id)
if err != nil {
return nil, err
}
w, err := st.website(ID(g.WebsiteID))
if err != nil {
return nil, err
}
return NewGroup(g, SetGroupWebsite(w), SetGroupConfig(st.cr)).SetStores(st.stores, nil), nil
}
// Groups creates a new group slice containing its website all related stores.
// May panic when a website pointer is nil.
func (st *Storage) Groups() (GroupSlice, error) {
groups := make(GroupSlice, len(st.groups), len(st.groups))
for i, g := range st.groups {
w, err := st.website(ID(g.WebsiteID))
if err != nil {
return nil, errgo.Mask(err)
}
groups[i] = NewGroup(g, SetGroupConfig(st.cr), SetGroupWebsite(w)).SetStores(st.stores, nil)
}
return groups, nil
}
// store returns a TableStore by an id or code.
// The non-empty code has precedence if available.
func (st *Storage) store(r Retriever) (*TableStore, error) {
if r == nil {
return nil, ErrStoreNotFound
}
if c, ok := r.(CodeRetriever); ok && c.Code() != "" {
return st.stores.FindByCode(c.Code())
}
return st.stores.FindByID(r.ID())
}
// Store creates a new Store which contains the the store, its group and website
// according to the interface definition.
func (st *Storage) Store(r Retriever) (*Store, error) {
s, err := st.store(r)
if err != nil {
return nil, errgo.Mask(err)
}
w, err := st.website(ID(s.WebsiteID))
if err != nil {
return nil, errgo.Mask(err)
}
g, err := st.group(ID(s.GroupID))
if err != nil {
return nil, errgo.Mask(err)
}
ns := NewStore(s, w, g, SetStoreConfig(st.cr))
ns.Website().SetGroupsStores(st.groups, st.stores)
ns.Group().SetStores(st.stores, w)
return ns, nil
}
// Stores creates a new store slice. Can return an error when the website or
// the group cannot be found.
func (st *Storage) Stores() (StoreSlice, error) {
stores := make(StoreSlice, len(st.stores), len(st.stores))
for i, s := range st.stores {
var err error
if stores[i], err = st.Store(ID(s.StoreID)); err != nil {
return nil, errgo.Mask(err)
}
}
return stores, nil
}
// DefaultStoreView traverses through the websites to find the default website and gets
// the default group which has the default store id assigned to. Only one website can be the default one.
func (st *Storage) DefaultStoreView() (*Store, error) {
for _, website := range st.websites {
if website.IsDefault.Bool && website.IsDefault.Valid {
g, err := st.group(ID(website.DefaultGroupID))
if err != nil {
return nil, err
}
return st.Store(ID(g.DefaultStoreID))
}
}
return nil, ErrStoreNotFound
}
// ReInit reloads all websites, groups and stores concurrently from the database. If GOMAXPROCS
// is set to > 1 then in parallel. Returns an error with location or nil. If an error occurs
// then all internal slices will be reset.
func (st *Storage) ReInit(dbrSess dbr.SessionRunner, cbs ...csdb.DbrSelectCb) error {
st.mu.Lock()
defer st.mu.Unlock()
errc := make(chan error)
defer close(errc)
// not sure about those three go
go func() {
for i := range st.websites {
st.websites[i] = nil // I'm not quite sure if that is needed to clear the pointers
}
st.websites = nil
_, err := st.websites.Load(dbrSess, cbs...)
errc <- errgo.Mask(err)
}()
go func() {
for i := range st.groups {
st.groups[i] = nil // I'm not quite sure if that is needed to clear the pointers
}
st.groups = nil
_, err := st.groups.Load(dbrSess, cbs...)
errc <- errgo.Mask(err)
}()
go func() {
for i := range st.stores {
st.stores[i] = nil // I'm not quite sure if that is needed to clear the pointers
}
st.stores = nil
_, err := st.stores.Load(dbrSess, cbs...)
errc <- errgo.Mask(err)
}()
for i := 0; i < 3; i++ {
if err := <-errc; err != nil {
// in case of error clear all
st.websites = nil
st.groups = nil
st.stores = nil
return err
}
}
return nil
}