mirror of
https://github.com/1f349/go-webdav.git
synced 2024-12-22 16:24:14 +00:00
caldav: add multi-calendar support
This commit is contained in:
parent
b46cbafa6f
commit
571eba7c02
@ -17,8 +17,6 @@ import (
|
|||||||
"github.com/emersion/go-webdav/internal"
|
"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
|
// TODO if nothing more Caldav-specific needs to be added this should be merged with carddav.PutAddressObjectOptions
|
||||||
type PutCalendarObjectOptions struct {
|
type PutCalendarObjectOptions struct {
|
||||||
// IfNoneMatch indicates that the client does not want to overwrite
|
// IfNoneMatch indicates that the client does not want to overwrite
|
||||||
@ -32,9 +30,10 @@ type PutCalendarObjectOptions struct {
|
|||||||
// Backend is a CalDAV server backend.
|
// Backend is a CalDAV server backend.
|
||||||
type Backend interface {
|
type Backend interface {
|
||||||
CalendarHomeSetPath(ctx context.Context) (string, error)
|
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)
|
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)
|
QueryCalendarObjects(ctx context.Context, query *CalendarQuery) ([]CalendarObject, error)
|
||||||
PutCalendarObject(ctx context.Context, path string, calendar *ical.Calendar, opts *PutCalendarObjectOptions) (loc string, err error)
|
PutCalendarObject(ctx context.Context, path string, calendar *ical.Calendar, opts *PutCalendarObjectOptions) (loc string, err error)
|
||||||
DeleteCalendarObject(ctx context.Context, path string) 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:
|
case resourceTypeCalendar:
|
||||||
// TODO for multiple calendars, look through all of them
|
ab, err := b.Backend.GetCalendar(r.Context(), r.URL.Path)
|
||||||
ab, err := b.Backend.Calendar(r.Context())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resps = append(resps, *resp)
|
resps = append(resps, resps_...)
|
||||||
if depth != internal.DepthZero {
|
|
||||||
resps_, err := b.propFindAllCalendarObjects(r.Context(), propfind, ab)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resps = append(resps, resps_...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case resourceTypeCalendarObject:
|
case resourceTypeCalendarObject:
|
||||||
ao, err := b.Backend.GetCalendarObject(r.Context(), r.URL.Path, &dataReq)
|
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) {
|
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
|
abs, err := b.Backend.ListCalendars(ctx)
|
||||||
ab, err := b.Backend.Calendar(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
abs := []*Calendar{ab}
|
|
||||||
|
|
||||||
var resps []internal.Response
|
var resps []internal.Response
|
||||||
for _, ab := range abs {
|
for _, ab := range abs {
|
||||||
resp, err := b.propFindCalendar(ctx, propfind, ab)
|
resp, err := b.propFindCalendar(ctx, propfind, &ab)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resps = append(resps, *resp)
|
resps = append(resps, *resp)
|
||||||
if recurse {
|
if recurse {
|
||||||
resps_, err := b.propFindAllCalendarObjects(ctx, propfind, ab)
|
resps_, err := b.propFindAllCalendarObjects(ctx, propfind, &ab)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
func (b *backend) propFindAllCalendarObjects(ctx context.Context, propfind *internal.PropFind, cal *Calendar) ([]internal.Response, error) {
|
||||||
var dataReq CalendarCompRequest
|
var dataReq CalendarCompRequest
|
||||||
aos, err := b.Backend.ListCalendarObjects(ctx, &dataReq)
|
aos, err := b.Backend.ListCalendarObjects(ctx, cal.Path, &dataReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,13 @@ package caldav
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/emersion/go-ical"
|
"github.com/emersion/go-ical"
|
||||||
)
|
)
|
||||||
@ -31,7 +33,7 @@ func TestPropFindSupportedCalendarComponent(t *testing.T) {
|
|||||||
req.Body = io.NopCloser(strings.NewReader(propFindSupportedCalendarComponentRequest))
|
req.Body = io.NopCloser(strings.NewReader(propFindSupportedCalendarComponentRequest))
|
||||||
req.Header.Set("Content-Type", "application/xml")
|
req.Header.Set("Content-Type", "application/xml")
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
handler := Handler{Backend: testBackend{calendar: calendar}}
|
handler := Handler{Backend: testBackend{calendars: []Calendar{*calendar}}}
|
||||||
handler.ServeHTTP(w, req)
|
handler.ServeHTTP(w, req)
|
||||||
|
|
||||||
res := w.Result()
|
res := w.Result()
|
||||||
@ -66,7 +68,7 @@ func TestPropFindRoot(t *testing.T) {
|
|||||||
req.Header.Set("Content-Type", "application/xml")
|
req.Header.Set("Content-Type", "application/xml")
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
calendar := &Calendar{}
|
calendar := &Calendar{}
|
||||||
handler := Handler{Backend: testBackend{calendar: calendar}}
|
handler := Handler{Backend: testBackend{calendars: []Calendar{*calendar}}}
|
||||||
handler.ServeHTTP(w, req)
|
handler.ServeHTTP(w, req)
|
||||||
|
|
||||||
res := w.Result()
|
res := w.Result()
|
||||||
@ -81,12 +83,116 @@ func TestPropFindRoot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type testBackend struct {
|
var reportCalendarData = `
|
||||||
calendar *Calendar
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<B:calendar-multiget xmlns:A="DAV:" xmlns:B="urn:ietf:params:xml:ns:caldav">
|
||||||
|
<A:prop>
|
||||||
|
<B:calendar-data/>
|
||||||
|
</A:prop>
|
||||||
|
<A:href>%s</A:href>
|
||||||
|
</B:calendar-multiget>
|
||||||
|
`
|
||||||
|
|
||||||
|
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(`<response xmlns="DAV:"><href>%s</href>`, 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) {
|
type testBackend struct {
|
||||||
return t.calendar, nil
|
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) {
|
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) {
|
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) {
|
func (t testBackend) PutCalendarObject(ctx context.Context, path string, calendar *ical.Calendar, opts *PutCalendarObjectOptions) (string, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t testBackend) ListCalendarObjects(ctx context.Context, req *CalendarCompRequest) ([]CalendarObject, error) {
|
func (t testBackend) ListCalendarObjects(ctx context.Context, path string, req *CalendarCompRequest) ([]CalendarObject, error) {
|
||||||
return nil, nil
|
return t.objectMap[path], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t testBackend) QueryCalendarObjects(ctx context.Context, query *CalendarQuery) ([]CalendarObject, error) {
|
func (t testBackend) QueryCalendarObjects(ctx context.Context, query *CalendarQuery) ([]CalendarObject, error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user