summaryrefslogtreecommitdiff
path: root/pim/lib/cal/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'pim/lib/cal/server.go')
-rw-r--r--pim/lib/cal/server.go86
1 files changed, 86 insertions, 0 deletions
diff --git a/pim/lib/cal/server.go b/pim/lib/cal/server.go
index f9da81e..4b588c5 100644
--- a/pim/lib/cal/server.go
+++ b/pim/lib/cal/server.go
@@ -14,6 +14,10 @@
package cal
import (
+ "bufio"
+ "bytes"
+ "crypto/rand"
+ "encoding/hex"
"fmt"
"os"
"sort"
@@ -22,6 +26,7 @@ import (
"sync"
"time"
+ "github.com/emersion/go-ical"
"github.com/knusbaum/go9p"
"github.com/knusbaum/go9p/fs"
)
@@ -81,6 +86,7 @@ func New(be Backend, c Config) *Server {
s.addAlarm()
s.addQuery()
s.addChanged()
+ s.addNew()
return s
}
@@ -323,3 +329,83 @@ func orNone(s string) string {
}
return s
}
+
+// addNew publishes the file you write an event to.
+//
+// cat invite.ics >/mnt/pim/calendars/work/new
+//
+// Writes are buffered and the object is created on close, because that
+// is when the thing you are writing is complete -- cp and cat both do
+// create, write, clunk, so the commit point falls out of 9p rather than
+// having to be invented.
+func (s *Server) addNew() {
+ create, ok := s.be.(Creator)
+ if !ok {
+ return // a backend that cannot add events does not offer the file
+ }
+ var mu sync.Mutex
+ buf := map[uint64][]byte{}
+
+ st := s.fsys.NewStat("new", s.user, s.user, 0222)
+ base := fs.NewStaticFile(st, []byte(""))
+ s.root.AddChild(&fs.WrappedFile{
+ File: base,
+ WriteF: func(fid uint64, off uint64, data []byte) (uint32, error) {
+ mu.Lock()
+ buf[fid] = append(buf[fid], data...)
+ mu.Unlock()
+ return uint32(len(data)), nil
+ },
+ CloseF: func(fid uint64) error {
+ mu.Lock()
+ b := buf[fid]
+ delete(buf, fid)
+ mu.Unlock()
+ if len(b) == 0 {
+ return nil
+ }
+ // Either a whole icalendar object, or the key: value form
+ // the tree itself emits. The second exists so a script
+ // never has to spell iCalendar, where a comma in a summary
+ // is a syntax error waiting to happen.
+ var c *ical.Calendar
+ var err error
+ if bytes.HasPrefix(bytes.TrimSpace(b), []byte("BEGIN:VCALENDAR")) {
+ c, err = ical.NewDecoder(bytes.NewReader(b)).Decode()
+ } else {
+ c, err = Compose(bufio.NewReader(bytes.NewReader(b)), newUID())
+ }
+ if err != nil {
+ Warnf("new: %v", err)
+ return err
+ }
+ if len(c.Events()) == 0 {
+ Warnf("new: no VEVENT")
+ return fmt.Errorf("new: no VEVENT")
+ }
+ if err := create.Create(c); err != nil {
+ Warnf("new: %v", err)
+ return err
+ }
+ if _, err := s.be.Sync(); err == nil {
+ s.Reload()
+ }
+ return nil
+ },
+ })
+}
+
+// newUID mints an identifier for an event we are creating. This is the
+// one place a UID is generated rather than echoed back: as organiser we
+// own the event, so we name it.
+func newUID() string {
+ var b [16]byte
+ if _, err := rand.Read(b[:]); err != nil {
+ return fmt.Sprintf("%d@pim", time.Now().UnixNano())
+ }
+ host, _ := os.Hostname()
+ if host == "" {
+ host = "pim"
+ }
+ return hex.EncodeToString(b[:]) + "@" + host
+}