api/pkg/caldav/parsing.go

198 lines
5.3 KiB
Go
Raw Normal View History

2020-02-07 16:27:45 +00:00
// Vikunja is a to-do list application to facilitate your life.
2021-02-02 19:19:13 +00:00
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
2019-05-22 17:48:48 +00:00
//
// This program is free software: you can redistribute it and/or modify
2020-12-23 15:41:52 +00:00
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
2019-05-22 17:48:48 +00:00
//
// 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
2020-12-23 15:41:52 +00:00
// GNU Affero General Public Licensee for more details.
2019-05-22 17:48:48 +00:00
//
2020-12-23 15:41:52 +00:00
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
2019-05-22 17:48:48 +00:00
package caldav
import (
"strconv"
"strings"
"time"
2019-05-22 17:48:48 +00:00
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
ics "github.com/arran4/golang-ical"
2019-05-22 17:48:48 +00:00
)
2022-11-13 16:07:01 +00:00
func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectTasks []*models.TaskWithComments) string {
2019-05-22 17:48:48 +00:00
// Make caldav todos from Vikunja todos
var caldavtodos []*Todo
2022-11-13 16:07:01 +00:00
for _, t := range projectTasks {
2019-05-22 17:48:48 +00:00
duration := t.EndDate.Sub(t.StartDate)
var categories []string
for _, label := range t.Labels {
categories = append(categories, label.Title)
}
2019-05-22 17:48:48 +00:00
// Find the UID of the parent task, if it exists:
var parentTaskUID string
if parentTasks, ok := t.RelatedTasks[models.RelationKindParenttask]; ok {
parentTaskUID = parentTasks[0].UID
}
caldavtodos = append(caldavtodos, &Todo{
Timestamp: t.Updated,
UID: t.UID,
Summary: t.Title,
Description: t.Description,
Completed: t.DoneAt,
2019-05-22 17:48:48 +00:00
// Organizer: &t.CreatedBy, // Disabled until we figure out how this works
Categories: categories,
Priority: t.Priority,
RelatedToParentUID: parentTaskUID,
Start: t.StartDate,
End: t.EndDate,
Created: t.Created,
Updated: t.Updated,
DueDate: t.DueDate,
Duration: duration,
RepeatAfter: t.RepeatAfter,
RepeatMode: t.RepeatMode,
2019-05-22 17:48:48 +00:00
})
}
caldavConfig := &Config{
2022-11-13 16:07:01 +00:00
Name: project.Title,
2019-05-22 17:48:48 +00:00
ProdID: "Vikunja Todo App",
}
return ParseTodos(caldavConfig, caldavtodos)
2019-05-22 17:48:48 +00:00
}
func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
parsed, err := ics.ParseCalendar(strings.NewReader(content))
2019-05-22 17:48:48 +00:00
if err != nil {
return nil, err
}
var parsedProperties = parsed.Components[0].UnknownPropertiesIANAProperties()
2019-05-22 17:48:48 +00:00
// We put the task details in a map to be able to handle them more easily
task := make(map[string]string)
for _, c := range parsedProperties {
task[c.IANAToken] = c.Value
2019-05-22 17:48:48 +00:00
}
// Parse the priority
2019-05-22 17:48:48 +00:00
var priority int64
if _, ok := task["PRIORITY"]; ok {
priorityParsed, err := strconv.ParseInt(task["PRIORITY"], 10, 64)
2019-05-22 17:48:48 +00:00
if err != nil {
return nil, err
}
priority = parseVTODOPriority(priorityParsed)
2019-05-22 17:48:48 +00:00
}
// Check if the task has a parent:
var parentTaskUID string
for _, c := range parsedProperties {
// Check if the entry is a relation:
if c.IANAToken != "RELATED-TO" {
continue
}
// Check if the relation has a type:
if _, ok := c.ICalParameters["RELTYPE"]; !ok {
continue
}
// Check that the type is "PARENT":
if len(c.ICalParameters["RELTYPE"]) != 1 || c.ICalParameters["RELTYPE"][0] != "PARENT" {
continue
}
// We have the id of the parent task:
parentTaskUID = c.Value
}
2019-05-22 17:48:48 +00:00
// Parse the enddate
duration, _ := time.ParseDuration(task["DURATION"])
description := strings.ReplaceAll(task["DESCRIPTION"], "\\,", ",")
description = strings.ReplaceAll(description, "\\n", "\n")
var labels []*models.Label
if val, ok := task["CATEGORIES"]; ok {
categories := strings.Split(val, ",")
labels = make([]*models.Label, 0, len(categories))
for _, category := range categories {
labels = append(labels, &models.Label{
Title: category,
})
}
}
2019-08-14 20:19:04 +00:00
vTask = &models.Task{
UID: task["UID"],
Title: task["SUMMARY"],
Description: description,
Priority: priority,
Labels: labels,
DueDate: caldavTimeToTimestamp(task["DUE"]),
Updated: caldavTimeToTimestamp(task["DTSTAMP"]),
StartDate: caldavTimeToTimestamp(task["DTSTART"]),
DoneAt: caldavTimeToTimestamp(task["COMPLETED"]),
2019-05-22 17:48:48 +00:00
}
if task["STATUS"] == "COMPLETED" {
vTask.Done = true
}
// Check if the task has a parent and create a dummy relation if yes:
if parentTaskUID != "" {
var parentTaskUID = parentTaskUID
if vTask.RelatedTasks == nil {
vTask.RelatedTasks = make(models.RelatedTaskMap)
}
vTask.RelatedTasks[models.RelationKindParenttask] = append(vTask.RelatedTasks[models.RelationKindParenttask], &models.Task{
UID: parentTaskUID,
})
}
if duration > 0 && !vTask.StartDate.IsZero() {
vTask.EndDate = vTask.StartDate.Add(duration)
2019-05-22 17:48:48 +00:00
}
return
}
// https://tools.ietf.org/html/rfc5545#section-3.3.5
func caldavTimeToTimestamp(tstring string) time.Time {
2019-05-22 17:48:48 +00:00
if tstring == "" {
return time.Time{}
2019-05-22 17:48:48 +00:00
}
format := DateFormat
if strings.HasSuffix(tstring, "Z") {
format = `20060102T150405Z`
}
if len(tstring) == 8 {
format = `20060102`
}
t, err := time.Parse(format, tstring)
2019-05-22 17:48:48 +00:00
if err != nil {
log.Warningf("Error while parsing caldav time %s to TimeStamp: %s", tstring, err)
return time.Time{}
2019-05-22 17:48:48 +00:00
}
return t
2019-05-22 17:48:48 +00:00
}