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