Fixed and improved events management.
authorSebastien Douheret <sebastien.douheret@iot.bzh>
Fri, 15 Dec 2017 23:10:14 +0000 (00:10 +0100)
committerSebastien Douheret <sebastien.douheret@iot.bzh>
Fri, 15 Dec 2017 23:10:14 +0000 (00:10 +0100)
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
12 files changed:
.vscode/launch.json
lib/syncthing/stEvent.go
lib/syncthing/stfolder.go
lib/xdsserver/apiv1-events.go
lib/xdsserver/events.go [new file with mode: 0644]
lib/xdsserver/folder-interface.go
lib/xdsserver/folder-pathmap.go
lib/xdsserver/folder-st-disable.go
lib/xdsserver/folder-st.go
lib/xdsserver/folders.go
lib/xdsserver/xdsserver.go
lib/xsapiv1/events.go

index 5583251..245c2ee 100644 (file)
@@ -13,7 +13,7 @@
                 "GOPATH": "${workspaceRoot}/../../../..:${env:GOPATH}",
                 "ROOT_DIR": "${workspaceRoot}/../../../.."
             },
-            "args": ["-log", "debug", "-c", "config.json.in"],
+            "args": ["-log", "debug"],
             "showLog": false
         },
         {
@@ -27,7 +27,8 @@
             "program": "${workspaceRoot}",
             "env": {
                 "GOPATH": "${workspaceRoot}/../../../..:${env:GOPATH}",
-                "ROOT_DIR": "${workspaceRoot}/../../../.."
+                "ROOT_DIR": "${workspaceRoot}/../../../..",
+                "XDS_LOG_SILLY": "0"
             },
             "args": ["-log", "debug", "-c", "__config_local_dev.json"],
             "showLog": false
index 6cb7a31..948f88a 100644 (file)
@@ -26,6 +26,8 @@ import (
        "time"
 
        "github.com/Sirupsen/logrus"
+       uuid "github.com/satori/go.uuid"
+       "github.com/syncthing/syncthing/lib/sync"
 )
 
 // Events .
@@ -37,6 +39,7 @@ type Events struct {
        st    *SyncThing
        log   *logrus.Logger
        cbArr map[string][]cbMap
+       mutex sync.Mutex
 }
 
 type Event struct {
@@ -75,7 +78,7 @@ type STEvent struct {
 }
 
 type cbMap struct {
-       id       int
+       id       string
        cb       EventsCB
        filterID string
        data     *EventsCBData
@@ -91,6 +94,7 @@ func (s *SyncThing) NewEventListener() *Events {
                st:          s,
                log:         s.log,
                cbArr:       make(map[string][]cbMap),
+               mutex:       sync.NewMutex(),
        }
 }
 
@@ -106,21 +110,24 @@ func (e *Events) Stop() {
 }
 
 // Register Add a listener on an event
-func (e *Events) Register(evName string, cb EventsCB, filterID string, data *EventsCBData) (int, error) {
+func (e *Events) Register(evName string, cb EventsCB, filterID string, data *EventsCBData) (string, error) {
        if evName == "" || !strings.Contains(EventsAll, evName) {
-               return -1, fmt.Errorf("Unknown event name")
+               return "", fmt.Errorf("Unknown event name")
        }
        if data == nil {
                data = &EventsCBData{}
        }
 
+       e.mutex.Lock()
+       defer e.mutex.Unlock()
+
        cbList := []cbMap{}
        if _, ok := e.cbArr[evName]; ok {
                cbList = e.cbArr[evName]
        }
 
-       id := len(cbList)
-       (*data)["id"] = strconv.Itoa(id)
+       id := uuid.NewV1().String()
+       (*data)["id"] = id
 
        e.cbArr[evName] = append(cbList, cbMap{id: id, cb: cb, filterID: filterID, data: data})
 
@@ -128,19 +135,23 @@ func (e *Events) Register(evName string, cb EventsCB, filterID string, data *Eve
 }
 
 // UnRegister Remove a listener event
-func (e *Events) UnRegister(evName string, id int) error {
-       cbKey, ok := e.cbArr[evName]
-       if !ok {
-               return fmt.Errorf("No event registered to such name")
-       }
-
-       // FIXME - NOT TESTED
-       if id >= len(cbKey) {
-               return fmt.Errorf("Invalid id")
-       } else if id == len(cbKey) {
-               e.cbArr[evName] = cbKey[:id-1]
-       } else {
-               e.cbArr[evName] = cbKey[id : id+1]
+func (e *Events) UnRegister(id string) error {
+       e.mutex.Lock()
+       defer e.mutex.Unlock()
+
+       for evName, cbKey := range e.cbArr {
+               newCbList := []cbMap{}
+               change := false
+               for _, k := range cbKey {
+                       if k.id != id {
+                               newCbList = append(newCbList, k)
+                       } else {
+                               change = true
+                       }
+               }
+               if change {
+                       e.cbArr[evName] = newCbList
+               }
        }
 
        return nil
@@ -207,8 +218,10 @@ func (e *Events) monitorLoop() {
                                        e.log.Warnf("ST EVENT: %d %s\n  %v", stEv.GlobalID, stEv.Type, stEv)
                                }
 
+                               e.mutex.Lock()
                                cbKey, ok := e.cbArr[stEv.Type]
                                if !ok {
+                                       e.mutex.Unlock()
                                        continue
                                }
 
@@ -264,6 +277,8 @@ func (e *Events) monitorLoop() {
                                                c.cb(evData, c.data)
                                        }
                                }
+
+                               e.mutex.Unlock()
                        }
                }
        }
index d67b164..1dcbfe1 100644 (file)
@@ -70,6 +70,12 @@ func (s *SyncThing) FolderLoadFromStConfig(f *[]xsapiv1.FolderConfig) error {
 
 // FolderChange is called when configuration has changed
 func (s *SyncThing) FolderChange(f xsapiv1.FolderConfig) (string, error) {
+       var label, id string
+
+       if id = f.ID; id == "" {
+               s.log.Errorln("Try to create Syncthing folder with null ID: %v", f)
+               return "", fmt.Errorf("Cannot create Syncthing folder (ID must be set")
+       }
 
        // Get current config
        stCfg, err := s.ConfigGet()
@@ -104,13 +110,9 @@ func (s *SyncThing) FolderChange(f xsapiv1.FolderConfig) (string, error) {
        }
 
        // Add or update Folder settings
-       var label, id string
        if label = f.Label; label == "" {
                label = strings.Split(id, "/")[0]
        }
-       if id = f.ID; id == "" {
-               id = stClientID[0:15] + "_" + label
-       }
 
        folder := stconfig.FolderConfiguration{
                ID:    id,
index 9f0a774..0942753 100644 (file)
@@ -19,8 +19,6 @@ package xdsserver
 
 import (
        "net/http"
-       "strings"
-       "time"
 
        "github.com/gin-gonic/gin"
        common "github.com/iotbzh/xds-common/golib"
@@ -29,14 +27,14 @@ import (
 
 // eventsList Registering for events that will be send over a WS
 func (s *APIService) eventsList(c *gin.Context) {
-
+       c.JSON(http.StatusOK, s.events.GetList())
 }
 
 // eventsRegister Registering for events that will be send over a WS
 func (s *APIService) eventsRegister(c *gin.Context) {
        var args xsapiv1.EventRegisterArgs
 
-       if c.BindJSON(&args) != nil {
+       if c.BindJSON(&args) != nil || args.Name == "" {
                common.APIError(c, "Invalid arguments")
                return
        }
@@ -47,82 +45,8 @@ func (s *APIService) eventsRegister(c *gin.Context) {
                return
        }
 
-       evType := strings.TrimPrefix(xsapiv1.EVTFolderStateChange, xsapiv1.EventTypePrefix)
-       if args.Name != evType {
-               common.APIError(c, "Unsupported event name")
-               return
-       }
-
-       /* XXX - to be removed if no plan to support "generic" event
-       var cbFunc st.EventsCB
-       cbFunc = func(ev st.Event, data *st.EventsCBData) {
-
-               evid, _ := strconv.Atoi((*data)["id"].(string))
-               ssid := (*data)["sid"].(string)
-               so := s.sessions.IOSocketGet(ssid)
-               if so == nil {
-                       s.log.Infof("Event %s not emitted - sid: %s", ev.Type, ssid)
-
-                       // Consider that client disconnected, so unregister this event
-                       s.mfolders.SThg.Events.UnRegister(ev.Type, evid)
-                       return
-               }
-
-               msg := EventMsg{
-                       Time: ev.Time,
-                       Type: ev.Type,
-                       Data: ev.Data,
-               }
-
-               if err := (*so).Emit(EVTAll, msg); err != nil {
-                       s.log.Errorf("WS Emit Event : %v", err)
-               }
-
-               if err := (*so).Emit(EventTypePrefix+ev.Type, msg); err != nil {
-                       s.log.Errorf("WS Emit Event : %v", err)
-               }
-       }
-
-       data := make(st.EventsCBData)
-       data["sid"] = sess.ID
-
-       id, err := s.mfolders.SThg.Events.Register(args.Name, cbFunc, args.ProjectID, &data)
-       */
-
-       var cbFunc FolderEventCB
-       cbFunc = func(cfg *xsapiv1.FolderConfig, data *FolderEventCBData) {
-               ssid := (*data)["sid"].(string)
-               so := s.sessions.IOSocketGet(ssid)
-               if so == nil {
-                       //s.log.Infof("Event %s not emitted - sid: %s", ev.Type, ssid)
-
-                       // Consider that client disconnected, so unregister this event
-                       // SEB FIXMEs.mfolders.RegisterEventChange(ev.Type)
-                       return
-               }
-
-               msg := xsapiv1.EventMsg{
-                       Time:   time.Now().String(),
-                       Type:   evType,
-                       Folder: *cfg,
-               }
-
-               s.Log.Debugf("WS Emit %s - Status=%10s, IsInSync=%6v, ID=%s",
-                       xsapiv1.EventTypePrefix+evType, cfg.Status, cfg.IsInSync, cfg.ID)
-
-               if err := (*so).Emit(xsapiv1.EventTypePrefix+evType, msg); err != nil {
-                       s.Log.Errorf("WS Emit Folder StateChanged event : %v", err)
-               }
-       }
-       data := make(FolderEventCBData)
-       data["sid"] = sess.ID
-
-       prjID, err := s.mfolders.ResolveID(args.ProjectID)
-       if err != nil {
-               common.APIError(c, err.Error())
-               return
-       }
-       if err = s.mfolders.RegisterEventChange(prjID, &cbFunc, &data); err != nil {
+       // Register to all or to a specific events
+       if err := s.events.Register(args.Name, sess.ID); err != nil {
                common.APIError(c, err.Error())
                return
        }
@@ -134,16 +58,22 @@ func (s *APIService) eventsRegister(c *gin.Context) {
 func (s *APIService) eventsUnRegister(c *gin.Context) {
        var args xsapiv1.EventUnRegisterArgs
 
-       if c.BindJSON(&args) != nil || args.Name == "" || args.ID < 0 {
+       if c.BindJSON(&args) != nil || args.Name == "" {
                common.APIError(c, "Invalid arguments")
                return
        }
-       /* TODO
-       if err := s.mfolders.SThg.Events.UnRegister(args.Name, args.ID); err != nil {
+
+       sess := s.sessions.Get(c)
+       if sess == nil {
+               common.APIError(c, "Unknown sessions")
+               return
+       }
+
+       // Register to all or to a specific events
+       if err := s.events.UnRegister(args.Name, sess.ID); err != nil {
                common.APIError(c, err.Error())
                return
        }
+
        c.JSON(http.StatusOK, gin.H{"status": "OK"})
-       */
-       common.APIError(c, "Not implemented yet")
 }
diff --git a/lib/xdsserver/events.go b/lib/xdsserver/events.go
new file mode 100644 (file)
index 0000000..007b89a
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 "IoT.bzh"
+ * Author Sebastien Douheret <sebastien@iot.bzh>
+ *
+ * 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 xdsserver
+
+import (
+       "fmt"
+       "time"
+
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
+)
+
+// EventDef Definition on one event
+type EventDef struct {
+       sids map[string]int
+}
+
+// Events Hold registered events per context
+type Events struct {
+       *Context
+       eventsMap map[string]*EventDef
+}
+
+// NewEvents creates an instance of Events
+func NewEvents(ctx *Context) *Events {
+       evMap := make(map[string]*EventDef)
+       for _, ev := range xsapiv1.EVTAllList {
+               evMap[ev] = &EventDef{
+                       sids: make(map[string]int),
+               }
+       }
+       return &Events{
+               Context:   ctx,
+               eventsMap: evMap,
+       }
+}
+
+// GetList returns the list of all supported events
+func (e *Events) GetList() []string {
+       return xsapiv1.EVTAllList
+}
+
+// Register Used by a client/session to register to a specific (or all) event(s)
+func (e *Events) Register(evName, sessionID string) error {
+       evs := xsapiv1.EVTAllList
+       if evName != xsapiv1.EVTAll {
+               if _, ok := e.eventsMap[evName]; !ok {
+                       return fmt.Errorf("Unsupported event type name")
+               }
+               evs = []string{evName}
+       }
+       for _, ev := range evs {
+               e.eventsMap[ev].sids[sessionID]++
+       }
+       return nil
+}
+
+// UnRegister Used by a client/session to un-register event(s)
+func (e *Events) UnRegister(evName, sessionID string) error {
+       evs := xsapiv1.EVTAllList
+       if evName != xsapiv1.EVTAll {
+               if _, ok := e.eventsMap[evName]; !ok {
+                       return fmt.Errorf("Unsupported event type name")
+               }
+               evs = []string{evName}
+       }
+       for _, ev := range evs {
+               if _, exist := e.eventsMap[ev].sids[sessionID]; exist {
+                       delete(e.eventsMap[ev].sids, sessionID)
+                       break
+               }
+       }
+       return nil
+}
+
+// Emit Used to manually emit an event
+func (e *Events) Emit(evName string, data interface{}, fromSid string) error {
+       var firstErr error
+
+       if _, ok := e.eventsMap[evName]; !ok {
+               return fmt.Errorf("Unsupported event type")
+       }
+
+       firstErr = nil
+       evm := e.eventsMap[evName]
+       e.LogSillyf("Emit Event %s: len(sids)=%d, data=%v", evName, len(evm.sids), data)
+       for sid := range evm.sids {
+               so := e.sessions.IOSocketGet(sid)
+               if so == nil {
+                       if firstErr == nil {
+                               firstErr = fmt.Errorf("IOSocketGet return nil (SID=%v)", sid)
+                       }
+                       continue
+               }
+               msg := xsapiv1.EventMsg{
+                       Time:          time.Now().String(),
+                       FromSessionID: fromSid,
+                       Type:          evName,
+                       Data:          data,
+               }
+               e.Log.Debugf("Emit Event %s: %v", evName, sid)
+               if err := (*so).Emit(evName, msg); err != nil {
+                       e.Log.Errorf("WS Emit %v error : %v", evName, err)
+                       if firstErr == nil {
+                               firstErr = err
+                       }
+               }
+       }
+
+       return firstErr
+}
index 2b836e7..151530c 100644 (file)
@@ -24,16 +24,15 @@ type FolderEventCB func(cfg *xsapiv1.FolderConfig, data *FolderEventCBData)
 
 // IFOLDER Folder interface
 type IFOLDER interface {
-       NewUID(suffix string) string                                          // Get a new folder UUID
-       Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error)              // Add a new folder
-       GetConfig() xsapiv1.FolderConfig                                        // Get folder public configuration
-       GetFullPath(dir string) string                                        // Get folder full path
-       ConvPathCli2Svr(s string) string                                      // Convert path from Client to Server
-       ConvPathSvr2Cli(s string) string                                      // Convert path from Server to Client
-       Remove() error                                                        // Remove a folder
-       Update(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error)           // Update a new folder
-       RegisterEventChange(cb *FolderEventCB, data *FolderEventCBData) error // Request events registration (sent through WS)
-       UnRegisterEventChange() error                                         // Un-register events
-       Sync() error                                                          // Force folder files synchronization
-       IsInSync() (bool, error)                                              // Check if folder files are in-sync
+       NewUID(suffix string) string                                    // Get a new folder UUID
+       Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error)    // Add a new folder
+       Setup(prj xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error)  // Local setup of the folder
+       GetConfig() xsapiv1.FolderConfig                                // Get folder public configuration
+       GetFullPath(dir string) string                                  // Get folder full path
+       ConvPathCli2Svr(s string) string                                // Convert path from Client to Server
+       ConvPathSvr2Cli(s string) string                                // Convert path from Server to Client
+       Remove() error                                                  // Remove a folder
+       Update(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) // Update a new folder
+       Sync() error                                                    // Force folder files synchronization
+       IsInSync() (bool, error)                                        // Check if folder files are in-sync
 }
index bb33a98..0452b13 100644 (file)
@@ -34,14 +34,14 @@ import (
 // PathMap .
 type PathMap struct {
        *Context
-       config xsapiv1.FolderConfig
+       fConfig xsapiv1.FolderConfig
 }
 
 // NewFolderPathMap Create a new instance of PathMap
 func NewFolderPathMap(ctx *Context) *PathMap {
        f := PathMap{
                Context: ctx,
-               config: xsapiv1.FolderConfig{
+               fConfig: xsapiv1.FolderConfig{
                        Status: xsapiv1.StatusDisable,
                },
        }
@@ -59,6 +59,12 @@ func (f *PathMap) NewUID(suffix string) string {
 
 // Add a new folder
 func (f *PathMap) Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
+       return f.Setup(cfg)
+}
+
+// Setup Setup local project config
+func (f *PathMap) Setup(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
+
        if cfg.DataPathMap.ServerPath == "" {
                return nil, fmt.Errorf("ServerPath must be set")
        }
@@ -80,10 +86,10 @@ func (f *PathMap) Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
                return nil, fmt.Errorf("ServerPath directory is not accessible: %s", dir)
        }
 
-       f.config = cfg
-       f.config.RootPath = dir
-       f.config.DataPathMap.ServerPath = dir
-       f.config.IsInSync = true
+       f.fConfig = cfg
+       f.fConfig.RootPath = dir
+       f.fConfig.DataPathMap.ServerPath = dir
+       f.fConfig.IsInSync = true
 
        // Verify file created by XDS agent when needed
        if cfg.DataPathMap.CheckFile != "" {
@@ -116,30 +122,30 @@ func (f *PathMap) Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
                }
        }
 
-       f.config.Status = xsapiv1.StatusEnable
+       f.fConfig.Status = xsapiv1.StatusEnable
 
-       return &f.config, nil
+       return &f.fConfig, nil
 }
 
 // GetConfig Get public part of folder config
 func (f *PathMap) GetConfig() xsapiv1.FolderConfig {
-       return f.config
+       return f.fConfig
 }
 
 // GetFullPath returns the full path of a directory (from server POV)
 func (f *PathMap) GetFullPath(dir string) string {
        if &dir == nil {
-               return f.config.DataPathMap.ServerPath
+               return f.fConfig.DataPathMap.ServerPath
        }
-       return filepath.Join(f.config.DataPathMap.ServerPath, dir)
+       return filepath.Join(f.fConfig.DataPathMap.ServerPath, dir)
 }
 
 // ConvPathCli2Svr Convert path from Client to Server
 func (f *PathMap) ConvPathCli2Svr(s string) string {
-       if f.config.ClientPath != "" && f.config.DataPathMap.ServerPath != "" {
+       if f.fConfig.ClientPath != "" && f.fConfig.DataPathMap.ServerPath != "" {
                return strings.Replace(s,
-                       f.config.ClientPath,
-                       f.config.DataPathMap.ServerPath,
+                       f.fConfig.ClientPath,
+                       f.fConfig.DataPathMap.ServerPath,
                        -1)
        }
        return s
@@ -147,10 +153,10 @@ func (f *PathMap) ConvPathCli2Svr(s string) string {
 
 // ConvPathSvr2Cli Convert path from Server to Client
 func (f *PathMap) ConvPathSvr2Cli(s string) string {
-       if f.config.ClientPath != "" && f.config.DataPathMap.ServerPath != "" {
+       if f.fConfig.ClientPath != "" && f.fConfig.DataPathMap.ServerPath != "" {
                return strings.Replace(s,
-                       f.config.DataPathMap.ServerPath,
-                       f.config.ClientPath,
+                       f.fConfig.DataPathMap.ServerPath,
+                       f.fConfig.ClientPath,
                        -1)
        }
        return s
@@ -164,21 +170,11 @@ func (f *PathMap) Remove() error {
 
 // Update update some fields of a folder
 func (f *PathMap) Update(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
-       if f.config.ID != cfg.ID {
+       if f.fConfig.ID != cfg.ID {
                return nil, fmt.Errorf("Invalid id")
        }
-       f.config = cfg
-       return &f.config, nil
-}
-
-// RegisterEventChange requests registration for folder change event
-func (f *PathMap) RegisterEventChange(cb *FolderEventCB, data *FolderEventCBData) error {
-       return nil
-}
-
-// UnRegisterEventChange remove registered callback
-func (f *PathMap) UnRegisterEventChange() error {
-       return nil
+       f.fConfig = cfg
+       return &f.fConfig, nil
 }
 
 // Sync Force folder files synchronization
index 4dbe2a9..c52854d 100644 (file)
@@ -29,7 +29,7 @@ import (
 // STFolderDisable .
 type STFolderDisable struct {
        *Context
-       config xsapiv1.FolderConfig
+       fConfig xsapiv1.FolderConfig
 }
 
 // NewFolderSTDisable Create a new instance of STFolderDisable
@@ -51,15 +51,20 @@ func (f *STFolderDisable) NewUID(suffix string) string {
 
 // Add a new folder
 func (f *STFolderDisable) Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
-       f.config = cfg
-       f.config.Status = xsapiv1.StatusDisable
-       f.config.IsInSync = false
-       return &f.config, nil
+       return f.Setup(cfg)
+}
+
+// Setup Setup local project config
+func (f *STFolderDisable) Setup(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
+       f.fConfig = cfg
+       f.fConfig.Status = xsapiv1.StatusDisable
+       f.fConfig.IsInSync = false
+       return &f.fConfig, nil
 }
 
 // GetConfig Get public part of folder config
 func (f *STFolderDisable) GetConfig() xsapiv1.FolderConfig {
-       return f.config
+       return f.fConfig
 }
 
 // GetFullPath returns the full path of a directory (from server POV)
@@ -87,16 +92,6 @@ func (f *STFolderDisable) Update(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfi
        return nil, nil
 }
 
-// RegisterEventChange requests registration for folder change event
-func (f *STFolderDisable) RegisterEventChange(cb *FolderEventCB, data *FolderEventCBData) error {
-       return nil
-}
-
-// UnRegisterEventChange remove registered callback
-func (f *STFolderDisable) UnRegisterEventChange() error {
-       return nil
-}
-
 // Sync Force folder files synchronization
 func (f *STFolderDisable) Sync() error {
        return nil
index c8f718a..9cbb570 100644 (file)
@@ -23,8 +23,8 @@ import (
        "path/filepath"
        "strings"
 
-       "github.com/iotbzh/xds-server/lib/xsapiv1"
        st "github.com/iotbzh/xds-server/lib/syncthing"
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
        uuid "github.com/satori/go.uuid"
        "github.com/syncthing/syncthing/lib/config"
 )
@@ -34,14 +34,14 @@ import (
 // STFolder .
 type STFolder struct {
        *Context
-       st                *st.SyncThing
-       fConfig           xsapiv1.FolderConfig
-       stfConfig         config.FolderConfiguration
-       eventIDs          []int
-       eventChangeCB     *FolderEventCB
-       eventChangeCBData *FolderEventCBData
+       st        *st.SyncThing
+       fConfig   xsapiv1.FolderConfig
+       stfConfig config.FolderConfiguration
+       eventIDs  []string
 }
 
+var stEventMonitored = []string{st.EventStateChanged, st.EventFolderPaused}
+
 // NewFolderST Create a new instance of STFolder
 func NewFolderST(ctx *Context, sthg *st.SyncThing) *STFolder {
        return &STFolder{
@@ -79,33 +79,42 @@ func (f *STFolder) Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error)
        f.fConfig = cfg
 
        // Update Syncthing folder
-       // (except if status is ErrorConfig)
-       // TODO: add cache to avoid multiple requests on startup
-       if f.fConfig.Status != xsapiv1.StatusErrorConfig {
-               id, err := f.st.FolderChange(f.fConfig)
-               if err != nil {
-                       return nil, err
-               }
+       _, err := f.st.FolderChange(f.fConfig)
+       if err != nil {
+               return nil, err
+       }
 
-               f.stfConfig, err = f.st.FolderConfigGet(id)
+       // Use Setup function to setup remains fields
+       return f.Setup(f.fConfig)
+}
+
+// Setup Setup local project config
+func (f *STFolder) Setup(fld xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
+
+       var err error
+
+       // Update folder Config
+       f.fConfig = fld
+
+       // Retrieve Syncthing folder config
+       f.stfConfig, err = f.st.FolderConfigGet(f.fConfig.ID)
+       if err != nil {
+               f.fConfig.Status = xsapiv1.StatusErrorConfig
+               return nil, err
+       }
+
+       // Register to events to update folder status
+       for _, evName := range stEventMonitored {
+               evID, err := f.st.Events.Register(evName, f.cbEventState, f.fConfig.ID, nil)
                if err != nil {
-                       f.fConfig.Status = xsapiv1.StatusErrorConfig
                        return nil, err
                }
-
-               // Register to events to update folder status
-               for _, evName := range []string{st.EventStateChanged, st.EventFolderPaused} {
-                       evID, err := f.st.Events.Register(evName, f.cbEventState, id, nil)
-                       if err != nil {
-                               return nil, err
-                       }
-                       f.eventIDs = append(f.eventIDs, evID)
-               }
-
-               f.fConfig.IsInSync = false // will be updated later by events
-               f.fConfig.Status = xsapiv1.StatusEnable
+               f.eventIDs = append(f.eventIDs, evID)
        }
 
+       f.fConfig.IsInSync = false // will be updated later by events
+       f.fConfig.Status = xsapiv1.StatusEnable
+
        return &f.fConfig, nil
 }
 
@@ -149,15 +158,27 @@ func (f *STFolder) ConvPathSvr2Cli(s string) string {
 
 // Remove a folder
 func (f *STFolder) Remove() error {
-       err := f.st.FolderDelete(f.stfConfig.ID)
+       var err1 error
+       // Un-register events
+       for _, evID := range f.eventIDs {
+               if err := f.st.Events.UnRegister(evID); err != nil && err1 == nil {
+                       // only report 1st error
+                       err1 = err
+               }
+       }
+
+       // Delete in Syncthing
+       err2 := f.st.FolderDelete(f.stfConfig.ID)
 
        // Delete folder on server side
-       err2 := os.RemoveAll(f.GetFullPath(""))
+       err3 := os.RemoveAll(f.GetFullPath(""))
 
-       if err != nil {
-               return err
+       if err1 != nil {
+               return err1
+       } else if err2 != nil {
+               return err2
        }
-       return err2
+       return err3
 }
 
 // Update update some fields of a folder
@@ -169,20 +190,6 @@ func (f *STFolder) Update(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, erro
        return &f.fConfig, nil
 }
 
-// RegisterEventChange requests registration for folder event change
-func (f *STFolder) RegisterEventChange(cb *FolderEventCB, data *FolderEventCBData) error {
-       f.eventChangeCB = cb
-       f.eventChangeCBData = data
-       return nil
-}
-
-// UnRegisterEventChange remove registered callback
-func (f *STFolder) UnRegisterEventChange() error {
-       f.eventChangeCB = nil
-       f.eventChangeCBData = nil
-       return nil
-}
-
 // Sync Force folder files synchronization
 func (f *STFolder) Sync() error {
        return f.st.FolderScan(f.stfConfig.ID, "")
@@ -222,9 +229,10 @@ func (f *STFolder) cbEventState(ev st.Event, data *st.EventsCBData) {
                f.fConfig.IsInSync = false
        }
 
-       if f.eventChangeCB != nil &&
-               (prevSync != f.fConfig.IsInSync || prevStatus != f.fConfig.Status) {
-               cpConf := f.fConfig
-               (*f.eventChangeCB)(&cpConf, f.eventChangeCBData)
+       if prevSync != f.fConfig.IsInSync || prevStatus != f.fConfig.Status {
+               // Emit Folder state change event
+               if err := f.events.Emit(xsapiv1.EVTFolderStateChange, &f.fConfig, ""); err != nil {
+                       f.Log.Warningf("Cannot notify folder change: %v", err)
+               }
        }
 }
index 7a45bbd..b0c198a 100644 (file)
@@ -216,6 +216,7 @@ func (f *Folders) Add(newF xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error)
 
 // CreateUpdate creates or update a folder
 func (f *Folders) createUpdate(newF xsapiv1.FolderConfig, create bool, initial bool) (*xsapiv1.FolderConfig, error) {
+       var err error
 
        fcMutex.Lock()
        defer fcMutex.Unlock()
@@ -269,12 +270,21 @@ func (f *Folders) createUpdate(newF xsapiv1.FolderConfig, create bool, initial b
        // Normalize path (needed for Windows path including bashlashes)
        newF.ClientPath = common.PathNormalize(newF.ClientPath)
 
-       // Add new folder
-       newFolder, err := fld.Add(newF)
-       if err != nil {
-               newF.Status = xsapiv1.StatusErrorConfig
-               log.Printf("ERROR Adding folder: %v\n", err)
-               return newFolder, err
+       var newFolder *xsapiv1.FolderConfig
+       if create {
+               // Add folder
+               if newFolder, err = fld.Add(newF); err != nil {
+                       newF.Status = xsapiv1.StatusErrorConfig
+                       log.Printf("ERROR Adding folder: %v\n", err)
+                       return newFolder, err
+               }
+       } else {
+               // Just update project config
+               if newFolder, err = fld.Setup(newF); err != nil {
+                       newF.Status = xsapiv1.StatusErrorConfig
+                       log.Printf("ERROR Updating folder: %v\n", err)
+                       return newFolder, err
+               }
        }
 
        // Add to folders list
@@ -287,13 +297,6 @@ func (f *Folders) createUpdate(newF xsapiv1.FolderConfig, create bool, initial b
                }
        }
 
-       // Register event change callback
-       for _, rcb := range f.registerCB {
-               if err := fld.RegisterEventChange(rcb.cb, rcb.data); err != nil {
-                       return newFolder, err
-               }
-       }
-
        // Force sync after creation
        // (need to defer to be sure that WS events will arrive after HTTP creation reply)
        go func() {
@@ -379,29 +382,6 @@ func (f *Folders) Update(id string, cfg xsapiv1.FolderConfig) (*xsapiv1.FolderCo
        return fld, err
 }
 
-// RegisterEventChange requests registration for folder event change
-func (f *Folders) RegisterEventChange(id string, cb *FolderEventCB, data *FolderEventCBData) error {
-
-       flds := make(map[string]*IFOLDER)
-       if id != "" {
-               // Register to a specific folder
-               flds[id] = f.Get(id)
-       } else {
-               // Register to all folders
-               flds = f.folders
-               f.registerCB = append(f.registerCB, RegisteredCB{cb: cb, data: data})
-       }
-
-       for _, fld := range flds {
-               err := (*fld).RegisterEventChange(cb, data)
-               if err != nil {
-                       return err
-               }
-       }
-
-       return nil
-}
-
 // ForceSync Force the synchronization of a folder
 func (f *Folders) ForceSync(id string) error {
        fc := f.Get(id)
index 64041b9..46e860b 100644 (file)
@@ -51,6 +51,7 @@ type Context struct {
        sdks          *SDKs
        WWWServer     *WebServer
        sessions      *Sessions
+       events        *Events
        Exit          chan os.Signal
 }
 
@@ -128,6 +129,9 @@ func (ctx *Context) Run() (int, error) {
                ctx._logPrint("Logging file for HTTP requests:  %s\n", logFileHTTPReq)
        }
 
+       // Create events management
+       ctx.events = NewEvents(ctx)
+
        // Create syncthing instance when section "syncthing" is present in server-config.json
        if ctx.Config.FileConf.SThgConf != nil {
                ctx.SThg = st.NewSyncThing(ctx.Config, ctx.Log)
index e19eb82..1552579 100644 (file)
 
 package xsapiv1
 
+import (
+       "encoding/json"
+       "fmt"
+)
+
 // EventRegisterArgs Parameters (json format) of /events/register command
 type EventRegisterArgs struct {
        Name      string `json:"name"`
@@ -31,9 +36,10 @@ type EventUnRegisterArgs struct {
 
 // EventMsg Message send
 type EventMsg struct {
-       Time   string       `json:"time"`
-       Type   string       `json:"type"`
-       Folder FolderConfig `json:"folder"`
+       Time          string      `json:"time"`
+       FromSessionID string      `json:"sessionID"` // Session ID of client who produce this event
+       Type          string      `json:"type"`
+       Data          interface{} `json:"data"` // Data
 }
 
 // EventEvent Event send in WS when an internal event (eg. Syncthing event is received)
@@ -46,3 +52,26 @@ const (
        EVTFolderChange      = EventTypePrefix + "folder-change"       // type EventMsg with Data type xsapiv1.???
        EVTFolderStateChange = EventTypePrefix + "folder-state-change" // type EventMsg with Data type xsapiv1.???
 )
+
+// EVTAllList List of all supported events
+var EVTAllList = []string{
+       EVTFolderChange,
+       EVTFolderStateChange,
+}
+
+// DecodeFolderConfig Helper to decode Data field type FolderConfig
+func (e *EventMsg) DecodeFolderConfig() (FolderConfig, error) {
+       var err error
+       f := FolderConfig{}
+       switch e.Type {
+       case EVTFolderChange, EVTFolderStateChange:
+               d := []byte{}
+               d, err = json.Marshal(e.Data)
+               if err == nil {
+                       err = json.Unmarshal(d, &f)
+               }
+       default:
+               err = fmt.Errorf("Invalid type")
+       }
+       return f, err
+}