feat: implement caldav search
This commit is contained in:
@ -1,12 +1,73 @@
|
|||||||
package calendar
|
package calendar
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dolanor/caldav-go/caldav"
|
||||||
|
"github.com/dolanor/caldav-go/caldav/entities"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/components"
|
||||||
|
"log"
|
||||||
"math"
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Caldav interface {
|
||||||
|
QueryEvents(path string, query *entities.CalendarQuery) (events []*components.Event, oerr error)
|
||||||
|
}
|
||||||
|
|
||||||
type Calendar struct {
|
type Calendar struct {
|
||||||
Location *time.Location
|
Location *time.Location
|
||||||
|
cdav Caldav
|
||||||
|
caldavPath string
|
||||||
|
caldavSummaryPattern string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCaldav(caldavUrl, caldavPath string) (Caldav, error) {
|
||||||
|
// create a reference to your CalDAV-compliant server
|
||||||
|
server, _ := caldav.NewServer(caldavUrl)
|
||||||
|
// create a CalDAV client to speak to the server
|
||||||
|
var client = caldav.NewClient(server, http.DefaultClient)
|
||||||
|
// start executing requests!
|
||||||
|
err := client.ValidateServer(caldavPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("bad caldav configuration, unable to validate connexion: %w", err)
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(calendar *Calendar)
|
||||||
|
|
||||||
|
func WithCaldav(cdav Caldav) Option {
|
||||||
|
return func(calendar *Calendar) {
|
||||||
|
calendar.cdav = cdav
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithCaldavSummaryPattern(caldavSummaryPattern string) Option {
|
||||||
|
return func(calendar *Calendar) {
|
||||||
|
calendar.caldavSummaryPattern = caldavSummaryPattern
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithCaldavPath(caldavPath string) Option {
|
||||||
|
return func(calendar *Calendar) {
|
||||||
|
calendar.caldavPath = caldavPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(location *time.Location, opts ...Option) *Calendar {
|
||||||
|
c := &Calendar{
|
||||||
|
location,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cal *Calendar) GetEasterDay(year int) time.Time {
|
func (cal *Calendar) GetEasterDay(year int) time.Time {
|
||||||
@ -72,20 +133,24 @@ func (cal *Calendar) GetHolidays(year int) *[]time.Time {
|
|||||||
return &joursFeries
|
return &joursFeries
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cal *Calendar) GetHolidaysSet(year int) *map[time.Time]bool {
|
func (cal *Calendar) GetHolidaysSet(year int) map[time.Time]bool {
|
||||||
holidays := cal.GetHolidays(year)
|
holidays := cal.GetHolidays(year)
|
||||||
result := make(map[time.Time]bool, len(*holidays))
|
result := make(map[time.Time]bool, len(*holidays))
|
||||||
for _, h := range *holidays {
|
for _, h := range *holidays {
|
||||||
result[h] = true
|
result[h] = true
|
||||||
}
|
}
|
||||||
return &result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func(cal *Calendar) IsHoliday(date time.Time) bool{
|
func (cal *Calendar) IsHoliday(date time.Time) bool {
|
||||||
h := cal.GetHolidaysSet(date.Year())
|
h := cal.GetHolidaysSet(date.Year())
|
||||||
d := date.In(cal.Location)
|
d := date.In(cal.Location)
|
||||||
day := time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, cal.Location)
|
day := time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, cal.Location)
|
||||||
return (*h)[day]
|
caldavHolidays, err := cal.IsHolidaysFromCaldav(day)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("unable to check holidays from caldav: %v", err)
|
||||||
|
}
|
||||||
|
return h[day] || caldavHolidays
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cal *Calendar) IsWorkingDay(date time.Time) bool {
|
func (cal *Calendar) IsWorkingDay(date time.Time) bool {
|
||||||
@ -96,6 +161,27 @@ func (cal *Calendar) IsWorkingDayToday() bool {
|
|||||||
return cal.IsWorkingDay(time.Now())
|
return cal.IsWorkingDay(time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cal *Calendar) IsWeekDay(day time.Time) bool{
|
func (cal *Calendar) IsWeekDay(day time.Time) bool {
|
||||||
return day.Weekday() >= time.Monday && day.Weekday() <= time.Friday
|
return day.Weekday() >= time.Monday && day.Weekday() <= time.Friday
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cal *Calendar) IsHolidaysFromCaldav(day time.Time) (bool, error) {
|
||||||
|
if cal.cdav == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
query, err := entities.NewEventRangeQuery(day.UTC(), day.UTC().Add(23*time.Hour+59*time.Minute))
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("unable to build events range query: %v", err)
|
||||||
|
}
|
||||||
|
events, err := cal.cdav.QueryEvents(cal.caldavPath, query)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("unable list events from caldav: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, evt := range events {
|
||||||
|
if strings.Contains(evt.Summary, cal.caldavSummaryPattern) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package calendar
|
package calendar
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/dolanor/caldav-go/caldav/entities"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/components"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/values"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -48,7 +51,7 @@ func TestCalendar_GetHolidays(t *testing.T) {
|
|||||||
time.Date(2020, time.December, 25, 0, 0, 0, 0, loc): true,
|
time.Date(2020, time.December, 25, 0, 0, 0, 0, loc): true,
|
||||||
}
|
}
|
||||||
|
|
||||||
c := Calendar{loc}
|
c := New(loc)
|
||||||
holidays := c.GetHolidays(2020)
|
holidays := c.GetHolidays(2020)
|
||||||
if len(*holidays) != len(expectedHolidays) {
|
if len(*holidays) != len(expectedHolidays) {
|
||||||
t.Errorf("bad number of holidays, %d but %d are expected", len(*holidays), len(expectedHolidays))
|
t.Errorf("bad number of holidays, %d but %d are expected", len(*holidays), len(expectedHolidays))
|
||||||
@ -80,13 +83,13 @@ func TestCalendar_GetHolidaysSet(t *testing.T) {
|
|||||||
time.Date(2020, time.December, 25, 0, 0, 0, 0, loc),
|
time.Date(2020, time.December, 25, 0, 0, 0, 0, loc),
|
||||||
}
|
}
|
||||||
|
|
||||||
c := Calendar{loc}
|
c := New(loc)
|
||||||
holidays := c.GetHolidaysSet(2020)
|
holidays := c.GetHolidaysSet(2020)
|
||||||
if len(*holidays) != len(expectedHolidays) {
|
if len(holidays) != len(expectedHolidays) {
|
||||||
t.Errorf("bad number of holidays, %d but %d are expected", len(*holidays), len(expectedHolidays))
|
t.Errorf("bad number of holidays, %d but %d are expected", len(holidays), len(expectedHolidays))
|
||||||
}
|
}
|
||||||
for _, h := range expectedHolidays {
|
for _, h := range expectedHolidays {
|
||||||
if !(*holidays)[h] {
|
if !(holidays)[h] {
|
||||||
t.Errorf("%v is not a holiday", h)
|
t.Errorf("%v is not a holiday", h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,10 +115,10 @@ func TestCalendar_IsHolidays(t *testing.T) {
|
|||||||
time.Date(2020, time.December, 25, 0, 0, 0, 0, loc),
|
time.Date(2020, time.December, 25, 0, 0, 0, 0, loc),
|
||||||
}
|
}
|
||||||
|
|
||||||
c := Calendar{loc}
|
c := New(loc)
|
||||||
holidays := c.GetHolidaysSet(2020)
|
holidays := c.GetHolidaysSet(2020)
|
||||||
if len(*holidays) != len(expectedHolidays) {
|
if len(holidays) != len(expectedHolidays) {
|
||||||
t.Errorf("bad number of holidays, %d but %d are expected", len(*holidays), len(expectedHolidays))
|
t.Errorf("bad number of holidays, %d but %d are expected", len(holidays), len(expectedHolidays))
|
||||||
}
|
}
|
||||||
for _, h := range expectedHolidays {
|
for _, h := range expectedHolidays {
|
||||||
if !c.IsHoliday(h) {
|
if !c.IsHoliday(h) {
|
||||||
@ -133,7 +136,7 @@ func TestCalendar_IsWorkingDay(t *testing.T) {
|
|||||||
t.Errorf("unable to load time location: %v", err)
|
t.Errorf("unable to load time location: %v", err)
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
c := Calendar{loc}
|
c := New(loc)
|
||||||
|
|
||||||
if c.IsWorkingDay(time.Date(2019, time.January, 01, 0, 0, 0, 0, loc)) {
|
if c.IsWorkingDay(time.Date(2019, time.January, 01, 0, 0, 0, 0, loc)) {
|
||||||
t.Error("1st january is not a working day")
|
t.Error("1st january is not a working day")
|
||||||
@ -164,3 +167,114 @@ func TestCalendar_IsWorkingDay(t *testing.T) {
|
|||||||
t.Error("Sunday should not be a working day")
|
t.Error("Sunday should not be a working day")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MockCaldav struct {
|
||||||
|
events []*components.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockCaldav) QueryEvents(_ string, _ *entities.CalendarQuery) ([]*components.Event, error) {
|
||||||
|
return m.events, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalendar_IsHolidaysFromCaldav(t *testing.T) {
|
||||||
|
loc, err := time.LoadLocation("Europe/Paris")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to load time location: %v", err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
type fields struct {
|
||||||
|
Location *time.Location
|
||||||
|
cdav *MockCaldav
|
||||||
|
caldavPath string
|
||||||
|
caldavSummaryPattern string
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
day time.Time
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Holidays in events",
|
||||||
|
fields: fields{
|
||||||
|
Location: loc,
|
||||||
|
cdav: &MockCaldav{
|
||||||
|
events: []*components.Event{
|
||||||
|
{
|
||||||
|
UID: "1",
|
||||||
|
DateStart: values.NewDateTime(time.Date(2022, time.April, 16, 0, 0, 0, 0, loc)),
|
||||||
|
DateEnd: values.NewDateTime(time.Date(2022, time.April, 17, 0, 0, 0, 0, loc)),
|
||||||
|
Summary: "Holidays",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
caldavPath: "my_calendar/",
|
||||||
|
caldavSummaryPattern: "Holidays",
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
day: time.Date(2022, time.April, 16, 0, 0, 0, 0, loc),
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Not Holidays in events",
|
||||||
|
fields: fields{
|
||||||
|
Location: loc,
|
||||||
|
cdav: &MockCaldav{
|
||||||
|
events: []*components.Event{
|
||||||
|
{
|
||||||
|
UID: "1",
|
||||||
|
DateStart: values.NewDateTime(time.Date(2022, time.April, 16, 0, 0, 0, 0, loc)),
|
||||||
|
DateEnd: values.NewDateTime(time.Date(2022, time.April, 17, 0, 0, 0, 0, loc)),
|
||||||
|
Summary: "Another event",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
caldavPath: "my_calendar/",
|
||||||
|
caldavSummaryPattern: "Holidays",
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
day: time.Date(2022, time.April, 16, 0, 0, 0, 0, loc),
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No events",
|
||||||
|
fields: fields{
|
||||||
|
Location: loc,
|
||||||
|
cdav: &MockCaldav{},
|
||||||
|
caldavPath: "my_calendar/",
|
||||||
|
caldavSummaryPattern: "Holidays",
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
day: time.Date(2022, time.April, 15, 0, 0, 0, 0, loc),
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cal := New(
|
||||||
|
loc,
|
||||||
|
WithCaldav(tt.fields.cdav),
|
||||||
|
WithCaldavPath(tt.fields.caldavPath),
|
||||||
|
WithCaldavSummaryPattern(tt.fields.caldavSummaryPattern),
|
||||||
|
)
|
||||||
|
got, err := cal.IsHolidaysFromCaldav(tt.args.day)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("IsHolidaysFromCaldav() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("IsHolidaysFromCaldav() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,6 +6,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/hellofresh/health-go/v4"
|
"github.com/hellofresh/health-go/v4"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@ -17,7 +20,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cal calendar.Calendar
|
cal *calendar.Calendar
|
||||||
|
location *time.Location
|
||||||
calCounter *prometheus.CounterVec
|
calCounter *prometheus.CounterVec
|
||||||
calSummary *prometheus.SummaryVec
|
calSummary *prometheus.SummaryVec
|
||||||
calHistogram *prometheus.HistogramVec
|
calHistogram *prometheus.HistogramVec
|
||||||
@ -28,7 +32,7 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("unable to load time location: %v", err)
|
log.Fatalf("unable to load time location: %v", err)
|
||||||
}
|
}
|
||||||
cal = calendar.Calendar{Location: loc}
|
location = loc
|
||||||
|
|
||||||
calCounter = promauto.NewCounterVec(prometheus.CounterOpts{
|
calCounter = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
Namespace: "domogeek",
|
Namespace: "domogeek",
|
||||||
@ -93,11 +97,25 @@ func (c *CalendarHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
func main() {
|
func main() {
|
||||||
var port int
|
var port int
|
||||||
var host string
|
var host string
|
||||||
|
var caldavUrl, caldavPath, caldavSummaryPattern string
|
||||||
|
|
||||||
flag.StringVar(&host, "host", "", "host to listen, default all addresses")
|
flag.StringVar(&host, "host", "", "host to listen, default all addresses")
|
||||||
flag.IntVar(&port, "port", 8080, "port to listen")
|
flag.IntVar(&port, "port", 8080, "port to listen")
|
||||||
|
flag.StringVar(&caldavUrl, "caldav-url", "", "caldav url to use to read holidays events")
|
||||||
|
flag.StringVar(&caldavPath, "caldav-path", "", "caldav path to use to read holidays events")
|
||||||
|
flag.StringVar(&caldavSummaryPattern, "caldav-summary-pattern", "Holidays", "Summary pattern that matches holidays event")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
cdav, err := calendar.NewCaldav(caldavUrl, caldavPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to init caldav instance")
|
||||||
|
}
|
||||||
|
cal = calendar.New(location,
|
||||||
|
calendar.WithCaldav(cdav),
|
||||||
|
calendar.WithCaldavPath(caldavPath),
|
||||||
|
calendar.WithCaldavSummaryPattern(caldavSummaryPattern),
|
||||||
|
)
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", host, port)
|
addr := fmt.Sprintf("%s:%d", host, port)
|
||||||
log.Printf("start server on %s", addr)
|
log.Printf("start server on %s", addr)
|
||||||
|
|
||||||
@ -117,9 +135,25 @@ func main() {
|
|||||||
Check: func(ctx context.Context) error {
|
Check: func(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
|
health.WithChecks(health.Config{
|
||||||
|
Name: "caldav",
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
SkipOnErr: false,
|
||||||
|
Check: func(ctx context.Context) error {
|
||||||
|
_, err := cal.IsHolidaysFromCaldav(time.Now())
|
||||||
|
return err
|
||||||
},
|
},
|
||||||
))
|
}),
|
||||||
|
)
|
||||||
http.Handle("/status", healthz.Handler())
|
http.Handle("/status", healthz.Handler())
|
||||||
|
|
||||||
|
signChan := make(chan os.Signal, 1)
|
||||||
|
go func() {
|
||||||
log.Fatal(http.ListenAndServe(addr, nil))
|
log.Fatal(http.ListenAndServe(addr, nil))
|
||||||
|
}()
|
||||||
|
|
||||||
|
signal.Notify(signChan, syscall.SIGTERM)
|
||||||
|
<-signChan
|
||||||
|
log.Printf("exit on sigterm")
|
||||||
}
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module domogeek
|
|||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/dolanor/caldav-go v0.2.1
|
||||||
github.com/hellofresh/health-go/v4 v4.5.0
|
github.com/hellofresh/health-go/v4 v4.5.0
|
||||||
github.com/prometheus/client_golang v1.12.1
|
github.com/prometheus/client_golang v1.12.1
|
||||||
)
|
)
|
||||||
|
5
go.sum
5
go.sum
@ -62,6 +62,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/dolanor/caldav-go v0.2.1 h1:wbARF+WKIryMOApYdX6CnFKY25Kz3ChPerfFc/R3Txk=
|
||||||
|
github.com/dolanor/caldav-go v0.2.1/go.mod h1:0A9uEq2TN7U1eNh11hHAZ0FA7jyuFSh5hXeaoFPC0fc=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
@ -226,9 +228,11 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
|
|||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
@ -645,6 +649,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
|||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
22
vendor/github.com/dolanor/caldav-go/LICENSE
generated
vendored
Normal file
22
vendor/github.com/dolanor/caldav-go/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
191
vendor/github.com/dolanor/caldav-go/caldav/client.go
generated
vendored
Normal file
191
vendor/github.com/dolanor/caldav-go/caldav/client.go
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
package caldav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
cent "github.com/dolanor/caldav-go/caldav/entities"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/components"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"github.com/dolanor/caldav-go/webdav"
|
||||||
|
"github.com/dolanor/caldav-go/webdav/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
// a client for making WebDAV requests
|
||||||
|
type Client webdav.Client
|
||||||
|
|
||||||
|
// downcasts the client to the WebDAV interface
|
||||||
|
func (c *Client) WebDAV() *webdav.Client {
|
||||||
|
return (*webdav.Client)(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the embedded CalDAV server reference
|
||||||
|
func (c *Client) Server() *Server {
|
||||||
|
return (*Server)(c.WebDAV().Server())
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetches a list of CalDAV features supported by the server
|
||||||
|
// returns an error if the server does not support DAV
|
||||||
|
func (c *Client) Features(path string) ([]string, error) {
|
||||||
|
var cfeatures []string
|
||||||
|
if features, err := c.WebDAV().Features(path); err != nil {
|
||||||
|
return cfeatures, utils.NewError(c.Features, "unable to detect features", c, err)
|
||||||
|
} else {
|
||||||
|
for _, feature := range features {
|
||||||
|
if strings.HasPrefix(feature, "calendar-") {
|
||||||
|
cfeatures = append(cfeatures, feature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cfeatures, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetches a list of CalDAV features and checks if a certain one is supported by the server
|
||||||
|
// returns an error if the server does not support DAV
|
||||||
|
func (c *Client) SupportsFeature(name string, path string) (bool, error) {
|
||||||
|
if features, err := c.Features(path); err != nil {
|
||||||
|
return false, utils.NewError(c.SupportsFeature, "feature detection failed", c, err)
|
||||||
|
} else {
|
||||||
|
var test = fmt.Sprintf("calendar-%s", name)
|
||||||
|
for _, feature := range features {
|
||||||
|
if feature == test {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetches a list of CalDAV features and checks if a certain one is supported by the server
|
||||||
|
// returns an error if the server does not support DAV
|
||||||
|
func (c *Client) ValidateServer(path string) error {
|
||||||
|
if found, err := c.SupportsFeature("access", path); err != nil {
|
||||||
|
return utils.NewError(c.SupportsFeature, "feature detection failed", c, err)
|
||||||
|
} else if !found {
|
||||||
|
return utils.NewError(c.SupportsFeature, "calendar access feature missing", c, nil)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new calendar collection on a given path
|
||||||
|
func (c *Client) MakeCalendar(path string) error {
|
||||||
|
if req, err := c.Server().NewRequest("MKCALENDAR", path); err != nil {
|
||||||
|
return utils.NewError(c.MakeCalendar, "unable to create request", c, err)
|
||||||
|
} else if resp, err := c.Do(req); err != nil {
|
||||||
|
return utils.NewError(c.MakeCalendar, "unable to execute request", c, err)
|
||||||
|
} else if resp.StatusCode != http.StatusCreated {
|
||||||
|
err := new(entities.Error)
|
||||||
|
resp.Decode(err)
|
||||||
|
msg := fmt.Sprintf("unexpected server response %s", resp.Status)
|
||||||
|
return utils.NewError(c.MakeCalendar, msg, c, err)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates or updates one or more events on the remote CalDAV server
|
||||||
|
func (c *Client) PutEvents(path string, events ...*components.Event) error {
|
||||||
|
if len(events) <= 0 {
|
||||||
|
return utils.NewError(c.PutEvents, "no calendar events provided", c, nil)
|
||||||
|
} else if cal := components.NewCalendar(events...); events[0] == nil {
|
||||||
|
return utils.NewError(c.PutEvents, "icalendar event must not be nil", c, nil)
|
||||||
|
} else if err := c.PutCalendars(path, cal); err != nil {
|
||||||
|
return utils.NewError(c.PutEvents, "unable to put calendar", c, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates or updates one or more calendars on the remote CalDAV server
|
||||||
|
func (c *Client) PutCalendars(path string, calendars ...*components.Calendar) error {
|
||||||
|
if req, err := c.Server().NewRequest("PUT", path, calendars); err != nil {
|
||||||
|
return utils.NewError(c.PutCalendars, "unable to encode request", c, err)
|
||||||
|
} else if resp, err := c.Do(req); err != nil {
|
||||||
|
return utils.NewError(c.PutCalendars, "unable to execute request", c, err)
|
||||||
|
} else if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusNoContent {
|
||||||
|
err := new(entities.Error)
|
||||||
|
resp.WebDAV().Decode(err)
|
||||||
|
msg := fmt.Sprintf("unexpected server response %s", resp.Status)
|
||||||
|
return utils.NewError(c.PutCalendars, msg, c, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempts to fetch an event on the remote CalDAV server
|
||||||
|
func (c *Client) GetEvents(path string) ([]*components.Event, error) {
|
||||||
|
cal := new(components.Calendar)
|
||||||
|
if req, err := c.Server().NewRequest("GET", path); err != nil {
|
||||||
|
return nil, utils.NewError(c.GetEvents, "unable to create request", c, err)
|
||||||
|
} else if resp, err := c.Do(req); err != nil {
|
||||||
|
return nil, utils.NewError(c.GetEvents, "unable to execute request", c, err)
|
||||||
|
} else if resp.StatusCode != http.StatusOK {
|
||||||
|
err := new(entities.Error)
|
||||||
|
resp.WebDAV().Decode(err)
|
||||||
|
msg := fmt.Sprintf("unexpected server response %s", resp.Status)
|
||||||
|
return nil, utils.NewError(c.GetEvents, msg, c, err)
|
||||||
|
} else if err := resp.Decode(cal); err != nil {
|
||||||
|
return nil, utils.NewError(c.GetEvents, "unable to decode response", c, err)
|
||||||
|
} else {
|
||||||
|
return cal.Events, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempts to fetch an event on the remote CalDAV server
|
||||||
|
func (c *Client) QueryEvents(path string, query *cent.CalendarQuery) (events []*components.Event, oerr error) {
|
||||||
|
ms := new(cent.Multistatus)
|
||||||
|
if req, err := c.Server().WebDAV().NewRequest("REPORT", path, query); err != nil {
|
||||||
|
oerr = utils.NewError(c.QueryEvents, "unable to create request", c, err)
|
||||||
|
} else if req.Http().Native().Header.Set("Depth", string(webdav.Depth1)); false {
|
||||||
|
} else if resp, err := c.WebDAV().Do(req); err != nil {
|
||||||
|
oerr = utils.NewError(c.QueryEvents, "unable to execute request", c, err)
|
||||||
|
} else if resp.StatusCode == http.StatusNotFound {
|
||||||
|
return // no events if not found
|
||||||
|
} else if resp.StatusCode != webdav.StatusMulti {
|
||||||
|
err := new(entities.Error)
|
||||||
|
msg := fmt.Sprintf("unexpected server response %s", resp.Status)
|
||||||
|
resp.Decode(err)
|
||||||
|
oerr = utils.NewError(c.QueryEvents, msg, c, err)
|
||||||
|
} else if err := resp.Decode(ms); err != nil {
|
||||||
|
msg := "unable to decode response"
|
||||||
|
oerr = utils.NewError(c.QueryEvents, msg, c, err)
|
||||||
|
} else {
|
||||||
|
for i, r := range ms.Responses {
|
||||||
|
for j, p := range r.PropStats {
|
||||||
|
if p.Prop == nil || p.Prop.CalendarData == nil {
|
||||||
|
continue
|
||||||
|
} else if cal, err := p.Prop.CalendarData.CalendarComponent(); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to decode property %d of response %d", j, i)
|
||||||
|
oerr = utils.NewError(c.QueryEvents, msg, c, err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
events = append(events, cal.Events...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// executes a CalDAV request
|
||||||
|
func (c *Client) Do(req *Request) (*Response, error) {
|
||||||
|
if resp, err := c.WebDAV().Do((*webdav.Request)(req)); err != nil {
|
||||||
|
return nil, utils.NewError(c.Do, "unable to execute CalDAV request", c, err)
|
||||||
|
} else {
|
||||||
|
return NewResponse(resp), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new client for communicating with an WebDAV server
|
||||||
|
func NewClient(server *Server, native *http.Client) *Client {
|
||||||
|
return (*Client)(webdav.NewClient((*webdav.Server)(server), native))
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new client for communicating with a WebDAV server
|
||||||
|
// uses the default HTTP client from net/http
|
||||||
|
func NewDefaultClient(server *Server) *Client {
|
||||||
|
return NewClient(server, http.DefaultClient)
|
||||||
|
}
|
51
vendor/github.com/dolanor/caldav-go/caldav/entities/calendar_data.go
generated
vendored
Normal file
51
vendor/github.com/dolanor/caldav-go/caldav/entities/calendar_data.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"github.com/dolanor/caldav-go/caldav/values"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/components"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// a CalDAV calendar data object
|
||||||
|
type CalendarData struct {
|
||||||
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-data"`
|
||||||
|
Component *Component `xml:",omitempty"`
|
||||||
|
RecurrenceSetLimit *RecurrenceSetLimit `xml:",omitempty"`
|
||||||
|
ExpandRecurrenceSet *ExpandRecurrenceSet `xml:",omitempty"`
|
||||||
|
Content string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CalendarData) CalendarComponent() (*components.Calendar, error) {
|
||||||
|
cal := new(components.Calendar)
|
||||||
|
if content := strings.TrimSpace(c.Content); content == "" {
|
||||||
|
return nil, utils.NewError(c.CalendarComponent, "no calendar data to decode", c, nil)
|
||||||
|
} else if err := icalendar.Unmarshal(content, cal); err != nil {
|
||||||
|
return nil, utils.NewError(c.CalendarComponent, "decoding calendar data failed", c, err)
|
||||||
|
} else {
|
||||||
|
return cal, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// an iCalendar specifier for returned calendar data
|
||||||
|
type Component struct {
|
||||||
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav comp"`
|
||||||
|
Properties []*PropertyName `xml:",omitempty"`
|
||||||
|
Components []*Component `xml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used to restrict recurring event data to a particular time range
|
||||||
|
type RecurrenceSetLimit struct {
|
||||||
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav limit-recurrence-set"`
|
||||||
|
StartTime *values.DateTime `xml:"start,attr"`
|
||||||
|
EndTime *values.DateTime `xml:"end,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used to expand recurring events into individual calendar event data
|
||||||
|
type ExpandRecurrenceSet struct {
|
||||||
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav expand"`
|
||||||
|
StartTime *values.DateTime `xml:"start,attr"`
|
||||||
|
EndTime *values.DateTime `xml:"end,attr"`
|
||||||
|
}
|
59
vendor/github.com/dolanor/caldav-go/caldav/entities/calendar_query.go
generated
vendored
Normal file
59
vendor/github.com/dolanor/caldav-go/caldav/entities/calendar_query.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"github.com/dolanor/caldav-go/caldav/values"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"github.com/dolanor/caldav-go/webdav/entities"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// a CalDAV calendar query object
|
||||||
|
type CalendarQuery struct {
|
||||||
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-query"`
|
||||||
|
Prop *Prop `xml:",omitempty"`
|
||||||
|
AllProp *entities.AllProp `xml:",omitempty"`
|
||||||
|
Filter *Filter `xml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new CalDAV query for iCalendar events from a particular time range
|
||||||
|
func NewEventRangeQuery(start, end time.Time) (*CalendarQuery, error) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var dtstart, dtend *values.DateTime
|
||||||
|
if dtstart, err = values.NewDateTime("start", start); err != nil {
|
||||||
|
return nil, utils.NewError(NewEventRangeQuery, "unable to encode start time", start, err)
|
||||||
|
} else if dtend, err = values.NewDateTime("end", end); err != nil {
|
||||||
|
return nil, utils.NewError(NewEventRangeQuery, "unable to encode end time", end, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct the query object
|
||||||
|
query := new(CalendarQuery)
|
||||||
|
|
||||||
|
// request all calendar data
|
||||||
|
query.Prop = new(Prop)
|
||||||
|
query.Prop.CalendarData = new(CalendarData)
|
||||||
|
|
||||||
|
// expand recurring events
|
||||||
|
query.Prop.CalendarData.ExpandRecurrenceSet = new(ExpandRecurrenceSet)
|
||||||
|
query.Prop.CalendarData.ExpandRecurrenceSet.StartTime = dtstart
|
||||||
|
query.Prop.CalendarData.ExpandRecurrenceSet.EndTime = dtend
|
||||||
|
|
||||||
|
// filter down calendar data to only iCalendar data
|
||||||
|
query.Filter = new(Filter)
|
||||||
|
query.Filter.ComponentFilter = new(ComponentFilter)
|
||||||
|
query.Filter.ComponentFilter.Name = values.CalendarComponentName
|
||||||
|
|
||||||
|
// filter down iCalendar data to only events
|
||||||
|
query.Filter.ComponentFilter.ComponentFilter = new(ComponentFilter)
|
||||||
|
query.Filter.ComponentFilter.ComponentFilter.Name = values.EventComponentName
|
||||||
|
|
||||||
|
// filter down the events to only those that fall within the time range
|
||||||
|
query.Filter.ComponentFilter.ComponentFilter.TimeRange = new(TimeRange)
|
||||||
|
query.Filter.ComponentFilter.ComponentFilter.TimeRange.StartTime = dtstart
|
||||||
|
query.Filter.ComponentFilter.ComponentFilter.TimeRange.EndTime = dtend
|
||||||
|
|
||||||
|
// return the event query
|
||||||
|
return query, nil
|
||||||
|
|
||||||
|
}
|
62
vendor/github.com/dolanor/caldav-go/caldav/entities/filter.go
generated
vendored
Normal file
62
vendor/github.com/dolanor/caldav-go/caldav/entities/filter.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"github.com/dolanor/caldav-go/caldav/values"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/properties"
|
||||||
|
)
|
||||||
|
|
||||||
|
// a CalDAV query filter entity
|
||||||
|
type Filter struct {
|
||||||
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav filter"`
|
||||||
|
ComponentFilter *ComponentFilter `xml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used to filter down calendar components, such as VCALENDAR > VEVENT
|
||||||
|
type ComponentFilter struct {
|
||||||
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav comp-filter"`
|
||||||
|
Name values.ComponentName `xml:"name,attr"`
|
||||||
|
ComponentFilter *ComponentFilter `xml:",omitempty"`
|
||||||
|
TimeRange *TimeRange `xml:",omitempty"`
|
||||||
|
PropertyFilter *PropertyFilter `xml:",omitempty"`
|
||||||
|
ParameterFilter *ParameterFilter `xml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used to restrict component filters to a particular time range
|
||||||
|
type TimeRange struct {
|
||||||
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav time-range"`
|
||||||
|
StartTime *values.DateTime `xml:"start,attr"`
|
||||||
|
EndTime *values.DateTime `xml:"end,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used to restrict component filters to a property value
|
||||||
|
type PropertyFilter struct {
|
||||||
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav prop-filter"`
|
||||||
|
Name properties.PropertyName `xml:"name,attr"`
|
||||||
|
TextMatch *TextMatch `xml:",omitempty"`
|
||||||
|
ParameterFilter *ParameterFilter `xml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used to restrict component filters to a parameter value
|
||||||
|
type ParameterFilter struct {
|
||||||
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav param-filter"`
|
||||||
|
Name properties.ParameterName `xml:"name,attr"`
|
||||||
|
TextMatch *TextMatch `xml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used to match properties by text value
|
||||||
|
type TextMatch struct {
|
||||||
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav text-match"`
|
||||||
|
Collation values.TextCollation `xml:"collation,attr,omitempty"`
|
||||||
|
NegateCondition values.HumanBoolean `xml:"attr,negate-condition,omitempty"`
|
||||||
|
Content string `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new CalDAV property value matcher
|
||||||
|
func NewPropertyMatcher(name properties.PropertyName, content string) *PropertyFilter {
|
||||||
|
pf := new(PropertyFilter)
|
||||||
|
pf.Name = name
|
||||||
|
pf.TextMatch = new(TextMatch)
|
||||||
|
pf.TextMatch.Content = content
|
||||||
|
return pf
|
||||||
|
}
|
23
vendor/github.com/dolanor/caldav-go/caldav/entities/multistatus.go
generated
vendored
Normal file
23
vendor/github.com/dolanor/caldav-go/caldav/entities/multistatus.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import "encoding/xml"
|
||||||
|
|
||||||
|
// metadata about a property
|
||||||
|
type PropStat struct {
|
||||||
|
XMLName xml.Name `xml:"propstat"`
|
||||||
|
Status string `xml:"status"`
|
||||||
|
Prop *Prop `xml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// a multistatus response entity
|
||||||
|
type Response struct {
|
||||||
|
XMLName xml.Name `xml:"response"`
|
||||||
|
Href string `xml:"href"`
|
||||||
|
PropStats []*PropStat `xml:"propstat,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// a request to find properties on an an entity or collection
|
||||||
|
type Multistatus struct {
|
||||||
|
XMLName xml.Name `xml:"DAV: multistatus"`
|
||||||
|
Responses []*Response `xml:"response,omitempty"`
|
||||||
|
}
|
23
vendor/github.com/dolanor/caldav-go/caldav/entities/prop.go
generated
vendored
Normal file
23
vendor/github.com/dolanor/caldav-go/caldav/entities/prop.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"github.com/dolanor/caldav-go/webdav/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
// a CalDAV Property resource
|
||||||
|
type Prop struct {
|
||||||
|
XMLName xml.Name `xml:"DAV: prop"`
|
||||||
|
GetContentType string `xml:"getcontenttype,omitempty"`
|
||||||
|
DisplayName string `xml:"displayname,omitempty"`
|
||||||
|
CalendarData *CalendarData `xml:",omitempty"`
|
||||||
|
ResourceType *entities.ResourceType `xml:",omitempty"`
|
||||||
|
CTag string `xml:"http://calendarserver.org/ns/ getctag,omitempty"`
|
||||||
|
ETag string `xml:"http://calendarserver.org/ns/ getetag,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used to restrict properties returned in calendar data
|
||||||
|
type PropertyName struct {
|
||||||
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav prop"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
}
|
56
vendor/github.com/dolanor/caldav-go/caldav/request.go
generated
vendored
Normal file
56
vendor/github.com/dolanor/caldav-go/caldav/request.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package caldav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/dolanor/caldav-go/http"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"github.com/dolanor/caldav-go/webdav"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
// an CalDAV request object
|
||||||
|
type Request webdav.Request
|
||||||
|
|
||||||
|
// downcasts the request to the WebDAV interface
|
||||||
|
func (r *Request) WebDAV() *webdav.Request {
|
||||||
|
return (*webdav.Request)(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new CalDAV request object
|
||||||
|
func NewRequest(method string, urlstr string, icaldata ...interface{}) (*Request, error) {
|
||||||
|
if buffer, err := icalToReadCloser(icaldata...); err != nil {
|
||||||
|
return nil, utils.NewError(NewRequest, "unable to encode icalendar data", icaldata, err)
|
||||||
|
} else if r, err := http.NewRequest(method, urlstr, buffer); err != nil {
|
||||||
|
return nil, utils.NewError(NewRequest, "unable to create request", urlstr, err)
|
||||||
|
} else {
|
||||||
|
if buffer != nil {
|
||||||
|
// set the content type to XML if we have a body
|
||||||
|
r.Native().Header.Set("Content-Type", "text/calendar; charset=UTF-8")
|
||||||
|
}
|
||||||
|
return (*Request)(r), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func icalToReadCloser(icaldata ...interface{}) (io.ReadCloser, error) {
|
||||||
|
var buffer []string
|
||||||
|
for _, icaldatum := range icaldata {
|
||||||
|
if encoded, err := icalendar.Marshal(icaldatum); err != nil {
|
||||||
|
return nil, utils.NewError(icalToReadCloser, "unable to encode as icalendar data", icaldatum, err)
|
||||||
|
} else {
|
||||||
|
// log.Printf("OUT: %+v", encoded)
|
||||||
|
buffer = append(buffer, encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(buffer) > 0 {
|
||||||
|
var encoded = strings.Join(buffer, "\n")
|
||||||
|
return ioutil.NopCloser(bytes.NewBuffer([]byte(encoded))), nil
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
40
vendor/github.com/dolanor/caldav-go/caldav/response.go
generated
vendored
Normal file
40
vendor/github.com/dolanor/caldav-go/caldav/response.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package caldav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dolanor/caldav-go/icalendar"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"github.com/dolanor/caldav-go/webdav"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
// a WebDAV response object
|
||||||
|
type Response webdav.Response
|
||||||
|
|
||||||
|
// downcasts the response to the WebDAV interface
|
||||||
|
func (r *Response) WebDAV() *webdav.Response {
|
||||||
|
return (*webdav.Response)(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes a CalDAV iCalendar response into the provided interface
|
||||||
|
func (r *Response) Decode(into interface{}) error {
|
||||||
|
if body := r.Body; body == nil {
|
||||||
|
return nil
|
||||||
|
} else if encoded, err := ioutil.ReadAll(body); err != nil {
|
||||||
|
return utils.NewError(r.Decode, "unable to read response body", r, err)
|
||||||
|
} else {
|
||||||
|
// log.Printf("IN: %+v", string(encoded))
|
||||||
|
if err := icalendar.Unmarshal(string(encoded), into); err != nil {
|
||||||
|
return utils.NewError(r.Decode, "unable to decode response body", r, err)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new WebDAV response object
|
||||||
|
func NewResponse(response *webdav.Response) *Response {
|
||||||
|
return (*Response)(response)
|
||||||
|
}
|
30
vendor/github.com/dolanor/caldav-go/caldav/server.go
generated
vendored
Normal file
30
vendor/github.com/dolanor/caldav-go/caldav/server.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package caldav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"github.com/dolanor/caldav-go/webdav"
|
||||||
|
)
|
||||||
|
|
||||||
|
// a server that accepts CalDAV requests
|
||||||
|
type Server webdav.Server
|
||||||
|
|
||||||
|
// NewServer creates a reference to a CalDAV server.
|
||||||
|
// host is the url to access the server, stopping at the port:
|
||||||
|
// https://user:password@host:port/
|
||||||
|
func NewServer(host string) (*Server, error) {
|
||||||
|
if s, err := webdav.NewServer(host); err != nil {
|
||||||
|
return nil, utils.NewError(NewServer, "unable to create WebDAV server", host, err)
|
||||||
|
} else {
|
||||||
|
return (*Server)(s), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// downcasts the server to the WebDAV interface
|
||||||
|
func (s *Server) WebDAV() *webdav.Server {
|
||||||
|
return (*webdav.Server)(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new CalDAV request object
|
||||||
|
func (s *Server) NewRequest(method string, path string, icaldata ...interface{}) (*Request, error) {
|
||||||
|
return NewRequest(method, s.WebDAV().Http().AbsUrlStr(path), icaldata...)
|
||||||
|
}
|
8
vendor/github.com/dolanor/caldav-go/caldav/values/component_name.go
generated
vendored
Normal file
8
vendor/github.com/dolanor/caldav-go/caldav/values/component_name.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
type ComponentName string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CalendarComponentName ComponentName = "VCALENDAR"
|
||||||
|
EventComponentName = "VEVENT"
|
||||||
|
)
|
31
vendor/github.com/dolanor/caldav-go/caldav/values/datetime.go
generated
vendored
Normal file
31
vendor/github.com/dolanor/caldav-go/caldav/values/datetime.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/values"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// a representation of a date and time for iCalendar
|
||||||
|
type DateTime struct {
|
||||||
|
name string
|
||||||
|
t time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new caldav datetime representation, must be in UTC
|
||||||
|
func NewDateTime(name string, t time.Time) (*DateTime, error) {
|
||||||
|
if t.Location() != time.UTC {
|
||||||
|
return nil, errors.New("CalDAV datetime must be in UTC")
|
||||||
|
} else {
|
||||||
|
return &DateTime{name: name, t: t.Truncate(time.Second)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the datetime value for the iCalendar specification
|
||||||
|
func (d *DateTime) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
|
||||||
|
layout := values.UTCDateTimeFormatString
|
||||||
|
value := d.t.Format(layout)
|
||||||
|
attr := xml.Attr{Name: name, Value: value}
|
||||||
|
return attr, nil
|
||||||
|
}
|
8
vendor/github.com/dolanor/caldav-go/caldav/values/human_boolean.go
generated
vendored
Normal file
8
vendor/github.com/dolanor/caldav-go/caldav/values/human_boolean.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
type HumanBoolean string
|
||||||
|
|
||||||
|
const (
|
||||||
|
YesHumanBoolean HumanBoolean = "yes"
|
||||||
|
NoHumanBoolean = "no"
|
||||||
|
)
|
8
vendor/github.com/dolanor/caldav-go/caldav/values/text_collation.go
generated
vendored
Normal file
8
vendor/github.com/dolanor/caldav-go/caldav/values/text_collation.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
type TextCollation string
|
||||||
|
|
||||||
|
const (
|
||||||
|
OctetTextCollation TextCollation = "i;octet"
|
||||||
|
ASCIICaseMapCollation = "i;ascii-casemap"
|
||||||
|
)
|
53
vendor/github.com/dolanor/caldav-go/http/client.go
generated
vendored
Normal file
53
vendor/github.com/dolanor/caldav-go/http/client.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// a client for making HTTP requests
|
||||||
|
type Client struct {
|
||||||
|
native *http.Client
|
||||||
|
server *Server
|
||||||
|
requestHeaders map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetHeader(key string, value string) {
|
||||||
|
if c.requestHeaders == nil {
|
||||||
|
c.requestHeaders = map[string]string{}
|
||||||
|
}
|
||||||
|
c.requestHeaders[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// downcasts to the native HTTP interface
|
||||||
|
func (c *Client) Native() *http.Client {
|
||||||
|
return c.native
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the embedded HTTP server reference
|
||||||
|
func (c *Client) Server() *Server {
|
||||||
|
return c.server
|
||||||
|
}
|
||||||
|
|
||||||
|
// executes an HTTP request
|
||||||
|
func (c *Client) Do(req *Request) (*Response, error) {
|
||||||
|
for key, value := range c.requestHeaders {
|
||||||
|
req.Header.Add(key, value)
|
||||||
|
}
|
||||||
|
if resp, err := c.Native().Do((*http.Request)(req)); err != nil {
|
||||||
|
return nil, utils.NewError(c.Do, "unable to execute HTTP request", c, err)
|
||||||
|
} else {
|
||||||
|
return NewResponse(resp), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new client for communicating with an HTTP server
|
||||||
|
func NewClient(server *Server, native *http.Client) *Client {
|
||||||
|
return &Client{server: server, native: native}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new client for communicating with a server
|
||||||
|
// uses the default HTTP client from net/http
|
||||||
|
func NewDefaultClient(server *Server) *Client {
|
||||||
|
return NewClient(server, http.DefaultClient)
|
||||||
|
}
|
39
vendor/github.com/dolanor/caldav-go/http/request.go
generated
vendored
Normal file
39
vendor/github.com/dolanor/caldav-go/http/request.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// an HTTP request object
|
||||||
|
type Request http.Request
|
||||||
|
|
||||||
|
// downcasts the request to the native HTTP interface
|
||||||
|
func (r *Request) Native() *http.Request {
|
||||||
|
return (*http.Request)(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new HTTP request object
|
||||||
|
func NewRequest(method string, urlstr string, body ...io.ReadCloser) (*Request, error) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var r = new(http.Request)
|
||||||
|
|
||||||
|
if len(body) > 0 && body[0] != nil {
|
||||||
|
r, err = http.NewRequest(method, urlstr, body[0])
|
||||||
|
} else {
|
||||||
|
r, err = http.NewRequest(method, urlstr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, utils.NewError(NewRequest, "unable to create request", urlstr, err)
|
||||||
|
} else if auth := r.URL.User; auth != nil {
|
||||||
|
pass, _ := auth.Password()
|
||||||
|
r.SetBasicAuth(auth.Username(), pass)
|
||||||
|
r.URL.User = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return (*Request)(r), nil
|
||||||
|
|
||||||
|
}
|
18
vendor/github.com/dolanor/caldav-go/http/response.go
generated
vendored
Normal file
18
vendor/github.com/dolanor/caldav-go/http/response.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// an HTTP response object
|
||||||
|
type Response http.Response
|
||||||
|
|
||||||
|
// downcasts the response to the native HTTP interface
|
||||||
|
func (r *Response) Native() *http.Response {
|
||||||
|
return (*http.Response)(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new HTTP response object
|
||||||
|
func NewResponse(response *http.Response) *Response {
|
||||||
|
return (*Response)(response)
|
||||||
|
}
|
48
vendor/github.com/dolanor/caldav-go/http/server.go
generated
vendored
Normal file
48
vendor/github.com/dolanor/caldav-go/http/server.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
spath "path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
// a server that accepts HTTP requests
|
||||||
|
type Server struct {
|
||||||
|
baseUrl *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a reference to an http server
|
||||||
|
func NewServer(baseUrlStr string) (*Server, error) {
|
||||||
|
var err error
|
||||||
|
var s = new(Server)
|
||||||
|
if s.baseUrl, err = url.Parse(baseUrlStr); err != nil {
|
||||||
|
return nil, utils.NewError(NewServer, "unable to parse server base url", baseUrlStr, err)
|
||||||
|
} else {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// converts a path name to an absolute URL
|
||||||
|
func (s *Server) UserInfo() *url.Userinfo {
|
||||||
|
return s.baseUrl.User
|
||||||
|
}
|
||||||
|
|
||||||
|
// converts a path name to an absolute URL
|
||||||
|
func (s *Server) AbsUrlStr(path string) string {
|
||||||
|
uri := *s.baseUrl
|
||||||
|
uri.Path = spath.Join(uri.Path, path)
|
||||||
|
if strings.HasSuffix(path, "/") {
|
||||||
|
uri.Path = uri.Path + "/"
|
||||||
|
}
|
||||||
|
return uri.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new HTTP request object
|
||||||
|
func (s *Server) NewRequest(method string, path string, body ...io.ReadCloser) (*Request, error) {
|
||||||
|
return NewRequest(method, s.AbsUrlStr(path), body...)
|
||||||
|
}
|
87
vendor/github.com/dolanor/caldav-go/icalendar/components/calendar.go
generated
vendored
Normal file
87
vendor/github.com/dolanor/caldav-go/icalendar/components/calendar.go
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/values"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Calendar struct {
|
||||||
|
|
||||||
|
// specifies the identifier corresponding to the highest version number or the minimum and maximum
|
||||||
|
// range of the iCalendar specification that is required in order to interpret the iCalendar object.
|
||||||
|
Version string `ical:",2.0"`
|
||||||
|
|
||||||
|
// specifies the identifier for the product that created the iCalendar object
|
||||||
|
ProductId string `ical:"prodid,-//dolanor/caldav-go//NONSGML v1.0.0//EN"`
|
||||||
|
|
||||||
|
// specifies the text value that uniquely identifies the "VTIMEZONE" calendar component.
|
||||||
|
TimeZoneId string `ical:"tzid,omitempty"`
|
||||||
|
|
||||||
|
// defines the iCalendar object method associated with the calendar object.
|
||||||
|
Method values.Method `ical:",omitempty"`
|
||||||
|
|
||||||
|
// defines the calendar scale used for the calendar information specified in the iCalendar object.
|
||||||
|
CalScale values.CalScale `ical:",omitempty"`
|
||||||
|
|
||||||
|
// defines the different timezones used by the various components nested within
|
||||||
|
TimeZones []*TimeZone `ical:",omitempty"`
|
||||||
|
|
||||||
|
// unique events to be stored together in the icalendar file
|
||||||
|
Events []*Event `ical:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calendar) UseTimeZone(location *time.Location) *TimeZone {
|
||||||
|
tz := NewDynamicTimeZone(location)
|
||||||
|
c.TimeZones = append(c.TimeZones, tz)
|
||||||
|
c.TimeZoneId = tz.Id
|
||||||
|
return tz
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calendar) UsingTimeZone() bool {
|
||||||
|
return len(c.TimeZoneId) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calendar) UsingGlobalTimeZone() bool {
|
||||||
|
return c.UsingTimeZone() && c.TimeZoneId[0] == '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calendar) ValidateICalValue() error {
|
||||||
|
|
||||||
|
for i, e := range c.Events {
|
||||||
|
|
||||||
|
if e == nil {
|
||||||
|
continue // skip nil events
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := e.ValidateICalValue(); err != nil {
|
||||||
|
msg := fmt.Sprintf("event %d failed validation", i)
|
||||||
|
return utils.NewError(c.ValidateICalValue, msg, c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.DateStart == nil && c.Method == "" {
|
||||||
|
msg := fmt.Sprintf("no value for method and no start date defined on event %d", i)
|
||||||
|
return utils.NewError(c.ValidateICalValue, msg, c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.UsingTimeZone() && !c.UsingGlobalTimeZone() {
|
||||||
|
for i, t := range c.TimeZones {
|
||||||
|
if t == nil || t.Id != c.TimeZoneId {
|
||||||
|
msg := fmt.Sprintf("timezone ID does not match timezone %d", i)
|
||||||
|
return utils.NewError(c.ValidateICalValue, msg, c, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCalendar(events ...*Event) *Calendar {
|
||||||
|
cal := new(Calendar)
|
||||||
|
cal.Events = events
|
||||||
|
return cal
|
||||||
|
}
|
172
vendor/github.com/dolanor/caldav-go/icalendar/components/event.go
generated
vendored
Normal file
172
vendor/github.com/dolanor/caldav-go/icalendar/components/event.go
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/values"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
|
||||||
|
// defines the persistent, globally unique identifier for the calendar component.
|
||||||
|
UID string `ical:",required"`
|
||||||
|
|
||||||
|
// indicates the date/time that the instance of the iCalendar object was created.
|
||||||
|
DateStamp *values.DateTime `ical:"dtstamp,required"`
|
||||||
|
|
||||||
|
// specifies when the calendar component begins.
|
||||||
|
DateStart *values.DateTime `ical:"dtstart,required"`
|
||||||
|
|
||||||
|
// specifies the date and time that a calendar component ends.
|
||||||
|
DateEnd *values.DateTime `ical:"dtend,omitempty"`
|
||||||
|
|
||||||
|
// specifies a positive duration of time.
|
||||||
|
Duration *values.Duration `ical:",omitempty"`
|
||||||
|
|
||||||
|
// defines the access classification for a calendar component.
|
||||||
|
AccessClassification values.EventAccessClassification `ical:"class,omitempty"`
|
||||||
|
|
||||||
|
// specifies the date and time that the calendar information was created by the calendar user agent in the
|
||||||
|
// calendar store.
|
||||||
|
// Note: This is analogous to the creation date and time for a file in the file system.
|
||||||
|
Created *values.DateTime `ical:",omitempty"`
|
||||||
|
|
||||||
|
// provides a more complete description of the calendar component, than that provided by the Summary property.
|
||||||
|
Description string `ical:",omitempty"`
|
||||||
|
|
||||||
|
// specifies information related to the global position for the activity specified by a calendar component.
|
||||||
|
Geo *values.Geo `ical:",omitempty"`
|
||||||
|
|
||||||
|
// specifies the date and time that the information associated with the calendar component was last revised in the
|
||||||
|
// calendar store.
|
||||||
|
// Note: This is analogous to the modification date and time for a file in the file system.
|
||||||
|
LastModified *values.DateTime `ical:"last-modified,omitempty"`
|
||||||
|
|
||||||
|
// defines the intended venue for the activity defined by a calendar component.
|
||||||
|
Location *values.Location `ical:",omitempty"`
|
||||||
|
|
||||||
|
// defines the organizer for a calendar component.
|
||||||
|
Organizer *values.OrganizerContact `ical:",omitempty"`
|
||||||
|
|
||||||
|
// defines the relative priority for a calendar component.
|
||||||
|
Priority int `ical:",omitempty"`
|
||||||
|
|
||||||
|
// defines the revision sequence number of the calendar component within a sequence of revisions.
|
||||||
|
Sequence int `ical:",omitempty"`
|
||||||
|
|
||||||
|
// efines the overall status or confirmation for the calendar component.
|
||||||
|
Status values.EventStatus `ical:",omitempty"`
|
||||||
|
|
||||||
|
// defines a short summary or subject for the calendar component.
|
||||||
|
Summary string `ical:",omitempty"`
|
||||||
|
|
||||||
|
// defines whether an event is transparent or not to busy time searches.
|
||||||
|
values.TimeTransparency `ical:"transp,omitempty"`
|
||||||
|
|
||||||
|
// defines a Uniform Resource Locator (URL) associated with the iCalendar object.
|
||||||
|
Url *values.Url `ical:",omitempty"`
|
||||||
|
|
||||||
|
// used in conjunction with the "UID" and "SEQUENCE" property to identify a specific instance of a recurring
|
||||||
|
// event calendar component. The property value is the effective value of the DateStart property of the
|
||||||
|
// recurrence instance.
|
||||||
|
RecurrenceId *values.DateTime `ical:"recurrence_id,omitempty"`
|
||||||
|
|
||||||
|
// defines a rule or repeating pattern for recurring events, to-dos, or time zone definitions.
|
||||||
|
RecurrenceRules []*values.RecurrenceRule `ical:",omitempty"`
|
||||||
|
|
||||||
|
// property provides the capability to associate a document object with a calendar component.
|
||||||
|
Attachment *values.Url `ical:"attach,omitempty"`
|
||||||
|
|
||||||
|
// defines an "Attendee" within a calendar component.
|
||||||
|
Attendees []*values.AttendeeContact `ical:",omitempty"`
|
||||||
|
|
||||||
|
// defines the categories for a calendar component.
|
||||||
|
Categories *values.CSV `ical:",omitempty"`
|
||||||
|
|
||||||
|
// specifies non-processing information intended to provide a comment to the calendar user.
|
||||||
|
Comments []values.Comment `ical:",omitempty"`
|
||||||
|
|
||||||
|
// used to represent contact information or alternately a reference to contact information associated with the calendar component.
|
||||||
|
ContactInfo *values.CSV `ical:"contact,omitempty"`
|
||||||
|
|
||||||
|
// defines the list of date/time exceptions for a recurring calendar component.
|
||||||
|
*values.ExceptionDateTimes `ical:",omitempty"`
|
||||||
|
|
||||||
|
// defines the list of date/times for a recurrence set.
|
||||||
|
*values.RecurrenceDateTimes `ical:",omitempty"`
|
||||||
|
|
||||||
|
// used to represent a relationship or reference between one calendar component and another.
|
||||||
|
RelatedTo *values.Url `ical:"related-to,omitempty"`
|
||||||
|
|
||||||
|
// defines the equipment or resources anticipated for an activity specified by a calendar entity.
|
||||||
|
Resources *values.CSV `ical:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates the event internals
|
||||||
|
func (e *Event) ValidateICalValue() error {
|
||||||
|
|
||||||
|
if e.UID == "" {
|
||||||
|
return utils.NewError(e.ValidateICalValue, "the UID value must be set", e, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.DateStart == nil {
|
||||||
|
return utils.NewError(e.ValidateICalValue, "event start date must be set", e, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.DateEnd == nil && e.Duration == nil {
|
||||||
|
return utils.NewError(e.ValidateICalValue, "event end date or duration must be set", e, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.DateEnd != nil && e.Duration != nil {
|
||||||
|
return utils.NewError(e.ValidateICalValue, "event end date and duration are mutually exclusive fields", e, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// adds one or more recurrence rule to the event
|
||||||
|
func (e *Event) AddRecurrenceRules(r ...*values.RecurrenceRule) {
|
||||||
|
e.RecurrenceRules = append(e.RecurrenceRules, r...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// adds one or more recurrence rule exception to the event
|
||||||
|
func (e *Event) AddRecurrenceExceptions(d ...*values.DateTime) {
|
||||||
|
if e.ExceptionDateTimes == nil {
|
||||||
|
e.ExceptionDateTimes = new(values.ExceptionDateTimes)
|
||||||
|
}
|
||||||
|
*e.ExceptionDateTimes = append(*e.ExceptionDateTimes, d...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks to see if the event is a recurrence
|
||||||
|
func (e *Event) IsRecurrence() bool {
|
||||||
|
return e.RecurrenceId != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks to see if the event is a recurrence override
|
||||||
|
func (e *Event) IsOverride() bool {
|
||||||
|
return e.IsRecurrence() && !e.RecurrenceId.Equals(e.DateStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new iCalendar event with no end time
|
||||||
|
func NewEvent(uid string, start time.Time) *Event {
|
||||||
|
e := new(Event)
|
||||||
|
e.UID = uid
|
||||||
|
e.DateStamp = values.NewDateTime(time.Now().UTC())
|
||||||
|
e.DateStart = values.NewDateTime(start)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new iCalendar event that lasts a certain duration
|
||||||
|
func NewEventWithDuration(uid string, start time.Time, duration time.Duration) *Event {
|
||||||
|
e := NewEvent(uid, start)
|
||||||
|
e.Duration = values.NewDuration(duration)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new iCalendar event that has an explicit start and end time
|
||||||
|
func NewEventWithEnd(uid string, start time.Time, end time.Time) *Event {
|
||||||
|
e := NewEvent(uid, start)
|
||||||
|
e.DateEnd = values.NewDateTime(end)
|
||||||
|
return e
|
||||||
|
}
|
40
vendor/github.com/dolanor/caldav-go/icalendar/components/timezone.go
generated
vendored
Normal file
40
vendor/github.com/dolanor/caldav-go/icalendar/components/timezone.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/values"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimeZone struct {
|
||||||
|
|
||||||
|
// defines the persistent, globally unique identifier for the calendar component.
|
||||||
|
Id string `ical:"tzid,required"`
|
||||||
|
|
||||||
|
// the location name, as defined by the standards body
|
||||||
|
ExtLocationName string `ical:"x-lic-location,omitempty"`
|
||||||
|
|
||||||
|
// defines a Uniform Resource Locator (URL) associated with the iCalendar object.
|
||||||
|
Url *values.Url `ical:"tzurl,omitempty"`
|
||||||
|
|
||||||
|
// specifies the date and time that the information associated with the calendar component was last revised in the
|
||||||
|
// calendar store.
|
||||||
|
// Note: This is analogous to the modification date and time for a file in the file system.
|
||||||
|
LastModified *values.DateTime `ical:"last-modified,omitempty"`
|
||||||
|
|
||||||
|
// TODO need to figure out how to handle standard and daylight savings time
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDynamicTimeZone(location *time.Location) *TimeZone {
|
||||||
|
t := new(TimeZone)
|
||||||
|
t.Id = location.String()
|
||||||
|
t.ExtLocationName = location.String()
|
||||||
|
t.Url = values.NewUrl(url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "tzurl.org",
|
||||||
|
Path: fmt.Sprintf("/zoneinfo/%s", t.Id),
|
||||||
|
})
|
||||||
|
return t
|
||||||
|
}
|
196
vendor/github.com/dolanor/caldav-go/icalendar/marshal.go
generated
vendored
Normal file
196
vendor/github.com/dolanor/caldav-go/icalendar/marshal.go
generated
vendored
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
package icalendar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/properties"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Newline = "\r\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
type encoder func(reflect.Value) (string, error)
|
||||||
|
|
||||||
|
func tagAndJoinValue(v reflect.Value, in []string) (string, error) {
|
||||||
|
if tag, err := extractTagFromValue(v); err != nil {
|
||||||
|
return "", utils.NewError(tagAndJoinValue, "unable to extract tag from value", v, err)
|
||||||
|
} else {
|
||||||
|
var out []string
|
||||||
|
out = append(out, properties.MarshalProperty(properties.NewProperty("begin", tag)))
|
||||||
|
out = append(out, in...)
|
||||||
|
out = append(out, properties.MarshalProperty(properties.NewProperty("end", tag)))
|
||||||
|
return strings.Join(out, Newline), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalCollection(v reflect.Value) (string, error) {
|
||||||
|
|
||||||
|
var out []string
|
||||||
|
|
||||||
|
for i, n := 0, v.Len(); i < n; i++ {
|
||||||
|
vi := v.Index(i).Interface()
|
||||||
|
if encoded, err := Marshal(vi); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to encode interface at index %d", i)
|
||||||
|
return "", utils.NewError(marshalCollection, msg, vi, err)
|
||||||
|
} else if encoded != "" {
|
||||||
|
out = append(out, encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(out, Newline), nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalStruct(v reflect.Value) (string, error) {
|
||||||
|
|
||||||
|
var out []string
|
||||||
|
|
||||||
|
// iterate over all fields
|
||||||
|
vtype := v.Type()
|
||||||
|
n := vtype.NumField()
|
||||||
|
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
|
||||||
|
// keep a reference to the field value and definition
|
||||||
|
fv := v.Field(i)
|
||||||
|
fs := vtype.Field(i)
|
||||||
|
|
||||||
|
// use the field definition to extract out property defaults
|
||||||
|
p := properties.PropertyFromStructField(fs)
|
||||||
|
if p == nil {
|
||||||
|
continue // skip explicitly ignored fields and private members
|
||||||
|
}
|
||||||
|
|
||||||
|
fi := fv.Interface()
|
||||||
|
|
||||||
|
// some fields are not properties, but actually nested objects.
|
||||||
|
// detect those early using the property and object encoder...
|
||||||
|
if _, ok := fi.(properties.CanEncodeValue); !ok && !isInvalidOrEmptyValue(fv) {
|
||||||
|
if encoded, err := encode(fv, objectEncoder); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to encode field %s", fs.Name)
|
||||||
|
return "", utils.NewError(marshalStruct, msg, v.Interface(), err)
|
||||||
|
} else if encoded != "" {
|
||||||
|
// encoding worked! no need to process as a property
|
||||||
|
out = append(out, encoded)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now check to see if the field value overrides the defaults...
|
||||||
|
if !isInvalidOrEmptyValue(fv) {
|
||||||
|
// first, check the field value interface for overrides...
|
||||||
|
if overrides, err := properties.PropertyFromInterface(fi); err != nil {
|
||||||
|
msg := fmt.Sprintf("field %s failed validation", fs.Name)
|
||||||
|
return "", utils.NewError(marshalStruct, msg, v.Interface(), err)
|
||||||
|
} else if p.Merge(overrides); p.Value == "" {
|
||||||
|
// then, if we couldn't find an override from the interface,
|
||||||
|
// try the simple string encoder...
|
||||||
|
if p.Value, err = stringEncoder(fv); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to encode field %s", fs.Name)
|
||||||
|
return "", utils.NewError(marshalStruct, msg, v.Interface(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we have a value by this point
|
||||||
|
if !p.HasNameAndValue() {
|
||||||
|
if p.OmitEmpty {
|
||||||
|
continue
|
||||||
|
} else if p.DefaultValue != "" {
|
||||||
|
p.Value = p.DefaultValue
|
||||||
|
} else if p.Required {
|
||||||
|
msg := fmt.Sprintf("missing value for required field %s", fs.Name)
|
||||||
|
return "", utils.NewError(Marshal, msg, v.Interface(), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode in the property
|
||||||
|
out = append(out, properties.MarshalProperty(p))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap the fields in the enclosing struct tags
|
||||||
|
return tagAndJoinValue(v, out)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func objectEncoder(v reflect.Value) (string, error) {
|
||||||
|
|
||||||
|
// decompose the value into its interface parts
|
||||||
|
v = dereferencePointerValue(v)
|
||||||
|
|
||||||
|
// encode the value based off of its type
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Array:
|
||||||
|
return marshalCollection(v)
|
||||||
|
case reflect.Struct:
|
||||||
|
return marshalStruct(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringEncoder(v reflect.Value) (string, error) {
|
||||||
|
return fmt.Sprintf("%v", v.Interface()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func propertyEncoder(v reflect.Value) (string, error) {
|
||||||
|
|
||||||
|
vi := v.Interface()
|
||||||
|
if p, err := properties.PropertyFromInterface(vi); err != nil {
|
||||||
|
|
||||||
|
// return early if interface fails its own validation
|
||||||
|
return "", err
|
||||||
|
|
||||||
|
} else if p.HasNameAndValue() {
|
||||||
|
|
||||||
|
// if an interface encodes its own name and value, it's a property
|
||||||
|
return properties.MarshalProperty(p), nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(v reflect.Value, encoders ...encoder) (string, error) {
|
||||||
|
|
||||||
|
for _, encode := range encoders {
|
||||||
|
if encoded, err := encode(v); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if encoded != "" {
|
||||||
|
return encoded, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// converts an iCalendar component into its string representation
|
||||||
|
func Marshal(target interface{}) (string, error) {
|
||||||
|
|
||||||
|
// don't do anything with invalid interfaces
|
||||||
|
v := reflect.ValueOf(target)
|
||||||
|
if isInvalidOrEmptyValue(v) {
|
||||||
|
return "", utils.NewError(Marshal, "unable to marshal empty or invalid values", target, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoded, err := encode(v, propertyEncoder, objectEncoder, stringEncoder); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if encoded == "" {
|
||||||
|
return "", utils.NewError(Marshal, "unable to encode interface, all methods exhausted", v.Interface(), nil)
|
||||||
|
} else {
|
||||||
|
return encoded, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
vendor/github.com/dolanor/caldav-go/icalendar/properties/interfaces.go
generated
vendored
Normal file
29
vendor/github.com/dolanor/caldav-go/icalendar/properties/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package properties
|
||||||
|
|
||||||
|
type CanValidateValue interface {
|
||||||
|
ValidateICalValue() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type CanDecodeValue interface {
|
||||||
|
DecodeICalValue(string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type CanDecodeParams interface {
|
||||||
|
DecodeICalParams(Params) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type CanEncodeTag interface {
|
||||||
|
EncodeICalTag() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CanEncodeValue interface {
|
||||||
|
EncodeICalValue() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CanEncodeName interface {
|
||||||
|
EncodeICalName() (PropertyName, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CanEncodeParams interface {
|
||||||
|
EncodeICalParams() (Params, error)
|
||||||
|
}
|
31
vendor/github.com/dolanor/caldav-go/icalendar/properties/names.go
generated
vendored
Normal file
31
vendor/github.com/dolanor/caldav-go/icalendar/properties/names.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package properties
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type PropertyName string
|
||||||
|
|
||||||
|
const (
|
||||||
|
UIDPropertyName PropertyName = "UID"
|
||||||
|
CommentPropertyName = "COMMENT"
|
||||||
|
OrganizerPropertyName = "ORGANIZER"
|
||||||
|
AttendeePropertyName = "ATTENDEE"
|
||||||
|
ExceptionDateTimesPropertyName = "EXDATE"
|
||||||
|
RecurrenceDateTimesPropertyName = "RDATE"
|
||||||
|
RecurrenceRulePropertyName = "RRULE"
|
||||||
|
LocationPropertyName = "LOCATION"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ParameterName string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CanonicalNameParameterName ParameterName = "CN"
|
||||||
|
TimeZoneIdPropertyName = "TZID"
|
||||||
|
ValuePropertyName = "VALUE"
|
||||||
|
AlternateRepresentationName = "ALTREP"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Params map[ParameterName]string
|
||||||
|
|
||||||
|
func (p PropertyName) Equals(test string) bool {
|
||||||
|
return strings.EqualFold(string(p), test)
|
||||||
|
}
|
177
vendor/github.com/dolanor/caldav-go/icalendar/properties/property.go
generated
vendored
Normal file
177
vendor/github.com/dolanor/caldav-go/icalendar/properties/property.go
generated
vendored
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package properties
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
var propNameSanitizer = strings.NewReplacer(
|
||||||
|
"_", "-",
|
||||||
|
":", "\\:",
|
||||||
|
)
|
||||||
|
|
||||||
|
var propValueSanitizer = strings.NewReplacer(
|
||||||
|
"\"", "'",
|
||||||
|
"\\", "\\\\",
|
||||||
|
"\n", "\\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
var propNameDesanitizer = strings.NewReplacer(
|
||||||
|
"-", "_",
|
||||||
|
"\\:", ":",
|
||||||
|
)
|
||||||
|
|
||||||
|
var propValueDesanitizer = strings.NewReplacer(
|
||||||
|
"'", "\"",
|
||||||
|
"\\\\", "\\",
|
||||||
|
"\\n", "\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
type Property struct {
|
||||||
|
Name PropertyName
|
||||||
|
Value, DefaultValue string
|
||||||
|
Params Params
|
||||||
|
OmitEmpty, Required bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Property) HasNameAndValue() bool {
|
||||||
|
return p.Name != "" && p.Value != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Property) Merge(override *Property) {
|
||||||
|
if override.Name != "" {
|
||||||
|
p.Name = override.Name
|
||||||
|
}
|
||||||
|
if override.Value != "" {
|
||||||
|
p.Value = override.Value
|
||||||
|
}
|
||||||
|
if override.Params != nil {
|
||||||
|
p.Params = override.Params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PropertyFromStructField(fs reflect.StructField) (p *Property) {
|
||||||
|
|
||||||
|
ftag := fs.Tag.Get("ical")
|
||||||
|
if fs.PkgPath != "" || ftag == "-" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p = new(Property)
|
||||||
|
|
||||||
|
// parse the field tag
|
||||||
|
if ftag != "" {
|
||||||
|
tags := strings.Split(ftag, ",")
|
||||||
|
p.Name = PropertyName(tags[0])
|
||||||
|
if len(tags) > 1 {
|
||||||
|
if tags[1] == "omitempty" {
|
||||||
|
p.OmitEmpty = true
|
||||||
|
} else if tags[1] == "required" {
|
||||||
|
p.Required = true
|
||||||
|
} else {
|
||||||
|
p.DefaultValue = tags[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we have a name
|
||||||
|
if p.Name == "" {
|
||||||
|
p.Name = PropertyName(fs.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Name = PropertyName(strings.ToUpper(string(p.Name)))
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarshalProperty(p *Property) string {
|
||||||
|
name := strings.ToUpper(propNameSanitizer.Replace(string(p.Name)))
|
||||||
|
value := propValueSanitizer.Replace(p.Value)
|
||||||
|
keys := []string{name}
|
||||||
|
for name, value := range p.Params {
|
||||||
|
name = ParameterName(strings.ToUpper(propNameSanitizer.Replace(string(name))))
|
||||||
|
value = propValueSanitizer.Replace(value)
|
||||||
|
if strings.ContainsAny(value, " :") {
|
||||||
|
keys = append(keys, fmt.Sprintf("%s=\"%s\"", name, value))
|
||||||
|
} else {
|
||||||
|
keys = append(keys, fmt.Sprintf("%s=%s", name, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name = strings.Join(keys, ";")
|
||||||
|
return fmt.Sprintf("%s:%s", name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PropertyFromInterface(target interface{}) (p *Property, err error) {
|
||||||
|
|
||||||
|
var ierr error
|
||||||
|
if va, ok := target.(CanValidateValue); ok {
|
||||||
|
if ierr = va.ValidateICalValue(); ierr != nil {
|
||||||
|
err = utils.NewError(PropertyFromInterface, "interface failed validation", target, ierr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p = new(Property)
|
||||||
|
|
||||||
|
if enc, ok := target.(CanEncodeName); ok {
|
||||||
|
if p.Name, ierr = enc.EncodeICalName(); ierr != nil {
|
||||||
|
err = utils.NewError(PropertyFromInterface, "interface failed name encoding", target, ierr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if enc, ok := target.(CanEncodeParams); ok {
|
||||||
|
if p.Params, ierr = enc.EncodeICalParams(); ierr != nil {
|
||||||
|
err = utils.NewError(PropertyFromInterface, "interface failed params encoding", target, ierr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if enc, ok := target.(CanEncodeValue); ok {
|
||||||
|
if p.Value, ierr = enc.EncodeICalValue(); ierr != nil {
|
||||||
|
err = utils.NewError(PropertyFromInterface, "interface failed value encoding", target, ierr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalProperty(line string) *Property {
|
||||||
|
nvp := strings.SplitN(line, ":", 2)
|
||||||
|
prop := new(Property)
|
||||||
|
if len(nvp) > 1 {
|
||||||
|
prop.Value = strings.TrimSpace(nvp[1])
|
||||||
|
}
|
||||||
|
npp := strings.Split(nvp[0], ";")
|
||||||
|
if len(npp) > 1 {
|
||||||
|
prop.Params = make(map[ParameterName]string, 0)
|
||||||
|
for i := 1; i < len(npp); i++ {
|
||||||
|
var key, value string
|
||||||
|
kvp := strings.Split(npp[i], "=")
|
||||||
|
key = strings.TrimSpace(kvp[0])
|
||||||
|
key = propNameDesanitizer.Replace(key)
|
||||||
|
if len(kvp) > 1 {
|
||||||
|
value = strings.TrimSpace(kvp[1])
|
||||||
|
value = propValueDesanitizer.Replace(value)
|
||||||
|
value = strings.Trim(value, "\"")
|
||||||
|
}
|
||||||
|
prop.Params[ParameterName(key)] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prop.Name = PropertyName(strings.TrimSpace(npp[0]))
|
||||||
|
prop.Name = PropertyName(propNameDesanitizer.Replace(string(prop.Name)))
|
||||||
|
prop.Value = propValueDesanitizer.Replace(prop.Value)
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProperty(name, value string) *Property {
|
||||||
|
return &Property{Name: PropertyName(name), Value: value}
|
||||||
|
}
|
78
vendor/github.com/dolanor/caldav-go/icalendar/reflect.go
generated
vendored
Normal file
78
vendor/github.com/dolanor/caldav-go/icalendar/reflect.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package icalendar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/properties"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
func isInvalidOrEmptyValue(v reflect.Value) bool {
|
||||||
|
if !v.IsValid() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
return v.IsNil()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func newValue(in reflect.Value) (out reflect.Value, isArrayElement bool) {
|
||||||
|
|
||||||
|
typ := in.Type()
|
||||||
|
kind := typ.Kind()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if kind == reflect.Array || kind == reflect.Slice {
|
||||||
|
isArrayElement = true
|
||||||
|
} else if kind != reflect.Ptr {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
typ = typ.Elem()
|
||||||
|
kind = typ.Kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
out = reflect.New(typ)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func dereferencePointerValue(v reflect.Value) reflect.Value {
|
||||||
|
for (v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr) && v.Elem().IsValid() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractTagFromValue(v reflect.Value) (string, error) {
|
||||||
|
|
||||||
|
vdref := dereferencePointerValue(v)
|
||||||
|
vtemp, _ := newValue(vdref)
|
||||||
|
|
||||||
|
if encoder, ok := vtemp.Interface().(properties.CanEncodeTag); ok {
|
||||||
|
if tag, err := encoder.EncodeICalTag(); err != nil {
|
||||||
|
return "", utils.NewError(extractTagFromValue, "unable to extract tag from interface", v.Interface(), err)
|
||||||
|
} else {
|
||||||
|
return strings.ToUpper(tag), nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
typ := vtemp.Elem().Type()
|
||||||
|
return strings.ToUpper(fmt.Sprintf("v%s", typ.Name())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
365
vendor/github.com/dolanor/caldav-go/icalendar/unmarshal.go
generated
vendored
Normal file
365
vendor/github.com/dolanor/caldav-go/icalendar/unmarshal.go
generated
vendored
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
package icalendar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/properties"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
var splitter = regexp.MustCompile("\r?\n")
|
||||||
|
|
||||||
|
type token struct {
|
||||||
|
name string
|
||||||
|
components map[string][]*token
|
||||||
|
properties map[properties.PropertyName][]*properties.Property
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenize(encoded string) (*token, error) {
|
||||||
|
if encoded = strings.TrimSpace(encoded); encoded == "" {
|
||||||
|
return nil, utils.NewError(tokenize, "no content to tokenize", encoded, nil)
|
||||||
|
}
|
||||||
|
return tokenizeSlice(splitter.Split(encoded, -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenizeSlice(slice []string, name ...string) (*token, error) {
|
||||||
|
|
||||||
|
tok := new(token)
|
||||||
|
size := len(slice)
|
||||||
|
|
||||||
|
if len(name) > 0 {
|
||||||
|
tok.name = name[0]
|
||||||
|
} else if size <= 0 {
|
||||||
|
return nil, utils.NewError(tokenizeSlice, "token has no content", slice, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
tok.properties = make(map[properties.PropertyName][]*properties.Property, 0)
|
||||||
|
tok.components = make(map[string][]*token, 0)
|
||||||
|
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
|
||||||
|
// Handle iCalendar's space-indented line break format
|
||||||
|
// See: https://www.ietf.org/rfc/rfc2445.txt section 4.1
|
||||||
|
// "a long line can be split between any two characters by inserting a CRLF immediately followed by a single
|
||||||
|
// linear white space character"
|
||||||
|
line := slice[i]
|
||||||
|
for ; i < size-1 && strings.HasPrefix(slice[i+1], " "); i++ {
|
||||||
|
next := slice[i+1]
|
||||||
|
line += next[1:len(next)]
|
||||||
|
}
|
||||||
|
|
||||||
|
prop := properties.UnmarshalProperty(line)
|
||||||
|
|
||||||
|
if prop.Name.Equals("begin") {
|
||||||
|
for j := i; j < size; j++ {
|
||||||
|
end := strings.Replace(line, "BEGIN", "END", 1)
|
||||||
|
if slice[j] == end {
|
||||||
|
if component, err := tokenizeSlice(slice[i+1:j], prop.Value); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to tokenize %s component", prop.Value)
|
||||||
|
return nil, utils.NewError(tokenizeSlice, msg, slice, err)
|
||||||
|
} else {
|
||||||
|
existing, _ := tok.components[prop.Value]
|
||||||
|
tok.components[prop.Value] = append(existing, component)
|
||||||
|
i = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if existing, ok := tok.properties[prop.Name]; ok {
|
||||||
|
tok.properties[prop.Name] = []*properties.Property{prop}
|
||||||
|
} else {
|
||||||
|
tok.properties[prop.Name] = append(existing, prop)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return tok, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func hydrateInterface(v reflect.Value, prop *properties.Property) (bool, error) {
|
||||||
|
|
||||||
|
// unable to decode into empty values
|
||||||
|
if isInvalidOrEmptyValue(v) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = v.Interface()
|
||||||
|
var hasValue = false
|
||||||
|
|
||||||
|
// decode a value if possible
|
||||||
|
if decoder, ok := i.(properties.CanDecodeValue); ok {
|
||||||
|
if err := decoder.DecodeICalValue(prop.Value); err != nil {
|
||||||
|
return false, utils.NewError(hydrateInterface, "error decoding property value", v, err)
|
||||||
|
} else {
|
||||||
|
hasValue = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode any params, if supported
|
||||||
|
if len(prop.Params) > 0 {
|
||||||
|
if decoder, ok := i.(properties.CanDecodeParams); ok {
|
||||||
|
if err := decoder.DecodeICalParams(prop.Params); err != nil {
|
||||||
|
return false, utils.NewError(hydrateInterface, "error decoding property parameters", v, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finish with any validation
|
||||||
|
if validator, ok := i.(properties.CanValidateValue); ok {
|
||||||
|
if err := validator.ValidateICalValue(); err != nil {
|
||||||
|
return false, utils.NewError(hydrateInterface, "error validating property value", v, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasValue, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func hydrateLiteral(v reflect.Value, prop *properties.Property) (reflect.Value, error) {
|
||||||
|
|
||||||
|
literal := dereferencePointerValue(v)
|
||||||
|
|
||||||
|
switch literal.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
if i, err := strconv.ParseBool(prop.Value); err != nil {
|
||||||
|
return literal, utils.NewError(hydrateLiteral, "unable to decode bool "+prop.Value, literal.Interface(), err)
|
||||||
|
} else {
|
||||||
|
literal.SetBool(i)
|
||||||
|
}
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
if i, err := strconv.ParseInt(prop.Value, 10, 64); err != nil {
|
||||||
|
return literal, utils.NewError(hydrateLiteral, "unable to decode int "+prop.Value, literal.Interface(), err)
|
||||||
|
} else {
|
||||||
|
literal.SetInt(i)
|
||||||
|
}
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
if i, err := strconv.ParseUint(prop.Value, 10, 64); err != nil {
|
||||||
|
return literal, utils.NewError(hydrateLiteral, "unable to decode uint "+prop.Value, literal.Interface(), err)
|
||||||
|
} else {
|
||||||
|
literal.SetUint(i)
|
||||||
|
}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
if i, err := strconv.ParseFloat(prop.Value, 64); err != nil {
|
||||||
|
return literal, utils.NewError(hydrateLiteral, "unable to decode float "+prop.Value, literal.Interface(), err)
|
||||||
|
} else {
|
||||||
|
literal.SetFloat(i)
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
literal.SetString(prop.Value)
|
||||||
|
default:
|
||||||
|
return literal, utils.NewError(hydrateLiteral, "unable to decode value as literal "+prop.Value, literal.Interface(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return literal, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func hydrateProperty(v reflect.Value, prop *properties.Property) error {
|
||||||
|
|
||||||
|
// check to see if the interface handles it's own hydration
|
||||||
|
if handled, err := hydrateInterface(v, prop); err != nil {
|
||||||
|
return utils.NewError(hydrateProperty, "unable to hydrate interface", v, err)
|
||||||
|
} else if handled {
|
||||||
|
return nil // exit early if handled by the interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got here, we need to create a new instance to
|
||||||
|
// set into the property.
|
||||||
|
var vnew, varr = newValue(v)
|
||||||
|
var vlit bool
|
||||||
|
|
||||||
|
// check to see if the new value handles it's own hydration
|
||||||
|
if handled, err := hydrateInterface(vnew, prop); err != nil {
|
||||||
|
return utils.NewError(hydrateProperty, "unable to hydrate new interface value", vnew, err)
|
||||||
|
} else if vlit = !handled; vlit {
|
||||||
|
// if not, treat it as a literal
|
||||||
|
if vnewlit, err := hydrateLiteral(vnew, prop); err != nil {
|
||||||
|
return utils.NewError(hydrateProperty, "unable to hydrate new literal value", vnew, err)
|
||||||
|
} else if _, err := hydrateInterface(vnewlit, prop); err != nil {
|
||||||
|
return utils.NewError(hydrateProperty, "unable to hydrate new literal interface value", vnewlit, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we can set the value
|
||||||
|
vnewval := dereferencePointerValue(vnew)
|
||||||
|
voldval := dereferencePointerValue(v)
|
||||||
|
|
||||||
|
// make sure we can set the new value into the provided pointer
|
||||||
|
|
||||||
|
if varr {
|
||||||
|
// for arrays, append the new value into the array structure
|
||||||
|
if !voldval.CanSet() {
|
||||||
|
return utils.NewError(hydrateProperty, "unable to set array value", v, nil)
|
||||||
|
} else {
|
||||||
|
voldval.Set(reflect.Append(voldval, vnewval))
|
||||||
|
}
|
||||||
|
} else if vlit {
|
||||||
|
// for literals, set the dereferenced value
|
||||||
|
if !voldval.CanSet() {
|
||||||
|
return utils.NewError(hydrateProperty, "unable to set literal value", v, nil)
|
||||||
|
} else {
|
||||||
|
voldval.Set(vnewval)
|
||||||
|
}
|
||||||
|
} else if !v.CanSet() {
|
||||||
|
return utils.NewError(hydrateProperty, "unable to set pointer value", v, nil)
|
||||||
|
} else {
|
||||||
|
// everything else should be a pointer, set it directly
|
||||||
|
v.Set(vnew)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func hydrateNestedComponent(v reflect.Value, component *token) error {
|
||||||
|
|
||||||
|
// create a new object to hold the property value
|
||||||
|
var vnew, varr = newValue(v)
|
||||||
|
if err := hydrateComponent(vnew, component); err != nil {
|
||||||
|
return utils.NewError(hydrateNestedComponent, "unable to decode component", component, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if varr {
|
||||||
|
// for arrays, append the new value into the array structure
|
||||||
|
voldval := dereferencePointerValue(v)
|
||||||
|
if !voldval.CanSet() {
|
||||||
|
return utils.NewError(hydrateNestedComponent, "unable to set array value", v, nil)
|
||||||
|
} else {
|
||||||
|
voldval.Set(reflect.Append(voldval, vnew))
|
||||||
|
}
|
||||||
|
} else if !v.CanSet() {
|
||||||
|
return utils.NewError(hydrateNestedComponent, "unable to set pointer value", v, nil)
|
||||||
|
} else {
|
||||||
|
// everything else should be a pointer, set it directly
|
||||||
|
v.Set(vnew)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func hydrateProperties(v reflect.Value, component *token) error {
|
||||||
|
|
||||||
|
vdref := dereferencePointerValue(v)
|
||||||
|
vtype := vdref.Type()
|
||||||
|
vkind := vdref.Kind()
|
||||||
|
|
||||||
|
if vkind != reflect.Struct {
|
||||||
|
return utils.NewError(hydrateProperties, "unable to hydrate properties of non-struct", v, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
n := vtype.NumField()
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
|
||||||
|
prop := properties.PropertyFromStructField(vtype.Field(i))
|
||||||
|
if prop == nil {
|
||||||
|
continue // skip if field is ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
vfield := vdref.Field(i)
|
||||||
|
|
||||||
|
// first try to hydrate property values
|
||||||
|
if properties, ok := component.properties[prop.Name]; ok {
|
||||||
|
for _, prop := range properties {
|
||||||
|
if err := hydrateProperty(vfield, prop); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to hydrate property %s", prop.Name)
|
||||||
|
return utils.NewError(hydrateProperties, msg, v, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// then try to hydrate components
|
||||||
|
vtemp, _ := newValue(vfield)
|
||||||
|
if tag, err := extractTagFromValue(vtemp); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to extract tag from property %s", prop.Name)
|
||||||
|
return utils.NewError(hydrateProperties, msg, v, err)
|
||||||
|
} else if components, ok := component.components[tag]; ok {
|
||||||
|
for _, comp := range components {
|
||||||
|
if err := hydrateNestedComponent(vfield, comp); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to hydrate component %s", prop.Name)
|
||||||
|
return utils.NewError(hydrateProperties, msg, v, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func hydrateComponent(v reflect.Value, component *token) error {
|
||||||
|
if tag, err := extractTagFromValue(v); err != nil {
|
||||||
|
return utils.NewError(hydrateComponent, "error extracting tag from value", component, err)
|
||||||
|
} else if tag != component.name {
|
||||||
|
msg := fmt.Sprintf("expected %s and found %s", tag, component.name)
|
||||||
|
return utils.NewError(hydrateComponent, msg, component, nil)
|
||||||
|
} else if err := hydrateProperties(v, component); err != nil {
|
||||||
|
return utils.NewError(hydrateComponent, "unable to hydrate properties", component, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hydrateComponents(v reflect.Value, components []*token) error {
|
||||||
|
vdref := dereferencePointerValue(v)
|
||||||
|
for i, component := range components {
|
||||||
|
velem := reflect.New(vdref.Type().Elem())
|
||||||
|
if err := hydrateComponent(velem, component); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to hydrate component %d", i)
|
||||||
|
return utils.NewError(hydrateComponent, msg, component, err)
|
||||||
|
} else {
|
||||||
|
v.Set(reflect.Append(vdref, velem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hydrateValue(v reflect.Value, component *token) error {
|
||||||
|
|
||||||
|
if !v.IsValid() || v.Kind() != reflect.Ptr {
|
||||||
|
return utils.NewError(hydrateValue, "unmarshal target must be a valid pointer", v, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle any encodable properties
|
||||||
|
if encoder, isprop := v.Interface().(properties.CanEncodeName); isprop {
|
||||||
|
if name, err := encoder.EncodeICalName(); err != nil {
|
||||||
|
return utils.NewError(hydrateValue, "unable to lookup property name", v, err)
|
||||||
|
} else if properties, found := component.properties[name]; !found || len(properties) == 0 {
|
||||||
|
return utils.NewError(hydrateValue, "no matching propery values found for "+string(name), v, nil)
|
||||||
|
} else if len(properties) > 1 {
|
||||||
|
return utils.NewError(hydrateValue, "more than one property value matches single property interface", v, nil)
|
||||||
|
} else {
|
||||||
|
return hydrateProperty(v, properties[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle components
|
||||||
|
vkind := dereferencePointerValue(v).Kind()
|
||||||
|
if tag, err := extractTagFromValue(v); err != nil {
|
||||||
|
return utils.NewError(hydrateValue, "unable to extract component tag", v, err)
|
||||||
|
} else if components, found := component.components[tag]; !found || len(components) == 0 {
|
||||||
|
msg := fmt.Sprintf("unable to find matching component for %s", tag)
|
||||||
|
return utils.NewError(hydrateValue, msg, v, nil)
|
||||||
|
} else if vkind == reflect.Array || vkind == reflect.Slice {
|
||||||
|
return hydrateComponents(v, components)
|
||||||
|
} else if len(components) > 1 {
|
||||||
|
return utils.NewError(hydrateValue, "non-array interface provided but more than one component found!", v, nil)
|
||||||
|
} else {
|
||||||
|
return hydrateComponent(v, components[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes encoded icalendar data into a native interface
|
||||||
|
func Unmarshal(encoded string, into interface{}) error {
|
||||||
|
if component, err := tokenize(encoded); err != nil {
|
||||||
|
return utils.NewError(Unmarshal, "unable to tokenize encoded data", encoded, err)
|
||||||
|
} else {
|
||||||
|
return hydrateValue(reflect.ValueOf(into), component)
|
||||||
|
}
|
||||||
|
}
|
7
vendor/github.com/dolanor/caldav-go/icalendar/values/cal_scale.go
generated
vendored
Normal file
7
vendor/github.com/dolanor/caldav-go/icalendar/values/cal_scale.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
type CalScale string
|
||||||
|
|
||||||
|
const (
|
||||||
|
GregorianCalScale CalScale = "GREGORIAN"
|
||||||
|
)
|
33
vendor/github.com/dolanor/caldav-go/icalendar/values/comment.go
generated
vendored
Normal file
33
vendor/github.com/dolanor/caldav-go/icalendar/values/comment.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/properties"
|
||||||
|
)
|
||||||
|
|
||||||
|
// specifies non-processing information intended to provide a comment to the calendar user.
|
||||||
|
type Comment string
|
||||||
|
|
||||||
|
// encodes the comment value for the iCalendar specification
|
||||||
|
func (c Comment) EncodeICalValue() (string, error) {
|
||||||
|
return string(c), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes the comment value from the iCalendar specification
|
||||||
|
func (c Comment) DecodeICalValue(value string) error {
|
||||||
|
c = Comment(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the comment value for the iCalendar specification
|
||||||
|
func (c Comment) EncodeICalName() (properties.PropertyName, error) {
|
||||||
|
return properties.CommentPropertyName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a list of comments from strings
|
||||||
|
func NewComments(comments ...string) []Comment {
|
||||||
|
var _comments []Comment
|
||||||
|
for _, comment := range comments {
|
||||||
|
_comments = append(_comments, Comment(comment))
|
||||||
|
}
|
||||||
|
return _comments
|
||||||
|
}
|
139
vendor/github.com/dolanor/caldav-go/icalendar/values/contact.go
generated
vendored
Normal file
139
vendor/github.com/dolanor/caldav-go/icalendar/values/contact.go
generated
vendored
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/properties"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"log"
|
||||||
|
"net/mail"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
// Specifies the organizer of a group scheduled calendar entity. The property is specified within the "VFREEBUSY"
|
||||||
|
// calendar component to specify the calendar user requesting the free or busy time. When publishing a "VFREEBUSY"
|
||||||
|
// calendar component, the property is used to specify the calendar that the published busy time came from.
|
||||||
|
//
|
||||||
|
// The property has the property parameters CN, for specifying the common or display name associated with the
|
||||||
|
// "Organizer", DIR, for specifying a pointer to the directory information associated with the "Organizer",
|
||||||
|
// SENT-BY, for specifying another calendar user that is acting on behalf of the "Organizer". The non-standard
|
||||||
|
// parameters may also be specified on this property. If the LANGUAGE property parameter is specified, the identified
|
||||||
|
// language applies to the CN parameter value.
|
||||||
|
type Contact struct {
|
||||||
|
Entry mail.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
type AttendeeContact Contact
|
||||||
|
type OrganizerContact Contact
|
||||||
|
|
||||||
|
// creates a new icalendar attendee representation
|
||||||
|
func NewAttendeeContact(name, email string) *AttendeeContact {
|
||||||
|
return &AttendeeContact{Entry: mail.Address{Name: name, Address: email}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new icalendar organizer representation
|
||||||
|
func NewOrganizerContact(name, email string) *OrganizerContact {
|
||||||
|
return &OrganizerContact{Entry: mail.Address{Name: name, Address: email}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates the contact value for the iCalendar specification
|
||||||
|
func (c *Contact) ValidateICalValue() error {
|
||||||
|
email := c.Entry.String()
|
||||||
|
if _, err := mail.ParseAddress(email); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to validate address %s", email)
|
||||||
|
return utils.NewError(c.ValidateICalValue, msg, c, err)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the contact value for the iCalendar specification
|
||||||
|
func (c *Contact) EncodeICalValue() (string, error) {
|
||||||
|
return fmt.Sprintf("MAILTO:%s", c.Entry.Address), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the contact params for the iCalendar specification
|
||||||
|
func (c *Contact) EncodeICalParams() (params properties.Params, err error) {
|
||||||
|
if c.Entry.Name != "" {
|
||||||
|
params = properties.Params{properties.CanonicalNameParameterName: c.Entry.Name}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes the contact value from the iCalendar specification
|
||||||
|
func (c *Contact) DecodeICalValue(value string) error {
|
||||||
|
parts := strings.SplitN(value, ":", 2)
|
||||||
|
if len(parts) > 1 {
|
||||||
|
c.Entry.Address = parts[1]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes the contact params from the iCalendar specification
|
||||||
|
func (c *Contact) DecodeICalParams(params properties.Params) error {
|
||||||
|
if name, found := params[properties.CanonicalNameParameterName]; found {
|
||||||
|
c.Entry.Name = name
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates the contact value for the iCalendar specification
|
||||||
|
func (c *OrganizerContact) ValidateICalValue() error {
|
||||||
|
return (*Contact)(c).ValidateICalValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the contact value for the iCalendar specification
|
||||||
|
func (c *OrganizerContact) EncodeICalValue() (string, error) {
|
||||||
|
return (*Contact)(c).EncodeICalValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the contact params for the iCalendar specification
|
||||||
|
func (c *OrganizerContact) EncodeICalParams() (params properties.Params, err error) {
|
||||||
|
return (*Contact)(c).EncodeICalParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes the contact value from the iCalendar specification
|
||||||
|
func (c *OrganizerContact) DecodeICalValue(value string) error {
|
||||||
|
return (*Contact)(c).DecodeICalValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes the contact params from the iCalendar specification
|
||||||
|
func (c *OrganizerContact) DecodeICalParams(params properties.Params) error {
|
||||||
|
return (*Contact)(c).DecodeICalParams(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the contact property name for the iCalendar specification
|
||||||
|
func (o *OrganizerContact) EncodeICalName() (properties.PropertyName, error) {
|
||||||
|
return properties.OrganizerPropertyName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates the contact value for the iCalendar specification
|
||||||
|
func (c *AttendeeContact) ValidateICalValue() error {
|
||||||
|
return (*Contact)(c).ValidateICalValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the contact value for the iCalendar specification
|
||||||
|
func (c *AttendeeContact) EncodeICalValue() (string, error) {
|
||||||
|
return (*Contact)(c).EncodeICalValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the contact params for the iCalendar specification
|
||||||
|
func (c *AttendeeContact) EncodeICalParams() (params properties.Params, err error) {
|
||||||
|
return (*Contact)(c).EncodeICalParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes the contact value from the iCalendar specification
|
||||||
|
func (c *AttendeeContact) DecodeICalValue(value string) error {
|
||||||
|
return (*Contact)(c).DecodeICalValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes the contact params from the iCalendar specification
|
||||||
|
func (c *AttendeeContact) DecodeICalParams(params properties.Params) error {
|
||||||
|
return (*Contact)(c).DecodeICalParams(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the contact property name for the iCalendar specification
|
||||||
|
func (o *AttendeeContact) EncodeICalName() (properties.PropertyName, error) {
|
||||||
|
return properties.AttendeePropertyName, nil
|
||||||
|
}
|
24
vendor/github.com/dolanor/caldav-go/icalendar/values/csv.go
generated
vendored
Normal file
24
vendor/github.com/dolanor/caldav-go/icalendar/values/csv.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
type CSV []string
|
||||||
|
|
||||||
|
func (csv *CSV) EncodeICalValue() (string, error) {
|
||||||
|
return strings.Join(*csv, ","), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csv *CSV) DecodeICalValue(value string) error {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
*csv = CSV(strings.Split(value, ","))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCSV(items ...string) *CSV {
|
||||||
|
return (*CSV)(&items)
|
||||||
|
}
|
265
vendor/github.com/dolanor/caldav-go/icalendar/values/datetime.go
generated
vendored
Normal file
265
vendor/github.com/dolanor/caldav-go/icalendar/values/datetime.go
generated
vendored
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/properties"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
const DateFormatString = "20060102"
|
||||||
|
const DateTimeFormatString = "20060102T150405"
|
||||||
|
const UTCDateTimeFormatString = "20060102T150405Z"
|
||||||
|
|
||||||
|
// a representation of a date and time for iCalendar
|
||||||
|
type DateTime struct {
|
||||||
|
t time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type DateTimes []*DateTime
|
||||||
|
|
||||||
|
// The exception dates, if specified, are used in computing the recurrence set. The recurrence set is the complete set
|
||||||
|
// of recurrence instances for a calendar component. The recurrence set is generated by considering the initial
|
||||||
|
// "DTSTART" property along with the "RRULE", "RDATE", "EXDATE" and "EXRULE" properties contained within the iCalendar
|
||||||
|
// object. The "DTSTART" property defines the first instance in the recurrence set. Multiple instances of the "RRULE"
|
||||||
|
// and "EXRULE" properties can also be specified to define more sophisticated recurrence sets. The final recurrence set
|
||||||
|
// is generated by gathering all of the start date-times generated by any of the specified "RRULE" and "RDATE"
|
||||||
|
// properties, and then excluding any start date and times which fall within the union of start date and times
|
||||||
|
// generated by any specified "EXRULE" and "EXDATE" properties. This implies that start date and times within exclusion
|
||||||
|
// related properties (i.e., "EXDATE" and "EXRULE") take precedence over those specified by inclusion properties
|
||||||
|
// (i.e., "RDATE" and "RRULE"). Where duplicate instances are generated by the "RRULE" and "RDATE" properties, only
|
||||||
|
// one recurrence is considered. Duplicate instances are ignored.
|
||||||
|
//
|
||||||
|
// The "EXDATE" property can be used to exclude the value specified in "DTSTART". However, in such cases the original
|
||||||
|
// "DTSTART" date MUST still be maintained by the calendaring and scheduling system because the original "DTSTART"
|
||||||
|
// value has inherent usage dependencies by other properties such as the "RECURRENCE-ID".
|
||||||
|
type ExceptionDateTimes DateTimes
|
||||||
|
|
||||||
|
// The recurrence dates, if specified, are used in computing the recurrence set. The recurrence set is the complete set
|
||||||
|
// of recurrence instances for a calendar component. The recurrence set is generated by considering the initial
|
||||||
|
// "DTSTART" property along with the "RRULE", "RDATE", "EXDATE" and "EXRULE" properties contained within the iCalendar
|
||||||
|
// object. The "DTSTART" property defines the first instance in the recurrence set. Multiple instances of the "RRULE"
|
||||||
|
// and "EXRULE" properties can also be specified to define more sophisticated recurrence sets. The final recurrence set
|
||||||
|
// is generated by gathering all of the start date-times generated by any of the specified "RRULE" and "RDATE"
|
||||||
|
// properties, and then excluding any start date and times which fall within the union of start date and times
|
||||||
|
// generated by any specified "EXRULE" and "EXDATE" properties. This implies that start date and times within exclusion
|
||||||
|
// related properties (i.e., "EXDATE" and "EXRULE") take precedence over those specified by inclusion properties
|
||||||
|
// (i.e., "RDATE" and "RRULE"). Where duplicate instances are generated by the "RRULE" and "RDATE" properties, only
|
||||||
|
// one recurrence is considered. Duplicate instances are ignored.
|
||||||
|
type RecurrenceDateTimes DateTimes
|
||||||
|
|
||||||
|
// creates a new icalendar datetime representation
|
||||||
|
func NewDateTime(t time.Time) *DateTime {
|
||||||
|
return &DateTime{t: t.Truncate(time.Second)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new icalendar datetime array representation
|
||||||
|
func NewDateTimes(dates ...*DateTime) DateTimes {
|
||||||
|
return DateTimes(dates)
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new icalendar datetime array representation
|
||||||
|
func NewExceptionDateTimes(dates ...*DateTime) *ExceptionDateTimes {
|
||||||
|
datetimes := NewDateTimes(dates...)
|
||||||
|
return (*ExceptionDateTimes)(&datetimes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new icalendar datetime array representation
|
||||||
|
func NewRecurrenceDateTimes(dates ...*DateTime) *RecurrenceDateTimes {
|
||||||
|
datetimes := NewDateTimes(dates...)
|
||||||
|
return (*RecurrenceDateTimes)(&datetimes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks to see if two datetimes are equal
|
||||||
|
func (d *DateTime) Equals(test *DateTime) bool {
|
||||||
|
return d.t.Equal(test.t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the native time for the datetime object
|
||||||
|
func (d *DateTime) NativeTime() time.Time {
|
||||||
|
return d.t
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the datetime value for the iCalendar specification
|
||||||
|
func (d *DateTime) EncodeICalValue() (string, error) {
|
||||||
|
val := d.t.Format(DateTimeFormatString)
|
||||||
|
loc := d.t.Location()
|
||||||
|
if loc == time.UTC {
|
||||||
|
val = fmt.Sprintf("%sZ", val)
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes the datetime value from the iCalendar specification
|
||||||
|
func (d *DateTime) DecodeICalValue(value string) error {
|
||||||
|
layout := DateTimeFormatString
|
||||||
|
if strings.HasSuffix(value, "Z") {
|
||||||
|
layout = UTCDateTimeFormatString
|
||||||
|
} else if len(value) == 8 {
|
||||||
|
layout = DateFormatString
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
d.t, err = time.ParseInLocation(layout, value, time.UTC)
|
||||||
|
if err != nil {
|
||||||
|
return utils.NewError(d.DecodeICalValue, "unable to parse datetime value", d, err)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the datetime params for the iCalendar specification
|
||||||
|
func (d *DateTime) EncodeICalParams() (params properties.Params, err error) {
|
||||||
|
loc := d.t.Location()
|
||||||
|
if loc != time.UTC {
|
||||||
|
params = properties.Params{properties.TimeZoneIdPropertyName: loc.String()}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes the datetime params from the iCalendar specification
|
||||||
|
func (d *DateTime) DecodeICalParams(params properties.Params) error {
|
||||||
|
layout := DateTimeFormatString
|
||||||
|
value := d.t.Format(layout)
|
||||||
|
if name, found := params[properties.TimeZoneIdPropertyName]; !found {
|
||||||
|
return nil
|
||||||
|
} else if loc, err := time.LoadLocation(name); err != nil {
|
||||||
|
return utils.NewError(d.DecodeICalValue, "unable to parse timezone", d, err)
|
||||||
|
} else if t, err := time.ParseInLocation(layout, value, loc); err != nil {
|
||||||
|
return utils.NewError(d.DecodeICalValue, "unable to parse datetime value", d, err)
|
||||||
|
} else {
|
||||||
|
d.t = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates the datetime value against the iCalendar specification
|
||||||
|
func (d *DateTime) ValidateICalValue() error {
|
||||||
|
|
||||||
|
loc := d.t.Location()
|
||||||
|
|
||||||
|
if loc == time.Local {
|
||||||
|
msg := "DateTime location may not Local, please use UTC or explicit Location"
|
||||||
|
return utils.NewError(d.ValidateICalValue, msg, d, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if loc.String() == "" {
|
||||||
|
msg := "DateTime location must have a valid name"
|
||||||
|
return utils.NewError(d.ValidateICalValue, msg, d, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the datetime value for the iCalendar specification
|
||||||
|
func (d *DateTime) String() string {
|
||||||
|
if s, err := d.EncodeICalValue(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes a list of datetime values for the iCalendar specification
|
||||||
|
func (ds *DateTimes) EncodeICalValue() (string, error) {
|
||||||
|
var csv CSV
|
||||||
|
for i, d := range *ds {
|
||||||
|
if s, err := d.EncodeICalValue(); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to encode datetime at index %d", i)
|
||||||
|
return "", utils.NewError(ds.EncodeICalValue, msg, ds, err)
|
||||||
|
} else {
|
||||||
|
csv = append(csv, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return csv.EncodeICalValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes a list of datetime params for the iCalendar specification
|
||||||
|
func (ds *DateTimes) EncodeICalParams() (params properties.Params, err error) {
|
||||||
|
if len(*ds) > 0 {
|
||||||
|
params, err = (*ds)[0].EncodeICalParams()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes a list of datetime params from the iCalendar specification
|
||||||
|
func (ds *DateTimes) DecodeICalParams(params properties.Params) error {
|
||||||
|
for i, d := range *ds {
|
||||||
|
if err := d.DecodeICalParams(params); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to decode datetime params for index %d", i)
|
||||||
|
return utils.NewError(ds.DecodeICalValue, msg, ds, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes a list of datetime values for the iCalendar specification
|
||||||
|
func (ds *DateTimes) DecodeICalValue(value string) error {
|
||||||
|
csv := new(CSV)
|
||||||
|
if err := csv.DecodeICalValue(value); err != nil {
|
||||||
|
return utils.NewError(ds.DecodeICalValue, "unable to decode datetime list as CSV", ds, err)
|
||||||
|
}
|
||||||
|
for i, value := range *csv {
|
||||||
|
d := new(DateTime)
|
||||||
|
if err := d.DecodeICalValue(value); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to decode datetime at index %d", i)
|
||||||
|
return utils.NewError(ds.DecodeICalValue, msg, ds, err)
|
||||||
|
} else {
|
||||||
|
*ds = append(*ds, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes exception date times property name for icalendar
|
||||||
|
func (e *ExceptionDateTimes) EncodeICalName() (properties.PropertyName, error) {
|
||||||
|
return properties.ExceptionDateTimesPropertyName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes recurrence date times property name for icalendar
|
||||||
|
func (r *RecurrenceDateTimes) EncodeICalName() (properties.PropertyName, error) {
|
||||||
|
return properties.RecurrenceDateTimesPropertyName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes exception date times property value for icalendar
|
||||||
|
func (e *ExceptionDateTimes) EncodeICalValue() (string, error) {
|
||||||
|
return (*DateTimes)(e).EncodeICalValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes recurrence date times property value for icalendar
|
||||||
|
func (r *RecurrenceDateTimes) EncodeICalValue() (string, error) {
|
||||||
|
return (*DateTimes)(r).EncodeICalValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes exception date times property value for icalendar
|
||||||
|
func (e *ExceptionDateTimes) DecodeICalValue(value string) error {
|
||||||
|
return (*DateTimes)(e).DecodeICalValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes recurrence date times property value for icalendar
|
||||||
|
func (r *RecurrenceDateTimes) DecodeICalValue(value string) error {
|
||||||
|
return (*DateTimes)(r).DecodeICalValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes exception date times property params for icalendar
|
||||||
|
func (e *ExceptionDateTimes) EncodeICalParams() (params properties.Params, err error) {
|
||||||
|
return (*DateTimes)(e).EncodeICalParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes recurrence date times property params for icalendar
|
||||||
|
func (r *RecurrenceDateTimes) EncodeICalParams() (params properties.Params, err error) {
|
||||||
|
return (*DateTimes)(r).EncodeICalParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes exception date times property params for icalendar
|
||||||
|
func (e *ExceptionDateTimes) DecodeICalParams(params properties.Params) error {
|
||||||
|
return (*DateTimes)(e).DecodeICalParams(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes recurrence date times property params for icalendar
|
||||||
|
func (r *RecurrenceDateTimes) DecodeICalParams(params properties.Params) error {
|
||||||
|
return (*DateTimes)(r).DecodeICalParams(params)
|
||||||
|
}
|
132
vendor/github.com/dolanor/caldav-go/icalendar/values/duration.go
generated
vendored
Normal file
132
vendor/github.com/dolanor/caldav-go/icalendar/values/duration.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
// a representation of duration for iCalendar
|
||||||
|
type Duration struct {
|
||||||
|
d time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// breaks apart the duration into its component time parts
|
||||||
|
func (d *Duration) Decompose() (weeks, days, hours, minutes, seconds int64) {
|
||||||
|
|
||||||
|
// chip away at this
|
||||||
|
rem := time.Duration(math.Abs(float64(d.d)))
|
||||||
|
|
||||||
|
div := time.Hour * 24 * 7
|
||||||
|
weeks = int64(rem / div)
|
||||||
|
rem = rem % div
|
||||||
|
div = div / 7
|
||||||
|
days = int64(rem / div)
|
||||||
|
rem = rem % div
|
||||||
|
div = div / 24
|
||||||
|
hours = int64(rem / div)
|
||||||
|
rem = rem % div
|
||||||
|
div = div / 60
|
||||||
|
minutes = int64(rem / div)
|
||||||
|
rem = rem % div
|
||||||
|
div = div / 60
|
||||||
|
seconds = int64(rem / div)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the native golang duration
|
||||||
|
func (d *Duration) NativeDuration() time.Duration {
|
||||||
|
return d.d
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if the duration is negative
|
||||||
|
func (d *Duration) IsPast() bool {
|
||||||
|
return d.d < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the duration of time into iCalendar format
|
||||||
|
func (d *Duration) EncodeICalValue() (string, error) {
|
||||||
|
var parts []string
|
||||||
|
weeks, days, hours, minutes, seconds := d.Decompose()
|
||||||
|
if d.IsPast() {
|
||||||
|
parts = append(parts, "-")
|
||||||
|
}
|
||||||
|
parts = append(parts, "P")
|
||||||
|
if weeks > 0 {
|
||||||
|
parts = append(parts, fmt.Sprintf("%dW", weeks))
|
||||||
|
}
|
||||||
|
if days > 0 {
|
||||||
|
parts = append(parts, fmt.Sprintf("%dD", days))
|
||||||
|
}
|
||||||
|
if hours > 0 || minutes > 0 || seconds > 0 {
|
||||||
|
parts = append(parts, "T")
|
||||||
|
if hours > 0 {
|
||||||
|
parts = append(parts, fmt.Sprintf("%dH", hours))
|
||||||
|
}
|
||||||
|
if minutes > 0 {
|
||||||
|
parts = append(parts, fmt.Sprintf("%dM", minutes))
|
||||||
|
}
|
||||||
|
if seconds > 0 {
|
||||||
|
parts = append(parts, fmt.Sprintf("%dS", seconds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var durationRegEx = regexp.MustCompile("(\\d+)(\\w)")
|
||||||
|
|
||||||
|
// decodes the duration of time from iCalendar format
|
||||||
|
func (d *Duration) DecodeICalValue(value string) error {
|
||||||
|
var seconds int64
|
||||||
|
var isPast = strings.HasPrefix(value, "-P")
|
||||||
|
var matches = durationRegEx.FindAllStringSubmatch(value, -1)
|
||||||
|
for _, match := range matches {
|
||||||
|
var multiplier int64
|
||||||
|
ivalue, err := strconv.ParseInt(match[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return utils.NewError(d.DecodeICalValue, "unable to decode duration value "+match[1], d, nil)
|
||||||
|
}
|
||||||
|
switch match[2] {
|
||||||
|
case "S":
|
||||||
|
multiplier = 1
|
||||||
|
case "M":
|
||||||
|
multiplier = 60
|
||||||
|
case "H":
|
||||||
|
multiplier = 60 * 60
|
||||||
|
case "D":
|
||||||
|
multiplier = 60 * 60 * 24
|
||||||
|
case "W":
|
||||||
|
multiplier = 60 * 60 * 24 * 7
|
||||||
|
default:
|
||||||
|
return utils.NewError(d.DecodeICalValue, "unable to decode duration segment "+match[2], d, nil)
|
||||||
|
}
|
||||||
|
seconds = seconds + multiplier*ivalue
|
||||||
|
}
|
||||||
|
d.d = time.Duration(seconds) * time.Second
|
||||||
|
if isPast {
|
||||||
|
d.d = -d.d
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Duration) String() string {
|
||||||
|
if s, err := d.EncodeICalValue(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new iCalendar duration representation
|
||||||
|
func NewDuration(d time.Duration) *Duration {
|
||||||
|
return &Duration{d: d}
|
||||||
|
}
|
17
vendor/github.com/dolanor/caldav-go/icalendar/values/event_access_classification.go
generated
vendored
Normal file
17
vendor/github.com/dolanor/caldav-go/icalendar/values/event_access_classification.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
// An access classification is only one component of the general security system within a calendar application.
|
||||||
|
// It provides a method of capturing the scope of the access the calendar owner intends for information within an
|
||||||
|
// individual calendar entry. The access classification of an individual iCalendar component is useful when measured
|
||||||
|
// along with the other security components of a calendar system (e.g., calendar user authentication, authorization,
|
||||||
|
// access rights, access role, etc.). Hence, the semantics of the individual access classifications cannot be completely
|
||||||
|
// defined by this memo alone. Additionally, due to the "blind" nature of most exchange processes using this memo, these
|
||||||
|
// access classifications cannot serve as an enforcement statement for a system receiving an iCalendar object. Rather,
|
||||||
|
// they provide a method for capturing the intention of the calendar owner for the access to the calendar component.
|
||||||
|
type EventAccessClassification string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PublicEventAccessClassification EventAccessClassification = "PUBLIC"
|
||||||
|
PrivateEventAccessClassification = "PRIVATE"
|
||||||
|
ConfidentialEventAccessClassification = "CONFIDENTIAL"
|
||||||
|
)
|
13
vendor/github.com/dolanor/caldav-go/icalendar/values/event_status.go
generated
vendored
Normal file
13
vendor/github.com/dolanor/caldav-go/icalendar/values/event_status.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
// In a group scheduled calendar component, the property is used by the "Organizer" to provide a confirmation of the
|
||||||
|
// event to the "Attendees".
|
||||||
|
// For example in an Event calendar component, the "Organizer" can indicate that a meeting is tentative, confirmed or
|
||||||
|
// cancelled.
|
||||||
|
type EventStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TentativeEventStatus EventStatus = "TENTATIVE" // Indicates event is tentative.
|
||||||
|
ConfirmedEventStatus = "CONFIRMED" // Indicates event is definite.
|
||||||
|
CancelledEventStatus = "CANCELLED" // Indicates event is cancelled.
|
||||||
|
)
|
69
vendor/github.com/dolanor/caldav-go/icalendar/values/geo.go
generated
vendored
Normal file
69
vendor/github.com/dolanor/caldav-go/icalendar/values/geo.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
// a representation of a geographical point for iCalendar
|
||||||
|
type Geo struct {
|
||||||
|
coords []float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new icalendar geo representation
|
||||||
|
func NewGeo(lat, lng float64) *Geo {
|
||||||
|
return &Geo{coords: []float64{lat, lng}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the latitude encoded into the geo point
|
||||||
|
func (g *Geo) Lat() float64 {
|
||||||
|
return g.coords[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the longitude encoded into the geo point
|
||||||
|
func (g *Geo) Lng() float64 {
|
||||||
|
return g.coords[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates the geo value against the iCalendar specification
|
||||||
|
func (g *Geo) ValidateICalValue() error {
|
||||||
|
|
||||||
|
if len(g.coords) != 2 {
|
||||||
|
return utils.NewError(g.ValidateICalValue, "geo value must have length of 2", g, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.Lat() < -90 || g.Lat() > 90 {
|
||||||
|
return utils.NewError(g.ValidateICalValue, "geo latitude must be between -90 and 90 degrees", g, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.Lng() < -180 || g.Lng() > 180 {
|
||||||
|
return utils.NewError(g.ValidateICalValue, "geo longitude must be between -180 and 180 degrees", g, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the geo value for the iCalendar specification
|
||||||
|
func (g *Geo) EncodeICalValue() (string, error) {
|
||||||
|
return fmt.Sprintf("%f %f", g.Lat(), g.Lng()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes the geo value from the iCalendar specification
|
||||||
|
func (g *Geo) DecodeICalValue(value string) error {
|
||||||
|
if latlng := strings.Split(value, " "); len(latlng) < 2 {
|
||||||
|
return utils.NewError(g.DecodeICalValue, "geo value must have both a latitude and longitude component", g, nil)
|
||||||
|
} else if lat, err := strconv.ParseFloat(latlng[0], 64); err != nil {
|
||||||
|
return utils.NewError(g.DecodeICalValue, "unable to decode latitude component", g, err)
|
||||||
|
} else if lng, err := strconv.ParseFloat(latlng[1], 64); err != nil {
|
||||||
|
return utils.NewError(g.DecodeICalValue, "unable to decode latitude component", g, err)
|
||||||
|
} else {
|
||||||
|
*g = Geo{coords: []float64{lat, lng}}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
78
vendor/github.com/dolanor/caldav-go/icalendar/values/location.go
generated
vendored
Normal file
78
vendor/github.com/dolanor/caldav-go/icalendar/values/location.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/properties"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
// Specific venues such as conference or meeting rooms may be explicitly specified using this property. An alternate
|
||||||
|
// representation may be specified that is a URI that points to directory information with more structured specification
|
||||||
|
// of the location. For example, the alternate representation may specify either an LDAP URI pointing to an LDAP server
|
||||||
|
// entry or a CID URI pointing to a MIME body part containing a vCard [RFC 2426] for the location.
|
||||||
|
type Location struct {
|
||||||
|
value string
|
||||||
|
altrep *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new icalendar location representation
|
||||||
|
func NewLocation(value string, altrep ...*url.URL) *Location {
|
||||||
|
loc := &Location{value: value}
|
||||||
|
if len(altrep) > 0 {
|
||||||
|
loc.altrep = altrep[0]
|
||||||
|
}
|
||||||
|
return loc
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns an alternate representation for the location
|
||||||
|
// if one exists
|
||||||
|
func (l *Location) AltRep() *url.URL {
|
||||||
|
return l.altrep
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the location for the iCalendar specification
|
||||||
|
func (l *Location) EncodeICalValue() (string, error) {
|
||||||
|
return l.value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes the location from the iCalendar specification
|
||||||
|
func (l *Location) DecodeICalValue(value string) error {
|
||||||
|
l.value = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the location params for the iCalendar specification
|
||||||
|
func (l *Location) EncodeICalParams() (params properties.Params, err error) {
|
||||||
|
if l.altrep != nil {
|
||||||
|
params = properties.Params{properties.AlternateRepresentationName: l.altrep.String()}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes the location params from the iCalendar specification
|
||||||
|
func (l *Location) DecodeICalParams(params properties.Params) error {
|
||||||
|
if rep, found := params[properties.AlternateRepresentationName]; !found {
|
||||||
|
return nil
|
||||||
|
} else if altrep, err := url.Parse(rep); err != nil {
|
||||||
|
return utils.NewError(l.DecodeICalValue, "unable to parse alternate representation", l, err)
|
||||||
|
} else {
|
||||||
|
l.altrep = altrep
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates the location against the iCalendar specification
|
||||||
|
func (l *Location) ValidateICalValue() error {
|
||||||
|
|
||||||
|
if l.altrep != nil {
|
||||||
|
if _, err := url.Parse(l.altrep.String()); err != nil {
|
||||||
|
msg := "location alternate representation must be a valid url"
|
||||||
|
return utils.NewError(l.ValidateICalValue, msg, l, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
7
vendor/github.com/dolanor/caldav-go/icalendar/values/method.go
generated
vendored
Normal file
7
vendor/github.com/dolanor/caldav-go/icalendar/values/method.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
type Method string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PublishMethod Method = "PUBLISH"
|
||||||
|
)
|
434
vendor/github.com/dolanor/caldav-go/icalendar/values/recurrence_rule.go
generated
vendored
Normal file
434
vendor/github.com/dolanor/caldav-go/icalendar/values/recurrence_rule.go
generated
vendored
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/properties"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The recurrence rule, if specified, is used in computing the recurrence set. The recurrence set is the complete set
|
||||||
|
// of recurrence instances for a calendar component. The recurrence set is generated by considering the initial
|
||||||
|
// "DTSTART" property along with the "RRULE", "RDATE", "EXDATE" and "EXRULE" properties contained within the iCalendar
|
||||||
|
// object. The "DTSTART" property defines the first instance in the recurrence set. Multiple instances of the "RRULE"
|
||||||
|
// and "EXRULE" properties can also be specified to define more sophisticated recurrence sets. The final recurrence
|
||||||
|
// set is generated by gathering all of the start date/times generated by any of the specified "RRULE" and "RDATE"
|
||||||
|
// properties, and excluding any start date/times which fall within the union of start date/times generated by any
|
||||||
|
// specified "EXRULE" and "EXDATE" properties. This implies that start date/times within exclusion related properties
|
||||||
|
// (i.e., "EXDATE" and "EXRULE") take precedence over those specified by inclusion properties
|
||||||
|
// (i.e., "RDATE" and "RRULE"). Where duplicate instances are generated by the "RRULE" and "RDATE" properties, only
|
||||||
|
// one recurrence is considered. Duplicate instances are ignored.
|
||||||
|
|
||||||
|
// The "DTSTART" and "DTEND" property pair or "DTSTART" and "DURATION" property pair, specified within the iCalendar
|
||||||
|
// object defines the first instance of the recurrence. When used with a recurrence rule, the "DTSTART" and "DTEND"
|
||||||
|
// properties MUST be specified in local time and the appropriate set of "VTIMEZONE" calendar components MUST be
|
||||||
|
// included. For detail on the usage of the "VTIMEZONE" calendar component, see the "VTIMEZONE" calendar component
|
||||||
|
// definition.
|
||||||
|
|
||||||
|
// Any duration associated with the iCalendar object applies to all members of the generated recurrence set. Any
|
||||||
|
// modified duration for specific recurrences MUST be explicitly specified using the "RDATE" property.
|
||||||
|
type RecurrenceRule struct {
|
||||||
|
Frequency RecurrenceFrequency
|
||||||
|
Until *DateTime
|
||||||
|
Count int
|
||||||
|
Interval int
|
||||||
|
BySecond []int
|
||||||
|
ByMinute []int
|
||||||
|
ByHour []int
|
||||||
|
ByDay []RecurrenceWeekday
|
||||||
|
ByMonthDay []int
|
||||||
|
ByYearDay []int
|
||||||
|
ByWeekNumber []int
|
||||||
|
ByMonth []int
|
||||||
|
BySetPosition []int
|
||||||
|
WeekStart RecurrenceWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
// the frequency an event recurs
|
||||||
|
type RecurrenceFrequency string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SecondRecurrenceFrequency RecurrenceFrequency = "SECONDLY"
|
||||||
|
MinuteRecurrenceFrequency = "MINUTELY"
|
||||||
|
HourRecurrenceFrequency = "HOURLY"
|
||||||
|
DayRecurrenceFrequency = "DAILY"
|
||||||
|
WeekRecurrenceFrequency = "WEEKLY"
|
||||||
|
MonthRecurrenceFrequency = "MONTHLY"
|
||||||
|
YearRecurrenceFrequency = "YEARLY"
|
||||||
|
)
|
||||||
|
|
||||||
|
// the frequency an event recurs
|
||||||
|
type RecurrenceWeekday string
|
||||||
|
|
||||||
|
const (
|
||||||
|
MondayRecurrenceWeekday RecurrenceWeekday = "MO"
|
||||||
|
TuesdayRecurrenceWeekday = "TU"
|
||||||
|
WednesdayRecurrenceWeekday = "WE"
|
||||||
|
ThursdayRecurrenceWeekday = "TH"
|
||||||
|
FridayRecurrenceWeekday = "FR"
|
||||||
|
SaturdayRecurrenceWeekday = "SA"
|
||||||
|
SundayRecurrenceWeekday = "SU"
|
||||||
|
)
|
||||||
|
|
||||||
|
// creates a new recurrence rule object for iCalendar
|
||||||
|
func NewRecurrenceRule(frequency RecurrenceFrequency) *RecurrenceRule {
|
||||||
|
return &RecurrenceRule{Frequency: frequency}
|
||||||
|
}
|
||||||
|
|
||||||
|
var weekdayRegExp = regexp.MustCompile("MO|TU|WE|TH|FR|SA|SU")
|
||||||
|
|
||||||
|
// returns true if weekday is a valid constant
|
||||||
|
func (r RecurrenceWeekday) IsValidWeekDay() bool {
|
||||||
|
return weekdayRegExp.MatchString(strings.ToUpper(string(r)))
|
||||||
|
}
|
||||||
|
|
||||||
|
var frequencyRegExp = regexp.MustCompile("SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY")
|
||||||
|
|
||||||
|
// returns true if weekday is a valid constant
|
||||||
|
func (r RecurrenceFrequency) IsValidFrequency() bool {
|
||||||
|
return frequencyRegExp.MatchString(strings.ToUpper(string(r)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the recurrence rule name for the iCalendar specification
|
||||||
|
func (r *RecurrenceRule) EncodeICalName() (properties.PropertyName, error) {
|
||||||
|
return properties.RecurrenceRulePropertyName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the recurrence rule value for the iCalendar specification
|
||||||
|
func (r *RecurrenceRule) EncodeICalValue() (string, error) {
|
||||||
|
|
||||||
|
out := []string{fmt.Sprintf("FREQ=%s", strings.ToUpper(string(r.Frequency)))}
|
||||||
|
|
||||||
|
if r.Until != nil {
|
||||||
|
if encoded, err := r.Until.EncodeICalValue(); err != nil {
|
||||||
|
return "", utils.NewError(r.EncodeICalValue, "unable to encode until date", r, err)
|
||||||
|
} else {
|
||||||
|
out = append(out, fmt.Sprintf("UNTIL=%s", encoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Count > 0 {
|
||||||
|
out = append(out, fmt.Sprintf("COUNT=%d", r.Count))
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Interval > 0 {
|
||||||
|
out = append(out, fmt.Sprintf("INTERVAL=%d", r.Interval))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.BySecond) > 0 {
|
||||||
|
if encoded, err := intsToCSV(r.BySecond); err != nil {
|
||||||
|
return "", utils.NewError(r.EncodeICalValue, "unable to encode by second value", r, err)
|
||||||
|
} else {
|
||||||
|
out = append(out, fmt.Sprintf("BYSECOND=%s", encoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.ByMinute) > 0 {
|
||||||
|
if encoded, err := intsToCSV(r.ByMinute); err != nil {
|
||||||
|
return "", utils.NewError(r.EncodeICalValue, "unable to encode by minute value", r, err)
|
||||||
|
} else {
|
||||||
|
out = append(out, fmt.Sprintf("BYMINUTE=%s", encoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.ByHour) > 0 {
|
||||||
|
if encoded, err := intsToCSV(r.ByHour); err != nil {
|
||||||
|
return "", utils.NewError(r.EncodeICalValue, "unable to encode by hour value", r, err)
|
||||||
|
} else {
|
||||||
|
out = append(out, fmt.Sprintf("BYHOUR=%s", encoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.ByDay) > 0 {
|
||||||
|
if encoded, err := daysToCSV(r.ByDay); err != nil {
|
||||||
|
return "", utils.NewError(r.EncodeICalValue, "unable to encode by day value", r, err)
|
||||||
|
} else {
|
||||||
|
out = append(out, fmt.Sprintf("BYDAY=%s", encoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.ByMonthDay) > 0 {
|
||||||
|
if encoded, err := intsToCSV(r.ByMonthDay); err != nil {
|
||||||
|
return "", utils.NewError(r.EncodeICalValue, "unable to encode by month day value", r, err)
|
||||||
|
} else {
|
||||||
|
out = append(out, fmt.Sprintf("BYMONTHDAY=%s", encoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.ByYearDay) > 0 {
|
||||||
|
if encoded, err := intsToCSV(r.ByYearDay); err != nil {
|
||||||
|
return "", utils.NewError(r.EncodeICalValue, "unable to encode by year day value", r, err)
|
||||||
|
} else {
|
||||||
|
out = append(out, fmt.Sprintf("BYYEARDAY=%s", encoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.ByWeekNumber) > 0 {
|
||||||
|
if encoded, err := intsToCSV(r.ByWeekNumber); err != nil {
|
||||||
|
return "", utils.NewError(r.EncodeICalValue, "unable to encode by week number value", r, err)
|
||||||
|
} else {
|
||||||
|
out = append(out, fmt.Sprintf("BYWEEKNO=%s", encoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.ByMonth) > 0 {
|
||||||
|
if encoded, err := intsToCSV(r.ByMonth); err != nil {
|
||||||
|
return "", utils.NewError(r.EncodeICalValue, "unable to encode by month value", r, err)
|
||||||
|
} else {
|
||||||
|
out = append(out, fmt.Sprintf("BYMONTH=%s", encoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.BySetPosition) > 0 {
|
||||||
|
if encoded, err := intsToCSV(r.BySetPosition); err != nil {
|
||||||
|
return "", utils.NewError(r.EncodeICalValue, "unable to encode by set position value", r, err)
|
||||||
|
} else {
|
||||||
|
out = append(out, fmt.Sprintf("BYSETPOS=%s", encoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.WeekStart != "" {
|
||||||
|
out = append(out, fmt.Sprintf("WKST=%s", r.WeekStart))
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(out, ";"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var rruleParamRegExp = regexp.MustCompile("(\\w+)\\s*=\\s*([^;]+)")
|
||||||
|
|
||||||
|
// decodes the recurrence rule value from the iCalendar specification
|
||||||
|
func (r *RecurrenceRule) DecodeICalValue(value string) error {
|
||||||
|
|
||||||
|
matches := rruleParamRegExp.FindAllStringSubmatch(value, -1)
|
||||||
|
if len(matches) <= 0 {
|
||||||
|
return utils.NewError(r.DecodeICalValue, "no recurrence rules found", r, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, match := range matches {
|
||||||
|
if err := r.decodeICalValue(match[1], match[2]); err != nil {
|
||||||
|
msg := fmt.Sprintf("unable to decode %s value", match[1])
|
||||||
|
return utils.NewError(r.DecodeICalValue, msg, r, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RecurrenceRule) decodeICalValue(name string, value string) error {
|
||||||
|
|
||||||
|
switch name {
|
||||||
|
case "FREQ":
|
||||||
|
r.Frequency = RecurrenceFrequency(value)
|
||||||
|
case "UNTIL":
|
||||||
|
until := new(DateTime)
|
||||||
|
if err := until.DecodeICalValue(value); err != nil {
|
||||||
|
return utils.NewError(r.decodeICalValue, "invalid until value "+value, r, err)
|
||||||
|
} else {
|
||||||
|
r.Until = until
|
||||||
|
}
|
||||||
|
case "COUNT":
|
||||||
|
if count, err := strconv.ParseInt(value, 10, 64); err != nil {
|
||||||
|
return utils.NewError(r.decodeICalValue, "invalid count value "+value, r, err)
|
||||||
|
} else {
|
||||||
|
r.Count = int(count)
|
||||||
|
}
|
||||||
|
case "INTERVAL":
|
||||||
|
if interval, err := strconv.ParseInt(value, 10, 64); err != nil {
|
||||||
|
return utils.NewError(r.decodeICalValue, "invalid interval value "+value, r, err)
|
||||||
|
} else {
|
||||||
|
r.Interval = int(interval)
|
||||||
|
}
|
||||||
|
case "BYSECOND":
|
||||||
|
if ints, err := csvToInts(value); err != nil {
|
||||||
|
return utils.NewError(r.decodeICalValue, "invalid by second value "+value, r, err)
|
||||||
|
} else {
|
||||||
|
r.BySecond = ints
|
||||||
|
}
|
||||||
|
case "BYMINUTE":
|
||||||
|
if ints, err := csvToInts(value); err != nil {
|
||||||
|
return utils.NewError(r.decodeICalValue, "invalid by minute value "+value, r, err)
|
||||||
|
} else {
|
||||||
|
r.ByMinute = ints
|
||||||
|
}
|
||||||
|
case "BYHOUR":
|
||||||
|
if ints, err := csvToInts(value); err != nil {
|
||||||
|
return utils.NewError(r.decodeICalValue, "invalid by hour value "+value, r, err)
|
||||||
|
} else {
|
||||||
|
r.ByHour = ints
|
||||||
|
}
|
||||||
|
case "BYDAY":
|
||||||
|
if days, err := csvToDays(value); err != nil {
|
||||||
|
return utils.NewError(r.decodeICalValue, "invalid by day value "+value, r, err)
|
||||||
|
} else {
|
||||||
|
r.ByDay = days
|
||||||
|
}
|
||||||
|
case "BYMONTHDAY":
|
||||||
|
if ints, err := csvToInts(value); err != nil {
|
||||||
|
return utils.NewError(r.decodeICalValue, "invalid by month day value "+value, r, err)
|
||||||
|
} else {
|
||||||
|
r.ByMonthDay = ints
|
||||||
|
}
|
||||||
|
case "BYYEARDAY":
|
||||||
|
if ints, err := csvToInts(value); err != nil {
|
||||||
|
return utils.NewError(r.decodeICalValue, "invalid by year day value "+value, r, err)
|
||||||
|
} else {
|
||||||
|
r.ByYearDay = ints
|
||||||
|
}
|
||||||
|
case "BYWEEKNO":
|
||||||
|
if ints, err := csvToInts(value); err != nil {
|
||||||
|
return utils.NewError(r.decodeICalValue, "invalid by week number value "+value, r, err)
|
||||||
|
} else {
|
||||||
|
r.ByWeekNumber = ints
|
||||||
|
}
|
||||||
|
case "BYMONTH":
|
||||||
|
if ints, err := csvToInts(value); err != nil {
|
||||||
|
return utils.NewError(r.decodeICalValue, "unable to encode by month value "+value, r, err)
|
||||||
|
} else {
|
||||||
|
r.ByMonth = ints
|
||||||
|
}
|
||||||
|
case "BYSETPOS":
|
||||||
|
if ints, err := csvToInts(value); err != nil {
|
||||||
|
return utils.NewError(r.decodeICalValue, "unable to encode by set position value "+value, r, err)
|
||||||
|
} else {
|
||||||
|
r.BySetPosition = ints
|
||||||
|
}
|
||||||
|
case "WKST":
|
||||||
|
r.WeekStart = RecurrenceWeekday(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates the recurrence rule value against the iCalendar specification
|
||||||
|
func (r *RecurrenceRule) ValidateICalValue() error {
|
||||||
|
if !r.Frequency.IsValidFrequency() {
|
||||||
|
return utils.NewError(r.ValidateICalValue, "a frequency is required in all recurrence rules", r, nil)
|
||||||
|
} else if r.Until != nil && r.Count > 0 {
|
||||||
|
return utils.NewError(r.ValidateICalValue, "until and count values are mutually exclusive", r, nil)
|
||||||
|
} else if found, fine := intsInRange(r.BySecond, 59); !fine {
|
||||||
|
msg := fmt.Sprintf("by second value of %d is out of bounds", found)
|
||||||
|
return utils.NewError(r.ValidateICalValue, msg, r, nil)
|
||||||
|
} else if found, fine := intsInRange(r.ByMinute, 59); !fine {
|
||||||
|
msg := fmt.Sprintf("by minute value of %d is out of bounds", found)
|
||||||
|
return utils.NewError(r.ValidateICalValue, msg, r, nil)
|
||||||
|
} else if found, fine := intsInRange(r.ByHour, 23); !fine {
|
||||||
|
msg := fmt.Sprintf("by hour value of %d is out of bounds", found)
|
||||||
|
return utils.NewError(r.ValidateICalValue, msg, r, nil)
|
||||||
|
} else if err := daysInRange(r.ByDay); err != nil {
|
||||||
|
return utils.NewError(r.ValidateICalValue, "by day value not in range", r, err)
|
||||||
|
} else if found, fine := intsInRange(r.ByMonthDay, 31); !fine {
|
||||||
|
msg := fmt.Sprintf("by month day value of %d is out of bounds", found)
|
||||||
|
return utils.NewError(r.ValidateICalValue, msg, r, nil)
|
||||||
|
} else if found, fine := intsInRange(r.ByYearDay, 366); !fine {
|
||||||
|
msg := fmt.Sprintf("by year day value of %d is out of bounds", found)
|
||||||
|
return utils.NewError(r.ValidateICalValue, msg, r, nil)
|
||||||
|
} else if found, fine := intsInRange(r.ByMonth, 12); !fine {
|
||||||
|
msg := fmt.Sprintf("by month value of %d is out of bounds", found)
|
||||||
|
return utils.NewError(r.ValidateICalValue, msg, r, nil)
|
||||||
|
} else if found, fine := intsInRange(r.BySetPosition, 366); !fine {
|
||||||
|
msg := fmt.Sprintf("by month value of %d is out of bounds", found)
|
||||||
|
return utils.NewError(r.ValidateICalValue, msg, r, nil)
|
||||||
|
} else if err := dayInRange(r.WeekStart); r.WeekStart != "" && err != nil {
|
||||||
|
return utils.NewError(r.ValidateICalValue, "week start value not in range", r, err)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func intsToCSV(ints []int) (string, error) {
|
||||||
|
csv := new(CSV)
|
||||||
|
for _, i := range ints {
|
||||||
|
*csv = append(*csv, fmt.Sprintf("%d", i))
|
||||||
|
}
|
||||||
|
return csv.EncodeICalValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
func csvToInts(value string) (ints []int, err error) {
|
||||||
|
csv := new(CSV)
|
||||||
|
if ierr := csv.DecodeICalValue(value); err != nil {
|
||||||
|
err = utils.NewError(csvToInts, "unable to decode CSV value", value, ierr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, v := range *csv {
|
||||||
|
if i, ierr := strconv.ParseInt(v, 10, 64); err != nil {
|
||||||
|
err = utils.NewError(csvToInts, "unable to parse int value "+v, value, ierr)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
ints = append(ints, int(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func intsInRange(ints []int, max int) (int, bool) {
|
||||||
|
for _, i := range ints {
|
||||||
|
if i < -max || i > max {
|
||||||
|
return i, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func daysInRange(days []RecurrenceWeekday) error {
|
||||||
|
for _, day := range days {
|
||||||
|
if err := dayInRange(day); err != nil {
|
||||||
|
msg := fmt.Sprintf("day value %s is not in range", day)
|
||||||
|
return utils.NewError(dayInRange, msg, days, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var dayRegExp = regexp.MustCompile("(\\d{1,2})?(\\w{2})")
|
||||||
|
|
||||||
|
func dayInRange(day RecurrenceWeekday) error {
|
||||||
|
var ordinal, weekday string
|
||||||
|
if matches := dayRegExp.FindAllStringSubmatch(string(day), -1); len(matches) <= 0 {
|
||||||
|
msg := fmt.Sprintf("weekday value %s is not in valid format", day)
|
||||||
|
return utils.NewError(dayInRange, msg, day, nil)
|
||||||
|
} else if len(matches[0]) > 2 {
|
||||||
|
ordinal = matches[0][1]
|
||||||
|
weekday = matches[0][2]
|
||||||
|
} else {
|
||||||
|
weekday = matches[0][1]
|
||||||
|
}
|
||||||
|
if !RecurrenceWeekday(weekday).IsValidWeekDay() {
|
||||||
|
msg := fmt.Sprintf("weekday value %s is not valid", weekday)
|
||||||
|
return utils.NewError(dayInRange, msg, day, nil)
|
||||||
|
} else if i, err := strconv.ParseInt(ordinal, 10, 64); ordinal != "" && err != nil {
|
||||||
|
msg := fmt.Sprintf("weekday ordinal value %d is not valid", i)
|
||||||
|
return utils.NewError(dayInRange, msg, day, err)
|
||||||
|
} else if i < -53 || i > 53 {
|
||||||
|
msg := fmt.Sprintf("weekday ordinal value %d is not in range", i)
|
||||||
|
return utils.NewError(dayInRange, msg, day, nil)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func daysToCSV(days []RecurrenceWeekday) (string, error) {
|
||||||
|
csv := new(CSV)
|
||||||
|
for _, day := range days {
|
||||||
|
*csv = append(*csv, strings.ToUpper(string(day)))
|
||||||
|
}
|
||||||
|
return csv.EncodeICalValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
func csvToDays(value string) (days []RecurrenceWeekday, err error) {
|
||||||
|
csv := new(CSV)
|
||||||
|
if ierr := csv.DecodeICalValue(value); err != nil {
|
||||||
|
err = utils.NewError(csvToInts, "unable to decode CSV value", value, ierr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, v := range *csv {
|
||||||
|
days = append(days, RecurrenceWeekday(v))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
12
vendor/github.com/dolanor/caldav-go/icalendar/values/time_transparency.go
generated
vendored
Normal file
12
vendor/github.com/dolanor/caldav-go/icalendar/values/time_transparency.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
// Time Transparency is the characteristic of an event that determines whether it appears to consume time on a calendar.
|
||||||
|
// Events that consume actual time for the individual or resource associated with the calendar SHOULD be recorded as
|
||||||
|
// OPAQUE, allowing them to be detected by free-busy time searches. Other events, which do not take up the individual's
|
||||||
|
// (or resource's) time SHOULD be recorded as TRANSPARENT, making them invisible to free-busy time searches.
|
||||||
|
type TimeTransparency string
|
||||||
|
|
||||||
|
const (
|
||||||
|
OpaqueTimeTransparency TimeTransparency = "OPAQUE" // Blocks or opaque on busy time searches. DEFAULT
|
||||||
|
TransparentTimeTransparency = "TRANSPARENT" // Transparent on busy time searches.
|
||||||
|
)
|
49
vendor/github.com/dolanor/caldav-go/icalendar/values/url.go
generated
vendored
Normal file
49
vendor/github.com/dolanor/caldav-go/icalendar/values/url.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package values
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dolanor/caldav-go/icalendar/properties"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// a representation of duration for iCalendar
|
||||||
|
type Url struct {
|
||||||
|
u url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the URL into iCalendar format
|
||||||
|
func (u *Url) EncodeICalValue() (string, error) {
|
||||||
|
return u.u.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodes the url params for the iCalendar specification
|
||||||
|
func (u *Url) EncodeICalParams() (params properties.Params, err error) {
|
||||||
|
params = properties.Params{
|
||||||
|
properties.ValuePropertyName: "URI",
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes the URL from iCalendar format
|
||||||
|
func (u *Url) DecodeICalValue(value string) error {
|
||||||
|
if parsed, err := url.Parse(value); err != nil {
|
||||||
|
return utils.NewError(u.ValidateICalValue, "unable to parse url", u, err)
|
||||||
|
} else {
|
||||||
|
u.u = *parsed
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates the URL for iCalendar format
|
||||||
|
func (u *Url) ValidateICalValue() error {
|
||||||
|
if _, err := url.Parse(u.u.String()); err != nil {
|
||||||
|
return utils.NewError(u.ValidateICalValue, "invalid URL object", u, err)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new iCalendar duration representation
|
||||||
|
func NewUrl(u url.URL) *Url {
|
||||||
|
return &Url{u: u}
|
||||||
|
}
|
37
vendor/github.com/dolanor/caldav-go/utils/error.go
generated
vendored
Normal file
37
vendor/github.com/dolanor/caldav-go/utils/error.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
method interface{}
|
||||||
|
message string
|
||||||
|
context interface{}
|
||||||
|
cause error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewError(method interface{}, message string, context interface{}, cause error) *Error {
|
||||||
|
e := new(Error)
|
||||||
|
e.method = method
|
||||||
|
e.message = message
|
||||||
|
e.context = context
|
||||||
|
e.cause = cause
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
pc := reflect.ValueOf(e.method).Pointer()
|
||||||
|
fn := runtime.FuncForPC(pc).Name()
|
||||||
|
msg := fmt.Sprintf("error: %s\nfunc: %s", e.message, fn)
|
||||||
|
if e.context != nil {
|
||||||
|
tname := reflect.ValueOf(e.context).Type()
|
||||||
|
msg = fmt.Sprintf("%s\ncontext: %s", msg, tname.String())
|
||||||
|
}
|
||||||
|
if e.cause != nil {
|
||||||
|
msg = fmt.Sprintf("%s\ncause: %s", msg, e.cause.Error())
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
119
vendor/github.com/dolanor/caldav-go/webdav/client.go
generated
vendored
Normal file
119
vendor/github.com/dolanor/caldav-go/webdav/client.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package webdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dolanor/caldav-go/http"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"github.com/dolanor/caldav-go/webdav/entities"
|
||||||
|
nhttp "net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusMulti = 207
|
||||||
|
)
|
||||||
|
|
||||||
|
// a client for making WebDAV requests
|
||||||
|
type Client http.Client
|
||||||
|
|
||||||
|
// downcasts the client to the local HTTP interface
|
||||||
|
func (c *Client) Http() *http.Client {
|
||||||
|
return (*http.Client)(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the embedded WebDav server reference
|
||||||
|
func (c *Client) Server() *Server {
|
||||||
|
return (*Server)(c.Http().Server())
|
||||||
|
}
|
||||||
|
|
||||||
|
// executes a WebDAV request
|
||||||
|
func (c *Client) Do(req *Request) (*Response, error) {
|
||||||
|
if resp, err := c.Http().Do((*http.Request)(req)); err != nil {
|
||||||
|
return nil, utils.NewError(c.Do, "unable to execute WebDAV request", c, err)
|
||||||
|
} else {
|
||||||
|
return NewResponse(resp), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks if a resource exists given a particular path
|
||||||
|
func (c *Client) Exists(path string) (bool, error) {
|
||||||
|
if req, err := c.Server().NewRequest("HEAD", path); err != nil {
|
||||||
|
return false, utils.NewError(c.Exists, "unable to create request", c, err)
|
||||||
|
} else if resp, err := c.Do(req); err != nil {
|
||||||
|
return false, utils.NewError(c.Exists, "unable to execute request", c, err)
|
||||||
|
} else {
|
||||||
|
return resp.StatusCode != nhttp.StatusNotFound, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deletes a resource if it exists on a particular path
|
||||||
|
func (c *Client) Delete(path string) error {
|
||||||
|
if req, err := c.Server().NewRequest("DELETE", path); err != nil {
|
||||||
|
return utils.NewError(c.Delete, "unable to create request", c, err)
|
||||||
|
} else if resp, err := c.Do(req); err != nil {
|
||||||
|
return utils.NewError(c.Delete, "unable to execute request", c, err)
|
||||||
|
} else if resp.StatusCode != nhttp.StatusNoContent && resp.StatusCode != nhttp.StatusNotFound {
|
||||||
|
err := new(entities.Error)
|
||||||
|
resp.Decode(err)
|
||||||
|
msg := fmt.Sprintf("unexpected server response %s", resp.Status)
|
||||||
|
return utils.NewError(c.Delete, msg, c, err)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetches a list of WebDAV features supported by the server
|
||||||
|
// returns an error if the server does not support DAV
|
||||||
|
func (c *Client) Features(path string) ([]string, error) {
|
||||||
|
if req, err := c.Server().NewRequest("OPTIONS", path); err != nil {
|
||||||
|
return []string{}, utils.NewError(c.Features, "unable to create request", c, err)
|
||||||
|
} else if resp, err := c.Do(req); err != nil {
|
||||||
|
return []string{}, utils.NewError(c.Features, "unable to execute request", c, err)
|
||||||
|
} else {
|
||||||
|
return resp.Features(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns an error if the server does not support WebDAV
|
||||||
|
func (c *Client) ValidateServer(path string) error {
|
||||||
|
if features, err := c.Features(path); err != nil {
|
||||||
|
return utils.NewError(c.ValidateServer, "feature detection failed", c, err)
|
||||||
|
} else if len(features) <= 0 {
|
||||||
|
return utils.NewError(c.ValidateServer, "no DAV headers found", c, err)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// executes a PROPFIND request against the WebDAV server
|
||||||
|
// returns a multistatus XML entity
|
||||||
|
func (c *Client) Propfind(path string, depth Depth, pf *entities.Propfind) (*entities.Multistatus, error) {
|
||||||
|
|
||||||
|
ms := new(entities.Multistatus)
|
||||||
|
|
||||||
|
if req, err := c.Server().NewRequest("PROPFIND", path, pf); err != nil {
|
||||||
|
return nil, utils.NewError(c.Propfind, "unable to create request", c, err)
|
||||||
|
} else if req.Http().Native().Header.Set("Depth", string(depth)); depth == "" {
|
||||||
|
return nil, utils.NewError(c.Propfind, "search depth must be defined", c, nil)
|
||||||
|
} else if resp, err := c.Do(req); err != nil {
|
||||||
|
return nil, utils.NewError(c.Propfind, "unable to execute request", c, err)
|
||||||
|
} else if resp.StatusCode != StatusMulti {
|
||||||
|
msg := fmt.Sprintf("unexpected status: %s", resp.Status)
|
||||||
|
return nil, utils.NewError(c.Propfind, msg, c, nil)
|
||||||
|
} else if err := resp.Decode(ms); err != nil {
|
||||||
|
return nil, utils.NewError(c.Propfind, "unable to decode response", c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ms, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new client for communicating with an WebDAV server
|
||||||
|
func NewClient(server *Server, native *nhttp.Client) *Client {
|
||||||
|
return (*Client)(http.NewClient((*http.Server)(server), native))
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new client for communicating with a WebDAV server
|
||||||
|
// uses the default HTTP client from net/http
|
||||||
|
func NewDefaultClient(server *Server) *Client {
|
||||||
|
return NewClient(server, nhttp.DefaultClient)
|
||||||
|
}
|
9
vendor/github.com/dolanor/caldav-go/webdav/depth.go
generated
vendored
Normal file
9
vendor/github.com/dolanor/caldav-go/webdav/depth.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package webdav
|
||||||
|
|
||||||
|
type Depth string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Depth0 Depth = "0"
|
||||||
|
Depth1 = "1"
|
||||||
|
DepthInfinity = "infinity"
|
||||||
|
)
|
18
vendor/github.com/dolanor/caldav-go/webdav/entities/error.go
generated
vendored
Normal file
18
vendor/github.com/dolanor/caldav-go/webdav/entities/error.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import "encoding/xml"
|
||||||
|
|
||||||
|
// a WebDAV error
|
||||||
|
type Error struct {
|
||||||
|
XMLName xml.Name `xml:"DAV: error"`
|
||||||
|
Description string `xml:"error-description,omitempty"`
|
||||||
|
Message string `xml:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
if e.Description != "" {
|
||||||
|
return e.Description
|
||||||
|
} else {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
}
|
23
vendor/github.com/dolanor/caldav-go/webdav/entities/multistatus.go
generated
vendored
Normal file
23
vendor/github.com/dolanor/caldav-go/webdav/entities/multistatus.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import "encoding/xml"
|
||||||
|
|
||||||
|
// metadata about a property
|
||||||
|
type PropStat struct {
|
||||||
|
XMLName xml.Name `xml:"propstat"`
|
||||||
|
Status string `xml:"status"`
|
||||||
|
Prop *Prop `xml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// a multistatus response entity
|
||||||
|
type Response struct {
|
||||||
|
XMLName xml.Name `xml:"response"`
|
||||||
|
Href string `xml:"href"`
|
||||||
|
PropStats []*PropStat `xml:"propstat,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// a request to find properties on an an entity or collection
|
||||||
|
type Multistatus struct {
|
||||||
|
XMLName xml.Name `xml:"DAV: multistatus"`
|
||||||
|
Responses []*Response `xml:"response,omitempty"`
|
||||||
|
}
|
32
vendor/github.com/dolanor/caldav-go/webdav/entities/prop.go
generated
vendored
Normal file
32
vendor/github.com/dolanor/caldav-go/webdav/entities/prop.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// a property of a resource
|
||||||
|
type Prop struct {
|
||||||
|
XMLName xml.Name `xml:"DAV: prop"`
|
||||||
|
GetContentType string `xml:"getcontenttype,omitempty"`
|
||||||
|
DisplayName string `xml:"displayname,omitempty"`
|
||||||
|
ResourceType *ResourceType `xml:",omitempty"`
|
||||||
|
CTag string `xml:"http://calendarserver.org/ns/ getctag,omitempty"`
|
||||||
|
ETag string `xml:"http://calendarserver.org/ns/ getetag,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// the type of a resource
|
||||||
|
type ResourceType struct {
|
||||||
|
XMLName xml.Name `xml:"resourcetype"`
|
||||||
|
Collection *ResourceTypeCollection `xml:",omitempty"`
|
||||||
|
Calendar *ResourceTypeCalendar `xml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// A calendar resource type
|
||||||
|
type ResourceTypeCalendar struct {
|
||||||
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// A collection resource type
|
||||||
|
type ResourceTypeCollection struct {
|
||||||
|
XMLName xml.Name `xml:"collection"`
|
||||||
|
}
|
20
vendor/github.com/dolanor/caldav-go/webdav/entities/propfind.go
generated
vendored
Normal file
20
vendor/github.com/dolanor/caldav-go/webdav/entities/propfind.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import "encoding/xml"
|
||||||
|
|
||||||
|
// a request to find properties on an an entity or collection
|
||||||
|
type Propfind struct {
|
||||||
|
XMLName xml.Name `xml:"DAV: propfind"`
|
||||||
|
AllProp *AllProp `xml:",omitempty"`
|
||||||
|
Props []*Prop `xml:"prop,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// a propfind property representing all properties
|
||||||
|
type AllProp struct {
|
||||||
|
XMLName xml.Name `xml:"allprop"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// a convenience method for searching all properties
|
||||||
|
func NewAllPropsFind() *Propfind {
|
||||||
|
return &Propfind{AllProp: new(AllProp)}
|
||||||
|
}
|
56
vendor/github.com/dolanor/caldav-go/webdav/request.go
generated
vendored
Normal file
56
vendor/github.com/dolanor/caldav-go/webdav/request.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package webdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"github.com/dolanor/caldav-go/http"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
|
||||||
|
// an WebDAV request object
|
||||||
|
type Request http.Request
|
||||||
|
|
||||||
|
// downcasts the request to the local HTTP interface
|
||||||
|
func (r *Request) Http() *http.Request {
|
||||||
|
return (*http.Request)(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new WebDAV request object
|
||||||
|
func NewRequest(method string, urlstr string, xmldata ...interface{}) (*Request, error) {
|
||||||
|
if buffer, length, err := xmlToReadCloser(xmldata); err != nil {
|
||||||
|
return nil, utils.NewError(NewRequest, "unable to encode xml data", xmldata, err)
|
||||||
|
} else if r, err := http.NewRequest(method, urlstr, buffer); err != nil {
|
||||||
|
return nil, utils.NewError(NewRequest, "unable to create request", urlstr, err)
|
||||||
|
} else {
|
||||||
|
if buffer != nil {
|
||||||
|
// set the content type to XML if we have a body
|
||||||
|
r.Native().Header.Set("Content-Type", "text/xml; charset=UTF-8")
|
||||||
|
r.ContentLength = int64(length)
|
||||||
|
}
|
||||||
|
return (*Request)(r), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func xmlToReadCloser(xmldata ...interface{}) (io.ReadCloser, int, error) {
|
||||||
|
var buffer []string
|
||||||
|
for _, xmldatum := range xmldata {
|
||||||
|
if encoded, err := xml.Marshal(xmldatum); err != nil {
|
||||||
|
return nil, 0, utils.NewError(xmlToReadCloser, "unable to encode as xml", xmldatum, err)
|
||||||
|
} else {
|
||||||
|
buffer = append(buffer, string(encoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(buffer) > 0 {
|
||||||
|
var encoded = strings.Join(buffer, "\n")
|
||||||
|
// log.Printf("[WebDAV Request]\n%+v\n", encoded)
|
||||||
|
return ioutil.NopCloser(bytes.NewBuffer([]byte(encoded))), len(encoded), nil
|
||||||
|
} else {
|
||||||
|
return nil, 0, nil
|
||||||
|
}
|
||||||
|
}
|
54
vendor/github.com/dolanor/caldav-go/webdav/response.go
generated
vendored
Normal file
54
vendor/github.com/dolanor/caldav-go/webdav/response.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package webdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"github.com/dolanor/caldav-go/http"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = log.Print
|
||||||
|
var _ = ioutil.ReadAll
|
||||||
|
|
||||||
|
// a WebDAV response object
|
||||||
|
type Response http.Response
|
||||||
|
|
||||||
|
// downcasts the response to the local HTTP interface
|
||||||
|
func (r *Response) Http() *http.Response {
|
||||||
|
return (*http.Response)(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a list of WebDAV features found in the response
|
||||||
|
func (r *Response) Features() (features []string) {
|
||||||
|
if dav := r.Header.Get("DAV"); dav != "" {
|
||||||
|
features = strings.Split(dav, ", ")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodes a WebDAV XML response into the provided interface
|
||||||
|
func (r *Response) Decode(into interface{}) error {
|
||||||
|
// data, _ := ioutil.ReadAll(r.Body)
|
||||||
|
// log.Printf("[WebDAV Response]\n%+v\n", string(data))
|
||||||
|
// if err := xml.Unmarshal(data, into); err != nil {
|
||||||
|
// return utils.NewError(r.Decode, "unable to decode response body", r, err)
|
||||||
|
// } else {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
if body := r.Body; body == nil {
|
||||||
|
return nil
|
||||||
|
} else if decoder := xml.NewDecoder(body); decoder == nil {
|
||||||
|
return nil
|
||||||
|
} else if err := decoder.Decode(into); err != nil {
|
||||||
|
return utils.NewError(r.Decode, "unable to decode response body", r, err)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new WebDAV response object
|
||||||
|
func NewResponse(response *http.Response) *Response {
|
||||||
|
return (*Response)(response)
|
||||||
|
}
|
28
vendor/github.com/dolanor/caldav-go/webdav/server.go
generated
vendored
Normal file
28
vendor/github.com/dolanor/caldav-go/webdav/server.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package webdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dolanor/caldav-go/http"
|
||||||
|
"github.com/dolanor/caldav-go/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// a server that accepts WebDAV requests
|
||||||
|
type Server http.Server
|
||||||
|
|
||||||
|
// creates a reference to an WebDAV server
|
||||||
|
func NewServer(baseUrlStr string) (*Server, error) {
|
||||||
|
if s, err := http.NewServer(baseUrlStr); err != nil {
|
||||||
|
return nil, utils.NewError(NewServer, "unable to create WebDAV server", baseUrlStr, err)
|
||||||
|
} else {
|
||||||
|
return (*Server)(s), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// downcasts the server to the local HTTP interface
|
||||||
|
func (s *Server) Http() *http.Server {
|
||||||
|
return (*http.Server)(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new WebDAV request object
|
||||||
|
func (s *Server) NewRequest(method string, path string, xmldata ...interface{}) (*Request, error) {
|
||||||
|
return NewRequest(method, s.Http().AbsUrlStr(path), xmldata...)
|
||||||
|
}
|
13
vendor/modules.txt
vendored
13
vendor/modules.txt
vendored
@ -4,6 +4,19 @@ github.com/beorn7/perks/quantile
|
|||||||
# github.com/cespare/xxhash/v2 v2.1.2
|
# github.com/cespare/xxhash/v2 v2.1.2
|
||||||
## explicit; go 1.11
|
## explicit; go 1.11
|
||||||
github.com/cespare/xxhash/v2
|
github.com/cespare/xxhash/v2
|
||||||
|
# github.com/dolanor/caldav-go v0.2.1
|
||||||
|
## explicit; go 1.13
|
||||||
|
github.com/dolanor/caldav-go/caldav
|
||||||
|
github.com/dolanor/caldav-go/caldav/entities
|
||||||
|
github.com/dolanor/caldav-go/caldav/values
|
||||||
|
github.com/dolanor/caldav-go/http
|
||||||
|
github.com/dolanor/caldav-go/icalendar
|
||||||
|
github.com/dolanor/caldav-go/icalendar/components
|
||||||
|
github.com/dolanor/caldav-go/icalendar/properties
|
||||||
|
github.com/dolanor/caldav-go/icalendar/values
|
||||||
|
github.com/dolanor/caldav-go/utils
|
||||||
|
github.com/dolanor/caldav-go/webdav
|
||||||
|
github.com/dolanor/caldav-go/webdav/entities
|
||||||
# github.com/golang/protobuf v1.5.2
|
# github.com/golang/protobuf v1.5.2
|
||||||
## explicit; go 1.9
|
## explicit; go 1.9
|
||||||
github.com/golang/protobuf/proto
|
github.com/golang/protobuf/proto
|
||||||
|
Reference in New Issue
Block a user