From 285332c351777b74abca638b8b2a2cde3c68edc6 Mon Sep 17 00:00:00 2001 From: Sebastien Douheret Date: Sat, 16 Dec 2017 00:10:14 +0100 Subject: [PATCH] Fixed and improved events management. Signed-off-by: Sebastien Douheret --- .vscode/launch.json | 5 +- lib/syncthing/stEvent.go | 51 +++++++++------ lib/syncthing/stfolder.go | 10 +-- lib/xdsserver/apiv1-events.go | 100 +++++------------------------ lib/xdsserver/events.go | 125 +++++++++++++++++++++++++++++++++++++ lib/xdsserver/folder-interface.go | 23 ++++--- lib/xdsserver/folder-pathmap.go | 56 ++++++++--------- lib/xdsserver/folder-st-disable.go | 27 ++++---- lib/xdsserver/folder-st.go | 110 +++++++++++++++++--------------- lib/xdsserver/folders.go | 52 +++++---------- lib/xdsserver/xdsserver.go | 4 ++ lib/xsapiv1/events.go | 35 ++++++++++- 12 files changed, 341 insertions(+), 257 deletions(-) create mode 100644 lib/xdsserver/events.go diff --git a/.vscode/launch.json b/.vscode/launch.json index 5583251..245c2ee 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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 diff --git a/lib/syncthing/stEvent.go b/lib/syncthing/stEvent.go index 6cb7a31..948f88a 100644 --- a/lib/syncthing/stEvent.go +++ b/lib/syncthing/stEvent.go @@ -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() } } } diff --git a/lib/syncthing/stfolder.go b/lib/syncthing/stfolder.go index d67b164..1dcbfe1 100644 --- a/lib/syncthing/stfolder.go +++ b/lib/syncthing/stfolder.go @@ -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, diff --git a/lib/xdsserver/apiv1-events.go b/lib/xdsserver/apiv1-events.go index 9f0a774..0942753 100644 --- a/lib/xdsserver/apiv1-events.go +++ b/lib/xdsserver/apiv1-events.go @@ -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 index 0000000..007b89a --- /dev/null +++ b/lib/xdsserver/events.go @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2017 "IoT.bzh" + * Author Sebastien Douheret + * + * 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 +} diff --git a/lib/xdsserver/folder-interface.go b/lib/xdsserver/folder-interface.go index 2b836e7..151530c 100644 --- a/lib/xdsserver/folder-interface.go +++ b/lib/xdsserver/folder-interface.go @@ -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 } diff --git a/lib/xdsserver/folder-pathmap.go b/lib/xdsserver/folder-pathmap.go index bb33a98..0452b13 100644 --- a/lib/xdsserver/folder-pathmap.go +++ b/lib/xdsserver/folder-pathmap.go @@ -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 diff --git a/lib/xdsserver/folder-st-disable.go b/lib/xdsserver/folder-st-disable.go index 4dbe2a9..c52854d 100644 --- a/lib/xdsserver/folder-st-disable.go +++ b/lib/xdsserver/folder-st-disable.go @@ -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 diff --git a/lib/xdsserver/folder-st.go b/lib/xdsserver/folder-st.go index c8f718a..9cbb570 100644 --- a/lib/xdsserver/folder-st.go +++ b/lib/xdsserver/folder-st.go @@ -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) + } } } diff --git a/lib/xdsserver/folders.go b/lib/xdsserver/folders.go index 7a45bbd..b0c198a 100644 --- a/lib/xdsserver/folders.go +++ b/lib/xdsserver/folders.go @@ -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) diff --git a/lib/xdsserver/xdsserver.go b/lib/xdsserver/xdsserver.go index 64041b9..46e860b 100644 --- a/lib/xdsserver/xdsserver.go +++ b/lib/xdsserver/xdsserver.go @@ -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) diff --git a/lib/xsapiv1/events.go b/lib/xsapiv1/events.go index e19eb82..1552579 100644 --- a/lib/xsapiv1/events.go +++ b/lib/xsapiv1/events.go @@ -17,6 +17,11 @@ 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 +} -- 2.16.6