12 "github.com/Sirupsen/logrus"
13 common "github.com/iotbzh/xds-common/golib"
14 "github.com/iotbzh/xds-server/lib/folder"
15 "github.com/iotbzh/xds-server/lib/syncthing"
16 "github.com/iotbzh/xds-server/lib/xdsconfig"
17 "github.com/syncthing/syncthing/lib/sync"
20 // Folders Represent a an XDS folders
23 Conf *xdsconfig.Config
26 folders map[string]*folder.IFOLDER
27 registerCB []RegisteredCB
30 type RegisteredCB struct {
32 data *folder.EventCBData
35 // Mutex to make add/delete atomic
36 var fcMutex = sync.NewMutex()
37 var ffMutex = sync.NewMutex()
39 // FoldersNew Create a new instance of Model Folders
40 func FoldersNew(cfg *xdsconfig.Config, st *st.SyncThing) *Folders {
41 file, _ := xdsconfig.FoldersConfigFilenameGet()
47 folders: make(map[string]*folder.IFOLDER),
48 registerCB: []RegisteredCB{},
52 // LoadConfig Load folders configuration from disk
53 func (f *Folders) LoadConfig() error {
54 var flds []folder.FolderConfig
55 var stFlds []folder.FolderConfig
58 if f.Conf.Options.NoFolderConfig {
59 f.Log.Infof("Don't read folder config file (-no-folderconfig option is set)")
60 } else if f.fileOnDisk != "" {
61 f.Log.Infof("Use folder config file: %s", f.fileOnDisk)
62 err := foldersConfigRead(f.fileOnDisk, &flds)
64 if strings.HasPrefix(err.Error(), "No folder config") {
65 f.Log.Warnf(err.Error())
71 f.Log.Warnf("Folders config filename not set")
74 // Retrieve initial Syncthing config (just append don't overwrite existing ones)
76 f.Log.Infof("Retrieve syncthing folder config")
77 if err := f.SThg.FolderLoadFromStConfig(&stFlds); err != nil {
78 // Don't exit on such error, just log it
79 f.Log.Errorf(err.Error())
82 f.Log.Infof("Syncthing support is disabled.")
85 // Merge syncthing folders into XDS folders
86 for _, stf := range stFlds {
88 for i, xf := range flds {
92 if xf.Type != folder.TypeCloudSync {
93 flds[i].Status = folder.StatusErrorConfig
100 flds = append(flds, stf)
104 // Detect ghost project
105 // (IOW existing in xds file config and not in syncthing database)
107 for i, xf := range flds {
108 // only for syncthing project
109 if xf.Type != folder.TypeCloudSync {
113 for _, stf := range stFlds {
120 flds[i].Status = folder.StatusErrorConfig
126 f.Log.Infof("Loading initial folders config: %d folders found", len(flds))
127 for _, fc := range flds {
128 if _, err := f.createUpdate(fc, false, true); err != nil {
133 // Save config on disk
134 err := f.SaveConfig()
139 // SaveConfig Save folders configuration to disk
140 func (f *Folders) SaveConfig() error {
141 if f.fileOnDisk == "" {
142 return fmt.Errorf("Folders config filename not set")
145 // FIXME: buffered save or avoid to write on disk each time
146 return foldersConfigWrite(f.fileOnDisk, f.getConfigArrUnsafe())
149 // ResolveID Complete a Folder ID (helper for user that can use partial ID value)
150 func (f *Folders) ResolveID(id string) (string, error) {
156 for iid := range f.folders {
157 if strings.HasPrefix(iid, id) {
158 match = append(match, iid)
164 } else if len(match) == 0 {
165 return id, fmt.Errorf("Unknown id")
167 return id, fmt.Errorf("Multiple IDs found with provided prefix: " + id)
170 // Get returns the folder config or nil if not existing
171 func (f *Folders) Get(id string) *folder.IFOLDER {
175 fc, exist := f.folders[id]
182 // GetConfigArr returns the config of all folders as an array
183 func (f *Folders) GetConfigArr() []folder.FolderConfig {
185 defer fcMutex.Unlock()
187 return f.getConfigArrUnsafe()
190 // getConfigArrUnsafe Same as GetConfigArr without mutex protection
191 func (f *Folders) getConfigArrUnsafe() []folder.FolderConfig {
192 conf := []folder.FolderConfig{}
193 for _, v := range f.folders {
194 conf = append(conf, (*v).GetConfig())
199 // Add adds a new folder
200 func (f *Folders) Add(newF folder.FolderConfig) (*folder.FolderConfig, error) {
201 return f.createUpdate(newF, true, false)
204 // CreateUpdate creates or update a folder
205 func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bool) (*folder.FolderConfig, error) {
208 defer fcMutex.Unlock()
211 if _, exist := f.folders[newF.ID]; create && exist {
212 return nil, fmt.Errorf("ID already exists")
214 if newF.ClientPath == "" {
215 return nil, fmt.Errorf("ClientPath must be set")
218 // Create a new folder object
219 var fld folder.IFOLDER
222 case folder.TypeCloudSync:
224 fld = f.SThg.NewFolderST(f.Conf)
226 f.Log.Debugf("Disable project %v (syncthing not initialized)", newF.ID)
227 fld = folder.NewFolderSTDisable(f.Conf)
231 case folder.TypePathMap:
232 fld = folder.NewFolderPathMap(f.Conf)
234 return nil, fmt.Errorf("Unsupported folder type")
237 // Allocate a new UUID
239 newF.ID = fld.NewUID("")
241 if !create && newF.ID == "" {
242 return nil, fmt.Errorf("Cannot update folder with null ID")
245 // Set default value if needed
246 if newF.Status == "" {
247 newF.Status = folder.StatusDisable
249 if newF.Label == "" {
250 newF.Label = filepath.Base(newF.ClientPath)
251 if len(newF.ID) > 8 {
252 newF.Label += "_" + newF.ID[0:8]
256 // Normalize path (needed for Windows path including bashlashes)
257 newF.ClientPath = common.PathNormalize(newF.ClientPath)
260 newFolder, err := fld.Add(newF)
262 newF.Status = folder.StatusErrorConfig
263 log.Printf("ERROR Adding folder: %v\n", err)
264 return newFolder, err
267 // Add to folders list
268 f.folders[newF.ID] = &fld
270 // Save config on disk
272 if err := f.SaveConfig(); err != nil {
273 return newFolder, err
277 // Register event change callback
278 for _, rcb := range f.registerCB {
279 if err := fld.RegisterEventChange(rcb.cb, rcb.data); err != nil {
280 return newFolder, err
284 // Force sync after creation
285 // (need to defer to be sure that WS events will arrive after HTTP creation reply)
287 time.Sleep(time.Millisecond * 500)
291 return newFolder, nil
294 // Delete deletes a specific folder
295 func (f *Folders) Delete(id string) (folder.FolderConfig, error) {
299 defer fcMutex.Unlock()
301 fld := folder.FolderConfig{}
302 fc, exist := f.folders[id]
304 return fld, fmt.Errorf("unknown id")
307 fld = (*fc).GetConfig()
309 if err = (*fc).Remove(); err != nil {
313 delete(f.folders, id)
315 // Save config on disk
321 // RegisterEventChange requests registration for folder event change
322 func (f *Folders) RegisterEventChange(id string, cb *folder.EventCB, data *folder.EventCBData) error {
324 flds := make(map[string]*folder.IFOLDER)
326 // Register to a specific folder
329 // Register to all folders
331 f.registerCB = append(f.registerCB, RegisteredCB{cb: cb, data: data})
334 for _, fld := range flds {
335 err := (*fld).RegisterEventChange(cb, data)
344 // ForceSync Force the synchronization of a folder
345 func (f *Folders) ForceSync(id string) error {
348 return fmt.Errorf("Unknown id")
353 // IsFolderInSync Returns true when folder is in sync
354 func (f *Folders) IsFolderInSync(id string) (bool, error) {
357 return false, fmt.Errorf("Unknown id")
359 return (*fc).IsInSync()
362 //*** Private functions ***
364 // Use XML format and not json to be able to save/load all fields including
365 // ones that are masked in json (IOW defined with `json:"-"`)
366 type xmlFolders struct {
367 XMLName xml.Name `xml:"folders"`
368 Version string `xml:"version,attr"`
369 Folders []folder.FolderConfig `xml:"folders"`
372 // foldersConfigRead reads folders config from disk
373 func foldersConfigRead(file string, folders *[]folder.FolderConfig) error {
374 if !common.Exists(file) {
375 return fmt.Errorf("No folder config file found (%s)", file)
379 defer ffMutex.Unlock()
381 fd, err := os.Open(file)
388 err = xml.NewDecoder(fd).Decode(&data)
390 *folders = data.Folders
395 // foldersConfigWrite writes folders config on disk
396 func foldersConfigWrite(file string, folders []folder.FolderConfig) error {
398 defer ffMutex.Unlock()
400 fd, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
411 enc := xml.NewEncoder(fd)
413 return enc.Encode(data)