2 * Copyright (C) 2017 "IoT.bzh"
3 * Author Sebastien Douheret <sebastien@iot.bzh>
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
28 "github.com/Sirupsen/logrus"
33 MonitorTime time.Duration
39 cbArr map[string][]cbMap
43 Type string `json:"type"`
44 Time time.Time `json:"time"`
45 Data map[string]string `json:"data"`
48 type EventsCBData map[string]interface{}
49 type EventsCB func(ev Event, cbData *EventsCBData)
52 EventFolderCompletion string = "FolderCompletion"
53 EventFolderSummary string = "FolderSummary"
54 EventFolderPaused string = "FolderPaused"
55 EventFolderResumed string = "FolderResumed"
56 EventFolderErrors string = "FolderErrors"
57 EventStateChanged string = "StateChanged"
60 var EventsAll string = EventFolderCompletion + "|" +
61 EventFolderSummary + "|" +
62 EventFolderPaused + "|" +
63 EventFolderResumed + "|" +
64 EventFolderErrors + "|" +
68 // Per-subscription sequential event ID. Named "id" for backwards compatibility with the REST API
69 SubscriptionID int `json:"id"`
70 // Global ID of the event across all subscriptions
71 GlobalID int `json:"globalID"`
72 Time time.Time `json:"time"`
73 Type string `json:"type"`
74 Data map[string]interface{} `json:"data"`
84 // NewEventListener Create a new instance of Event listener
85 func (s *SyncThing) NewEventListener() *Events {
86 _, dbg := os.LookupEnv("XDS_DEBUG_STEVENTS") // set to add more debug log
88 MonitorTime: 100, // in Milliseconds
90 stop: make(chan bool, 1),
93 cbArr: make(map[string][]cbMap),
97 // Start starts event monitoring loop
98 func (e *Events) Start() error {
103 // Stop stops event monitoring loop
104 func (e *Events) Stop() {
108 // Register Add a listener on an event
109 func (e *Events) Register(evName string, cb EventsCB, filterID string, data *EventsCBData) (int, error) {
110 if evName == "" || !strings.Contains(EventsAll, evName) {
111 return -1, fmt.Errorf("Unknown event name")
114 data = &EventsCBData{}
118 if _, ok := e.cbArr[evName]; ok {
119 cbList = e.cbArr[evName]
123 (*data)["id"] = strconv.Itoa(id)
125 e.cbArr[evName] = append(cbList, cbMap{id: id, cb: cb, filterID: filterID, data: data})
130 // UnRegister Remove a listener event
131 func (e *Events) UnRegister(evName string, id int) error {
132 cbKey, ok := e.cbArr[evName]
134 return fmt.Errorf("No event registered to such name")
137 // FIXME - NOT TESTED
138 if id >= len(cbKey) {
139 return fmt.Errorf("Invalid id")
140 } else if id == len(cbKey) {
141 e.cbArr[evName] = cbKey[:id-1]
143 e.cbArr[evName] = cbKey[id : id+1]
149 // GetEvents returns the Syncthing events
150 func (e *Events) getEvents(since int) ([]STEvent, error) {
155 url += "?since=" + strconv.Itoa(since)
157 if err := e.st.client.HTTPGet(url, &data); err != nil {
160 err := json.Unmarshal(data, &ev)
164 // Loop to monitor Syncthing events
165 func (e *Events) monitorLoop() {
166 e.log.Infof("Event monitoring running...")
173 e.log.Infof("Event monitoring exited")
176 case <-time.After(e.MonitorTime * time.Millisecond):
180 time.Sleep(time.Second)
181 if cntErrConn > cntErrRetry {
182 e.log.Error("ST Event monitor: ST connection down")
185 if _, err := e.getEvents(since); err == nil {
186 e.st.Connected = true
188 // XXX - should we reset since value ?
196 stEvArr, err := e.getEvents(since)
198 e.log.Errorf("Syncthing Get Events: %v", err)
199 e.st.Connected = false
204 for _, stEv := range stEvArr {
205 since = stEv.SubscriptionID
207 e.log.Warnf("ST EVENT: %d %s\n %v", stEv.GlobalID, stEv.Type, stEv)
210 cbKey, ok := e.cbArr[stEv.Type]
221 // FIXME: re-define data struct for each events
222 // instead of map of string and use JSON marshing/unmarshing
224 evData.Data = make(map[string]string)
227 case EventFolderCompletion:
228 fID = convString(stEv.Data["folder"])
229 evData.Data["completion"] = convFloat64(stEv.Data["completion"])
231 case EventFolderSummary:
232 fID = convString(stEv.Data["folder"])
233 evData.Data["needBytes"] = convInt64(stEv.Data["needBytes"])
234 evData.Data["state"] = convString(stEv.Data["state"])
236 case EventFolderPaused, EventFolderResumed:
237 fID = convString(stEv.Data["id"])
238 evData.Data["label"] = convString(stEv.Data["label"])
240 case EventFolderErrors:
241 fID = convString(stEv.Data["folder"])
242 // TODO decode array evData.Data["errors"] = convString(stEv.Data["errors"])
244 case EventStateChanged:
245 fID = convString(stEv.Data["folder"])
246 evData.Data["from"] = convString(stEv.Data["from"])
247 evData.Data["to"] = convString(stEv.Data["to"])
250 e.log.Warnf("Unsupported event type")
254 evData.Data["id"] = fID
257 // Call all registered callbacks
258 for _, c := range cbKey {
260 e.log.Warnf("EVENT CB fID=%s, filterID=%s", fID, c.filterID)
262 // Call when filterID is not set or when it matches
263 if c.filterID == "" || (fID != "" && fID == c.filterID) {
272 func convString(d interface{}) string {
276 func convFloat64(d interface{}) string {
277 return strconv.FormatFloat(d.(float64), 'f', -1, 64)
280 func convInt64(d interface{}) string {
281 return strconv.FormatInt(d.(int64), 10)