-
Notifications
You must be signed in to change notification settings - Fork 23
/
accessstate.go
347 lines (295 loc) · 8.3 KB
/
accessstate.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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors.
//
// SPDX-License-Identifier: Apache-2.0
package accessobj
import (
"fmt"
"sync"
"github.com/mandelsoft/vfs/pkg/vfs"
"github.com/modern-go/reflect2"
"github.com/opencontainers/go-digest"
"github.com/open-component-model/ocm/v2/pkg/common/accessio"
"github.com/open-component-model/ocm/v2/pkg/errors"
)
// These objects deal with descriptor based state descriptions
// of an access object
type AccessMode byte
const (
ACC_WRITABLE = AccessMode(0)
ACC_READONLY = AccessMode(1)
ACC_CREATE = AccessMode(2)
)
func (m AccessMode) IsReadonly() bool {
return (m & ACC_READONLY) != 0
}
func (m AccessMode) IsCreate() bool {
return (m & ACC_CREATE) != 0
}
var ErrReadOnly = accessio.ErrReadOnly
// StateHandler is responsible to handle the technical representation of state
// carrying object as byte array.
type StateHandler interface {
Initial() interface{}
Encode(d interface{}) ([]byte, error)
Decode([]byte) (interface{}, error)
Equivalent(a, b interface{}) bool
}
// StateAccess is responsible to handle the persistence
// of a state object.
type StateAccess interface {
// Get returns the technical representation of a state object from its persistence
// It MUST return an errors.IsErrNotFound compatible error
// if the persistence not yet exists.
Get() (accessio.BlobAccess, error)
// Digest() digest.Digest
Put(data []byte) error
}
// BlobStateAccess provides state handling for data given by a blob access.
type BlobStateAccess struct {
lock sync.RWMutex
blob accessio.BlobAccess
}
var _ StateAccess = (*BlobStateAccess)(nil)
func NewBlobStateAccess(blob accessio.BlobAccess) StateAccess {
return &BlobStateAccess{
blob: blob,
}
}
func NewBlobStateAccessForData(mimeType string, data []byte) StateAccess {
return &BlobStateAccess{
blob: accessio.BlobAccessForData(mimeType, data),
}
}
func (b *BlobStateAccess) Get() (accessio.BlobAccess, error) {
b.lock.RLock()
defer b.lock.RUnlock()
return b.blob, nil
}
func (b *BlobStateAccess) Put(data []byte) error {
b.lock.Lock()
defer b.lock.Unlock()
b.blob = accessio.BlobAccessForData(b.blob.MimeType(), data)
return nil
}
func (b *BlobStateAccess) Digest() digest.Digest {
b.lock.RLock()
defer b.lock.RUnlock()
return b.blob.Digest()
}
// State manages the modification and access of state
// with a technical representation as byte array
// It tries to keep the byte representation unchanged as long as
// possible.
type State interface {
IsReadOnly() bool
IsCreate() bool
GetOriginalBlob() accessio.BlobAccess
GetBlob() (accessio.BlobAccess, error)
HasChanged() bool
GetOriginalState() interface{}
GetState() interface{}
// Update updates the technical representation in its persistence
Update() (bool, error)
}
type state struct {
mode AccessMode
access StateAccess
handler StateHandler
originalBlob accessio.BlobAccess
original interface{}
current interface{}
}
var _ State = (*state)(nil)
// NewState creates a new State based on its persistence handling
// and the management of its technical representation as byte array.
func NewState(mode AccessMode, a StateAccess, p StateHandler) (State, error) {
state, err := newState(mode, a, p)
// avoid nil pinter problem: go is great
if err != nil {
return nil, err
}
return state, nil
}
func newState(mode AccessMode, a StateAccess, p StateHandler) (*state, error) {
blob, err := a.Get()
if err != nil {
if (mode&ACC_CREATE) == 0 || !errors.IsErrNotFound(err) {
return nil, err
}
}
var current, original interface{}
if blob != nil {
data, err := blob.Get()
if err != nil {
return nil, fmt.Errorf("failed to get blob data: %w", err)
}
blob = accessio.BlobAccessForData(blob.MimeType(), data) // cache original data
current, err = p.Decode(data)
if err != nil {
return nil, fmt.Errorf("failed to decode blob data: %w", err)
}
// we don't need a copy operation, because we can just deserialize it twice.
original, _ = p.Decode(data)
} else {
current = p.Initial()
}
return &state{
mode: mode,
access: a,
handler: p,
originalBlob: blob,
original: original,
current: current,
}, nil
}
// NewBlobStateForBlob provides state handling for an object persisted as a blob.
// It tries to keep the blob representation unchanged as long as possible
// consulting the state handler responsible for analysing the binary blob data
// and the object.
func NewBlobStateForBlob(mode AccessMode, blob accessio.BlobAccess, p StateHandler) (State, error) {
if blob == nil {
data, err := p.Encode(p.Initial())
if err != nil {
return nil, err
}
blob = accessio.BlobAccessForData("", data)
}
return NewState(mode, NewBlobStateAccess(blob), p)
}
// NewBlobStateForObject returns a representation state handling for a given object.
func NewBlobStateForObject(mode AccessMode, obj interface{}, p StateHandler) (State, error) {
if reflect2.IsNil(obj) {
obj = p.Initial()
}
data, err := p.Encode(obj)
if err != nil {
return nil, err
}
return NewBlobStateForBlob(mode, accessio.BlobAccessForData("", data), p)
}
func (s *state) IsReadOnly() bool {
return s.mode.IsReadonly()
}
func (s *state) IsCreate() bool {
return s.mode.IsCreate()
}
func (s *state) Refresh() error {
n, err := newState(s.mode, s.access, s.handler)
if err != nil {
return fmt.Errorf("unable to create new state: %w", err)
}
*s = *n
return nil
}
func (s *state) GetOriginalState() interface{} {
if s.originalBlob == nil {
return nil
}
// always provide a private copy to not corrupt the internal state
var original interface{}
data, err := s.originalBlob.Get()
if err == nil {
original, err = s.handler.Decode(data)
}
if err != nil {
panic("use of invalid state: " + err.Error())
}
return original
}
func (s *state) GetState() interface{} {
return s.current
}
func (s *state) GetOriginalBlob() accessio.BlobAccess {
return s.originalBlob
}
func (s *state) HasChanged() bool {
if s.original == nil {
return true
}
return !s.handler.Equivalent(s.original, s.current)
}
func (s *state) GetBlob() (accessio.BlobAccess, error) {
if !s.HasChanged() {
return s.originalBlob, nil
}
data, err := s.handler.Encode(s.current)
if err != nil {
return nil, err
}
if s.originalBlob != nil {
return accessio.BlobAccessForData(s.originalBlob.MimeType(), data), nil
}
return accessio.BlobAccessForData("", data), nil
}
func (s *state) Update() (bool, error) {
if !s.HasChanged() {
return false, nil
}
if s.IsReadOnly() {
return true, ErrReadOnly
}
data, err := s.handler.Encode(s.current)
if err != nil {
return false, err
}
original, err := s.handler.Decode(data)
if err != nil {
return false, err
}
err = s.access.Put(data)
if err != nil {
return false, err
}
mimeType := ""
if s.originalBlob != nil {
mimeType = s.originalBlob.MimeType()
}
s.originalBlob = accessio.BlobAccessForData(mimeType, data)
s.original = original
return true, nil
}
////////////////////////////////////////////////////////////////////////////////
type fileBasedAccess struct {
filesystem vfs.FileSystem
path string
mimeType string
mode vfs.FileMode
}
func (f *fileBasedAccess) Get() (accessio.BlobAccess, error) {
ok, err := vfs.FileExists(f.filesystem, f.path)
if err != nil {
return nil, err
}
if !ok {
return nil, errors.ErrNotFoundWrap(vfs.ErrNotExist, "file", f.path)
}
return accessio.BlobAccessForFile(f.mimeType, f.path, f.filesystem), nil
}
func (f *fileBasedAccess) Put(data []byte) error {
if err := vfs.WriteFile(f.filesystem, f.path, data, f.mode); err != nil {
return fmt.Errorf("unable to write file %q: %w", f.path, err)
}
return nil
}
func (f *fileBasedAccess) Digest() digest.Digest {
data, err := f.filesystem.Open(f.path)
if err == nil {
defer data.Close()
d, err := digest.FromReader(data)
if err == nil {
return d
}
}
return ""
}
////////////////////////////////////////////////////////////////////////////////
// NewFileBasedState create a new State object based on a file based persistence
// of the state carrying object.
func NewFileBasedState(acc AccessMode, fs vfs.FileSystem, path string, mimeType string, h StateHandler, mode vfs.FileMode) (State, error) {
return NewState(acc, &fileBasedAccess{
filesystem: fs,
path: path,
mode: mode,
mimeType: mimeType,
}, h)
}