Initial commit.
[src/xds/xds-agent.git] / lib / syncthing / st.go
1 package st
2
3 import (
4         "encoding/json"
5         "io/ioutil"
6         "os"
7         "path"
8         "strings"
9         "syscall"
10         "time"
11
12         "fmt"
13
14         "os/exec"
15
16         "github.com/Sirupsen/logrus"
17         "github.com/iotbzh/xds-agent/lib/common"
18         "github.com/iotbzh/xds-agent/lib/xdsconfig"
19         "github.com/syncthing/syncthing/lib/config"
20 )
21
22 // SyncThing .
23 type SyncThing struct {
24         BaseURL string
25         ApiKey  string
26         Home    string
27         STCmd   *exec.Cmd
28         STICmd  *exec.Cmd
29
30         // Private fields
31         binDir      string
32         exitSTChan  chan ExitChan
33         exitSTIChan chan ExitChan
34         client      *common.HTTPClient
35         log         *logrus.Logger
36 }
37
38 // Monitor process exit
39 type ExitChan struct {
40         status int
41         err    error
42 }
43
44 // NewSyncThing creates a new instance of Syncthing
45 //func NewSyncThing(url string, apiKey string, home string, log *logrus.Logger) *SyncThing {
46 func NewSyncThing(conf *xdsconfig.SyncThingConf, log *logrus.Logger) *SyncThing {
47         url := conf.GuiAddress
48         apiKey := conf.GuiAPIKey
49         home := conf.Home
50
51         s := SyncThing{
52                 BaseURL: url,
53                 ApiKey:  apiKey,
54                 Home:    home,
55                 binDir:  conf.BinDir,
56                 log:     log,
57         }
58
59         if s.BaseURL == "" {
60                 s.BaseURL = "http://localhost:8384"
61         }
62         if s.BaseURL[0:7] != "http://" {
63                 s.BaseURL = "http://" + s.BaseURL
64         }
65
66         return &s
67 }
68
69 // Start Starts syncthing process
70 func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan *chan ExitChan) (*exec.Cmd, error) {
71
72         // Kill existing process (useful for debug ;-) )
73         if os.Getenv("DEBUG_MODE") != "" {
74                 exec.Command("bash", "-c", "pkill -9 "+exeName).Output()
75         }
76
77         path, err := exec.LookPath(path.Join(s.binDir, exeName))
78         if err != nil {
79                 return nil, fmt.Errorf("Cannot find %s executable in %s", exeName, s.binDir)
80         }
81         cmd := exec.Command(path, args...)
82         cmd.Env = os.Environ()
83         for _, ev := range env {
84                 cmd.Env = append(cmd.Env, ev)
85         }
86
87         err = cmd.Start()
88         if err != nil {
89                 return nil, err
90         }
91
92         *eChan = make(chan ExitChan, 1)
93         go func(c *exec.Cmd) {
94                 status := 0
95                 cmdOut, err := c.StdoutPipe()
96                 if err == nil {
97                         s.log.Errorf("Pipe stdout error for : %s", err)
98                 } else if cmdOut != nil {
99                         stdOutput, _ := ioutil.ReadAll(cmdOut)
100                         fmt.Printf("STDOUT: %s\n", stdOutput)
101                 }
102                 sts, err := c.Process.Wait()
103                 if !sts.Success() {
104                         s := sts.Sys().(syscall.WaitStatus)
105                         status = s.ExitStatus()
106                 }
107                 *eChan <- ExitChan{status, err}
108         }(cmd)
109
110         return cmd, nil
111 }
112
113 // Start Starts syncthing process
114 func (s *SyncThing) Start() (*exec.Cmd, error) {
115         var err error
116         args := []string{
117                 "--home=" + s.Home,
118                 "-no-browser",
119                 "--gui-address=" + s.BaseURL,
120         }
121
122         if s.ApiKey != "" {
123                 args = append(args, "-gui-apikey=\""+s.ApiKey+"\"")
124         }
125         if s.log.Level == logrus.DebugLevel {
126                 args = append(args, "-verbose")
127         }
128
129         env := []string{
130                 "STNODEFAULTFOLDER=1",
131         }
132
133         s.STCmd, err = s.startProc("syncthing", args, env, &s.exitSTChan)
134
135         return s.STCmd, err
136 }
137
138 // StartInotify Starts syncthing-inotify process
139 func (s *SyncThing) StartInotify() (*exec.Cmd, error) {
140         var err error
141
142         args := []string{
143                 "--home=" + s.Home,
144                 "-target=" + s.BaseURL,
145         }
146         if s.log.Level == logrus.DebugLevel {
147                 args = append(args, "-verbosity=4")
148         }
149
150         env := []string{}
151
152         s.STICmd, err = s.startProc("syncthing-inotify", args, env, &s.exitSTIChan)
153
154         return s.STICmd, err
155 }
156
157 func (s *SyncThing) stopProc(pname string, proc *os.Process, exit chan ExitChan) {
158         if err := proc.Signal(os.Interrupt); err != nil {
159                 s.log.Errorf("Proc interrupt %s error: %s", pname, err.Error())
160
161                 select {
162                 case <-exit:
163                 case <-time.After(time.Second):
164                         // A bigger bonk on the head.
165                         if err := proc.Signal(os.Kill); err != nil {
166                                 s.log.Errorf("Proc term %s error: %s", pname, err.Error())
167                         }
168                         <-exit
169                 }
170         }
171         s.log.Infof("%s stopped (PID %d)", pname, proc.Pid)
172 }
173
174 // Stop Stops syncthing process
175 func (s *SyncThing) Stop() {
176         if s.STCmd == nil {
177                 return
178         }
179         s.stopProc("syncthing", s.STCmd.Process, s.exitSTChan)
180         s.STCmd = nil
181 }
182
183 // StopInotify Stops syncthing process
184 func (s *SyncThing) StopInotify() {
185         if s.STICmd == nil {
186                 return
187         }
188         s.stopProc("syncthing-inotify", s.STICmd.Process, s.exitSTIChan)
189         s.STICmd = nil
190 }
191
192 // Connect Establish HTTP connection with Syncthing
193 func (s *SyncThing) Connect() error {
194         var err error
195         s.client, err = common.HTTPNewClient(s.BaseURL,
196                 common.HTTPClientConfig{
197                         URLPrefix:           "/rest",
198                         HeaderClientKeyName: "X-Syncthing-ID",
199                 })
200         if err != nil {
201                 msg := ": " + err.Error()
202                 if strings.Contains(err.Error(), "connection refused") {
203                         msg = fmt.Sprintf("(url: %s)", s.BaseURL)
204                 }
205                 return fmt.Errorf("ERROR: cannot connect to Syncthing %s", msg)
206         }
207         if s.client == nil {
208                 return fmt.Errorf("ERROR: cannot connect to Syncthing (null client)")
209         }
210         return nil
211 }
212
213 // IDGet returns the Syncthing ID of Syncthing instance running locally
214 func (s *SyncThing) IDGet() (string, error) {
215         var data []byte
216         if err := s.client.HTTPGet("system/status", &data); err != nil {
217                 return "", err
218         }
219         status := make(map[string]interface{})
220         json.Unmarshal(data, &status)
221         return status["myID"].(string), nil
222 }
223
224 // ConfigGet returns the current Syncthing configuration
225 func (s *SyncThing) ConfigGet() (config.Configuration, error) {
226         var data []byte
227         config := config.Configuration{}
228         if err := s.client.HTTPGet("system/config", &data); err != nil {
229                 return config, err
230         }
231         err := json.Unmarshal(data, &config)
232         return config, err
233 }
234
235 // ConfigSet set Syncthing configuration
236 func (s *SyncThing) ConfigSet(cfg config.Configuration) error {
237         body, err := json.Marshal(cfg)
238         if err != nil {
239                 return err
240         }
241         return s.client.HTTPPost("system/config", string(body))
242 }