1c94df76b7
Withdrawal read published, which is in-memory, so the second loop only ever withdrew reminders this process had published. Fire a reminder, restart mavcaldav, and its event stayed in the collection forever with nothing left to revisit it. "Losing it costs nothing, the next tick rebuilds it" holds for events that should be there and not for the ones that should not. The first tick now PROPFINDs the collection and reconciles what it finds against what is pending. Only hrefs carrying ReminderUIDPrefix are read back, so the pass can never propose deleting a file maven did not create. A failed read is retried on the next tick rather than skipped for the life of the process. Two smaller things from the same review. checkRenderTarget takes the whole read set, so a second calendar to read cannot quietly fall outside the guarantee the package comment makes. writeIfChanged loses its confidence parameter, which every caller passed 1.0 and nothing read. Found in review of #56.
70 lines
2.3 KiB
Go
70 lines
2.3 KiB
Go
package calendar
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"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)
|
|
}
|
|
|
|
// ReminderIDFromPath reads back what ReminderPath wrote, given an href out of a
|
|
// PROPFIND. It reports false for anything that is not a resource maven
|
|
// published, which is what keeps a reconciliation pass from touching a file it
|
|
// did not create.
|
|
func ReminderIDFromPath(href string) (int64, bool) {
|
|
name := href
|
|
if i := strings.LastIndex(name, "/"); i >= 0 {
|
|
name = name[i+1:]
|
|
}
|
|
if !strings.HasPrefix(name, ReminderUIDPrefix) || !strings.HasSuffix(name, ".ics") {
|
|
return 0, false
|
|
}
|
|
digits := name[len(ReminderUIDPrefix) : len(name)-len(".ics")]
|
|
if digits == "" {
|
|
return 0, false
|
|
}
|
|
id, err := strconv.ParseInt(digits, 10, 64)
|
|
if err != nil || id <= 0 {
|
|
return 0, false
|
|
}
|
|
return id, true
|
|
}
|