3af290152c
Radicale becomes a write-only render target, not a store. sqlite stays canonical: every poll mavcaldav reads the pending reminders out of core and publishes each one as a single-event iCal resource, withdrawing the ones that have fired or been cancelled. Losing the collection costs nothing — the next tick rebuilds it, and nothing is ever read back from it. It structurally cannot write to a calendar maven only reads. The render URL and credential are their own flags, and -render-url is refused at startup when it names the collection -url reads; the only paths it addresses carry the maven-reminder- prefix, so even aimed at the wrong collection it can only touch resources it created. Rendering is off unless -render-url is given. The calendar data model now lives in one place, internal/calendar: the Event, the iCal parse it comes from and the render it goes to, the fact key/value encoding, and the source constants that say which calendars may be written to. It was a parse inlined in cmd/mavcaldav and a Sprintf in two files; #126 and #128 both need to agree with it. Fixes a latent day-boundary bug moved out of that inline parse: it took the day number off a local clock reading but built the window boundaries in UTC, so on a box east of Greenwich part of the evening fell outside "today" and the poller saw an empty calendar after 20:00 UTC. Today is now the owner's day in the owner's location, which is what the busy gate and the day plan mean.
46 lines
1.6 KiB
Go
46 lines
1.6 KiB
Go
package calendar
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ReminderUIDPrefix namespaces every event maven publishes. Two reasons it is a
|
|
// fixed prefix and not a random UUID: the render is idempotent (the same
|
|
// reminder always lands on the same UID, so re-rendering overwrites instead of
|
|
// duplicating), and everything maven owns in the target collection is
|
|
// identifiable at a glance — she never touches a file she did not create.
|
|
const ReminderUIDPrefix = "maven-reminder-"
|
|
|
|
// DefaultReminderDuration — how long a rendered reminder occupies. A reminder
|
|
// is an instant, a calendar entry is a span, so one has to be invented; 30
|
|
// minutes reads as a block in a calendar app without swallowing the afternoon.
|
|
const DefaultReminderDuration = 30 * time.Minute
|
|
|
|
// ReminderEvent maps a reminder to the event that represents it. id and fire
|
|
// come from the store; payload is the RU text as the owner said it, rendered
|
|
// verbatim as the summary — the calendar is a view of sqlite, not a place to
|
|
// rephrase.
|
|
func ReminderEvent(id int64, fire time.Time, payload string, dur time.Duration) Event {
|
|
if dur <= 0 {
|
|
dur = DefaultReminderDuration
|
|
}
|
|
summary := strings.TrimSpace(payload)
|
|
if summary == "" {
|
|
summary = "напоминание"
|
|
}
|
|
return Event{
|
|
UID: fmt.Sprintf("%s%d", ReminderUIDPrefix, id),
|
|
Summary: summary,
|
|
Start: fire,
|
|
End: fire.Add(dur),
|
|
}
|
|
}
|
|
|
|
// ReminderPath is the collection-relative filename for a rendered reminder.
|
|
// One event per resource, per the CalDAV convention.
|
|
func ReminderPath(id int64) string {
|
|
return fmt.Sprintf("%s%d.ics", ReminderUIDPrefix, id)
|
|
}
|