// SiYuan - Refactor your thinking // Copyright (c) 2020-present, b3log.org // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package model import ( "bytes" "context" "errors" "fmt" "io" "os" "path" "strings" "sync" "github.com/88250/gulu" "github.com/emersion/go-vcard" "github.com/emersion/go-webdav/carddav" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/util" ) const ( // REF: https://developers.google.com/people/carddav#resources CardDavPrefixPath = "/carddav" CardDavPrincipalsPath = CardDavPrefixPath + "/principals" // 0 resourceTypeRoot CardDavUserPrincipalPath = CardDavPrincipalsPath + "/main" // 1 resourceTypeUserPrincipal CardDavHomeSetPath = CardDavUserPrincipalPath + "/contacts" // 2 resourceTypeAddressBookHomeSet CardDavDefaultAddressBookPath = CardDavHomeSetPath + "/default" // 3 resourceTypeAddressBook CardDavDefaultAddressBookName = "default" CardDavAddressBooksMetaDataFilePath = CardDavHomeSetPath + "/address-books.json" VCardFileExt = "." + vcard.Extension // .vcf ) type CardDavPathDepth int const ( cardDavPathDepth_Root CardDavPathDepth = 1 + iota // /carddav cardDavPathDepth_Principals // /carddav/principals cardDavPathDepth_UserPrincipal // /carddav/principals/main cardDavPathDepth_HomeSet // /carddav/principals/main/contacts cardDavPathDepth_AddressBook // /carddav/principals/main/contacts/default cardDavPathDepth_Address // /carddav/principals/main/contacts/default/id.vcf ) var ( addressBookMaxResourceSize int64 = 0 addressBookSupportedAddressData = []carddav.AddressDataType{ { ContentType: vcard.MIMEType, Version: "3.0", }, { ContentType: vcard.MIMEType, Version: "4.0", }, } defaultAddressBook = carddav.AddressBook{ Path: CardDavDefaultAddressBookPath, Name: CardDavDefaultAddressBookName, Description: "Default address book", MaxResourceSize: addressBookMaxResourceSize, SupportedAddressData: addressBookSupportedAddressData, } contacts = Contacts{ loaded: false, changed: false, lock: sync.Mutex{}, books: sync.Map{}, booksMetaData: []*carddav.AddressBook{}, } ErrorCardDavPathInvalid = errors.New("CardDAV: path is invalid") ErrorCardDavBookNotFound = errors.New("CardDAV: address book not found") ErrorCardDavBookPathInvalid = errors.New("CardDAV: address book path is invalid") ErrorCardDavAddressNotFound = errors.New("CardDAV: address not found") ErrorCardDavAddressFileExtensionNameInvalid = errors.New("CardDAV: address file extension name is invalid") ) // ImportVCardFile imports a address book from a vCard file (*.vcf) func ImportAddressBook(addressBookPath, cardContent string) (addresses []*AddressObject, err error) { // TODO: Check whether the path is valid (PathDepth: Address) // TODO: Check whether the address book exists // TODO: Decode the card content // TODO: Save the cards to the file system return } // ExportAddressBook exports a address book to a vCard file (*.vcf) func ExportAddressBook(addressBookPath string) (cardContent string, err error) { // TODO: Check whether the path is valid (PathDepth: AddressBook) // TODO: Check whether the address book exists // TODO: Encode the card content return } // AddressBooksMetaDataFilePath returns the absolute path of the address books meta data file func AddressBooksMetaDataFilePath() string { return DavPath2DirectoryPath(CardDavAddressBooksMetaDataFilePath) } func GetCardDavPathDepth(urlPath string) CardDavPathDepth { urlPath = PathCleanWithSlash(urlPath) return CardDavPathDepth(len(strings.Split(urlPath, "/")) - 1) } // ParseAddressPath parses address path to address book path and address ID func ParseAddressPath(addressPath string) (addressBookPath string, addressID string, err error) { addressBookPath, addressFileName := path.Split(addressPath) addressID = path.Base(addressFileName) addressFileExt := util.Ext(addressFileName) if GetCardDavPathDepth(addressBookPath) != cardDavPathDepth_AddressBook { err = ErrorCardDavBookPathInvalid return } if addressFileExt != VCardFileExt { err = ErrorCardDavAddressFileExtensionNameInvalid return } return } // AddressPropsFilter filters address properties func AddressPropsFilter(address *carddav.AddressObject, req *carddav.AddressDataRequest) *carddav.AddressObject { var card *vcard.Card card = &address.Card // if req.AllProp { // card = &address.Card // } else { // card = &vcard.Card{} // for _, prop := range req.Props { // fields := address.Card[prop] // if fields != nil { // for _, field := range fields { // card.Add(prop, field) // } // } // } // } return &carddav.AddressObject{ Path: address.Path, ModTime: address.ModTime, ContentLength: address.ContentLength, ETag: address.ETag, Card: *card, } } func LoadCards(filePath string) (cards []*vcard.Card, err error) { data, err := os.ReadFile(filePath) if err != nil { logging.LogErrorf("read vCard file [%s] failed: %s", filePath, err) return } decoder := vcard.NewDecoder(bytes.NewReader(data)) for { card, err := decoder.Decode() if err != nil { if err == io.EOF { break } logging.LogErrorf("decode vCard file [%s] failed: %s", filePath, err) return nil, err } cards = append(cards, &card) } return } type Contacts struct { loaded bool changed bool lock sync.Mutex // load & save books sync.Map // Path -> *AddressBook booksMetaData []*carddav.AddressBook } // load all contacts func (c *Contacts) load() error { c.books.Clear() // load address books meta data addressBooksMetaDataFilePath := AddressBooksMetaDataFilePath() metaData, err := os.ReadFile(addressBooksMetaDataFilePath) if os.IsNotExist(err) { // create & save default address book c.booksMetaData = []*carddav.AddressBook{&defaultAddressBook} if err := c.saveAddressBooksMetaData(); err != nil { return err } } else { // load meta data file c.booksMetaData = []*carddav.AddressBook{} if err = gulu.JSON.UnmarshalJSON(metaData, &c.booksMetaData); err != nil { logging.LogErrorf("unmarshal address books meta data failed: %s", err) return err } } // load vCard files (*.vcf) wg := &sync.WaitGroup{} wg.Add(len(c.booksMetaData)) for _, addressBookMetaData := range c.booksMetaData { addressBook := &AddressBook{ Changed: false, DirectoryPath: DavPath2DirectoryPath(addressBookMetaData.Path), MetaData: addressBookMetaData, Addresses: sync.Map{}, } c.books.Store(addressBookMetaData.Path, addressBook) go func() { defer wg.Done() addressBook.load() }() } wg.Wait() c.loaded = true c.changed = false return nil } // save all contacts func (c *Contacts) save(force bool) error { if force || c.changed { // save address books meta data if err := c.saveAddressBooksMetaData(); err != nil { return err } // save all address to *.vbf files wg := &sync.WaitGroup{} c.books.Range(func(path any, book any) bool { wg.Add(1) go func() { defer wg.Done() // path_ := path.(string) book_ := book.(*AddressBook) book_.save(force) }() return true }) wg.Wait() c.changed = false } return nil } // save all contacts func (c *Contacts) saveAddressBooksMetaData() error { return SaveMetaData(c.booksMetaData, AddressBooksMetaDataFilePath()) } func (c *Contacts) Load() error { c.lock.Lock() defer c.lock.Unlock() if !c.loaded { return c.load() } return nil } func (c *Contacts) GetAddress(addressPath string) (addressBook *AddressBook, addressObject *AddressObject, err error) { bookPath, addressID, err := ParseAddressPath(addressPath) if err != nil { logging.LogErrorf("parse address path [%s] failed: %s", addressPath, err) return } if value, ok := c.books.Load(bookPath); ok { addressBook = value.(*AddressBook) } else { err = ErrorCardDavBookNotFound return } if value, ok := addressBook.Addresses.Load(addressID); ok { addressObject = value.(*AddressObject) } else { err = ErrorCardDavAddressNotFound return } return } func (c *Contacts) DeleteAddress(addressPath string) (addressBook *AddressBook, addressObject *AddressObject, err error) { bookPath, addressID, err := ParseAddressPath(addressPath) if err != nil { logging.LogErrorf("parse address path [%s] failed: %s", addressPath, err) return } if value, ok := c.books.Load(bookPath); ok { addressBook = value.(*AddressBook) } else { err = ErrorCardDavBookNotFound return } if value, loaded := addressBook.Addresses.LoadAndDelete(addressID); loaded { addressObject = value.(*AddressObject) } else { err = ErrorCardDavAddressNotFound return } if err = os.Remove(addressObject.FilePath); err != nil { logging.LogErrorf("remove file [%s] failed: %s", addressObject.FilePath, err) return } return } func (c *Contacts) ListAddressBooks() (addressBooks []carddav.AddressBook, err error) { c.lock.Lock() defer c.lock.Unlock() for _, addressBook := range contacts.booksMetaData { addressBooks = append(addressBooks, *addressBook) } return } func (c *Contacts) GetAddressBook(path string) (addressBook *carddav.AddressBook, err error) { c.lock.Lock() defer c.lock.Unlock() if book, ok := contacts.books.Load(path); ok { addressBook = book.(*AddressBook).MetaData return } err = ErrorCardDavBookNotFound return } func (c *Contacts) CreateAddressBook(addressBookMetaData *carddav.AddressBook) (err error) { c.lock.Lock() defer c.lock.Unlock() var addressBook *AddressBook // update map if value, ok := c.books.Load(addressBookMetaData.Path); ok { // update map item addressBook = value.(*AddressBook) addressBook.MetaData = addressBookMetaData } else { // insert map item addressBook = &AddressBook{ Changed: false, DirectoryPath: DavPath2DirectoryPath(addressBookMetaData.Path), MetaData: addressBookMetaData, Addresses: sync.Map{}, } c.books.Store(addressBookMetaData.Path, addressBook) } var index = -1 for i, item := range c.booksMetaData { if item.Path == addressBookMetaData.Path { index = i break } } if index >= 0 { // update list c.booksMetaData[index] = addressBookMetaData } else { // insert list c.booksMetaData = append(c.booksMetaData, addressBookMetaData) } // create address book directory if err = os.MkdirAll(addressBook.DirectoryPath, 0755); err != nil { logging.LogErrorf("create directory [%s] failed: %s", addressBook.DirectoryPath, err) return } // save meta data if err = c.saveAddressBooksMetaData(); err != nil { return } return } func (c *Contacts) DeleteAddressBook(path string) (err error) { c.lock.Lock() defer c.lock.Unlock() var addressBook *AddressBook // delete map item if value, loaded := c.books.LoadAndDelete(path); loaded { addressBook = value.(*AddressBook) } // delete list item for i, item := range c.booksMetaData { if item.Path == path { c.booksMetaData = append(c.booksMetaData[:i], c.booksMetaData[i+1:]...) break } } // remove address book directory if err = os.RemoveAll(addressBook.DirectoryPath); err != nil { logging.LogErrorf("remove directory [%s] failed: %s", addressBook.DirectoryPath, err) return } // save meta data if err = c.saveAddressBooksMetaData(); err != nil { return } return nil } func (c *Contacts) GetAddressObject(addressPath string, req *carddav.AddressDataRequest) (addressObject *carddav.AddressObject, err error) { c.lock.Lock() defer c.lock.Unlock() _, address, err := c.GetAddress(addressPath) if err != nil { return } addressObject = AddressPropsFilter(address.Data, req) return } func (c *Contacts) ListAddressObjects(bookPath string, req *carddav.AddressDataRequest) (addressObjects []carddav.AddressObject, err error) { c.lock.Lock() defer c.lock.Unlock() var addressBook *AddressBook if value, ok := c.books.Load(bookPath); ok { addressBook = value.(*AddressBook) } else { err = ErrorCardDavBookNotFound return } addressBook.Addresses.Range(func(id any, address any) bool { addressObjects = append(addressObjects, *AddressPropsFilter(address.(*AddressObject).Data, req)) return true }) return } func (c *Contacts) QueryAddressObjects(urlPath string, query *carddav.AddressBookQuery) (addressObjects []carddav.AddressObject, err error) { c.lock.Lock() defer c.lock.Unlock() switch GetCardDavPathDepth(urlPath) { case cardDavPathDepth_Root, cardDavPathDepth_Principals, cardDavPathDepth_UserPrincipal, cardDavPathDepth_HomeSet: c.books.Range(func(path any, book any) bool { addressBook := book.(*AddressBook) addressBook.Addresses.Range(func(id any, address any) bool { addressObjects = append(addressObjects, *address.(*AddressObject).Data) return true }) return true }) case cardDavPathDepth_AddressBook: if value, ok := c.books.Load(urlPath); ok { addressBook := value.(*AddressBook) addressBook.Addresses.Range(func(id any, address any) bool { addressObjects = append(addressObjects, *address.(*AddressObject).Data) return true }) } case cardDavPathDepth_Address: if _, address, _ := c.GetAddress(urlPath); address != nil { addressObjects = append(addressObjects, *address.Data) } default: err = ErrorCardDavPathInvalid return } addressObjects, err = carddav.Filter(query, addressObjects) return } func (c *Contacts) PutAddressObject(addressPath string, card vcard.Card, opts *carddav.PutAddressObjectOptions) (addressObject *carddav.AddressObject, err error) { c.lock.Lock() defer c.lock.Unlock() bookPath, addressID, err := ParseAddressPath(addressPath) if err != nil { logging.LogErrorf("parse address path [%s] failed: %s", addressPath, err) return } var addressBook *AddressBook if value, ok := c.books.Load(bookPath); ok { addressBook = value.(*AddressBook) } else { err = ErrorCardDavBookNotFound return } // TODO: 处理 opts.IfNoneMatch (If-None-Match) 与 opts.IfMatch (If-Match) var address *AddressObject if value, ok := addressBook.Addresses.Load(addressID); ok { address = value.(*AddressObject) address.Data.Card = card address.Changed = true } else { address = &AddressObject{ Changed: true, FilePath: DavPath2DirectoryPath(addressPath), BookPath: bookPath, Data: &carddav.AddressObject{ Card: card, }, } } err = address.save(true) if err != nil { return } err = address.update() if err != nil { return } addressBook.Addresses.Store(addressID, address) addressObject = address.Data return } func (c *Contacts) DeleteAddressObject(addressPath string) (err error) { c.lock.Lock() defer c.lock.Unlock() _, _, err = c.DeleteAddress(addressPath) if err != nil { return } return } type AddressBook struct { Changed bool DirectoryPath string MetaData *carddav.AddressBook Addresses sync.Map // id -> *AddressObject } // load an address book from multiple *.vcf files func (b *AddressBook) load() error { if err := os.MkdirAll(b.DirectoryPath, 0755); err != nil { logging.LogErrorf("create directory [%s] failed: %s", b.DirectoryPath, err) return err } entries, err := os.ReadDir(b.DirectoryPath) if err != nil { logging.LogErrorf("read dir [%s] failed: %s", b.DirectoryPath, err) return err } wg := &sync.WaitGroup{} for _, entry := range entries { if !entry.IsDir() { filename := entry.Name() ext := util.Ext(filename) if ext == VCardFileExt { wg.Add(1) go func() { defer wg.Done() // load cards addressFilePath := path.Join(b.DirectoryPath, filename) vCards, err := LoadCards(addressFilePath) if err != nil { return } switch len(vCards) { case 0: // invalid file case 1: // file contain 1 card address := &AddressObject{ FilePath: addressFilePath, BookPath: b.MetaData.Path, Data: &carddav.AddressObject{ Card: *vCards[0], }, } if err := address.update(); err != nil { return } id := path.Base(filename) b.Addresses.Store(id, address) default: // file contain multiple cards // Create a file for each card addressesWaitGroup := &sync.WaitGroup{} for _, vCard := range vCards { addressesWaitGroup.Add(1) go func() { defer addressesWaitGroup.Done() filename_ := util.AssetName(filename) address := &AddressObject{ FilePath: path.Join(b.DirectoryPath, filename_), BookPath: b.MetaData.Path, Data: &carddav.AddressObject{ Card: *vCard, }, } if err := address.save(true); err != nil { return } if err := address.update(); err != nil { return } id := path.Base(filename) b.Addresses.Store(id, address) }() } addressesWaitGroup.Wait() // Delete original file with multiple cards if err := os.Remove(addressFilePath); err != nil { logging.LogErrorf("remove file [%s] failed: %s", addressFilePath, err) return } } }() } } } wg.Wait() return nil } // save an address book to multiple *.vcf files func (b *AddressBook) save(force bool) error { if force || b.Changed { // create directory if err := os.MkdirAll(b.DirectoryPath, 0755); err != nil { logging.LogErrorf("create directory [%s] failed: %s", b.DirectoryPath, err) return err } wg := &sync.WaitGroup{} b.Addresses.Range(func(id any, address any) bool { wg.Add(1) go func() { defer wg.Done() // id_ := id.(string) address_ := address.(*AddressObject) address_.save(force) address_.update() }() return true }) wg.Wait() b.Changed = false } return nil } type AddressObject struct { Changed bool FilePath string BookPath string Data *carddav.AddressObject } // load an address from *.vcf file func (o *AddressObject) load() error { // load vCard file cards, err := LoadCards(o.FilePath) if err != nil { return err } if len(cards) != 1 { return fmt.Errorf("file [%s] contains multiple cards", o.FilePath) } // create address object o.Data = &carddav.AddressObject{ Card: *cards[0], } // update file info err = o.update() if err != nil { return err } o.Changed = false return nil } // save an address to *.vcf file func (o *AddressObject) save(force bool) error { if force || o.Changed { var addressData bytes.Buffer // encode data encoder := vcard.NewEncoder(&addressData) if err := encoder.Encode(o.Data.Card); err != nil { logging.LogErrorf("encode card [%s] failed: %s", o.Data.Path, err) return err } // create directory dirPath := path.Dir(o.FilePath) if err := os.MkdirAll(dirPath, 0755); err != nil { logging.LogErrorf("create directory [%s] failed: %s", dirPath, err) return err } // write file if err := os.WriteFile(o.FilePath, addressData.Bytes(), 0755); err != nil { logging.LogErrorf("write file [%s] failed: %s", o.FilePath, err) return err } o.Changed = false } return nil } // update file info func (o *AddressObject) update() error { addressFileInfo, err := os.Stat(o.FilePath) if err != nil { logging.LogErrorf("get file [%s] info failed: %s", o.FilePath, err) return err } o.Data.Path = PathJoinWithSlash(o.BookPath, addressFileInfo.Name()) o.Data.ModTime = addressFileInfo.ModTime() o.Data.ContentLength = addressFileInfo.Size() o.Data.ETag = FileETag(addressFileInfo) return nil } type CardDavBackend struct{} func (b *CardDavBackend) CurrentUserPrincipal(ctx context.Context) (string, error) { // logging.LogDebugf("CardDAV CurrentUserPrincipal") return CardDavUserPrincipalPath, nil } func (b *CardDavBackend) AddressBookHomeSetPath(ctx context.Context) (string, error) { // logging.LogDebugf("CardDAV AddressBookHomeSetPath") return CardDavHomeSetPath, nil } func (b *CardDavBackend) ListAddressBooks(ctx context.Context) (addressBooks []carddav.AddressBook, err error) { // logging.LogDebugf("CardDAV ListAddressBooks") if err = contacts.Load(); err != nil { return } addressBooks, err = contacts.ListAddressBooks() // logging.LogDebugf("CardDAV ListAddressBooks <- addressBooks: %#v, err: %s", addressBooks, err) return } func (b *CardDavBackend) GetAddressBook(ctx context.Context, bookPath string) (addressBook *carddav.AddressBook, err error) { // logging.LogDebugf("CardDAV GetAddressBook -> bookPath: %s", bookPath) bookPath = PathCleanWithSlash(bookPath) if err = contacts.Load(); err != nil { return } addressBook, err = contacts.GetAddressBook(bookPath) // logging.LogDebugf("CardDAV GetAddressBook <- addressBook: %#v, err: %s", addressBook, err) return } func (b *CardDavBackend) CreateAddressBook(ctx context.Context, addressBook *carddav.AddressBook) (err error) { // logging.LogDebugf("CardDAV CreateAddressBook -> addressBook: %#v", addressBook) addressBook.Path = PathCleanWithSlash(addressBook.Path) if err = contacts.Load(); err != nil { return } err = contacts.CreateAddressBook(addressBook) // logging.LogDebugf("CardDAV CreateAddressBook <- err: %s", err) return } func (b *CardDavBackend) DeleteAddressBook(ctx context.Context, bookPath string) (err error) { // logging.LogDebugf("CardDAV DeleteAddressBook -> bookPath: %s", bookPath) bookPath = PathCleanWithSlash(bookPath) if err = contacts.Load(); err != nil { return } err = contacts.DeleteAddressBook(bookPath) // logging.LogDebugf("CardDAV DeleteAddressBook <- err: %s", err) return } func (b *CardDavBackend) GetAddressObject(ctx context.Context, addressPath string, req *carddav.AddressDataRequest) (addressObject *carddav.AddressObject, err error) { // logging.LogDebugf("CardDAV GetAddressObject -> addressPath: %s, req: %#v", addressPath, req) addressPath = PathCleanWithSlash(addressPath) if err = contacts.Load(); err != nil { return } addressObject, err = contacts.GetAddressObject(addressPath, req) // logging.LogDebugf("CardDAV GetAddressObject <- addressObject: %#v, err: %s", addressObject, err) return } func (b *CardDavBackend) ListAddressObjects(ctx context.Context, bookPath string, req *carddav.AddressDataRequest) (addressObjects []carddav.AddressObject, err error) { // logging.LogDebugf("CardDAV ListAddressObjects -> bookPath: %s, req: %#v", bookPath, req) bookPath = PathCleanWithSlash(bookPath) if err = contacts.Load(); err != nil { return } addressObjects, err = contacts.ListAddressObjects(bookPath, req) // logging.LogDebugf("CardDAV ListAddressObjects <- addressObjects: %#v, err: %s", addressObjects, err) return } func (b *CardDavBackend) QueryAddressObjects(ctx context.Context, urlPath string, query *carddav.AddressBookQuery) (addressObjects []carddav.AddressObject, err error) { // logging.LogDebugf("CardDAV QueryAddressObjects -> urlPath: %s, query: %#v", urlPath, query) urlPath = PathCleanWithSlash(urlPath) if err = contacts.Load(); err != nil { return } addressObjects, err = contacts.QueryAddressObjects(urlPath, query) // logging.LogDebugf("CardDAV QueryAddressObjects <- addressObjects: %#v, err: %s", addressObjects, err) return } func (b *CardDavBackend) PutAddressObject(ctx context.Context, addressPath string, card vcard.Card, opts *carddav.PutAddressObjectOptions) (addressObject *carddav.AddressObject, err error) { // logging.LogDebugf("CardDAV PutAddressObject -> addressPath: %s, card: %#v, opts: %#v", addressPath, card, opts) addressPath = PathCleanWithSlash(addressPath) if err = contacts.Load(); err != nil { return } addressObject, err = contacts.PutAddressObject(addressPath, card, opts) // logging.LogDebugf("CardDAV PutAddressObject <- addressObject: %#v, err: %s", addressObject, err) return } func (b *CardDavBackend) DeleteAddressObject(ctx context.Context, addressPath string) (err error) { // logging.LogDebugf("CardDAV DeleteAddressObject -> addressPath: %s", addressPath) addressPath = PathCleanWithSlash(addressPath) if err = contacts.Load(); err != nil { return } err = contacts.DeleteAddressObject(addressPath) // logging.LogDebugf("CardDAV DeleteAddressObject <- err: %s", err) return }