18 "github.com/Sirupsen/logrus"
19 "github.com/iotbzh/xds-server/lib/common"
20 "github.com/iotbzh/xds-server/lib/xdsconfig"
21 "github.com/syncthing/syncthing/lib/config"
25 type SyncThing struct {
35 exitSTChan chan ExitChan
36 exitSTIChan chan ExitChan
37 client *common.HTTPClient
41 // ExitChan Channel used for process exit
42 type ExitChan struct {
47 // NewSyncThing creates a new instance of Syncthing
48 func NewSyncThing(conf *xdsconfig.Config, log *logrus.Logger) *SyncThing {
49 var url, apiKey, home, binDir string
52 stCfg := conf.FileConf.SThgConf
54 url = stCfg.GuiAddress
55 apiKey = stCfg.GuiAPIKey
61 url = "http://localhost:8384"
63 if url[0:7] != "http://" {
72 if binDir, err = filepath.Abs(filepath.Dir(os.Args[0])); err != nil {
73 binDir = "/usr/local/bin"
82 logsDir: conf.FileConf.LogsDir,
89 // Start Starts syncthing process
90 func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan *chan ExitChan) (*exec.Cmd, error) {
92 // Kill existing process (useful for debug ;-) )
93 if os.Getenv("DEBUG_MODE") != "" {
94 exec.Command("bash", "-c", "pkill -9 "+exeName).Output()
97 path, err := exec.LookPath(path.Join(s.binDir, exeName))
99 return nil, fmt.Errorf("Cannot find %s executable in %s", exeName, s.binDir)
101 cmd := exec.Command(path, args...)
102 cmd.Env = os.Environ()
103 for _, ev := range env {
104 cmd.Env = append(cmd.Env, ev)
109 logFilename := filepath.Join(s.logsDir, exeName+".log")
111 outfile, err := os.Create(logFilename)
113 return nil, fmt.Errorf("Cannot create log file %s", logFilename)
116 cmdOut, err := cmd.StdoutPipe()
118 return nil, fmt.Errorf("Pipe stdout error for : %s", err)
121 go io.Copy(outfile, cmdOut)
129 *eChan = make(chan ExitChan, 1)
130 go func(c *exec.Cmd, oF *os.File) {
132 sts, err := c.Process.Wait()
134 s := sts.Sys().(syscall.WaitStatus)
135 status = s.ExitStatus()
140 s.log.Debugf("%s exited with status %d, err %v", exeName, status, err)
142 *eChan <- ExitChan{status, err}
148 // Start Starts syncthing process
149 func (s *SyncThing) Start() (*exec.Cmd, error) {
152 s.log.Infof(" ST home=%s", s.Home)
153 s.log.Infof(" ST url=%s", s.BaseURL)
158 "--gui-address=" + s.BaseURL,
162 args = append(args, "-gui-apikey=\""+s.APIKey+"\"")
163 s.log.Infof(" ST apikey=%s", s.APIKey)
165 if s.log.Level == logrus.DebugLevel {
166 args = append(args, "-verbose")
170 "STNODEFAULTFOLDER=1",
173 s.STCmd, err = s.startProc("syncthing", args, env, &s.exitSTChan)
178 // StartInotify Starts syncthing-inotify process
179 func (s *SyncThing) StartInotify() (*exec.Cmd, error) {
182 s.log.Infof(" STI home=%s", s.Home)
183 s.log.Infof(" STI url=%s", s.BaseURL)
187 "-target=" + s.BaseURL,
189 if s.log.Level == logrus.DebugLevel {
190 args = append(args, "-verbosity=4")
195 s.STICmd, err = s.startProc("syncthing-inotify", args, env, &s.exitSTIChan)
200 func (s *SyncThing) stopProc(pname string, proc *os.Process, exit chan ExitChan) {
201 if err := proc.Signal(os.Interrupt); err != nil {
202 s.log.Infof("Proc interrupt %s error: %s", pname, err.Error())
206 case <-time.After(time.Second):
207 // A bigger bonk on the head.
208 if err := proc.Signal(os.Kill); err != nil {
209 s.log.Infof("Proc term %s error: %s", pname, err.Error())
214 s.log.Infof("%s stopped (PID %d)", pname, proc.Pid)
217 // Stop Stops syncthing process
218 func (s *SyncThing) Stop() {
222 s.stopProc("syncthing", s.STCmd.Process, s.exitSTChan)
226 // StopInotify Stops syncthing process
227 func (s *SyncThing) StopInotify() {
231 s.stopProc("syncthing-inotify", s.STICmd.Process, s.exitSTIChan)
235 // Connect Establish HTTP connection with Syncthing
236 func (s *SyncThing) Connect() error {
238 s.client, err = common.HTTPNewClient(s.BaseURL,
239 common.HTTPClientConfig{
241 HeaderClientKeyName: "X-Syncthing-ID",
244 msg := ": " + err.Error()
245 if strings.Contains(err.Error(), "connection refused") {
246 msg = fmt.Sprintf("(url: %s)", s.BaseURL)
248 return fmt.Errorf("ERROR: cannot connect to Syncthing %s", msg)
251 return fmt.Errorf("ERROR: cannot connect to Syncthing (null client)")
254 s.client.SetLogger(s.log)
259 // IDGet returns the Syncthing ID of Syncthing instance running locally
260 func (s *SyncThing) IDGet() (string, error) {
262 if err := s.client.HTTPGet("system/status", &data); err != nil {
265 status := make(map[string]interface{})
266 json.Unmarshal(data, &status)
267 return status["myID"].(string), nil
270 // ConfigGet returns the current Syncthing configuration
271 func (s *SyncThing) ConfigGet() (config.Configuration, error) {
273 config := config.Configuration{}
274 if err := s.client.HTTPGet("system/config", &data); err != nil {
277 err := json.Unmarshal(data, &config)
281 // ConfigSet set Syncthing configuration
282 func (s *SyncThing) ConfigSet(cfg config.Configuration) error {
283 body, err := json.Marshal(cfg)
287 return s.client.HTTPPost("system/config", string(body))