Fixed Syncthing folder status events and exec command.
[src/xds/xds-agent.git] / lib / agent / projects.go
1 package agent
2
3 import (
4         "fmt"
5         "log"
6         "time"
7
8         "github.com/iotbzh/xds-agent/lib/syncthing"
9         "github.com/syncthing/syncthing/lib/sync"
10 )
11
12 // Projects Represent a an XDS Projects
13 type Projects struct {
14         *Context
15         SThg     *st.SyncThing
16         projects map[string]*IPROJECT
17 }
18
19 // Mutex to make add/delete atomic
20 var pjMutex = sync.NewMutex()
21
22 // NewProjects Create a new instance of Project Model
23 func NewProjects(ctx *Context, st *st.SyncThing) *Projects {
24         return &Projects{
25                 Context:  ctx,
26                 SThg:     st,
27                 projects: make(map[string]*IPROJECT),
28         }
29 }
30
31 // Init Load Projects configuration
32 func (p *Projects) Init(server *XdsServer) error {
33         svrList := make(map[string]*XdsServer)
34         // If server not set, load for all servers
35         if server == nil {
36                 svrList = p.xdsServers
37         } else {
38                 svrList[server.ID] = server
39         }
40         errMsg := ""
41         for _, svr := range svrList {
42                 if svr.Disabled {
43                         continue
44                 }
45                 xFlds := []XdsFolderConfig{}
46                 if err := svr.GetFolders(&xFlds); err != nil {
47                         errMsg += fmt.Sprintf("Cannot retrieve folders config of XDS server ID %s : %v \n", svr.ID, err.Error())
48                         continue
49                 }
50                 p.Log.Debugf("Connected to XDS Server %s, %d projects detected", svr.ID, len(xFlds))
51                 for _, prj := range xFlds {
52                         newP := svr.FolderToProject(prj)
53                         if _, err := p.createUpdate(newP, false, true); err != nil {
54                                 errMsg += "Error while creating project id " + prj.ID + ": " + err.Error() + "\n "
55                                 continue
56                         }
57                 }
58         }
59
60         p.Log.Infof("Number of loaded Projects: %d", len(p.projects))
61
62         if errMsg != "" {
63                 return fmt.Errorf(errMsg)
64         }
65         return nil
66 }
67
68 // Get returns the folder config or nil if not existing
69 func (p *Projects) Get(id string) *IPROJECT {
70         if id == "" {
71                 return nil
72         }
73         fc, exist := p.projects[id]
74         if !exist {
75                 return nil
76         }
77         return fc
78 }
79
80 // GetProjectArr returns the config of all folders as an array
81 func (p *Projects) GetProjectArr() []ProjectConfig {
82         pjMutex.Lock()
83         defer pjMutex.Unlock()
84
85         return p.GetProjectArrUnsafe()
86 }
87
88 // GetProjectArrUnsafe Same as GetProjectArr without mutex protection
89 func (p *Projects) GetProjectArrUnsafe() []ProjectConfig {
90         conf := []ProjectConfig{}
91         for _, v := range p.projects {
92                 prj := (*v).GetProject()
93                 conf = append(conf, *prj)
94         }
95         return conf
96 }
97
98 // Add adds a new folder
99 func (p *Projects) Add(newF ProjectConfig) (*ProjectConfig, error) {
100         prj, err := p.createUpdate(newF, true, false)
101         if err != nil {
102                 return prj, err
103         }
104
105         // Notify client with event
106         if err := p.events.Emit(EVTProjectAdd, *prj); err != nil {
107                 p.Log.Warningf("Cannot notify project deletion: %v", err)
108         }
109
110         return prj, err
111 }
112
113 // CreateUpdate creates or update a folder
114 func (p *Projects) createUpdate(newF ProjectConfig, create bool, initial bool) (*ProjectConfig, error) {
115         var err error
116
117         pjMutex.Lock()
118         defer pjMutex.Unlock()
119
120         // Sanity check
121         if _, exist := p.projects[newF.ID]; create && exist {
122                 return nil, fmt.Errorf("ID already exists")
123         }
124         if newF.ClientPath == "" {
125                 return nil, fmt.Errorf("ClientPath must be set")
126         }
127         if newF.ServerID == "" {
128                 return nil, fmt.Errorf("Server ID must be set")
129         }
130         var svr *XdsServer
131         var exist bool
132         if svr, exist = p.xdsServers[newF.ServerID]; !exist {
133                 return nil, fmt.Errorf("Unknown Server ID %s", newF.ServerID)
134         }
135
136         // Check type supported
137         b, exist := svr.ServerConfig.SupportedSharing[string(newF.Type)]
138         if !exist || !b {
139                 return nil, fmt.Errorf("Server doesn't support project type %s", newF.Type)
140         }
141
142         // Create a new folder object
143         var fld IPROJECT
144         switch newF.Type {
145         // SYNCTHING
146         case TypeCloudSync:
147                 if p.SThg != nil {
148                         fld = NewProjectST(p.Context, svr)
149                 } else {
150                         return nil, fmt.Errorf("Cloud Sync project not supported")
151                 }
152
153         // PATH MAP
154         case TypePathMap:
155                 fld = NewProjectPathMap(p.Context, svr)
156         default:
157                 return nil, fmt.Errorf("Unsupported folder type")
158         }
159
160         var newPrj *ProjectConfig
161         if create {
162                 // Add project on server
163                 if newPrj, err = fld.Add(newF); err != nil {
164                         newF.Status = StatusErrorConfig
165                         log.Printf("ERROR Adding project: %v\n", err)
166                         return newPrj, err
167                 }
168         } else {
169                 // Just update project config
170                 if newPrj, err = fld.UpdateProject(newF); err != nil {
171                         newF.Status = StatusErrorConfig
172                         log.Printf("ERROR Updating project: %v\n", err)
173                         return newPrj, err
174                 }
175         }
176
177         // Sanity check
178         if newPrj.ID == "" {
179                 log.Printf("ERROR project ID empty: %v", newF)
180                 return newPrj, fmt.Errorf("Project ID empty")
181         }
182
183         // Add to folders list
184         p.projects[newPrj.ID] = &fld
185
186         // Force sync after creation
187         // (need to defer to be sure that WS events will arrive after HTTP creation reply)
188         go func() {
189                 time.Sleep(time.Millisecond * 500)
190                 fld.Sync()
191         }()
192
193         return newPrj, nil
194 }
195
196 // Delete deletes a specific folder
197 func (p *Projects) Delete(id string) (ProjectConfig, error) {
198         var err error
199
200         pjMutex.Lock()
201         defer pjMutex.Unlock()
202
203         fld := ProjectConfig{}
204         fc, exist := p.projects[id]
205         if !exist {
206                 return fld, fmt.Errorf("unknown id")
207         }
208
209         prj := (*fc).GetProject()
210
211         if err = (*fc).Delete(); err != nil {
212                 return *prj, err
213         }
214
215         delete(p.projects, id)
216
217         // Notify client with event
218         if err := p.events.Emit(EVTProjectDelete, *prj); err != nil {
219                 p.Log.Warningf("Cannot notify project deletion: %v", err)
220         }
221
222         return *prj, err
223 }
224
225 // ForceSync Force the synchronization of a folder
226 func (p *Projects) ForceSync(id string) error {
227         fc := p.Get(id)
228         if fc == nil {
229                 return fmt.Errorf("Unknown id")
230         }
231         return (*fc).Sync()
232 }
233
234 // IsProjectInSync Returns true when folder is in sync
235 func (p *Projects) IsProjectInSync(id string) (bool, error) {
236         fc := p.Get(id)
237         if fc == nil {
238                 return false, fmt.Errorf("Unknown id")
239         }
240         return (*fc).IsInSync()
241 }