11 "github.com/Sirupsen/logrus"
12 common "github.com/iotbzh/xds-common/golib"
13 "github.com/iotbzh/xds-server/lib/folder"
14 "github.com/iotbzh/xds-server/lib/syncthing"
15 "github.com/iotbzh/xds-server/lib/xdsconfig"
16 uuid "github.com/satori/go.uuid"
17 "github.com/syncthing/syncthing/lib/sync"
20 // Folders Represent a an XDS folders
23 Conf *xdsconfig.Config
26 folders map[string]*folder.IFOLDER
29 // Mutex to make add/delete atomic
30 var fcMutex = sync.NewMutex()
31 var ffMutex = sync.NewMutex()
33 // FoldersNew Create a new instance of Model Folders
34 func FoldersNew(cfg *xdsconfig.Config, st *st.SyncThing) *Folders {
35 file, _ := xdsconfig.FoldersConfigFilenameGet()
41 folders: make(map[string]*folder.IFOLDER),
45 // LoadConfig Load folders configuration from disk
46 func (f *Folders) LoadConfig() error {
47 var flds []folder.FolderConfig
48 var stFlds []folder.FolderConfig
51 if f.Conf.Options.NoFolderConfig {
52 f.Log.Infof("Don't read folder config file (-no-folderconfig option is set)")
53 } else if f.fileOnDisk != "" {
54 f.Log.Infof("Use folder config file: %s", f.fileOnDisk)
55 err := foldersConfigRead(f.fileOnDisk, &flds)
57 if strings.HasPrefix(err.Error(), "No folder config") {
58 f.Log.Warnf(err.Error())
64 f.Log.Warnf("Folders config filename not set")
67 // Retrieve initial Syncthing config (just append don't overwrite existing ones)
69 f.Log.Infof("Retrieve syncthing folder config")
70 if err := f.SThg.FolderLoadFromStConfig(&stFlds); err != nil {
71 // Don't exit on such error, just log it
72 f.Log.Errorf(err.Error())
76 // Merge syncthing folders into XDS folders
77 for _, stf := range stFlds {
79 for i, xf := range flds {
83 if xf.Type != folder.TypeCloudSync {
84 flds[i].Status = folder.StatusErrorConfig
91 flds = append(flds, stf)
95 // Detect ghost project
96 // (IOW existing in xds file config and not in syncthing database)
97 for i, xf := range flds {
98 // only for syncthing project
99 if xf.Type != folder.TypeCloudSync {
103 for _, stf := range stFlds {
110 flds[i].Status = folder.StatusErrorConfig
115 f.Log.Infof("Loading initial folders config: %d folders found", len(flds))
116 for _, fc := range flds {
117 if _, err := f.createUpdate(fc, false); err != nil {
125 // SaveConfig Save folders configuration to disk
126 func (f *Folders) SaveConfig() error {
127 if f.fileOnDisk == "" {
128 return fmt.Errorf("Folders config filename not set")
131 // FIXME: buffered save or avoid to write on disk each time
132 return foldersConfigWrite(f.fileOnDisk, f.getConfigArrUnsafe())
135 // Get returns the folder config or nil if not existing
136 func (f *Folders) Get(id string) *folder.IFOLDER {
140 fc, exist := f.folders[id]
147 // GetConfigArr returns the config of all folders as an array
148 func (f *Folders) GetConfigArr() []folder.FolderConfig {
150 defer fcMutex.Unlock()
152 return f.getConfigArrUnsafe()
155 // getConfigArrUnsafe Same as GetConfigArr without mutex protection
156 func (f *Folders) getConfigArrUnsafe() []folder.FolderConfig {
157 var conf []folder.FolderConfig
159 for _, v := range f.folders {
160 conf = append(conf, (*v).GetConfig())
165 // Add adds a new folder
166 func (f *Folders) Add(newF folder.FolderConfig) (*folder.FolderConfig, error) {
167 return f.createUpdate(newF, true)
170 // CreateUpdate creates or update a folder
171 func (f *Folders) createUpdate(newF folder.FolderConfig, create bool) (*folder.FolderConfig, error) {
174 defer fcMutex.Unlock()
177 if _, exist := f.folders[newF.ID]; create && exist {
178 return nil, fmt.Errorf("ID already exists")
180 if newF.ClientPath == "" {
181 return nil, fmt.Errorf("ClientPath must be set")
184 // Allocate a new UUID
186 newF.ID = uuid.NewV1().String()
188 if !create && newF.ID == "" {
189 return nil, fmt.Errorf("Cannot update folder with null ID")
192 // Set default value if needed
193 if newF.Status == "" {
194 newF.Status = folder.StatusDisable
197 if newF.Label == "" {
198 newF.Label = filepath.Base(newF.ClientPath) + "_" + newF.ID[0:8]
201 var fld folder.IFOLDER
204 case folder.TypeCloudSync:
206 return nil, fmt.Errorf("ClownSync type not supported (syncthing not initialized)")
208 fld = f.SThg.NewFolderST(f.Conf)
210 case folder.TypePathMap:
211 fld = folder.NewFolderPathMap(f.Conf)
213 return nil, fmt.Errorf("Unsupported folder type")
216 // Normalize path (needed for Windows path including bashlashes)
217 newF.ClientPath = common.PathNormalize(newF.ClientPath)
220 newFolder, err := fld.Add(newF)
222 newF.Status = folder.StatusErrorConfig
223 log.Printf("ERROR Adding folder: %v\n", err)
224 return newFolder, err
227 // Register folder object
228 f.folders[newF.ID] = &fld
230 // Save config on disk
233 return newFolder, err
236 // Delete deletes a specific folder
237 func (f *Folders) Delete(id string) (folder.FolderConfig, error) {
241 defer fcMutex.Unlock()
243 fld := folder.FolderConfig{}
244 fc, exist := f.folders[id]
246 return fld, fmt.Errorf("unknown id")
249 fld = (*fc).GetConfig()
251 if err = (*fc).Remove(); err != nil {
255 delete(f.folders, id)
257 // Save config on disk
263 // ForceSync Force the synchronization of a folder
264 func (f *Folders) ForceSync(id string) error {
267 return fmt.Errorf("Unknown id")
272 // IsFolderInSync Returns true when folder is in sync
273 func (f *Folders) IsFolderInSync(id string) (bool, error) {
276 return false, fmt.Errorf("Unknown id")
278 return (*fc).IsInSync()
281 //*** Private functions ***
283 // Use XML format and not json to be able to save/load all fields including
284 // ones that are masked in json (IOW defined with `json:"-"`)
285 type xmlFolders struct {
286 XMLName xml.Name `xml:"folders"`
287 Version string `xml:"version,attr"`
288 Folders []folder.FolderConfig `xml:"folders"`
291 // foldersConfigRead reads folders config from disk
292 func foldersConfigRead(file string, folders *[]folder.FolderConfig) error {
293 if !common.Exists(file) {
294 return fmt.Errorf("No folder config file found (%s)", file)
298 defer ffMutex.Unlock()
300 fd, err := os.Open(file)
307 err = xml.NewDecoder(fd).Decode(&data)
309 *folders = data.Folders
314 // foldersConfigWrite writes folders config on disk
315 func foldersConfigWrite(file string, folders []folder.FolderConfig) error {
317 defer ffMutex.Unlock()
319 fd, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
330 enc := xml.NewEncoder(fd)
332 return enc.Encode(data)