diff --git a/storage/filesystem.go b/storage/filesystem.go index 9f54b81..dfbe372 100644 --- a/storage/filesystem.go +++ b/storage/filesystem.go @@ -2,6 +2,8 @@ package storage import ( "context" + "crypto/md5" + "encoding/base64" "encoding/json" "fmt" "io/ioutil" @@ -56,6 +58,31 @@ func (b *filesystemBackend) pathForContext(ctx context.Context) (string, error) return path, nil } +func etagForFile(path string) (string, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return "", err + } + csum := md5.Sum(data) + return base64.StdEncoding.EncodeToString(csum[:]), nil +} + +func vcardFromFile(path string) (*vcard.Card, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + dec := vcard.NewDecoder(f) + card, err := dec.Decode() + if err != nil { + return nil, err + } + + return &card, nil +} + func createDefaultAddressBook(path string) error { // TODO what should the default address book look like? defaultAB := carddav.AddressBook{ @@ -105,30 +132,114 @@ func (b *filesystemBackend) AddressBook(ctx context.Context) (*carddav.AddressBo return &addressBook, nil } -func (*filesystemBackend) GetAddressObject(ctx context.Context, path string, req *carddav.AddressDataRequest) (*carddav.AddressObject, error) { - //TODO - log.Fatalf("PutAddressObject(ctx, '%s', req): not implemented", path) - return nil, nil +func (b *filesystemBackend) GetAddressObject(ctx context.Context, path string, req *carddav.AddressDataRequest) (*carddav.AddressObject, error) { + basePath, err := b.pathForContext(ctx) + if err != nil { + return nil, err + } + path = filepath.Join(basePath, path) + + info, err := os.Stat(path) + if err != nil { + return nil, err + } + + card, err := vcardFromFile(path) + if err != nil { + return nil, err + } + + etag, err := etagForFile(path) + if err != nil { + return nil, err + } + + obj := carddav.AddressObject{ + Path: "/" + filepath.Base(path), + ModTime: info.ModTime(), + ETag: etag, + Card: *card, + } + return &obj, nil } -func (*filesystemBackend) ListAddressObjects(ctx context.Context, req *carddav.AddressDataRequest) ([]carddav.AddressObject, error) { - return []carddav.AddressObject{}, nil - //panic("TODO") +func (b *filesystemBackend) ListAddressObjects(ctx context.Context, req *carddav.AddressDataRequest) ([]carddav.AddressObject, error) { + result := []carddav.AddressObject{} + + path, err := b.pathForContext(ctx) + if err != nil { + return result, err + } + + err = filepath.Walk(path, func(filename string, info os.FileInfo, err error) error { + // TODO this heuristic will not work for all clients + if filepath.Ext(filename) != ".vcf" { + return nil + } + + card, err := vcardFromFile(filename) + if err != nil { + return err + } + + etag, err := etagForFile(filename) + if err != nil { + return err + } + + obj := carddav.AddressObject{ + Path: "/" + filepath.Base(filename), + ModTime: info.ModTime(), + ETag: etag, + Card: *card, + } + result = append(result, obj) + return nil + }) + if err != nil { + panic(err) + } + + return result, nil } func (*filesystemBackend) QueryAddressObjects(ctx context.Context, query *carddav.AddressBookQuery) ([]carddav.AddressObject, error) { + // TODO + log.Println("QueryAddressObjects called, not implemented") return []carddav.AddressObject{}, nil - //panic("TODO") } -func (*filesystemBackend) PutAddressObject(ctx context.Context, path string, card vcard.Card) (loc string, err error) { - //TODO - log.Fatalf("PutAddressObject(ctx, '%s', card): not implemented", path) - return "", nil +func (b *filesystemBackend) PutAddressObject(ctx context.Context, path string, card vcard.Card) (loc string, err error) { + basePath, err := b.pathForContext(ctx) + if err != nil { + return "", err + } + path = filepath.Join(basePath, path) + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + return "", err + } + defer f.Close() + + enc := vcard.NewEncoder(f) + err = enc.Encode(card) + if err != nil { + return "", err + } + + return path, nil } -func (*filesystemBackend) DeleteAddressObject(ctx context.Context, path string) error { - //TODO - log.Fatalf("DeleteAddressObject(ctx, '%s'): not implemented", path) +func (b *filesystemBackend) DeleteAddressObject(ctx context.Context, path string) error { + basePath, err := b.pathForContext(ctx) + if err != nil { + return err + } + path = filepath.Join(basePath, path) + //TODO does this need more security/sanity checks? + err = os.Remove(path) + if err != nil { + return err + } return nil }