|
@@ -0,0 +1,340 @@
|
|
|
|
+package flowbuilder
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "encoding/json"
|
|
|
|
+ "errors"
|
|
|
|
+ "flow"
|
|
|
|
+ "flow/registry"
|
|
|
|
+ "fmt"
|
|
|
|
+ "log"
|
|
|
|
+ "reflect"
|
|
|
|
+ "strconv"
|
|
|
|
+ "time"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+// ErrLoop loop error
|
|
|
|
+var ErrLoop = errors.New("Looping is disabled for now")
|
|
|
|
+
|
|
|
|
+// FlowBuilder builds a flow from flow-ui json data
|
|
|
|
+type FlowBuilder struct {
|
|
|
|
+ registry *registry.R
|
|
|
|
+ doc *FlowDocument
|
|
|
|
+ flow *flow.Flow
|
|
|
|
+ nodeTrack map[string]bool
|
|
|
|
+ Err error
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// New creates a New builder
|
|
|
|
+func New(r *registry.R) *FlowBuilder {
|
|
|
|
+ return &FlowBuilder{
|
|
|
|
+ registry: r,
|
|
|
|
+ nodeTrack: map[string]bool{},
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Load document from json into builder
|
|
|
|
+func (fb *FlowBuilder) Load(rawData []byte) *FlowBuilder {
|
|
|
|
+ doc := &FlowDocument{[]Node{}, []Link{}, []Trigger{}}
|
|
|
|
+ log.Println("Loading document from:", string(rawData))
|
|
|
|
+ err := json.Unmarshal(rawData, doc)
|
|
|
|
+ if err != nil {
|
|
|
|
+ fb.Err = err
|
|
|
|
+ return fb
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fb.doc = doc
|
|
|
|
+ fb.flow = flow.New()
|
|
|
|
+ fb.flow.SetRegistry(fb.registry)
|
|
|
|
+
|
|
|
|
+ return fb
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Build a flow starting from node
|
|
|
|
+func (fb *FlowBuilder) Build(ID string) flow.Operation {
|
|
|
|
+ if fb.Err != nil {
|
|
|
|
+ op, _ := fb.flow.DefErrOp(ID, fb.Err)
|
|
|
|
+ return op
|
|
|
|
+ }
|
|
|
|
+ f := fb.flow
|
|
|
|
+ r := fb.registry
|
|
|
|
+ doc := fb.doc
|
|
|
|
+
|
|
|
|
+ if _, ok := fb.nodeTrack[ID]; ok {
|
|
|
|
+ fb.Err = ErrLoop //fmt.Errorf("[%v] Looping through nodes is disabled:", ID)
|
|
|
|
+ op, _ := fb.flow.DefErrOp(ID, fb.Err)
|
|
|
|
+ return op
|
|
|
|
+ }
|
|
|
|
+ fb.nodeTrack[ID] = true
|
|
|
|
+ defer delete(fb.nodeTrack, ID)
|
|
|
|
+
|
|
|
|
+ // If flow already has ID just return
|
|
|
|
+ if op := f.GetOp(ID); op != nil {
|
|
|
|
+ return op
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ node := fb.doc.fetchNodeByID(ID)
|
|
|
|
+ if node == nil {
|
|
|
|
+ op, _ := fb.flow.DefErrOp(ID, fmt.Errorf("node not found [%v]", ID))
|
|
|
|
+ return op
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var op flow.Operation
|
|
|
|
+
|
|
|
|
+ switch node.Src {
|
|
|
|
+ case "Input":
|
|
|
|
+ inputID, err := strconv.Atoi(node.Prop["input"])
|
|
|
|
+ if err != nil {
|
|
|
|
+ op, _ = f.DefErrOp(node.ID, errors.New("Invalid inputID value, must be a number"))
|
|
|
|
+ } else {
|
|
|
|
+ op, _ = f.In(inputID) // By id perhaps
|
|
|
|
+ }
|
|
|
|
+ case "Variable":
|
|
|
|
+ raw := node.Prop["init"]
|
|
|
|
+ val, err := parseValue(nil, raw)
|
|
|
|
+ if err != nil {
|
|
|
|
+ op, _ = f.DefErrOp(node.ID, err)
|
|
|
|
+ } else {
|
|
|
|
+ op = f.DefVar(node.ID, node.Label, val)
|
|
|
|
+ }
|
|
|
|
+ case "Const":
|
|
|
|
+ raw := node.Label
|
|
|
|
+ val, err := parseValue(nil, raw)
|
|
|
|
+ if err != nil {
|
|
|
|
+ op, _ = f.DefErrOp(node.ID, err)
|
|
|
|
+ } else {
|
|
|
|
+ op, _ = f.DefConst(node.ID, val)
|
|
|
|
+ }
|
|
|
|
+ default:
|
|
|
|
+ // Load entry
|
|
|
|
+ entry, err := r.Entry(node.Src)
|
|
|
|
+ if err != nil {
|
|
|
|
+ op, _ = f.DefErrOp(node.ID, err)
|
|
|
|
+ }
|
|
|
|
+ //// Process inputs ////
|
|
|
|
+ param := make([]flow.Data, len(entry.Inputs))
|
|
|
|
+ for i := range param {
|
|
|
|
+ l := doc.fetchLinkTo(node.ID, i)
|
|
|
|
+ if l == nil {
|
|
|
|
+ // Const value
|
|
|
|
+ v, err := parseValue(entry.Inputs[i], node.DefaultInputs[i])
|
|
|
|
+ if err != nil {
|
|
|
|
+ param[i], _ = f.ErrOp(err)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ param[i] = v
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ param[i] = fb.Build(l.From)
|
|
|
|
+ }
|
|
|
|
+ op, err = f.DefOp(node.ID, node.Src, param...)
|
|
|
|
+ if err != nil {
|
|
|
|
+ op, _ := f.DefErrOp(node.ID, err)
|
|
|
|
+ return op
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return op
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fb *FlowBuilder) addTriggersTo(node Node) error {
|
|
|
|
+ // Process triggers for this node
|
|
|
|
+ triggers := fb.doc.fetchTriggerFrom(node.ID)
|
|
|
|
+ for _, t := range triggers {
|
|
|
|
+ op := fb.Build(t.To)
|
|
|
|
+ // Register the thing here
|
|
|
|
+ fb.flow.Hook(flow.Hook{
|
|
|
|
+ Any: func(name string, ID string, triggerTime time.Time, extra ...interface{}) {
|
|
|
|
+ if name != "Error" && name != "Finish" {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if ID != t.From {
|
|
|
|
+ log.Printf("ID[%v] triggered [%v], I'm t.From: %v", ID, name, t.From)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ exec := false
|
|
|
|
+ for _, o := range t.On {
|
|
|
|
+ if name == o {
|
|
|
|
+ exec = true
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if !exec {
|
|
|
|
+ log.Println("Mismatching trigger, but its a test")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //op := opfb.flow.GetOp(t.To) // Repeating
|
|
|
|
+ go op.Process(name) // Background
|
|
|
|
+ },
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Flow returns the build flow
|
|
|
|
+func (fb *FlowBuilder) Flow() *flow.Flow {
|
|
|
|
+ return fb.flow
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// FlowBuild build the graph based on an starting node
|
|
|
|
+/*func FlowBuild(rawData []byte, r *registry.R, startingID string) (*flow.Flow, error) {
|
|
|
|
+
|
|
|
|
+ var build func(ID string) (flow.Operation, error)
|
|
|
|
+ build = func(ID string) (flow.Operation, error) {
|
|
|
|
+ if _, ok := nodeTrack[ID]; ok {
|
|
|
|
+ return nil, ErrLoop //fmt.Errorf("[%v] Looping through nodes is disabled:", ID)
|
|
|
|
+ }
|
|
|
|
+ nodeTrack[ID] = true
|
|
|
|
+ defer delete(nodeTrack, ID)
|
|
|
|
+
|
|
|
|
+ // If flow already has ID just return
|
|
|
|
+ if op := f.GetOp(ID); op != nil {
|
|
|
|
+ log.Println("Return operation")
|
|
|
|
+ return op, nil
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ node := doc.fetchNodeByID(ID)
|
|
|
|
+ if node == nil {
|
|
|
|
+ return nil, fmt.Errorf("node not found [%v]", startingID)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var op flow.Operation
|
|
|
|
+
|
|
|
|
+ switch node.Src {
|
|
|
|
+ case "Input":
|
|
|
|
+ inputID, err := strconv.Atoi(node.Prop["input"])
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, errors.New("Invalid inputID value, must be a number")
|
|
|
|
+ }
|
|
|
|
+ op, _ = f.In(inputID) // By id perhaps
|
|
|
|
+ case "Variable":
|
|
|
|
+ raw := node.Prop["init"]
|
|
|
|
+ val, err := parseValue(nil, raw)
|
|
|
|
+ if err != nil {
|
|
|
|
+ op, _ = f.DefErrOp(node.ID, err)
|
|
|
|
+ } else {
|
|
|
|
+ op = f.DefVar(node.ID, node.Label, val)
|
|
|
|
+ }
|
|
|
|
+ case "Const":
|
|
|
|
+ raw := node.Label
|
|
|
|
+ val, err := parseValue(nil, raw)
|
|
|
|
+ if err != nil {
|
|
|
|
+ op, _ = f.DefErrOp(node.ID, err)
|
|
|
|
+ } else {
|
|
|
|
+ op, _ = f.DefConst(node.ID, val)
|
|
|
|
+ }
|
|
|
|
+ default:
|
|
|
|
+ // Load entry
|
|
|
|
+ entry, err := r.Entry(node.Src)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ //// Process inputs ////
|
|
|
|
+ param := make([]flow.Data, len(entry.Inputs))
|
|
|
|
+ for i := range param {
|
|
|
|
+ l := doc.fetchLinkTo(node.ID, i)
|
|
|
|
+ if l == nil {
|
|
|
|
+ // Const value
|
|
|
|
+ v, err := parseValue(entry.Inputs[i], node.DefaultInputs[i])
|
|
|
|
+ if err != nil {
|
|
|
|
+ param[i], _ = f.ErrOp(err)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ param[i] = v
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ inOp, err := build(l.From)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ param[i] = inOp
|
|
|
|
+ }
|
|
|
|
+ op, err = f.DefOp(node.ID, node.Src, param...)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Process triggers for this node
|
|
|
|
+ triggers := doc.fetchTriggerFrom(node.ID)
|
|
|
|
+ for _, t := range triggers {
|
|
|
|
+ _, err := build(t.To)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ // Register the thing here
|
|
|
|
+ f.Hook(flow.Hook{
|
|
|
|
+ Any: func(name string, ID string, triggerTime time.Time, extra ...interface{}) {
|
|
|
|
+ if name != "Error" && name != "Finish" {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if ID != t.From {
|
|
|
|
+ log.Printf("ID[%v] triggered [%v], I'm t.From: %v", ID, name, t.From)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ exec := false
|
|
|
|
+ for _, o := range t.On {
|
|
|
|
+ if name == o {
|
|
|
|
+ exec = true
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if !exec {
|
|
|
|
+ log.Println("Mismatching trigger, but its a test")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ op := f.GetOp(t.To) // Repeating
|
|
|
|
+ go op.Process(name) // Background
|
|
|
|
+ },
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ return op, nil
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _, err = build(startingID)
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Println("Error building")
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ log.Println("Flow:", f)
|
|
|
|
+ return f, nil
|
|
|
|
+}*/
|
|
|
|
+
|
|
|
|
+// Or give a string
|
|
|
|
+func parseValue(typ reflect.Type, raw string) (flow.Data, error) {
|
|
|
|
+ if typ == nil {
|
|
|
|
+ var val flow.Data
|
|
|
|
+ err := json.Unmarshal([]byte(raw), &val)
|
|
|
|
+ if err != nil { // Try to unmarshal as a string?
|
|
|
|
+ val = string(raw)
|
|
|
|
+ }
|
|
|
|
+ return val, nil
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var ret flow.Data
|
|
|
|
+ switch typ.Kind() {
|
|
|
|
+ case reflect.Int:
|
|
|
|
+ v, err := strconv.Atoi(raw)
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Println("Wrong int conversion", err)
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ ret = v
|
|
|
|
+ case reflect.String:
|
|
|
|
+ ret = raw
|
|
|
|
+ default:
|
|
|
|
+ if len(raw) == 0 {
|
|
|
|
+ ret = reflect.Zero(typ)
|
|
|
|
+ } else {
|
|
|
|
+ refVal := reflect.New(typ)
|
|
|
|
+ err := json.Unmarshal([]byte(raw), refVal.Interface())
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ ret = refVal.Interface()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ log.Printf("Returning %#v", ret)
|
|
|
|
+ return ret, nil
|
|
|
|
+}
|