Better caldav support #73

Merged
konrad merged 82 commits from feature/better-caldav-support into master 2019-05-22 17:48:49 +00:00
4 changed files with 65 additions and 11 deletions
Showing only changes of commit 0dac93ce91 - Show all commits

View File

@ -194,7 +194,7 @@ Sorry for some of them being in German, I'll tranlate them at some point.
* [ ] Cleanup the whole mess I made with the handlers and storage providers etc -> Probably a good idea to create a seperate storage provider etc for lists and tasks * [ ] Cleanup the whole mess I made with the handlers and storage providers etc -> Probably a good idea to create a seperate storage provider etc for lists and tasks
* [ ] Check if only needed things are queried from the db when accessing dav (for ex. no need to get all tasks when we actually only need the title) * [ ] Check if only needed things are queried from the db when accessing dav (for ex. no need to get all tasks when we actually only need the title)
* [x] Fix OPTIONS Requests to the rest of the api being broken * [x] Fix OPTIONS Requests to the rest of the api being broken
* [ ] Parse all props defined in rfc5545 * [x] Parse all props defined in rfc5545
* [ ] COMPLETED -> Need to actually save the time the task was completed * [ ] COMPLETED -> Need to actually save the time the task was completed
* [ ] Tests * [ ] Tests

View File

@ -17,11 +17,13 @@
package caldav package caldav
import ( import (
"bytes"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/web/handler" "code.vikunja.io/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/samedi/caldav-go" "github.com/samedi/caldav-go"
"io/ioutil"
"strconv" "strconv"
"strings" "strings"
) )
@ -53,6 +55,22 @@ func ListHandler(c echo.Context) error {
user: &u, user: &u,
} }
// Try to parse a task from the request payload
body, _ := ioutil.ReadAll(c.Request().Body)
// Restore the io.ReadCloser to its original state
c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(body))
// Parse it
vtodo := string(body)
if vtodo != "" && strings.HasPrefix(vtodo, `BEGIN:VCALENDAR`) {
storage.task, err = parseTaskFromVTODO(vtodo)
if err != nil {
log.Log.Error(err)
return echo.ErrInternalServerError
}
}
//fmt.Printf("[CALDAV] Request Body: %v", string(body))
/* /*
This is the request body evolution sends to get a list with all calendars (=lists with tasks in our case): This is the request body evolution sends to get a list with all calendars (=lists with tasks in our case):
@ -101,10 +119,6 @@ func ListHandler(c echo.Context) error {
// <D:status>HTTP/1.1 404 Not Found</D:status> // <D:status>HTTP/1.1 404 Not Found</D:status>
// </D:propstat> // </D:propstat>
//buf := new(bytes.Buffer)
//buf.ReadFrom(c.Request().Body)
//fmt.Printf("[CALDAV] Request Body: %v", buf.String())
/* /*
More debugging resulted in this: More debugging resulted in this:
@ -225,6 +239,36 @@ func ListHandler(c echo.Context) error {
*/ */
/*
Marking a task as done:
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
BEGIN:VTODO
UID:3ada92f28b4ceda38562ebf047c6ff05400d4c572352a
DTSTAMP:20190511T183631
DTSTART:19700101T000000
DTEND:19700101T000000
SUMMARY:sdgs
ORGANIZER;CN=:user
CREATED:20190511T183631
PRIORITY:0
LAST-MODIFIED:20190512T193428Z
COMPLETED:20190512T193428Z
PERCENT-COMPLETE:100
STATUS:COMPLETED
END:VTODO
END:VCALENDAR
*/
// Looks like when trying to update a task, the client makes a request with a different uuid.
// This would then result in Vikunja not finding the task the client wants to update (since that uid does not exist).
// I've yet to find out why.
caldav.SetupStorage(storage) caldav.SetupStorage(storage)
caldav.SetupUser("dav/principals/" + u.Username) caldav.SetupUser("dav/principals/" + u.Username)
response := caldav.HandleRequest(c.Request()) response := caldav.HandleRequest(c.Request())

View File

@ -116,7 +116,9 @@ func (vcls *VikunjaCaldavListStorage) GetResource(rpath string) (*data.Resource,
// If the task is not nil, we need to get the task and not the list // If the task is not nil, we need to get the task and not the list
if vcls.task != nil { if vcls.task != nil {
task, err := models.GetTaskSimple(vcls.task) // save and override the updated unix date to not break any later etag checks
updated := vcls.task.Updated
task, err := models.GetTaskSimple(&models.ListTask{ID: vcls.task.ID, UID: vcls.task.UID})
if err != nil { if err != nil {
if models.IsErrListTaskDoesNotExist(err) { if models.IsErrListTaskDoesNotExist(err) {
return nil, false, errs.ResourceNotFoundError return nil, false, errs.ResourceNotFoundError
@ -125,6 +127,9 @@ func (vcls *VikunjaCaldavListStorage) GetResource(rpath string) (*data.Resource,
} }
vcls.task = &task vcls.task = &task
if updated > 0 {
vcls.task.Updated = updated
}
rr := VikunjaListResourceAdapter{ rr := VikunjaListResourceAdapter{
list: vcls.list, list: vcls.list,
task: &task, task: &task,
@ -247,12 +252,14 @@ func (vlra *VikunjaListResourceAdapter) CalculateEtag() string {
fmt.Printf("[CALDAV] CalculateEtag\n") fmt.Printf("[CALDAV] CalculateEtag\n")
// Return the etag of a task if we have one // Return the etag of a task if we have one
if vlra.task != nil { if vlra.task != nil {
// Extra prefixes... ough... return `"` + strconv.FormatInt(vlra.task.ID, 10) + `-` + strconv.FormatInt(vlra.task.Updated, 10) + `"`
// Dirty hacks, here we come...
return `"` + strconv.FormatInt(vlra.task.ID, 10) + `-` + strconv.FormatInt(vlra.task.Updated, 10) + vlra.task.UID + `"`
} }
// FIXME: because of this, we need to update the list updated timestamp everytime a task on the list is added or modified. // FIXME: because of this, we need to update the list updated timestamp everytime a task on the list is added or modified.
// For now, we randomize this to prevent client-side caching // For now, we randomize this to prevent client-side caching
// This also returns the etag of the list, and not of the task,
// which becomes problematic because the client uses this etag (= the one from the list) to make
// Requests to update a task. These do not match and thus updating a task fails.
return strconv.FormatInt(vlra.list.ID, 10) + `-` + strconv.FormatInt(vlra.list.Updated, 10) + utils.MakeRandomString(10) return strconv.FormatInt(vlra.list.ID, 10) + `-` + strconv.FormatInt(vlra.list.Updated, 10) + utils.MakeRandomString(10)
} }

View File

@ -20,7 +20,6 @@ import (
"code.vikunja.io/api/pkg/caldav" "code.vikunja.io/api/pkg/caldav"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/utils"
"github.com/laurent22/ical-go/ical" "github.com/laurent22/ical-go/ical"
"strconv" "strconv"
"time" "time"
@ -37,7 +36,7 @@ func getCaldavTodosForTasks(tasks []*models.ListTask) string {
caldavtodos = append(caldavtodos, &caldav.Todo{ caldavtodos = append(caldavtodos, &caldav.Todo{
TimestampUnix: t.Updated, TimestampUnix: t.Updated,
UID: utils.Sha256(strconv.FormatInt(t.ID, 10)), UID: t.UID,
Summary: t.Text, Summary: t.Text,
Description: t.Description, Description: t.Description,
CompletedUnix: 0, CompletedUnix: 0,
@ -109,6 +108,10 @@ func parseTaskFromVTODO(content string) (vTask *models.ListTask, err error) {
} }
func caldavTimeToUnixTimestamp(tstring string) int64 { func caldavTimeToUnixTimestamp(tstring string) int64 {
if tstring == "" {
return 0
}
t, err := time.Parse(caldav.DateFormat, tstring) t, err := time.Parse(caldav.DateFormat, tstring)
if err != nil { if err != nil {
log.Log.Errorf("Error while parsing caldav time %s to unix time: %s", tstring, err) log.Log.Errorf("Error while parsing caldav time %s to unix time: %s", tstring, err)