22 "github.com/Sirupsen/logrus"
23 common "github.com/iotbzh/xds-common/golib"
24 "github.com/iotbzh/xds-server/lib/xdsconfig"
25 "github.com/syncthing/syncthing/lib/config"
29 type SyncThing struct {
41 exitSTChan chan ExitChan
42 exitSTIChan chan ExitChan
43 conf *xdsconfig.Config
44 client *common.HTTPClient
49 // ExitChan Channel used for process exit
50 type ExitChan struct {
55 // ConfigInSync Check whether if Syncthing configuration is in sync
56 type configInSync struct {
57 ConfigInSync bool `json:"configInSync"`
60 // FolderStatus Information about the current status of a folder.
61 type FolderStatus struct {
62 GlobalFiles int `json:"globalFiles"`
63 GlobalDirectories int `json:"globalDirectories"`
64 GlobalSymlinks int `json:"globalSymlinks"`
65 GlobalDeleted int `json:"globalDeleted"`
66 GlobalBytes int64 `json:"globalBytes"`
68 LocalFiles int `json:"localFiles"`
69 LocalDirectories int `json:"localDirectories"`
70 LocalSymlinks int `json:"localSymlinks"`
71 LocalDeleted int `json:"localDeleted"`
72 LocalBytes int64 `json:"localBytes"`
74 NeedFiles int `json:"needFiles"`
75 NeedDirectories int `json:"needDirectories"`
76 NeedSymlinks int `json:"needSymlinks"`
77 NeedDeletes int `json:"needDeletes"`
78 NeedBytes int64 `json:"needBytes"`
80 InSyncFiles int `json:"inSyncFiles"`
81 InSyncBytes int64 `json:"inSyncBytes"`
83 State string `json:"state"`
84 StateChanged time.Time `json:"stateChanged"`
86 Sequence int64 `json:"sequence"`
88 IgnorePatterns bool `json:"ignorePatterns"`
91 // NewSyncThing creates a new instance of Syncthing
92 func NewSyncThing(conf *xdsconfig.Config, log *logrus.Logger) *SyncThing {
93 var url, apiKey, home, binDir string
96 stCfg := conf.FileConf.SThgConf
98 url = stCfg.GuiAddress
99 apiKey = stCfg.GuiAPIKey
101 binDir = stCfg.BinDir
105 url = "http://localhost:8384"
107 if url[0:7] != "http://" {
108 url = "http://" + url
116 if binDir, err = filepath.Abs(filepath.Dir(os.Args[0])); err != nil {
117 binDir = "/usr/local/bin"
126 logsDir: conf.FileConf.LogsDir,
131 // Create Events monitoring
132 s.Events = s.NewEventListener()
137 // Start Starts syncthing process
138 func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan *chan ExitChan) (*exec.Cmd, error) {
140 // Kill existing process (useful for debug ;-) )
141 if os.Getenv("DEBUG_MODE") != "" {
142 exec.Command("bash", "-c", "pkill -9 "+exeName).Output()
145 path, err := exec.LookPath(path.Join(s.binDir, exeName))
147 return nil, fmt.Errorf("Cannot find %s executable in %s", exeName, s.binDir)
149 cmd := exec.Command(path, args...)
150 cmd.Env = os.Environ()
151 for _, ev := range env {
152 cmd.Env = append(cmd.Env, ev)
157 logFilename := filepath.Join(s.logsDir, exeName+".log")
159 outfile, err := os.Create(logFilename)
161 return nil, fmt.Errorf("Cannot create log file %s", logFilename)
164 cmdOut, err := cmd.StdoutPipe()
166 return nil, fmt.Errorf("Pipe stdout error for : %s", err)
169 go io.Copy(outfile, cmdOut)
177 *eChan = make(chan ExitChan, 1)
178 go func(c *exec.Cmd, oF *os.File) {
180 sts, err := c.Process.Wait()
182 s := sts.Sys().(syscall.WaitStatus)
183 status = s.ExitStatus()
188 s.log.Debugf("%s exited with status %d, err %v", exeName, status, err)
190 *eChan <- ExitChan{status, err}
196 // Start Starts syncthing process
197 func (s *SyncThing) Start() (*exec.Cmd, error) {
200 s.log.Infof(" ST home=%s", s.Home)
201 s.log.Infof(" ST url=%s", s.BaseURL)
206 "--gui-address=" + s.BaseURL,
210 args = append(args, "-gui-apikey=\""+s.APIKey+"\"")
211 s.log.Infof(" ST apikey=%s", s.APIKey)
213 if s.log.Level == logrus.DebugLevel {
214 args = append(args, "-verbose")
218 "STNODEFAULTFOLDER=1",
220 "STNORESTART=1", // FIXME SEB remove ?
223 s.STCmd, err = s.startProc("syncthing", args, env, &s.exitSTChan)
225 // Use autogenerated apikey if not set by config.json
226 if err == nil && s.APIKey == "" {
227 if fd, err := os.Open(filepath.Join(s.Home, "config.xml")); err == nil {
229 if b, err := ioutil.ReadAll(fd); err == nil {
230 re := regexp.MustCompile("<apikey>(.*)</apikey>")
231 key := re.FindStringSubmatch(string(b))
242 // StartInotify Starts syncthing-inotify process
243 func (s *SyncThing) StartInotify() (*exec.Cmd, error) {
245 exeName := "syncthing-inotify"
247 s.log.Infof(" STI url=%s", s.BaseURL)
250 "-target=" + s.BaseURL,
253 args = append(args, "-api="+s.APIKey)
254 s.log.Infof("%s uses apikey=%s", exeName, s.APIKey)
256 if s.log.Level == logrus.DebugLevel {
257 args = append(args, "-verbosity=4")
262 s.STICmd, err = s.startProc(exeName, args, env, &s.exitSTIChan)
267 func (s *SyncThing) stopProc(pname string, proc *os.Process, exit chan ExitChan) {
268 if err := proc.Signal(os.Interrupt); err != nil {
269 s.log.Infof("Proc interrupt %s error: %s", pname, err.Error())
273 case <-time.After(time.Second):
274 // A bigger bonk on the head.
275 if err := proc.Signal(os.Kill); err != nil {
276 s.log.Infof("Proc term %s error: %s", pname, err.Error())
281 s.log.Infof("%s stopped (PID %d)", pname, proc.Pid)
284 // Stop Stops syncthing process
285 func (s *SyncThing) Stop() {
289 s.stopProc("syncthing", s.STCmd.Process, s.exitSTChan)
293 // StopInotify Stops syncthing process
294 func (s *SyncThing) StopInotify() {
298 s.stopProc("syncthing-inotify", s.STICmd.Process, s.exitSTIChan)
302 // Connect Establish HTTP connection with Syncthing
303 func (s *SyncThing) Connect() error {
306 s.client, err = common.HTTPNewClient(s.BaseURL,
307 common.HTTPClientConfig{
309 HeaderClientKeyName: "X-Syncthing-ID",
312 msg := ": " + err.Error()
313 if strings.Contains(err.Error(), "connection refused") {
314 msg = fmt.Sprintf("(url: %s)", s.BaseURL)
316 return fmt.Errorf("ERROR: cannot connect to Syncthing %s", msg)
319 return fmt.Errorf("ERROR: cannot connect to Syncthing (null client)")
322 // Redirect HTTP log into a file
323 s.client.SetLogLevel(s.conf.Log.Level.String())
324 s.client.LoggerPrefix = "SYNCTHING: "
325 s.client.LoggerOut = s.conf.LogVerboseOut
327 s.MyID, err = s.IDGet()
329 return fmt.Errorf("ERROR: cannot retrieve ID")
334 // Start events monitoring
335 err = s.Events.Start()
340 // IDGet returns the Syncthing ID of Syncthing instance running locally
341 func (s *SyncThing) IDGet() (string, error) {
343 if err := s.client.HTTPGet("system/status", &data); err != nil {
346 status := make(map[string]interface{})
347 json.Unmarshal(data, &status)
348 return status["myID"].(string), nil
351 // ConfigGet returns the current Syncthing configuration
352 func (s *SyncThing) ConfigGet() (config.Configuration, error) {
354 config := config.Configuration{}
355 if err := s.client.HTTPGet("system/config", &data); err != nil {
358 err := json.Unmarshal(data, &config)
362 // ConfigSet set Syncthing configuration
363 func (s *SyncThing) ConfigSet(cfg config.Configuration) error {
364 body, err := json.Marshal(cfg)
368 return s.client.HTTPPost("system/config", string(body))
371 // IsConfigInSync Returns true if configuration is in sync
372 func (s *SyncThing) IsConfigInSync() (bool, error) {
375 if err := s.client.HTTPGet("system/config/insync", &data); err != nil {
378 if err := json.Unmarshal(data, &d); err != nil {
381 return d.ConfigInSync, nil