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