From 4695555e178bcabe54c5bf82117c9c4cef5440b5 Mon Sep 17 00:00:00 2001 From: Sebastien Douheret Date: Wed, 11 Oct 2017 00:24:02 +0200 Subject: [PATCH] Fixed Syncthing folder status events and exec command. --- .vscode/launch.json | 3 +- .vscode/settings.json | 6 +- lib/agent/agent.go | 36 +-- lib/agent/apiv1-exec.go | 66 +++-- lib/agent/apiv1-version.go | 2 +- lib/agent/events.go | 15 +- lib/agent/project-interface.go | 18 +- lib/agent/project-pathmap.go | 26 +- lib/agent/project-st.go | 155 +++++++++--- lib/agent/projects.go | 33 +-- lib/agent/session.go | 8 +- lib/agent/webserver.go | 4 +- lib/agent/xdsserver.go | 285 ++++++++++++++++------ lib/syncthing/st.go | 8 +- lib/syncthing/stEvent.go | 2 +- lib/syncthing/stfolder.go | 33 +-- lib/xdsconfig/config.go | 9 +- lib/xdsconfig/configfile.go | 8 +- webapp/src/app/config/config.component.ts | 1 - webapp/src/app/services/config.service.ts | 120 +-------- webapp/src/app/services/syncthing.service.ts | 352 --------------------------- webapp/src/app/services/xdsagent.service.ts | 6 +- 22 files changed, 480 insertions(+), 716 deletions(-) delete mode 100644 webapp/src/app/services/syncthing.service.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index d4a4e1e..69944df 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,8 @@ "env": { "GOPATH": "${workspaceRoot}/../../../..:${env:GOPATH}", "WORKSPACE_ROOT": "${workspaceRoot}", - "DEBUG_MODE": "1", + "XDS_DEBUG_MODE": "1", + "XDS_LOG_SILLY": "0", "ROOT_DIR": "${workspaceRoot}/../../../.." }, "args": ["-log", "debug", "-c", "__agent-config_local_dev.json"], diff --git a/.vscode/settings.json b/.vscode/settings.json index c82504e..eceb734 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -33,6 +33,7 @@ "socketio", "ldflags", "SThg", + "stconfig", "Intf", "dismissible", "rpath", @@ -40,6 +41,7 @@ "sess", "IXDS", "golib", + "xdsapi", "xdsconfig", "xdsserver", "xdsagent", @@ -50,6 +52,8 @@ "sdkid", "Flds", "prjs", - "iosk" + "iosk", + "CIFS", + "IPROJECT" ] } diff --git a/lib/agent/agent.go b/lib/agent/agent.go index 29b0622..3bdd89f 100644 --- a/lib/agent/agent.go +++ b/lib/agent/agent.go @@ -20,18 +20,19 @@ const cookieMaxAge = "3600" // Context holds the Agent context structure type Context struct { - ProgName string - Config *xdsconfig.Config - Log *logrus.Logger - SThg *st.SyncThing - SThgCmd *exec.Cmd - SThgInotCmd *exec.Cmd - - webServer *WebServer + ProgName string + Config *xdsconfig.Config + Log *logrus.Logger + LogLevelSilly bool + SThg *st.SyncThing + SThgCmd *exec.Cmd + SThgInotCmd *exec.Cmd + + webServer *WebServer xdsServers map[string]*XdsServer - sessions *Sessions - events *Events - projects *Projects + sessions *Sessions + events *Events + projects *Projects Exit chan os.Signal } @@ -53,15 +54,18 @@ func NewAgent(cliCtx *cli.Context) *Context { } log.Formatter = &logrus.TextFormatter{} + sillyVal, sillyLog := os.LookupEnv("XDS_LOG_SILLY") + // Define default configuration ctx := Context{ - ProgName: cliCtx.App.Name, - Log: log, - Exit: make(chan os.Signal, 1), + ProgName: cliCtx.App.Name, + Log: log, + LogLevelSilly: (sillyLog && sillyVal == "1"), + Exit: make(chan os.Signal, 1), - webServer: nil, + webServer: nil, xdsServers: make(map[string]*XdsServer), - events: nil, + events: nil, } // register handler on SIGTERM / exit diff --git a/lib/agent/apiv1-exec.go b/lib/agent/apiv1-exec.go index 83ec7aa..37070f7 100644 --- a/lib/agent/apiv1-exec.go +++ b/lib/agent/apiv1-exec.go @@ -7,13 +7,17 @@ import ( "github.com/gin-gonic/gin" common "github.com/iotbzh/xds-common/golib" + uuid "github.com/satori/go.uuid" ) -// Only define useful fields +// ExecArgs Only define used fields type ExecArgs struct { - ID string `json:"id" binding:"required"` + ID string `json:"id" binding:"required"` + CmdID string `json:"cmdID"` // command unique ID } +var execCmdID = 1 + // ExecCmd executes remotely a command func (s *APIService) execCmd(c *gin.Context) { s._execRequest("/exec", c) @@ -24,24 +28,26 @@ func (s *APIService) execSignalCmd(c *gin.Context) { s._execRequest("/signal", c) } -func (s *APIService) _execRequest(url string, c *gin.Context) { +func (s *APIService) _execRequest(cmd string, c *gin.Context) { data, err := c.GetRawData() if err != nil { common.APIError(c, err.Error()) } + args := ExecArgs{} + // XXX - we cannot use c.BindJSON, so directly unmarshall it + // (see https://github.com/gin-gonic/gin/issues/1078) + if err := json.Unmarshal(data, &args); err != nil { + common.APIError(c, "Invalid arguments") + return + } + // First get Project ID to retrieve Server ID and send command to right server id := c.Param("id") if id == "" { - args := ExecArgs{} - // XXX - we cannot use c.BindJSON, so directly unmarshall it - // (see https://github.com/gin-gonic/gin/issues/1078) - if err := json.Unmarshal(data, &args); err != nil { - common.APIError(c, "Invalid arguments") - return - } id = args.ID } + prj := s.projects.Get(id) if prj == nil { common.APIError(c, "Unknown id") @@ -68,21 +74,47 @@ func (s *APIService) _execRequest(url string, c *gin.Context) { // Forward XDSServer WS events to client WS // TODO removed static event name list and get it from XDSServer - for _, evName := range []string{ + evtList := []string{ "exec:input", "exec:output", - "exec:exit", "exec:inferior-input", "exec:inferior-output", - } { + } + so := *sock + fwdFuncID := []uuid.UUID{} + for _, evName := range evtList { evN := evName - svr.EventOn(evN, func(evData interface{}) { - (*sock).Emit(evN, evData) - }) + fwdFunc := func(evData interface{}) { + // Forward event to Client/Dashboard + so.Emit(evN, evData) + } + id, err := svr.EventOn(evN, fwdFunc) + if err != nil { + common.APIError(c, err.Error()) + return + } + fwdFuncID = append(fwdFuncID, id) + } + + // Handle Exit event separately to cleanup registered listener + var exitFuncID uuid.UUID + exitFunc := func(evData interface{}) { + so.Emit("exec:exit", evData) + + // cleanup listener + for i, evName := range evtList { + svr.EventOff(evName, fwdFuncID[i]) + } + svr.EventOff("exec:exit", exitFuncID) + } + exitFuncID, err = svr.EventOn("exec:exit", exitFunc) + if err != nil { + common.APIError(c, err.Error()) + return } // Forward back command to right server - response, err := svr.HTTPPostBody(url, string(data)) + response, err := svr.SendCommand(cmd, data) if err != nil { common.APIError(c, err.Error()) return diff --git a/lib/agent/apiv1-version.go b/lib/agent/apiv1-version.go index c2387c1..6b4923f 100644 --- a/lib/agent/apiv1-version.go +++ b/lib/agent/apiv1-version.go @@ -33,7 +33,7 @@ func (s *APIService) getVersion(c *gin.Context) { svrVer := []version{} for _, svr := range s.xdsServers { res := version{} - if err := svr.HTTPGet("/version", &res); err != nil { + if err := svr.GetVersion(&res); err != nil { common.APIError(c, "Cannot retrieve version of XDS server ID %s : %v", svr.ID, err.Error()) return } diff --git a/lib/agent/events.go b/lib/agent/events.go index 24efc5a..e66f758 100644 --- a/lib/agent/events.go +++ b/lib/agent/events.go @@ -18,7 +18,7 @@ const ( EVTProjectChange = "project-state-change" // data type ProjectConfig ) -var EVTAllList = []string{ +var _EVTAllList = []string{ EVTServerConfig, EVTProjectAdd, EVTProjectDelete, @@ -33,7 +33,6 @@ type EventMsg struct { } type EventDef struct { - // SEB cbs []EventsCB sids map[string]int } @@ -45,7 +44,7 @@ type Events struct { // NewEvents creates an instance of Events func NewEvents(ctx *Context) *Events { evMap := make(map[string]*EventDef) - for _, ev := range EVTAllList { + for _, ev := range _EVTAllList { evMap[ev] = &EventDef{ sids: make(map[string]int), } @@ -58,12 +57,12 @@ func NewEvents(ctx *Context) *Events { // GetList returns the list of all supported events func (e *Events) GetList() []string { - return EVTAllList + return _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 := EVTAllList + evs := _EVTAllList if evName != EVTAll { if _, ok := e.eventsMap[evName]; !ok { return fmt.Errorf("Unsupported event type name") @@ -78,7 +77,7 @@ func (e *Events) Register(evName, sessionID string) error { // UnRegister Used by a client/session to unregister event(s) func (e *Events) UnRegister(evName, sessionID string) error { - evs := EVTAllList + evs := _EVTAllList if evName != EVTAll { if _, ok := e.eventsMap[evName]; !ok { return fmt.Errorf("Unsupported event type name") @@ -102,7 +101,9 @@ func (e *Events) Emit(evName string, data interface{}) error { return fmt.Errorf("Unsupported event type") } - e.Log.Debugf("Emit Event %s: %v", evName, data) + if e.LogLevelSilly { + e.Log.Debugf("Emit Event %s: %v", evName, data) + } firstErr = nil evm := e.eventsMap[evName] diff --git a/lib/agent/project-interface.go b/lib/agent/project-interface.go index 031e1d9..0a4a17e 100644 --- a/lib/agent/project-interface.go +++ b/lib/agent/project-interface.go @@ -18,19 +18,15 @@ const ( StatusSyncing = "Syncing" ) -type EventCBData map[string]interface{} -type EventCB func(cfg *ProjectConfig, data *EventCBData) - // IPROJECT Project interface type IPROJECT interface { - Add(cfg ProjectConfig) (*ProjectConfig, error) // Add a new project - Delete() error // Delete a project - GetProject() *ProjectConfig // Get project public configuration - SetProject(prj ProjectConfig) *ProjectConfig // Set project configuration - GetServer() *XdsServer // Get XdsServer that holds this project - GetFullPath(dir string) string // Get project full path - Sync() error // Force project files synchronization - IsInSync() (bool, error) // Check if project files are in-sync + Add(cfg ProjectConfig) (*ProjectConfig, error) // Add a new project + Delete() error // Delete a project + GetProject() *ProjectConfig // Get project public configuration + UpdateProject(prj ProjectConfig) (*ProjectConfig, error) // Update project configuration + GetServer() *XdsServer // Get XdsServer that holds this project + Sync() error // Force project files synchronization + IsInSync() (bool, error) // Check if project files are in-sync } // ProjectConfig is the config for one project diff --git a/lib/agent/project-pathmap.go b/lib/agent/project-pathmap.go index 1de8e11..6c49d6a 100644 --- a/lib/agent/project-pathmap.go +++ b/lib/agent/project-pathmap.go @@ -1,16 +1,12 @@ package agent -import ( - "path/filepath" -) - // IPROJECT interface implementation for native/path mapping projects // PathMap . type PathMap struct { *Context server *XdsServer - folder *FolderConfig + folder *XdsFolderConfig } // NewProjectPathMap Create a new instance of PathMap @@ -18,7 +14,7 @@ func NewProjectPathMap(ctx *Context, svr *XdsServer) *PathMap { p := PathMap{ Context: ctx, server: svr, - folder: &FolderConfig{}, + folder: &XdsFolderConfig{}, } return &p } @@ -49,10 +45,14 @@ func (p *PathMap) GetProject() *ProjectConfig { return &prj } -// SetProject Set project config -func (p *PathMap) SetProject(prj ProjectConfig) *ProjectConfig { +// UpdateProject Set project config +func (p *PathMap) UpdateProject(prj ProjectConfig) (*ProjectConfig, error) { p.folder = p.server.ProjectToFolder(prj) - return p.GetProject() + np := p.GetProject() + if err := p.events.Emit(EVTProjectChange, np); err != nil { + return np, err + } + return np, nil } // GetServer Get the XdsServer that holds this project @@ -60,14 +60,6 @@ func (p *PathMap) GetServer() *XdsServer { return p.server } -// GetFullPath returns the full path of a directory (from server POV) -func (p *PathMap) GetFullPath(dir string) string { - if &dir == nil { - return p.folder.DataPathMap.ServerPath - } - return filepath.Join(p.folder.DataPathMap.ServerPath, dir) -} - // Sync Force project files synchronization func (p *PathMap) Sync() error { return nil diff --git a/lib/agent/project-st.go b/lib/agent/project-st.go index 28a287c..c0d2550 100644 --- a/lib/agent/project-st.go +++ b/lib/agent/project-st.go @@ -1,16 +1,17 @@ package agent -import "github.com/iotbzh/xds-agent/lib/syncthing" - -// SEB TODO +import ( + st "github.com/iotbzh/xds-agent/lib/syncthing" +) // IPROJECT interface implementation for syncthing projects // STProject . type STProject struct { *Context - server *XdsServer - folder *FolderConfig + server *XdsServer + folder *XdsFolderConfig + eventIDs []int } // NewProjectST Create a new instance of STProject @@ -18,7 +19,7 @@ func NewProjectST(ctx *Context, svr *XdsServer) *STProject { p := STProject{ Context: ctx, server: svr, - folder: &FolderConfig{}, + folder: &XdsFolderConfig{}, } return &p } @@ -27,6 +28,7 @@ func NewProjectST(ctx *Context, svr *XdsServer) *STProject { func (p *STProject) Add(cfg ProjectConfig) (*ProjectConfig, error) { var err error + // Add project/folder into XDS Server err = p.server.FolderAdd(p.server.ProjectToFolder(cfg), p.folder) if err != nil { return nil, err @@ -34,19 +36,37 @@ func (p *STProject) Add(cfg ProjectConfig) (*ProjectConfig, error) { svrPrj := p.GetProject() // Declare project into local Syncthing - p.SThg.FolderChange(st.FolderChangeArg{ - ID: cfg.ID, - Label: cfg.Label, + id, err := p.SThg.FolderChange(st.FolderChangeArg{ + ID: svrPrj.ID, + Label: svrPrj.Label, RelativePath: cfg.ClientPath, SyncThingID: p.server.ServerConfig.Builder.SyncThingID, }) + if err != nil { + return nil, err + } - return svrPrj, nil + locPrj, err := p.SThg.FolderConfigGet(id) + if err != nil { + svrPrj.Status = StatusErrorConfig + return nil, err + } + if svrPrj.ID != locPrj.ID { + p.Log.Errorf("Project ID in XDSServer and local ST differ: %s != %s", svrPrj.ID, locPrj.ID) + } + + // Use Update function to setup remains fields + return p.UpdateProject(*svrPrj) } // Delete a project func (p *STProject) Delete() error { - return p.server.FolderDelete(p.folder.ID) + errSvr := p.server.FolderDelete(p.folder.ID) + errLoc := p.SThg.FolderDelete(p.folder.ID) + if errSvr != nil { + return errSvr + } + return errLoc } // GetProject Get public part of project config @@ -56,38 +76,111 @@ func (p *STProject) GetProject() *ProjectConfig { return &prj } -// SetProject Set project config -func (p *STProject) SetProject(prj ProjectConfig) *ProjectConfig { - // SEB TODO +// UpdateProject Update project config +func (p *STProject) UpdateProject(prj ProjectConfig) (*ProjectConfig, error) { + // Update folder p.folder = p.server.ProjectToFolder(prj) - return p.GetProject() + svrPrj := p.GetProject() + + // Register events to update folder status + // Register to XDS Server events + p.server.EventOn("event:FolderStateChanged", p._cbServerFolderChanged) + if err := p.server.EventRegister("FolderStateChanged", svrPrj.ID); err != nil { + p.Log.Warningf("XDS Server EventRegister failed: %v", err) + return svrPrj, err + } + + // Register to Local Syncthing events + for _, evName := range []string{st.EventStateChanged, st.EventFolderPaused} { + evID, err := p.SThg.Events.Register(evName, p._cbLocalSTEvents, svrPrj.ID, nil) + if err != nil { + return nil, err + } + p.eventIDs = append(p.eventIDs, evID) + } + + return svrPrj, nil } // GetServer Get the XdsServer that holds this project func (p *STProject) GetServer() *XdsServer { - // SEB TODO return p.server } -// GetFullPath returns the full path of a directory (from server POV) -func (p *STProject) GetFullPath(dir string) string { - /* SEB - if &dir == nil { - return p.folder.DataSTProject.ServerPath - } - return filepath.Join(p.folder.DataSTProject.ServerPath, dir) - */ - return "SEB TODO" -} - // Sync Force project files synchronization func (p *STProject) Sync() error { - // SEB TODO - return nil + if err := p.server.FolderSync(p.folder.ID); err != nil { + return err + } + return p.SThg.FolderScan(p.folder.ID, "") } // IsInSync Check if project files are in-sync func (p *STProject) IsInSync() (bool, error) { - // SEB TODO - return false, nil + // Should be up-to-date by callbacks (see below) + return p.folder.IsInSync, nil +} + +/** +** Private functions +***/ + +// callback use to update (XDS Server) folder IsInSync status + +func (p *STProject) _cbServerFolderChanged(data interface{}) { + evt := data.(XdsEventFolderChange) + + // Only process event that concerns this project/folder ID + if p.folder.ID != evt.Folder.ID { + return + } + + if evt.Folder.IsInSync != p.folder.DataCloudSync.STSvrIsInSync || + evt.Folder.Status != p.folder.DataCloudSync.STSvrStatus { + + p.folder.DataCloudSync.STSvrIsInSync = evt.Folder.IsInSync + p.folder.DataCloudSync.STSvrStatus = evt.Folder.Status + + if err := p.events.Emit(EVTProjectChange, p.server.FolderToProject(*p.folder)); err != nil { + p.Log.Warningf("Cannot notify project change: %v", err) + } + } +} + +// callback use to update IsInSync status +func (p *STProject) _cbLocalSTEvents(ev st.Event, data *st.EventsCBData) { + + inSync := p.folder.DataCloudSync.STLocIsInSync + sts := p.folder.DataCloudSync.STLocStatus + prevSync := inSync + prevStatus := sts + + switch ev.Type { + + case st.EventStateChanged: + to := ev.Data["to"] + switch to { + case "scanning", "syncing": + sts = StatusSyncing + case "idle": + sts = StatusEnable + } + inSync = (to == "idle") + + case st.EventFolderPaused: + if sts == StatusEnable { + sts = StatusPause + } + inSync = false + } + + if prevSync != inSync || prevStatus != sts { + + p.folder.DataCloudSync.STLocIsInSync = inSync + p.folder.DataCloudSync.STLocStatus = sts + + if err := p.events.Emit(EVTProjectChange, p.server.FolderToProject(*p.folder)); err != nil { + p.Log.Warningf("Cannot notify project change: %v", err) + } + } } diff --git a/lib/agent/projects.go b/lib/agent/projects.go index 39c120f..5e20395 100644 --- a/lib/agent/projects.go +++ b/lib/agent/projects.go @@ -14,16 +14,8 @@ type Projects struct { *Context SThg *st.SyncThing projects map[string]*IPROJECT - //SEB registerCB []RegisteredCB } -/* SEB -type RegisteredCB struct { - cb *EventCB - data *EventCBData -} -*/ - // Mutex to make add/delete atomic var pjMutex = sync.NewMutex() @@ -33,7 +25,6 @@ func NewProjects(ctx *Context, st *st.SyncThing) *Projects { Context: ctx, SThg: st, projects: make(map[string]*IPROJECT), - //registerCB: []RegisteredCB{}, } } @@ -51,26 +42,19 @@ func (p *Projects) Init(server *XdsServer) error { if svr.Disabled { continue } - xFlds := []FolderConfig{} - if err := svr.HTTPGet("/folders", &xFlds); err != nil { + xFlds := []XdsFolderConfig{} + if err := svr.GetFolders(&xFlds); err != nil { errMsg += fmt.Sprintf("Cannot retrieve folders config of XDS server ID %s : %v \n", svr.ID, err.Error()) continue } - p.Log.Debugf("Server %s, %d projects detected", svr.ID[:8], len(xFlds)) + p.Log.Debugf("Connected to XDS Server %s, %d projects detected", svr.ID, len(xFlds)) for _, prj := range xFlds { newP := svr.FolderToProject(prj) - if /*nPrj*/ _, err := p.createUpdate(newP, false, true); err != nil { + if _, err := p.createUpdate(newP, false, true); err != nil { errMsg += "Error while creating project id " + prj.ID + ": " + err.Error() + "\n " continue } - - /* FIXME emit EVTProjectChange event ? - if err := p.events.Emit(EVTProjectChange, *nPrj); err != nil { - p.Log.Warningf("Cannot notify project change: %v", err) - } - */ } - } p.Log.Infof("Number of loaded Projects: %d", len(p.projects)) @@ -161,7 +145,6 @@ func (p *Projects) createUpdate(newF ProjectConfig, create bool, initial bool) ( // SYNCTHING case TypeCloudSync: if p.SThg != nil { - /*SEB fld = f.SThg.NewFolderST(f.Conf)*/ fld = NewProjectST(p.Context, svr) } else { return nil, fmt.Errorf("Cloud Sync project not supported") @@ -179,12 +162,16 @@ func (p *Projects) createUpdate(newF ProjectConfig, create bool, initial bool) ( // Add project on server if newPrj, err = fld.Add(newF); err != nil { newF.Status = StatusErrorConfig - log.Printf("ERROR Adding folder: %v\n", err) + log.Printf("ERROR Adding project: %v\n", err) return newPrj, err } } else { // Just update project config - newPrj = fld.SetProject(newF) + if newPrj, err = fld.UpdateProject(newF); err != nil { + newF.Status = StatusErrorConfig + log.Printf("ERROR Updating project: %v\n", err) + return newPrj, err + } } // Sanity check diff --git a/lib/agent/session.go b/lib/agent/session.go index e50abe1..06789d5 100644 --- a/lib/agent/session.go +++ b/lib/agent/session.go @@ -194,15 +194,13 @@ func (s *Sessions) refresh(sid string) { } func (s *Sessions) monitorSessMap() { - const dbgFullTrace = false // for debugging - for { select { case <-s.stop: s.Log.Debugln("Stop monitorSessMap") return case <-time.After(sessionMonitorTime * time.Second): - if dbgFullTrace { + if s.LogLevelSilly { s.Log.Debugf("Sessions Map size: %d", len(s.sessMap)) s.Log.Debugf("Sessions Map : %v", s.sessMap) } @@ -214,7 +212,9 @@ func (s *Sessions) monitorSessMap() { s.mutex.Lock() for _, ss := range s.sessMap { if ss.expireAt.Sub(time.Now()) < 0 { - //SEB DEBUG s.Log.Debugf("Delete expired session id: %s", ss.ID) + if s.LogLevelSilly { + s.Log.Debugf("Delete expired session id: %s", ss.ID) + } delete(s.sessMap, ss.ID) } } diff --git a/lib/agent/webserver.go b/lib/agent/webserver.go index ead06d1..4b2e024 100644 --- a/lib/agent/webserver.go +++ b/lib/agent/webserver.go @@ -148,11 +148,9 @@ func (s *WebServer) middlewareXDSDetails() gin.HandlerFunc { } } -/* SEB func (s *WebServer) isValidAPIKey(key string) bool { - return (key == s.Config.FileConf.XDSAPIKey && key != "") + return (s.Config.FileConf.XDSAPIKey != "" && key == s.Config.FileConf.XDSAPIKey) } -*/ func (s *WebServer) middlewareCSRF() gin.HandlerFunc { return func(c *gin.Context) { diff --git a/lib/agent/xdsserver.go b/lib/agent/xdsserver.go index 518c68b..c900c9e 100644 --- a/lib/agent/xdsserver.go +++ b/lib/agent/xdsserver.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "net/http" "strings" + "sync" "time" "github.com/gin-gonic/gin" @@ -16,7 +17,7 @@ import ( sio_client "github.com/sebd71/go-socket.io-client" ) -// Server . +// XdsServer . type XdsServer struct { *Context ID string @@ -26,11 +27,13 @@ type XdsServer struct { ConnRetry int Connected bool Disabled bool - ServerConfig *xdsServerConfig + ServerConfig *XdsServerConfig - // callbacks + // Events management CBOnError func(error) CBOnDisconnect func(error) + sockEvents map[string][]*caller + sockEventsLock *sync.Mutex // Private fields client *common.HTTPClient @@ -39,25 +42,25 @@ type XdsServer struct { apiRouter *gin.RouterGroup } -// xdsServerConfig Data return by GET /config -type xdsServerConfig struct { +// XdsServerConfig Data return by GET /config +type XdsServerConfig struct { ID string `json:"id"` Version string `json:"version"` APIVersion string `json:"apiVersion"` VersionGitTag string `json:"gitTag"` SupportedSharing map[string]bool `json:"supportedSharing"` - Builder xdsBuilderConfig `json:"builder"` + Builder XdsBuilderConfig `json:"builder"` } -// xdsBuilderConfig represents the builder container configuration -type xdsBuilderConfig struct { +// XdsBuilderConfig represents the builder container configuration +type XdsBuilderConfig struct { IP string `json:"ip"` Port string `json:"port"` SyncThingID string `json:"syncThingID"` } -// FolderType XdsServer folder type -type FolderType string +// XdsFolderType XdsServer folder type +type XdsFolderType string const ( XdsTypePathMap = "PathMap" @@ -65,28 +68,52 @@ const ( XdsTypeCifsSmb = "CIFS" ) -// FolderConfig XdsServer folder config -type FolderConfig struct { - ID string `json:"id"` - Label string `json:"label"` - ClientPath string `json:"path"` - Type FolderType `json:"type"` - Status string `json:"status"` - IsInSync bool `json:"isInSync"` - DefaultSdk string `json:"defaultSdk"` +// XdsFolderConfig XdsServer folder config +type XdsFolderConfig struct { + ID string `json:"id"` + Label string `json:"label"` + ClientPath string `json:"path"` + Type XdsFolderType `json:"type"` + Status string `json:"status"` + IsInSync bool `json:"isInSync"` + DefaultSdk string `json:"defaultSdk"` // Specific data depending on which Type is used - DataPathMap PathMapConfig `json:"dataPathMap,omitempty"` - DataCloudSync CloudSyncConfig `json:"dataCloudSync,omitempty"` + DataPathMap XdsPathMapConfig `json:"dataPathMap,omitempty"` + DataCloudSync XdsCloudSyncConfig `json:"dataCloudSync,omitempty"` } -// PathMapConfig Path mapping specific data -type PathMapConfig struct { +// XdsPathMapConfig Path mapping specific data +type XdsPathMapConfig struct { ServerPath string `json:"serverPath"` } -// CloudSyncConfig CloudSync (AKA Syncthing) specific data -type CloudSyncConfig struct { - SyncThingID string `json:"syncThingID"` +// XdsCloudSyncConfig CloudSync (AKA Syncthing) specific data +type XdsCloudSyncConfig struct { + SyncThingID string `json:"syncThingID"` + STSvrStatus string `json:"-"` + STSvrIsInSync bool `json:"-"` + STLocStatus string `json:"-"` + STLocIsInSync bool `json:"-"` +} + +// XdsEventRegisterArgs arguments used to register to XDS server events +type XdsEventRegisterArgs struct { + Name string `json:"name"` + ProjectID string `json:"filterProjectID"` +} + +// XdsEventFolderChange Folder change event structure +type XdsEventFolderChange struct { + Time string `json:"time"` + Type string `json:"type"` + Folder XdsFolderConfig `json:"folder"` +} + +// caller Used to chain event listeners +type caller struct { + id uuid.UUID + EventName string + Func func(interface{}) } const _IDTempoPrefix = "tempo-" @@ -103,7 +130,9 @@ func NewXdsServer(ctx *Context, conf xdsconfig.XDSServerConf) *XdsServer { Connected: false, Disabled: false, - logOut: ctx.Log.Out, + sockEvents: make(map[string][]*caller), + sockEventsLock: &sync.Mutex{}, + logOut: ctx.Log.Out, } } @@ -138,7 +167,7 @@ func (xs *XdsServer) Connect() error { time.Sleep(time.Second) } if retry == 0 { - // FIXME SEB: re-use _reconnect to wait longer in background + // FIXME: re-use _reconnect to wait longer in background return fmt.Errorf("Connection to XDS Server failure") } if err != nil { @@ -161,16 +190,35 @@ func (xs *XdsServer) SetLoggerOutput(out io.Writer) { xs.logOut = out } +// SendCommand Send a command to XDS Server +func (xs *XdsServer) SendCommand(cmd string, body []byte) (*http.Response, error) { + url := cmd + if !strings.HasPrefix("/", cmd) { + url = "/" + cmd + } + return xs.client.HTTPPostWithRes(url, string(body)) +} + +// GetVersion Send Get request to retrieve XDS Server version +func (xs *XdsServer) GetVersion(res interface{}) error { + return xs._HTTPGet("/version", &res) +} + +// GetFolders Send GET request to get current folder configuration +func (xs *XdsServer) GetFolders(prjs *[]XdsFolderConfig) error { + return xs._HTTPGet("/folders", prjs) +} + // FolderAdd Send POST request to add a folder -func (xs *XdsServer) FolderAdd(prj *FolderConfig, res interface{}) error { - response, err := xs.HTTPPost("/folder", prj) +func (xs *XdsServer) FolderAdd(prj *XdsFolderConfig, res interface{}) error { + response, err := xs._HTTPPost("/folder", prj) if err != nil { return err } if response.StatusCode != 200 { return fmt.Errorf("FolderAdd error status=%s", response.Status) } - // Result is a FolderConfig that is equivalent to ProjectConfig + // Result is a XdsFolderConfig that is equivalent to ProjectConfig err = json.Unmarshal(xs.client.ResponseToBArray(response), res) return err @@ -181,27 +229,9 @@ func (xs *XdsServer) FolderDelete(id string) error { return xs.client.HTTPDelete("/folder/" + id) } -// HTTPGet . -func (xs *XdsServer) HTTPGet(url string, data interface{}) error { - var dd []byte - if err := xs.client.HTTPGet(url, &dd); err != nil { - return err - } - return json.Unmarshal(dd, &data) -} - -// HTTPPost . -func (xs *XdsServer) HTTPPost(url string, data interface{}) (*http.Response, error) { - body, err := json.Marshal(data) - if err != nil { - return nil, err - } - return xs.HTTPPostBody(url, string(body)) -} - -// HTTPPostBody . -func (xs *XdsServer) HTTPPostBody(url string, body string) (*http.Response, error) { - return xs.client.HTTPPostWithRes(url, body) +// FolderSync Send POST request to force synchronization of a folder +func (xs *XdsServer) FolderSync(id string) error { + return xs.client.HTTPPost("/folder/sync/"+id, "") } // SetAPIRouterGroup . @@ -218,7 +248,7 @@ func (xs *XdsServer) PassthroughGet(url string) { xs.apiRouter.GET(url, func(c *gin.Context) { var data interface{} - if err := xs.HTTPGet(url, &data); err != nil { + if err := xs._HTTPGet(url, &data); err != nil { if strings.Contains(err.Error(), "connection refused") { xs.Connected = false xs._NotifyState() @@ -246,7 +276,7 @@ func (xs *XdsServer) PassthroughPost(url string) { return } - response, err := xs.HTTPPostBody(url, string(bodyReq[:n])) + response, err := xs._HTTPPost(url, bodyReq[:n]) if err != nil { common.APIError(c, err.Error()) return @@ -260,49 +290,132 @@ func (xs *XdsServer) PassthroughPost(url string) { }) } +// EventRegister Post a request to register to an XdsServer event +func (xs *XdsServer) EventRegister(evName string, id string) error { + var err error + _, err = xs._HTTPPost("/events/register", XdsEventRegisterArgs{ + Name: evName, + ProjectID: id, + }) + return err +} + // EventOn Register a callback on events reception -func (xs *XdsServer) EventOn(message string, f interface{}) (err error) { +func (xs *XdsServer) EventOn(evName string, f func(interface{})) (uuid.UUID, error) { if xs.ioSock == nil { - return fmt.Errorf("Io.Socket not initialized") + return uuid.Nil, fmt.Errorf("Io.Socket not initialized") } - // FIXME SEB: support chain / multiple listeners - /* sockEvents map[string][]*caller + xs.sockEventsLock.Lock() - xs.sockEvents[message] = append(xs.sockEvents[message], f) - xs.sockEventsLock.Unlock() - xs.ioSock.On(message, func(ev) { + defer xs.sockEventsLock.Unlock() + + if _, exist := xs.sockEvents[evName]; !exist { + // Register listener only the first time + evn := evName + + // FIXME: use generic type: data interface{} instead of data XdsEventFolderChange + var err error + if evName == "event:FolderStateChanged" { + err = xs.ioSock.On(evn, func(data XdsEventFolderChange) error { + xs.sockEventsLock.Lock() + defer xs.sockEventsLock.Unlock() + for _, c := range xs.sockEvents[evn] { + c.Func(data) + } + return nil + }) + } else { + err = xs.ioSock.On(evn, f) + } + if err != nil { + return uuid.Nil, err + } + } - }) - */ - return xs.ioSock.On(message, f) + c := &caller{ + id: uuid.NewV1(), + EventName: evName, + Func: f, + } + + xs.sockEvents[evName] = append(xs.sockEvents[evName], c) + return c.id, nil +} + +// EventOff Un-register a (or all) callbacks associated to an event +func (xs *XdsServer) EventOff(evName string, id uuid.UUID) error { + xs.sockEventsLock.Lock() + defer xs.sockEventsLock.Unlock() + if _, exist := xs.sockEvents[evName]; exist { + if id == uuid.Nil { + // Un-register all + xs.sockEvents[evName] = []*caller{} + } else { + // Un-register only the specified callback + for i, ff := range xs.sockEvents[evName] { + if uuid.Equal(ff.id, id) { + xs.sockEvents[evName] = append(xs.sockEvents[evName][:i], xs.sockEvents[evName][i+1:]...) + break + } + } + } + } + return nil } -// ProjectToFolder -func (xs *XdsServer) ProjectToFolder(pPrj ProjectConfig) *FolderConfig { +// ProjectToFolder Convert Project structure to Folder structure +func (xs *XdsServer) ProjectToFolder(pPrj ProjectConfig) *XdsFolderConfig { stID := "" if pPrj.Type == XdsTypeCloudSync { stID, _ = xs.SThg.IDGet() } - fPrj := FolderConfig{ + fPrj := XdsFolderConfig{ ID: pPrj.ID, Label: pPrj.Label, ClientPath: pPrj.ClientPath, - Type: FolderType(pPrj.Type), + Type: XdsFolderType(pPrj.Type), Status: pPrj.Status, IsInSync: pPrj.IsInSync, DefaultSdk: pPrj.DefaultSdk, - DataPathMap: PathMapConfig{ + DataPathMap: XdsPathMapConfig{ ServerPath: pPrj.ServerPath, }, - DataCloudSync: CloudSyncConfig{ - SyncThingID: stID, + DataCloudSync: XdsCloudSyncConfig{ + SyncThingID: stID, + STLocIsInSync: pPrj.IsInSync, + STLocStatus: pPrj.Status, + STSvrIsInSync: pPrj.IsInSync, + STSvrStatus: pPrj.Status, }, } + return &fPrj } -// FolderToProject -func (xs *XdsServer) FolderToProject(fPrj FolderConfig) ProjectConfig { +// FolderToProject Convert Folder structure to Project structure +func (xs *XdsServer) FolderToProject(fPrj XdsFolderConfig) ProjectConfig { + inSync := fPrj.IsInSync + sts := fPrj.Status + + if fPrj.Type == XdsTypeCloudSync { + inSync = fPrj.DataCloudSync.STSvrIsInSync && fPrj.DataCloudSync.STLocIsInSync + + sts = fPrj.DataCloudSync.STSvrStatus + switch fPrj.DataCloudSync.STLocStatus { + case StatusErrorConfig, StatusDisable, StatusPause: + sts = fPrj.DataCloudSync.STLocStatus + break + case StatusSyncing: + if sts != StatusErrorConfig && sts != StatusDisable && sts != StatusPause { + sts = StatusSyncing + } + break + case StatusEnable: + // keep STSvrStatus + break + } + } + pPrj := ProjectConfig{ ID: fPrj.ID, ServerID: xs.ID, @@ -310,8 +423,8 @@ func (xs *XdsServer) FolderToProject(fPrj FolderConfig) ProjectConfig { ClientPath: fPrj.ClientPath, ServerPath: fPrj.DataPathMap.ServerPath, Type: ProjectType(fPrj.Type), - Status: fPrj.Status, - IsInSync: fPrj.IsInSync, + Status: sts, + IsInSync: inSync, DefaultSdk: fPrj.DefaultSdk, } return pPrj @@ -350,6 +463,24 @@ func (xs *XdsServer) _CreateConnectHTTP() error { return nil } +// _HTTPGet . +func (xs *XdsServer) _HTTPGet(url string, data interface{}) error { + var dd []byte + if err := xs.client.HTTPGet(url, &dd); err != nil { + return err + } + return json.Unmarshal(dd, &data) +} + +// _HTTPPost . +func (xs *XdsServer) _HTTPPost(url string, data interface{}) (*http.Response, error) { + body, err := json.Marshal(data) + if err != nil { + return nil, err + } + return xs.client.HTTPPostWithRes(url, string(body)) +} + // Re-established connection func (xs *XdsServer) _reconnect() error { err := xs._connect(true) @@ -363,8 +494,8 @@ func (xs *XdsServer) _reconnect() error { // Established HTTP and WS connection and retrieve XDSServer config func (xs *XdsServer) _connect(reConn bool) error { - xdsCfg := xdsServerConfig{} - if err := xs.HTTPGet("/config", &xdsCfg); err != nil { + xdsCfg := XdsServerConfig{} + if err := xs._HTTPGet("/config", &xdsCfg); err != nil { xs.Connected = false if !reConn { xs._NotifyState() diff --git a/lib/syncthing/st.go b/lib/syncthing/st.go index 304cfca..924f407 100644 --- a/lib/syncthing/st.go +++ b/lib/syncthing/st.go @@ -119,7 +119,7 @@ func NewSyncThing(conf *xdsconfig.Config, log *logrus.Logger) *SyncThing { } // Create Events monitoring - // SEB TO TEST s.Events = s.NewEventListener() + s.Events = s.NewEventListener() return &s } @@ -130,7 +130,7 @@ func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan var exePath string // Kill existing process (useful for debug ;-) ) - if os.Getenv("DEBUG_MODE") != "" { + if _, dbg := os.LookupEnv("XDS_DEBUG_MODE"); dbg { fmt.Printf("\n!!! DEBUG_MODE set: KILL existing %s process(es) !!!\n", exeName) exec.Command("bash", "-c", "ps -ax |grep "+exeName+" |grep "+s.BaseURL+" |cut -d' ' -f 1|xargs -I{} kill -9 {}").Output() } @@ -227,7 +227,7 @@ func (s *SyncThing) Start() (*exec.Cmd, error) { "STNOUPGRADE=1", } - /* SEB STILL NEEDED, if not SUP code + /* FIXME - STILL NEEDED ?, if not SUP code // XXX - temporary hack because -gui-apikey seems to correctly handle by // syncthing the early first time @@ -371,7 +371,7 @@ func (s *SyncThing) Connect() error { s.Connected = true // Start events monitoring - //SEB TODO err = s.Events.Start() + err = s.Events.Start() return err } diff --git a/lib/syncthing/stEvent.go b/lib/syncthing/stEvent.go index 9ca8b78..0017555 100644 --- a/lib/syncthing/stEvent.go +++ b/lib/syncthing/stEvent.go @@ -66,7 +66,7 @@ type cbMap struct { // NewEventListener Create a new instance of Event listener func (s *SyncThing) NewEventListener() *Events { - _, dbg := os.LookupEnv("XDS_DEBUG_STEVENTS") // set to add more debug log + _, dbg := os.LookupEnv("XDS_LOG_SILLY_STEVENTS") // set to add more debug log return &Events{ MonitorTime: 100, // in Milliseconds Debug: dbg, diff --git a/lib/syncthing/stfolder.go b/lib/syncthing/stfolder.go index 2ad6859..196e3c7 100644 --- a/lib/syncthing/stfolder.go +++ b/lib/syncthing/stfolder.go @@ -6,22 +6,21 @@ import ( "strings" common "github.com/iotbzh/xds-common/golib" - "github.com/iotbzh/xds-server/lib/folder" stconfig "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/protocol" ) -// FIXME remove and use an interface on xdsconfig.FolderConfig +// FolderChangeArg argument structure used by FolderChange type FolderChangeArg struct { ID string Label string RelativePath string SyncThingID string - ShareRootDir string } // FolderLoadFromStConfig Load/Retrieve folder config from syncthing database -func (s *SyncThing) FolderLoadFromStConfig(f *[]folder.FolderConfig) error { +/* +func (s *SyncThing) FolderLoadFromStConfig(f *[]XdsFolderConfig) error { defaultSdk := "" // cannot know which was the default sdk @@ -41,26 +40,21 @@ func (s *SyncThing) FolderLoadFromStConfig(f *[]folder.FolderConfig) error { } for _, stFld := range stCfg.Folders { - /* - cliPath := strings.TrimPrefix(stFld.Path, s.conf.FileConf.ShareRootDir) - if cliPath == "" { - cliPath = stFld.Path - }*/ - cliPath := stFld.Path - *f = append(*f, folder.FolderConfig{ + *f = append(*f, XdsFolderConfig{ ID: stFld.ID, Label: stFld.Label, - ClientPath: strings.TrimRight(cliPath, "/"), - Type: folder.TypeCloudSync, - Status: folder.StatusDisable, + ClientPath: strings.TrimRight(stFld.Path, "/"), + Type: XdsTypeCloudSync, + Status: StatusDisable, DefaultSdk: defaultSdk, - RootPath: "", //s.conf.FileConf.ShareRootDir, - DataCloudSync: folder.CloudSyncConfig{SyncThingID: devID}, + RootPath: "", + DataCloudSync: XdsCloudSyncConfig{SyncThingID: devID}, }) } return nil } +*/ // FolderChange is called when configuration has changed func (s *SyncThing) FolderChange(f FolderChangeArg) (string, error) { @@ -111,8 +105,6 @@ func (s *SyncThing) FolderChange(f FolderChangeArg) (string, error) { if err != nil { pathCli = f.RelativePath } - // SEB still need ShareRootDir ? a sup - // pathCli := filepath.Join(f.ShareRootDir, f.RelativePath) folder := stconfig.FolderConfiguration{ ID: id, @@ -146,11 +138,8 @@ func (s *SyncThing) FolderChange(f FolderChangeArg) (string, error) { } err = s.ConfigSet(stCfg) - if err != nil { - s.log.Errorln(err) - } - return id, nil + return id, err } // FolderDelete is called to delete a folder config diff --git a/lib/xdsconfig/config.go b/lib/xdsconfig/config.go index 9cff862..9279a67 100644 --- a/lib/xdsconfig/config.go +++ b/lib/xdsconfig/config.go @@ -10,10 +10,12 @@ import ( "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" common "github.com/iotbzh/xds-common/golib" + uuid "github.com/satori/go.uuid" ) // Config parameters (json format) of /config command type Config struct { + AgentUID string Version string APIVersion string VersionGitTag string @@ -43,8 +45,12 @@ func Init(ctx *cli.Context, log *logrus.Logger) (*Config, error) { defaultWebAppDir := "${EXEPATH}/www" defaultSTHomeDir := "${HOME}/.xds/agent/syncthing-config" + // TODO: allocate uuid only the first time and save+reuse it later + uuid := uuid.NewV1().String() + // Define default configuration c := Config{ + AgentUID: uuid, Version: ctx.App.Metadata["version"].(string), APIVersion: DefaultAPIVersion, VersionGitTag: ctx.App.Metadata["git-tag"].(string), @@ -59,7 +65,6 @@ func Init(ctx *cli.Context, log *logrus.Logger) (*Config, error) { HTTPPort: "8800", WebAppDir: defaultWebAppDir, LogsDir: "/tmp/logs", - // SEB XDSAPIKey: "1234abcezam", ServersConf: []XDSServerConf{ XDSServerConf{ URL: "http://localhost:8000", @@ -107,6 +112,8 @@ func Init(ctx *cli.Context, log *logrus.Logger) (*Config, error) { return nil, fmt.Errorf("Cannot create logs dir: %v", err) } } + + c.Log.Infoln("Agent UUID: ", uuid) c.Log.Infoln("Logs file: ", c.Options.LogFile) c.Log.Infoln("Logs directory: ", c.FileConf.LogsDir) diff --git a/lib/xdsconfig/configfile.go b/lib/xdsconfig/configfile.go index a47038b..6eaaf6a 100644 --- a/lib/xdsconfig/configfile.go +++ b/lib/xdsconfig/configfile.go @@ -26,10 +26,10 @@ type XDSServerConf struct { } type FileConfig struct { - HTTPPort string `json:"httpPort"` - WebAppDir string `json:"webAppDir"` - LogsDir string `json:"logsDir"` - // SEB A SUP ? XDSAPIKey string `json:"xds-apikey"` + HTTPPort string `json:"httpPort"` + WebAppDir string `json:"webAppDir"` + LogsDir string `json:"logsDir"` + XDSAPIKey string `json:"xds-apikey"` ServersConf []XDSServerConf `json:"xdsServers"` SThgConf *SyncThingConf `json:"syncthing"` } diff --git a/webapp/src/app/config/config.component.ts b/webapp/src/app/config/config.component.ts index 101596f..6377844 100644 --- a/webapp/src/app/config/config.component.ts +++ b/webapp/src/app/config/config.component.ts @@ -102,7 +102,6 @@ export class ConfigComponent implements OnInit { xdsAgentRestartConn() { let url = this.xdsServerUrl; this.xdsAgentSvr.setServerUrl(this.curServerID, url); - this.configSvr.loadProjects(); } } diff --git a/webapp/src/app/services/config.service.ts b/webapp/src/app/services/config.service.ts index 090df7b..bbe2fb8 100644 --- a/webapp/src/app/services/config.service.ts +++ b/webapp/src/app/services/config.service.ts @@ -1,25 +1,13 @@ -import { Injectable, OnInit } from '@angular/core'; -import { Http, Headers, RequestOptionsArgs, Response } from '@angular/http'; -import { Location } from '@angular/common'; +import { Injectable } from '@angular/core'; import { CookieService } from 'ngx-cookie'; import { Observable } from 'rxjs/Observable'; -import { Subscriber } from 'rxjs/Subscriber'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; -// Import RxJs required methods -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/observable/throw'; -import 'rxjs/add/operator/mergeMap'; - - -import { XDSAgentService, IXDSProjectConfig } from "../services/xdsagent.service"; import { AlertService, IAlert } from "../services/alert.service"; import { UtilsService } from "../services/utils.service"; export interface IConfig { projectsRootDir: string; - //SEB projects: IProject[]; } @Injectable() @@ -29,21 +17,15 @@ export class ConfigService { private confSubject: BehaviorSubject; private confStore: IConfig; - // SEB cleanup private AgentConnectObs = null; - // SEB cleanup private stConnectObs = null; constructor(private _window: Window, private cookie: CookieService, - private xdsAgentSvr: XDSAgentService, private alert: AlertService, private utils: UtilsService, ) { this.load(); this.confSubject = >new BehaviorSubject(this.confStore); this.Conf$ = this.confSubject.asObservable(); - - // force to load projects - this.loadProjects(); } // Load config @@ -71,107 +53,7 @@ export class ConfigService { this.cookie.putObject("xds-config", cfg); } - loadProjects() { - /* SEB - // Setup connection with local XDS agent - if (this.AgentConnectObs) { - try { - this.AgentConnectObs.unsubscribe(); - } catch (err) { } - this.AgentConnectObs = null; - } - - let cfg = this.confStore.xdsAgent; - this.AgentConnectObs = this.xdsAgentSvr.connect(cfg.retry, cfg.URL) - .subscribe((sts) => { - //console.log("Agent sts", sts); - // FIXME: load projects from local XDS Agent and - // not directly from local syncthing - this._loadProjectFromLocalST(); - - }, error => { - if (error.indexOf("XDS local Agent not responding") !== -1) { - let url_OS_Linux = "https://en.opensuse.org/LinuxAutomotive#Installation_AGL_XDS"; - let url_OS_Other = "https://github.com/iotbzh/xds-agent#how-to-install-on-other-platform"; - let msg = `` + error + `
- You may need to install and execute XDS-Agent:
- On Linux machine -
- On Windows machine -
- On MacOS machine - `; - this.alert.error(msg); - } else { - this.alert.error(error); - } - }); - */ - } - - /* SEB - private _loadProjectFromLocalST() { - // Remove previous subscriber if existing - if (this.stConnectObs) { - try { - this.stConnectObs.unsubscribe(); - } catch (err) { } - this.stConnectObs = null; - } - - // FIXME: move this code and all logic about syncthing inside XDS Agent - // Setup connection with local SyncThing - let retry = this.confStore.localSThg.retry; - let url = this.confStore.localSThg.URL; - this.stConnectObs = this.stSvr.connect(retry, url).subscribe((sts) => { - this.confStore.localSThg.ID = sts.ID; - this.confStore.localSThg.tilde = sts.tilde; - if (this.confStore.projectsRootDir === "") { - this.confStore.projectsRootDir = sts.tilde; - } - - // Rebuild projects definition from local and remote syncthing - this.confStore.projects = []; - - this.xdsServerSvr.getProjects().subscribe(remotePrj => { - this.stSvr.getProjects().subscribe(localPrj => { - remotePrj.forEach(rPrj => { - let lPrj = localPrj.filter(item => item.id === rPrj.id); - if (lPrj.length > 0 || rPrj.type === ProjectType.NATIVE_PATHMAP) { - this._addProject(rPrj, true); - } - }); - this.confSubject.next(Object.assign({}, this.confStore)); - }), error => this.alert.error('Could not load initial state of local projects.'); - }), error => this.alert.error('Could not load initial state of remote projects.'); - - }, error => { - if (error.indexOf("Syncthing local daemon not responding") !== -1) { - let msg = "" + error + "
"; - msg += "Please check that local XDS-Agent is running.
"; - msg += "
"; - this.alert.error(msg); - } else { - this.alert.error(error); - } - }); - } - - set syncToolURL(url: string) { - this.confStore.localSThg.URL = url; - this.save(); - } - */ - set projectsRootDir(p: string) { - /* SEB - if (p.charAt(0) === '~') { - p = this.confStore.localSThg.tilde + p.substring(1); - } - */ this.confStore.projectsRootDir = p; this.save(); } diff --git a/webapp/src/app/services/syncthing.service.ts b/webapp/src/app/services/syncthing.service.ts deleted file mode 100644 index 1561cbf..0000000 --- a/webapp/src/app/services/syncthing.service.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { Injectable } from '@angular/core'; -/* -import { Http, Headers, RequestOptionsArgs, Response } from '@angular/http'; -import { CookieService } from 'ngx-cookie'; -import { Location } from '@angular/common'; -import { Observable } from 'rxjs/Observable'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; - -// Import RxJs required methods -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/observable/throw'; -import 'rxjs/add/observable/of'; -import 'rxjs/add/observable/timer'; -import 'rxjs/add/operator/retryWhen'; - -export interface ISyncThingProject { - id: string; - path: string; - serverSyncThingID: string; - label?: string; -} - -export interface ISyncThingStatus { - ID: string; - baseURL: string; - connected: boolean; - connectionRetry: number; - tilde: string; - rawStatus: any; -} - -// Private interfaces of Syncthing -const ISTCONFIG_VERSION = 20; - -interface ISTFolderDeviceConfiguration { - deviceID: string; - introducedBy: string; -} -interface ISTFolderConfiguration { - id: string; - label: string; - path: string; - type?: number; - devices?: ISTFolderDeviceConfiguration[]; - rescanIntervalS?: number; - ignorePerms?: boolean; - autoNormalize?: boolean; - minDiskFreePct?: number; - versioning?: { type: string; params: string[] }; - copiers?: number; - pullers?: number; - hashers?: number; - order?: number; - ignoreDelete?: boolean; - scanProgressIntervalS?: number; - pullerSleepS?: number; - pullerPauseS?: number; - maxConflicts?: number; - disableSparseFiles?: boolean; - disableTempIndexes?: boolean; - fsync?: boolean; - paused?: boolean; -} - -interface ISTDeviceConfiguration { - deviceID: string; - name?: string; - address?: string[]; - compression?: string; - certName?: string; - introducer?: boolean; - skipIntroductionRemovals?: boolean; - introducedBy?: string; - paused?: boolean; - allowedNetwork?: string[]; -} - -interface ISTGuiConfiguration { - enabled: boolean; - address: string; - user?: string; - password?: string; - useTLS: boolean; - apiKey?: string; - insecureAdminAccess?: boolean; - theme: string; - debugging: boolean; - insecureSkipHostcheck?: boolean; -} - -interface ISTOptionsConfiguration { - listenAddresses: string[]; - globalAnnounceServer: string[]; - // To be completed ... -} - -interface ISTConfiguration { - version: number; - folders: ISTFolderConfiguration[]; - devices: ISTDeviceConfiguration[]; - gui: ISTGuiConfiguration; - options: ISTOptionsConfiguration; - ignoredDevices: string[]; -} - -// Default settings -const DEFAULT_GUI_PORT = 8384; -const DEFAULT_GUI_API_KEY = "1234abcezam"; -const DEFAULT_RESCAN_INTERV = 0; // 0: use syncthing-inotify to detect changes - -*/ - -@Injectable() -export class SyncthingService { - - /* SEB A SUP - public Status$: Observable; - - private baseRestUrl: string; - private apikey: string; - private localSTID: string; - private stCurVersion: number; - private connectionMaxRetry: number; - private _status: ISyncThingStatus = { - ID: null, - baseURL: "", - connected: false, - connectionRetry: 0, - tilde: "", - rawStatus: null, - }; - private statusSubject = >new BehaviorSubject(this._status); - - constructor(private http: Http, private _window: Window, private cookie: CookieService) { - this._status.baseURL = 'http://localhost:' + DEFAULT_GUI_PORT; - this.baseRestUrl = this._status.baseURL + '/rest'; - this.apikey = DEFAULT_GUI_API_KEY; - this.stCurVersion = -1; - this.connectionMaxRetry = 10; // 10 seconds - - this.Status$ = this.statusSubject.asObservable(); - } - - connect(retry: number, url?: string): Observable { - if (url) { - this._status.baseURL = url; - this.baseRestUrl = this._status.baseURL + '/rest'; - } - this._status.connected = false; - this._status.ID = null; - this._status.connectionRetry = 0; - this.connectionMaxRetry = retry || 3600; // 1 hour - return this.getStatus(); - } - - getID(): Observable { - if (this._status.ID != null) { - return Observable.of(this._status.ID); - } - return this.getStatus().map(sts => sts.ID); - } - - getStatus(): Observable { - return this._get('/system/status') - .map((status) => { - this._status.ID = status["myID"]; - this._status.tilde = status["tilde"]; - console.debug('ST local ID', this._status.ID); - - this._status.rawStatus = status; - - return this._status; - }); - } - - getProjects(): Observable { - return this._getConfig() - .map((conf) => conf.folders); - } - - addProject(prj: ISyncThingProject): Observable { - return this.getID() - .flatMap(() => this._getConfig()) - .flatMap((stCfg) => { - let newDevID = prj.serverSyncThingID; - - // Add new Device if needed - let dev = stCfg.devices.filter(item => item.deviceID === newDevID); - if (dev.length <= 0) { - stCfg.devices.push( - { - deviceID: newDevID, - name: "Builder_" + newDevID.slice(0, 15), - address: ["dynamic"], - } - ); - } - - // Add or update Folder settings - let label = prj.label || ""; - let scanInterval = parseInt(this.cookie.get("st-rescanInterval"), 10) || DEFAULT_RESCAN_INTERV; - let folder: ISTFolderConfiguration = { - id: prj.id, - label: label, - path: prj.path, - devices: [{ deviceID: newDevID, introducedBy: "" }], - autoNormalize: true, - rescanIntervalS: scanInterval, - }; - - let idx = stCfg.folders.findIndex(item => item.id === prj.id); - if (idx === -1) { - stCfg.folders.push(folder); - } else { - let newFld = Object.assign({}, stCfg.folders[idx], folder); - stCfg.folders[idx] = newFld; - } - - // Set new config - return this._setConfig(stCfg); - }) - .flatMap(() => this._getConfig()) - .map((newConf) => { - let idx = newConf.folders.findIndex(item => item.id === prj.id); - return newConf.folders[idx]; - }); - } - - deleteProject(id: string): Observable { - let delPrj: ISTFolderConfiguration; - return this._getConfig() - .flatMap((conf: ISTConfiguration) => { - let idx = conf.folders.findIndex(item => item.id === id); - if (idx === -1) { - throw new Error("Cannot delete project: not found"); - } - delPrj = Object.assign({}, conf.folders[idx]); - conf.folders.splice(idx, 1); - return this._setConfig(conf); - }) - .map(() => delPrj); - } - - // - // --- Private functions --- - // - private _getConfig(): Observable { - return this._get('/system/config'); - } - - private _setConfig(cfg: ISTConfiguration): Observable { - return this._post('/system/config', cfg); - } - - private _attachAuthHeaders(options?: any) { - options = options || {}; - let headers = options.headers || new Headers(); - // headers.append('Authorization', 'Basic ' + btoa('username:password')); - headers.append('Accept', 'application/json'); - headers.append('Content-Type', 'application/json'); - if (this.apikey !== "") { - headers.append('X-API-Key', this.apikey); - - } - options.headers = headers; - return options; - } - - private _checkAlive(): Observable { - if (this._status.connected) { - return Observable.of(true); - } - - return this.http.get(this.baseRestUrl + '/system/version', this._attachAuthHeaders()) - .map((r) => this._status.connected = true) - .retryWhen((attempts) => { - this._status.connectionRetry = 0; - return attempts.flatMap(error => { - this._status.connected = false; - if (++this._status.connectionRetry >= this.connectionMaxRetry) { - return Observable.throw("Syncthing local daemon not responding (url=" + this._status.baseURL + ")"); - } else { - return Observable.timer(1000); - } - }); - }); - } - - private _getAPIVersion(): Observable { - if (this.stCurVersion !== -1) { - return Observable.of(this.stCurVersion); - } - - return this.http.get(this.baseRestUrl + '/system/config', this._attachAuthHeaders()) - .map((res: Response) => { - let conf: ISTConfiguration = res.json(); - this.stCurVersion = (conf && conf.version) || -1; - return this.stCurVersion; - }) - .catch(this._handleError); - } - - private _checkAPIVersion(): Observable { - return this._getAPIVersion().map(ver => { - if (ver !== ISTCONFIG_VERSION) { - throw new Error("Unsupported Syncthing version api (" + ver + - " != " + ISTCONFIG_VERSION + ") !"); - } - return ver; - }); - } - - private _get(url: string): Observable { - return this._checkAlive() - .flatMap(() => this._checkAPIVersion()) - .flatMap(() => this.http.get(this.baseRestUrl + url, this._attachAuthHeaders())) - .map((res: Response) => res.json()) - .catch(this._handleError); - } - - private _post(url: string, body: any): Observable { - return this._checkAlive() - .flatMap(() => this._checkAPIVersion()) - .flatMap(() => this.http.post(this.baseRestUrl + url, JSON.stringify(body), this._attachAuthHeaders())) - .map((res: Response) => { - if (res && res.status && res.status === 200) { - return res; - } - throw new Error(res.toString()); - - }) - .catch(this._handleError); - } - - private _handleError(error: Response | any) { - // In a real world app, you might use a remote logging infrastructure - let errMsg: string; - if (this._status) { - this._status.connected = false; - } - if (error instanceof Response) { - const body = error.json() || 'Server error'; - const err = body.error || JSON.stringify(body); - errMsg = `${error.status} - ${error.statusText || ''} ${err}`; - } else { - errMsg = error.message ? error.message : error.toString(); - } - return Observable.throw(errMsg); - } - */ -} diff --git a/webapp/src/app/services/xdsagent.service.ts b/webapp/src/app/services/xdsagent.service.ts index e570399..fd84ccb 100644 --- a/webapp/src/app/services/xdsagent.service.ts +++ b/webapp/src/app/services/xdsagent.service.ts @@ -305,7 +305,7 @@ export class XDSAgentService { id: prjID, rpath: dir, cmd: cmd, - sdkid: sdkid || "", + sdkID: sdkid || "", args: args || [], env: env || [], }); @@ -317,7 +317,7 @@ export class XDSAgentService { { id: prjID, rpath: dir, - sdkid: sdkid, + sdkID: sdkid, args: args || [], env: env || [], }); @@ -382,7 +382,7 @@ export class XDSAgentService { if (err instanceof Response) { const body = err.json() || 'Agent error'; e = body.error || JSON.stringify(body); - if (!e || e === "") { + if (!e || e === "" || e === '{"isTrusted":true}') { e = `${err.status} - ${err.statusText || 'Unknown error'}`; } } else if (typeof err === "object") { -- 2.16.6