summaryrefslogtreecommitdiff
path: root/pim/cmd/caldavfs/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'pim/cmd/caldavfs/main.go')
-rw-r--r--pim/cmd/caldavfs/main.go90
1 files changed, 75 insertions, 15 deletions
diff --git a/pim/cmd/caldavfs/main.go b/pim/cmd/caldavfs/main.go
index c3306de..7a0a4d3 100644
--- a/pim/cmd/caldavfs/main.go
+++ b/pim/cmd/caldavfs/main.go
@@ -33,20 +33,33 @@ func fatal(format string, a ...interface{}) {
}
type backend struct {
- name string
- client *caldav.Client
- path string // the calendar collection
- window time.Duration
- refresh time.Duration
- sched bool // server does the scheduling for us
+ name string
+ endpoint string
+ client *caldav.Client
+ path string // the calendar collection
+ window time.Duration
+ refresh time.Duration
+ sched bool // server does the scheduling for us
+
+ user string
+ pass string
+ me []string
mu sync.Mutex
evs []*cal.Event
sig string
+ obj map[string]objref // uid -> where it lives on the server
last time.Time
err string
}
+// objref is what a write needs: the path to PUT back to, and the etag
+// that says nobody else has touched it since we looked.
+type objref struct {
+ path string
+ etag string
+}
+
func (b *backend) Name() string { return b.name }
// autoschedule is reported so a dry run shows it; it decides whether
@@ -54,12 +67,18 @@ func (b *backend) Name() string { return b.name }
func (b *backend) Sched() bool { return b.sched }
func (b *backend) Caps() string {
- // Writing is not implemented yet, so say so rather than promise it.
- // When it is: "read write rsvp" plus "schedule" when the server
- // advertises calendar-auto-schedule.
- return "read"
+ c := "read rsvp"
+ if b.sched {
+ // the server sends the iMIP for us; one PUT does both halves
+ c += " schedule"
+ }
+ return c
}
+// Me is whose calendar this is. Without it nothing can tell which of an
+// event's attendees to change.
+func (b *backend) Me() []string { return b.me }
+
func (b *backend) Refresh() time.Duration { return b.refresh }
func (b *backend) Status() (time.Time, string) {
@@ -120,15 +139,22 @@ func (b *backend) Sync() (bool, error) {
}
cals := make([]*ical.Calendar, 0, len(objs))
+ ref := make(map[string]objref, len(objs))
for _, o := range objs {
- if o.Data != nil {
- cals = append(cals, o.Data)
+ if o.Data == nil {
+ continue
+ }
+ cals = append(cals, o.Data)
+ for _, e := range o.Data.Events() {
+ if uid, err := e.Props.Text(ical.PropUID); err == nil && uid != "" {
+ ref[uid] = objref{o.Path, o.ETag}
+ }
}
}
evs := cal.FromCalendars(cals)
b.mu.Lock()
- b.evs, b.sig = evs, sig
+ b.evs, b.sig, b.obj = evs, sig, ref
b.mu.Unlock()
cal.Warnf("%s: %d objects", b.name, len(objs))
return true, nil
@@ -182,11 +208,13 @@ func main() {
srv = flag.String("s", "", "service name to post in /srv (default caldav.$user.$pid)")
days = flag.Int("w", 400, "expansion window in days")
poll = flag.Duration("r", 15*time.Minute, "how often to re-query")
+ me = flag.String("m", "", "your addresses on this calendar, comma separated (default: -u)")
dry = flag.Bool("n", false, "load and report, do not serve")
+ once = flag.String("R", "", "answer one invitation: uid:ACCEPTED|DECLINED|TENTATIVE, then exit")
)
flag.Usage = func() {
fmt.Fprintf(os.Stderr,
- "usage: caldav/fs -e endpoint -u user -p pwfile [-C calendar] [-N name] [-s srv] [-w days] [-r poll] [-n]\n")
+ "usage: caldav/fs -e endpoint -u user -p pwfile [-C calendar] [-N name] [-m me] [-s srv] [-w days] [-r poll] [-n]\n")
os.Exit(2)
}
flag.Parse()
@@ -218,7 +246,9 @@ func main() {
}
be := &backend{
- name: label, client: client, path: coll.Path, sched: sched,
+ name: label, endpoint: *endpoint, client: client,
+ path: coll.Path, sched: sched,
+ user: *user, pass: pass, me: mefrom(*me, *user),
window: time.Duration(*days) * 24 * time.Hour,
refresh: *poll,
}
@@ -227,6 +257,20 @@ func main() {
User: who, Srv: *srv, Conf: *pwfile,
Window: time.Duration(*days) * 24 * time.Hour,
})
+ if *once != "" {
+ uid, want, ok := strings.Cut(*once, ":")
+ if !ok {
+ fatal("-R wants uid:PARTSTAT")
+ }
+ if _, err := be.Sync(); err != nil {
+ fatal("%v", err)
+ }
+ if err := be.RSVP(uid, time.Time{}, strings.ToUpper(want)); err != nil {
+ fatal("%v", err)
+ }
+ cal.Warnf("%s: %s", uid, strings.ToUpper(want))
+ return
+ }
if *dry {
if err := s.Report(); err != nil {
fatal("%v", err)
@@ -237,3 +281,19 @@ func main() {
fatal("%v", err)
}
}
+
+// mefrom decides which addresses count as us. The account name is the
+// obvious default, but you may be invited at an alias and have to be
+// told about it.
+func mefrom(list, user string) []string {
+ if list == "" {
+ return []string{user}
+ }
+ var out []string
+ for _, a := range strings.Split(list, ",") {
+ if a = strings.TrimSpace(a); a != "" {
+ out = append(out, a)
+ }
+ }
+ return out
+}