Refit source files to have a public xs-apiv1 lib package.
authorSebastien Douheret <sebastien.douheret@iot.bzh>
Wed, 29 Nov 2017 07:54:00 +0000 (08:54 +0100)
committerSebastien Douheret <sebastien.douheret@iot.bzh>
Wed, 29 Nov 2017 10:10:30 +0000 (11:10 +0100)
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
32 files changed:
.vscode/settings.json
lib/apiv1/make.go [deleted file]
lib/apiv1/version.go [deleted file]
lib/crosssdk/sdk.go [deleted file]
lib/syncthing/stfolder.go
lib/xdsconfig/builderconfig.go
lib/xdsconfig/config.go
lib/xdsserver/apiv1-config.go [moved from lib/apiv1/config.go with 78% similarity]
lib/xdsserver/apiv1-events.go [moved from lib/apiv1/events.go with 64% similarity]
lib/xdsserver/apiv1-exec.go [moved from lib/apiv1/exec.go with 60% similarity]
lib/xdsserver/apiv1-folders.go [moved from lib/apiv1/folders.go with 90% similarity]
lib/xdsserver/apiv1-make.go [new file with mode: 0644]
lib/xdsserver/apiv1-sdks.go [moved from lib/apiv1/sdks.go with 96% similarity]
lib/xdsserver/apiv1-version.go [new file with mode: 0644]
lib/xdsserver/apiv1.go [moved from lib/apiv1/apiv1.go with 58% similarity]
lib/xdsserver/folder-interface.go [new file with mode: 0644]
lib/xdsserver/folder-pathmap.go [moved from lib/folder/folder-pathmap.go with 84% similarity]
lib/xdsserver/folder-st-disable.go [moved from lib/folder/folder-st-disable.go with 75% similarity]
lib/xdsserver/folder-st.go [moved from lib/syncthing/folder-st.go with 75% similarity]
lib/xdsserver/folders.go [moved from lib/model/folders.go with 79% similarity]
lib/xdsserver/sdk.go [new file with mode: 0644]
lib/xdsserver/sdks.go [moved from lib/crosssdk/sdks.go with 72% similarity]
lib/xdsserver/sessions.go [moved from lib/session/session.go with 92% similarity]
lib/xdsserver/webserver.go [moved from lib/webserver/server.go with 59% similarity]
lib/xdsserver/xdsserver.go [new file with mode: 0644]
lib/xsapiv1/config.go [new file with mode: 0644]
lib/xsapiv1/events.go [new file with mode: 0644]
lib/xsapiv1/exec.go [new file with mode: 0644]
lib/xsapiv1/folders.go [moved from lib/folder/folder-interface.go with 59% similarity]
lib/xsapiv1/sdks.go [new file with mode: 0644]
lib/xsapiv1/version.go [new file with mode: 0644]
main.go

index 569ca52..5a12530 100644 (file)
@@ -28,6 +28,7 @@
         "rpath", "WSID", "sess", "IXDS", "xdsconfig", "xdsserver", "mfolder",
         "inotify", "Inot", "pname", "pkill", "sdkid", "CLOUDSYNC", "xdsagent",
         "gdbserver", "golib", "eows", "mfolders", "IFOLDER", "flds", "dflt",
-        "stconfig", "reflectme", "franciscocpg"
+        "stconfig", "reflectme", "franciscocpg", "crosssdk", "urfave", "EXEPATH",
+        "conv"
     ]
 }
diff --git a/lib/apiv1/make.go b/lib/apiv1/make.go
deleted file mode 100644 (file)
index 6e0c7d6..0000000
+++ /dev/null
@@ -1,215 +0,0 @@
-package apiv1
-
-import (
-       "net/http"
-       "strings"
-
-       "time"
-
-       "strconv"
-
-       "github.com/gin-gonic/gin"
-       common "github.com/iotbzh/xds-common/golib"
-)
-
-// MakeArgs is the parameters (json format) of /make command
-type MakeArgs struct {
-       ID            string   `json:"id"`
-       SdkID         string   `json:"sdkID"` // sdk ID to use for setting env
-       CmdID         string   `json:"cmdID"` // command unique ID
-       Args          []string `json:"args"`  // args to pass to make command
-       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
-}
-
-// MakeOutMsg Message send on each output (stdout+stderr) of make command
-type MakeOutMsg struct {
-       CmdID     string `json:"cmdID"`
-       Timestamp string `json:"timestamp"`
-       Stdout    string `json:"stdout"`
-       Stderr    string `json:"stderr"`
-}
-
-// MakeExitMsg Message send on make command exit
-type MakeExitMsg struct {
-       CmdID     string `json:"cmdID"`
-       Timestamp string `json:"timestamp"`
-       Code      int    `json:"code"`
-       Error     error  `json:"error"`
-}
-
-// MakeOutEvent Event send in WS when characters are received on stdout/stderr
-const MakeOutEvent = "make:output"
-
-// MakeExitEvent Event send in WS when command exited
-const MakeExitEvent = "make:exit"
-
-var makeCommandID = 1
-
-func (s *APIService) buildMake(c *gin.Context) {
-       var args MakeArgs
-
-       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
-       }
-       sop := sess.IOSocket
-       if sop == nil {
-               common.APIError(c, "Websocket not established")
-               return
-       }
-
-       // Allow to pass id in url (/make/:id) or as JSON argument
-       idArg := c.Param("id")
-       if idArg == "" {
-               idArg = args.ID
-       }
-       if idArg == "" {
-               common.APIError(c, "Invalid id")
-               return
-       }
-       id, err := s.mfolders.ResolveID(idArg)
-       if err != nil {
-               common.APIError(c, err.Error())
-               return
-       }
-       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 {
-               // TODO get default timeout from config.json file
-               execTmo = 24 * 60 * 60 // 1 day
-       }
-
-       // TODO merge all code below with exec.go
-
-       // Define callback for output
-       var oCB common.EmitOutputCB
-       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:%s", MakeOutEvent, sid, cmdID)
-                       return
-               }
-
-               // Retrieve project ID and RootPath
-               prjID := (*data)["ID"].(string)
-               prjRootPath := (*data)["RootPath"].(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", MakeOutEvent, sid, id, prjID)
-
-               // FIXME replace by .BroadcastTo a room
-               err := (*so).Emit(MakeOutEvent, MakeOutMsg{
-                       CmdID:     cmdID,
-                       Timestamp: time.Now().String(),
-                       Stdout:    stdout,
-                       Stderr:    stderr,
-               })
-               if err != nil {
-                       s.log.Errorf("WS Emit : %v", err)
-               }
-       }
-
-       // Define callback for output
-       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:%s", MakeExitEvent, cmdID)
-                       return
-               }
-
-               // Retrieve project ID and RootPath
-               prjID := (*data)["ID"].(string)
-               exitImm := (*data)["ExitImmediate"].(bool)
-
-               // XXX - workaround to be sure that Syncthing detected all changes
-               if err := s.mfolders.ForceSync(prjID); err != nil {
-                       s.log.Errorf("Error while syncing folder %s: %v", prjID, err)
-               }
-               if !exitImm {
-                       // Wait end of file sync
-                       // 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.mfolders.IsFolderInSync(prjID); sync || err != nil {
-                                       if err != nil {
-                                               s.log.Errorf("ERROR IsFolderInSync (%s): %v", prjID, err)
-                                       }
-                                       break
-                               }
-                               time.Sleep(time.Second)
-                       }
-               }
-
-               // FIXME replace by .BroadcastTo a room
-               e := (*so).Emit(MakeExitEvent, MakeExitMsg{
-                       CmdID:     id,
-                       Timestamp: time.Now().String(),
-                       Code:      code,
-                       Error:     err,
-               })
-               if e != nil {
-                       s.log.Errorf("WS Emit : %v", e)
-               }
-       }
-
-       // Unique ID for each commands
-       if args.CmdID == "" {
-               args.CmdID = s.cfg.ServerUID[:18] + "_" + strconv.Itoa(makeCommandID)
-               makeCommandID++
-       }
-       cmd := []string{}
-
-       // Retrieve env command regarding Sdk ID
-       if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); len(envCmd) > 0 {
-               cmd = append(cmd, envCmd...)
-               cmd = append(cmd, "&&")
-       }
-
-       cmd = append(cmd, "cd", folder.GetFullPath(args.RPath), "&&", "make")
-       if len(args.Args) > 0 {
-               cmd = append(cmd, args.Args...)
-       }
-
-       s.log.Debugf("Execute [Cmd ID %d]: %v", args.CmdID, cmd)
-
-       data := make(map[string]interface{})
-       data["ID"] = prj.ID
-       data["RootPath"] = prj.RootPath
-       data["ExitImmediate"] = args.ExitImmediate
-
-       err = common.ExecPipeWs(cmd, args.Env, sop, sess.ID, args.CmdID, execTmo, s.log, oCB, eCB, &data)
-       if err != nil {
-               common.APIError(c, err.Error())
-               return
-       }
-
-       c.JSON(http.StatusOK,
-               gin.H{
-                       "status": "OK",
-                       "cmdID":  args.CmdID,
-               })
-}
diff --git a/lib/apiv1/version.go b/lib/apiv1/version.go
deleted file mode 100644 (file)
index 8f928ec..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-package apiv1
-
-import (
-       "net/http"
-
-       "github.com/gin-gonic/gin"
-)
-
-type version struct {
-       ID            string `json:"id"`
-       Version       string `json:"version"`
-       APIVersion    string `json:"apiVersion"`
-       VersionGitTag string `json:"gitTag"`
-}
-
-// getInfo : return various information about server
-func (s *APIService) getVersion(c *gin.Context) {
-       response := version{
-               ID:            s.cfg.ServerUID,
-               Version:       s.cfg.Version,
-               APIVersion:    s.cfg.APIVersion,
-               VersionGitTag: s.cfg.VersionGitTag,
-       }
-
-       c.JSON(http.StatusOK, response)
-}
diff --git a/lib/crosssdk/sdk.go b/lib/crosssdk/sdk.go
deleted file mode 100644 (file)
index 5be8954..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-package crosssdk
-
-import (
-       "fmt"
-       "path/filepath"
-
-       uuid "github.com/satori/go.uuid"
-)
-
-// SDK Define a cross tool chain used to build application
-type SDK struct {
-       ID      string `json:"id" binding:"required"`
-       Name    string `json:"name"`
-       Profile string `json:"profile"`
-       Version string `json:"version"`
-       Arch    string `json:"arch"`
-       Path    string `json:"path"`
-
-       // Not exported fields
-       EnvFile string `json:"-"`
-}
-
-// NewCrossSDK creates a new instance of Syncthing
-func NewCrossSDK(path string) (*SDK, error) {
-       // Assume that we have .../<profile>/<version>/<arch>
-       s := SDK{Path: path}
-
-       s.Arch = filepath.Base(path)
-
-       d := filepath.Dir(path)
-       s.Version = filepath.Base(d)
-
-       d = filepath.Dir(d)
-       s.Profile = filepath.Base(d)
-
-       // Use V3 to ensure that we get same uuid on restart
-       s.ID = uuid.NewV3(uuid.FromStringOrNil("sdks"), s.Profile+"_"+s.Arch+"_"+s.Version).String()
-       s.Name = s.Arch + "  (" + s.Version + ")"
-
-       envFile := filepath.Join(path, "environment-setup*")
-       ef, err := filepath.Glob(envFile)
-       if err != nil {
-               return nil, fmt.Errorf("Cannot retrieve environment setup file: %v", err)
-       }
-       if len(ef) != 1 {
-               return nil, fmt.Errorf("No environment setup file found match %s", envFile)
-       }
-       s.EnvFile = ef[0]
-
-       return &s, nil
-}
-
-// GetEnvCmd returns the command used to initialized the environment
-func (s *SDK) GetEnvCmd() []string {
-       return []string{"source", s.EnvFile}
-}
index 503ba4b..1a5a7ec 100644 (file)
@@ -6,13 +6,13 @@ import (
        "path/filepath"
        "strings"
 
-       "github.com/iotbzh/xds-server/lib/folder"
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
        stconfig "github.com/syncthing/syncthing/lib/config"
        "github.com/syncthing/syncthing/lib/protocol"
 )
 
 // FolderLoadFromStConfig Load/Retrieve folder config from syncthing database
-func (s *SyncThing) FolderLoadFromStConfig(f *[]folder.FolderConfig) error {
+func (s *SyncThing) FolderLoadFromStConfig(f *[]xsapiv1.FolderConfig) error {
 
        defaultSdk := "" // cannot know which was the default sdk
 
@@ -36,15 +36,15 @@ func (s *SyncThing) FolderLoadFromStConfig(f *[]folder.FolderConfig) error {
                if cliPath == "" {
                        cliPath = stFld.Path
                }
-               *f = append(*f, folder.FolderConfig{
+               *f = append(*f, xsapiv1.FolderConfig{
                        ID:            stFld.ID,
                        Label:         stFld.Label,
                        ClientPath:    strings.TrimRight(cliPath, "/"),
-                       Type:          folder.TypeCloudSync,
-                       Status:        folder.StatusDisable,
+                       Type:          xsapiv1.TypeCloudSync,
+                       Status:        xsapiv1.StatusDisable,
                        DefaultSdk:    defaultSdk,
                        RootPath:      s.conf.FileConf.ShareRootDir,
-                       DataCloudSync: folder.CloudSyncConfig{SyncThingID: devID},
+                       DataCloudSync: xsapiv1.CloudSyncConfig{SyncThingID: devID},
                })
        }
 
@@ -52,7 +52,7 @@ func (s *SyncThing) FolderLoadFromStConfig(f *[]folder.FolderConfig) error {
 }
 
 // FolderChange is called when configuration has changed
-func (s *SyncThing) FolderChange(f folder.FolderConfig) (string, error) {
+func (s *SyncThing) FolderChange(f xsapiv1.FolderConfig) (string, error) {
 
        // Get current config
        stCfg, err := s.ConfigGet()
index 6fc1814..3b1ca55 100644 (file)
@@ -3,24 +3,19 @@ package xdsconfig
 import (
        "errors"
        "net"
-)
 
-// BuilderConfig represents the builder container configuration
-type BuilderConfig struct {
-       IP          string `json:"ip"`
-       Port        string `json:"port"`
-       SyncThingID string `json:"syncThingID"`
-}
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
+)
 
 // NewBuilderConfig creates a new BuilderConfig instance
-func NewBuilderConfig(stID string) (BuilderConfig, error) {
+func NewBuilderConfig(stID string) (xsapiv1.BuilderConfig, error) {
        // Do we really need it ? may be not accessible from client side
        ip, err := getLocalIP()
        if err != nil {
-               return BuilderConfig{}, err
+               return xsapiv1.BuilderConfig{}, err
        }
 
-       b := BuilderConfig{
+       b := xsapiv1.BuilderConfig{
                IP:          ip, // TODO currently not used
                Port:        "", // TODO currently not used
                SyncThingID: stID,
index 0fc1346..0201d40 100644 (file)
@@ -9,16 +9,13 @@ import (
        "github.com/Sirupsen/logrus"
        "github.com/codegangsta/cli"
        common "github.com/iotbzh/xds-common/golib"
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
 )
 
 // Config parameters (json format) of /config command
 type Config struct {
-       ServerUID        string          `json:"id"`
-       Version          string          `json:"version"`
-       APIVersion       string          `json:"apiVersion"`
-       VersionGitTag    string          `json:"gitTag"`
-       SupportedSharing map[string]bool `json:"supportedSharing"`
-       Builder          BuilderConfig   `json:"builder"`
+       // Public APIConfig fields
+       xsapiv1.APIConfig
 
        // Private (un-exported fields in REST GET /config route)
        Options       Options        `json:"-"`
@@ -64,12 +61,14 @@ func Init(cliCtx *cli.Context, log *logrus.Logger) (*Config, error) {
 
        // Define default configuration
        c := Config{
-               ServerUID:        uuid,
-               Version:          cliCtx.App.Metadata["version"].(string),
-               APIVersion:       DefaultAPIVersion,
-               VersionGitTag:    cliCtx.App.Metadata["git-tag"].(string),
-               Builder:          BuilderConfig{},
-               SupportedSharing: map[string]bool{ /*FIXME USE folder.TypePathMap*/ "PathMap": true},
+               APIConfig: xsapiv1.APIConfig{
+                       ServerUID:        uuid,
+                       Version:          cliCtx.App.Metadata["version"].(string),
+                       APIVersion:       DefaultAPIVersion,
+                       VersionGitTag:    cliCtx.App.Metadata["git-tag"].(string),
+                       Builder:          xsapiv1.BuilderConfig{},
+                       SupportedSharing: map[string]bool{ /*FIXME USE folder.TypePathMap*/ "PathMap": true},
+               },
 
                Options: Options{
                        ConfigFile:     cliCtx.GlobalString("config"),
similarity index 78%
rename from lib/apiv1/config.go
rename to lib/xdsserver/apiv1-config.go
index 4b53217..5a5bb6e 100644 (file)
@@ -1,4 +1,4 @@
-package apiv1
+package xdsserver
 
 import (
        "net/http"
@@ -6,7 +6,7 @@ import (
 
        "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/xsapiv1"
 )
 
 var confMut sync.Mutex
@@ -16,7 +16,7 @@ func (s *APIService) getConfig(c *gin.Context) {
        confMut.Lock()
        defer confMut.Unlock()
 
-       c.JSON(http.StatusOK, s.cfg)
+       c.JSON(http.StatusOK, s.Config)
 }
 
 // SetConfig sets server configuration
@@ -24,7 +24,7 @@ func (s *APIService) setConfig(c *gin.Context) {
        // FIXME - must be tested
        c.JSON(http.StatusNotImplemented, "Not implemented")
 
-       var cfgArg xdsconfig.Config
+       var cfgArg xsapiv1.APIConfig
 
        if c.BindJSON(&cfgArg) != nil {
                common.APIError(c, "Invalid arguments")
@@ -34,7 +34,7 @@ func (s *APIService) setConfig(c *gin.Context) {
        confMut.Lock()
        defer confMut.Unlock()
 
-       s.log.Debugln("SET config: ", cfgArg)
+       s.Log.Debugln("SET config: ", cfgArg)
 
        common.APIError(c, "Not Supported")
 }
similarity index 64%
rename from lib/apiv1/events.go
rename to lib/xdsserver/apiv1-events.go
index d837571..3823f9e 100644 (file)
@@ -1,43 +1,13 @@
-package apiv1
+package xdsserver
 
 import (
        "net/http"
        "strings"
        "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 (
-       // EventTypePrefix Used as event prefix
-       EventTypePrefix = "event:" // following by event type
-
-       // Supported Events type
-       EVTAll               = EventTypePrefix + "all"
-       EVTFolderChange      = EventTypePrefix + "folder-change"       // type EventMsg with Data type apiv1.???
-       EVTFolderStateChange = EventTypePrefix + "folder-state-change" // type EventMsg with Data type apiv1.???
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
 )
 
 // eventsList Registering for events that will be send over a WS
@@ -47,7 +17,7 @@ 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
+       var args xsapiv1.EventRegisterArgs
 
        if c.BindJSON(&args) != nil {
                common.APIError(c, "Invalid arguments")
@@ -60,7 +30,7 @@ func (s *APIService) eventsRegister(c *gin.Context) {
                return
        }
 
-       evType := strings.TrimPrefix(EVTFolderStateChange, EventTypePrefix)
+       evType := strings.TrimPrefix(xsapiv1.EVTFolderStateChange, xsapiv1.EventTypePrefix)
        if args.Name != evType {
                common.APIError(c, "Unsupported event name")
                return
@@ -102,8 +72,8 @@ func (s *APIService) eventsRegister(c *gin.Context) {
        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) {
+       var cbFunc FolderEventCB
+       cbFunc = func(cfg *xsapiv1.FolderConfig, data *FolderEventCBData) {
                ssid := (*data)["sid"].(string)
                so := s.sessions.IOSocketGet(ssid)
                if so == nil {
@@ -114,20 +84,20 @@ func (s *APIService) eventsRegister(c *gin.Context) {
                        return
                }
 
-               msg := EventMsg{
+               msg := xsapiv1.EventMsg{
                        Time:   time.Now().String(),
                        Type:   evType,
                        Folder: *cfg,
                }
 
-               s.log.Debugf("WS Emit %s - Status=%10s, IsInSync=%6v, ID=%s",
-                       EventTypePrefix+evType, cfg.Status, cfg.IsInSync, cfg.ID)
+               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(EventTypePrefix+evType, msg); err != nil {
-                       s.log.Errorf("WS Emit Folder StateChanged event : %v", err)
+               if err := (*so).Emit(xsapiv1.EventTypePrefix+evType, msg); err != nil {
+                       s.Log.Errorf("WS Emit Folder StateChanged event : %v", err)
                }
        }
-       data := make(folder.EventCBData)
+       data := make(FolderEventCBData)
        data["sid"] = sess.ID
 
        prjID, err := s.mfolders.ResolveID(args.ProjectID)
@@ -145,7 +115,7 @@ func (s *APIService) eventsRegister(c *gin.Context) {
 
 // eventsRegister Registering for events that will be send over a WS
 func (s *APIService) eventsUnRegister(c *gin.Context) {
-       var args EventUnRegisterArgs
+       var args xsapiv1.EventUnRegisterArgs
 
        if c.BindJSON(&args) != nil || args.Name == "" || args.ID < 0 {
                common.APIError(c, "Invalid arguments")
similarity index 60%
rename from lib/apiv1/exec.go
rename to lib/xdsserver/apiv1-exec.go
index baf431f..ce5e7b7 100644 (file)
@@ -1,4 +1,4 @@
-package apiv1
+package xdsserver
 
 import (
        "fmt"
@@ -12,91 +12,17 @@ import (
        "github.com/gin-gonic/gin"
        common "github.com/iotbzh/xds-common/golib"
        "github.com/iotbzh/xds-common/golib/eows"
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
        "github.com/kr/pty"
 )
 
-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
-               CmdID           string   `json:"cmdID"` // command unique ID
-               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
-       }
-
-       // ExecRes JSON result of /exec command
-       ExecRes struct {
-               Status string `json:"status"` // status OK
-               CmdID  string `json:"cmdID"`  // command unique ID
-       }
-
-       // ExecSigRes JSON result of /signal command
-       ExecSigRes struct {
-               Status string `json:"status"` // status OK
-               CmdID  string `json:"cmdID"`  // command unique ID
-       }
-
-       // ExecInMsg Message used to received input characters (stdin)
-       ExecInMsg struct {
-               CmdID     string `json:"cmdID"`
-               Timestamp string `json:"timestamp"`
-               Stdin     string `json:"stdin"`
-       }
-
-       // 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
-       }
-)
-
-const (
-       // ExecInEvent Event send in WS when characters are sent (stdin)
-       ExecInEvent = "exec:input"
-
-       // 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
+       var args xsapiv1.ExecArgs
        if c.BindJSON(&args) != nil {
                common.APIError(c, "Invalid arguments")
                return
@@ -180,19 +106,19 @@ func (s *APIService) execCmd(c *gin.Context) {
                        return
                }
 
-               s.log.Debugf("Client command tty: %v %v\n", gdbTty.Name(), gdbTty.Name())
+               s.Log.Debugf("Client command tty: %v %v\n", gdbTty.Name(), gdbTty.Name())
                cmdArgs = append(cmdArgs, "--tty="+gdbTty.Name())
        }
 
        // Unique ID for each commands
        if args.CmdID == "" {
-               args.CmdID = s.cfg.ServerUID[:18] + "_" + strconv.Itoa(execCommandID)
+               args.CmdID = s.Config.ServerUID[:18] + "_" + strconv.Itoa(execCommandID)
                execCommandID++
        }
 
        // Create new execution over WS context
        execWS := eows.New(strings.Join(cmd, " "), cmdArgs, sop, sess.ID, args.CmdID)
-       execWS.Log = s.log
+       execWS.Log = s.Log
 
        // Append client project dir to environment
        execWS.Env = append(args.Env, "CLIENT_PROJECT_DIR="+prj.ClientPath)
@@ -207,9 +133,9 @@ func (s *APIService) execCmd(c *gin.Context) {
        }
 
        // Define callback for input (stdin)
-       execWS.InputEvent = ExecInEvent
+       execWS.InputEvent = xsapiv1.ExecInEvent
        execWS.InputCB = func(e *eows.ExecOverWS, stdin string) (string, error) {
-               s.log.Debugf("STDIN <<%v>>", strings.Replace(stdin, "\n", "\\n", -1))
+               s.Log.Debugf("STDIN <<%v>>", strings.Replace(stdin, "\n", "\\n", -1))
 
                // Handle Ctrl-D
                if len(stdin) == 1 && stdin == "\x04" {
@@ -223,7 +149,7 @@ func (s *APIService) execCmd(c *gin.Context) {
                prjID := (*data)["ID"].(string)
                f := s.mfolders.Get(prjID)
                if f == nil {
-                       s.log.Errorf("InputCB: Cannot get folder ID %s", prjID)
+                       s.Log.Errorf("InputCB: Cannot get folder ID %s", prjID)
                } else {
                        // Translate paths from client to server
                        stdin = (*f).ConvPathCli2Svr(stdin)
@@ -237,7 +163,7 @@ func (s *APIService) execCmd(c *gin.Context) {
                // IO socket can be nil when disconnected
                so := s.sessions.IOSocketGet(e.Sid)
                if so == nil {
-                       s.log.Infof("%s not emitted: WS closed (sid:%s, msgid:%s)", ExecOutEvent, e.Sid, e.CmdID)
+                       s.Log.Infof("%s not emitted: WS closed (sid:%s, msgid:%s)", xsapiv1.ExecOutEvent, e.Sid, e.CmdID)
                        return
                }
 
@@ -248,30 +174,30 @@ func (s *APIService) execCmd(c *gin.Context) {
 
                f := s.mfolders.Get(prjID)
                if f == nil {
-                       s.log.Errorf("OutputCB: Cannot get folder ID %s", prjID)
+                       s.Log.Errorf("OutputCB: Cannot get folder ID %s", prjID)
                } else {
                        // Translate paths from server to client
                        stdout = (*f).ConvPathSvr2Cli(stdout)
                        stderr = (*f).ConvPathSvr2Cli(stderr)
                }
 
-               s.log.Debugf("%s emitted - WS sid[4:] %s - id:%s - prjID:%s", ExecOutEvent, e.Sid[4:], e.CmdID, prjID)
+               s.Log.Debugf("%s emitted - WS sid[4:] %s - id:%s - prjID:%s", xsapiv1.ExecOutEvent, e.Sid[4:], e.CmdID, prjID)
                if stdout != "" {
-                       s.log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1))
+                       s.Log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1))
                }
                if stderr != "" {
-                       s.log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1))
+                       s.Log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1))
                }
 
                // FIXME replace by .BroadcastTo a room
-               err := (*so).Emit(ExecOutEvent, ExecOutMsg{
+               err := (*so).Emit(xsapiv1.ExecOutEvent, xsapiv1.ExecOutMsg{
                        CmdID:     e.CmdID,
                        Timestamp: time.Now().String(),
                        Stdout:    stdout,
                        Stderr:    stderr,
                })
                if err != nil {
-                       s.log.Errorf("WS Emit : %v", err)
+                       s.Log.Errorf("WS Emit : %v", err)
                }
 
                // XXX - Workaround due to gdbserver bug that doesn't redirect
@@ -289,27 +215,27 @@ func (s *APIService) execCmd(c *gin.Context) {
                                                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{
+                                               s.Log.Debugf("STDOUT INFERIOR: <<%v>>", out)
+                                               err := (*so).Emit(xsapiv1.ExecInferiorOutEvent, xsapiv1.ExecOutMsg{
                                                        CmdID:     e.CmdID,
                                                        Timestamp: time.Now().String(),
                                                        Stdout:    out,
                                                        Stderr:    "",
                                                })
                                                if err != nil {
-                                                       s.log.Errorf("WS Emit : %v", err)
+                                                       s.Log.Errorf("WS Emit : %v", err)
                                                }
                                        }
                                }
                        } else {
-                               s.log.Errorf("INFERIOR out parsing error: stdout=<%v>", stdout)
+                               s.Log.Errorf("INFERIOR out parsing error: stdout=<%v>", stdout)
                        }
                }
        }
 
        // Define callback for output
        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)
+               s.Log.Debugf("Command [Cmd ID %s] exited: code %d, error: %v", e.CmdID, code, err)
 
                // Close client tty
                defer func() {
@@ -324,7 +250,7 @@ func (s *APIService) execCmd(c *gin.Context) {
                // IO socket can be nil when disconnected
                so := s.sessions.IOSocketGet(e.Sid)
                if so == nil {
-                       s.log.Infof("%s not emitted - WS closed (id:%s)", ExecExitEvent, e.CmdID)
+                       s.Log.Infof("%s not emitted - WS closed (id:%s)", xsapiv1.ExecExitEvent, e.CmdID)
                        return
                }
 
@@ -335,34 +261,34 @@ func (s *APIService) execCmd(c *gin.Context) {
 
                // XXX - workaround to be sure that Syncthing detected all changes
                if err := s.mfolders.ForceSync(prjID); err != nil {
-                       s.log.Errorf("Error while syncing folder %s: %v", prjID, err)
+                       s.Log.Errorf("Error while syncing folder %s: %v", prjID, err)
                }
                if !exitImm {
                        // Wait end of file sync
                        // FIXME pass as argument
                        tmo := 60
                        for t := tmo; t > 0; t-- {
-                               s.log.Debugf("Wait file in-sync for %s (%d/%d)", prjID, t, tmo)
+                               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)
+                                               s.Log.Errorf("ERROR IsFolderInSync (%s): %v", prjID, err)
                                        }
                                        break
                                }
                                time.Sleep(time.Second)
                        }
-                       s.log.Debugf("OK file are synchronized.")
+                       s.Log.Debugf("OK file are synchronized.")
                }
 
                // FIXME replace by .BroadcastTo a room
-               errSoEmit := (*so).Emit(ExecExitEvent, ExecExitMsg{
+               errSoEmit := (*so).Emit(xsapiv1.ExecExitEvent, xsapiv1.ExecExitMsg{
                        CmdID:     e.CmdID,
                        Timestamp: time.Now().String(),
                        Code:      code,
                        Error:     err,
                })
                if errSoEmit != nil {
-                       s.log.Errorf("WS Emit : %v", errSoEmit)
+                       s.Log.Errorf("WS Emit : %v", errSoEmit)
                }
        }
 
@@ -378,7 +304,7 @@ func (s *APIService) execCmd(c *gin.Context) {
        execWS.UserData = &data
 
        // Start command execution
-       s.log.Infof("Execute [Cmd ID %s]: %v %v", execWS.CmdID, execWS.Cmd, execWS.Args)
+       s.Log.Infof("Execute [Cmd ID %s]: %v %v", execWS.CmdID, execWS.Cmd, execWS.Args)
 
        err = execWS.Start()
        if err != nil {
@@ -386,19 +312,19 @@ func (s *APIService) execCmd(c *gin.Context) {
                return
        }
 
-       c.JSON(http.StatusOK, ExecRes{Status: "OK", CmdID: execWS.CmdID})
+       c.JSON(http.StatusOK, xsapiv1.ExecResult{Status: "OK", CmdID: execWS.CmdID})
 }
 
 // ExecCmd executes remotely a command
 func (s *APIService) execSignalCmd(c *gin.Context) {
-       var args ExecSignalArgs
+       var args xsapiv1.ExecSignalArgs
 
        if c.BindJSON(&args) != nil {
                common.APIError(c, "Invalid arguments")
                return
        }
 
-       s.log.Debugf("Signal %s for command ID %s", args.Signal, args.CmdID)
+       s.Log.Debugf("Signal %s for command ID %s", args.Signal, args.CmdID)
 
        e := eows.GetEows(args.CmdID)
        if e == nil {
@@ -412,5 +338,5 @@ func (s *APIService) execSignalCmd(c *gin.Context) {
                return
        }
 
-       c.JSON(http.StatusOK, ExecSigRes{Status: "OK", CmdID: args.CmdID})
+       c.JSON(http.StatusOK, xsapiv1.ExecSigResult{Status: "OK", CmdID: args.CmdID})
 }
similarity index 90%
rename from lib/apiv1/folders.go
rename to lib/xdsserver/apiv1-folders.go
index 073445c..fe11e52 100644 (file)
@@ -1,4 +1,4 @@
-package apiv1
+package xdsserver
 
 import (
        "net/http"
@@ -6,7 +6,7 @@ import (
 
        "github.com/gin-gonic/gin"
        common "github.com/iotbzh/xds-common/golib"
-       "github.com/iotbzh/xds-server/lib/folder"
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
 )
 
 // getFolders returns all folders configuration
@@ -32,13 +32,13 @@ func (s *APIService) getFolder(c *gin.Context) {
 
 // addFolder adds a new folder to server config
 func (s *APIService) addFolder(c *gin.Context) {
-       var cfgArg folder.FolderConfig
+       var cfgArg xsapiv1.FolderConfig
        if c.BindJSON(&cfgArg) != nil {
                common.APIError(c, "Invalid arguments")
                return
        }
 
-       s.log.Debugln("Add folder config: ", cfgArg)
+       s.Log.Debugln("Add folder config: ", cfgArg)
 
        newFld, err := s.mfolders.Add(cfgArg)
        if err != nil {
@@ -77,7 +77,7 @@ func (s *APIService) syncFolder(c *gin.Context) {
                common.APIError(c, err.Error())
                return
        }
-       s.log.Debugln("Sync folder id: ", id)
+       s.Log.Debugln("Sync folder id: ", id)
 
        err = s.mfolders.ForceSync(id)
        if err != nil {
@@ -96,7 +96,7 @@ func (s *APIService) delFolder(c *gin.Context) {
                return
        }
 
-       s.log.Debugln("Delete folder id ", id)
+       s.Log.Debugln("Delete folder id ", id)
 
        delEntry, err := s.mfolders.Delete(id)
        if err != nil {
@@ -114,9 +114,9 @@ func (s *APIService) updateFolder(c *gin.Context) {
                return
        }
 
-       s.log.Debugln("Update folder id ", id)
+       s.Log.Debugln("Update folder id ", id)
 
-       var cfgArg folder.FolderConfig
+       var cfgArg xsapiv1.FolderConfig
        if c.BindJSON(&cfgArg) != nil {
                common.APIError(c, "Invalid arguments")
                return
diff --git a/lib/xdsserver/apiv1-make.go b/lib/xdsserver/apiv1-make.go
new file mode 100644 (file)
index 0000000..bcb4d95
--- /dev/null
@@ -0,0 +1,214 @@
+package xdsserver
+
+import (
+       "github.com/gin-gonic/gin"
+       common "github.com/iotbzh/xds-common/golib"
+)
+
+/* TODO: Deprecated - should be removed
+// MakeArgs is the parameters (json format) of /make command
+type MakeArgs struct {
+       ID            string   `json:"id"`
+       SdkID         string   `json:"sdkID"` // sdk ID to use for setting env
+       CmdID         string   `json:"cmdID"` // command unique ID
+       Args          []string `json:"args"`  // args to pass to make command
+       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
+}
+
+// MakeOutMsg Message send on each output (stdout+stderr) of make command
+type MakeOutMsg struct {
+       CmdID     string `json:"cmdID"`
+       Timestamp string `json:"timestamp"`
+       Stdout    string `json:"stdout"`
+       Stderr    string `json:"stderr"`
+}
+
+// MakeExitMsg Message send on make command exit
+type MakeExitMsg struct {
+       CmdID     string `json:"cmdID"`
+       Timestamp string `json:"timestamp"`
+       Code      int    `json:"code"`
+       Error     error  `json:"error"`
+}
+
+// MakeOutEvent Event send in WS when characters are received on stdout/stderr
+const MakeOutEvent = "make:output"
+
+// MakeExitEvent Event send in WS when command exited
+const MakeExitEvent = "make:exit"
+
+var makeCommandID = 1
+*/
+
+func (s *APIService) buildMake(c *gin.Context) {
+       common.APIError(c, "/make route is not longer supported, use /exec instead")
+
+       /*
+               var args MakeArgs
+
+               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
+               }
+               sop := sess.IOSocket
+               if sop == nil {
+                       common.APIError(c, "Websocket not established")
+                       return
+               }
+
+               // Allow to pass id in url (/make/:id) or as JSON argument
+               idArg := c.Param("id")
+               if idArg == "" {
+                       idArg = args.ID
+               }
+               if idArg == "" {
+                       common.APIError(c, "Invalid id")
+                       return
+               }
+               id, err := s.mfolders.ResolveID(idArg)
+               if err != nil {
+                       common.APIError(c, err.Error())
+                       return
+               }
+               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 {
+                       // TODO get default timeout from config.json file
+                       execTmo = 24 * 60 * 60 // 1 day
+               }
+
+               // TODO merge all code below with exec.go
+
+               // Define callback for output
+               var oCB common.EmitOutputCB
+               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:%s", MakeOutEvent, sid, cmdID)
+                               return
+                       }
+
+                       // Retrieve project ID and RootPath
+                       prjID := (*data)["ID"].(string)
+                       prjRootPath := (*data)["RootPath"].(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", MakeOutEvent, sid, id, prjID)
+
+                       // FIXME replace by .BroadcastTo a room
+                       err := (*so).Emit(MakeOutEvent, MakeOutMsg{
+                               CmdID:     cmdID,
+                               Timestamp: time.Now().String(),
+                               Stdout:    stdout,
+                               Stderr:    stderr,
+                       })
+                       if err != nil {
+                               s.log.Errorf("WS Emit : %v", err)
+                       }
+               }
+
+               // Define callback for output
+               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:%s", MakeExitEvent, cmdID)
+                               return
+                       }
+
+                       // Retrieve project ID and RootPath
+                       prjID := (*data)["ID"].(string)
+                       exitImm := (*data)["ExitImmediate"].(bool)
+
+                       // XXX - workaround to be sure that Syncthing detected all changes
+                       if err := s.mfolders.ForceSync(prjID); err != nil {
+                               s.log.Errorf("Error while syncing folder %s: %v", prjID, err)
+                       }
+                       if !exitImm {
+                               // Wait end of file sync
+                               // 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.mfolders.IsFolderInSync(prjID); sync || err != nil {
+                                               if err != nil {
+                                                       s.log.Errorf("ERROR IsFolderInSync (%s): %v", prjID, err)
+                                               }
+                                               break
+                                       }
+                                       time.Sleep(time.Second)
+                               }
+                       }
+
+                       // FIXME replace by .BroadcastTo a room
+                       e := (*so).Emit(MakeExitEvent, MakeExitMsg{
+                               CmdID:     id,
+                               Timestamp: time.Now().String(),
+                               Code:      code,
+                               Error:     err,
+                       })
+                       if e != nil {
+                               s.log.Errorf("WS Emit : %v", e)
+                       }
+               }
+
+               // Unique ID for each commands
+               if args.CmdID == "" {
+                       args.CmdID = s.cfg.ServerUID[:18] + "_" + strconv.Itoa(makeCommandID)
+                       makeCommandID++
+               }
+               cmd := []string{}
+
+               // Retrieve env command regarding Sdk ID
+               if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); len(envCmd) > 0 {
+                       cmd = append(cmd, envCmd...)
+                       cmd = append(cmd, "&&")
+               }
+
+               cmd = append(cmd, "cd", folder.GetFullPath(args.RPath), "&&", "make")
+               if len(args.Args) > 0 {
+                       cmd = append(cmd, args.Args...)
+               }
+
+               s.log.Debugf("Execute [Cmd ID %d]: %v", args.CmdID, cmd)
+
+               data := make(map[string]interface{})
+               data["ID"] = prj.ID
+               data["RootPath"] = prj.RootPath
+               data["ExitImmediate"] = args.ExitImmediate
+
+               err = common.ExecPipeWs(cmd, args.Env, sop, sess.ID, args.CmdID, execTmo, s.log, oCB, eCB, &data)
+               if err != nil {
+                       common.APIError(c, err.Error())
+                       return
+               }
+
+               c.JSON(http.StatusOK,
+                       gin.H{
+                               "status": "OK",
+                               "cmdID":  args.CmdID,
+                       })
+       */
+}
similarity index 96%
rename from lib/apiv1/sdks.go
rename to lib/xdsserver/apiv1-sdks.go
index f67a0ef..be9fcf7 100644 (file)
@@ -1,4 +1,4 @@
-package apiv1
+package xdsserver
 
 import (
        "net/http"
diff --git a/lib/xdsserver/apiv1-version.go b/lib/xdsserver/apiv1-version.go
new file mode 100644 (file)
index 0000000..2c2547c
--- /dev/null
@@ -0,0 +1,20 @@
+package xdsserver
+
+import (
+       "net/http"
+
+       "github.com/gin-gonic/gin"
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
+)
+
+// getInfo : return various information about server
+func (s *APIService) getVersion(c *gin.Context) {
+       response := xsapiv1.Version{
+               ID:            s.Config.ServerUID,
+               Version:       s.Config.Version,
+               APIVersion:    s.Config.APIVersion,
+               VersionGitTag: s.Config.VersionGitTag,
+       }
+
+       c.JSON(http.StatusOK, response)
+}
similarity index 58%
rename from lib/apiv1/apiv1.go
rename to lib/xdsserver/apiv1.go
index fffed2d..1f6df9e 100644 (file)
@@ -1,36 +1,20 @@
-package apiv1
+package xdsserver
 
 import (
-       "github.com/Sirupsen/logrus"
        "github.com/gin-gonic/gin"
-
-       "github.com/iotbzh/xds-server/lib/crosssdk"
-       "github.com/iotbzh/xds-server/lib/model"
-       "github.com/iotbzh/xds-server/lib/session"
-       "github.com/iotbzh/xds-server/lib/xdsconfig"
 )
 
 // APIService .
 type APIService struct {
-       router    *gin.Engine
+       *Context
        apiRouter *gin.RouterGroup
-       sessions  *session.Sessions
-       cfg       *xdsconfig.Config
-       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, mfolders *model.Folders, sdks *crosssdk.SDKs) *APIService {
+// NewAPIV1 creates a new instance of API service
+func NewAPIV1(ctx *Context) *APIService {
        s := &APIService{
-               router:    r,
-               sessions:  sess,
-               apiRouter: r.Group("/api/v1"),
-               cfg:       cfg,
-               mfolders:  mfolders,
-               sdks:      sdks,
-               log:       cfg.Log,
+               Context:   ctx,
+               apiRouter: ctx.WWWServer.router.Group("/api/v1"),
        }
 
        s.apiRouter.GET("/version", s.getVersion)
diff --git a/lib/xdsserver/folder-interface.go b/lib/xdsserver/folder-interface.go
new file mode 100644 (file)
index 0000000..c2b2ada
--- /dev/null
@@ -0,0 +1,22 @@
+package xdsserver
+
+import "github.com/iotbzh/xds-server/lib/xsapiv1"
+
+type FolderEventCBData map[string]interface{}
+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
+}
similarity index 84%
rename from lib/folder/folder-pathmap.go
rename to lib/xdsserver/folder-pathmap.go
index c5691a3..c5318de 100644 (file)
@@ -1,4 +1,4 @@
-package folder
+package xdsserver
 
 import (
        "fmt"
@@ -8,7 +8,7 @@ import (
        "strings"
 
        common "github.com/iotbzh/xds-common/golib"
-       "github.com/iotbzh/xds-server/lib/xdsconfig"
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
        uuid "github.com/satori/go.uuid"
 )
 
@@ -16,16 +16,16 @@ import (
 
 // PathMap .
 type PathMap struct {
-       globalConfig *xdsconfig.Config
-       config       FolderConfig
+       *Context
+       config xsapiv1.FolderConfig
 }
 
 // NewFolderPathMap Create a new instance of PathMap
-func NewFolderPathMap(gc *xdsconfig.Config) *PathMap {
+func NewFolderPathMap(ctx *Context) *PathMap {
        f := PathMap{
-               globalConfig: gc,
-               config: FolderConfig{
-                       Status: StatusDisable,
+               Context: ctx,
+               config: xsapiv1.FolderConfig{
+                       Status: xsapiv1.StatusDisable,
                },
        }
        return &f
@@ -41,7 +41,7 @@ func (f *PathMap) NewUID(suffix string) string {
 }
 
 // Add a new folder
-func (f *PathMap) Add(cfg FolderConfig) (*FolderConfig, error) {
+func (f *PathMap) Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
        if cfg.DataPathMap.ServerPath == "" {
                return nil, fmt.Errorf("ServerPath must be set")
        }
@@ -49,7 +49,7 @@ func (f *PathMap) Add(cfg FolderConfig) (*FolderConfig, error) {
        // Use shareRootDir if ServerPath is a relative path
        dir := cfg.DataPathMap.ServerPath
        if !filepath.IsAbs(dir) {
-               dir = filepath.Join(f.globalConfig.FileConf.ShareRootDir, dir)
+               dir = filepath.Join(f.Config.FileConf.ShareRootDir, dir)
        }
 
        // Sanity check
@@ -92,20 +92,20 @@ func (f *PathMap) Add(cfg FolderConfig) (*FolderConfig, error) {
                        }
 
                        // Write a specific message that will be check back on agent side
-                       msg := "Pathmap checked message written by xds-server ID: " + f.globalConfig.ServerUID + "\n"
+                       msg := "Pathmap checked message written by xds-server ID: " + f.Config.ServerUID + "\n"
                        if n, err := fd.WriteString(msg); n != len(msg) || err != nil {
                                return nil, fmt.Errorf(errMsg, 5, err)
                        }
                }
        }
 
-       f.config.Status = StatusEnable
+       f.config.Status = xsapiv1.StatusEnable
 
        return &f.config, nil
 }
 
 // GetConfig Get public part of folder config
-func (f *PathMap) GetConfig() FolderConfig {
+func (f *PathMap) GetConfig() xsapiv1.FolderConfig {
        return f.config
 }
 
@@ -146,7 +146,7 @@ func (f *PathMap) Remove() error {
 }
 
 // Update update some fields of a folder
-func (f *PathMap) Update(cfg FolderConfig) (*FolderConfig, error) {
+func (f *PathMap) Update(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
        if f.config.ID != cfg.ID {
                return nil, fmt.Errorf("Invalid id")
        }
@@ -155,7 +155,7 @@ func (f *PathMap) Update(cfg FolderConfig) (*FolderConfig, error) {
 }
 
 // RegisterEventChange requests registration for folder change event
-func (f *PathMap) RegisterEventChange(cb *EventCB, data *EventCBData) error {
+func (f *PathMap) RegisterEventChange(cb *FolderEventCB, data *FolderEventCBData) error {
        return nil
 }
 
similarity index 75%
rename from lib/folder/folder-st-disable.go
rename to lib/xdsserver/folder-st-disable.go
index e936494..64b1efc 100644 (file)
@@ -1,7 +1,7 @@
-package folder
+package xdsserver
 
 import (
-       "github.com/iotbzh/xds-server/lib/xdsconfig"
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
        uuid "github.com/satori/go.uuid"
 )
 
@@ -11,14 +11,14 @@ import (
 
 // STFolderDisable .
 type STFolderDisable struct {
-       globalConfig *xdsconfig.Config
-       config       FolderConfig
+       *Context
+       config xsapiv1.FolderConfig
 }
 
 // NewFolderSTDisable Create a new instance of STFolderDisable
-func NewFolderSTDisable(gc *xdsconfig.Config) *STFolderDisable {
+func NewFolderSTDisable(ctx *Context) *STFolderDisable {
        f := STFolderDisable{
-               globalConfig: gc,
+               Context: ctx,
        }
        return &f
 }
@@ -33,15 +33,15 @@ func (f *STFolderDisable) NewUID(suffix string) string {
 }
 
 // Add a new folder
-func (f *STFolderDisable) Add(cfg FolderConfig) (*FolderConfig, error) {
+func (f *STFolderDisable) Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
        f.config = cfg
-       f.config.Status = StatusDisable
+       f.config.Status = xsapiv1.StatusDisable
        f.config.IsInSync = false
        return &f.config, nil
 }
 
 // GetConfig Get public part of folder config
-func (f *STFolderDisable) GetConfig() FolderConfig {
+func (f *STFolderDisable) GetConfig() xsapiv1.FolderConfig {
        return f.config
 }
 
@@ -66,12 +66,12 @@ func (f *STFolderDisable) Remove() error {
 }
 
 // Update update some fields of a folder
-func (f *STFolderDisable) Update(cfg FolderConfig) (*FolderConfig, error) {
+func (f *STFolderDisable) Update(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
        return nil, nil
 }
 
 // RegisterEventChange requests registration for folder change event
-func (f *STFolderDisable) RegisterEventChange(cb *EventCB, data *EventCBData) error {
+func (f *STFolderDisable) RegisterEventChange(cb *FolderEventCB, data *FolderEventCBData) error {
        return nil
 }
 
similarity index 75%
rename from lib/syncthing/folder-st.go
rename to lib/xdsserver/folder-st.go
index 74aa4bb..04bbf76 100644 (file)
@@ -1,4 +1,4 @@
-package st
+package xdsserver
 
 import (
        "fmt"
@@ -6,8 +6,8 @@ import (
        "path/filepath"
        "strings"
 
-       "github.com/iotbzh/xds-server/lib/folder"
-       "github.com/iotbzh/xds-server/lib/xdsconfig"
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
+       st "github.com/iotbzh/xds-server/lib/syncthing"
        uuid "github.com/satori/go.uuid"
        "github.com/syncthing/syncthing/lib/config"
 )
@@ -16,20 +16,20 @@ import (
 
 // STFolder .
 type STFolder struct {
-       globalConfig      *xdsconfig.Config
-       st                *SyncThing
-       fConfig           folder.FolderConfig
+       *Context
+       st                *st.SyncThing
+       fConfig           xsapiv1.FolderConfig
        stfConfig         config.FolderConfiguration
        eventIDs          []int
-       eventChangeCB     *folder.EventCB
-       eventChangeCBData *folder.EventCBData
+       eventChangeCB     *FolderEventCB
+       eventChangeCBData *FolderEventCBData
 }
 
 // NewFolderST Create a new instance of STFolder
-func (s *SyncThing) NewFolderST(gc *xdsconfig.Config) *STFolder {
+func NewFolderST(ctx *Context, sthg *st.SyncThing) *STFolder {
        return &STFolder{
-               globalConfig: gc,
-               st:           s,
+               Context: ctx,
+               st:      sthg,
        }
 }
 
@@ -47,7 +47,7 @@ func (f *STFolder) NewUID(suffix string) string {
 }
 
 // Add a new folder
-func (f *STFolder) Add(cfg folder.FolderConfig) (*folder.FolderConfig, error) {
+func (f *STFolder) Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
 
        // Sanity check
        if cfg.DataCloudSync.SyncThingID == "" {
@@ -56,7 +56,7 @@ func (f *STFolder) Add(cfg folder.FolderConfig) (*folder.FolderConfig, error) {
 
        // rootPath should not be empty
        if cfg.RootPath == "" {
-               cfg.RootPath = f.globalConfig.FileConf.ShareRootDir
+               cfg.RootPath = f.Config.FileConf.ShareRootDir
        }
 
        f.fConfig = cfg
@@ -64,7 +64,7 @@ func (f *STFolder) Add(cfg folder.FolderConfig) (*folder.FolderConfig, error) {
        // Update Syncthing folder
        // (except if status is ErrorConfig)
        // TODO: add cache to avoid multiple requests on startup
-       if f.fConfig.Status != folder.StatusErrorConfig {
+       if f.fConfig.Status != xsapiv1.StatusErrorConfig {
                id, err := f.st.FolderChange(f.fConfig)
                if err != nil {
                        return nil, err
@@ -72,12 +72,12 @@ func (f *STFolder) Add(cfg folder.FolderConfig) (*folder.FolderConfig, error) {
 
                f.stfConfig, err = f.st.FolderConfigGet(id)
                if err != nil {
-                       f.fConfig.Status = folder.StatusErrorConfig
+                       f.fConfig.Status = xsapiv1.StatusErrorConfig
                        return nil, err
                }
 
                // Register to events to update folder status
-               for _, evName := range []string{EventStateChanged, EventFolderPaused} {
+               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
@@ -86,14 +86,14 @@ func (f *STFolder) Add(cfg folder.FolderConfig) (*folder.FolderConfig, error) {
                }
 
                f.fConfig.IsInSync = false // will be updated later by events
-               f.fConfig.Status = folder.StatusEnable
+               f.fConfig.Status = xsapiv1.StatusEnable
        }
 
        return &f.fConfig, nil
 }
 
 // GetConfig Get public part of folder config
-func (f *STFolder) GetConfig() folder.FolderConfig {
+func (f *STFolder) GetConfig() xsapiv1.FolderConfig {
        return f.fConfig
 }
 
@@ -144,7 +144,7 @@ func (f *STFolder) Remove() error {
 }
 
 // Update update some fields of a folder
-func (f *STFolder) Update(cfg folder.FolderConfig) (*folder.FolderConfig, error) {
+func (f *STFolder) Update(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
        if f.fConfig.ID != cfg.ID {
                return nil, fmt.Errorf("Invalid id")
        }
@@ -153,7 +153,7 @@ func (f *STFolder) Update(cfg folder.FolderConfig) (*folder.FolderConfig, error)
 }
 
 // RegisterEventChange requests registration for folder event change
-func (f *STFolder) RegisterEventChange(cb *folder.EventCB, data *folder.EventCBData) error {
+func (f *STFolder) RegisterEventChange(cb *FolderEventCB, data *FolderEventCBData) error {
        f.eventChangeCB = cb
        f.eventChangeCBData = data
        return nil
@@ -182,25 +182,25 @@ func (f *STFolder) IsInSync() (bool, error) {
 }
 
 // callback use to update IsInSync status
-func (f *STFolder) cbEventState(ev Event, data *EventsCBData) {
+func (f *STFolder) cbEventState(ev st.Event, data *st.EventsCBData) {
        prevSync := f.fConfig.IsInSync
        prevStatus := f.fConfig.Status
 
        switch ev.Type {
 
-       case EventStateChanged:
+       case st.EventStateChanged:
                to := ev.Data["to"]
                switch to {
                case "scanning", "syncing":
-                       f.fConfig.Status = folder.StatusSyncing
+                       f.fConfig.Status = xsapiv1.StatusSyncing
                case "idle":
-                       f.fConfig.Status = folder.StatusEnable
+                       f.fConfig.Status = xsapiv1.StatusEnable
                }
                f.fConfig.IsInSync = (to == "idle")
 
-       case EventFolderPaused:
-               if f.fConfig.Status == folder.StatusEnable {
-                       f.fConfig.Status = folder.StatusPause
+       case st.EventFolderPaused:
+               if f.fConfig.Status == xsapiv1.StatusEnable {
+                       f.fConfig.Status = xsapiv1.StatusPause
                }
                f.fConfig.IsInSync = false
        }
similarity index 79%
rename from lib/model/folders.go
rename to lib/xdsserver/folders.go
index 0e28538..e36f84c 100644 (file)
@@ -1,4 +1,4 @@
-package model
+package xdsserver
 
 import (
        "encoding/xml"
@@ -9,28 +9,25 @@ import (
        "strings"
        "time"
 
-       "github.com/Sirupsen/logrus"
        "github.com/franciscocpg/reflectme"
        common "github.com/iotbzh/xds-common/golib"
-       "github.com/iotbzh/xds-server/lib/folder"
-       "github.com/iotbzh/xds-server/lib/syncthing"
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
        "github.com/iotbzh/xds-server/lib/xdsconfig"
        "github.com/syncthing/syncthing/lib/sync"
 )
 
 // Folders Represent a an XDS folders
 type Folders struct {
+       *Context
        fileOnDisk string
-       Conf       *xdsconfig.Config
-       Log        *logrus.Logger
-       SThg       *st.SyncThing
-       folders    map[string]*folder.IFOLDER
+       folders    map[string]*IFOLDER
        registerCB []RegisteredCB
 }
 
+// RegisteredCB Hold registered callbacks
 type RegisteredCB struct {
-       cb   *folder.EventCB
-       data *folder.EventCBData
+       cb   *FolderEventCB
+       data *FolderEventCBData
 }
 
 // Mutex to make add/delete atomic
@@ -38,25 +35,23 @@ var fcMutex = sync.NewMutex()
 var ffMutex = sync.NewMutex()
 
 // FoldersNew Create a new instance of Model Folders
-func FoldersNew(cfg *xdsconfig.Config, st *st.SyncThing) *Folders {
+func FoldersNew(ctx *Context) *Folders {
        file, _ := xdsconfig.FoldersConfigFilenameGet()
        return &Folders{
+               Context:    ctx,
                fileOnDisk: file,
-               Conf:       cfg,
-               Log:        cfg.Log,
-               SThg:       st,
-               folders:    make(map[string]*folder.IFOLDER),
+               folders:    make(map[string]*IFOLDER),
                registerCB: []RegisteredCB{},
        }
 }
 
 // LoadConfig Load folders configuration from disk
 func (f *Folders) LoadConfig() error {
-       var flds []folder.FolderConfig
-       var stFlds []folder.FolderConfig
+       var flds []xsapiv1.FolderConfig
+       var stFlds []xsapiv1.FolderConfig
 
        // load from disk
-       if f.Conf.Options.NoFolderConfig {
+       if f.Config.Options.NoFolderConfig {
                f.Log.Infof("Don't read folder config file (-no-folderconfig option is set)")
        } else if f.fileOnDisk != "" {
                f.Log.Infof("Use folder config file: %s", f.fileOnDisk)
@@ -90,8 +85,8 @@ func (f *Folders) LoadConfig() error {
                        if xf.ID == stf.ID {
                                found = true
                                // sanity check
-                               if xf.Type != folder.TypeCloudSync {
-                                       flds[i].Status = folder.StatusErrorConfig
+                               if xf.Type != xsapiv1.TypeCloudSync {
+                                       flds[i].Status = xsapiv1.StatusErrorConfig
                                }
                                break
                        }
@@ -107,7 +102,7 @@ func (f *Folders) LoadConfig() error {
        if f.SThg != nil {
                for i, xf := range flds {
                        // only for syncthing project
-                       if xf.Type != folder.TypeCloudSync {
+                       if xf.Type != xsapiv1.TypeCloudSync {
                                continue
                        }
                        found := false
@@ -118,7 +113,7 @@ func (f *Folders) LoadConfig() error {
                                }
                        }
                        if !found {
-                               flds[i].Status = folder.StatusErrorConfig
+                               flds[i].Status = xsapiv1.StatusErrorConfig
                        }
                }
        }
@@ -169,7 +164,7 @@ func (f *Folders) ResolveID(id string) (string, error) {
 }
 
 // Get returns the folder config or nil if not existing
-func (f *Folders) Get(id string) *folder.IFOLDER {
+func (f *Folders) Get(id string) *IFOLDER {
        if id == "" {
                return nil
        }
@@ -181,7 +176,7 @@ func (f *Folders) Get(id string) *folder.IFOLDER {
 }
 
 // GetConfigArr returns the config of all folders as an array
-func (f *Folders) GetConfigArr() []folder.FolderConfig {
+func (f *Folders) GetConfigArr() []xsapiv1.FolderConfig {
        fcMutex.Lock()
        defer fcMutex.Unlock()
 
@@ -189,8 +184,8 @@ func (f *Folders) GetConfigArr() []folder.FolderConfig {
 }
 
 // getConfigArrUnsafe Same as GetConfigArr without mutex protection
-func (f *Folders) getConfigArrUnsafe() []folder.FolderConfig {
-       conf := []folder.FolderConfig{}
+func (f *Folders) getConfigArrUnsafe() []xsapiv1.FolderConfig {
+       conf := []xsapiv1.FolderConfig{}
        for _, v := range f.folders {
                conf = append(conf, (*v).GetConfig())
        }
@@ -198,12 +193,12 @@ func (f *Folders) getConfigArrUnsafe() []folder.FolderConfig {
 }
 
 // Add adds a new folder
-func (f *Folders) Add(newF folder.FolderConfig) (*folder.FolderConfig, error) {
+func (f *Folders) Add(newF xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
        return f.createUpdate(newF, true, false)
 }
 
 // CreateUpdate creates or update a folder
-func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bool) (*folder.FolderConfig, error) {
+func (f *Folders) createUpdate(newF xsapiv1.FolderConfig, create bool, initial bool) (*xsapiv1.FolderConfig, error) {
 
        fcMutex.Lock()
        defer fcMutex.Unlock()
@@ -217,20 +212,20 @@ func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bo
        }
 
        // Create a new folder object
-       var fld folder.IFOLDER
+       var fld IFOLDER
        switch newF.Type {
        // SYNCTHING
-       case folder.TypeCloudSync:
+       case xsapiv1.TypeCloudSync:
                if f.SThg != nil {
-                       fld = f.SThg.NewFolderST(f.Conf)
+                       fld = NewFolderST(f.Context, f.SThg)
                } else {
                        f.Log.Debugf("Disable project %v (syncthing not initialized)", newF.ID)
-                       fld = folder.NewFolderSTDisable(f.Conf)
+                       fld = NewFolderSTDisable(f.Context)
                }
 
        // PATH MAP
-       case folder.TypePathMap:
-               fld = folder.NewFolderPathMap(f.Conf)
+       case xsapiv1.TypePathMap:
+               fld = NewFolderPathMap(f.Context)
        default:
                return nil, fmt.Errorf("Unsupported folder type")
        }
@@ -245,7 +240,7 @@ func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bo
 
        // Set default value if needed
        if newF.Status == "" {
-               newF.Status = folder.StatusDisable
+               newF.Status = xsapiv1.StatusDisable
        }
        if newF.Label == "" {
                newF.Label = filepath.Base(newF.ClientPath)
@@ -260,7 +255,7 @@ func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bo
        // Add new folder
        newFolder, err := fld.Add(newF)
        if err != nil {
-               newF.Status = folder.StatusErrorConfig
+               newF.Status = xsapiv1.StatusErrorConfig
                log.Printf("ERROR Adding folder: %v\n", err)
                return newFolder, err
        }
@@ -293,13 +288,13 @@ func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bo
 }
 
 // Delete deletes a specific folder
-func (f *Folders) Delete(id string) (folder.FolderConfig, error) {
+func (f *Folders) Delete(id string) (xsapiv1.FolderConfig, error) {
        var err error
 
        fcMutex.Lock()
        defer fcMutex.Unlock()
 
-       fld := folder.FolderConfig{}
+       fld := xsapiv1.FolderConfig{}
        fc, exist := f.folders[id]
        if !exist {
                return fld, fmt.Errorf("unknown id")
@@ -320,7 +315,7 @@ func (f *Folders) Delete(id string) (folder.FolderConfig, error) {
 }
 
 // Update Update a specific folder
-func (f *Folders) Update(id string, cfg folder.FolderConfig) (*folder.FolderConfig, error) {
+func (f *Folders) Update(id string, cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
        fcMutex.Lock()
        defer fcMutex.Unlock()
 
@@ -330,12 +325,12 @@ func (f *Folders) Update(id string, cfg folder.FolderConfig) (*folder.FolderConf
        }
 
        // Copy current in a new object to change nothing in case of an error rises
-       newCfg := folder.FolderConfig{}
+       newCfg := xsapiv1.FolderConfig{}
        reflectme.Copy((*fc).GetConfig(), &newCfg)
 
        // Only update some fields
        dirty := false
-       for _, fieldName := range folder.FolderConfigUpdatableFields {
+       for _, fieldName := range xsapiv1.FolderConfigUpdatableFields {
                valNew, err := reflectme.GetField(cfg, fieldName)
                if err == nil {
                        valCur, err := reflectme.GetField(newCfg, fieldName)
@@ -368,9 +363,9 @@ func (f *Folders) Update(id string, cfg folder.FolderConfig) (*folder.FolderConf
 }
 
 // RegisterEventChange requests registration for folder event change
-func (f *Folders) RegisterEventChange(id string, cb *folder.EventCB, data *folder.EventCBData) error {
+func (f *Folders) RegisterEventChange(id string, cb *FolderEventCB, data *FolderEventCBData) error {
 
-       flds := make(map[string]*folder.IFOLDER)
+       flds := make(map[string]*IFOLDER)
        if id != "" {
                // Register to a specific folder
                flds[id] = f.Get(id)
@@ -413,13 +408,13 @@ func (f *Folders) IsFolderInSync(id string) (bool, error) {
 // Use XML format and not json to be able to save/load all fields including
 // ones that are masked in json (IOW defined with `json:"-"`)
 type xmlFolders struct {
-       XMLName xml.Name              `xml:"folders"`
-       Version string                `xml:"version,attr"`
-       Folders []folder.FolderConfig `xml:"folders"`
+       XMLName xml.Name             `xml:"folders"`
+       Version string               `xml:"version,attr"`
+       Folders []xsapiv1.FolderConfig `xml:"folders"`
 }
 
 // foldersConfigRead reads folders config from disk
-func foldersConfigRead(file string, folders *[]folder.FolderConfig) error {
+func foldersConfigRead(file string, folders *[]xsapiv1.FolderConfig) error {
        if !common.Exists(file) {
                return fmt.Errorf("No folder config file found (%s)", file)
        }
@@ -442,7 +437,7 @@ func foldersConfigRead(file string, folders *[]folder.FolderConfig) error {
 }
 
 // foldersConfigWrite writes folders config on disk
-func foldersConfigWrite(file string, folders []folder.FolderConfig) error {
+func foldersConfigWrite(file string, folders []xsapiv1.FolderConfig) error {
        ffMutex.Lock()
        defer ffMutex.Unlock()
 
diff --git a/lib/xdsserver/sdk.go b/lib/xdsserver/sdk.go
new file mode 100644 (file)
index 0000000..c0acb24
--- /dev/null
@@ -0,0 +1,56 @@
+package xdsserver
+
+import (
+       "fmt"
+       "path/filepath"
+
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
+       uuid "github.com/satori/go.uuid"
+)
+
+// CrossSDK Hold SDK config
+type CrossSDK struct {
+       sdk xsapiv1.SDK
+}
+
+// NewCrossSDK creates a new instance of Syncthing
+func NewCrossSDK(path string) (*CrossSDK, error) {
+       // Assume that we have .../<profile>/<version>/<arch>
+       s := CrossSDK{
+               sdk: xsapiv1.SDK{Path: path},
+       }
+
+       s.sdk.Arch = filepath.Base(path)
+
+       d := filepath.Dir(path)
+       s.sdk.Version = filepath.Base(d)
+
+       d = filepath.Dir(d)
+       s.sdk.Profile = filepath.Base(d)
+
+       // Use V3 to ensure that we get same uuid on restart
+       s.sdk.ID = uuid.NewV3(uuid.FromStringOrNil("sdks"), s.sdk.Profile+"_"+s.sdk.Arch+"_"+s.sdk.Version).String()
+       s.sdk.Name = s.sdk.Arch + "  (" + s.sdk.Version + ")"
+
+       envFile := filepath.Join(path, "environment-setup*")
+       ef, err := filepath.Glob(envFile)
+       if err != nil {
+               return nil, fmt.Errorf("Cannot retrieve environment setup file: %v", err)
+       }
+       if len(ef) != 1 {
+               return nil, fmt.Errorf("No environment setup file found match %s", envFile)
+       }
+       s.sdk.EnvFile = ef[0]
+
+       return &s, nil
+}
+
+// Get Return SDK definition
+func (s *CrossSDK) Get() *xsapiv1.SDK {
+       return &s.sdk
+}
+
+// GetEnvCmd returns the command used to initialized the environment
+func (s *CrossSDK) GetEnvCmd() []string {
+       return []string{"source", s.sdk.EnvFile}
+}
similarity index 72%
rename from lib/crosssdk/sdks.go
rename to lib/xdsserver/sdks.go
index a3da184..1a40ab5 100644 (file)
@@ -1,4 +1,4 @@
-package crosssdk
+package xdsserver
 
 import (
        "fmt"
@@ -7,33 +7,34 @@ import (
        "strings"
        "sync"
 
-       "github.com/Sirupsen/logrus"
        common "github.com/iotbzh/xds-common/golib"
-       "github.com/iotbzh/xds-server/lib/xdsconfig"
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
 )
 
 // SDKs List of installed SDK
 type SDKs struct {
-       Sdks map[string]*SDK
+       *Context
+       Sdks map[string]*CrossSDK
 
        mutex sync.Mutex
 }
 
-// Init creates a new instance of Syncthing
-func Init(cfg *xdsconfig.Config, log *logrus.Logger) (*SDKs, error) {
+// NewSDKs creates a new instance of SDKs
+func NewSDKs(ctx *Context) (*SDKs, error) {
        s := SDKs{
-               Sdks: make(map[string]*SDK),
+               Context: ctx,
+               Sdks:    make(map[string]*CrossSDK),
        }
 
        // Retrieve installed sdks
-       sdkRD := cfg.FileConf.SdkRootDir
+       sdkRD := ctx.Config.FileConf.SdkRootDir
 
        if common.Exists(sdkRD) {
 
                // Assume that SDK install tree is <rootdir>/<profile>/<version>/<arch>
                dirs, err := filepath.Glob(path.Join(sdkRD, "*", "*", "*"))
                if err != nil {
-                       log.Debugf("Error while retrieving SDKs: dir=%s, error=%s", sdkRD, err.Error())
+                       ctx.Log.Debugf("Error while retrieving SDKs: dir=%s, error=%s", sdkRD, err.Error())
                        return &s, err
                }
                s.mutex.Lock()
@@ -43,16 +44,16 @@ func Init(cfg *xdsconfig.Config, log *logrus.Logger) (*SDKs, error) {
                        if !common.IsDir(d) {
                                continue
                        }
-                       sdk, err := NewCrossSDK(d)
+                       cSdk, err := NewCrossSDK(d)
                        if err != nil {
-                               log.Debugf("Error while processing SDK dir=%s, err=%s", d, err.Error())
+                               ctx.Log.Debugf("Error while processing SDK dir=%s, err=%s", d, err.Error())
                                continue
                        }
-                       s.Sdks[sdk.ID] = sdk
+                       s.Sdks[cSdk.sdk.ID] = cSdk
                }
        }
 
-       log.Debugf("SDKs: %d cross sdks found", len(s.Sdks))
+       ctx.Log.Debugf("SDKs: %d cross sdks found", len(s.Sdks))
 
        return &s, nil
 }
@@ -79,7 +80,7 @@ func (s *SDKs) ResolveID(id string) (string, error) {
 }
 
 // Get returns an SDK from id
-func (s *SDKs) Get(id string) *SDK {
+func (s *SDKs) Get(id string) *xsapiv1.SDK {
        s.mutex.Lock()
        defer s.mutex.Unlock()
 
@@ -87,16 +88,16 @@ func (s *SDKs) Get(id string) *SDK {
        if !exist {
                return nil
        }
-       return sc
+       return (*sc).Get()
 }
 
 // GetAll returns all existing SDKs
-func (s *SDKs) GetAll() []SDK {
+func (s *SDKs) GetAll() []xsapiv1.SDK {
        s.mutex.Lock()
        defer s.mutex.Unlock()
-       res := []SDK{}
+       res := []xsapiv1.SDK{}
        for _, v := range s.Sdks {
-               res = append(res, *v)
+               res = append(res, *(*v).Get())
        }
        return res
 }
similarity index 92%
rename from lib/session/session.go
rename to lib/xdsserver/sessions.go
index 60b7b8a..6da9fd8 100644 (file)
@@ -1,4 +1,4 @@
-package session
+package xdsserver
 
 import (
        "encoding/base64"
@@ -36,7 +36,7 @@ type ClientSession struct {
 
 // Sessions holds client sessions
 type Sessions struct {
-       router        *gin.Engine
+       *Context
        cookieMaxAge  int64
        sessMap       map[string]ClientSession
        mutex         sync.Mutex
@@ -46,21 +46,19 @@ type Sessions struct {
 }
 
 // NewClientSessions .
-func NewClientSessions(router *gin.Engine, log *logrus.Logger, cookieMaxAge string, sillyLog bool) *Sessions {
+func NewClientSessions(ctx *Context, cookieMaxAge string) *Sessions {
        ckMaxAge, err := strconv.ParseInt(cookieMaxAge, 10, 0)
        if err != nil {
                ckMaxAge = 0
        }
        s := Sessions{
-               router:        router,
-               cookieMaxAge:  ckMaxAge,
-               sessMap:       make(map[string]ClientSession),
-               mutex:         sync.NewMutex(),
-               log:           log,
-               LogLevelSilly: sillyLog,
-               stop:          make(chan struct{}),
+               Context:      ctx,
+               cookieMaxAge: ckMaxAge,
+               sessMap:      make(map[string]ClientSession),
+               mutex:        sync.NewMutex(),
+               stop:         make(chan struct{}),
        }
-       s.router.Use(s.Middleware())
+       s.WWWServer.router.Use(s.Middleware())
 
        // Start monitoring of sessions Map (use to manage expiration and cleanup)
        go s.monitorSessMap()
similarity index 59%
rename from lib/webserver/server.go
rename to lib/xdsserver/webserver.go
index 85a2c40..0e1676a 100644 (file)
@@ -1,4 +1,4 @@
-package webserver
+package xdsserver
 
 import (
        "fmt"
@@ -12,62 +12,45 @@ import (
        "github.com/gin-contrib/static"
        "github.com/gin-gonic/gin"
        "github.com/googollee/go-socket.io"
-       "github.com/iotbzh/xds-server/lib/apiv1"
-       "github.com/iotbzh/xds-server/lib/crosssdk"
-       "github.com/iotbzh/xds-server/lib/model"
-       "github.com/iotbzh/xds-server/lib/session"
-       "github.com/iotbzh/xds-server/lib/xdsconfig"
 )
 
-// Server .
-type Server struct {
+// WebServer .
+type WebServer struct {
+       *Context
        router    *gin.Engine
-       api       *apiv1.APIService
+       api       *APIService
        sIOServer *socketio.Server
        webApp    *gin.RouterGroup
-       cfg       *xdsconfig.Config
-       sessions  *session.Sessions
-       mfolders  *model.Folders
-       sdks      *crosssdk.SDKs
-       log       *logrus.Logger
-       sillyLog  bool
        stop      chan struct{} // signals intentional stop
 }
 
 const indexFilename = "index.html"
-const cookieMaxAge = "3600"
 
-// New creates an instance of Server
-func New(cfg *xdsconfig.Config, mfolders *model.Folders, sdks *crosssdk.SDKs, logr *logrus.Logger, sillyLog bool) *Server {
+// NewWebServer creates an instance of WebServer
+func NewWebServer(ctx *Context) *WebServer {
 
        // Setup logging for gin router
-       if logr.Level == logrus.DebugLevel {
+       if ctx.Log.Level == logrus.DebugLevel {
                gin.SetMode(gin.DebugMode)
        } else {
                gin.SetMode(gin.ReleaseMode)
        }
 
        // Redirect gin logs into another logger (LogVerboseOut may be stderr or a file)
-       gin.DefaultWriter = cfg.LogVerboseOut
-       gin.DefaultErrorWriter = cfg.LogVerboseOut
-       log.SetOutput(cfg.LogVerboseOut)
+       gin.DefaultWriter = ctx.Config.LogVerboseOut
+       gin.DefaultErrorWriter = ctx.Config.LogVerboseOut
+       log.SetOutput(ctx.Config.LogVerboseOut)
 
        // FIXME - fix pb about isTerminal=false when out is in VSC Debug Console
 
        // Creates gin router
        r := gin.New()
 
-       svr := &Server{
+       svr := &WebServer{
                router:    r,
                api:       nil,
                sIOServer: nil,
                webApp:    nil,
-               cfg:       cfg,
-               sessions:  nil,
-               mfolders:  mfolders,
-               sdks:      sdks,
-               log:       logr,
-               sillyLog:  sillyLog,
                stop:      make(chan struct{}),
        }
 
@@ -75,7 +58,7 @@ func New(cfg *xdsconfig.Config, mfolders *model.Folders, sdks *crosssdk.SDKs, lo
 }
 
 // Serve starts a new instance of the Web Server
-func (s *Server) Serve() error {
+func (s *WebServer) Serve() error {
        var err error
 
        // Setup middlewares
@@ -84,16 +67,13 @@ func (s *Server) Serve() error {
        s.router.Use(s.middlewareXDSDetails())
        s.router.Use(s.middlewareCORS())
 
-       // Sessions manager
-       s.sessions = session.NewClientSessions(s.router, s.log, cookieMaxAge, s.sillyLog)
-
        // Create REST API
-       s.api = apiv1.New(s.router, s.sessions, s.cfg, s.mfolders, s.sdks)
+       s.api = NewAPIV1(s.Context)
 
        // Websocket routes
        s.sIOServer, err = socketio.NewServer(nil)
        if err != nil {
-               s.log.Fatalln(err)
+               s.Log.Fatalln(err)
        }
 
        s.router.GET("/socket.io/", s.socketHandler)
@@ -104,12 +84,12 @@ func (s *Server) Serve() error {
        */
 
        // Web Application (serve on / )
-       idxFile := path.Join(s.cfg.FileConf.WebAppDir, indexFilename)
+       idxFile := path.Join(s.Config.FileConf.WebAppDir, indexFilename)
        if _, err := os.Stat(idxFile); err != nil {
-               s.log.Fatalln("Web app directory not found, check/use webAppDir setting in config file: ", idxFile)
+               s.Log.Fatalln("Web app directory not found, check/use webAppDir setting in config file: ", idxFile)
        }
-       s.log.Infof("Serve WEB app dir: %s", s.cfg.FileConf.WebAppDir)
-       s.router.Use(static.Serve("/", static.LocalFile(s.cfg.FileConf.WebAppDir, true)))
+       s.Log.Infof("Serve WEB app dir: %s", s.Config.FileConf.WebAppDir)
+       s.router.Use(static.Serve("/", static.LocalFile(s.Config.FileConf.WebAppDir, true)))
        s.webApp = s.router.Group("/", s.serveIndexFile)
        {
                s.webApp.GET("/")
@@ -118,10 +98,10 @@ func (s *Server) Serve() error {
        // Serve in the background
        serveError := make(chan error, 1)
        go func() {
-               msg := fmt.Sprintf("Web Server running on localhost:%s ...\n", s.cfg.FileConf.HTTPPort)
-               s.log.Infof(msg)
+               msg := fmt.Sprintf("Web Server running on localhost:%s ...\n", s.Config.FileConf.HTTPPort)
+               s.Log.Infof(msg)
                fmt.Printf(msg)
-               serveError <- http.ListenAndServe(":"+s.cfg.FileConf.HTTPPort, s.router)
+               serveError <- http.ListenAndServe(":"+s.Config.FileConf.HTTPPort, s.router)
        }()
 
        // Wait for stop, restart or error signals
@@ -129,36 +109,36 @@ func (s *Server) Serve() error {
        case <-s.stop:
                // Shutting down permanently
                s.sessions.Stop()
-               s.log.Infoln("shutting down (stop)")
+               s.Log.Infoln("shutting down (stop)")
        case err = <-serveError:
                // Error due to listen/serve failure
-               s.log.Errorln(err)
+               s.Log.Errorln(err)
        }
 
        return nil
 }
 
 // Stop web server
-func (s *Server) Stop() {
+func (s *WebServer) Stop() {
        close(s.stop)
 }
 
 // serveIndexFile provides initial file (eg. index.html) of webapp
-func (s *Server) serveIndexFile(c *gin.Context) {
+func (s *WebServer) serveIndexFile(c *gin.Context) {
        c.HTML(200, indexFilename, gin.H{})
 }
 
 // Add details in Header
-func (s *Server) middlewareXDSDetails() gin.HandlerFunc {
+func (s *WebServer) middlewareXDSDetails() gin.HandlerFunc {
        return func(c *gin.Context) {
-               c.Header("XDS-Version", s.cfg.Version)
-               c.Header("XDS-API-Version", s.cfg.APIVersion)
+               c.Header("XDS-Version", s.Config.Version)
+               c.Header("XDS-API-Version", s.Config.APIVersion)
                c.Next()
        }
 }
 
 // CORS middleware
-func (s *Server) middlewareCORS() gin.HandlerFunc {
+func (s *WebServer) middlewareCORS() gin.HandlerFunc {
        return func(c *gin.Context) {
                if c.Request.Method == "OPTIONS" {
                        c.Header("Access-Control-Allow-Origin", "*")
@@ -174,7 +154,7 @@ func (s *Server) middlewareCORS() gin.HandlerFunc {
 }
 
 // socketHandler is the handler for the "main" websocket connection
-func (s *Server) socketHandler(c *gin.Context) {
+func (s *WebServer) socketHandler(c *gin.Context) {
 
        // Retrieve user session
        sess := s.sessions.Get(c)
@@ -184,17 +164,17 @@ func (s *Server) socketHandler(c *gin.Context) {
        }
 
        s.sIOServer.On("connection", func(so socketio.Socket) {
-               s.log.Debugf("WS Connected (SID=%v)", so.Id())
+               s.Log.Debugf("WS Connected (SID=%v)", so.Id())
                s.sessions.UpdateIOSocket(sess.ID, &so)
 
                so.On("disconnection", func() {
-                       s.log.Debugf("WS disconnected (SID=%v)", so.Id())
+                       s.Log.Debugf("WS disconnected (SID=%v)", so.Id())
                        s.sessions.UpdateIOSocket(sess.ID, nil)
                })
        })
 
        s.sIOServer.On("error", func(so socketio.Socket, err error) {
-               s.log.Errorf("WS SID=%v Error : %v", so.Id(), err.Error())
+               s.Log.Errorf("WS SID=%v Error : %v", so.Id(), err.Error())
        })
 
        s.sIOServer.ServeHTTP(c.Writer, c.Request)
diff --git a/lib/xdsserver/xdsserver.go b/lib/xdsserver/xdsserver.go
new file mode 100644 (file)
index 0000000..dcdedc4
--- /dev/null
@@ -0,0 +1,202 @@
+package xdsserver
+
+import (
+       "fmt"
+       "os"
+       "os/exec"
+       "os/signal"
+       "path/filepath"
+       "syscall"
+       "time"
+
+       "github.com/Sirupsen/logrus"
+       "github.com/codegangsta/cli"
+       "github.com/iotbzh/xds-server/lib/xsapiv1"
+
+       "github.com/iotbzh/xds-server/lib/syncthing"
+       "github.com/iotbzh/xds-server/lib/xdsconfig"
+)
+
+const cookieMaxAge = "3600"
+
+// Context holds the XDS server context
+type Context struct {
+       ProgName      string
+       Cli           *cli.Context
+       Config        *xdsconfig.Config
+       Log           *logrus.Logger
+       LogLevelSilly bool
+       SThg          *st.SyncThing
+       SThgCmd       *exec.Cmd
+       SThgInotCmd   *exec.Cmd
+       mfolders      *Folders
+       sdks          *SDKs
+       WWWServer     *WebServer
+       sessions      *Sessions
+       Exit          chan os.Signal
+}
+
+// NewXdsServer Create a new instance of XDS server
+func NewXdsServer(cliCtx *cli.Context) *Context {
+       var err error
+
+       // Set logger level and formatter
+       log := cliCtx.App.Metadata["logger"].(*logrus.Logger)
+
+       logLevel := cliCtx.GlobalString("log")
+       if logLevel == "" {
+               logLevel = "error" // FIXME get from Config DefaultLogLevel
+       }
+       if log.Level, err = logrus.ParseLevel(logLevel); err != nil {
+               fmt.Printf("Invalid log level : \"%v\"\n", logLevel)
+               os.Exit(1)
+       }
+       log.Formatter = &logrus.TextFormatter{}
+
+       sillyVal, sillyLog := os.LookupEnv("XDS_LOG_SILLY")
+
+       // Define default configuration
+       ctx := Context{
+               ProgName:      cliCtx.App.Name,
+               Cli:           cliCtx,
+               Log:           log,
+               LogLevelSilly: (sillyLog && sillyVal == "1"),
+               Exit:          make(chan os.Signal, 1),
+       }
+
+       // register handler on SIGTERM / exit
+       signal.Notify(ctx.Exit, os.Interrupt, syscall.SIGTERM)
+       go handlerSigTerm(&ctx)
+
+       return &ctx
+}
+
+// Run Main function called to run XDS Server
+func (ctx *Context) Run() (int, error) {
+       var err error
+
+       // Logs redirected into a file when logfile option or logsDir config is set
+       ctx.Config.LogVerboseOut = os.Stderr
+       if ctx.Config.FileConf.LogsDir != "" {
+               if ctx.Config.Options.LogFile != "stdout" {
+                       logFile := ctx.Config.Options.LogFile
+
+                       fdL, err := os.OpenFile(logFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
+                       if err != nil {
+                               msgErr := fmt.Sprintf("Cannot create log file %s", logFile)
+                               return int(syscall.EPERM), fmt.Errorf(msgErr)
+                       }
+                       ctx.Log.Out = fdL
+
+                       ctx._logPrint("Logging file: %s\n", logFile)
+               }
+
+               logFileHTTPReq := filepath.Join(ctx.Config.FileConf.LogsDir, "xds-server-verbose.log")
+               fdLH, err := os.OpenFile(logFileHTTPReq, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
+               if err != nil {
+                       msgErr := fmt.Sprintf("Cannot create log file %s", logFileHTTPReq)
+                       return int(syscall.EPERM), fmt.Errorf(msgErr)
+               }
+               ctx.Config.LogVerboseOut = fdLH
+
+               ctx._logPrint("Logging file for HTTP requests:  %s\n", logFileHTTPReq)
+       }
+
+       // Create syncthing instance when section "syncthing" is present in config.json
+       if ctx.Config.FileConf.SThgConf != nil {
+               ctx.SThg = st.NewSyncThing(ctx.Config, ctx.Log)
+       }
+
+       // Start local instance of Syncthing and Syncthing-notify
+       if ctx.SThg != nil {
+               ctx.Log.Infof("Starting Syncthing...")
+               ctx.SThgCmd, err = ctx.SThg.Start()
+               if err != nil {
+                       return -4, err
+               }
+               ctx._logPrint("Syncthing started (PID %d)\n", ctx.SThgCmd.Process.Pid)
+
+               ctx.Log.Infof("Starting Syncthing-inotify...")
+               ctx.SThgInotCmd, err = ctx.SThg.StartInotify()
+               if err != nil {
+                       return -4, err
+               }
+               ctx._logPrint("Syncthing-inotify started (PID %d)\n", ctx.SThgInotCmd.Process.Pid)
+
+               // Establish connection with local Syncthing (retry if connection fail)
+               ctx._logPrint("Establishing connection with Syncthing...\n")
+               time.Sleep(2 * time.Second)
+               maxRetry := 30
+               retry := maxRetry
+               err = nil
+               for retry > 0 {
+                       if err = ctx.SThg.Connect(); err == nil {
+                               break
+                       }
+                       ctx.Log.Warningf("Establishing connection to Syncthing (retry %d/%d)", retry, maxRetry)
+                       time.Sleep(time.Second)
+                       retry--
+               }
+               if err != nil || retry == 0 {
+                       return -4, err
+               }
+
+               // FIXME: do we still need Builder notion ? if no cleanup
+               if ctx.Config.Builder, err = xdsconfig.NewBuilderConfig(ctx.SThg.MyID); err != nil {
+                       return -4, err
+               }
+               ctx.Config.SupportedSharing[xsapiv1.TypeCloudSync] = true
+       }
+
+       // Init model folder
+       ctx.mfolders = FoldersNew(ctx)
+
+       // Load initial folders config from disk
+       if err := ctx.mfolders.LoadConfig(); err != nil {
+               return -5, err
+       }
+
+       // Init cross SDKs
+       ctx.sdks, err = NewSDKs(ctx)
+       if err != nil {
+               return -6, err
+       }
+
+       // Create Web Server
+       ctx.WWWServer = NewWebServer(ctx)
+
+       // Sessions manager
+       ctx.sessions = NewClientSessions(ctx, cookieMaxAge)
+
+       // Run Web Server until exit requested (blocking call)
+       if err = ctx.WWWServer.Serve(); err != nil {
+               ctx.Log.Println(err)
+               return -7, err
+       }
+
+       return -99, fmt.Errorf("Program exited ")
+}
+
+// Helper function to log message on both stdout and logger
+func (ctx *Context) _logPrint(format string, args ...interface{}) {
+       fmt.Printf(format, args...)
+       if ctx.Log.Out != os.Stdout {
+               ctx.Log.Infof(format, args...)
+       }
+}
+
+// Handle exit and properly stop/close all stuff
+func handlerSigTerm(ctx *Context) {
+       <-ctx.Exit
+       if ctx.SThg != nil {
+               ctx.Log.Infof("Stoping Syncthing... (PID %d)", ctx.SThgCmd.Process.Pid)
+               ctx.SThg.Stop()
+               ctx.Log.Infof("Stoping Syncthing-inotify... (PID %d)", ctx.SThgInotCmd.Process.Pid)
+               ctx.SThg.StopInotify()
+       }
+       if ctx.WWWServer != nil {
+               ctx.Log.Infof("Stoping Web server...")
+               ctx.WWWServer.Stop()
+       }
+       os.Exit(0)
+}
diff --git a/lib/xsapiv1/config.go b/lib/xsapiv1/config.go
new file mode 100644 (file)
index 0000000..33bc116
--- /dev/null
@@ -0,0 +1,18 @@
+package xsapiv1
+
+// APIConfig parameters (json format) of /config command
+type APIConfig struct {
+       ServerUID        string          `json:"id"`
+       Version          string          `json:"version"`
+       APIVersion       string          `json:"apiVersion"`
+       VersionGitTag    string          `json:"gitTag"`
+       SupportedSharing map[string]bool `json:"supportedSharing"`
+       Builder          BuilderConfig   `json:"builder"`
+}
+
+// BuilderConfig represents the builder container configuration
+type BuilderConfig struct {
+       IP          string `json:"ip"`
+       Port        string `json:"port"`
+       SyncThingID string `json:"syncThingID"`
+}
diff --git a/lib/xsapiv1/events.go b/lib/xsapiv1/events.go
new file mode 100644 (file)
index 0000000..1304b3a
--- /dev/null
@@ -0,0 +1,31 @@
+package xsapiv1
+
+// EventRegisterArgs Parameters (json format) of /events/register command
+type EventRegisterArgs struct {
+       Name      string `json:"name"`
+       ProjectID string `json:"filterProjectID"`
+}
+
+// EventUnRegisterArgs Parameters of /events/unregister command
+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 FolderConfig `json:"folder"`
+}
+
+// EventEvent Event send in WS when an internal event (eg. Syncthing event is received)
+const (
+       // EventTypePrefix Used as event prefix
+       EventTypePrefix = "event:" // following by event type
+
+       // Supported Events type
+       EVTAll               = EventTypePrefix + "all"
+       EVTFolderChange      = EventTypePrefix + "folder-change"       // type EventMsg with Data type xsapiv1.???
+       EVTFolderStateChange = EventTypePrefix + "folder-state-change" // type EventMsg with Data type xsapiv1.???
+)
diff --git a/lib/xsapiv1/exec.go b/lib/xsapiv1/exec.go
new file mode 100644 (file)
index 0000000..dce9eff
--- /dev/null
@@ -0,0 +1,76 @@
+package xsapiv1
+
+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
+               CmdID           string   `json:"cmdID"` // command unique ID
+               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
+       }
+
+       // ExecResult JSON result of /exec command
+       ExecResult struct {
+               Status string `json:"status"` // status OK
+               CmdID  string `json:"cmdID"`  // command unique ID
+       }
+
+       // ExecSigResult JSON result of /signal command
+       ExecSigResult struct {
+               Status string `json:"status"` // status OK
+               CmdID  string `json:"cmdID"`  // command unique ID
+       }
+
+       // ExecInMsg Message used to received input characters (stdin)
+       ExecInMsg struct {
+               CmdID     string `json:"cmdID"`
+               Timestamp string `json:"timestamp"`
+               Stdin     string `json:"stdin"`
+       }
+
+       // 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
+       }
+)
+
+const (
+       // ExecInEvent Event send in WS when characters are sent (stdin)
+       ExecInEvent = "exec:input"
+
+       // 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"
+)
similarity index 59%
rename from lib/folder/folder-interface.go
rename to lib/xsapiv1/folders.go
index 3208869..9506a1d 100644 (file)
@@ -1,4 +1,4 @@
-package folder
+package xsapiv1
 
 // FolderType definition
 type FolderType string
@@ -18,25 +18,6 @@ const (
        StatusSyncing     = "Syncing"
 )
 
-type EventCBData map[string]interface{}
-type EventCB func(cfg *FolderConfig, data *EventCBData)
-
-// IFOLDER Folder interface
-type IFOLDER interface {
-       NewUID(suffix string) string                              // Get a new folder UUID
-       Add(cfg FolderConfig) (*FolderConfig, error)              // Add a new folder
-       GetConfig() 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 FolderConfig) (*FolderConfig, error)           // Update a new folder
-       RegisterEventChange(cb *EventCB, data *EventCBData) 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
-}
-
 // FolderConfig is the config for one folder
 type FolderConfig struct {
        ID         string     `json:"id"`
diff --git a/lib/xsapiv1/sdks.go b/lib/xsapiv1/sdks.go
new file mode 100644 (file)
index 0000000..5ca4dd5
--- /dev/null
@@ -0,0 +1,14 @@
+package xsapiv1
+
+// SDK Define a cross tool chain used to build application
+type SDK struct {
+       ID      string `json:"id" binding:"required"`
+       Name    string `json:"name"`
+       Profile string `json:"profile"`
+       Version string `json:"version"`
+       Arch    string `json:"arch"`
+       Path    string `json:"path"`
+
+       // Not exported fields
+       EnvFile string `json:"-"`
+}
diff --git a/lib/xsapiv1/version.go b/lib/xsapiv1/version.go
new file mode 100644 (file)
index 0000000..8c3a742
--- /dev/null
@@ -0,0 +1,9 @@
+package xsapiv1
+
+// XDS server Version
+type Version struct {
+       ID            string `json:"id"`
+       Version       string `json:"version"`
+       APIVersion    string `json:"apiVersion"`
+       VersionGitTag string `json:"gitTag"`
+}
diff --git a/main.go b/main.go
index 6089e74..1bb7110 100644 (file)
--- a/main.go
+++ b/main.go
@@ -5,20 +5,11 @@ package main
 import (
        "fmt"
        "os"
-       "os/exec"
-       "os/signal"
-       "path/filepath"
-       "syscall"
-       "time"
 
        "github.com/Sirupsen/logrus"
        "github.com/codegangsta/cli"
-       "github.com/iotbzh/xds-server/lib/crosssdk"
-       "github.com/iotbzh/xds-server/lib/folder"
-       "github.com/iotbzh/xds-server/lib/model"
-       "github.com/iotbzh/xds-server/lib/syncthing"
-       "github.com/iotbzh/xds-server/lib/webserver"
        "github.com/iotbzh/xds-server/lib/xdsconfig"
+       "github.com/iotbzh/xds-server/lib/xdsserver"
 )
 
 const (
@@ -39,192 +30,23 @@ var AppVersion = "?.?.?"
 // Should be set by compilation -ldflags "-X main.AppSubVersion=xxx"
 var AppSubVersion = "unknown-dev"
 
-// Context holds the XDS server context
-type Context struct {
-       ProgName      string
-       Cli           *cli.Context
-       Config        *xdsconfig.Config
-       Log           *logrus.Logger
-       LogLevelSilly bool
-       SThg          *st.SyncThing
-       SThgCmd       *exec.Cmd
-       SThgInotCmd   *exec.Cmd
-       MFolders      *model.Folders
-       SDKs          *crosssdk.SDKs
-       WWWServer     *webserver.Server
-       Exit          chan os.Signal
-}
-
-// NewContext Create a new instance of XDS server
-func NewContext(cliCtx *cli.Context) *Context {
-       var err error
-
-       // Set logger level and formatter
-       log := cliCtx.App.Metadata["logger"].(*logrus.Logger)
-
-       logLevel := cliCtx.GlobalString("log")
-       if logLevel == "" {
-               logLevel = "error" // FIXME get from Config DefaultLogLevel
-       }
-       if log.Level, err = logrus.ParseLevel(logLevel); err != nil {
-               fmt.Printf("Invalid log level : \"%v\"\n", logLevel)
-               os.Exit(1)
-       }
-       log.Formatter = &logrus.TextFormatter{}
-
-       sillyVal, sillyLog := os.LookupEnv("XDS_LOG_SILLY")
-
-       // Define default configuration
-       ctx := Context{
-               ProgName:      cliCtx.App.Name,
-               Cli:           cliCtx,
-               Log:           log,
-               LogLevelSilly: (sillyLog && sillyVal == "1"),
-               Exit:          make(chan os.Signal, 1),
-       }
-
-       // register handler on SIGTERM / exit
-       signal.Notify(ctx.Exit, os.Interrupt, syscall.SIGTERM)
-       go handlerSigTerm(&ctx)
-
-       return &ctx
-}
-
-// Handle exit and properly stop/close all stuff
-func handlerSigTerm(ctx *Context) {
-       <-ctx.Exit
-       if ctx.SThg != nil {
-               ctx.Log.Infof("Stoping Syncthing... (PID %d)", ctx.SThgCmd.Process.Pid)
-               ctx.SThg.Stop()
-               ctx.Log.Infof("Stoping Syncthing-inotify... (PID %d)", ctx.SThgInotCmd.Process.Pid)
-               ctx.SThg.StopInotify()
-       }
-       if ctx.WWWServer != nil {
-               ctx.Log.Infof("Stoping Web server...")
-               ctx.WWWServer.Stop()
-       }
-       os.Exit(0)
-}
-
-// Helper function to log message on both stdout and logger
-func logPrint(ctx *Context, format string, args ...interface{}) {
-       fmt.Printf(format, args...)
-       if ctx.Log.Out != os.Stdout {
-               ctx.Log.Infof(format, args...)
-       }
-}
-
 // XDS Server application main routine
 func xdsApp(cliCtx *cli.Context) error {
        var err error
 
        // Create XDS server context
-       ctx := NewContext(cliCtx)
+       ctxSvr := xdsserver.NewXdsServer(cliCtx)
 
        // Load config
-       cfg, err := xdsconfig.Init(ctx.Cli, ctx.Log)
+       ctxSvr.Config, err = xdsconfig.Init(cliCtx, ctxSvr.Log)
        if err != nil {
                return cli.NewExitError(err, -2)
        }
-       ctx.Config = cfg
 
-       // Logs redirected into a file when logfile option or logsDir config is set
-       ctx.Config.LogVerboseOut = os.Stderr
-       if ctx.Config.FileConf.LogsDir != "" {
-               if ctx.Config.Options.LogFile != "stdout" {
-                       logFile := ctx.Config.Options.LogFile
-
-                       fdL, err := os.OpenFile(logFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
-                       if err != nil {
-                               msgErr := fmt.Sprintf("Cannot create log file %s", logFile)
-                               return cli.NewExitError(msgErr, int(syscall.EPERM))
-                       }
-                       ctx.Log.Out = fdL
-
-                       logPrint(ctx, "Logging file: %s\n", logFile)
-               }
-
-               logFileHTTPReq := filepath.Join(ctx.Config.FileConf.LogsDir, "xds-server-verbose.log")
-               fdLH, err := os.OpenFile(logFileHTTPReq, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
-               if err != nil {
-                       msgErr := fmt.Sprintf("Cannot create log file %s", logFileHTTPReq)
-                       return cli.NewExitError(msgErr, int(syscall.EPERM))
-               }
-               ctx.Config.LogVerboseOut = fdLH
-
-               logPrint(ctx, "Logging file for HTTP requests:  %s\n", logFileHTTPReq)
-       }
-
-       // Create syncthing instance when section "syncthing" is present in config.json
-       if ctx.Config.FileConf.SThgConf != nil {
-               ctx.SThg = st.NewSyncThing(ctx.Config, ctx.Log)
-       }
-
-       // Start local instance of Syncthing and Syncthing-notify
-       if ctx.SThg != nil {
-               ctx.Log.Infof("Starting Syncthing...")
-               ctx.SThgCmd, err = ctx.SThg.Start()
-               if err != nil {
-                       return cli.NewExitError(err, -4)
-               }
-               logPrint(ctx, "Syncthing started (PID %d)\n", ctx.SThgCmd.Process.Pid)
-
-               ctx.Log.Infof("Starting Syncthing-inotify...")
-               ctx.SThgInotCmd, err = ctx.SThg.StartInotify()
-               if err != nil {
-                       return cli.NewExitError(err, -4)
-               }
-               logPrint(ctx, "Syncthing-inotify started (PID %d)\n", ctx.SThgInotCmd.Process.Pid)
-
-               // Establish connection with local Syncthing (retry if connection fail)
-               logPrint(ctx, "Establishing connection with Syncthing...\n")
-               time.Sleep(2 * time.Second)
-               maxRetry := 30
-               retry := maxRetry
-               err = nil
-               for retry > 0 {
-                       if err = ctx.SThg.Connect(); err == nil {
-                               break
-                       }
-                       ctx.Log.Warningf("Establishing connection to Syncthing (retry %d/%d)", retry, maxRetry)
-                       time.Sleep(time.Second)
-                       retry--
-               }
-               if err != nil || retry == 0 {
-                       return cli.NewExitError(err, -4)
-               }
-
-               // FIXME: do we still need Builder notion ? if no cleanup
-               if ctx.Config.Builder, err = xdsconfig.NewBuilderConfig(ctx.SThg.MyID); err != nil {
-                       return cli.NewExitError(err, -4)
-               }
-               ctx.Config.SupportedSharing[folder.TypeCloudSync] = true
-       }
-
-       // Init model folder
-       ctx.MFolders = model.FoldersNew(ctx.Config, ctx.SThg)
-
-       // Load initial folders config from disk
-       if err := ctx.MFolders.LoadConfig(); err != nil {
-               return cli.NewExitError(err, -5)
-       }
-
-       // Init cross SDKs
-       ctx.SDKs, err = crosssdk.Init(ctx.Config, ctx.Log)
-       if err != nil {
-               return cli.NewExitError(err, -6)
-       }
-
-       // Create Web Server
-       ctx.WWWServer = webserver.New(ctx.Config, ctx.MFolders, ctx.SDKs, ctx.Log, ctx.LogLevelSilly)
-
-       // Run Web Server until exit requested (blocking call)
-       if err = ctx.WWWServer.Serve(); err != nil {
-               ctx.Log.Println(err)
-               return cli.NewExitError(err, -7)
-       }
+       // Run XDS Server (main loop)
+       errCode, err := ctxSvr.Run()
 
-       return cli.NewExitError("Program exited ", -99)
+       return cli.NewExitError(err, errCode)
 }
 
 // main