Disambiguate shared calendars with a name colliding with the owner's
If a calendar shared with a user has the same name as one of the user's own calendars (or as another share from a different owner), DAVx5 would show two calendars with an identical, indistinguishable title. The shared calendar's CalDAV display name (Calendar.Name, exposed via displayname/DAVx5's calendar list) now gets the owning user's username appended in parentheses in that case, e.g. "personal (alice)" — the underlying calendar path/URL is unaffected, and a user's own calendars are never renamed (only shared ones can collide with something else). internal/caldav/backend.go: calendarMeta split into calendarMeta (own calendars, unchanged name) and calendarMetaNamed (explicit display name); new sharedDisplayName(requester, owner, realName) checks requester's own calendars and other shares for a collision. Wired into both ListCalendars and GetCalendar (the latter used by direct PROPFIND/REPORT against a shared calendar's own URL). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -136,7 +136,8 @@ func (b *Backend) ListCalendars(ctx context.Context) ([]caldav.Calendar, error)
|
||||
continue // owner's calendar no longer exists
|
||||
}
|
||||
localName := sharedCalendarName(sh.Owner, sh.CalendarName)
|
||||
cals = append(cals, b.calendarMeta(sh.Owner, sh.CalendarName, localName))
|
||||
displayName := b.sharedDisplayName(p.Username, sh.Owner, sh.CalendarName)
|
||||
cals = append(cals, b.calendarMetaNamed(sh.Owner, sh.CalendarName, localName, displayName))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +164,11 @@ func (b *Backend) GetCalendar(ctx context.Context, calPath string) (*caldav.Cale
|
||||
if _, err := b.store.GetCollection(owner, "cal-"+realName); err != nil {
|
||||
return nil, webdav.NewHTTPError(http.StatusNotFound, err)
|
||||
}
|
||||
cal := b.calendarMeta(owner, realName, localName)
|
||||
displayName := realName
|
||||
if owner != requester {
|
||||
displayName = b.sharedDisplayName(requester, owner, realName)
|
||||
}
|
||||
cal := b.calendarMetaNamed(owner, realName, localName, displayName)
|
||||
return &cal, nil
|
||||
}
|
||||
|
||||
@@ -369,6 +374,14 @@ func (b *Backend) resolveCalendar(requester, localName string, requireWrite bool
|
||||
}
|
||||
|
||||
func (b *Backend) calendarMeta(owner, realName, localName string) caldav.Calendar {
|
||||
return b.calendarMetaNamed(owner, realName, localName, realName)
|
||||
}
|
||||
|
||||
// calendarMetaNamed is calendarMeta with an explicit displayName, used for
|
||||
// a calendar shared with the requester whose name collides with one of
|
||||
// their own calendars (see sharedDisplayName) — the Description still
|
||||
// always names the real calendar, only the client-visible Name changes.
|
||||
func (b *Backend) calendarMetaNamed(owner, realName, localName, displayName string) caldav.Calendar {
|
||||
desc := fmt.Sprintf("%s's %s calendar", owner, realName)
|
||||
return caldav.Calendar{
|
||||
Path: calHomePath() + localName + "/",
|
||||
@@ -376,13 +389,46 @@ func (b *Backend) calendarMeta(owner, realName, localName string) caldav.Calenda
|
||||
// "owner~name" synthetic local name used internally to keep a
|
||||
// shared calendar's URL unique) — clients like DAVx5 show this as
|
||||
// the calendar's label, and "alice~work" looked confusing there.
|
||||
Name: realName,
|
||||
Name: displayName,
|
||||
Description: desc,
|
||||
SupportedComponentSet: []string{"VEVENT", "VTODO", "VJOURNAL"},
|
||||
MaxResourceSize: 10 * 1024 * 1024, // 10 MiB
|
||||
}
|
||||
}
|
||||
|
||||
// sharedDisplayName returns realName, suffixed with the owning user's
|
||||
// name in parentheses (e.g. "Work (bob)") if requester has another
|
||||
// calendar with the exact same display name — either one of their own,
|
||||
// or another share from a different owner — so DAVx5 (and other clients)
|
||||
// don't show two calendars with an identical, indistinguishable title.
|
||||
// Only called for calendars shared with requester; requester's own
|
||||
// calendars never need disambiguating against themselves.
|
||||
func (b *Backend) sharedDisplayName(requester, owner, realName string) string {
|
||||
own, err := b.dbase.ListCalendars(requester)
|
||||
if err != nil {
|
||||
b.logger.Warn("listing own calendars for disambiguation", "error", err)
|
||||
}
|
||||
for _, c := range own {
|
||||
if c.Name == realName {
|
||||
return fmt.Sprintf("%s (%s)", realName, owner)
|
||||
}
|
||||
}
|
||||
|
||||
shares, err := b.dbase.CalendarsSharedWith(requester)
|
||||
if err != nil {
|
||||
b.logger.Warn("listing shared calendars for disambiguation", "error", err)
|
||||
}
|
||||
for _, sh := range shares {
|
||||
if sh.Owner == owner && sh.CalendarName == realName {
|
||||
continue // the calendar itself, not a collision
|
||||
}
|
||||
if sh.CalendarName == realName {
|
||||
return fmt.Sprintf("%s (%s)", realName, owner)
|
||||
}
|
||||
}
|
||||
return realName
|
||||
}
|
||||
|
||||
func (b *Backend) decodeObject(objPath string, data []byte) (*caldav.CalendarObject, error) {
|
||||
// Some clients (Outlook/Exchange, some Thunderbird/Lightning setups)
|
||||
// write Windows timezone names into TZID instead of IANA ones, which
|
||||
|
||||
Reference in New Issue
Block a user