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