diff options
Diffstat (limited to 'lib/apiv1')
-rw-r--r-- | lib/apiv1/agent.go | 3 | ||||
-rw-r--r-- | lib/apiv1/apiv1.go | 12 | ||||
-rw-r--r-- | lib/apiv1/config.go | 7 | ||||
-rw-r--r-- | lib/apiv1/events.go | 147 | ||||
-rw-r--r-- | lib/apiv1/exec.go | 322 | ||||
-rw-r--r-- | lib/apiv1/folders.go | 50 | ||||
-rw-r--r-- | lib/apiv1/make.go | 28 |
7 files changed, 448 insertions, 121 deletions
diff --git a/lib/apiv1/agent.go b/lib/apiv1/agent.go index 651f246..925f12b 100644 --- a/lib/apiv1/agent.go +++ b/lib/apiv1/agent.go @@ -11,6 +11,7 @@ import ( common "github.com/iotbzh/xds-common/golib" ) +// XDSAgentTarball . type XDSAgentTarball struct { OS string `json:"os"` Arch string `json:"arch"` @@ -18,6 +19,8 @@ type XDSAgentTarball struct { RawVersion string `json:"raw-version"` FileURL string `json:"fileUrl"` } + +// XDSAgentInfo . type XDSAgentInfo struct { Tarballs []XDSAgentTarball `json:"tarballs"` } diff --git a/lib/apiv1/apiv1.go b/lib/apiv1/apiv1.go index 7fa69e9..262f513 100644 --- a/lib/apiv1/apiv1.go +++ b/lib/apiv1/apiv1.go @@ -16,19 +16,19 @@ type APIService struct { apiRouter *gin.RouterGroup sessions *session.Sessions cfg *xdsconfig.Config - mfolder *model.Folder + mfolders *model.Folders sdks *crosssdk.SDKs log *logrus.Logger } // New creates a new instance of API service -func New(r *gin.Engine, sess *session.Sessions, cfg *xdsconfig.Config, mfolder *model.Folder, sdks *crosssdk.SDKs) *APIService { +func New(r *gin.Engine, sess *session.Sessions, cfg *xdsconfig.Config, mfolders *model.Folders, sdks *crosssdk.SDKs) *APIService { s := &APIService{ router: r, sessions: sess, apiRouter: r.Group("/api/v1"), cfg: cfg, - mfolder: mfolder, + mfolders: mfolders, sdks: sdks, log: cfg.Log, } @@ -42,6 +42,7 @@ func New(r *gin.Engine, sess *session.Sessions, cfg *xdsconfig.Config, mfolder * s.apiRouter.GET("/folders", s.getFolders) s.apiRouter.GET("/folder/:id", s.getFolder) s.apiRouter.POST("/folder", s.addFolder) + s.apiRouter.POST("/folder/sync/:id", s.syncFolder) s.apiRouter.DELETE("/folder/:id", s.delFolder) s.apiRouter.GET("/sdks", s.getSdks) @@ -52,6 +53,11 @@ func New(r *gin.Engine, sess *session.Sessions, cfg *xdsconfig.Config, mfolder * s.apiRouter.POST("/exec", s.execCmd) s.apiRouter.POST("/exec/:id", s.execCmd) + s.apiRouter.POST("/signal", s.execSignalCmd) + + s.apiRouter.GET("/events", s.eventsList) + s.apiRouter.POST("/events/register", s.eventsRegister) + s.apiRouter.POST("/events/unregister", s.eventsUnRegister) return s } diff --git a/lib/apiv1/config.go b/lib/apiv1/config.go index 662ec8e..4b53217 100644 --- a/lib/apiv1/config.go +++ b/lib/apiv1/config.go @@ -36,10 +36,5 @@ func (s *APIService) setConfig(c *gin.Context) { s.log.Debugln("SET config: ", cfgArg) - if err := s.mfolder.UpdateAll(cfgArg); err != nil { - common.APIError(c, err.Error()) - return - } - - c.JSON(http.StatusOK, s.cfg) + common.APIError(c, "Not Supported") } diff --git a/lib/apiv1/events.go b/lib/apiv1/events.go new file mode 100644 index 0000000..da8298c --- /dev/null +++ b/lib/apiv1/events.go @@ -0,0 +1,147 @@ +package apiv1 + +import ( + "net/http" + "time" + + "github.com/iotbzh/xds-server/lib/folder" + + "github.com/gin-gonic/gin" + common "github.com/iotbzh/xds-common/golib" +) + +// EventArgs is the parameters (json format) of /events/register command +type EventRegisterArgs struct { + Name string `json:"name"` + ProjectID string `json:"filterProjectID"` +} + +type EventUnRegisterArgs struct { + Name string `json:"name"` + ID int `json:"id"` +} + +// EventMsg Message send +type EventMsg struct { + Time string `json:"time"` + Type string `json:"type"` + Folder folder.FolderConfig `json:"folder"` +} + +// EventEvent Event send in WS when an internal event (eg. Syncthing event is received) +const EventEventAll = "event:all" +const EventEventType = "event:" // following by event type + +// eventsList Registering for events that will be send over a WS +func (s *APIService) eventsList(c *gin.Context) { + +} + +// eventsRegister Registering for events that will be send over a WS +func (s *APIService) eventsRegister(c *gin.Context) { + var args EventRegisterArgs + + if c.BindJSON(&args) != nil { + common.APIError(c, "Invalid arguments") + return + } + + sess := s.sessions.Get(c) + if sess == nil { + common.APIError(c, "Unknown sessions") + return + } + + evType := "FolderStateChanged" + 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(EventEventAll, msg); err != nil { + s.log.Errorf("WS Emit Event : %v", err) + } + + if err := (*so).Emit(EventEventType+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 folder.EventCB + cbFunc = func(cfg *folder.FolderConfig, data *folder.EventCBData) { + 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 := EventMsg{ + Time: time.Now().String(), + Type: evType, + Folder: *cfg, + } + + if err := (*so).Emit(EventEventType+evType, msg); err != nil { + s.log.Errorf("WS Emit Folder StateChanged event : %v", err) + } + } + data := make(folder.EventCBData) + data["sid"] = sess.ID + + err := s.mfolders.RegisterEventChange(args.ProjectID, &cbFunc, &data) + if err != nil { + common.APIError(c, err.Error()) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "OK"}) +} + +// eventsRegister Registering for events that will be send over a WS +func (s *APIService) eventsUnRegister(c *gin.Context) { + var args EventUnRegisterArgs + + if c.BindJSON(&args) != nil || args.Name == "" || args.ID < 0 { + common.APIError(c, "Invalid arguments") + return + } + /* TODO + if err := s.mfolders.SThg.Events.UnRegister(args.Name, args.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/apiv1/exec.go b/lib/apiv1/exec.go index 654ff64..6300dba 100644 --- a/lib/apiv1/exec.go +++ b/lib/apiv1/exec.go @@ -1,53 +1,88 @@ package apiv1 import ( + "fmt" "net/http" + "os" + "regexp" "strconv" "strings" "time" "github.com/gin-gonic/gin" common "github.com/iotbzh/xds-common/golib" + "github.com/iotbzh/xds-common/golib/eows" + "github.com/kr/pty" ) -// ExecArgs JSON parameters of /exec command -type ExecArgs struct { - ID string `json:"id" binding:"required"` - SdkID string `json:"sdkid"` // sdk ID to use for setting env - Cmd string `json:"cmd" binding:"required"` - Args []string `json:"args"` - Env []string `json:"env"` - RPath string `json:"rpath"` // relative path into project - ExitImmediate bool `json:"exitImmediate"` // when true, exit event sent immediately when command exited (IOW, don't wait file synchronization) - CmdTimeout int `json:"timeout"` // command completion timeout in Second -} +type ( + // ExecArgs JSON parameters of /exec command + ExecArgs struct { + ID string `json:"id" binding:"required"` + SdkID string `json:"sdkid"` // sdk ID to use for setting env + Cmd string `json:"cmd" binding:"required"` + Args []string `json:"args"` + Env []string `json:"env"` + RPath string `json:"rpath"` // relative path into project + TTY bool `json:"tty"` // Use a tty, specific to gdb --tty option + TTYGdbserverFix bool `json:"ttyGdbserverFix"` // Set to true to activate gdbserver workaround about inferior output + ExitImmediate bool `json:"exitImmediate"` // when true, exit event sent immediately when command exited (IOW, don't wait file synchronization) + CmdTimeout int `json:"timeout"` // command completion timeout in Second + } -// ExecOutMsg Message send on each output (stdout+stderr) of executed command -type ExecOutMsg struct { - CmdID string `json:"cmdID"` - Timestamp string `json:"timestamp"` - Stdout string `json:"stdout"` - Stderr string `json:"stderr"` -} + // ExecInMsg Message used to received input characters (stdin) + ExecInMsg struct { + CmdID string `json:"cmdID"` + Timestamp string `json:"timestamp"` + Stdin string `json:"stdin"` + } -// ExecExitMsg Message send when executed command exited -type ExecExitMsg struct { - CmdID string `json:"cmdID"` - Timestamp string `json:"timestamp"` - Code int `json:"code"` - Error error `json:"error"` -} + // ExecOutMsg Message used to send output characters (stdout+stderr) + ExecOutMsg struct { + CmdID string `json:"cmdID"` + Timestamp string `json:"timestamp"` + Stdout string `json:"stdout"` + Stderr string `json:"stderr"` + } + + // ExecExitMsg Message sent when executed command exited + ExecExitMsg struct { + CmdID string `json:"cmdID"` + Timestamp string `json:"timestamp"` + Code int `json:"code"` + Error error `json:"error"` + } + + // ExecSignalArgs JSON parameters of /exec/signal command + ExecSignalArgs struct { + CmdID string `json:"cmdID" binding:"required"` // command id + Signal string `json:"signal" binding:"required"` // signal number + } +) -// ExecOutEvent Event send in WS when characters are received -const ExecOutEvent = "exec:output" +const ( + // ExecInEvent Event send in WS when characters are sent (stdin) + ExecInEvent = "exec:input" -// ExecExitEvent Event send in WS when program exited -const ExecExitEvent = "exec:exit" + // ExecOutEvent Event send in WS when characters are received (stdout or stderr) + ExecOutEvent = "exec:output" + + // ExecExitEvent Event send in WS when program exited + ExecExitEvent = "exec:exit" + + // ExecInferiorInEvent Event send in WS when characters are sent to an inferior (used by gdb inferior/tty) + ExecInferiorInEvent = "exec:inferior-input" + + // ExecInferiorOutEvent Event send in WS when characters are received by an inferior + ExecInferiorOutEvent = "exec:inferior-output" +) var execCommandID = 1 // ExecCmd executes remotely a command func (s *APIService) execCmd(c *gin.Context) { + var gdbPty, gdbTty *os.File + var err error var args ExecArgs if c.BindJSON(&args) != nil { common.APIError(c, "Invalid arguments") @@ -78,41 +113,123 @@ func (s *APIService) execCmd(c *gin.Context) { return } - prj := s.mfolder.GetFolderFromID(id) - if prj == nil { + f := s.mfolders.Get(id) + if f == nil { common.APIError(c, "Unknown id") return } + folder := *f + prj := folder.GetConfig() - execTmo := args.CmdTimeout - if execTmo == 0 { + // Build command line + cmd := []string{} + // Setup env var regarding Sdk ID (used for example to setup cross toolchain) + if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); len(envCmd) > 0 { + cmd = append(cmd, envCmd...) + cmd = append(cmd, "&&") + } else { + // It's an error if no envcmd found while a sdkid has been provided + if args.SdkID != "" { + common.APIError(c, "Unknown sdkid") + return + } + } + + cmd = append(cmd, "cd", folder.GetFullPath(args.RPath)) + // FIXME - add 'exec' prevents to use syntax: + // xds-exec -l debug -c xds-config.env -- "cd build && cmake .." + // but exec is mandatory to allow to pass correctly signals + // As workaround, exec is set for now on client side (eg. in xds-gdb) + //cmd = append(cmd, "&&", "exec", args.Cmd) + cmd = append(cmd, "&&", args.Cmd) + + // Process command arguments + cmdArgs := make([]string, len(args.Args)+1) + copy(cmdArgs, args.Args) + + // Allocate pts if tty if used + if args.TTY { + gdbPty, gdbTty, err = pty.Open() + if err != nil { + common.APIError(c, err.Error()) + return + } + + s.log.Debugf("Client command tty: %v %v\n", gdbTty.Name(), gdbTty.Name()) + cmdArgs = append(cmdArgs, "--tty="+gdbTty.Name()) + } + + // Unique ID for each commands + cmdID := strconv.Itoa(execCommandID) + execCommandID++ + + // Create new execution over WS context + execWS := eows.New(strings.Join(cmd, " "), cmdArgs, sop, sess.ID, cmdID) + execWS.Log = s.log + + // Append client project dir to environment + execWS.Env = append(args.Env, "CLIENT_PROJECT_DIR="+prj.ClientPath) + + // Set command execution timeout + if args.CmdTimeout == 0 { + // 0 : default timeout // TODO get default timeout from config.json file - execTmo = 24 * 60 * 60 // 1 day + execWS.CmdExecTimeout = 24 * 60 * 60 // 1 day + } else { + execWS.CmdExecTimeout = args.CmdTimeout } - // Define callback for output - var oCB common.EmitOutputCB - oCB = func(sid string, id int, stdout, stderr string, data *map[string]interface{}) { + // Define callback for input (stdin) + execWS.InputEvent = ExecInEvent + execWS.InputCB = func(e *eows.ExecOverWS, stdin string) (string, error) { + s.log.Debugf("STDIN <<%v>>", strings.Replace(stdin, "\n", "\\n", -1)) + + // Handle Ctrl-D + if len(stdin) == 1 && stdin == "\x04" { + // Close stdin + errMsg := fmt.Errorf("close stdin: %v", stdin) + return "", errMsg + } + + // Set correct path + data := e.UserData + rootPath := (*data)["RootPath"].(string) + clientPath := (*data)["ClientPath"].(string) + stdin = strings.Replace(stdin, clientPath, rootPath+"/"+clientPath, -1) + + return stdin, nil + } + + // Define callback for output (stdout+stderr) + execWS.OutputCB = func(e *eows.ExecOverWS, stdout, stderr string) { // IO socket can be nil when disconnected - so := s.sessions.IOSocketGet(sid) + so := s.sessions.IOSocketGet(e.Sid) if so == nil { - s.log.Infof("%s not emitted: WS closed - sid: %s - msg id:%d", ExecOutEvent, sid, id) + s.log.Infof("%s not emitted: WS closed (sid:%s, msgid:%s)", ExecOutEvent, e.Sid, e.CmdID) return } // Retrieve project ID and RootPath + data := e.UserData prjID := (*data)["ID"].(string) prjRootPath := (*data)["RootPath"].(string) + gdbServerTTY := (*data)["gdbServerTTY"].(string) // Cleanup any references to internal rootpath in stdout & stderr stdout = strings.Replace(stdout, prjRootPath, "", -1) stderr = strings.Replace(stderr, prjRootPath, "", -1) - s.log.Debugf("%s emitted - WS sid %s - id:%d - prjID:%s", ExecOutEvent, sid, id, prjID) + s.log.Debugf("%s emitted - WS sid[4:] %s - id:%s - prjID:%s", ExecOutEvent, e.Sid[4:], e.CmdID, prjID) + if stdout != "" { + s.log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1)) + } + if stderr != "" { + s.log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1)) + } // FIXME replace by .BroadcastTo a room err := (*so).Emit(ExecOutEvent, ExecOutMsg{ - CmdID: strconv.Itoa(id), + CmdID: e.CmdID, Timestamp: time.Now().String(), Stdout: stdout, Stderr: stderr, @@ -120,25 +237,58 @@ func (s *APIService) execCmd(c *gin.Context) { if err != nil { s.log.Errorf("WS Emit : %v", err) } + + // XXX - Workaround due to gdbserver bug that doesn't redirect + // inferior output (https://bugs.eclipse.org/bugs/show_bug.cgi?id=437532#c13) + if gdbServerTTY == "workaround" && len(stdout) > 1 && stdout[0] == '&' { + + // Extract and cleanup string like &"bla bla\n" + re := regexp.MustCompile("&\"(.*)\"") + rer := re.FindAllStringSubmatch(stdout, -1) + out := "" + if rer != nil && len(rer) > 0 { + for _, o := range rer { + if len(o) >= 1 { + out = strings.Replace(o[1], "\\n", "\n", -1) + out = strings.Replace(out, "\\r", "\r", -1) + out = strings.Replace(out, "\\t", "\t", -1) + + s.log.Debugf("STDOUT INFERIOR: <<%v>>", out) + err := (*so).Emit(ExecInferiorOutEvent, ExecOutMsg{ + CmdID: e.CmdID, + Timestamp: time.Now().String(), + Stdout: out, + Stderr: "", + }) + if err != nil { + s.log.Errorf("WS Emit : %v", err) + } + } + } + } else { + s.log.Errorf("INFERIOR out parsing error: stdout=<%v>", stdout) + } + } } // Define callback for output - eCB := func(sid string, id int, code int, err error, data *map[string]interface{}) { - s.log.Debugf("Command [Cmd ID %d] exited: code %d, error: %v", id, code, err) + execWS.ExitCB = func(e *eows.ExecOverWS, code int, err error) { + s.log.Debugf("Command [Cmd ID %s] exited: code %d, error: %v", e.CmdID, code, err) // IO socket can be nil when disconnected - so := s.sessions.IOSocketGet(sid) + so := s.sessions.IOSocketGet(e.Sid) if so == nil { - s.log.Infof("%s not emitted - WS closed (id:%d", ExecExitEvent, id) + s.log.Infof("%s not emitted - WS closed (id:%s)", ExecExitEvent, e.CmdID) return } // Retrieve project ID and RootPath + data := e.UserData prjID := (*data)["ID"].(string) exitImm := (*data)["ExitImmediate"].(bool) // XXX - workaround to be sure that Syncthing detected all changes - if err := s.mfolder.ForceSync(prjID); err != nil { + if err := s.mfolders.ForceSync(prjID); err != nil { s.log.Errorf("Error while syncing folder %s: %v", prjID, err) } if !exitImm { @@ -146,8 +296,8 @@ func (s *APIService) execCmd(c *gin.Context) { // FIXME pass as argument tmo := 60 for t := tmo; t > 0; t-- { - s.log.Debugf("Wait file insync for %s (%d/%d)", prjID, t, tmo) - if sync, err := s.mfolder.IsFolderInSync(prjID); sync || err != nil { + s.log.Debugf("Wait file in-sync for %s (%d/%d)", prjID, t, tmo) + if sync, err := s.mfolders.IsFolderInSync(prjID); sync || err != nil { if err != nil { s.log.Errorf("ERROR IsFolderInSync (%s): %v", prjID, err) } @@ -157,44 +307,73 @@ func (s *APIService) execCmd(c *gin.Context) { } } + // Close client tty + if gdbPty != nil { + gdbPty.Close() + } + if gdbTty != nil { + gdbTty.Close() + } + // FIXME replace by .BroadcastTo a room - e := (*so).Emit(ExecExitEvent, ExecExitMsg{ - CmdID: strconv.Itoa(id), + errSoEmit := (*so).Emit(ExecExitEvent, ExecExitMsg{ + CmdID: e.CmdID, Timestamp: time.Now().String(), Code: code, Error: err, }) - if e != nil { - s.log.Errorf("WS Emit : %v", e) + if errSoEmit != nil { + s.log.Errorf("WS Emit : %v", errSoEmit) } } - cmdID := execCommandID - execCommandID++ - cmd := []string{} - - // Setup env var regarding Sdk ID (used for example to setup cross toolchain) - if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); len(envCmd) > 0 { - cmd = append(cmd, envCmd...) - cmd = append(cmd, "&&") + // User data (used within callbacks) + data := make(map[string]interface{}) + data["ID"] = prj.ID + data["RootPath"] = prj.RootPath + data["ClientPath"] = prj.ClientPath + data["ExitImmediate"] = args.ExitImmediate + if args.TTY && args.TTYGdbserverFix { + data["gdbServerTTY"] = "workaround" + } else { + data["gdbServerTTY"] = "" } + execWS.UserData = &data - cmd = append(cmd, "cd", prj.GetFullPath(args.RPath), "&&", args.Cmd) - if len(args.Args) > 0 { - cmd = append(cmd, args.Args...) + // Start command execution + s.log.Debugf("Execute [Cmd ID %s]: %v %v", execWS.CmdID, execWS.Cmd, execWS.Args) + + err = execWS.Start() + if err != nil { + common.APIError(c, err.Error()) + return } - // Append client project dir to environment - args.Env = append(args.Env, "CLIENT_PROJECT_DIR="+prj.RelativePath) + c.JSON(http.StatusOK, + gin.H{ + "status": "OK", + "cmdID": execWS.CmdID, + }) +} - s.log.Debugf("Execute [Cmd ID %d]: %v", cmdID, cmd) +// ExecCmd executes remotely a command +func (s *APIService) execSignalCmd(c *gin.Context) { + var args ExecSignalArgs - data := make(map[string]interface{}) - data["ID"] = prj.ID - data["RootPath"] = prj.RootPath - data["ExitImmediate"] = args.ExitImmediate + if c.BindJSON(&args) != nil { + common.APIError(c, "Invalid arguments") + return + } + + s.log.Debugf("Signal %s for command ID %s", args.Signal, args.CmdID) + + e := eows.GetEows(args.CmdID) + if e == nil { + common.APIError(c, "unknown cmdID") + return + } - err := common.ExecPipeWs(cmd, args.Env, sop, sess.ID, cmdID, execTmo, s.log, oCB, eCB, &data) + err := e.Signal(args.Signal) if err != nil { common.APIError(c, err.Error()) return @@ -203,6 +382,5 @@ func (s *APIService) execCmd(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "OK", - "cmdID": cmdID, }) } diff --git a/lib/apiv1/folders.go b/lib/apiv1/folders.go index 44bda24..cf56c3f 100644 --- a/lib/apiv1/folders.go +++ b/lib/apiv1/folders.go @@ -2,49 +2,39 @@ package apiv1 import ( "net/http" - "strconv" "github.com/gin-gonic/gin" common "github.com/iotbzh/xds-common/golib" - "github.com/iotbzh/xds-server/lib/xdsconfig" + "github.com/iotbzh/xds-server/lib/folder" ) // getFolders returns all folders configuration func (s *APIService) getFolders(c *gin.Context) { - confMut.Lock() - defer confMut.Unlock() - - c.JSON(http.StatusOK, s.cfg.Folders) + c.JSON(http.StatusOK, s.mfolders.GetConfigArr()) } // getFolder returns a specific folder configuration func (s *APIService) getFolder(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil || id < 0 || id > len(s.cfg.Folders) { + f := s.mfolders.Get(c.Param("id")) + if f == nil { common.APIError(c, "Invalid id") return } - confMut.Lock() - defer confMut.Unlock() - - c.JSON(http.StatusOK, s.cfg.Folders[id]) + c.JSON(http.StatusOK, (*f).GetConfig()) } // addFolder adds a new folder to server config func (s *APIService) addFolder(c *gin.Context) { - var cfgArg xdsconfig.FolderConfig + var cfgArg folder.FolderConfig if c.BindJSON(&cfgArg) != nil { common.APIError(c, "Invalid arguments") return } - confMut.Lock() - defer confMut.Unlock() - s.log.Debugln("Add folder config: ", cfgArg) - newFld, err := s.mfolder.UpdateFolder(cfgArg) + newFld, err := s.mfolders.Add(cfgArg) if err != nil { common.APIError(c, err.Error()) return @@ -53,25 +43,31 @@ func (s *APIService) addFolder(c *gin.Context) { c.JSON(http.StatusOK, newFld) } -// delFolder deletes folder from server config -func (s *APIService) delFolder(c *gin.Context) { +// syncFolder force synchronization of folder files +func (s *APIService) syncFolder(c *gin.Context) { id := c.Param("id") - if id == "" { - common.APIError(c, "Invalid id") + + s.log.Debugln("Sync folder id: ", id) + + err := s.mfolders.ForceSync(id) + if err != nil { + common.APIError(c, err.Error()) return } - confMut.Lock() - defer confMut.Unlock() + c.JSON(http.StatusOK, "") +} + +// delFolder deletes folder from server config +func (s *APIService) delFolder(c *gin.Context) { + id := c.Param("id") s.log.Debugln("Delete folder id ", id) - var delEntry xdsconfig.FolderConfig - var err error - if delEntry, err = s.mfolder.DeleteFolder(id); err != nil { + delEntry, err := s.mfolders.Delete(id) + if err != nil { common.APIError(c, err.Error()) return } c.JSON(http.StatusOK, delEntry) - } diff --git a/lib/apiv1/make.go b/lib/apiv1/make.go index 5cd98c6..cf76476 100644 --- a/lib/apiv1/make.go +++ b/lib/apiv1/make.go @@ -76,11 +76,13 @@ func (s *APIService) buildMake(c *gin.Context) { return } - prj := s.mfolder.GetFolderFromID(id) - if prj == nil { + pf := s.mfolders.Get(id) + if pf == nil { common.APIError(c, "Unknown id") return } + folder := *pf + prj := folder.GetConfig() execTmo := args.CmdTimeout if execTmo == 0 { @@ -92,11 +94,11 @@ func (s *APIService) buildMake(c *gin.Context) { // Define callback for output var oCB common.EmitOutputCB - oCB = func(sid string, id int, stdout, stderr string, data *map[string]interface{}) { + oCB = func(sid string, cmdID string, stdout, stderr string, data *map[string]interface{}) { // IO socket can be nil when disconnected so := s.sessions.IOSocketGet(sid) if so == nil { - s.log.Infof("%s not emitted: WS closed - sid: %s - msg id:%d", MakeOutEvent, sid, id) + s.log.Infof("%s not emitted: WS closed - sid: %s - msg id:%s", MakeOutEvent, sid, cmdID) return } @@ -112,7 +114,7 @@ func (s *APIService) buildMake(c *gin.Context) { // FIXME replace by .BroadcastTo a room err := (*so).Emit(MakeOutEvent, MakeOutMsg{ - CmdID: strconv.Itoa(id), + CmdID: cmdID, Timestamp: time.Now().String(), Stdout: stdout, Stderr: stderr, @@ -123,13 +125,13 @@ func (s *APIService) buildMake(c *gin.Context) { } // Define callback for output - eCB := func(sid string, id int, code int, err error, data *map[string]interface{}) { - s.log.Debugf("Command [Cmd ID %d] exited: code %d, error: %v", id, code, err) + eCB := func(sid string, cmdID string, code int, err error, data *map[string]interface{}) { + s.log.Debugf("Command [Cmd ID %s] exited: code %d, error: %v", cmdID, code, err) // IO socket can be nil when disconnected so := s.sessions.IOSocketGet(sid) if so == nil { - s.log.Infof("%s not emitted - WS closed (id:%d", MakeExitEvent, id) + s.log.Infof("%s not emitted - WS closed (id:%s", MakeExitEvent, cmdID) return } @@ -138,7 +140,7 @@ func (s *APIService) buildMake(c *gin.Context) { exitImm := (*data)["ExitImmediate"].(bool) // XXX - workaround to be sure that Syncthing detected all changes - if err := s.mfolder.ForceSync(prjID); err != nil { + if err := s.mfolders.ForceSync(prjID); err != nil { s.log.Errorf("Error while syncing folder %s: %v", prjID, err) } if !exitImm { @@ -147,7 +149,7 @@ func (s *APIService) buildMake(c *gin.Context) { tmo := 60 for t := tmo; t > 0; t-- { s.log.Debugf("Wait file insync for %s (%d/%d)", prjID, t, tmo) - if sync, err := s.mfolder.IsFolderInSync(prjID); sync || err != nil { + if sync, err := s.mfolders.IsFolderInSync(prjID); sync || err != nil { if err != nil { s.log.Errorf("ERROR IsFolderInSync (%s): %v", prjID, err) } @@ -159,7 +161,7 @@ func (s *APIService) buildMake(c *gin.Context) { // FIXME replace by .BroadcastTo a room e := (*so).Emit(MakeExitEvent, MakeExitMsg{ - CmdID: strconv.Itoa(id), + CmdID: id, Timestamp: time.Now().String(), Code: code, Error: err, @@ -169,7 +171,7 @@ func (s *APIService) buildMake(c *gin.Context) { } } - cmdID := makeCommandID + cmdID := strconv.Itoa(makeCommandID) makeCommandID++ cmd := []string{} @@ -179,7 +181,7 @@ func (s *APIService) buildMake(c *gin.Context) { cmd = append(cmd, "&&") } - cmd = append(cmd, "cd", prj.GetFullPath(args.RPath), "&&", "make") + cmd = append(cmd, "cd", folder.GetFullPath(args.RPath), "&&", "make") if len(args.Args) > 0 { cmd = append(cmd, args.Args...) } |