diff options
Diffstat (limited to 'pim/cmd/caldavfs/main.go')
| -rw-r--r-- | pim/cmd/caldavfs/main.go | 90 |
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 +} |
