diff --git a/caldav/server.go b/caldav/server.go
index bb86925..c67b3cb 100644
--- a/caldav/server.go
+++ b/caldav/server.go
@@ -17,8 +17,6 @@ import (
"github.com/emersion/go-webdav/internal"
)
-// TODO: add support for multiple calendars
-
// TODO if nothing more Caldav-specific needs to be added this should be merged with carddav.PutAddressObjectOptions
type PutCalendarObjectOptions struct {
// IfNoneMatch indicates that the client does not want to overwrite
@@ -32,9 +30,10 @@ type PutCalendarObjectOptions struct {
// Backend is a CalDAV server backend.
type Backend interface {
CalendarHomeSetPath(ctx context.Context) (string, error)
- Calendar(ctx context.Context) (*Calendar, error)
+ ListCalendars(ctx context.Context) ([]Calendar, error)
+ GetCalendar(ctx context.Context, path string) (*Calendar, error)
GetCalendarObject(ctx context.Context, path string, req *CalendarCompRequest) (*CalendarObject, error)
- ListCalendarObjects(ctx context.Context, req *CalendarCompRequest) ([]CalendarObject, error)
+ ListCalendarObjects(ctx context.Context, path string, req *CalendarCompRequest) ([]CalendarObject, error)
QueryCalendarObjects(ctx context.Context, query *CalendarQuery) ([]CalendarObject, error)
PutCalendarObject(ctx context.Context, path string, calendar *ical.Calendar, opts *PutCalendarObjectOptions) (loc string, err error)
DeleteCalendarObject(ctx context.Context, path string) error
@@ -424,24 +423,21 @@ func (b *backend) PropFind(r *http.Request, propfind *internal.PropFind, depth i
}
}
case resourceTypeCalendar:
- // TODO for multiple calendars, look through all of them
- ab, err := b.Backend.Calendar(r.Context())
+ ab, err := b.Backend.GetCalendar(r.Context(), r.URL.Path)
if err != nil {
return nil, err
}
- if r.URL.Path == ab.Path {
- resp, err := b.propFindCalendar(r.Context(), propfind, ab)
+ resp, err := b.propFindCalendar(r.Context(), propfind, ab)
+ if err != nil {
+ return nil, err
+ }
+ resps = append(resps, *resp)
+ if depth != internal.DepthZero {
+ resps_, err := b.propFindAllCalendarObjects(r.Context(), propfind, ab)
if err != nil {
return nil, err
}
- resps = append(resps, *resp)
- if depth != internal.DepthZero {
- resps_, err := b.propFindAllCalendarObjects(r.Context(), propfind, ab)
- if err != nil {
- return nil, err
- }
- resps = append(resps, resps_...)
- }
+ resps = append(resps, resps_...)
}
case resourceTypeCalendarObject:
ao, err := b.Backend.GetCalendarObject(r.Context(), r.URL.Path, &dataReq)
@@ -580,22 +576,20 @@ func (b *backend) propFindCalendar(ctx context.Context, propfind *internal.PropF
}
func (b *backend) propFindAllCalendars(ctx context.Context, propfind *internal.PropFind, recurse bool) ([]internal.Response, error) {
- // TODO iterate over all calendars once having multiple is supported
- ab, err := b.Backend.Calendar(ctx)
+ abs, err := b.Backend.ListCalendars(ctx)
if err != nil {
return nil, err
}
- abs := []*Calendar{ab}
var resps []internal.Response
for _, ab := range abs {
- resp, err := b.propFindCalendar(ctx, propfind, ab)
+ resp, err := b.propFindCalendar(ctx, propfind, &ab)
if err != nil {
return nil, err
}
resps = append(resps, *resp)
if recurse {
- resps_, err := b.propFindAllCalendarObjects(ctx, propfind, ab)
+ resps_, err := b.propFindAllCalendarObjects(ctx, propfind, &ab)
if err != nil {
return nil, err
}
@@ -650,7 +644,7 @@ func (b *backend) propFindCalendarObject(ctx context.Context, propfind *internal
func (b *backend) propFindAllCalendarObjects(ctx context.Context, propfind *internal.PropFind, cal *Calendar) ([]internal.Response, error) {
var dataReq CalendarCompRequest
- aos, err := b.Backend.ListCalendarObjects(ctx, &dataReq)
+ aos, err := b.Backend.ListCalendarObjects(ctx, cal.Path, &dataReq)
if err != nil {
return nil, err
}
diff --git a/caldav/server_test.go b/caldav/server_test.go
index c063871..f6e5f40 100644
--- a/caldav/server_test.go
+++ b/caldav/server_test.go
@@ -2,11 +2,13 @@ package caldav
import (
"context"
+ "fmt"
"io"
"io/ioutil"
"net/http/httptest"
"strings"
"testing"
+ "time"
"github.com/emersion/go-ical"
)
@@ -31,7 +33,7 @@ func TestPropFindSupportedCalendarComponent(t *testing.T) {
req.Body = io.NopCloser(strings.NewReader(propFindSupportedCalendarComponentRequest))
req.Header.Set("Content-Type", "application/xml")
w := httptest.NewRecorder()
- handler := Handler{Backend: testBackend{calendar: calendar}}
+ handler := Handler{Backend: testBackend{calendars: []Calendar{*calendar}}}
handler.ServeHTTP(w, req)
res := w.Result()
@@ -66,7 +68,7 @@ func TestPropFindRoot(t *testing.T) {
req.Header.Set("Content-Type", "application/xml")
w := httptest.NewRecorder()
calendar := &Calendar{}
- handler := Handler{Backend: testBackend{calendar: calendar}}
+ handler := Handler{Backend: testBackend{calendars: []Calendar{*calendar}}}
handler.ServeHTTP(w, req)
res := w.Result()
@@ -81,12 +83,116 @@ func TestPropFindRoot(t *testing.T) {
}
}
-type testBackend struct {
- calendar *Calendar
+var reportCalendarData = `
+
+
+
+
+
+ %s
+
+`
+
+func TestMultiCalendarBackend(t *testing.T) {
+ calendarB := Calendar{Path: "/user/calendars/b", SupportedComponentSet: []string{"VTODO"}}
+ calendars := []Calendar{
+ Calendar{Path: "/user/calendars/a"},
+ calendarB,
+ }
+ eventSummary := "This is a todo"
+ event := ical.NewEvent()
+ event.Name = ical.CompToDo
+ event.Props.SetText(ical.PropUID, "46bbf47a-1861-41a3-ae06-8d8268c6d41e")
+ event.Props.SetDateTime(ical.PropDateTimeStamp, time.Now())
+ event.Props.SetText(ical.PropSummary, eventSummary)
+ cal := ical.NewCalendar()
+ cal.Props.SetText(ical.PropVersion, "2.0")
+ cal.Props.SetText(ical.PropProductID, "-//xyz Corp//NONSGML PDA Calendar Version 1.0//EN")
+ cal.Children = []*ical.Component{
+ event.Component,
+ }
+ object := CalendarObject{
+ Path: "/user/calendars/b/test.ics",
+ Data: cal,
+ }
+ req := httptest.NewRequest("PROPFIND", "/user/calendars/", strings.NewReader(propFindUserPrincipal))
+ req.Header.Set("Content-Type", "application/xml")
+ w := httptest.NewRecorder()
+ handler := Handler{Backend: testBackend{
+ calendars: calendars,
+ objectMap: map[string][]CalendarObject{
+ calendarB.Path: []CalendarObject{object},
+ },
+ }}
+ handler.ServeHTTP(w, req)
+
+ res := w.Result()
+ defer res.Body.Close()
+ data, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Error(err)
+ }
+ resp := string(data)
+ for _, calendar := range calendars {
+ if !strings.Contains(resp, fmt.Sprintf(`%s`, calendar.Path)) {
+ t.Errorf("Calendar: %v not returned in PROPFIND, response:\n%s", calendar, resp)
+ }
+ }
+
+ // Now do a PROPFIND for the last calendar
+ req = httptest.NewRequest("PROPFIND", calendarB.Path, strings.NewReader(propFindSupportedCalendarComponentRequest))
+ req.Header.Set("Content-Type", "application/xml")
+ w = httptest.NewRecorder()
+ handler.ServeHTTP(w, req)
+
+ res = w.Result()
+ defer res.Body.Close()
+ data, err = ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Error(err)
+ }
+ resp = string(data)
+ if !strings.Contains(resp, "VTODO") {
+ t.Errorf("Expected component: VTODO not found in response:\n%v", resp)
+ }
+ if !strings.Contains(resp, object.Path) {
+ t.Errorf("Expected calendar object: %v not found in response:\n%v", object, resp)
+ }
+
+ // Now do a REPORT to get the actual data for the event
+ req = httptest.NewRequest("REPORT", calendarB.Path, strings.NewReader(fmt.Sprintf(reportCalendarData, object.Path)))
+ req.Header.Set("Content-Type", "application/xml")
+ w = httptest.NewRecorder()
+ handler.ServeHTTP(w, req)
+
+ res = w.Result()
+ defer res.Body.Close()
+ data, err = ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Error(err)
+ }
+ resp = string(data)
+ if !strings.Contains(resp, fmt.Sprintf("SUMMARY:%s", eventSummary)) {
+ t.Errorf("ICAL content not properly returned in response:\n%v", resp)
+ }
}
-func (t testBackend) Calendar(ctx context.Context) (*Calendar, error) {
- return t.calendar, nil
+type testBackend struct {
+ calendars []Calendar
+ objectMap map[string][]CalendarObject
+}
+
+func (t testBackend) ListCalendars(ctx context.Context) ([]Calendar, error) {
+ return t.calendars, nil
+}
+
+func (t testBackend) GetCalendar(ctx context.Context, path string) (*Calendar, error) {
+ for _, cal := range t.calendars {
+ if cal.Path == path {
+ return &cal, nil
+ }
+ }
+ return nil, fmt.Errorf("Calendar for path: %s not found", path)
}
func (t testBackend) CalendarHomeSetPath(ctx context.Context) (string, error) {
@@ -102,15 +208,22 @@ func (t testBackend) DeleteCalendarObject(ctx context.Context, path string) erro
}
func (t testBackend) GetCalendarObject(ctx context.Context, path string, req *CalendarCompRequest) (*CalendarObject, error) {
- return nil, nil
+ for _, objs := range t.objectMap {
+ for _, obj := range objs {
+ if obj.Path == path {
+ return &obj, nil
+ }
+ }
+ }
+ return nil, fmt.Errorf("Couldn't find calendar object at: %s", path)
}
func (t testBackend) PutCalendarObject(ctx context.Context, path string, calendar *ical.Calendar, opts *PutCalendarObjectOptions) (string, error) {
return "", nil
}
-func (t testBackend) ListCalendarObjects(ctx context.Context, req *CalendarCompRequest) ([]CalendarObject, error) {
- return nil, nil
+func (t testBackend) ListCalendarObjects(ctx context.Context, path string, req *CalendarCompRequest) ([]CalendarObject, error) {
+ return t.objectMap[path], nil
}
func (t testBackend) QueryCalendarObjects(ctx context.Context, query *CalendarQuery) ([]CalendarObject, error) {