diff --git a/config.go b/config.go index 5b9c831..d01a78e 100644 --- a/config.go +++ b/config.go @@ -1,8 +1,8 @@ package caldav import ( - "github.com/samedi/caldav-go/data" - "github.com/samedi/caldav-go/global" + "github.com/ngradwohl/caldav-go/data" + "github.com/ngradwohl/caldav-go/global" ) // SetupStorage sets the storage to be used by the server. The storage is where the resources data will be fetched from. diff --git a/data/filters.go b/data/filters.go index 24349f2..3c7c8d0 100644 --- a/data/filters.go +++ b/data/filters.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/samedi/caldav-go/lib" + "github.com/ngradwohl/caldav-go/lib" ) const ( diff --git a/data/resource.go b/data/resource.go index 41dd27a..fbb3b35 100644 --- a/data/resource.go +++ b/data/resource.go @@ -8,11 +8,12 @@ import ( "strconv" "strings" "time" + "regexp" "github.com/laurent22/ical-go" - "github.com/samedi/caldav-go/files" - "github.com/samedi/caldav-go/lib" + "github.com/ngradwohl/caldav-go/files" + "github.com/ngradwohl/caldav-go/lib" ) // ResourceInterface defines the main interface of a CalDAV resource object. This @@ -69,10 +70,13 @@ type Resource struct { func NewResource(rawPath string, adp ResourceAdapter) Resource { pClean := lib.ToSlashPath(rawPath) pSplit := strings.Split(strings.Trim(pClean, "/"), "/") - + p := pClean + if (adp.IsCollection()) { + p+="/" + } return Resource{ Name: pSplit[len(pSplit)-1], - Path: pClean, + Path: p, pathSplit: pSplit, adapter: adp, } @@ -128,8 +132,20 @@ func (r *Resource) EndTimeUTC() time.Time { // Recurrences returns an array of resource recurrences. // NOTE: Recurrences are not supported yet. An empty array will always be returned. func (r *Resource) Recurrences() []ResourceRecurrence { - // TODO: Implement. This server does not support iCal recurrences yet. We just return an empty array. - return []ResourceRecurrence{} + vevent := r.icalVEVENT() + rrule := vevent.PropString("RRULE", "") + + if ( rrule != "" ) { + log.Printf("RECURRENCE : %s, Path: %s", rrule, r.Path ) + start := r.StartTimeUTC() + end := r.EndTimeUTC() + duration := end.Sub(start) + result := r.calcRecurrences( start, duration, rrule ) + return result + + } else { + return []ResourceRecurrence{} + } } // HasProperty tells whether the resource has the provided property in its iCal content. @@ -320,6 +336,56 @@ func (r *Resource) icalendar() *ical.Node { return icalNode } +func (r *Resource) calcRecurrences( start time.Time, duration time.Duration, rrule string) ([]ResourceRecurrence) { + result := []ResourceRecurrence{} + rule := NewRecurrenceRule(rrule) + + count := rule.getIntParam("COUNT", 1000) + until := rule.getTimeParam("UNTIL", time.Date(9999,12,31,23,59,59,00,time.UTC)) + + //log.Println("UNTIL ", until) + c := 0 + stmp := start + var skip bool + for c < count { + c += 1 + stmp, skip = rule.GetNext(stmp) + if (!stmp.Before(until)) { + break + } + if (skip) { + continue + } + recurrence := ResourceRecurrence{ stmp, stmp.Add(duration) } + result = append(result, recurrence) + } + + // TODO Parse rrule + // start: + // FREQ (SECONDLY, MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY) + // INTERVAL (done) + // These can either be filters or set a value directly - if the freq is smaller than the unit it filters otherwise sets to a fixed value + // cond: + // BYSECOND + // BYMINUTE + // BYHOUR + // BYDAY + // BYMONTHDAY + // BYYEARDAY + // BYWEEKNO + // BYMONTH + // BYSETPOS + // WKST + + // end + // COUNT (done) + // UNTIL (done) + + // TODO add rdate + // TODO remove exdate + return result; +} + // FileResourceAdapter implements the `ResourceAdapter` for resources stored as files in the file system. type FileResourceAdapter struct { finfo os.FileInfo @@ -369,3 +435,299 @@ func (adp *FileResourceAdapter) CalculateEtag() string { func (adp *FileResourceAdapter) GetModTime() time.Time { return adp.finfo.ModTime() } + + +type RecurrenceRuleInterface interface { + GetIntParam(name string, defaultValue int) int + GetStringParam(name string, defaultValue string) string + GetTimeParam(name string, defaultValue time.Time) time.Time +} + +type RecurrenceRule struct { + rrule string + params map[string]string +} + +func NewRecurrenceRule(rrule string) RecurrenceRule { + var rex = regexp.MustCompile("(\\w+)=([a-zA-Z0-9-]+)") + data := rex.FindAllStringSubmatch(rrule, -1) + + p := make(map[string]string) + for _, kv := range data { + k := kv[1] + v := kv[2] + p[k] = v + } + + return RecurrenceRule{ + rrule: rrule, + params: p, + } +} + +func (r *RecurrenceRule) getIntParam(name string, defaultValue int) int { + v := defaultValue + if val, ok := r.params[name]; ok { + tmp, err := strconv.Atoi(val) + if err == nil { + v = tmp + } + } + return v +} + +func( r *RecurrenceRule) hasParam(name string) bool { + if _, ok := r.params[name]; ok { + return true; + } + return false +} + +func (r *RecurrenceRule) getParam(name string, defaultValue string) string { + v := defaultValue + if val, ok := r.params[name]; ok { + v = val + } + return v +} + +func (r *RecurrenceRule) getTimeParam(name string, defaultValue time.Time) time.Time { + v := defaultValue + if tmp,ok := r.params[name]; ok { + if d,ok2 := time.Parse("20060102T150405Z", tmp);ok2==nil { + v = d + } + } + return v +} + +func (r *RecurrenceRule) GetNext(start time.Time) (time.Time, bool) { + interval := r.getIntParam("INTERVAL", 1) + var inc time.Duration + + freq := r.getParam("FREQ", "") + var res time.Time + + switch freq { + case "SECONDLY": + inc,_ = time.ParseDuration("1s") + inc = time.Duration(int64(inc)*int64(interval)) + res = start.Add(inc) + case "MINUTELY": + inc,_ = time.ParseDuration("1m") + inc = time.Duration(int64(inc)*int64(interval)) + res = start.Add(inc) + case "HOURLY": + inc,_ = time.ParseDuration("1h") + inc = time.Duration(int64(inc)*int64(interval)) + res = start.Add(inc) + case "DAILY": + inc,_ = time.ParseDuration("24h") + inc = time.Duration(int64(inc)*int64(interval)) + res = start.Add(inc) + case "WEEKLY": + inc,_ = time.ParseDuration("168h") + inc = time.Duration(int64(inc)*int64(interval)) + res = start.Add(inc) + case "MONTHLY": + year:=start.Year() + month:=int(start.Month())-1 + if month + interval >= 12 { + year = year + 1 + } + month = (month + interval) % 12 + day:=start.Day() + hour:=start.Hour() + minute:=start.Minute() + second:=start.Second() + nanosecond:=start.Nanosecond() + res = time.Date(year, time.Month(month+1), day, hour, minute, second, nanosecond, time.UTC) + case "YEARLY": + year:=start.Year() + year = year + interval + + month:=start.Month() + day:=start.Day() + hour:=start.Hour() + minute:=start.Minute() + second:=start.Second() + nanosecond:=start.Nanosecond() + res = time.Date(year, month, day, hour, minute, second, nanosecond, time.UTC) + default: + inc,_ = time.ParseDuration("24h") + inc = time.Duration(int64(inc)*int64(interval)) + res = start.Add(inc) + } + return r.replaceBy(res), r.skipBy(res) +} + +func (r *RecurrenceRule) replaceBy(start time.Time) time.Time { + // TODO WKST + // TODO LISTS of by values + // TODO BYEASTER + // TODO BYWEEKNO + fint := r.freqToInt(r.getParam("FREQ", "")) + t := start + if (fint == 4 && r.hasParam("BYDAY")) { // Weekly + w1 := int(t.Weekday()) + w2 := r.parseWeekday(r.getParam("BYDAY", "")) + wdiff := w2-w1 + if wdiff < 0 { + wdiff += 7 + } + inc,_ := time.ParseDuration("24h") + inc = time.Duration(int64(inc)*int64(wdiff)) + t = start.Add(inc) + } + + year:=t.Year() + month:=int(t.Month())-1 + if (fint > 5 && r.hasParam("BYMONTH")) { + month = r.getIntParam("BYMONTH", 0)-1 + } + day:=t.Day() + if (fint == 6 && r.hasParam("BYYEARDAY")) { + first := time.Date(year, time.Month(1), 1, 0, 0, 0, 0, time.UTC) + inc,_ := time.ParseDuration("24h") + days := r.getIntParam("BYYEARDAY",0) + inc = time.Duration(int64(inc)*int64(days)) + tmp := first.Add(inc) + day = tmp.Day() + month =int(tmp.Month())-1 + } + + if (fint > 4 && r.hasParam("BYMONTHDAY")) { + day = r.getIntParam("BYMONTHDAY", 0) + } + if (fint == 5 && r.hasParam("BYDAY")) { // Monthly + byday := r.getParam("BYDAY", "") + d:=0 + pos:= r.getIntParam("BYPOS", 1) + if (len(byday) <= 2) { + d = r.parseWeekday(byday) + } else { + d = r.parseWeekday(byday[len(byday)-1:]) + tmp,err:=strconv.Atoi(byday[:len(byday)-2]) + if err == nil { + pos = tmp + } + } + if pos > 0 { + first := time.Date(year, time.Month(month+1), 1, 0, 0, 0, 0, time.UTC) + + w1 := int(first.Weekday()) + wdiff := d - w1; + if wdiff < 0 { + wdiff+=7 + } + day = 1+ wdiff + 7*(pos-1) + } else { + yofs := 0 + if month == 11 { + yofs = 1 // handle december + } + first := time.Date(year+yofs, time.Month(month+2), 1, 0, 0, 0, 0, time.UTC) + + w1 := int(first.Weekday()) + wdiff := d - w1; + if wdiff < 0 { + wdiff+=7 + } + wdiff += pos * 7 + inc,_ := time.ParseDuration("24h") + inc = time.Duration(int64(inc)*int64(wdiff)) + day = first.Add(inc).Day() + } + } + + hour:=t.Hour() + if (fint > 3 && r.hasParam("BYHOUR")) { + hour = r.getIntParam("BYHOUR", 0) + } + minute:=t.Minute() + if (fint > 2 && r.hasParam("BYMINUTE")) { + minute = r.getIntParam("BYMINUTE", 0) + } + second:=t.Second() + if (fint > 1 && r.hasParam("BYSECOND")) { + second = r.getIntParam("BYSECOND", 0) + } + nanosecond:=t.Nanosecond() + + t = time.Date(year, time.Month(month+1), day, hour, minute, second, nanosecond, time.UTC) + return t; +} + +func (r *RecurrenceRule) freqToInt(freq string) int { + switch freq { + case "SECONDLY": + return 0 + case "MINUTELY": + return 1 + case "HOURLY": + return 2 + case "DAILY": + return 3 + case "WEEKLY": + return 4 + case "MONTHLY": + return 5 + case "YEARLY": + return 6 + default: + return 6 + } +} + +func (r *RecurrenceRule) parseWeekday(day string) int { + i, err := strconv.Atoi(day) + if err == nil { + return i + } + + switch day { + case "SO": + return 0 + case "MO": + return 1 + case "TU": + return 2 + case "WE": + return 3 + case "TH": + return 4 + case "FR": + return 5 + case "SA": + return 6 + default: + return 1 + } +} + +func (r *RecurrenceRule) skipBy(t time.Time) bool { + fint := r.freqToInt(r.getParam("FREQ", "")) + month:=int(t.Month()) + hour:=t.Hour() + minute:=t.Minute() + second:=t.Second() + + if (fint <= 5 && r.hasParam("BYMONTH")) { + return r.getIntParam("BYMONTH",1) != month; + } + if (fint <= 3 && r.hasParam("BYDAY")) { + d1 := int(t.Weekday()) + d2 := r.parseWeekday( r.getParam("BYDAY","")) + return d1 != d2 + } + if (fint <= 2 && r.hasParam("BYHOUR")) { + return r.getIntParam("BYHOUR",0) != hour + } + if (fint <= 1 && r.hasParam("BYMINUTE")) { + return r.getIntParam("BYMINUTE",0) != minute + } + if (fint == 0 && r.hasParam("BYSECOND")) { + return r.getIntParam("BYSECOND",0) != second + } + return false +} diff --git a/data/resource_test.go b/data/resource_test.go index f13718f..05ea9b2 100644 --- a/data/resource_test.go +++ b/data/resource_test.go @@ -327,6 +327,236 @@ func TestPropertyParams(t *testing.T) { } } +func TestRecurrenceOnce(t *testing.T) { + adp := new(FakeResourceAdapter) + res := NewResource("/foo", adp) + + adp.contentData = ` + BEGIN:VCALENDAR + BEGIN:VEVENT + DTSTART:20160914T170000Z + DTEND:20160914T180000Z + RRULE: FREQ=DAILY;COUNT=1 + END:VEVENT + END:VCALENDAR + ` + if len(res.Recurrences()) != 1 { + t.Error("Expected 1 Recurrencies got, ", len(res.Recurrences())) + } + + if (res.Recurrences()[0].StartTime != time.Date(2016,9,15,17,0,0,0, time.UTC)) { + t.Error("Unexpected Start time, ", res.Recurrences()[0].StartTime); + } +} + +func TestRecurrenceCountInterval(t *testing.T) { + adp := new(FakeResourceAdapter) + res := NewResource("/foo", adp) + + adp.contentData = ` + BEGIN:VCALENDAR + BEGIN:VEVENT + DTSTART:20160914T170000Z + DTEND:20160914T180000Z + RRULE: FREQ=DAILY;COUNT=2;INTERVAL=2 + END:VEVENT + END:VCALENDAR + ` + if len(res.Recurrences()) != 2 { + t.Error("Expected 2 Recurrencies got, ", len(res.Recurrences())) + } + + if (res.Recurrences()[1].StartTime != time.Date(2016,9,18,17,0,0,0, time.UTC)) { + t.Error("Unexpected Start time, ", res.Recurrences()[1].StartTime); + } +} + +func TestRecurrenceCountYearly(t *testing.T) { + adp := new(FakeResourceAdapter) + res := NewResource("/foo", adp) + + adp.contentData = ` + BEGIN:VCALENDAR + BEGIN:VEVENT + DTSTART:20160914T170000Z + DTEND:20160914T180000Z + RRULE: FREQ=YEARLY;COUNT=2;INTERVAL=2 + END:VEVENT + END:VCALENDAR + ` + if len(res.Recurrences()) != 2 { + t.Error("Expected 2 Recurrencies got, ", len(res.Recurrences())) + } + + if (res.Recurrences()[1].StartTime != time.Date(2020,9,14,17,0,0,0, time.UTC)) { + t.Error("Unexpected Start time, ", res.Recurrences()[1].StartTime); + } +} + + +func TestRecurrenceCountMonthly(t *testing.T) { + adp := new(FakeResourceAdapter) + res := NewResource("/foo", adp) + + adp.contentData = ` + BEGIN:VCALENDAR + BEGIN:VEVENT + DTSTART:20160914T170000Z + DTEND:20160914T180000Z + RRULE: FREQ=MONTHLY;COUNT=2;INTERVAL=2 + END:VEVENT + END:VCALENDAR + ` + if len(res.Recurrences()) != 2 { + t.Error("Expected 2 Recurrencies got, ", len(res.Recurrences())) + } + + if (res.Recurrences()[1].StartTime != time.Date(2017,1,14,17,0,0,0, time.UTC)) { + t.Error("Unexpected Start time, ", res.Recurrences()[1].StartTime); + } +} + +func TestRecurrenceUntil(t *testing.T) { + adp := new(FakeResourceAdapter) + res := NewResource("/foo", adp) + + adp.contentData = ` + BEGIN:VCALENDAR + BEGIN:VEVENT + DTSTART:20160914T170000Z + DTEND:20160914T180000Z + RRULE: FREQ=WEEKLY; UNTIL=20160929T000000Z + END:VEVENT + END:VCALENDAR + ` + if len(res.Recurrences()) != 2 { + t.Error("Expected 2 Recurrencies got, ", res.Recurrences()) + } + + if (res.Recurrences()[1].StartTime != time.Date(2016,9,28,17,0,0,0, time.UTC)) { + t.Error("Unexpected Start time, ", res.Recurrences()[1].StartTime); + } +} + +func TestByWeekday(t *testing.T) { + adp := new(FakeResourceAdapter) + res := NewResource("/foo", adp) + + adp.contentData = ` + BEGIN:VCALENDAR + BEGIN:VEVENT + DTSTART:20190101T170000Z + DTEND:20190101T180000Z + RRULE: FREQ=WEEKLY;BYDAY=MO;COUNT=3 + END:VEVENT + END:VCALENDAR + ` + if len(res.Recurrences()) != 3 { + t.Error("Expected 3 Recurrencies got, ", res.Recurrences()) + } + + if (res.Recurrences()[2].StartTime != time.Date(2019,1,28,17,0,0,0, time.UTC)) { + t.Error("Unexpected Start time, ", res.Recurrences()[2].StartTime); + } +} + +func TestByWeekdayNum(t *testing.T) { + adp := new(FakeResourceAdapter) + res := NewResource("/foo", adp) + + adp.contentData = ` + BEGIN:VCALENDAR + BEGIN:VEVENT + DTSTART:20190101T170000Z + DTEND:20190101T180000Z + RRULE: FREQ=WEEKLY;BYDAY=1;COUNT=3 + END:VEVENT + END:VCALENDAR + ` + if len(res.Recurrences()) != 3 { + t.Error("Expected 3 Recurrencies got, ", res.Recurrences()) + } + + if (res.Recurrences()[2].StartTime != time.Date(2019,1,28,17,0,0,0, time.UTC)) { + t.Error("Unexpected Start time, ", res.Recurrences()[2].StartTime); + } +} + +func TestByWeekdayPos(t *testing.T) { + adp := new(FakeResourceAdapter) + res := NewResource("/foo", adp) + + adp.contentData = ` + BEGIN:VCALENDAR + BEGIN:VEVENT + DTSTART:20190101T170000Z + DTEND:20190101T180000Z + RRULE: FREQ=MONTHLY;BYDAY=2MO;COUNT=3 + END:VEVENT + END:VCALENDAR + ` + + fmt.Println(res.Recurrences()) + if len(res.Recurrences()) != 3 { + t.Error("Expected 3 Recurrencies got, ", res.Recurrences()) + } + + if (res.Recurrences()[2].StartTime != time.Date(2019,4,8,17,0,0,0, time.UTC)) { + t.Error("Unexpected Start time, ", res.Recurrences()[2].StartTime); + } +} + +func TestByWeekdayPosParam(t *testing.T) { + adp := new(FakeResourceAdapter) + res := NewResource("/foo", adp) + + adp.contentData = ` + BEGIN:VCALENDAR + BEGIN:VEVENT + DTSTART:20190101T170000Z + DTEND:20190101T180000Z + RRULE: FREQ=MONTHLY;BYDAY=MO;BYPOS=2;COUNT=3 + END:VEVENT + END:VCALENDAR + ` + + fmt.Println(res.Recurrences()) + if len(res.Recurrences()) != 3 { + t.Error("Expected 3 Recurrencies got, ", res.Recurrences()) + } + + if (res.Recurrences()[2].StartTime != time.Date(2019,4,8,17,0,0,0, time.UTC)) { + t.Error("Unexpected Start time, ", res.Recurrences()[2].StartTime); + } +} + +func TestByWeekdayPosNeg(t *testing.T) { + adp := new(FakeResourceAdapter) + res := NewResource("/foo", adp) + + adp.contentData = ` + BEGIN:VCALENDAR + BEGIN:VEVENT + DTSTART:20190101T170000Z + DTEND:20190101T180000Z + RRULE: FREQ=MONTHLY;BYDAY=-1MO;COUNT=3 + END:VEVENT + END:VCALENDAR + ` + + fmt.Println(res.Recurrences()) + if len(res.Recurrences()) != 3 { + t.Error("Expected 3 Recurrencies got, ", res.Recurrences()) + } + + if (res.Recurrences()[2].StartTime != time.Date(2019,4,29,17,0,0,0, time.UTC)) { + t.Error("Unexpected Start time, ", res.Recurrences()[2].StartTime); + } +} + + + + type FakeResourceAdapter struct { collection bool etag string diff --git a/data/storage.go b/data/storage.go index 524fa10..68e1a91 100644 --- a/data/storage.go +++ b/data/storage.go @@ -1,8 +1,8 @@ package data import ( - "github.com/samedi/caldav-go/errs" - "github.com/samedi/caldav-go/files" + "github.com/ngradwohl/caldav-go/errs" + "github.com/ngradwohl/caldav-go/files" "io/ioutil" "log" "os" diff --git a/files/paths.go b/files/paths.go index cfcc4e8..9fb8851 100644 --- a/files/paths.go +++ b/files/paths.go @@ -1,7 +1,7 @@ package files import ( - "github.com/samedi/caldav-go/lib" + "github.com/ngradwohl/caldav-go/lib" "path/filepath" "strings" ) diff --git a/glide.yaml b/glide.yaml index 03463de..c6186b8 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,4 +1,4 @@ -package: github.com/samedi/caldav-go +package: github.com/ngradwohl/caldav-go import: - package: github.com/beevik/etree - package: github.com/laurent22/ical-go diff --git a/global/global.go b/global/global.go index 371c8a6..baab764 100644 --- a/global/global.go +++ b/global/global.go @@ -3,8 +3,8 @@ package global import ( - "github.com/samedi/caldav-go/data" - "github.com/samedi/caldav-go/lib" + "github.com/ngradwohl/caldav-go/data" + "github.com/ngradwohl/caldav-go/lib" ) // Storage represents the global storage used in the CRUD operations of resources. Default storage is the `data.FileStorage`. diff --git a/handler.go b/handler.go index b4bc63e..8dd2777 100644 --- a/handler.go +++ b/handler.go @@ -3,8 +3,8 @@ package caldav import ( "net/http" - "github.com/samedi/caldav-go/data" - "github.com/samedi/caldav-go/handlers" + "github.com/ngradwohl/caldav-go/data" + "github.com/ngradwohl/caldav-go/handlers" ) // RequestHandler handles the given CALDAV request and writes the reponse righ away. This function is to be diff --git a/handlers/builder.go b/handlers/builder.go index e7f638d..628b7bc 100644 --- a/handlers/builder.go +++ b/handlers/builder.go @@ -3,8 +3,8 @@ package handlers import ( "net/http" - "github.com/samedi/caldav-go/data" - "github.com/samedi/caldav-go/global" + "github.com/ngradwohl/caldav-go/data" + "github.com/ngradwohl/caldav-go/global" ) // HandlerInterface represents a CalDAV request handler. It has only one function `Handle`, diff --git a/handlers/multistatus.go b/handlers/multistatus.go index 6994e0f..bf35a3b 100644 --- a/handlers/multistatus.go +++ b/handlers/multistatus.go @@ -3,10 +3,10 @@ package handlers import ( "encoding/xml" "fmt" - "github.com/samedi/caldav-go/data" - "github.com/samedi/caldav-go/global" - "github.com/samedi/caldav-go/ixml" - "github.com/samedi/caldav-go/lib" + "github.com/ngradwohl/caldav-go/data" + "github.com/ngradwohl/caldav-go/global" + "github.com/ngradwohl/caldav-go/ixml" + "github.com/ngradwohl/caldav-go/lib" "net/http" ) @@ -128,6 +128,13 @@ func (ms *multistatusResp) Propstats(resource *data.Resource, reqprops []xml.Nam } pfound = true } + case ixml.CALENDAR_COLOR_TG: + if resource.IsCollection() { + if (resource.HasProperty("VEVENT","COLOR")) { + pvalue.Contents = append(pvalue.Contents, resource.GetPropertyValue("VEVENT","COLOR")) + pfound = true + } + } } if !pfound { diff --git a/handlers/multistatus_test.go b/handlers/multistatus_test.go index dccd6cc..f57823d 100644 --- a/handlers/multistatus_test.go +++ b/handlers/multistatus_test.go @@ -4,7 +4,7 @@ import ( "encoding/xml" "testing" - "github.com/samedi/caldav-go/test" + "github.com/ngradwohl/caldav-go/test" ) // Tests the XML serialization when the option to return a minimal content is set or not. @@ -29,7 +29,7 @@ func TestToXML(t *testing.T) { ms.Minimal = false expected := ` - + /123 @@ -55,7 +55,7 @@ func TestToXML(t *testing.T) { ms.Minimal = true expected = ` - + /123 @@ -80,7 +80,7 @@ func TestToXML(t *testing.T) { expected = ` - + /123 diff --git a/handlers/put.go b/handlers/put.go index 339439c..0e3484d 100644 --- a/handlers/put.go +++ b/handlers/put.go @@ -1,7 +1,7 @@ package handlers import ( - "github.com/samedi/caldav-go/errs" + "github.com/ngradwohl/caldav-go/errs" "net/http" ) diff --git a/handlers/report.go b/handlers/report.go index 5171aa3..4d99e92 100644 --- a/handlers/report.go +++ b/handlers/report.go @@ -6,8 +6,8 @@ import ( "net/http" "strings" - "github.com/samedi/caldav-go/data" - "github.com/samedi/caldav-go/ixml" + "github.com/ngradwohl/caldav-go/data" + "github.com/ngradwohl/caldav-go/ixml" ) type reportHandler struct { diff --git a/handlers/report_test.go b/handlers/report_test.go index b047ec2..13329fc 100644 --- a/handlers/report_test.go +++ b/handlers/report_test.go @@ -5,8 +5,8 @@ import ( "net/http" "testing" - "github.com/samedi/caldav-go/ixml" - "github.com/samedi/caldav-go/test" + "github.com/ngradwohl/caldav-go/ixml" + "github.com/ngradwohl/caldav-go/test" ) // Test 1: when the URL path points to a collection and passing the list of hrefs in the body. @@ -42,7 +42,7 @@ func TestHandle1(t *testing.T) { // the ones that do not belong are ignored. expectedRespBody := fmt.Sprintf(` - + /test-data/report/123-456-789.ics @@ -105,7 +105,7 @@ func TestHandle2(t *testing.T) { // The response should contain only the resource from the URL. The rest are ignored expectedRespBody := fmt.Sprintf(` - + /test-data/report/123-456-789.ics @@ -160,7 +160,7 @@ func TestHandle3(t *testing.T) { expectedRespBody := fmt.Sprintf(` - + /test-data/report/football.ics @@ -218,7 +218,7 @@ func TestHandle4(t *testing.T) { // The response should omit all the nodes with status 404. expectedRespBody := fmt.Sprintf(` - + /test-data/report/123-456-789.ics diff --git a/handlers/response.go b/handlers/response.go index 3e87a4d..b5255d2 100644 --- a/handlers/response.go +++ b/handlers/response.go @@ -1,7 +1,7 @@ package handlers import ( - "github.com/samedi/caldav-go/errs" + "github.com/ngradwohl/caldav-go/errs" "io" "net/http" ) diff --git a/integration_test.go b/integration_test.go index 4e6fc96..e03afb7 100644 --- a/integration_test.go +++ b/integration_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "github.com/samedi/caldav-go/ixml" - "github.com/samedi/caldav-go/test" + "github.com/ngradwohl/caldav-go/ixml" + "github.com/ngradwohl/caldav-go/test" ) // ============= TESTS ====================== @@ -186,7 +186,7 @@ func TestPROPFIND(t *testing.T) { ` expectedRespBody := fmt.Sprintf(` - + /test-data/propfind/123-456-789.ics @@ -238,7 +238,7 @@ func TestPROPFIND(t *testing.T) { ` expectedRespBody = fmt.Sprintf(` - + /test-data/propfind/123-456-789.ics @@ -274,7 +274,7 @@ func TestPROPFIND(t *testing.T) { // the response should omit all the nodes with status 404. expectedRespBody = fmt.Sprintf(` - + /test-data/propfind/123-456-789.ics @@ -313,9 +313,9 @@ func TestPROPFIND(t *testing.T) { expectedRespBody = ` - + - /test-data/propfind + /test-data/propfind/ text/calendar @@ -335,9 +335,9 @@ func TestPROPFIND(t *testing.T) { expectedRespBody = ` - + - /test-data/propfind + /test-data/propfind/ text/calendar @@ -383,7 +383,7 @@ func TestREPORT(t *testing.T) { expectedRespBody := fmt.Sprintf(` - + /test-data/report/123-456-789.ics diff --git a/ixml/ixml.go b/ixml/ixml.go index 3097cb9..76a77af 100644 --- a/ixml/ixml.go +++ b/ixml/ixml.go @@ -6,19 +6,21 @@ import ( "fmt" "net/http" - "github.com/samedi/caldav-go/lib" + "github.com/ngradwohl/caldav-go/lib" ) const ( DAV_NS = "DAV:" CALDAV_NS = "urn:ietf:params:xml:ns:caldav" CALSERV_NS = "http://calendarserver.org/ns/" + APPLE_NS = "http://apple.com/ns/ical/" ) var NS_PREFIXES = map[string]string{ DAV_NS: "D", CALDAV_NS: "C", CALSERV_NS: "CS", + APPLE_NS: "A", } var ( @@ -44,6 +46,7 @@ var ( RESOURCE_TYPE_TG = xml.Name{DAV_NS, "resourcetype"} STATUS_TG = xml.Name{DAV_NS, "status"} SUPPORTED_CALENDAR_COMPONENT_SET_TG = xml.Name{CALDAV_NS, "supported-calendar-component-set"} + CALENDAR_COLOR_TG = xml.Name{APPLE_NS, "calendar-color"} ) // Namespaces returns the default XML namespaces in for CalDAV contents. @@ -51,7 +54,8 @@ func Namespaces() string { bf := new(lib.StringBuffer) bf.Write(`xmlns:%s="%s" `, NS_PREFIXES[DAV_NS], DAV_NS) bf.Write(`xmlns:%s="%s" `, NS_PREFIXES[CALDAV_NS], CALDAV_NS) - bf.Write(`xmlns:%s="%s"`, NS_PREFIXES[CALSERV_NS], CALSERV_NS) + bf.Write(`xmlns:%s="%s" `, NS_PREFIXES[CALSERV_NS], CALSERV_NS) + bf.Write(`xmlns:%s="%s"`, NS_PREFIXES[APPLE_NS], APPLE_NS) return bf.String() } diff --git a/test/resources.go b/test/resources.go index 2935312..efba5a6 100644 --- a/test/resources.go +++ b/test/resources.go @@ -3,8 +3,8 @@ package test import ( "os" - "github.com/samedi/caldav-go/data" - "github.com/samedi/caldav-go/global" + "github.com/ngradwohl/caldav-go/data" + "github.com/ngradwohl/caldav-go/global" ) // Creates a fake storage to be used in unit tests.