blob: 27a67466f72f9d354780a540615094e4dd8e1367 [file] [log] [blame]
/*
* Copyright 2018-present Open Networking Foundation
* 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 model
import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/google/uuid"
"reflect"
)
type Root struct {
*Node
DirtyNodes map[string]*Node
KvStore *Backend
Loading bool
RevisionClass interface{}
Callbacks []func() interface{}
NotificationCallbacks []func() interface{}
}
func NewRoot(initialData interface{}, kvStore *Backend, revisionClass interface{}) *Root {
root := &Root{}
root.KvStore = kvStore
root.DirtyNodes = make(map[string]*Node)
root.Loading = false
if kvStore != nil /*&& FIXME: RevisionClass is a subclass of PersistedConfigRevision */ {
revisionClass = reflect.TypeOf(PersistedRevision{})
}
root.RevisionClass = revisionClass
root.Callbacks = []func() interface{}{}
root.NotificationCallbacks = []func() interface{}{}
root.Node = NewNode(root, initialData, false, "")
return root
}
func (r *Root) makeRevision(branch *Branch, data interface{}, children map[string][]*Revision) *Revision {
return &Revision{}
}
func (r *Root) makeTxBranch() string {
txid_bin, _ := uuid.New().MarshalBinary()
txid := hex.EncodeToString(txid_bin)[:12]
r.DirtyNodes[txid] = r.Node
r.Node.makeTxBranch(txid)
return txid
}
func (r *Root) deleteTxBranch(txid string) {
for _, dirtyNode := range r.DirtyNodes {
dirtyNode.deleteTxBranch(txid)
}
delete(r.DirtyNodes, txid)
}
func (r *Root) foldTxBranch(txid string) {
// TODO: implement foldTxBranch
// if err := r.Node.mergeTxBranch(txid, dryRun=true); err != nil {
// r.deleteTxBranch(txid)
// } else {
// r.Node.mergeTxBranch(txid)
// r.executeCallbacks()
// }
}
func (r *Root) executeCallbacks() {
for len(r.Callbacks) > 0 {
callback := r.Callbacks[0]
r.Callbacks = r.Callbacks[1:]
callback()
}
for len(r.NotificationCallbacks) > 0 {
callback := r.NotificationCallbacks[0]
r.NotificationCallbacks = r.NotificationCallbacks[1:]
callback()
}
}
func (r *Root) noCallbacks() bool {
return len(r.Callbacks) == 0
}
func (r *Root) addCallback(callback func() interface{}) {
r.Callbacks = append(r.Callbacks, callback)
}
func (r *Root) addNotificationCallback(callback func() interface{}) {
r.NotificationCallbacks = append(r.NotificationCallbacks, callback)
}
func (r *Root) Update(path string, data interface{}, strict bool, txid string, makeBranch t_makeBranch) *Revision {
var result *Revision
// FIXME: the more i look at this... i think i need to implement an interface for Node & root
if makeBranch == nil {
// TODO: raise error
}
if r.noCallbacks() {
// TODO: raise error
}
if txid != "" {
//dirtied := r.DirtyNodes[txid]
trackDirty := func(node *Node) *Branch {
//dirtied.Add(Node)
return node.makeTxBranch(txid)
}
result = r.Node.Update(path, data, strict, txid, trackDirty)
} else {
result = r.Node.Update(path, data, strict, "", nil)
}
r.executeCallbacks()
return result
}
func (r *Root) Add(path string, data interface{}, txid string, makeBranch t_makeBranch) *Revision {
var result *Revision
// FIXME: the more i look at this... i think i need to implement an interface for Node & root
if makeBranch == nil {
// TODO: raise error
}
if r.noCallbacks() {
// TODO: raise error
}
if txid != "" {
//dirtied := r.DirtyNodes[txid]
trackDirty := func(node *Node) *Branch {
//dirtied.Add(Node)
return node.makeTxBranch(txid)
}
result = r.Node.Add(path, data, txid, trackDirty)
} else {
result = r.Node.Add(path, data, "", nil)
}
r.executeCallbacks()
return result
}
func (r *Root) Remove(path string, txid string, makeBranch t_makeBranch) *Revision {
var result *Revision
// FIXME: the more i look at this... i think i need to implement an interface for Node & root
if makeBranch == nil {
// TODO: raise error
}
if r.noCallbacks() {
// TODO: raise error
}
if txid != "" {
//dirtied := r.DirtyNodes[txid]
trackDirty := func(node *Node) *Branch {
//dirtied.Add(Node)
return node.makeTxBranch(txid)
}
result = r.Node.Remove(path, txid, trackDirty)
} else {
result = r.Node.Remove(path, "", nil)
}
r.executeCallbacks()
return result
}
func (r *Root) Load(rootClass interface{}) *Root {
//fakeKvStore := &Backend{}
//root := NewRoot(rootClass, fakeKvStore, PersistedRevision{})
//r.KvStore = KvStore
r.loadFromPersistence(rootClass)
return r
}
func (r *Root) LoadLatest(hash string) {
r.Node.LoadLatest(r.KvStore, hash)
}
type rootData struct {
Latest string `json:GetLatest`
Tags map[string]string `json:Tags`
}
func (r *Root) loadFromPersistence(rootClass interface{}) {
var data rootData
r.Loading = true
blob, _ := r.KvStore.Get("root")
if err := json.Unmarshal(blob.Value.([]byte), &data); err != nil {
fmt.Errorf("problem to unmarshal blob - error:%s\n", err.Error())
}
for tag, hash := range data.Tags {
r.Node.LoadLatest(r.KvStore, hash)
r.Node.Tags[tag] = r.Node.Latest()
}
r.Node.LoadLatest(r.KvStore, data.Latest)
r.Loading = false
}