From 08adbdaf1fede8906b433f9bb842d71fe7ee5c3c Mon Sep 17 00:00:00 2001 From: Sean Slater Date: Sat, 1 Apr 2023 08:58:07 -0400 Subject: [PATCH 01/10] Add Todo and Journal Property Methods - Add methods for VTodo and VJournal - Changed the shared properties to be attached to the ComponentBase so those methods don't need to be repeated across all attributes that share them - If a property is shared across some, but not all components, then it's a private method on the ComponentBase and public method on the Components (eg LOCATION for Event and Todo but not Journal) - Shifted position of some Event methods in the file to live after the VEvent struct --- calendar.go | 91 ++++------- components.go | 414 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 332 insertions(+), 173 deletions(-) diff --git a/calendar.go b/calendar.go index 5b3d7c4..a68bee8 100644 --- a/calendar.go +++ b/calendar.go @@ -26,33 +26,38 @@ const ( type ComponentProperty Property const ( - ComponentPropertyUniqueId = ComponentProperty(PropertyUid) // TEXT - ComponentPropertyDtstamp = ComponentProperty(PropertyDtstamp) - ComponentPropertyOrganizer = ComponentProperty(PropertyOrganizer) - ComponentPropertyAttendee = ComponentProperty(PropertyAttendee) - ComponentPropertyAttach = ComponentProperty(PropertyAttach) - ComponentPropertyDescription = ComponentProperty(PropertyDescription) // TEXT - ComponentPropertyCategories = ComponentProperty(PropertyCategories) // TEXT - ComponentPropertyClass = ComponentProperty(PropertyClass) // TEXT - ComponentPropertyColor = ComponentProperty(PropertyColor) // TEXT - ComponentPropertyCreated = ComponentProperty(PropertyCreated) - ComponentPropertySummary = ComponentProperty(PropertySummary) // TEXT - ComponentPropertyDtStart = ComponentProperty(PropertyDtstart) - ComponentPropertyDtEnd = ComponentProperty(PropertyDtend) - ComponentPropertyLocation = ComponentProperty(PropertyLocation) // TEXT - ComponentPropertyStatus = ComponentProperty(PropertyStatus) // TEXT - ComponentPropertyFreebusy = ComponentProperty(PropertyFreebusy) - ComponentPropertyLastModified = ComponentProperty(PropertyLastModified) - ComponentPropertyUrl = ComponentProperty(PropertyUrl) - ComponentPropertyGeo = ComponentProperty(PropertyGeo) - ComponentPropertyTransp = ComponentProperty(PropertyTransp) - ComponentPropertySequence = ComponentProperty(PropertySequence) - ComponentPropertyExdate = ComponentProperty(PropertyExdate) - ComponentPropertyExrule = ComponentProperty(PropertyExrule) - ComponentPropertyRdate = ComponentProperty(PropertyRdate) - ComponentPropertyRrule = ComponentProperty(PropertyRrule) - ComponentPropertyAction = ComponentProperty(PropertyAction) - ComponentPropertyTrigger = ComponentProperty(PropertyTrigger) + ComponentPropertyUniqueId = ComponentProperty(PropertyUid) // TEXT + ComponentPropertyDtstamp = ComponentProperty(PropertyDtstamp) + ComponentPropertyOrganizer = ComponentProperty(PropertyOrganizer) + ComponentPropertyAttendee = ComponentProperty(PropertyAttendee) + ComponentPropertyAttach = ComponentProperty(PropertyAttach) + ComponentPropertyDescription = ComponentProperty(PropertyDescription) // TEXT + ComponentPropertyCategories = ComponentProperty(PropertyCategories) // TEXT + ComponentPropertyClass = ComponentProperty(PropertyClass) // TEXT + ComponentPropertyColor = ComponentProperty(PropertyColor) // TEXT + ComponentPropertyCreated = ComponentProperty(PropertyCreated) + ComponentPropertySummary = ComponentProperty(PropertySummary) // TEXT + ComponentPropertyDtStart = ComponentProperty(PropertyDtstart) + ComponentPropertyDtEnd = ComponentProperty(PropertyDtend) + ComponentPropertyLocation = ComponentProperty(PropertyLocation) // TEXT + ComponentPropertyStatus = ComponentProperty(PropertyStatus) // TEXT + ComponentPropertyFreebusy = ComponentProperty(PropertyFreebusy) + ComponentPropertyLastModified = ComponentProperty(PropertyLastModified) + ComponentPropertyUrl = ComponentProperty(PropertyUrl) + ComponentPropertyGeo = ComponentProperty(PropertyGeo) + ComponentPropertyTransp = ComponentProperty(PropertyTransp) + ComponentPropertySequence = ComponentProperty(PropertySequence) + ComponentPropertyExdate = ComponentProperty(PropertyExdate) + ComponentPropertyExrule = ComponentProperty(PropertyExrule) + ComponentPropertyRdate = ComponentProperty(PropertyRdate) + ComponentPropertyRrule = ComponentProperty(PropertyRrule) + ComponentPropertyAction = ComponentProperty(PropertyAction) + ComponentPropertyTrigger = ComponentProperty(PropertyTrigger) + ComponentPropertyPriority = ComponentProperty(PropertyPriority) + ComponentPropertyResources = ComponentProperty(PropertyResources) + ComponentPropertyCompleted = ComponentProperty(PropertyCompleted) + ComponentPropertyDue = ComponentProperty(PropertyDue) + ComponentPropertyPercentComplete = ComponentProperty(PropertyPercentComplete) ) type Property string @@ -404,38 +409,6 @@ func (calendar *Calendar) setProperty(property Property, value string, props ... calendar.CalendarProperties = append(calendar.CalendarProperties, r) } -func NewEvent(uniqueId string) *VEvent { - e := &VEvent{ - ComponentBase{ - Properties: []IANAProperty{ - {BaseProperty{IANAToken: ToText(string(ComponentPropertyUniqueId)), Value: uniqueId}}, - }, - }, - } - return e -} - -func (calendar *Calendar) AddEvent(id string) *VEvent { - e := NewEvent(id) - calendar.Components = append(calendar.Components, e) - return e -} - -func (calendar *Calendar) AddVEvent(e *VEvent) { - calendar.Components = append(calendar.Components, e) -} - -func (calendar *Calendar) Events() (r []*VEvent) { - r = []*VEvent{} - for i := range calendar.Components { - switch event := calendar.Components[i].(type) { - case *VEvent: - r = append(r, event) - } - } - return -} - func ParseCalendar(r io.Reader) (*Calendar, error) { state := "begin" c := &Calendar{} diff --git a/components.go b/components.go index cb88376..f356071 100644 --- a/components.go +++ b/components.go @@ -30,6 +30,7 @@ func (cb *ComponentBase) UnknownPropertiesIANAProperties() []IANAProperty { func (cb *ComponentBase) SubComponents() []Component { return cb.Components } + func (base ComponentBase) serializeThis(writer io.Writer, componentType string) { fmt.Fprint(writer, "BEGIN:"+componentType, "\r\n") for _, p := range base.Properties { @@ -41,6 +42,14 @@ func (base ComponentBase) serializeThis(writer io.Writer, componentType string) fmt.Fprint(writer, "END:"+componentType, "\r\n") } +func NewComponent(uniqueId string) ComponentBase { + return ComponentBase{ + Properties: []IANAProperty{ + {BaseProperty{IANAToken: ToText(string(ComponentPropertyUniqueId)), Value: uniqueId}}, + }, + } +} + func (cb *ComponentBase) GetProperty(componentProperty ComponentProperty) *IANAProperty { for i := range cb.Properties { if cb.Properties[i].IANAToken == string(componentProperty) { @@ -80,20 +89,6 @@ func (cb *ComponentBase) AddProperty(property ComponentProperty, value string, p cb.Properties = append(cb.Properties, r) } -type VEvent struct { - ComponentBase -} - -func (c *VEvent) serialize(w io.Writer) { - c.ComponentBase.serializeThis(w, "VEVENT") -} - -func (c *VEvent) Serialize() string { - b := &bytes.Buffer{} - c.ComponentBase.serializeThis(b, "VEVENT") - return b.String() -} - const ( icalTimestampFormatUtc = "20060102T150405Z" icalTimestampFormatLocal = "20060102T150405" @@ -105,60 +100,32 @@ var ( timeStampVariations = regexp.MustCompile("^([0-9]{8})?([TZ])?([0-9]{6})?(Z)?$") ) -func (event *VEvent) SetCreatedTime(t time.Time, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyCreated, t.UTC().Format(icalTimestampFormatUtc), props...) -} - -func (event *VEvent) SetDtStampTime(t time.Time, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyDtstamp, t.UTC().Format(icalTimestampFormatUtc), props...) +func (cb *ComponentBase) SetCreatedTime(t time.Time, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyCreated, t.UTC().Format(icalTimestampFormatUtc), props...) } -func (event *VEvent) SetModifiedAt(t time.Time, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyLastModified, t.UTC().Format(icalTimestampFormatUtc), props...) +func (cb *ComponentBase) SetDtStampTime(t time.Time, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyDtstamp, t.UTC().Format(icalTimestampFormatUtc), props...) } -func (event *VEvent) SetSequence(seq int, props ...PropertyParameter) { - event.SetProperty(ComponentPropertySequence, strconv.Itoa(seq), props...) +func (cb *ComponentBase) SetModifiedAt(t time.Time, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyLastModified, t.UTC().Format(icalTimestampFormatUtc), props...) } -func (event *VEvent) SetStartAt(t time.Time, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyDtStart, t.UTC().Format(icalTimestampFormatUtc), props...) +func (cb *ComponentBase) SetSequence(seq int, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertySequence, strconv.Itoa(seq), props...) } -func (event *VEvent) SetAllDayStartAt(t time.Time, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyDtStart, t.UTC().Format(icalDateFormatUtc), props...) +func (cb *ComponentBase) SetStartAt(t time.Time, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyDtStart, t.UTC().Format(icalTimestampFormatUtc), props...) } -func (event *VEvent) SetEndAt(t time.Time, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyDtEnd, t.UTC().Format(icalTimestampFormatUtc), props...) -} - -func (event *VEvent) SetAllDayEndAt(t time.Time, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyDtEnd, t.UTC().Format(icalDateFormatUtc), props...) +func (cb *ComponentBase) SetAllDayStartAt(t time.Time, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyDtStart, t.UTC().Format(icalDateFormatUtc), props...) } -// SetDuration updates the duration of an event. -// This function will set either the end or start time of an event depending what is already given. -// The duration defines the length of a event relative to start or end time. -// -// Notice: It will not set the DURATION key of the ics - only DTSTART and DTEND will be affected. -func (event *VEvent) SetDuration(d time.Duration) error { - t, err := event.GetStartAt() - if err == nil { - event.SetEndAt(t.Add(d)) - return nil - } else { - t, err = event.GetEndAt() - if err == nil { - event.SetStartAt(t.Add(-d)) - return nil - } - } - return errors.New("start or end not yet defined") -} - -func (event *VEvent) getTimeProp(componentProperty ComponentProperty, expectAllDay bool) (time.Time, error) { - timeProp := event.GetProperty(componentProperty) +func (cb *ComponentBase) getTimeProp(componentProperty ComponentProperty, expectAllDay bool) (time.Time, error) { + timeProp := cb.GetProperty(componentProperty) if timeProp == nil { return time.Time{}, errors.New("property not found") } @@ -224,99 +191,89 @@ func (event *VEvent) getTimeProp(componentProperty ComponentProperty, expectAllD return time.Time{}, fmt.Errorf("time value matched but not supported, got '%s'", timeVal) } -func (event *VEvent) GetStartAt() (time.Time, error) { - return event.getTimeProp(ComponentPropertyDtStart, false) +func (cb *ComponentBase) GetStartAt() (time.Time, error) { + return cb.getTimeProp(ComponentPropertyDtStart, false) } -func (event *VEvent) GetEndAt() (time.Time, error) { - return event.getTimeProp(ComponentPropertyDtEnd, false) +func (cb *ComponentBase) GetAllDayStartAt() (time.Time, error) { + return cb.getTimeProp(ComponentPropertyDtStart, true) } -func (event *VEvent) GetAllDayStartAt() (time.Time, error) { - return event.getTimeProp(ComponentPropertyDtStart, true) +func (cb *ComponentBase) SetSummary(s string, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertySummary, ToText(s), props...) } -func (event *VEvent) GetAllDayEndAt() (time.Time, error) { - return event.getTimeProp(ComponentPropertyDtEnd, true) +func (cb *ComponentBase) SetStatus(s ObjectStatus, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyStatus, ToText(string(s)), props...) } -type TimeTransparency string - -const ( - TransparencyOpaque TimeTransparency = "OPAQUE" // default - TransparencyTransparent TimeTransparency = "TRANSPARENT" -) - -func (event *VEvent) SetTimeTransparency(v TimeTransparency, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyTransp, string(v), props...) +func (cb *ComponentBase) SetDescription(s string, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyDescription, ToText(s), props...) } -func (event *VEvent) SetSummary(s string, props ...PropertyParameter) { - event.SetProperty(ComponentPropertySummary, ToText(s), props...) +func (cb *ComponentBase) setLocation(s string, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyLocation, ToText(s), props...) } -func (event *VEvent) SetStatus(s ObjectStatus, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyStatus, ToText(string(s)), props...) +func (cb *ComponentBase) setGeo(lat interface{}, lng interface{}, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyGeo, fmt.Sprintf("%v;%v", lat, lng), props...) } -func (event *VEvent) SetDescription(s string, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyDescription, ToText(s), props...) +func (cb *ComponentBase) SetURL(s string, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyUrl, s, props...) } -func (event *VEvent) SetLocation(s string, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyLocation, ToText(s), props...) +func (cb *ComponentBase) SetOrganizer(s string, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyOrganizer, s, props...) } -func (event *VEvent) SetGeo(lat interface{}, lng interface{}, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyGeo, fmt.Sprintf("%v;%v", lat, lng), props...) +func (cb *ComponentBase) SetColor(s string, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyColor, s, props...) } -func (event *VEvent) SetURL(s string, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyUrl, s, props...) +func (cb *ComponentBase) SetClass(c Classification, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyClass, string(c), props...) } -func (event *VEvent) SetOrganizer(s string, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyOrganizer, s, props...) +func (cb *ComponentBase) setPriority(p int, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyPriority, strconv.Itoa(p), props...) } -func (event *VEvent) SetColor(s string, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyColor, s, props...) +func (cb *ComponentBase) setResources(r string, props ...PropertyParameter) { + cb.SetProperty(ComponentPropertyResources, r, props...) } -func (event *VEvent) SetClass(c Classification, props ...PropertyParameter) { - event.SetProperty(ComponentPropertyClass, string(c), props...) -} -func (event *VEvent) AddAttendee(s string, props ...PropertyParameter) { - event.AddProperty(ComponentPropertyAttendee, "mailto:"+s, props...) +func (cb *ComponentBase) AddAttendee(s string, props ...PropertyParameter) { + cb.AddProperty(ComponentPropertyAttendee, "mailto:"+s, props...) } -func (event *VEvent) AddExdate(s string, props ...PropertyParameter) { - event.AddProperty(ComponentPropertyExdate, s, props...) +func (cb *ComponentBase) AddExdate(s string, props ...PropertyParameter) { + cb.AddProperty(ComponentPropertyExdate, s, props...) } -func (event *VEvent) AddExrule(s string, props ...PropertyParameter) { - event.AddProperty(ComponentPropertyExrule, s, props...) +func (cb *ComponentBase) AddExrule(s string, props ...PropertyParameter) { + cb.AddProperty(ComponentPropertyExrule, s, props...) } -func (event *VEvent) AddRdate(s string, props ...PropertyParameter) { - event.AddProperty(ComponentPropertyRdate, s, props...) +func (cb *ComponentBase) AddRdate(s string, props ...PropertyParameter) { + cb.AddProperty(ComponentPropertyRdate, s, props...) } -func (event *VEvent) AddRrule(s string, props ...PropertyParameter) { - event.AddProperty(ComponentPropertyRrule, s, props...) +func (cb *ComponentBase) AddRrule(s string, props ...PropertyParameter) { + cb.AddProperty(ComponentPropertyRrule, s, props...) } -func (event *VEvent) AddAttachment(s string, props ...PropertyParameter) { - event.AddProperty(ComponentPropertyAttach, s, props...) +func (cb *ComponentBase) AddAttachment(s string, props ...PropertyParameter) { + cb.AddProperty(ComponentPropertyAttach, s, props...) } -func (event *VEvent) AddAttachmentURL(uri string, contentType string) { - event.AddAttachment(uri, WithFmtType(contentType)) +func (cb *ComponentBase) AddAttachmentURL(uri string, contentType string) { + cb.AddAttachment(uri, WithFmtType(contentType)) } -func (event *VEvent) AddAttachmentBinary(binary []byte, contentType string) { - event.AddAttachment(base64.StdEncoding.EncodeToString(binary), +func (cb *ComponentBase) AddAttachmentBinary(binary []byte, contentType string) { + cb.AddAttachment(base64.StdEncoding.EncodeToString(binary), WithFmtType(contentType), WithEncoding("base64"), WithValue("binary"), ) } @@ -351,13 +308,13 @@ func (attendee *Attendee) getProperty(parameter Parameter) []string { return nil } -func (event *VEvent) Attendees() (r []*Attendee) { +func (cb *ComponentBase) Attendees() (r []*Attendee) { r = []*Attendee{} - for i := range event.Properties { - switch event.Properties[i].IANAToken { + for i := range cb.Properties { + switch cb.Properties[i].IANAToken { case string(ComponentPropertyAttendee): a := &Attendee{ - event.Properties[i], + cb.Properties[i], } r = append(r, a) } @@ -365,8 +322,8 @@ func (event *VEvent) Attendees() (r []*Attendee) { return } -func (event *VEvent) Id() string { - p := event.GetProperty(ComponentPropertyUniqueId) +func (cb *ComponentBase) Id() string { + p := cb.GetProperty(ComponentPropertyUniqueId) if p != nil { return FromText(p.Value) } @@ -392,6 +349,113 @@ func (event *VEvent) Alarms() (r []*VAlarm) { return } + +type VEvent struct { + ComponentBase +} + +func (c *VEvent) serialize(w io.Writer) { + c.ComponentBase.serializeThis(w, "VEVENT") +} + +func (c *VEvent) Serialize() string { + b := &bytes.Buffer{} + c.ComponentBase.serializeThis(b, "VEVENT") + return b.String() +} + +func NewEvent(uniqueId string) *VEvent { + e := &VEvent{ + NewComponent(uniqueId), + } + return e +} + +func (calendar *Calendar) AddEvent(id string) *VEvent { + e := NewEvent(id) + calendar.Components = append(calendar.Components, e) + return e +} + +func (calendar *Calendar) AddVEvent(e *VEvent) { + calendar.Components = append(calendar.Components, e) +} + +func (calendar *Calendar) Events() (r []*VEvent) { + r = []*VEvent{} + for i := range calendar.Components { + switch event := calendar.Components[i].(type) { + case *VEvent: + r = append(r, event) + } + } + return +} + +func (event *VEvent) SetEndAt(t time.Time, props ...PropertyParameter) { + event.SetProperty(ComponentPropertyDtEnd, t.UTC().Format(icalTimestampFormatUtc), props...) +} + +func (event *VEvent) SetAllDayEndAt(t time.Time, props ...PropertyParameter) { + event.SetProperty(ComponentPropertyDtEnd, t.UTC().Format(icalDateFormatUtc), props...) +} + +func (event *VEvent) SetLocation(s string, props ...PropertyParameter) { + event.setLocation(s, props...); +} + +func (event *VEvent) SetGeo(lat interface{}, lng interface{}, props ...PropertyParameter) { + event.setGeo(lat, lng, props...); +} + +func (event *VEvent) SetPriority(p int, props ...PropertyParameter) { + event.setPriority(p, props...); +} + +func (event *VEvent) SetResources(r string, props ...PropertyParameter) { + event.setResources(r, props...) +} + +// SetDuration updates the duration of an event. +// This function will set either the end or start time of an event depending what is already given. +// The duration defines the length of a event relative to start or end time. +// +// Notice: It will not set the DURATION key of the ics - only DTSTART and DTEND will be affected. +func (event *VEvent) SetDuration(d time.Duration) error { + t, err := event.GetStartAt() + if err == nil { + event.SetEndAt(t.Add(d)) + return nil + } else { + t, err = event.GetEndAt() + if err == nil { + event.SetStartAt(t.Add(-d)) + return nil + } + } + return errors.New("start or end not yet defined") +} + +func (event *VEvent) GetEndAt() (time.Time, error) { + return event.getTimeProp(ComponentPropertyDtEnd, false) +} + +func (event *VEvent) GetAllDayEndAt() (time.Time, error) { + return event.getTimeProp(ComponentPropertyDtEnd, true) +} + +type TimeTransparency string + +const ( + TransparencyOpaque TimeTransparency = "OPAQUE" // default + TransparencyTransparent TimeTransparency = "TRANSPARENT" +) + +func (event *VEvent) SetTimeTransparency(v TimeTransparency, props ...PropertyParameter) { + event.SetProperty(ComponentPropertyTransp, string(v), props...) +} + + type VTodo struct { ComponentBase } @@ -406,6 +470,100 @@ func (c *VTodo) Serialize() string { return b.String() } +func NewTodo(uniqueId string) *VTodo { + e := &VTodo{ + NewComponent(uniqueId), + } + return e +} + +func (calendar *Calendar) AddTodo(id string) *VTodo { + e := NewTodo(id) + calendar.Components = append(calendar.Components, e) + return e +} + +func (calendar *Calendar) AddVTodo(e *VTodo) { + calendar.Components = append(calendar.Components, e) +} + +func (calendar *Calendar) Todos() (r []*VTodo) { + r = []*VTodo{} + for i := range calendar.Components { + switch todo := calendar.Components[i].(type) { + case *VTodo: + r = append(r, todo) + } + } + return +} + +func (todo *VTodo) SetCompletedAt(t time.Time, props ...PropertyParameter) { + todo.SetProperty(ComponentPropertyCompleted, t.UTC().Format(icalTimestampFormatUtc), props...) +} + +func (todo *VTodo) SetAllDayCompletedAt(t time.Time, props ...PropertyParameter) { + todo.SetProperty(ComponentPropertyCompleted, t.UTC().Format(icalDateFormatUtc), props...) +} + +func (todo *VTodo) SetDueAt(t time.Time, props ...PropertyParameter) { + todo.SetProperty(ComponentPropertyDue, t.UTC().Format(icalTimestampFormatUtc), props...) +} + +func (todo *VTodo) SetAllDayDueAt(t time.Time, props ...PropertyParameter) { + todo.SetProperty(ComponentPropertyDue, t.UTC().Format(icalDateFormatUtc), props...) +} + +func (todo *VTodo) SetPercentComplete(p int, props ...PropertyParameter) { + todo.SetProperty(ComponentPropertyPercentComplete, strconv.Itoa(p), props...) +} + + +func (todo *VTodo) SetLocation(s string, props ...PropertyParameter) { + todo.setLocation(s, props...); +} + +func (todo *VTodo) setGeo(lat interface{}, lng interface{}, props ...PropertyParameter) { + todo.setGeo(lat, lng, props...); +} + +func (todo *VTodo) SetPriority(p int, props ...PropertyParameter) { + todo.setPriority(p, props...); +} + +func (todo *VTodo) SetResources(r string, props ...PropertyParameter) { + todo.setResources(r, props...) +} + +// SetDuration updates the duration of an event. +// This function will set either the end or start time of an event depending what is already given. +// The duration defines the length of a event relative to start or end time. +// +// Notice: It will not set the DURATION key of the ics - only DTSTART and DTEND will be affected. +func (todo *VTodo) SetDuration(d time.Duration) error { + t, err := todo.GetStartAt() + if err == nil { + todo.SetDueAt(t.Add(d)) + return nil + } else { + t, err = todo.GetDueAt() + if err == nil { + todo.SetStartAt(t.Add(-d)) + return nil + } + } + return errors.New("start or end not yet defined") +} + +func (todo *VTodo) GetDueAt() (time.Time, error) { + return todo.getTimeProp(ComponentPropertyDue, false) +} + +func (todo *VTodo) GetAllDayDueAt() (time.Time, error) { + return todo.getTimeProp(ComponentPropertyDue, true) +} + + type VJournal struct { ComponentBase } @@ -420,6 +578,34 @@ func (c *VJournal) Serialize() string { return b.String() } +func NewJournal(uniqueId string) *VJournal { + e := &VJournal{ + NewComponent(uniqueId), + } + return e +} + +func (calendar *Calendar) AddJournal(id string) *VJournal { + e := NewJournal(id) + calendar.Components = append(calendar.Components, e) + return e +} + +func (calendar *Calendar) AddVJournal(e *VJournal) { + calendar.Components = append(calendar.Components, e) +} + +func (calendar *Calendar) Journals() (r []*VJournal) { + r = []*VJournal{} + for i := range calendar.Components { + switch journal := calendar.Components[i].(type) { + case *VJournal: + r = append(r, journal) + } + } + return +} + type VBusy struct { ComponentBase } From 14095cc944ebfb3c7fbb883cf7490d38c8c47610 Mon Sep 17 00:00:00 2001 From: Sean Slater Date: Sun, 2 Apr 2023 16:26:42 -0400 Subject: [PATCH 02/10] Add Alarm, Timezone, and FreeBusy Property Methods - Add methods for Alarm, Timezone, and FreeBusy - Only methods for creating and adding right now, will add methods for the properties unique to them later --- components.go | 118 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 5 deletions(-) diff --git a/components.go b/components.go index f356071..ec5637d 100644 --- a/components.go +++ b/components.go @@ -330,18 +330,22 @@ func (cb *ComponentBase) Id() string { return "" } -func (event *VEvent) AddAlarm() *VAlarm { +func (cb *ComponentBase) addAlarm() *VAlarm { a := &VAlarm{ ComponentBase: ComponentBase{}, } - event.Components = append(event.Components, a) + cb.Components = append(cb.Components, a) return a } -func (event *VEvent) Alarms() (r []*VAlarm) { +func (cb *ComponentBase) addVAlarm(a *VAlarm) { + event.Components = append(cb.Components, a) +} + +func (cb *ComponentBase) alarms() (r []*VAlarm) { r = []*VAlarm{} - for i := range event.Components { - switch alarm := event.Components[i].(type) { + for i := range cb.Components { + switch alarm := cb.Components[i].(type) { case *VAlarm: r = append(r, alarm) } @@ -436,6 +440,18 @@ func (event *VEvent) SetDuration(d time.Duration) error { return errors.New("start or end not yet defined") } +func (event *VEvent) AddAlarm() *VAlarm { + return event.addAlarm() +} + +func (event *VEvent) AddVAlarm(a *VAlarm) { + event.addVAlarm(a) +} + +func (event *VEvent) Alarms() (r []*VAlarm) { + return event.Alarms() +} + func (event *VEvent) GetEndAt() (time.Time, error) { return event.getTimeProp(ComponentPropertyDtEnd, false) } @@ -555,6 +571,18 @@ func (todo *VTodo) SetDuration(d time.Duration) error { return errors.New("start or end not yet defined") } +func (todo *VTodo) AddAlarm() *VAlarm { + return todo.addAlarm() +} + +func (todo *VTodo) AddVAlarm(a *VAlarm) { + todo.addVAlarm(a) +} + +func (todo *VTodo) Alarms() (r []*VAlarm) { + return todo.Alarms() +} + func (todo *VTodo) GetDueAt() (time.Time, error) { return todo.getTimeProp(ComponentPropertyDue, false) } @@ -620,6 +648,34 @@ func (c *VBusy) serialize(w io.Writer) { c.ComponentBase.serializeThis(w, "VBUSY") } +func NewBusy(uniqueId string) *VBusy { + e := &VBusy{ + NewComponent(uniqueId), + } + return e +} + +func (calendar *Calendar) AddBusy(id string) *VBusy { + e := NewBusy(id) + calendar.Components = append(calendar.Components, e) + return e +} + +func (calendar *Calendar) AddVBusy(e *VBusy) { + calendar.Components = append(calendar.Components, e) +} + +func (calendar *Calendar) Busys() (r []*VBusy) { + r = []*VBusy{} + for i := range calendar.Components { + switch busy := calendar.Components[i].(type) { + case *VBusy: + r = append(r, busy) + } + } + return +} + type VTimezone struct { ComponentBase } @@ -634,6 +690,38 @@ func (c *VTimezone) serialize(w io.Writer) { c.ComponentBase.serializeThis(w, "VTIMEZONE") } +func NewTimezone(tzId string) *VTimezone { + e := &VTimezone{ + ComponentBase{ + Properties: []IANAProperty{ + {BaseProperty{IANAToken: ToText(string(ComponentPropertyTzid)), Value: tzId}}, + }, + } + } + return e +} + +func (calendar *Calendar) AddTimezone(id string) *VTimezone { + e := NewTimezone(id) + calendar.Components = append(calendar.Components, e) + return e +} + +func (calendar *Calendar) AddVTimezone(e *VTimezone) { + calendar.Components = append(calendar.Components, e) +} + +func (calendar *Calendar) Timezones() (r []*VTimezone) { + r = []*VTimezone{} + for i := range calendar.Components { + switch timezone := calendar.Components[i].(type) { + case *VTimezone: + r = append(r, timezone) + } + } + return +} + type VAlarm struct { ComponentBase } @@ -648,6 +736,26 @@ func (c *VAlarm) serialize(w io.Writer) { c.ComponentBase.serializeThis(w, "VALARM") } +func NewAlarm(tzId string) *VAlarm { + e := &VAlarm{} + return e +} + +func (calendar *Calendar) AddVAlarm(e *VAlarm) { + calendar.Components = append(calendar.Components, e) +} + +func (calendar *Calendar) Alarms() (r []*VAlarm) { + r = []*VAlarm{} + for i := range calendar.Components { + switch alarm := calendar.Components[i].(type) { + case *VAlarm: + r = append(r, alarm) + } + } + return +} + func (alarm *VAlarm) SetAction(a Action, props ...PropertyParameter) { alarm.SetProperty(ComponentPropertyAction, string(a), props...) } From 67727dda1ccad4d94e050383e9a4b8c9ceff15c5 Mon Sep 17 00:00:00 2001 From: Sean Slater Date: Thu, 6 Apr 2023 12:29:40 -0400 Subject: [PATCH 03/10] Fix Typos in a couple places - Recursive call for Alarms - SetGeo was not a public method - Missing comma in struct creation --- calendar.go | 1 + components.go | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/calendar.go b/calendar.go index a68bee8..3ee16a8 100644 --- a/calendar.go +++ b/calendar.go @@ -58,6 +58,7 @@ const ( ComponentPropertyCompleted = ComponentProperty(PropertyCompleted) ComponentPropertyDue = ComponentProperty(PropertyDue) ComponentPropertyPercentComplete = ComponentProperty(PropertyPercentComplete) + ComponentPropertyTzid = ComponentProperty(PropertyTzid) ) type Property string diff --git a/components.go b/components.go index ec5637d..234356a 100644 --- a/components.go +++ b/components.go @@ -339,7 +339,7 @@ func (cb *ComponentBase) addAlarm() *VAlarm { } func (cb *ComponentBase) addVAlarm(a *VAlarm) { - event.Components = append(cb.Components, a) + cb.Components = append(cb.Components, a) } func (cb *ComponentBase) alarms() (r []*VAlarm) { @@ -449,7 +449,7 @@ func (event *VEvent) AddVAlarm(a *VAlarm) { } func (event *VEvent) Alarms() (r []*VAlarm) { - return event.Alarms() + return event.alarms() } func (event *VEvent) GetEndAt() (time.Time, error) { @@ -539,7 +539,7 @@ func (todo *VTodo) SetLocation(s string, props ...PropertyParameter) { todo.setLocation(s, props...); } -func (todo *VTodo) setGeo(lat interface{}, lng interface{}, props ...PropertyParameter) { +func (todo *VTodo) SetGeo(lat interface{}, lng interface{}, props ...PropertyParameter) { todo.setGeo(lat, lng, props...); } @@ -580,7 +580,7 @@ func (todo *VTodo) AddVAlarm(a *VAlarm) { } func (todo *VTodo) Alarms() (r []*VAlarm) { - return todo.Alarms() + return todo.alarms() } func (todo *VTodo) GetDueAt() (time.Time, error) { @@ -696,7 +696,7 @@ func NewTimezone(tzId string) *VTimezone { Properties: []IANAProperty{ {BaseProperty{IANAToken: ToText(string(ComponentPropertyTzid)), Value: tzId}}, }, - } + }, } return e } From 6d4f14f630b870fe44df70148192d6d0f6d945b7 Mon Sep 17 00:00:00 2001 From: Sean Slater Date: Thu, 27 Apr 2023 19:29:33 -0400 Subject: [PATCH 04/10] Add Comment and Category Properties to Components --- calendar.go | 1 + components.go | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/calendar.go b/calendar.go index 3ee16a8..7f21a86 100644 --- a/calendar.go +++ b/calendar.go @@ -59,6 +59,7 @@ const ( ComponentPropertyDue = ComponentProperty(PropertyDue) ComponentPropertyPercentComplete = ComponentProperty(PropertyPercentComplete) ComponentPropertyTzid = ComponentProperty(PropertyTzid) + ComponentPropertyComment = ComponentProperty(PropertyComment) ) type Property string diff --git a/components.go b/components.go index 234356a..02c8eeb 100644 --- a/components.go +++ b/components.go @@ -278,6 +278,14 @@ func (cb *ComponentBase) AddAttachmentBinary(binary []byte, contentType string) ) } +func (cb *ComponentBase) AddComment(s string, props ...PropertyParameter) { + cb.AddProperty(ComponentPropertyComment, s, props...) +} + +func (cb *ComponentBase) AddCategory(s string, props ...PropertyParameter) { + cb.AddProperty(ComponentPropertyCategories, s, props...) +} + type Attendee struct { IANAProperty } From 4b2d6a18937033ca1da5af964b71ce498271d351 Mon Sep 17 00:00:00 2001 From: Sean Slater Date: Thu, 27 Apr 2023 22:09:44 -0400 Subject: [PATCH 05/10] Add changes for All Day Events to All Day Todos --- components.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components.go b/components.go index 87f093e..73a9f84 100644 --- a/components.go +++ b/components.go @@ -529,7 +529,8 @@ func (todo *VTodo) SetCompletedAt(t time.Time, props ...PropertyParameter) { } func (todo *VTodo) SetAllDayCompletedAt(t time.Time, props ...PropertyParameter) { - todo.SetProperty(ComponentPropertyCompleted, t.UTC().Format(icalDateFormatUtc), props...) + props = append(props, WithValue(string(ValueDataTypeDate))) + todo.SetProperty(ComponentPropertyCompleted, t.Format(icalDateFormatLocal), props...) } func (todo *VTodo) SetDueAt(t time.Time, props ...PropertyParameter) { @@ -537,7 +538,8 @@ func (todo *VTodo) SetDueAt(t time.Time, props ...PropertyParameter) { } func (todo *VTodo) SetAllDayDueAt(t time.Time, props ...PropertyParameter) { - todo.SetProperty(ComponentPropertyDue, t.UTC().Format(icalDateFormatUtc), props...) + props = append(props, WithValue(string(ValueDataTypeDate))) + todo.SetProperty(ComponentPropertyDue, t.Format(icalDateFormatLocal), props...) } func (todo *VTodo) SetPercentComplete(p int, props ...PropertyParameter) { From 5f3bef9829df7253ceef3087dc29e3d9bd073dab Mon Sep 17 00:00:00 2001 From: Arran Ubels Date: Mon, 8 Jan 2024 15:56:48 +1100 Subject: [PATCH 06/10] Unnecessary functions causing lint recursion errors. --- components.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/components.go b/components.go index 488c6d8..74d6864 100644 --- a/components.go +++ b/components.go @@ -424,10 +424,6 @@ func (event *VEvent) SetLastModifiedAt(t time.Time, props ...PropertyParameter) event.SetProperty(ComponentPropertyLastModified, t.UTC().Format(icalTimestampFormatUtc), props...) } -func (event *VEvent) SetLocation(s string, props ...PropertyParameter) { - event.SetLocation(s, props...) -} - func (event *VEvent) SetGeo(lat interface{}, lng interface{}, props ...PropertyParameter) { event.setGeo(lat, lng, props...) } @@ -555,10 +551,6 @@ func (todo *VTodo) SetPercentComplete(p int, props ...PropertyParameter) { todo.SetProperty(ComponentPropertyPercentComplete, strconv.Itoa(p), props...) } -func (todo *VTodo) SetLocation(s string, props ...PropertyParameter) { - todo.SetLocation(s, props...) -} - func (todo *VTodo) SetGeo(lat interface{}, lng interface{}, props ...PropertyParameter) { todo.setGeo(lat, lng, props...) } From 55df13ec27738206ce0973a2046bb625c40ada72 Mon Sep 17 00:00:00 2001 From: Arran Ubels Date: Mon, 8 Jan 2024 15:57:44 +1100 Subject: [PATCH 07/10] Consistent todo receiver --- components.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components.go b/components.go index 74d6864..a367811 100644 --- a/components.go +++ b/components.go @@ -491,13 +491,13 @@ type VTodo struct { ComponentBase } -func (c *VTodo) serialize(w io.Writer) { - c.ComponentBase.serializeThis(w, "VTODO") +func (todo *VTodo) serialize(w io.Writer) { + todo.ComponentBase.serializeThis(w, "VTODO") } -func (c *VTodo) Serialize() string { +func (todo *VTodo) Serialize() string { b := &bytes.Buffer{} - c.ComponentBase.serializeThis(b, "VTODO") + todo.ComponentBase.serializeThis(b, "VTODO") return b.String() } From 9e30bdf5f06c08661809d4195ec05e138458b6e7 Mon Sep 17 00:00:00 2001 From: Arran Ubels Date: Mon, 8 Jan 2024 15:58:10 +1100 Subject: [PATCH 08/10] Ignore explicitly --- components.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components.go b/components.go index a367811..f2557ab 100644 --- a/components.go +++ b/components.go @@ -32,14 +32,14 @@ func (cb *ComponentBase) SubComponents() []Component { } func (base ComponentBase) serializeThis(writer io.Writer, componentType string) { - fmt.Fprint(writer, "BEGIN:"+componentType, "\r\n") + _, _ = fmt.Fprint(writer, "BEGIN:"+componentType, "\r\n") for _, p := range base.Properties { p.serialize(writer) } for _, c := range base.Components { c.serialize(writer) } - fmt.Fprint(writer, "END:"+componentType, "\r\n") + _, _ = fmt.Fprint(writer, "END:"+componentType, "\r\n") } func NewComponent(uniqueId string) ComponentBase { From 1e96c159570cc454f2c4a5318b7ac3c091014f23 Mon Sep 17 00:00:00 2001 From: Arran Ubels Date: Mon, 8 Jan 2024 15:58:23 +1100 Subject: [PATCH 09/10] Consistent receivers. --- components.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components.go b/components.go index f2557ab..7713239 100644 --- a/components.go +++ b/components.go @@ -31,12 +31,12 @@ func (cb *ComponentBase) SubComponents() []Component { return cb.Components } -func (base ComponentBase) serializeThis(writer io.Writer, componentType string) { +func (cb ComponentBase) serializeThis(writer io.Writer, componentType string) { _, _ = fmt.Fprint(writer, "BEGIN:"+componentType, "\r\n") - for _, p := range base.Properties { + for _, p := range cb.Properties { p.serialize(writer) } - for _, c := range base.Components { + for _, c := range cb.Components { c.serialize(writer) } _, _ = fmt.Fprint(writer, "END:"+componentType, "\r\n") From cf8d1b371d4511c6842a8182ad17ba90e9396186 Mon Sep 17 00:00:00 2001 From: Arran Ubels Date: Mon, 8 Jan 2024 15:59:24 +1100 Subject: [PATCH 10/10] Ignore explicitly --- calendar.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/calendar.go b/calendar.go index 7f21a86..e7f6ca0 100644 --- a/calendar.go +++ b/calendar.go @@ -305,14 +305,14 @@ func (calendar *Calendar) Serialize() string { } func (calendar *Calendar) SerializeTo(w io.Writer) error { - fmt.Fprint(w, "BEGIN:VCALENDAR", "\r\n") + _, _ = fmt.Fprint(w, "BEGIN:VCALENDAR", "\r\n") for _, p := range calendar.CalendarProperties { p.serialize(w) } for _, c := range calendar.Components { c.serialize(w) } - fmt.Fprint(w, "END:VCALENDAR", "\r\n") + _, _ = fmt.Fprint(w, "END:VCALENDAR", "\r\n") return nil } @@ -534,7 +534,7 @@ func (cs *CalendarStream) ReadLine() (*ContentLine, error) { if len(p) == 0 { c = false } else if p[0] == ' ' || p[0] == '\t' { - cs.b.Discard(1) // nolint:errcheck + _, _ = cs.b.Discard(1) // nolint:errcheck } else { c = false }