2024-06-19 19:27:13 +00:00
package main
import (
"context"
// "fmt"
webdav "github.com/emersion/go-webdav"
"github.com/emersion/go-webdav/caldav"
2024-06-29 01:00:39 +00:00
"github.com/emersion/go-ical"
2024-06-19 19:27:13 +00:00
"strings"
2024-06-29 01:00:39 +00:00
"github.com/google/uuid"
2024-06-21 18:38:41 +00:00
"time"
2024-06-29 01:00:39 +00:00
"errors"
// "fmt"
2024-06-19 19:27:13 +00:00
)
2024-06-29 01:00:39 +00:00
// type TODO struct {
// Name string
// Desc string
// Time string //only parsed _time_
// // Priority int //TODO
// // Subtasks []TODO //TODO
// // Repeat //TODO
// // Alarm //TODO
// UID string //for editing events
// DueDateTime time.Time //for adding new events
// }
2024-06-19 19:27:13 +00:00
2024-06-29 01:00:39 +00:00
//TODO Is it safe to create global variables?
2024-06-19 19:27:13 +00:00
var clientWebDAV * webdav . Client
var client * caldav . Client // clientCalDAV
// var calendarObjects []caldav.CalendarObject
var ctx = context . Background ( )
2024-06-21 18:38:41 +00:00
2024-06-19 19:27:13 +00:00
// var authSession caldav.Client // clientCalDAV
2024-06-29 01:00:39 +00:00
func InitDAVclients ( url , user , pass string ) error {
2024-06-21 18:38:41 +00:00
2024-06-19 19:27:13 +00:00
var err error
2024-06-29 01:00:39 +00:00
authSession := webdav . HTTPClientWithBasicAuth ( nil , user , pass )
2024-06-19 19:27:13 +00:00
2024-06-29 01:00:39 +00:00
clientWebDAV , err = webdav . NewClient ( authSession , url )
if err != nil {
// Handle error
return err
}
client , err = caldav . NewClient ( authSession , url )
2024-06-19 19:27:13 +00:00
if err != nil {
// Handle error
return err
}
2024-06-29 01:00:39 +00:00
return nil
}
func ( options * Options ) InitDAVclients ( ) error {
err := InitDAVclients ( options . URL , options . User , options . Password )
if err != nil {
// Handle error
return err
}
return nil
}
func ( m model ) InitDAVclients ( ) error {
err := InitDAVclients ( m . Creds . URL , m . Creds . Username , m . Creds . Password )
2024-06-19 19:27:13 +00:00
if err != nil {
// Handle error
return err
}
return nil
}
2024-06-21 18:38:41 +00:00
func GetCalendars ( ) ( [ ] caldav . Calendar , error ) {
2024-06-19 19:27:13 +00:00
principal , err := clientWebDAV . FindCurrentUserPrincipal ( ctx )
if err != nil {
// Handle error
2024-06-21 18:38:41 +00:00
return nil , err
2024-06-19 19:27:13 +00:00
}
// fmt.Println("principal: ",principal) TODO log
// Find the calendar home set
calendarHomeSet , err := client . FindCalendarHomeSet ( ctx , principal )
if err != nil {
2024-06-21 18:38:41 +00:00
return nil , err
2024-06-19 19:27:13 +00:00
}
// Find calendars in the calendar home set
calendars , err := client . FindCalendars ( ctx , calendarHomeSet )
if err != nil {
2024-06-21 18:38:41 +00:00
return nil , err
2024-06-19 19:27:13 +00:00
}
return calendars , nil
}
2024-06-21 18:38:41 +00:00
func GetTODOs ( calendarPath string ) ( calendarObjects [ ] caldav . CalendarObject , err error ) {
2024-06-29 01:00:39 +00:00
//TODO can we use calendar.Data.Component.Children to dont make this close to pointless request?
2024-06-19 19:27:13 +00:00
date := time . Now ( )
2024-06-21 18:38:41 +00:00
dateStart := time . Date ( date . Year ( ) , date . Month ( ) , date . Day ( ) , 23 , 59 , 59 , 0 , date . Location ( ) ) . AddDate ( 0 , 0 , - 1 ) //TODO too complex - time.Now().Add(-92 * time.Hour),
2024-06-19 19:27:13 +00:00
// dateEnd:= time.Date(date.Year(), date.Month(), date.Day(), 23, 59, 59, 0, date.Location()) //date +1 day
2024-06-21 18:38:41 +00:00
dateEnd := date . AddDate ( 0 , 1 , 0 ) //TODO we hard limit pulling events only for this month, to prevent getting tooo much events
2024-06-19 19:27:13 +00:00
calQuery := caldav . CalendarQuery {
CompRequest : caldav . CalendarCompRequest {
2024-06-21 18:38:41 +00:00
Name : "VCALENDAR" ,
Comps : [ ] caldav . CalendarCompRequest { {
Name : "VTODO" ,
Props : [ ] string {
"UID" ,
"SUMMARY" ,
"COMPLETED" ,
"DESCRIPTION" ,
"DUE" ,
"PRIORITY" ,
"RELATED-TO" ,
} ,
} ,
} ,
} ,
2024-06-19 19:27:13 +00:00
CompFilter : caldav . CompFilter {
2024-06-21 18:38:41 +00:00
Name : "VCALENDAR" ,
Comps : [ ] caldav . CompFilter {
{
Name : "VTODO" ,
Start : dateStart ,
End : dateEnd ,
// FYI: server-side filtering is not supported for Nextcloud
2024-06-19 19:27:13 +00:00
} ,
2024-06-21 18:38:41 +00:00
} ,
} ,
2024-06-19 19:27:13 +00:00
}
2024-06-21 18:38:41 +00:00
calendarObjects , err = client . QueryCalendar ( ctx , calendarPath , & calQuery )
2024-06-19 19:27:13 +00:00
if err != nil {
2024-06-21 18:38:41 +00:00
return nil , err
2024-06-19 19:27:13 +00:00
}
2024-06-21 18:38:41 +00:00
return calendarObjects , nil
2024-06-19 19:27:13 +00:00
}
2024-06-29 01:00:39 +00:00
// func ParseDueDateTODOs_depricated(calObjs []caldav.CalendarObject, date time.Time) ([]TODO, error) {
// var output []TODO
//
// for _, calObj := range calObjs {
// // fmt.Println((*(*calObj.Data).Children[0]).Name)
// // TODO STATUS map[] COMPLETED
// for _, event := range (*calObj.Data).Children {
// // if (*event).Name == "VTODO" {
// // var notCompletedTODO, withDate, fromToday bool
// var notCompletedTODO, fromToday bool
// //TODO we can optimize there if we encounter wrong state to forcefully stop next analysis
// //TODO we can use event.Props.Get
// // notCompletedTODO
// if (*event).Props["COMPLETED"] == nil {
// if (*event).Props["STATUS"] == nil {
// notCompletedTODO = true
// } else {
// if (*event).Props["STATUS"][0].Value != "COMPLETED" {
// notCompletedTODO = true
// }
// }
// }
//
// // withTodayDate
// if (*event).Props["DUE"] != nil {
// // withDate = true
// // fromToday
// if strings.HasPrefix((*event).Props["DUE"][0].Value, date.Format("20060102")) {
// fromToday = true
// }
// }
//
// // if notCompletedTODO && withDate && fromToday {
// if notCompletedTODO && fromToday {
//
// var tmpTODO TODO
//
// if (*event).Props["SUMMARY"] != nil {
// name := (*event).Props["SUMMARY"][0].Value
// tmpTODO.Name = name
// } else {
// tmpTODO.Name = "<EMPTY>"
// }
// if (*event).Props["DESCRIPTION"] != nil {
// description := (*event).Props["DESCRIPTION"][0].Value
// tmpTODO.Desc = description
// }
//
// var todoTime string
// due := (*event).Props["DUE"][0].Value
// index := strings.Index(due, "T")
// if index != -1 {
// str := due[index+1:]
// todoTime = str[:2] + ":" + str[2:4]
//
// tmpTODO.Time = todoTime
// }
// output = append(output, tmpTODO)
// }
// // }
// }
// }
//
// // //TODO sort: time, priority (if no time)
// // // it means, put DUE at first, other DUE;VALUE=DATE in priority order
// // //TODO color: priority, add time icon if have time
// // //TODO UID:7ed30f40-fce1-422c-be3b-0486dcfe8943
// // //TODO RELATED-TO:7ed30f40-fce1-422c-be3b-0486dcfe8943 # subtask
// // //TODO PRIORITY:1 #1-high, 5-mid, 9-low
// //TODO repeat function???
//
// //TODO on complete -repeat function
// // RRULE:FREQ=WEEKLY;INTERVAL=1
//
// //TODO if no repeat - mark as complted
// // STATUS:COMPLETED
// // COMPLETED:20240421T065323Z
// // PERCENT-COMPLETE:100
//
// //TODO support notifcations/alarms???
// // BEGIN:VTODO
// // ...
// // BEGIN:VALARM
// // TRIGGER;RELATED=END:-PT15M
// // ACTION:DISPLAY
// // DESCRIPTION:Default Tasks.org description
// // END:VALARM
// // END:VTODO
//
// return output, nil
// }
func ParseDueDateTODOs ( calObjs [ ] caldav . CalendarObject , date time . Time ) ( [ ] ical . Event , error ) {
var output [ ] ical . Event
2024-06-19 19:27:13 +00:00
2024-06-21 18:38:41 +00:00
for _ , calObj := range calObjs {
2024-06-29 01:00:39 +00:00
2024-06-19 19:27:13 +00:00
// fmt.Println((*(*calObj.Data).Children[0]).Name)
// TODO STATUS map[] COMPLETED
2024-06-29 01:00:39 +00:00
// for _, event := range (*calObj.Data).Children {
// event
// for _, event1 := range (*calObj.Data).Children {
// // fmt.Println("1:", event1)
// tmp := ical.Event{Component: event1}
// fmt.Println(tmp.Props.Get(ical.PropUID).Text())
// // for _, event2 := range tmp.Events() {
// // // for _, event2 := range event1.Events() {
// // fmt.Println("1:", event2)
// // }
// }
for _ , eventComponent := range ( * calObj . Data ) . Children {
event := ical . Event { Component : eventComponent }
// for _, event := range (*calObj.Data).Children {
// fmt.Println("1:", event) //TODO rm me
2024-06-19 19:27:13 +00:00
// if (*event).Name == "VTODO" {
2024-06-29 01:00:39 +00:00
// var notCompletedTODO, withDate, fromToday bool
var notCompletedTODO , fromToday bool
2024-06-21 18:38:41 +00:00
//TODO we can optimize there if we encounter wrong state to forcefully stop next analysis
2024-06-29 01:00:39 +00:00
//TODO we can use event.Props.Get
2024-06-21 18:38:41 +00:00
// notCompletedTODO
2024-06-29 01:00:39 +00:00
if event . Props [ "COMPLETED" ] == nil {
if event . Props [ "STATUS" ] == nil {
2024-06-21 18:38:41 +00:00
notCompletedTODO = true
} else {
2024-06-29 01:00:39 +00:00
if event . Props [ "STATUS" ] [ 0 ] . Value != "COMPLETED" {
2024-06-21 18:38:41 +00:00
notCompletedTODO = true
2024-06-19 19:27:13 +00:00
}
}
2024-06-21 18:38:41 +00:00
}
// withTodayDate
2024-06-29 01:00:39 +00:00
if event . Props [ "DUE" ] != nil {
// withDate = true
2024-06-21 18:38:41 +00:00
// fromToday
2024-06-29 01:00:39 +00:00
if strings . HasPrefix ( event . Props [ "DUE" ] [ 0 ] . Value , date . Format ( "20060102" ) ) {
2024-06-21 18:38:41 +00:00
fromToday = true
}
}
2024-06-29 01:00:39 +00:00
// if notCompletedTODO && withDate && fromToday {
if notCompletedTODO && fromToday {
2024-06-21 18:38:41 +00:00
2024-06-29 01:00:39 +00:00
tmpTODO := event
if event . Props [ "SUMMARY" ] == nil {
tmpTODO . Props . SetText ( ical . PropSummary , "<EMPTY>" )
2024-06-19 19:27:13 +00:00
}
2024-06-29 01:00:39 +00:00
if event . Props [ "DESCRIPTION" ] == nil {
tmpTODO . Props . SetText ( ical . PropDescription , "" )
2024-06-21 18:38:41 +00:00
}
2024-06-29 01:00:39 +00:00
2024-06-21 18:38:41 +00:00
2024-06-29 01:00:39 +00:00
// var todoTime string
// due := (*event).Props["DUE"][0].Value
// index := strings.Index(due, "T")
// if index != -1 {
// str := due[index+1:]
// todoTime = str[:2] + ":" + str[2:4]
//
// tmpTODO.Time = todoTime
// }
2024-06-19 19:27:13 +00:00
2024-06-29 01:00:39 +00:00
2024-06-21 18:38:41 +00:00
output = append ( output , tmpTODO )
}
2024-06-19 19:27:13 +00:00
// }
}
}
2024-06-21 18:38:41 +00:00
// //TODO sort: time, priority (if no time)
// // it means, put DUE at first, other DUE;VALUE=DATE in priority order
// //TODO color: priority, add time icon if have time
// //TODO UID:7ed30f40-fce1-422c-be3b-0486dcfe8943
// //TODO RELATED-TO:7ed30f40-fce1-422c-be3b-0486dcfe8943 # subtask
// //TODO PRIORITY:1 #1-high, 5-mid, 9-low
//TODO repeat function???
2024-06-19 19:27:13 +00:00
2024-06-29 01:00:39 +00:00
//TODO on complete -repeat function
// RRULE:FREQ=WEEKLY;INTERVAL=1
//TODO if no repeat - mark as complted
// STATUS:COMPLETED
// COMPLETED:20240421T065323Z
// PERCENT-COMPLETE:100
//TODO support notifcations/alarms???
// BEGIN:VTODO
// ...
// BEGIN:VALARM
// TRIGGER;RELATED=END:-PT15M
// ACTION:DISPLAY
// DESCRIPTION:Default Tasks.org description
// END:VALARM
// END:VTODO
// fmt.Println(output) //TODO rm me
// time.Sleep(3*time.Second)//TODO rm me
2024-06-21 18:38:41 +00:00
return output , nil
2024-06-19 19:27:13 +00:00
}
2024-06-29 01:00:39 +00:00
// type todoInterface struct {
// name string
// description string
// priority int
// dueTime time.Time
// name string
// }
func CreateTodo ( name , description string , priority int , dueTime time . Time ) ( event ical . Event , err error ) {
uid , err := uuid . NewUUID ( )
if err != nil { return }
event = * ical . NewEvent ( )
event . Props . SetText ( ical . PropUID , uid . String ( ) )
event . Props . SetText ( ical . PropSummary , name )
event . Props . SetText ( ical . PropDescription , description )
if ! dueTime . IsZero ( ) { event . Props . SetDateTime ( ical . PropDateTimeEnd , dueTime ) } // 'zero' time is `time.Time{}`
switch priority {
case 0 : //No priority
event . Props . SetText ( ical . PropPriority , "0" )
case 1 : //Light
event . Props . SetText ( ical . PropPriority , "1" )
case 2 : //Medium
event . Props . SetText ( ical . PropPriority , "5" )
case 3 : //Urgent
event . Props . SetText ( ical . PropPriority , "9" )
default :
err = errors . New ( "Wrong priority, expecting 0-3" )
return
}
event . Props . SetDateTime ( ical . PropDateTimeStamp , time . Now ( ) . UTC ( ) )
event . Props . SetDateTime ( ical . PropCreated , time . Now ( ) . UTC ( ) ) //TODO if it don't exist already (in case if we edit todo)
event . Props . SetDateTime ( ical . PropLastModified , time . Now ( ) . UTC ( ) )
return
}
func ( m model ) UploadTodo ( event ical . Event ) ( err error ) {
// event.Props.SetDateTime(ical.PropDateTimeStart, startDateTime)
//TODO PropPriority = "PRIORITY"
// TODO Alarm component properties
// PropAction = "ACTION"
// PropRepeat = "REPEAT"
// PropTrigger = "TRIGGER"}
calendar , err := client . GetCalendarObject ( ctx , m . Creds . CalendarPath )
if err != nil { return }
calendar . Data . Component . Children = append ( calendar . Data . Component . Children , event . Component )
var buf strings . Builder
encoder := ical . NewEncoder ( & buf )
err = encoder . Encode ( calendar . Data )
if err != nil { return }
_ , err = client . PutCalendarObject ( ctx , m . Creds . CalendarPath , calendar . Data )
if err != nil { return }
return nil
}
func ( m model ) DelTodo ( todo ical . Event ) ( err error ) {
delUID , err := todo . Props . Get ( ical . PropUID ) . Text ( )
if err != nil { return }
calendar , err := client . GetCalendarObject ( ctx , m . Creds . CalendarPath )
if err != nil { return }
var newEvents [ ] * ical . Component
for _ , component := range calendar . Data . Component . Children {
if component . Name == ical . CompEvent {
var uid string
uid , err = component . Props . Text ( ical . PropUID )
if err != nil { return }
if uid != delUID {
newEvents = append ( newEvents , component )
}
}
}
calendar . Data . Component . Children = newEvents
var buf strings . Builder
encoder := ical . NewEncoder ( & buf )
err = encoder . Encode ( calendar . Data )
if err != nil { return }
_ , err = client . PutCalendarObject ( ctx , m . Creds . CalendarPath , calendar . Data )
if err != nil { return }
return nil
}
func ( m model ) EditTodo ( todo ical . Event ) ( err error ) {
//TODO is there proper edit function ???
err = m . DelTodo ( todo )
if err != nil { return }
err = m . UploadTodo ( todo )
if err != nil {
//TODO panic, we deleted but cant upload event. Need to save somehow. Or at least write debug string with parameters so we can re-add manually in that case
return
}
return nil
}
//TODO
func ( m model ) CompleteTodo ( todo ical . Event ) ( err error ) {
//TODO if it repitable - Repeate, otherwise complete it
err = m . EditTodo ( todo )
if err != nil { return }
return nil
}