package storage import ( "context" "encoding/json" "fmt" "io/ioutil" "log" "os" "path/filepath" "github.com/emersion/go-vcard" "github.com/emersion/go-webdav/carddav" "git.sr.ht/~sircmpwn/tokidoki/auth" ) type filesystemBackend struct { path string } var nilBackend carddav.Backend = (*filesystemBackend)(nil) func NewFilesystem(path string) (carddav.Backend, error) { info, err := os.Stat(path) if err != nil { return nilBackend, fmt.Errorf("failed to create filesystem backend: %s", err.Error()) } if !info.IsDir() { return nilBackend, fmt.Errorf("base path for filesystem backend must be a directory") } return &filesystemBackend{ path: path, }, nil } func (b *filesystemBackend) pathForContext(ctx context.Context) (string, error) { raw := ctx.Value(auth.AuthCtxKey) if raw == nil { return b.path, nil } authCtx, ok := raw.(*auth.AuthContext) if !ok { panic("Invalid data in auth context!") } //TODO sanitize user name or at least check if valid dir name? path := filepath.Join(b.path, authCtx.UserName) _, err := os.Stat(path) if os.IsNotExist(err) { err = os.Mkdir(path, 0755) if err != nil { return "", fmt.Errorf("error creating '%s': %s", path, err.Error()) } } return path, nil } func createDefaultAddressBook(path string) error { // TODO what should the default address book look like? defaultAB := carddav.AddressBook{ Path: "/default", Name: "My contacts", Description: "Default address book", MaxResourceSize: 1024, SupportedAddressData: []carddav.AddressDataType{}, } blob, err := json.MarshalIndent(defaultAB, "", " ") if err != nil { return fmt.Errorf("error creating default address book: %s", err.Error()) } err = os.WriteFile(path, blob, 0644) if err != nil { return fmt.Errorf("error writing default address book: %s", err.Error()) } return nil } func (b *filesystemBackend) AddressBook(ctx context.Context) (*carddav.AddressBook, error) { path, err := b.pathForContext(ctx) if err != nil { return nil, err } path = filepath.Join(path, "default.json") _, err = os.Stat(path) if os.IsNotExist(err) { err = createDefaultAddressBook(path) if err != nil { return nil, err } } else if err != nil { return nil, fmt.Errorf("error opening address book: %s", err.Error()) } data, err := ioutil.ReadFile(path) if err != nil { return nil, fmt.Errorf("error opening address book: %s", err.Error()) } var addressBook carddav.AddressBook err = json.Unmarshal(data, &addressBook) if err != nil { return nil, fmt.Errorf("error reading address book: %s", err.Error()) } 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 (*filesystemBackend) ListAddressObjects(ctx context.Context, req *carddav.AddressDataRequest) ([]carddav.AddressObject, error) { return []carddav.AddressObject{}, nil //panic("TODO") } func (*filesystemBackend) QueryAddressObjects(ctx context.Context, query *carddav.AddressBookQuery) ([]carddav.AddressObject, error) { 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 (*filesystemBackend) DeleteAddressObject(ctx context.Context, path string) error { //TODO log.Fatalf("DeleteAddressObject(ctx, '%s'): not implemented", path) return nil }