diff options
Diffstat (limited to 'lib/syncthing')
-rw-r--r-- | lib/syncthing/st.go | 242 | ||||
-rw-r--r-- | lib/syncthing/stfolder.go | 116 |
2 files changed, 358 insertions, 0 deletions
diff --git a/lib/syncthing/st.go b/lib/syncthing/st.go new file mode 100644 index 0000000..e513876 --- /dev/null +++ b/lib/syncthing/st.go @@ -0,0 +1,242 @@ +package st + +import ( + "encoding/json" + "io/ioutil" + "os" + "path" + "strings" + "syscall" + "time" + + "fmt" + + "os/exec" + + "github.com/Sirupsen/logrus" + "github.com/iotbzh/xds-agent/lib/common" + "github.com/iotbzh/xds-agent/lib/xdsconfig" + "github.com/syncthing/syncthing/lib/config" +) + +// SyncThing . +type SyncThing struct { + BaseURL string + ApiKey string + Home string + STCmd *exec.Cmd + STICmd *exec.Cmd + + // Private fields + binDir string + exitSTChan chan ExitChan + exitSTIChan chan ExitChan + client *common.HTTPClient + log *logrus.Logger +} + +// Monitor process exit +type ExitChan struct { + status int + err error +} + +// NewSyncThing creates a new instance of Syncthing +//func NewSyncThing(url string, apiKey string, home string, log *logrus.Logger) *SyncThing { +func NewSyncThing(conf *xdsconfig.SyncThingConf, log *logrus.Logger) *SyncThing { + url := conf.GuiAddress + apiKey := conf.GuiAPIKey + home := conf.Home + + s := SyncThing{ + BaseURL: url, + ApiKey: apiKey, + Home: home, + binDir: conf.BinDir, + log: log, + } + + if s.BaseURL == "" { + s.BaseURL = "http://localhost:8384" + } + if s.BaseURL[0:7] != "http://" { + s.BaseURL = "http://" + s.BaseURL + } + + return &s +} + +// Start Starts syncthing process +func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan *chan ExitChan) (*exec.Cmd, error) { + + // Kill existing process (useful for debug ;-) ) + if os.Getenv("DEBUG_MODE") != "" { + exec.Command("bash", "-c", "pkill -9 "+exeName).Output() + } + + path, err := exec.LookPath(path.Join(s.binDir, exeName)) + if err != nil { + return nil, fmt.Errorf("Cannot find %s executable in %s", exeName, s.binDir) + } + cmd := exec.Command(path, args...) + cmd.Env = os.Environ() + for _, ev := range env { + cmd.Env = append(cmd.Env, ev) + } + + err = cmd.Start() + if err != nil { + return nil, err + } + + *eChan = make(chan ExitChan, 1) + go func(c *exec.Cmd) { + status := 0 + cmdOut, err := c.StdoutPipe() + if err == nil { + s.log.Errorf("Pipe stdout error for : %s", err) + } else if cmdOut != nil { + stdOutput, _ := ioutil.ReadAll(cmdOut) + fmt.Printf("STDOUT: %s\n", stdOutput) + } + sts, err := c.Process.Wait() + if !sts.Success() { + s := sts.Sys().(syscall.WaitStatus) + status = s.ExitStatus() + } + *eChan <- ExitChan{status, err} + }(cmd) + + return cmd, nil +} + +// Start Starts syncthing process +func (s *SyncThing) Start() (*exec.Cmd, error) { + var err error + args := []string{ + "--home=" + s.Home, + "-no-browser", + "--gui-address=" + s.BaseURL, + } + + if s.ApiKey != "" { + args = append(args, "-gui-apikey=\""+s.ApiKey+"\"") + } + if s.log.Level == logrus.DebugLevel { + args = append(args, "-verbose") + } + + env := []string{ + "STNODEFAULTFOLDER=1", + } + + s.STCmd, err = s.startProc("syncthing", args, env, &s.exitSTChan) + + return s.STCmd, err +} + +// StartInotify Starts syncthing-inotify process +func (s *SyncThing) StartInotify() (*exec.Cmd, error) { + var err error + + args := []string{ + "--home=" + s.Home, + "-target=" + s.BaseURL, + } + if s.log.Level == logrus.DebugLevel { + args = append(args, "-verbosity=4") + } + + env := []string{} + + s.STICmd, err = s.startProc("syncthing-inotify", args, env, &s.exitSTIChan) + + return s.STICmd, err +} + +func (s *SyncThing) stopProc(pname string, proc *os.Process, exit chan ExitChan) { + if err := proc.Signal(os.Interrupt); err != nil { + s.log.Errorf("Proc interrupt %s error: %s", pname, err.Error()) + + select { + case <-exit: + case <-time.After(time.Second): + // A bigger bonk on the head. + if err := proc.Signal(os.Kill); err != nil { + s.log.Errorf("Proc term %s error: %s", pname, err.Error()) + } + <-exit + } + } + s.log.Infof("%s stopped (PID %d)", pname, proc.Pid) +} + +// Stop Stops syncthing process +func (s *SyncThing) Stop() { + if s.STCmd == nil { + return + } + s.stopProc("syncthing", s.STCmd.Process, s.exitSTChan) + s.STCmd = nil +} + +// StopInotify Stops syncthing process +func (s *SyncThing) StopInotify() { + if s.STICmd == nil { + return + } + s.stopProc("syncthing-inotify", s.STICmd.Process, s.exitSTIChan) + s.STICmd = nil +} + +// Connect Establish HTTP connection with Syncthing +func (s *SyncThing) Connect() error { + var err error + s.client, err = common.HTTPNewClient(s.BaseURL, + common.HTTPClientConfig{ + URLPrefix: "/rest", + HeaderClientKeyName: "X-Syncthing-ID", + }) + if err != nil { + msg := ": " + err.Error() + if strings.Contains(err.Error(), "connection refused") { + msg = fmt.Sprintf("(url: %s)", s.BaseURL) + } + return fmt.Errorf("ERROR: cannot connect to Syncthing %s", msg) + } + if s.client == nil { + return fmt.Errorf("ERROR: cannot connect to Syncthing (null client)") + } + return nil +} + +// IDGet returns the Syncthing ID of Syncthing instance running locally +func (s *SyncThing) IDGet() (string, error) { + var data []byte + if err := s.client.HTTPGet("system/status", &data); err != nil { + return "", err + } + status := make(map[string]interface{}) + json.Unmarshal(data, &status) + return status["myID"].(string), nil +} + +// ConfigGet returns the current Syncthing configuration +func (s *SyncThing) ConfigGet() (config.Configuration, error) { + var data []byte + config := config.Configuration{} + if err := s.client.HTTPGet("system/config", &data); err != nil { + return config, err + } + err := json.Unmarshal(data, &config) + return config, err +} + +// ConfigSet set Syncthing configuration +func (s *SyncThing) ConfigSet(cfg config.Configuration) error { + body, err := json.Marshal(cfg) + if err != nil { + return err + } + return s.client.HTTPPost("system/config", string(body)) +} diff --git a/lib/syncthing/stfolder.go b/lib/syncthing/stfolder.go new file mode 100644 index 0000000..d79e579 --- /dev/null +++ b/lib/syncthing/stfolder.go @@ -0,0 +1,116 @@ +package st + +import ( + "path/filepath" + "strings" + + "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/protocol" +) + +// FIXME remove and use an interface on xdsconfig.FolderConfig +type FolderChangeArg struct { + ID string + Label string + RelativePath string + SyncThingID string + ShareRootDir string +} + +// FolderChange is called when configuration has changed +func (s *SyncThing) FolderChange(f FolderChangeArg) error { + + // Get current config + stCfg, err := s.ConfigGet() + if err != nil { + s.log.Errorln(err) + return err + } + + // Add new Device if needed + var devID protocol.DeviceID + if err := devID.UnmarshalText([]byte(f.SyncThingID)); err != nil { + s.log.Errorf("not a valid device id (err %v)\n", err) + return err + } + + newDevice := config.DeviceConfiguration{ + DeviceID: devID, + Name: f.SyncThingID, + Addresses: []string{"dynamic"}, + } + + var found = false + for _, device := range stCfg.Devices { + if device.DeviceID == devID { + found = true + break + } + } + if !found { + stCfg.Devices = append(stCfg.Devices, newDevice) + } + + // Add or update Folder settings + var label, id string + if label = f.Label; label == "" { + label = strings.Split(id, "/")[0] + } + if id = f.ID; id == "" { + id = f.SyncThingID[0:15] + "_" + label + } + + folder := config.FolderConfiguration{ + ID: id, + Label: label, + RawPath: filepath.Join(f.ShareRootDir, f.RelativePath), + } + + folder.Devices = append(folder.Devices, config.FolderDeviceConfiguration{ + DeviceID: newDevice.DeviceID, + }) + + found = false + var fld config.FolderConfiguration + for _, fld = range stCfg.Folders { + if folder.ID == fld.ID { + fld = folder + found = true + break + } + } + if !found { + stCfg.Folders = append(stCfg.Folders, folder) + fld = stCfg.Folders[0] + } + + err = s.ConfigSet(stCfg) + if err != nil { + s.log.Errorln(err) + } + + return nil +} + +// FolderDelete is called to delete a folder config +func (s *SyncThing) FolderDelete(id string) error { + // Get current config + stCfg, err := s.ConfigGet() + if err != nil { + s.log.Errorln(err) + return err + } + + for i, fld := range stCfg.Folders { + if id == fld.ID { + stCfg.Folders = append(stCfg.Folders[:i], stCfg.Folders[i+1:]...) + err = s.ConfigSet(stCfg) + if err != nil { + s.log.Errorln(err) + return err + } + } + } + + return nil +} |