12 "github.com/Sirupsen/logrus"
13 "github.com/franciscocpg/reflectme"
14 common "github.com/iotbzh/xds-common/golib"
15 "github.com/iotbzh/xds-server/lib/folder"
16 "github.com/iotbzh/xds-server/lib/syncthing"
17 "github.com/iotbzh/xds-server/lib/xdsconfig"
18 "github.com/syncthing/syncthing/lib/sync"
21 // Folders Represent a an XDS folders
24 Conf *xdsconfig.Config
27 folders map[string]*folder.IFOLDER
28 registerCB []RegisteredCB
31 type RegisteredCB struct {
33 data *folder.EventCBData
36 // Mutex to make add/delete atomic
37 var fcMutex = sync.NewMutex()
38 var ffMutex = sync.NewMutex()
40 // FoldersNew Create a new instance of Model Folders
41 func FoldersNew(cfg *xdsconfig.Config, st *st.SyncThing) *Folders {
42 file, _ := xdsconfig.FoldersConfigFilenameGet()
48 folders: make(map[string]*folder.IFOLDER),
49 registerCB: []RegisteredCB{},
53 // LoadConfig Load folders configuration from disk
54 func (f *Folders) LoadConfig() error {
55 var flds []folder.FolderConfig
56 var stFlds []folder.FolderConfig
59 if f.Conf.Options.NoFolderConfig {
60 f.Log.Infof("Don't read folder config file (-no-folderconfig option is set)")
61 } else if f.fileOnDisk != "" {
62 f.Log.Infof("Use folder config file: %s", f.fileOnDisk)
63 err := foldersConfigRead(f.fileOnDisk, &flds)
65 if strings.HasPrefix(err.Error(), "No folder config") {
66 f.Log.Warnf(err.Error())
72 f.Log.Warnf("Folders config filename not set")
75 // Retrieve initial Syncthing config (just append don't overwrite existing ones)
77 f.Log.Infof("Retrieve syncthing folder config")
78 if err := f.SThg.FolderLoadFromStConfig(&stFlds); err != nil {
79 // Don't exit on such error, just log it
80 f.Log.Errorf(err.Error())
83 f.Log.Infof("Syncthing support is disabled.")
86 // Merge syncthing folders into XDS folders
87 for _, stf := range stFlds {
89 for i, xf := range flds {
93 if xf.Type != folder.TypeCloudSync {
94 flds[i].Status = folder.StatusErrorConfig
101 flds = append(flds, stf)
105 // Detect ghost project
106 // (IOW existing in xds file config and not in syncthing database)
108 for i, xf := range flds {
109 // only for syncthing project
110 if xf.Type != folder.TypeCloudSync {
114 for _, stf := range stFlds {
121 flds[i].Status = folder.StatusErrorConfig
127 f.Log.Infof("Loading initial folders config: %d folders found", len(flds))
128 for _, fc := range flds {
129 if _, err := f.createUpdate(fc, false, true); err != nil {
134 // Save config on disk
135 err := f.SaveConfig()
140 // SaveConfig Save folders configuration to disk
141 func (f *Folders) SaveConfig() error {
142 if f.fileOnDisk == "" {
143 return fmt.Errorf("Folders config filename not set")
146 // FIXME: buffered save or avoid to write on disk each time
147 return foldersConfigWrite(f.fileOnDisk, f.getConfigArrUnsafe())
150 // ResolveID Complete a Folder ID (helper for user that can use partial ID value)
151 func (f *Folders) ResolveID(id string) (string, error) {
157 for iid := range f.folders {
158 if strings.HasPrefix(iid, id) {
159 match = append(match, iid)
165 } else if len(match) == 0 {
166 return id, fmt.Errorf("Unknown id")
168 return id, fmt.Errorf("Multiple IDs found with provided prefix: " + id)
171 // Get returns the folder config or nil if not existing
172 func (f *Folders) Get(id string) *folder.IFOLDER {
176 fc, exist := f.folders[id]
183 // GetConfigArr returns the config of all folders as an array
184 func (f *Folders) GetConfigArr() []folder.FolderConfig {
186 defer fcMutex.Unlock()
188 return f.getConfigArrUnsafe()
191 // getConfigArrUnsafe Same as GetConfigArr without mutex protection
192 func (f *Folders) getConfigArrUnsafe() []folder.FolderConfig {
193 conf := []folder.FolderConfig{}
194 for _, v := range f.folders {
195 conf = append(conf, (*v).GetConfig())
200 // Add adds a new folder
201 func (f *Folders) Add(newF folder.FolderConfig) (*folder.FolderConfig, error) {
202 return f.createUpdate(newF, true, false)
205 // CreateUpdate creates or update a folder
206 func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bool) (*folder.FolderConfig, error) {
209 defer fcMutex.Unlock()
212 if _, exist := f.folders[newF.ID]; create && exist {
213 return nil, fmt.Errorf("ID already exists")
215 if newF.ClientPath == "" {
216 return nil, fmt.Errorf("ClientPath must be set")
219 // Create a new folder object
220 var fld folder.IFOLDER
223 case folder.TypeCloudSync:
225 fld = f.SThg.NewFolderST(f.Conf)
227 f.Log.Debugf("Disable project %v (syncthing not initialized)", newF.ID)
228 fld = folder.NewFolderSTDisable(f.Conf)
232 case folder.TypePathMap:
233 fld = folder.NewFolderPathMap(f.Conf)
235 return nil, fmt.Errorf("Unsupported folder type")
238 // Allocate a new UUID
240 newF.ID = fld.NewUID("")
242 if !create && newF.ID == "" {
243 return nil, fmt.Errorf("Cannot update folder with null ID")
246 // Set default value if needed
247 if newF.Status == "" {
248 newF.Status = folder.StatusDisable
250 if newF.Label == "" {
251 newF.Label = filepath.Base(newF.ClientPath)
252 if len(newF.ID) > 8 {
253 newF.Label += "_" + newF.ID[0:8]
257 // Normalize path (needed for Windows path including bashlashes)
258 newF.ClientPath = common.PathNormalize(newF.ClientPath)
261 newFolder, err := fld.Add(newF)
263 newF.Status = folder.StatusErrorConfig
264 log.Printf("ERROR Adding folder: %v\n", err)
265 return newFolder, err
268 // Add to folders list
269 f.folders[newF.ID] = &fld
271 // Save config on disk
273 if err := f.SaveConfig(); err != nil {
274 return newFolder, err
278 // Register event change callback
279 for _, rcb := range f.registerCB {
280 if err := fld.RegisterEventChange(rcb.cb, rcb.data); err != nil {
281 return newFolder, err
285 // Force sync after creation
286 // (need to defer to be sure that WS events will arrive after HTTP creation reply)
288 time.Sleep(time.Millisecond * 500)
292 return newFolder, nil
295 // Delete deletes a specific folder
296 func (f *Folders) Delete(id string) (folder.FolderConfig, error) {
300 defer fcMutex.Unlock()
302 fld := folder.FolderConfig{}
303 fc, exist := f.folders[id]
305 return fld, fmt.Errorf("unknown id")
308 fld = (*fc).GetConfig()
310 if err = (*fc).Remove(); err != nil {
314 delete(f.folders, id)
316 // Save config on disk
322 // Update Update a specific folder
323 func (f *Folders) Update(id string, cfg folder.FolderConfig) (*folder.FolderConfig, error) {
325 defer fcMutex.Unlock()
327 fc, exist := f.folders[id]
329 return nil, fmt.Errorf("unknown id")
332 // Copy current in a new object to change nothing in case of an error rises
333 newCfg := folder.FolderConfig{}
334 reflectme.Copy((*fc).GetConfig(), &newCfg)
336 // Only update some fields
338 for _, fieldName := range folder.FolderConfigUpdatableFields {
339 valNew, err := reflectme.GetField(cfg, fieldName)
341 valCur, err := reflectme.GetField(newCfg, fieldName)
342 if err == nil && valNew != valCur {
343 err = reflectme.SetField(&newCfg, fieldName, valNew)
356 fld, err := (*fc).Update(newCfg)
361 // Save config on disk
364 // Send event to notified changes
365 // TODO emit folder change event
370 // RegisterEventChange requests registration for folder event change
371 func (f *Folders) RegisterEventChange(id string, cb *folder.EventCB, data *folder.EventCBData) error {
373 flds := make(map[string]*folder.IFOLDER)
375 // Register to a specific folder
378 // Register to all folders
380 f.registerCB = append(f.registerCB, RegisteredCB{cb: cb, data: data})
383 for _, fld := range flds {
384 err := (*fld).RegisterEventChange(cb, data)
393 // ForceSync Force the synchronization of a folder
394 func (f *Folders) ForceSync(id string) error {
397 return fmt.Errorf("Unknown id")
402 // IsFolderInSync Returns true when folder is in sync
403 func (f *Folders) IsFolderInSync(id string) (bool, error) {
406 return false, fmt.Errorf("Unknown id")
408 return (*fc).IsInSync()
411 //*** Private functions ***
413 // Use XML format and not json to be able to save/load all fields including
414 // ones that are masked in json (IOW defined with `json:"-"`)
415 type xmlFolders struct {
416 XMLName xml.Name `xml:"folders"`
417 Version string `xml:"version,attr"`
418 Folders []folder.FolderConfig `xml:"folders"`
421 // foldersConfigRead reads folders config from disk
422 func foldersConfigRead(file string, folders *[]folder.FolderConfig) error {
423 if !common.Exists(file) {
424 return fmt.Errorf("No folder config file found (%s)", file)
428 defer ffMutex.Unlock()
430 fd, err := os.Open(file)
437 err = xml.NewDecoder(fd).Decode(&data)
439 *folders = data.Folders
444 // foldersConfigWrite writes folders config on disk
445 func foldersConfigWrite(file string, folders []folder.FolderConfig) error {
447 defer ffMutex.Unlock()
449 fd, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
460 enc := xml.NewEncoder(fd)
462 return enc.Encode(data)