Fixed xds-server folder events detection.
[src/xds/xds-agent.git] / lib / agent / project-st.go
1 /*
2  * Copyright (C) 2017 "IoT.bzh"
3  * Author Sebastien Douheret <sebastien@iot.bzh>
4  *
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
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  */
17
18 package agent
19
20 import (
21         "encoding/json"
22         "fmt"
23
24         st "github.com/iotbzh/xds-agent/lib/syncthing"
25         "github.com/iotbzh/xds-agent/lib/xaapiv1"
26         "github.com/iotbzh/xds-server/lib/xsapiv1"
27 )
28
29 // IPROJECT interface implementation for syncthing projects
30
31 // STProject .
32 type STProject struct {
33         *Context
34         server   *XdsServer
35         folder   *xsapiv1.FolderConfig
36         eventIDs []int
37 }
38
39 // NewProjectST Create a new instance of STProject
40 func NewProjectST(ctx *Context, svr *XdsServer) *STProject {
41         p := STProject{
42                 Context: ctx,
43                 server:  svr,
44                 folder:  &xsapiv1.FolderConfig{},
45         }
46         return &p
47 }
48
49 // Add a new project
50 func (p *STProject) Add(cfg xaapiv1.ProjectConfig) (*xaapiv1.ProjectConfig, error) {
51         var err error
52
53         // Add project/folder into XDS Server
54         err = p.server.FolderAdd(p.server.ProjectToFolder(cfg), p.folder)
55         if err != nil {
56                 return nil, err
57         }
58         svrPrj := p.GetProject()
59
60         // Declare project into local Syncthing
61         id, err := p.SThg.FolderChange(st.FolderChangeArg{
62                 ID:           svrPrj.ID,
63                 Label:        svrPrj.Label,
64                 RelativePath: cfg.ClientPath,
65                 SyncThingID:  p.server.ServerConfig.Builder.SyncThingID,
66         })
67         if err != nil {
68                 return nil, err
69         }
70
71         locPrj, err := p.SThg.FolderConfigGet(id)
72         if err != nil {
73                 svrPrj.Status = xaapiv1.StatusErrorConfig
74                 return nil, err
75         }
76         if svrPrj.ID != locPrj.ID {
77                 p.Log.Errorf("Project ID in XDSServer and local ST differ: %s != %s", svrPrj.ID, locPrj.ID)
78         }
79
80         // Use Setup function to setup remains fields
81         return p.Setup(*svrPrj)
82 }
83
84 // Delete a project
85 func (p *STProject) Delete() error {
86         errSvr := p.server.FolderDelete(p.folder.ID)
87         errLoc := p.SThg.FolderDelete(p.folder.ID)
88         if errSvr != nil {
89                 return errSvr
90         }
91         return errLoc
92 }
93
94 // GetProject Get public part of project config
95 func (p *STProject) GetProject() *xaapiv1.ProjectConfig {
96         prj := p.server.FolderToProject(*p.folder)
97         prj.ServerID = p.server.ID
98         return &prj
99 }
100
101 // Setup Setup local project config
102 func (p *STProject) Setup(prj xaapiv1.ProjectConfig) (*xaapiv1.ProjectConfig, error) {
103         // Update folder
104         p.folder = p.server.ProjectToFolder(prj)
105         svrPrj := p.GetProject()
106
107         // Register events to update folder status
108         // Register to XDS Server events
109         p.server.EventOn(xsapiv1.EVTFolderStateChange, "", p._cbServerFolderChanged)
110         if err := p.server.EventRegister(xsapiv1.EVTFolderStateChange, svrPrj.ID); err != nil {
111                 p.Log.Warningf("XDS Server EventRegister failed: %v", err)
112                 return svrPrj, err
113         }
114
115         // Register to Local Syncthing events
116         for _, evName := range []string{st.EventStateChanged, st.EventFolderPaused} {
117                 evID, err := p.SThg.Events.Register(evName, p._cbLocalSTEvents, svrPrj.ID, nil)
118                 if err != nil {
119                         return nil, err
120                 }
121                 p.eventIDs = append(p.eventIDs, evID)
122         }
123
124         return svrPrj, nil
125 }
126
127 // Update Update some field of a project
128 func (p *STProject) Update(prj xaapiv1.ProjectConfig) (*xaapiv1.ProjectConfig, error) {
129
130         if p.folder.ID != prj.ID {
131                 return nil, fmt.Errorf("Invalid id")
132         }
133
134         err := p.server.FolderUpdate(p.server.ProjectToFolder(prj), p.folder)
135         if err != nil {
136                 return nil, err
137         }
138
139         return p.GetProject(), nil
140 }
141
142 // GetServer Get the XdsServer that holds this project
143 func (p *STProject) GetServer() *XdsServer {
144         return p.server
145 }
146
147 // Sync Force project files synchronization
148 func (p *STProject) Sync() error {
149         if err := p.server.FolderSync(p.folder.ID); err != nil {
150                 return err
151         }
152         return p.SThg.FolderScan(p.folder.ID, "")
153 }
154
155 // IsInSync Check if project files are in-sync
156 func (p *STProject) IsInSync() (bool, error) {
157         // Should be up-to-date by callbacks (see below)
158         return p.folder.IsInSync, nil
159 }
160
161 /**
162 ** Private functions
163 ***/
164
165 // callback use to update (XDS Server) folder IsInSync status
166
167 func (p *STProject) _cbServerFolderChanged(pData interface{}, data interface{}) error {
168         evt := xsapiv1.EventMsg{}
169         d, err := json.Marshal(data)
170         if err != nil {
171                 p.Log.Errorf("Cannot marshal XDS Server event folder-change err=%v", err)
172                 return err
173         }
174         if err = json.Unmarshal(d, &evt); err != nil {
175                 p.Log.Errorf("Cannot unmarshal XDS Server event folder-change err=%v", err)
176                 return err
177         }
178
179         fld, err := evt.DecodeFolderConfig()
180         if err != nil {
181                 p.Log.Errorf("Cannot decode FolderChanged event: %v", data)
182         }
183
184         // Only process event that concerns this project/folder ID
185         if p.folder.ID != fld.ID {
186                 return nil
187         }
188
189         if fld.IsInSync != p.folder.DataCloudSync.STSvrIsInSync ||
190                 fld.Status != p.folder.DataCloudSync.STSvrStatus {
191
192                 p.folder.DataCloudSync.STSvrIsInSync = fld.IsInSync
193                 p.folder.DataCloudSync.STSvrStatus = fld.Status
194
195                 if err := p.events.Emit(xaapiv1.EVTProjectChange, p.server.FolderToProject(*p.folder), ""); err != nil {
196                         p.Log.Warningf("Cannot notify project change (from server): %v", err)
197                 }
198         }
199         return nil
200 }
201
202 // callback use to update IsInSync status
203 func (p *STProject) _cbLocalSTEvents(ev st.Event, data *st.EventsCBData) {
204
205         inSync := p.folder.DataCloudSync.STLocIsInSync
206         sts := p.folder.DataCloudSync.STLocStatus
207         prevSync := inSync
208         prevStatus := sts
209
210         switch ev.Type {
211
212         case st.EventStateChanged:
213                 to := ev.Data["to"]
214                 switch to {
215                 case "scanning", "syncing":
216                         sts = xaapiv1.StatusSyncing
217                 case "idle":
218                         sts = xaapiv1.StatusEnable
219                 }
220                 inSync = (to == "idle")
221
222         case st.EventFolderPaused:
223                 if sts == xaapiv1.StatusEnable {
224                         sts = xaapiv1.StatusPause
225                 }
226                 inSync = false
227         }
228
229         if prevSync != inSync || prevStatus != sts {
230
231                 p.folder.DataCloudSync.STLocIsInSync = inSync
232                 p.folder.DataCloudSync.STLocStatus = sts
233
234                 if err := p.events.Emit(xaapiv1.EVTProjectChange, p.server.FolderToProject(*p.folder), ""); err != nil {
235                         p.Log.Warningf("Cannot notify project change (local): %v", err)
236                 }
237         }
238 }