2 * Copyright (C) 2017-2018 "IoT.bzh"
3 * Author Sebastien Douheret <sebastien@iot.bzh>
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
29 common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
30 "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xdsconfig"
31 "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
32 "github.com/franciscocpg/reflectme"
33 "github.com/syncthing/syncthing/lib/sync"
36 // Folders Represent a an XDS folders
40 folders map[string]*IFOLDER
41 registerCB []RegisteredCB
44 // RegisteredCB Hold registered callbacks
45 type RegisteredCB struct {
47 data *FolderEventCBData
50 // Mutex to make add/delete atomic
51 var fcMutex = sync.NewMutex()
52 var ffMutex = sync.NewMutex()
54 // FoldersConstructor Create a new instance of Model Folders
55 func FoldersConstructor(ctx *Context) *Folders {
56 file, _ := xdsconfig.FoldersConfigFilenameGet()
60 folders: make(map[string]*IFOLDER),
61 registerCB: []RegisteredCB{},
65 // LoadConfig Load folders configuration from disk
66 func (f *Folders) LoadConfig() error {
67 var flds []xsapiv1.FolderConfig
68 var stFlds []xsapiv1.FolderConfig
71 if f.Config.Options.NoFolderConfig {
72 f.Log.Infof("Don't read folder config file (-no-folderconfig option is set)")
73 } else if f.fileOnDisk != "" {
74 f.Log.Infof("Use folder config file: %s", f.fileOnDisk)
75 err := foldersConfigRead(f.fileOnDisk, &flds)
77 if strings.HasPrefix(err.Error(), "EOF") {
78 f.Log.Warnf("Empty folder config file")
79 } else if strings.HasPrefix(err.Error(), "No folder config") {
80 f.Log.Warnf(err.Error())
86 f.Log.Warnf("Folders config filename not set")
89 // Retrieve initial Syncthing config (just append don't overwrite existing ones)
91 f.Log.Infof("Retrieve syncthing folder config")
92 if err := f.SThg.FolderLoadFromStConfig(&stFlds); err != nil {
93 // Don't exit on such error, just log it
94 f.Log.Errorf(err.Error())
97 f.Log.Infof("Syncthing support is disabled.")
100 // Merge syncthing folders into XDS folders
101 for _, stf := range stFlds {
103 for i, xf := range flds {
107 if xf.Type != xsapiv1.TypeCloudSync {
108 flds[i].Status = xsapiv1.StatusErrorConfig
115 flds = append(flds, stf)
119 // Detect ghost project
120 // (IOW existing in xds file config and not in syncthing database)
122 for i, xf := range flds {
123 // only for syncthing project
124 if xf.Type != xsapiv1.TypeCloudSync {
128 for _, stf := range stFlds {
135 flds[i].Status = xsapiv1.StatusErrorConfig
141 f.Log.Infof("Loading initial folders config: %d folders found", len(flds))
142 for _, fc := range flds {
143 if _, err := f.createUpdate(fc, false, true); err != nil {
148 // Save config on disk
149 err := f.SaveConfig()
154 // SaveConfig Save folders configuration to disk
155 func (f *Folders) SaveConfig() error {
156 if f.fileOnDisk == "" {
157 return fmt.Errorf("Folders config filename not set")
160 // FIXME: buffered save or avoid to write on disk each time
161 return foldersConfigWrite(f.fileOnDisk, f.getConfigArrUnsafe())
164 // ResolveID Complete a Folder ID (helper for user that can use partial ID value)
165 func (f *Folders) ResolveID(id string) (string, error) {
171 for iid := range f.folders {
172 if strings.HasPrefix(iid, id) {
173 match = append(match, iid)
179 } else if len(match) == 0 {
180 return id, fmt.Errorf("Unknown id")
182 return id, fmt.Errorf("Multiple IDs found %v", match)
185 // Get returns the folder config or nil if not existing
186 func (f *Folders) Get(id string) *IFOLDER {
190 fc, exist := f.folders[id]
197 // GetConfigArr returns the config of all folders as an array
198 func (f *Folders) GetConfigArr() []xsapiv1.FolderConfig {
200 defer fcMutex.Unlock()
202 return f.getConfigArrUnsafe()
205 // getConfigArrUnsafe Same as GetConfigArr without mutex protection
206 func (f *Folders) getConfigArrUnsafe() []xsapiv1.FolderConfig {
207 conf := []xsapiv1.FolderConfig{}
208 for _, v := range f.folders {
209 conf = append(conf, (*v).GetConfig())
214 // Add adds a new folder
215 func (f *Folders) Add(newF xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
216 return f.createUpdate(newF, true, false)
219 // CreateUpdate creates or update a folder
220 func (f *Folders) createUpdate(newF xsapiv1.FolderConfig, create bool, initial bool) (*xsapiv1.FolderConfig, error) {
224 defer fcMutex.Unlock()
227 if _, exist := f.folders[newF.ID]; create && exist {
228 return nil, fmt.Errorf("ID already exists")
230 if newF.ClientPath == "" {
231 return nil, fmt.Errorf("ClientPath must be set")
234 // Create a new folder object
238 case xsapiv1.TypeCloudSync:
240 fld = NewFolderST(f.Context, f.SThg)
242 f.Log.Debugf("Disable project %v (syncthing not initialized)", newF.ID)
243 fld = NewFolderSTDisable(f.Context)
247 case xsapiv1.TypePathMap:
248 fld = NewFolderPathMap(f.Context)
250 return nil, fmt.Errorf("Unsupported folder type")
253 // Allocate a new UUID
255 newF.ID = fld.NewUID("")
257 if !create && newF.ID == "" {
258 return nil, fmt.Errorf("Cannot update folder with null ID")
261 // Set default value if needed
262 if newF.Status == "" {
263 newF.Status = xsapiv1.StatusDisable
265 if newF.Label == "" {
266 newF.Label = filepath.Base(newF.ClientPath)
267 if len(newF.ID) > 8 {
268 newF.Label += "_" + newF.ID[0:8]
272 // Normalize path (needed for Windows path including bashlashes)
273 newF.ClientPath = common.PathNormalize(newF.ClientPath)
275 var newFolder *xsapiv1.FolderConfig
278 if newFolder, err = fld.Add(newF); err != nil {
279 newF.Status = xsapiv1.StatusErrorConfig
280 log.Printf("ERROR Adding folder: %v\n", err)
281 return newFolder, err
284 // Just update project config
285 if newFolder, err = fld.Setup(newF); err != nil {
286 newF.Status = xsapiv1.StatusErrorConfig
287 log.Printf("ERROR Updating folder: %v\n", err)
288 return newFolder, err
292 // Add to folders list
293 f.folders[newF.ID] = &fld
295 // Save config on disk
297 if err := f.SaveConfig(); err != nil {
298 return newFolder, err
302 // Force sync after creation
303 // (need to defer to be sure that WS events will arrive after HTTP creation reply)
305 time.Sleep(time.Millisecond * 500)
309 return newFolder, nil
312 // Delete deletes a specific folder
313 func (f *Folders) Delete(id string) (xsapiv1.FolderConfig, error) {
317 defer fcMutex.Unlock()
319 fld := xsapiv1.FolderConfig{}
320 fc, exist := f.folders[id]
322 return fld, fmt.Errorf("unknown id")
325 fld = (*fc).GetConfig()
327 if err = (*fc).Remove(); err != nil {
331 delete(f.folders, id)
333 // Save config on disk
339 // Update Update a specific folder
340 func (f *Folders) Update(id string, cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
342 defer fcMutex.Unlock()
344 fc, exist := f.folders[id]
346 return nil, fmt.Errorf("unknown id")
349 // Copy current in a new object to change nothing in case of an error rises
350 newCfg := xsapiv1.FolderConfig{}
351 reflectme.Copy((*fc).GetConfig(), &newCfg)
353 // Only update some fields
355 for _, fieldName := range xsapiv1.FolderConfigUpdatableFields {
356 valNew, err := reflectme.GetField(cfg, fieldName)
358 valCur, err := reflectme.GetField(newCfg, fieldName)
359 if err == nil && valNew != valCur {
360 err = reflectme.SetField(&newCfg, fieldName, valNew)
373 fld, err := (*fc).Update(newCfg)
378 // Save config on disk
381 // Send event to notified changes
382 // TODO emit folder change event
387 // ForceSync Force the synchronization of a folder
388 func (f *Folders) ForceSync(id string) error {
391 return fmt.Errorf("Unknown id")
396 // IsFolderInSync Returns true when folder is in sync
397 func (f *Folders) IsFolderInSync(id string) (bool, error) {
400 return false, fmt.Errorf("Unknown id")
402 return (*fc).IsInSync()
405 //*** Private functions ***
407 // Use XML format and not json to be able to save/load all fields including
408 // ones that are masked in json (IOW defined with `json:"-"`)
409 type xmlFolders struct {
410 XMLName xml.Name `xml:"folders"`
411 Version string `xml:"version,attr"`
412 Folders []xsapiv1.FolderConfig `xml:"folders"`
415 // foldersConfigRead reads folders config from disk
416 func foldersConfigRead(file string, folders *[]xsapiv1.FolderConfig) error {
417 if !common.Exists(file) {
418 return fmt.Errorf("No folder config file found (%s)", file)
422 defer ffMutex.Unlock()
424 fd, err := os.Open(file)
431 err = xml.NewDecoder(fd).Decode(&data)
433 // Decode old type encoding (number) for backward compatibility
434 for i, d := range data.Folders {
437 data.Folders[i].Type = xsapiv1.TypePathMap
439 data.Folders[i].Type = xsapiv1.TypeCloudSync
441 data.Folders[i].Type = xsapiv1.TypeCifsSmb
445 *folders = data.Folders
450 // foldersConfigWrite writes folders config on disk
451 func foldersConfigWrite(file string, folders []xsapiv1.FolderConfig) error {
453 defer ffMutex.Unlock()
455 fd, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
466 enc := xml.NewEncoder(fd)
468 return enc.Encode(data)