mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender.git
synced 2025-07-16 09:38:49 +02:00
174 lines
5.8 KiB
Go
174 lines
5.8 KiB
Go
//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
|
|
//Copyright (C) 2024 HTWKalender support@htwkalender.de
|
|
|
|
//This program is free software: you can redistribute it and/or modify
|
|
//it under the terms of the GNU Affero General Public License as published by
|
|
//the Free Software Foundation, either version 3 of the License, or
|
|
//(at your option) any later version.
|
|
|
|
//This program is distributed in the hope that it will be useful,
|
|
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
//GNU Affero General Public License for more details.
|
|
|
|
//You should have received a copy of the GNU Affero General Public License
|
|
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package ical
|
|
|
|
import (
|
|
"htwkalender/model"
|
|
"htwkalender/service/functions"
|
|
clock "htwkalender/service/functions/time"
|
|
"htwkalender/service/names"
|
|
"time"
|
|
|
|
"github.com/jordic/goics"
|
|
_ "time/tzdata"
|
|
)
|
|
|
|
// IcalModel local type for EmitICal function
|
|
type IcalModel struct {
|
|
Events model.Events
|
|
Mapping map[string]model.FeedCollection
|
|
}
|
|
|
|
// EmitICal implements the interface for goics
|
|
func (icalModel IcalModel) EmitICal() goics.Componenter {
|
|
internalClock := clock.RealClock{}
|
|
c := generateIcalEmit(icalModel, internalClock)
|
|
return c
|
|
}
|
|
|
|
func generateIcalEmit(icalModel IcalModel, internalClock clock.Clock) *goics.Component {
|
|
europeTime, _ := time.LoadLocation("Europe/Berlin")
|
|
c := goics.NewComponent()
|
|
c.SetType("VCALENDAR")
|
|
// PRODID is required by the standard
|
|
c.AddProperty("PRODID", "-//HTWK Kalender//htwkalender.de//DE")
|
|
|
|
c.AddProperty("VERSION", "2.0")
|
|
c.AddProperty("CALSCALE", "GREGORIAN")
|
|
c.AddProperty("TZID", "EUROPE/BERLIN")
|
|
c.AddProperty("X-WR-CALNAME", "HTWK Kalender")
|
|
c.AddProperty("X-WR-TIMEZONE", "EUROPE/BERLIN")
|
|
//add v time zone
|
|
icalModel.vtimezone(c)
|
|
|
|
for _, event := range icalModel.Events {
|
|
mapEntry, mappingFound := icalModel.Mapping[event.UUID]
|
|
|
|
s := goics.NewComponent()
|
|
s.SetType("VEVENT")
|
|
|
|
s.AddProperty(goics.FormatDateTime("DTSTAMP", internalClock.Now().Local().In(europeTime)))
|
|
|
|
// create a unique id for the event by hashing the event start, end, course and name
|
|
var eventHash = functions.HashString(event.Start.String() + event.End.String() + event.Course + event.Name + event.Rooms)
|
|
|
|
s.AddProperty("UID", eventHash+"@htwkalender.de")
|
|
s.AddProperty(goics.FormatDateTime("DTEND", event.End.Time().Local().In(europeTime)))
|
|
s.AddProperty(goics.FormatDateTime("DTSTART", event.Start.Time().Local().In(europeTime)))
|
|
|
|
if mappingFound {
|
|
addPropertyIfNotEmpty(s, "SUMMARY", replaceNameIfUserDefined(&event, mapEntry))
|
|
addAlarmIfSpecified(s, event, mapEntry, internalClock)
|
|
} else {
|
|
addPropertyIfNotEmpty(s, "SUMMARY", event.Name)
|
|
}
|
|
|
|
addPropertyIfNotEmpty(s, "DESCRIPTION", generateDescription(event))
|
|
addPropertyIfNotEmpty(s, "LOCATION", event.Rooms)
|
|
c.AddComponent(s)
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (icalModel IcalModel) vtimezone(c *goics.Component) {
|
|
tz := goics.NewComponent()
|
|
tz.SetType("VTIMEZONE")
|
|
tz.AddProperty("TZID", "EUROPE/BERLIN")
|
|
//add standard time
|
|
icalModel.standard(tz)
|
|
//add daylight time
|
|
icalModel.daylight(tz)
|
|
|
|
c.AddComponent(tz)
|
|
}
|
|
|
|
func (icalModel IcalModel) standard(tz *goics.Component) {
|
|
st := NewHtwkalenderComponent()
|
|
st.SetType("STANDARD")
|
|
st.AddProperty("DTSTART", "19701025T030000")
|
|
st.AddProperty("RRULE", "FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU")
|
|
st.AddProperty("TZOFFSETFROM", "+0200")
|
|
st.AddProperty("TZOFFSETTO", "+0100")
|
|
st.AddProperty("TZNAME", "CET")
|
|
tz.AddComponent(st)
|
|
}
|
|
|
|
// create an override for goics component function Write
|
|
// to add the RRULE property
|
|
|
|
func (icalModel IcalModel) daylight(tz *goics.Component) {
|
|
dt := NewHtwkalenderComponent()
|
|
dt.SetType("DAYLIGHT")
|
|
dt.AddProperty("DTSTART", "19700329T020000")
|
|
dt.AddProperty("TZOFFSETFROM", "+0100")
|
|
dt.AddProperty("TZOFFSETTO", "+0200")
|
|
dt.AddProperty("TZNAME", "CEST")
|
|
dt.AddProperty("RRULE", "FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU")
|
|
tz.AddComponent(dt)
|
|
}
|
|
|
|
// if reminder is specified in the configuration for this event, an alarm will be added to the event
|
|
func addAlarmIfSpecified(s *goics.Component, event model.Event, mapping model.FeedCollection, clock clock.Clock) {
|
|
// if event.Start > now
|
|
// then add alarm
|
|
if event.Start.Time().Local().After(clock.Now().Local()) && mapping.Reminder {
|
|
a := goics.NewComponent()
|
|
a.SetType("VALARM")
|
|
a.AddProperty("TRIGGER", "-P0DT0H15M0S")
|
|
a.AddProperty("ACTION", "DISPLAY")
|
|
a.AddProperty("DESCRIPTION", "Next course: "+replaceNameIfUserDefined(&event, mapping)+" in "+event.Rooms)
|
|
s.AddComponent(a)
|
|
}
|
|
}
|
|
|
|
// replaceNameIfUserDefined replaces the name of the event with the user defined name if it is not empty
|
|
// all contained template strings will be replaced with the corresponding values from the event
|
|
func replaceNameIfUserDefined(event *model.Event, mapping model.FeedCollection) string {
|
|
if !functions.OnlyWhitespace(mapping.UserDefinedName) {
|
|
return names.ReplaceTemplateSubStrings(mapping.UserDefinedName, *event)
|
|
}
|
|
|
|
return event.Name
|
|
}
|
|
|
|
// AddPropertyIfNotEmpty adds a property to the component if the value is not empty
|
|
// or contains only whitespaces
|
|
func addPropertyIfNotEmpty(component *goics.Component, key string, value string) {
|
|
if !functions.OnlyWhitespace(value) {
|
|
component.AddProperty(key, value)
|
|
}
|
|
}
|
|
|
|
func generateDescription(event model.Event) string {
|
|
var description string
|
|
|
|
if !functions.OnlyWhitespace(event.Notes) {
|
|
description += "Notizen: " + event.Notes + "\n"
|
|
}
|
|
if !functions.OnlyWhitespace(event.Prof) {
|
|
description += "Prof: " + event.Prof + "\n"
|
|
}
|
|
if !functions.OnlyWhitespace(event.Course) {
|
|
description += "Gruppe: " + event.Course + "\n"
|
|
}
|
|
if !functions.OnlyWhitespace(event.EventType) {
|
|
description += "Typ: " + event.EventType + event.Compulsory + "\n"
|
|
}
|
|
|
|
return description
|
|
}
|