Implemented basic CalDAV query and parsing

This commit is contained in:
Andreas Schneider 2018-04-01 11:18:51 +02:00
parent 20a1ebbf1f
commit 9a346c5533
2 changed files with 187 additions and 0 deletions

View File

@ -169,6 +169,17 @@ func main() {
for _, item := range items {
fmt.Printf("%#v\n", item)
}
c := NewCalDAV(s.CalDAV)
calDavItems, err := c.GetEvents()
if err != nil {
log.Println(err)
return
}
for _, item := range calDavItems {
fmt.Printf("%s\n UID: %s\n Summary: %s\n Start: %s\n End: %s\n",
item.HRef, item.UID(), item.Summary(), item.Start(), item.End())
}
}
const folderIdRequest = `<?xml version="1.0" encoding="utf-8"?>

176
src/calanonsync/caldav.go Normal file
View File

@ -0,0 +1,176 @@
package main
import (
"bufio"
"encoding/xml"
"errors"
"io"
"log"
"net/http"
"strings"
"time"
)
const ICAL_TIME = "20060102T150405"
const ICAL_DATE = "20060102"
type CalDAV struct {
ServerSettings
httpClient *http.Client
}
type ICal struct {
data []string
eventData []string // sub slice
}
func (i ICal) Summary() string {
for _, s := range i.eventData {
if strings.HasPrefix(s, "SUMMARY:") {
return s[len("SUMMARY:"):]
}
}
return ""
}
func (i ICal) UID() string {
for _, s := range i.eventData {
if strings.HasPrefix(s, "UID:") {
return s[len("UID:"):]
}
}
return ""
}
func (i ICal) getTimeField(f string) time.Time {
for _, s := range i.eventData {
if strings.HasPrefix(s, f) {
st := s[strings.LastIndex(s, ":")+1:]
// BEWARE, we don't parse possible ICal timezones (yet). If it is not
// a UTC time, we simply assume local time.
zone := time.Local
if st[len(st)-1:] == "Z" {
st = st[:len(st)-1]
zone = time.UTC
}
t, err := time.ParseInLocation(ICAL_TIME, st, zone)
if err != nil {
log.Printf("Cannot decode %s '%s': %s", f, st, err)
}
return t
}
}
return time.Time{}
}
func (i ICal) Start() time.Time {
return i.getTimeField("DTSTART")
}
func (i ICal) End() time.Time {
return i.getTimeField("DTEND")
}
func (i *ICal) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
s := ""
err := d.DecodeElement(&s, &start)
if err != nil {
return err
}
ical, err := ParseICal(strings.NewReader(s))
if err != nil {
return err
}
*i = *ical
return nil
}
func ParseICal(r io.Reader) (*ICal, error) {
var data []string
scanner := bufio.NewScanner(r)
eventStart := -1
eventEnd := -1
i := 0
for scanner.Scan() {
line := scanner.Text()
data = append(data, line)
if eventStart == -1 && line == "BEGIN:VEVENT" {
eventStart = i
}
if eventEnd == -1 && line == "END:VEVENT" {
eventEnd = i
}
i++
}
err := scanner.Err()
if err != nil {
return nil, err
}
// If we didn't find a start or an end, abort.
if eventStart < 0 || eventEnd <= eventStart {
return nil, errors.New("calendar item without event")
}
return &ICal{
data: data,
eventData: data[eventStart+1 : eventEnd],
}, err
}
func NewCalDAV(settings ServerSettings) *CalDAV {
return &CalDAV{
httpClient: http.DefaultClient,
ServerSettings: settings,
}
}
type CalDAVItem struct {
HRef string
ICal
}
func (c *CalDAV) GetEvents() ([]CalDAVItem, error) {
req, err := http.NewRequest("PROPFIND", c.URL, strings.NewReader(`<propfind xmlns='DAV:'><prop><calendar-data xmlns='urn:ietf:params:xml:ns:caldav'/></prop></propfind>`))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "text/xml")
req.SetBasicAuth(c.Username, c.Password)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
ms := &MultiStatus{}
err = xml.NewDecoder(resp.Body).Decode(ms)
if err != nil {
return nil, err
}
result := make([]CalDAVItem, 0, len(ms.Response))
for _, p := range ms.Response {
item := CalDAVItem{p.HRef, p.PropStat.Prop.CalendarData}
result = append(result, item)
}
return result, nil
}
type MultiStatus struct {
Response []PropFindResponse `xml:"response"`
}
type PropFindResponse struct {
HRef string `xml:"href"`
PropStat struct {
Prop struct {
CalendarData ICal `xml:"calendar-data"`
} `xml:"prop"`
} `xml:"propstat"`
}