12 "github.com/franciscocpg/reflectme"
13 common "github.com/iotbzh/xds-common/golib"
14 "github.com/iotbzh/xds-server/lib/xsapiv1"
15 "github.com/iotbzh/xds-server/lib/xdsconfig"
16 "github.com/syncthing/syncthing/lib/sync"
19 // Folders Represent a an XDS folders
23 folders map[string]*IFOLDER
24 registerCB []RegisteredCB
27 // RegisteredCB Hold registered callbacks
28 type RegisteredCB struct {
30 data *FolderEventCBData
33 // Mutex to make add/delete atomic
34 var fcMutex = sync.NewMutex()
35 var ffMutex = sync.NewMutex()
37 // FoldersNew Create a new instance of Model Folders
38 func FoldersNew(ctx *Context) *Folders {
39 file, _ := xdsconfig.FoldersConfigFilenameGet()
43 folders: make(map[string]*IFOLDER),
44 registerCB: []RegisteredCB{},
48 // LoadConfig Load folders configuration from disk
49 func (f *Folders) LoadConfig() error {
50 var flds []xsapiv1.FolderConfig
51 var stFlds []xsapiv1.FolderConfig
54 if f.Config.Options.NoFolderConfig {
55 f.Log.Infof("Don't read folder config file (-no-folderconfig option is set)")
56 } else if f.fileOnDisk != "" {
57 f.Log.Infof("Use folder config file: %s", f.fileOnDisk)
58 err := foldersConfigRead(f.fileOnDisk, &flds)
60 if strings.HasPrefix(err.Error(), "No folder config") {
61 f.Log.Warnf(err.Error())
67 f.Log.Warnf("Folders config filename not set")
70 // Retrieve initial Syncthing config (just append don't overwrite existing ones)
72 f.Log.Infof("Retrieve syncthing folder config")
73 if err := f.SThg.FolderLoadFromStConfig(&stFlds); err != nil {
74 // Don't exit on such error, just log it
75 f.Log.Errorf(err.Error())
78 f.Log.Infof("Syncthing support is disabled.")
81 // Merge syncthing folders into XDS folders
82 for _, stf := range stFlds {
84 for i, xf := range flds {
88 if xf.Type != xsapiv1.TypeCloudSync {
89 flds[i].Status = xsapiv1.StatusErrorConfig
96 flds = append(flds, stf)
100 // Detect ghost project
101 // (IOW existing in xds file config and not in syncthing database)
103 for i, xf := range flds {
104 // only for syncthing project
105 if xf.Type != xsapiv1.TypeCloudSync {
109 for _, stf := range stFlds {
116 flds[i].Status = xsapiv1.StatusErrorConfig
122 f.Log.Infof("Loading initial folders config: %d folders found", len(flds))
123 for _, fc := range flds {
124 if _, err := f.createUpdate(fc, false, true); err != nil {
129 // Save config on disk
130 err := f.SaveConfig()
135 // SaveConfig Save folders configuration to disk
136 func (f *Folders) SaveConfig() error {
137 if f.fileOnDisk == "" {
138 return fmt.Errorf("Folders config filename not set")
141 // FIXME: buffered save or avoid to write on disk each time
142 return foldersConfigWrite(f.fileOnDisk, f.getConfigArrUnsafe())
145 // ResolveID Complete a Folder ID (helper for user that can use partial ID value)
146 func (f *Folders) ResolveID(id string) (string, error) {
152 for iid := range f.folders {
153 if strings.HasPrefix(iid, id) {
154 match = append(match, iid)
160 } else if len(match) == 0 {
161 return id, fmt.Errorf("Unknown id")
163 return id, fmt.Errorf("Multiple IDs found with provided prefix: " + id)
166 // Get returns the folder config or nil if not existing
167 func (f *Folders) Get(id string) *IFOLDER {
171 fc, exist := f.folders[id]
178 // GetConfigArr returns the config of all folders as an array
179 func (f *Folders) GetConfigArr() []xsapiv1.FolderConfig {
181 defer fcMutex.Unlock()
183 return f.getConfigArrUnsafe()
186 // getConfigArrUnsafe Same as GetConfigArr without mutex protection
187 func (f *Folders) getConfigArrUnsafe() []xsapiv1.FolderConfig {
188 conf := []xsapiv1.FolderConfig{}
189 for _, v := range f.folders {
190 conf = append(conf, (*v).GetConfig())
195 // Add adds a new folder
196 func (f *Folders) Add(newF xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
197 return f.createUpdate(newF, true, false)
200 // CreateUpdate creates or update a folder
201 func (f *Folders) createUpdate(newF xsapiv1.FolderConfig, create bool, initial bool) (*xsapiv1.FolderConfig, error) {
204 defer fcMutex.Unlock()
207 if _, exist := f.folders[newF.ID]; create && exist {
208 return nil, fmt.Errorf("ID already exists")
210 if newF.ClientPath == "" {
211 return nil, fmt.Errorf("ClientPath must be set")
214 // Create a new folder object
218 case xsapiv1.TypeCloudSync:
220 fld = NewFolderST(f.Context, f.SThg)
222 f.Log.Debugf("Disable project %v (syncthing not initialized)", newF.ID)
223 fld = NewFolderSTDisable(f.Context)
227 case xsapiv1.TypePathMap:
228 fld = NewFolderPathMap(f.Context)
230 return nil, fmt.Errorf("Unsupported folder type")
233 // Allocate a new UUID
235 newF.ID = fld.NewUID("")
237 if !create && newF.ID == "" {
238 return nil, fmt.Errorf("Cannot update folder with null ID")
241 // Set default value if needed
242 if newF.Status == "" {
243 newF.Status = xsapiv1.StatusDisable
245 if newF.Label == "" {
246 newF.Label = filepath.Base(newF.ClientPath)
247 if len(newF.ID) > 8 {
248 newF.Label += "_" + newF.ID[0:8]
252 // Normalize path (needed for Windows path including bashlashes)
253 newF.ClientPath = common.PathNormalize(newF.ClientPath)
256 newFolder, err := fld.Add(newF)
258 newF.Status = xsapiv1.StatusErrorConfig
259 log.Printf("ERROR Adding folder: %v\n", err)
260 return newFolder, err
263 // Add to folders list
264 f.folders[newF.ID] = &fld
266 // Save config on disk
268 if err := f.SaveConfig(); err != nil {
269 return newFolder, err
273 // Register event change callback
274 for _, rcb := range f.registerCB {
275 if err := fld.RegisterEventChange(rcb.cb, rcb.data); err != nil {
276 return newFolder, err
280 // Force sync after creation
281 // (need to defer to be sure that WS events will arrive after HTTP creation reply)
283 time.Sleep(time.Millisecond * 500)
287 return newFolder, nil
290 // Delete deletes a specific folder
291 func (f *Folders) Delete(id string) (xsapiv1.FolderConfig, error) {
295 defer fcMutex.Unlock()
297 fld := xsapiv1.FolderConfig{}
298 fc, exist := f.folders[id]
300 return fld, fmt.Errorf("unknown id")
303 fld = (*fc).GetConfig()
305 if err = (*fc).Remove(); err != nil {
309 delete(f.folders, id)
311 // Save config on disk
317 // Update Update a specific folder
318 func (f *Folders) Update(id string, cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) {
320 defer fcMutex.Unlock()
322 fc, exist := f.folders[id]
324 return nil, fmt.Errorf("unknown id")
327 // Copy current in a new object to change nothing in case of an error rises
328 newCfg := xsapiv1.FolderConfig{}
329 reflectme.Copy((*fc).GetConfig(), &newCfg)
331 // Only update some fields
333 for _, fieldName := range xsapiv1.FolderConfigUpdatableFields {
334 valNew, err := reflectme.GetField(cfg, fieldName)
336 valCur, err := reflectme.GetField(newCfg, fieldName)
337 if err == nil && valNew != valCur {
338 err = reflectme.SetField(&newCfg, fieldName, valNew)
351 fld, err := (*fc).Update(newCfg)
356 // Save config on disk
359 // Send event to notified changes
360 // TODO emit folder change event
365 // RegisterEventChange requests registration for folder event change
366 func (f *Folders) RegisterEventChange(id string, cb *FolderEventCB, data *FolderEventCBData) error {
368 flds := make(map[string]*IFOLDER)
370 // Register to a specific folder
373 // Register to all folders
375 f.registerCB = append(f.registerCB, RegisteredCB{cb: cb, data: data})
378 for _, fld := range flds {
379 err := (*fld).RegisterEventChange(cb, data)
388 // ForceSync Force the synchronization of a folder
389 func (f *Folders) ForceSync(id string) error {
392 return fmt.Errorf("Unknown id")
397 // IsFolderInSync Returns true when folder is in sync
398 func (f *Folders) IsFolderInSync(id string) (bool, error) {
401 return false, fmt.Errorf("Unknown id")
403 return (*fc).IsInSync()
406 //*** Private functions ***
408 // Use XML format and not json to be able to save/load all fields including
409 // ones that are masked in json (IOW defined with `json:"-"`)
410 type xmlFolders struct {
411 XMLName xml.Name `xml:"folders"`
412 Version string `xml:"version,attr"`
413 Folders []xsapiv1.FolderConfig `xml:"folders"`
416 // foldersConfigRead reads folders config from disk
417 func foldersConfigRead(file string, folders *[]xsapiv1.FolderConfig) error {
418 if !common.Exists(file) {
419 return fmt.Errorf("No folder config file found (%s)", file)
423 defer ffMutex.Unlock()
425 fd, err := os.Open(file)
432 err = xml.NewDecoder(fd).Decode(&data)
434 *folders = data.Folders
439 // foldersConfigWrite writes folders config on disk
440 func foldersConfigWrite(file string, folders []xsapiv1.FolderConfig) error {
442 defer ffMutex.Unlock()
444 fd, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
455 enc := xml.NewEncoder(fd)
457 return enc.Encode(data)