diff --git a/macro/annotation.html b/macro/annotation.html
new file mode 100644
index 00000000..3e258f2e
--- /dev/null
+++ b/macro/annotation.html
@@ -0,0 +1,4 @@
+
What Ho, Streamtools!
+
diff --git a/makefile b/makefile
index c4544aa6..01dd6d5b 100644
--- a/makefile
+++ b/makefile
@@ -7,6 +7,7 @@ all: $(BINARIES)
$(BLDDIR)/%:
go get github.com/jteeuwen/go-bindata/...
+ go-bindata -pkg=macros -o st/macros/static_bindata.go macro/...
go-bindata -pkg=server -o st/server/static_bindata.go gui/... examples/...
cd st/library && go get .
cd st/server && go get .
diff --git a/st/macros/macros.go b/st/macros/macros.go
new file mode 100644
index 00000000..c6fdd129
--- /dev/null
+++ b/st/macros/macros.go
@@ -0,0 +1,20 @@
+package macros
+
+import "log"
+
+var Macros = map[string]string{
+ "annotation": "macro/annotation.html",
+}
+
+var MacroDefs = map[string][]byte{}
+
+func Start() {
+ for k, macro := range Macros {
+ macroAsset, err := Asset(macro)
+ if err != nil {
+ log.Println("cannot find macro asset", macro)
+ continue
+ }
+ MacroDefs[k] = macroAsset
+ }
+}
diff --git a/st/main.go b/st/main.go
index 4d368772..99704ca0 100644
--- a/st/main.go
+++ b/st/main.go
@@ -2,12 +2,14 @@ package main
import (
"flag"
+ "log"
+ "os"
+
"github.com/nytlabs/streamtools/st/library"
"github.com/nytlabs/streamtools/st/loghub"
+ "github.com/nytlabs/streamtools/st/macros"
"github.com/nytlabs/streamtools/st/server"
"github.com/nytlabs/streamtools/st/util"
- "log"
- "os"
)
var (
@@ -28,6 +30,7 @@ func main() {
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
library.Start()
+ macros.Start()
loghub.Start()
s := server.NewServer()
diff --git a/st/server/api.go b/st/server/api.go
index e5fdfae9..036c11f2 100644
--- a/st/server/api.go
+++ b/st/server/api.go
@@ -533,6 +533,66 @@ func (s *Server) listBlockHandler(w http.ResponseWriter, r *http.Request) {
s.apiWrap(w, r, 200, blocks)
}
+// listMacroHandler retuns a slice of the current macros operating in the sytem.
+func (s *Server) listMacroHandler(w http.ResponseWriter, r *http.Request) {
+ s.manager.Mu.Lock()
+ defer s.manager.Mu.Unlock()
+
+ macros, err := json.Marshal(s.manager.ListMacros())
+ if err != nil {
+ s.apiWrap(w, r, 500, s.response(err.Error()))
+ return
+ }
+ s.apiWrap(w, r, 200, macros)
+}
+
+// createMacrosHandler asks the manager to create a macro and then return that
+// macro if the macro has been creates.
+func (s *Server) createMacroHandler(w http.ResponseWriter, r *http.Request) {
+ s.manager.Mu.Lock()
+ defer s.manager.Mu.Unlock()
+
+ var macro *MacroInfo
+
+ body, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ s.apiWrap(w, r, 500, s.response(err.Error()))
+ return
+ }
+
+ err = json.Unmarshal(body, ¯o)
+ if err != nil {
+ s.apiWrap(w, r, 500, s.response(err.Error()))
+ return
+ }
+
+ mmacro, err := s.manager.CreateMacro(macro)
+ if err != nil {
+ s.apiWrap(w, r, 500, s.response(err.Error()))
+ return
+ }
+
+ loghub.UI <- &loghub.LogMsg{
+ Type: loghub.CREATE,
+ Data: mmacro,
+ Id: s.Id,
+ }
+
+ loghub.Log <- &loghub.LogMsg{
+ Type: loghub.CREATE,
+ Data: fmt.Sprintf("Macro %s", mmacro.Id),
+ Id: s.Id,
+ }
+
+ jblock, err := json.Marshal(mmacro)
+ if err != nil {
+ s.apiWrap(w, r, 500, s.response(err.Error()))
+ return
+ }
+
+ s.apiWrap(w, r, 200, jblock)
+}
+
// createBlockHandler asks the manager to create a block and then return that block
// if the block has been creates.
func (s *Server) createBlockHandler(w http.ResponseWriter, r *http.Request) {
@@ -587,6 +647,53 @@ func (s *Server) createBlockHandler(w http.ResponseWriter, r *http.Request) {
s.apiWrap(w, r, 200, jblock)
}
+// updateMacroHandler updates the coordinates of a macro.
+func (s *Server) updateMacroHandler(w http.ResponseWriter, r *http.Request) {
+ s.manager.Mu.Lock()
+ defer s.manager.Mu.Unlock()
+
+ var macro *MacroInfo
+
+ body, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ s.apiWrap(w, r, 500, s.response(err.Error()))
+ return
+ }
+
+ err = json.Unmarshal(body, ¯o)
+ if err != nil {
+ s.apiWrap(w, r, 500, s.response(err.Error()))
+ return
+ }
+
+ mmacro, err := s.manager.UpdateMacro(macro)
+
+ if err != nil {
+ s.apiWrap(w, r, 500, s.response(err.Error()))
+ return
+ }
+
+ jblock, err := json.Marshal(mmacro)
+ if err != nil {
+ s.apiWrap(w, r, 500, s.response(err.Error()))
+ return
+ }
+
+ loghub.Log <- &loghub.LogMsg{
+ Type: loghub.UPDATE,
+ Data: fmt.Sprintf("Block %s", mmacro.Id),
+ Id: s.Id,
+ }
+
+ loghub.UI <- &loghub.LogMsg{
+ Type: loghub.UPDATE_POSITION,
+ Data: mmacro,
+ Id: s.Id,
+ }
+
+ s.apiWrap(w, r, 200, jblock)
+}
+
// updateBlockHandler updates the coordinates of a block.
// block.id and block.type can't be changes. block.rule is set through sendRoute
func (s *Server) updateBlockHandler(w http.ResponseWriter, r *http.Request) {
@@ -657,6 +764,41 @@ func (s *Server) blockInfoHandler(w http.ResponseWriter, r *http.Request) {
s.apiWrap(w, r, 200, jconn)
}
+// MacroInfoHandler returns a macro given an id
+func (s *Server) MacroInfoHandler(w http.ResponseWriter, r *http.Request) {
+ s.manager.Mu.Lock()
+ defer s.manager.Mu.Unlock()
+
+ vars := mux.Vars(r)
+
+ conn, err := s.manager.GetMacro(vars["id"])
+ if err != nil {
+ s.apiWrap(w, r, 500, s.response(err.Error()))
+ return
+ }
+
+ jconn, err := json.Marshal(conn)
+ if err != nil {
+ s.apiWrap(w, r, 500, s.response(err.Error()))
+ return
+ }
+ s.apiWrap(w, r, 200, jconn)
+}
+
+// deleteMacroHandler asks the block manager to delete a macro.
+func (s *Server) deleteMacroHandler(w http.ResponseWriter, r *http.Request) {
+ s.manager.Mu.Lock()
+ defer s.manager.Mu.Unlock()
+
+ vars := mux.Vars(r)
+ _, err := s.manager.DeleteMacro(vars["id"])
+ if err != nil {
+ s.apiWrap(w, r, 500, s.response(err.Error()))
+ return
+ }
+ s.apiWrap(w, r, 200, s.response("OK"))
+}
+
// deleteBlockHandler asks the block manager to delete a block.
func (s *Server) deleteBlockHandler(w http.ResponseWriter, r *http.Request) {
s.manager.Mu.Lock()
@@ -1063,6 +1205,13 @@ func (s *Server) Run() {
r.HandleFunc("/import", s.importHandler).Methods("POST")
r.HandleFunc("/import", s.optionsHandler).Methods("OPTIONS")
r.HandleFunc("/export", s.exportHandler).Methods("GET")
+
+ r.HandleFunc("/macros", s.listMacroHandler).Methods("GET") // list all blocks
+ r.HandleFunc("/macros", s.createMacroHandler).Methods("POST") // create block w/o id
+ r.HandleFunc("/macros/{id}", s.MacroInfoHandler).Methods("GET") // get block info
+ r.HandleFunc("/macros/{id}", s.updateMacroHandler).Methods("PUT") // update block
+ r.HandleFunc("/macros/{id}", s.deleteMacroHandler).Methods("DELETE") // delete block
+
r.HandleFunc("/blocks", s.listBlockHandler).Methods("GET") // list all blocks
r.HandleFunc("/blocks", s.createBlockHandler).Methods("POST") // create block w/o id
r.HandleFunc("/blocks", s.optionsHandler).Methods("OPTIONS") // allow cross-domain
diff --git a/st/server/blockmanager.go b/st/server/blockmanager.go
index 500cca49..ae60a922 100644
--- a/st/server/blockmanager.go
+++ b/st/server/blockmanager.go
@@ -3,12 +3,14 @@ package server
import (
"errors"
"fmt"
- "github.com/nytlabs/streamtools/st/blocks"
- "github.com/nytlabs/streamtools/st/library"
"net/url"
"strconv"
"sync"
"time"
+
+ "github.com/nytlabs/streamtools/st/blocks"
+ "github.com/nytlabs/streamtools/st/library"
+ "github.com/nytlabs/streamtools/st/macros"
)
type BlockInfo struct {
@@ -27,14 +29,29 @@ type ConnectionInfo struct {
chans blocks.BlockChans
}
+type MacroInfo struct {
+ Id string
+ Type string
+ Dimensions *Dims
+ Content string
+}
+
type Coords struct {
X float64
Y float64
}
+type Dims struct {
+ X float64
+ Y float64
+ W float64
+ H float64
+}
+
type BlockManager struct {
blockMap map[string]*BlockInfo
connMap map[string]*ConnectionInfo
+ macroMap map[string]*MacroInfo
genId chan string
Mu *sync.Mutex
}
@@ -54,6 +71,7 @@ func NewBlockManager() *BlockManager {
return &BlockManager{
blockMap: make(map[string]*BlockInfo),
connMap: make(map[string]*ConnectionInfo),
+ macroMap: make(map[string]*MacroInfo),
genId: idChan,
Mu: &sync.Mutex{},
}
@@ -72,7 +90,8 @@ func (b *BlockManager) GetId() string {
func (b *BlockManager) IdExists(id string) bool {
_, okB := b.blockMap[id]
_, okC := b.connMap[id]
- return okB || okC
+ _, okD := b.macroMap[id]
+ return okB || okC || okD
}
func (b *BlockManager) IdSafe(id string) bool {
@@ -156,6 +175,15 @@ func (b *BlockManager) UpdateBlock(id string, coord *Coords) (*BlockInfo, error)
return block, nil
}
+func (b *BlockManager) UpdateMacro(macroInfo *MacroInfo) (*MacroInfo, error) {
+ _, ok := b.macroMap[macroInfo.Id]
+ if !ok {
+ return nil, errors.New(fmt.Sprintf("Cannot update macro %s: does not exist", macroInfo.Id))
+ }
+ b.macroMap[macroInfo.Id] = macroInfo
+ return macroInfo, nil
+}
+
func (b *BlockManager) Send(id string, route string, msg interface{}) error {
_, ok := b.blockMap[id]
if !ok {
@@ -297,6 +325,37 @@ func (b *BlockManager) Connect(connInfo *ConnectionInfo) (*ConnectionInfo, error
return connInfo, nil
}
+func (b *BlockManager) CreateMacro(macroInfo *MacroInfo) (*MacroInfo, error) {
+ if macroInfo == nil {
+ return nil, errors.New("Cannot create: no macro data.")
+ }
+
+ // check to see if the ID is OK
+ if !b.IdSafe(macroInfo.Id) {
+ return nil, errors.New(fmt.Sprintf("Cannot create macro %s: invalid id", macroInfo.Id))
+ }
+
+ // create ID if there is none
+ if macroInfo.Id == "" {
+ macroInfo.Id = b.GetId()
+ }
+
+ // make sure ID doesn't already exist
+ if b.IdExists(macroInfo.Id) {
+ return nil, errors.New(fmt.Sprintf("Cannot create macro %s: id already exists", macroInfo.Id))
+ }
+
+ content, ok := macros.MacroDefs[macroInfo.Type]
+ if !ok {
+ return nil, errors.New("requested macro does not exist in macros library")
+ }
+ macroInfo.Content = string(content)
+
+ b.macroMap[macroInfo.Id] = macroInfo
+
+ return macroInfo, nil
+}
+
func (b *BlockManager) GetSocket(fromId string) (chan *blocks.Msg, string, error) {
_, ok := b.blockMap[fromId]
if !ok {
@@ -352,6 +411,15 @@ func (b *BlockManager) GetBlock(id string) (*BlockInfo, error) {
return block, nil
}
+func (b *BlockManager) GetMacro(id string) (*MacroInfo, error) {
+ macro, ok := b.macroMap[id]
+ if !ok {
+ return nil, errors.New(fmt.Sprintf("Cannot get macro %s: does not exist", id))
+ }
+
+ return macro, nil
+}
+
func (b *BlockManager) GetConnection(id string) (*ConnectionInfo, error) {
_, ok := b.connMap[id]
if !ok {
@@ -416,6 +484,17 @@ func (b *BlockManager) DeleteConnection(id string) (string, error) {
return id, nil
}
+func (b *BlockManager) DeleteMacro(id string) (string, error) {
+ _, ok := b.macroMap[id]
+ if !ok {
+ return "", errors.New(fmt.Sprintf("Cannot delete macro %s: does not exist", id))
+ }
+
+ delete(b.macroMap, id)
+
+ return id, nil
+}
+
func (b *BlockManager) StatusBlocks() []string {
var wg sync.WaitGroup
MsgChan := make(chan string, len(b.blockMap))
@@ -470,3 +549,13 @@ func (b *BlockManager) ListConnections() []*ConnectionInfo {
}
return conns
}
+
+func (b *BlockManager) ListMacros() []*MacroInfo {
+ i := 0
+ macros := make([]*MacroInfo, len(b.macroMap), len(b.macroMap))
+ for _, v := range b.macroMap {
+ macros[i] = v
+ i++
+ }
+ return macros
+}