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:
2026-08-28 14:22:19 +02:00
parent f463c01f0f
commit 2222038637
8 changed files with 212 additions and 25 deletions
+6 -6
View File
@@ -74,7 +74,7 @@ func (b *Backend) ListAddressBooks(ctx context.Context) ([]carddav.AddressBook,
var books []carddav.AddressBook
for _, name := range names {
if err := b.store.EnsureCollection(p.Username, "card-"+name); err != nil {
if err := b.store.EnsureCollection(p.Username, "addressbooks/"+name); err != nil {
b.logger.Warn("ensuring address book directory", "book", name, "error", err)
continue
}
@@ -85,7 +85,7 @@ func (b *Backend) ListAddressBooks(ctx context.Context) ([]carddav.AddressBook,
disk, _ := b.store.ListCollections(p.Username)
configured := make(map[string]bool)
for _, n := range names {
configured["card-"+n] = true
configured["addressbooks/"+n] = true
}
for _, dir := range disk {
if strings.HasPrefix(dir, "card-") && !configured[dir] {
@@ -101,7 +101,7 @@ func (b *Backend) ListAddressBooks(ctx context.Context) ([]carddav.AddressBook,
b.logger.Warn("listing shared address books", "error", err)
}
for _, sh := range shares {
if _, err := b.store.GetCollection(sh.Owner, "card-"+sh.AddressBookName); err != nil {
if _, err := b.store.GetCollection(sh.Owner, "addressbooks/"+sh.AddressBookName); err != nil {
continue // owner's address book no longer exists
}
localName := sharedBookName(sh.Owner, sh.AddressBookName)
@@ -138,7 +138,7 @@ func (b *Backend) GetAddressObject(ctx context.Context, objPath string, req *car
return nil, err
}
data, err := b.store.GetObject(owner, "card-"+realName, objID)
data, err := b.store.GetObject(owner, "addressbooks/"+realName, objID)
if err != nil {
return nil, webdav.NewHTTPError(http.StatusNotFound, err)
}
@@ -156,7 +156,7 @@ func (b *Backend) ListAddressObjects(ctx context.Context, bookPath string, req *
return nil, err
}
ids, err := b.store.ListObjects(owner, "card-"+realName)
ids, err := b.store.ListObjects(owner, "addressbooks/"+realName)
if err != nil {
return nil, err
}
@@ -209,7 +209,7 @@ func (b *Backend) DeleteAddressBook(ctx context.Context, bookPath string) error
if err := b.dbase.DeleteAddressBook(owner, realName); err != nil && err != db.ErrResourceNotFound {
return fmt.Errorf("unregistering address book: %w", err)
}
return b.store.DeleteCollection(owner, "card-"+realName)
return b.store.DeleteCollection(owner, "addressbooks/"+realName)
}
func (b *Backend) PutAddressObject(ctx context.Context, objPath string, card vcard.Card, opts *carddav.PutAddressObjectOptions) (*carddav.AddressObject, error) {