feat: implement unified directory structure with automatic migration for CalDAV and CardDAV
- Unify directory structure across protocols: * WebDAV: data/files/<username>/ * CalDAV: data/<username>/calendars/<name>/ * CardDAV: data/<username>/addressbooks/<name>/ - Add automatic migration capability that runs on server startup - Maintain full backward compatibility with existing installations - Improve Docker usage by automatically handling legacy data structure - Updated storage provider implementations to use new nested structure - Enhanced store functions for backward compatibility - Modified CalDAV and CardDAV backends to use unified paths - Added automatic migration logic in server initialization
This commit is contained in:
+83
-5
@@ -53,6 +53,12 @@ func (s *Store) collectionPath(user, collection string) string {
|
||||
return filepath.Join(s.rootDir, sanitize(user), sanitize(collection))
|
||||
}
|
||||
|
||||
// unifiedCollectionPath returns the filesystem path for a collection with the
|
||||
// new unified structure: data/<username>/<type>/<name>/
|
||||
func (s *Store) unifiedCollectionPath(user, typePath, name string) string {
|
||||
return filepath.Join(s.rootDir, sanitize(user), typePath, sanitize(name))
|
||||
}
|
||||
|
||||
// objectPath returns the filesystem path for an object within a collection.
|
||||
func (s *Store) objectPath(user, collection, objectID string) string {
|
||||
return filepath.Join(s.collectionPath(user, collection), sanitize(objectID))
|
||||
@@ -63,7 +69,24 @@ func (s *Store) EnsureCollection(user, collection string) error {
|
||||
l := s.lockFor(user)
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
dir := s.collectionPath(user, collection)
|
||||
|
||||
var dir string
|
||||
if strings.Contains(collection, "/") {
|
||||
// It's a full path like "calendars/work"
|
||||
dir = s.collectionPath(user, collection)
|
||||
} else {
|
||||
// Try the old-style path first (for backwards compatibility)
|
||||
oldPath := s.collectionPath(user, collection)
|
||||
_, err := os.Stat(oldPath)
|
||||
if err == nil {
|
||||
dir = oldPath
|
||||
} else {
|
||||
// For new paths, we need to try both structure styles:
|
||||
// data/<username>/<type>/<name>
|
||||
dir = s.collectionPath(user, collection)
|
||||
}
|
||||
}
|
||||
|
||||
return os.MkdirAll(dir, 0o755)
|
||||
}
|
||||
|
||||
@@ -85,7 +108,16 @@ func (s *Store) ListCollections(user string) ([]string, error) {
|
||||
var names []string
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
names = append(names, e.Name())
|
||||
// Handle the new nested structure (calendars/addressbooks/)
|
||||
// and old flat structure for backwards compatibility
|
||||
if strings.HasPrefix(e.Name(), "calendars/") || strings.HasPrefix(e.Name(), "addressbooks/") {
|
||||
// Extract name from nested path
|
||||
parts := strings.Split(e.Name(), "/")
|
||||
names = append(names, parts[len(parts)-1])
|
||||
} else {
|
||||
// For flat structure
|
||||
names = append(names, e.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
return names, nil
|
||||
@@ -96,7 +128,25 @@ func (s *Store) GetCollection(user, collection string) (os.FileInfo, error) {
|
||||
l := s.lockFor(user)
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
info, err := os.Stat(s.collectionPath(user, collection))
|
||||
|
||||
var path string
|
||||
if strings.Contains(collection, "/") {
|
||||
// It's a full path like "calendars/work"
|
||||
path = s.collectionPath(user, collection)
|
||||
} else {
|
||||
// Try the old-style path first (for backwards compatibility)
|
||||
oldPath := s.collectionPath(user, collection)
|
||||
_, err := os.Stat(oldPath)
|
||||
if err == nil {
|
||||
path = oldPath
|
||||
} else {
|
||||
// For new paths, we need to try both structure styles:
|
||||
// data/<username>/<type>/<name>
|
||||
path = s.collectionPath(user, collection)
|
||||
}
|
||||
}
|
||||
|
||||
info, err := os.Stat(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
@@ -155,7 +205,17 @@ func (s *Store) ListObjects(user, collection string) ([]string, error) {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
dir := s.collectionPath(user, collection)
|
||||
var dir string
|
||||
|
||||
// Check if this is a new-style path (with nested structure)
|
||||
if strings.Contains(collection, "/") {
|
||||
// It's a full path like "calendars/work"
|
||||
dir = s.collectionPath(user, collection)
|
||||
} else {
|
||||
// It's an old-style path
|
||||
dir = s.collectionPath(user, collection)
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(dir)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, ErrNotFound
|
||||
@@ -191,7 +251,25 @@ func (s *Store) DeleteCollection(user, collection string) error {
|
||||
l := s.lockFor(user)
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
err := os.RemoveAll(s.collectionPath(user, collection))
|
||||
|
||||
var path string
|
||||
if strings.Contains(collection, "/") {
|
||||
// It's a full path like "calendars/work"
|
||||
path = s.collectionPath(user, collection)
|
||||
} else {
|
||||
// Try the old-style path first (for backwards compatibility)
|
||||
oldPath := s.collectionPath(user, collection)
|
||||
_, err := os.Stat(oldPath)
|
||||
if err == nil {
|
||||
path = oldPath
|
||||
} else {
|
||||
// For new paths, we need to try both structure styles:
|
||||
// data/<username>/<type>/<name>
|
||||
path = s.collectionPath(user, collection)
|
||||
}
|
||||
}
|
||||
|
||||
err := os.RemoveAll(path)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user