🎨 Implement CalDAV Calendar manage functions
This commit is contained in:
parent
72fbc9ca87
commit
7497817f70
3 changed files with 418 additions and 82 deletions
|
@ -17,9 +17,15 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/emersion/go-ical"
|
||||
"github.com/emersion/go-webdav/caldav"
|
||||
"github.com/siyuan-note/logging"
|
||||
|
@ -35,6 +41,8 @@ const (
|
|||
|
||||
CalDavDefaultCalendarName = "default"
|
||||
|
||||
CalDavCalendarsMetaDataFilePath = CalDavHomeSetPath + "/calendars.json"
|
||||
|
||||
ICalendarFileExt = "." + ical.Extension // .ics
|
||||
)
|
||||
|
||||
|
@ -67,8 +75,34 @@ var (
|
|||
calendars: sync.Map{},
|
||||
calendarsMetaData: []*caldav.Calendar{},
|
||||
}
|
||||
|
||||
ErrorCalDavPathInvalid = errors.New("CalDAV: path is invalid")
|
||||
|
||||
ErrorCalDavCalendarNotFound = errors.New("CalDAV: calendar not found")
|
||||
ErrorCalDavCalendarPathInvalid = errors.New("CalDAV: calendar path is invalid")
|
||||
|
||||
ErrorCalDavCalendarObjectNotFound = errors.New("CalDAV: calendar object not found")
|
||||
ErrorCalDavCalendarObjectPathInvalid = errors.New("CalDAV: calendar object path is invalid")
|
||||
)
|
||||
|
||||
// CalendarsMetaDataFilePath returns the absolute path of the calendars' meta data file
|
||||
func CalendarsMetaDataFilePath() string {
|
||||
return DavPath2DirectoryPath(CalDavCalendarsMetaDataFilePath)
|
||||
}
|
||||
|
||||
// LoadCalendarObject loads a iCalendar file (*.ics)
|
||||
func LoadCalendarObject(filePath string) (calendar *ical.Calendar, err error) {
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("read iCalendar file [%s] failed: %s", filePath, err)
|
||||
return
|
||||
}
|
||||
|
||||
decoder := ical.NewDecoder(bytes.NewReader(data))
|
||||
calendar, err = decoder.Decode()
|
||||
return
|
||||
}
|
||||
|
||||
type Calendars struct {
|
||||
loaded bool
|
||||
changed bool
|
||||
|
@ -77,6 +111,173 @@ type Calendars struct {
|
|||
calendarsMetaData []*caldav.Calendar
|
||||
}
|
||||
|
||||
func (c *Calendars) load() error {
|
||||
c.calendars.Clear()
|
||||
|
||||
// load calendars meta data file
|
||||
calendarsMetaDataFilePath := CalendarsMetaDataFilePath()
|
||||
metaData, err := os.ReadFile(calendarsMetaDataFilePath)
|
||||
if os.IsNotExist(err) {
|
||||
// create & save default calendar
|
||||
c.calendarsMetaData = []*caldav.Calendar{&defaultCalendar}
|
||||
if err := c.saveCalendarsMetaData(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// load meta data file
|
||||
c.calendarsMetaData = []*caldav.Calendar{}
|
||||
if err = gulu.JSON.UnmarshalJSON(metaData, &c.calendarsMetaData); err != nil {
|
||||
logging.LogErrorf("unmarshal address books meta data failed: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// load iCalendar files (*.ics)
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(c.calendarsMetaData))
|
||||
for _, calendarMetaData := range c.calendarsMetaData {
|
||||
calendar := &Calendar{
|
||||
Changed: false,
|
||||
DirectoryPath: DavPath2DirectoryPath(calendarMetaData.Path),
|
||||
MetaData: calendarMetaData,
|
||||
Objects: sync.Map{},
|
||||
}
|
||||
c.calendars.Store(calendarMetaData.Path, calendar)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
calendar.load()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
c.loaded = true
|
||||
c.changed = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// save all calendars
|
||||
func (c *Calendars) saveCalendarsMetaData() error {
|
||||
return SaveMetaData(c.calendarsMetaData, CalendarsMetaDataFilePath())
|
||||
}
|
||||
|
||||
func (c *Calendars) Load() error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if !c.loaded {
|
||||
return c.load()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Calendars) CreateCalendar(calendarMetaData *caldav.Calendar) (err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
var calendar *Calendar
|
||||
|
||||
// update map
|
||||
if value, ok := c.calendars.Load(calendarMetaData.Path); ok {
|
||||
// update map item
|
||||
calendar = value.(*Calendar)
|
||||
calendar.MetaData = calendarMetaData
|
||||
} else {
|
||||
// insert map item
|
||||
calendar = &Calendar{
|
||||
Changed: false,
|
||||
DirectoryPath: DavPath2DirectoryPath(calendarMetaData.Path),
|
||||
MetaData: calendarMetaData,
|
||||
Objects: sync.Map{},
|
||||
}
|
||||
c.calendars.Store(calendarMetaData.Path, calendar)
|
||||
}
|
||||
|
||||
var index = -1
|
||||
for i, item := range c.calendarsMetaData {
|
||||
if item.Path == calendarMetaData.Path {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if index >= 0 {
|
||||
// update list
|
||||
c.calendarsMetaData[index] = calendarMetaData
|
||||
} else {
|
||||
// insert list
|
||||
c.calendarsMetaData = append(c.calendarsMetaData, calendarMetaData)
|
||||
}
|
||||
|
||||
// create calendar directory
|
||||
if err = os.MkdirAll(calendar.DirectoryPath, 0755); err != nil {
|
||||
logging.LogErrorf("create directory [%s] failed: %s", calendar.DirectoryPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
// save meta data
|
||||
if err = c.saveCalendarsMetaData(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Calendars) ListCalendars() (calendars []caldav.Calendar, err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
for _, calendar := range c.calendarsMetaData {
|
||||
calendars = append(calendars, *calendar)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Calendars) GetCalendar(calendarPath string) (calendar *caldav.Calendar, err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if value, ok := calendars.calendars.Load(calendarPath); ok {
|
||||
calendar = value.(*Calendar).MetaData
|
||||
return
|
||||
}
|
||||
|
||||
err = ErrorCalDavCalendarNotFound
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Calendars) DeleteCalendar(calendarPath string) (err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
var calendar *Calendar
|
||||
|
||||
// delete map item
|
||||
if value, loaded := c.calendars.LoadAndDelete(calendarPath); loaded {
|
||||
calendar = value.(*Calendar)
|
||||
}
|
||||
|
||||
// delete list item
|
||||
for i, item := range c.calendarsMetaData {
|
||||
if item.Path == calendarPath {
|
||||
c.calendarsMetaData = append(c.calendarsMetaData[:i], c.calendarsMetaData[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// remove address book directory
|
||||
if err = os.RemoveAll(calendar.DirectoryPath); err != nil {
|
||||
logging.LogErrorf("remove directory [%s] failed: %s", calendar.DirectoryPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
// save meta data
|
||||
if err = c.saveCalendarsMetaData(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Calendar struct {
|
||||
Changed bool
|
||||
DirectoryPath string
|
||||
|
@ -84,6 +285,50 @@ type Calendar struct {
|
|||
Objects sync.Map // id -> *CalendarObject
|
||||
}
|
||||
|
||||
func (c *Calendar) load() error {
|
||||
if err := os.MkdirAll(c.DirectoryPath, 0755); err != nil {
|
||||
logging.LogErrorf("create directory [%s] failed: %s", c.DirectoryPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(c.DirectoryPath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("read dir [%s] failed: %s", c.DirectoryPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
filename := entry.Name()
|
||||
ext := path.Ext(filename)
|
||||
if ext == ICalendarFileExt {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
// create & load calendar object
|
||||
calendarObjectFilePath := path.Join(c.DirectoryPath, filename)
|
||||
calendarObject := &CalendarObject{
|
||||
Changed: false,
|
||||
FilePath: calendarObjectFilePath,
|
||||
CalendarPath: c.MetaData.Path,
|
||||
}
|
||||
err = calendarObject.load()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
id := path.Base(filename)
|
||||
c.Objects.Store(id, calendarObject)
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
type CalendarObject struct {
|
||||
Changed bool
|
||||
FilePath string
|
||||
|
@ -91,6 +336,44 @@ type CalendarObject struct {
|
|||
Data *caldav.CalendarObject
|
||||
}
|
||||
|
||||
func (o *CalendarObject) load() error {
|
||||
// load iCalendar file
|
||||
calendarObjectData, err := LoadCalendarObject(o.FilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create address object
|
||||
o.Data = &caldav.CalendarObject{
|
||||
Data: calendarObjectData,
|
||||
}
|
||||
|
||||
// update file info
|
||||
err = o.update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Changed = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// update file info
|
||||
func (o *CalendarObject) 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.CalendarPath, addressFileInfo.Name())
|
||||
o.Data.ModTime = addressFileInfo.ModTime()
|
||||
o.Data.ContentLength = addressFileInfo.Size()
|
||||
o.Data.ETag = fmt.Sprintf("%x-%x", addressFileInfo.ModTime(), addressFileInfo.Size())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type CalDavBackend struct{}
|
||||
|
||||
func (b *CalDavBackend) CurrentUserPrincipal(ctx context.Context) (string, error) {
|
||||
|
@ -105,19 +388,45 @@ func (b *CalDavBackend) CalendarHomeSetPath(ctx context.Context) (string, error)
|
|||
|
||||
func (b *CalDavBackend) CreateCalendar(ctx context.Context, calendar *caldav.Calendar) (err error) {
|
||||
logging.LogDebugf("CalDAV CreateCalendar -> calendar: %#v", calendar)
|
||||
// TODO: create calendar
|
||||
if err = calendars.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = calendars.CreateCalendar(calendar)
|
||||
logging.LogDebugf("CalDAV CreateCalendar <- err: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *CalDavBackend) ListCalendars(ctx context.Context) (calendars []caldav.Calendar, err error) {
|
||||
func (b *CalDavBackend) ListCalendars(ctx context.Context) (calendars_ []caldav.Calendar, err error) {
|
||||
logging.LogDebugf("CalDAV ListCalendars")
|
||||
// TODO: list calendars
|
||||
if err = calendars.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
calendars_, err = calendars.ListCalendars()
|
||||
logging.LogDebugf("CalDAV ListCalendars <- calendars: %#v, err: %s", calendars_, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *CalDavBackend) GetCalendar(ctx context.Context, calendarPath string) (calendar *caldav.Calendar, err error) {
|
||||
logging.LogDebugf("CalDAV GetCalendar -> calendarPath: %s", calendarPath)
|
||||
// TODO: get calendar
|
||||
if err = calendars.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
calendar, err = calendars.GetCalendar(calendarPath)
|
||||
logging.LogDebugf("CalDAV GetCalendar <- calendar: %#v, err: %s", calendar, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *CalDavBackend) DeleteCalendar(ctx context.Context, calendarPath string) (err error) {
|
||||
logging.LogDebugf("CalDAV DeleteCalendar -> calendarPath: %s", calendarPath)
|
||||
if err = calendars.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = calendars.DeleteCalendar(calendarPath)
|
||||
logging.LogDebugf("CalDAV DeleteCalendar <- err: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
@ -89,14 +88,13 @@ var (
|
|||
booksMetaData: []*carddav.AddressBook{},
|
||||
}
|
||||
|
||||
ErrorNotFound = errors.New("CardDAV: not found")
|
||||
ErrorPathInvalid = errors.New("CardDAV: path is invalid")
|
||||
ErrorCardDavPathInvalid = errors.New("CardDAV: path is invalid")
|
||||
|
||||
ErrorBookNotFound = errors.New("CardDAV: address book not found")
|
||||
ErrorBookPathInvalid = errors.New("CardDAV: address book path is invalid")
|
||||
ErrorCardDavBookNotFound = errors.New("CardDAV: address book not found")
|
||||
ErrorCardDavBookPathInvalid = errors.New("CardDAV: address book path is invalid")
|
||||
|
||||
ErrorAddressNotFound = errors.New("CardDAV: address not found")
|
||||
ErrorAddressFileExtensionNameInvalid = errors.New("CardDAV: address file extension name 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)
|
||||
|
@ -116,19 +114,9 @@ func ExportAddressBook(addressBookPath string) (cardContent string, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// CardDavPath2DirectoryPath converts CardDAV path to absolute path of the file system
|
||||
func CardDavPath2DirectoryPath(cardDavPath string) string {
|
||||
return filepath.Join(util.DataDir, "storage", strings.TrimPrefix(cardDavPath, "/"))
|
||||
}
|
||||
|
||||
// CardDavHomeSetDirectoryPath returns the absolute path of the address book home set directory
|
||||
func CardDavHomeSetDirectoryPath() string {
|
||||
return CardDavPath2DirectoryPath(CardDavHomeSetPath)
|
||||
}
|
||||
|
||||
// AddressBooksMetaDataFilePath returns the absolute path of the address books meta data file
|
||||
func AddressBooksMetaDataFilePath() string {
|
||||
return CardDavPath2DirectoryPath(CardDavAddressBooksMetaDataFilePath)
|
||||
return DavPath2DirectoryPath(CardDavAddressBooksMetaDataFilePath)
|
||||
}
|
||||
|
||||
func GetCardDavPathDepth(urlPath string) CardDavPathDepth {
|
||||
|
@ -143,12 +131,12 @@ func ParseAddressPath(addressPath string) (addressBookPath string, addressID str
|
|||
addressFileExt := path.Ext(addressFileName)
|
||||
|
||||
if GetCardDavPathDepth(addressBookPath) != cardDavPathDepth_AddressBook {
|
||||
err = ErrorBookPathInvalid
|
||||
err = ErrorCardDavBookPathInvalid
|
||||
return
|
||||
}
|
||||
|
||||
if addressFileExt != VCardFileExt {
|
||||
err = ErrorAddressFileExtensionNameInvalid
|
||||
err = ErrorCardDavAddressFileExtensionNameInvalid
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -217,9 +205,12 @@ type Contacts struct {
|
|||
// 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
|
||||
|
@ -233,12 +224,13 @@ func (c *Contacts) load() error {
|
|||
}
|
||||
}
|
||||
|
||||
// load vCard files (*.vcf)
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(c.booksMetaData))
|
||||
for _, addressBookMetaData := range c.booksMetaData {
|
||||
addressBook := &AddressBook{
|
||||
Changed: false,
|
||||
DirectoryPath: CardDavPath2DirectoryPath(addressBookMetaData.Path),
|
||||
DirectoryPath: DavPath2DirectoryPath(addressBookMetaData.Path),
|
||||
MetaData: addressBookMetaData,
|
||||
Addresses: sync.Map{},
|
||||
}
|
||||
|
@ -283,25 +275,7 @@ func (c *Contacts) save(force bool) error {
|
|||
|
||||
// save all contacts
|
||||
func (c *Contacts) saveAddressBooksMetaData() error {
|
||||
data, err := gulu.JSON.MarshalIndentJSON(c.booksMetaData, "", " ")
|
||||
if err != nil {
|
||||
logging.LogErrorf("marshal address books meta data failed: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
dirPath := CardDavHomeSetDirectoryPath()
|
||||
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
||||
logging.LogErrorf("create directory [%s] failed: %s", dirPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
filePath := AddressBooksMetaDataFilePath()
|
||||
if err := os.WriteFile(filePath, data, 0755); err != nil {
|
||||
logging.LogErrorf("write file [%s] failed: %s", filePath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return SaveMetaData(c.booksMetaData, AddressBooksMetaDataFilePath())
|
||||
}
|
||||
|
||||
func (c *Contacts) Load() error {
|
||||
|
@ -324,14 +298,14 @@ func (c *Contacts) GetAddress(addressPath string) (addressBook *AddressBook, add
|
|||
if value, ok := c.books.Load(bookPath); ok {
|
||||
addressBook = value.(*AddressBook)
|
||||
} else {
|
||||
err = ErrorBookNotFound
|
||||
err = ErrorCardDavBookNotFound
|
||||
return
|
||||
}
|
||||
|
||||
if value, ok := addressBook.Addresses.Load(addressID); ok {
|
||||
addressObject = value.(*AddressObject)
|
||||
} else {
|
||||
err = ErrorAddressNotFound
|
||||
err = ErrorCardDavAddressNotFound
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -357,7 +331,7 @@ func (c *Contacts) GetAddressBook(path string) (addressBook *carddav.AddressBook
|
|||
return
|
||||
}
|
||||
|
||||
err = ErrorBookNotFound
|
||||
err = ErrorCardDavBookNotFound
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -376,7 +350,7 @@ func (c *Contacts) CreateAddressBook(addressBookMetaData *carddav.AddressBook) (
|
|||
// insert map item
|
||||
addressBook = &AddressBook{
|
||||
Changed: false,
|
||||
DirectoryPath: CardDavPath2DirectoryPath(addressBookMetaData.Path),
|
||||
DirectoryPath: DavPath2DirectoryPath(addressBookMetaData.Path),
|
||||
MetaData: addressBookMetaData,
|
||||
Addresses: sync.Map{},
|
||||
}
|
||||
|
@ -401,7 +375,7 @@ func (c *Contacts) CreateAddressBook(addressBookMetaData *carddav.AddressBook) (
|
|||
|
||||
// create address book directory
|
||||
if err = os.MkdirAll(addressBook.DirectoryPath, 0755); err != nil {
|
||||
logging.LogErrorf("create directory [%s] failed: %s", addressBook, err)
|
||||
logging.LogErrorf("create directory [%s] failed: %s", addressBook.DirectoryPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -434,7 +408,7 @@ func (c *Contacts) DeleteAddressBook(path string) (err error) {
|
|||
|
||||
// remove address book directory
|
||||
if err = os.RemoveAll(addressBook.DirectoryPath); err != nil {
|
||||
logging.LogErrorf("remove directory [%s] failed: %s", addressBook, err)
|
||||
logging.LogErrorf("remove directory [%s] failed: %s", addressBook.DirectoryPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -467,7 +441,7 @@ func (c *Contacts) ListAddressObjects(bookPath string, req *carddav.AddressDataR
|
|||
if value, ok := c.books.Load(bookPath); ok {
|
||||
addressBook = value.(*AddressBook)
|
||||
} else {
|
||||
err = ErrorBookNotFound
|
||||
err = ErrorCardDavBookNotFound
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -506,7 +480,7 @@ func (c *Contacts) QueryAddressObjects(urlPath string, query *carddav.AddressBoo
|
|||
addressObjects = append(addressObjects, *address.Data)
|
||||
}
|
||||
default:
|
||||
err = ErrorPathInvalid
|
||||
err = ErrorCardDavPathInvalid
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -528,7 +502,7 @@ func (c *Contacts) PutAddressObject(addressPath string, card vcard.Card, opts *c
|
|||
if value, ok := c.books.Load(bookPath); ok {
|
||||
addressBook = value.(*AddressBook)
|
||||
} else {
|
||||
err = ErrorBookNotFound
|
||||
err = ErrorCardDavBookNotFound
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -546,7 +520,7 @@ func (c *Contacts) PutAddressObject(addressPath string, card vcard.Card, opts *c
|
|||
} else {
|
||||
address = &AddressObject{
|
||||
Changed: true,
|
||||
FilePath: CardDavPath2DirectoryPath(addressPath),
|
||||
FilePath: DavPath2DirectoryPath(addressPath),
|
||||
BookPath: bookPath,
|
||||
Data: &carddav.AddressObject{
|
||||
Card: card,
|
||||
|
@ -574,7 +548,7 @@ func (c *Contacts) DeleteAddressObject(addressPath string) (err error) {
|
|||
defer c.lock.Unlock()
|
||||
|
||||
_, address, err := c.GetAddress(addressPath)
|
||||
if err != nil && err != ErrorAddressNotFound {
|
||||
if err != nil && err != ErrorCardDavAddressNotFound {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -718,38 +692,27 @@ type AddressObject struct {
|
|||
|
||||
// load an address from *.vcf file
|
||||
func (o *AddressObject) load() error {
|
||||
// get file info
|
||||
addressFileInfo, err := os.Stat(o.FilePath)
|
||||
// load vCard file
|
||||
cards, err := LoadCards(o.FilePath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("get file [%s] info failed: %s", o.FilePath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// read file
|
||||
addressData, err := os.ReadFile(o.FilePath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("read file [%s] failed: %s", o.FilePath, err)
|
||||
return err
|
||||
if len(cards) != 1 {
|
||||
return fmt.Errorf("file [%s] contains multiple cards", o.FilePath)
|
||||
}
|
||||
|
||||
// decode file
|
||||
reader := bytes.NewReader(addressData)
|
||||
decoder := vcard.NewDecoder(reader)
|
||||
card, err := decoder.Decode()
|
||||
if err != nil {
|
||||
logging.LogErrorf("decode file [%s] failed: %s", o.FilePath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// load data
|
||||
o.Changed = false
|
||||
// create address object
|
||||
o.Data = &carddav.AddressObject{
|
||||
Path: path.Join(o.BookPath, addressFileInfo.Name()),
|
||||
ModTime: addressFileInfo.ModTime(),
|
||||
ContentLength: addressFileInfo.Size(),
|
||||
ETag: fmt.Sprintf("%x-%x", addressFileInfo.ModTime(), addressFileInfo.Size()),
|
||||
Card: card,
|
||||
Card: *cards[0],
|
||||
}
|
||||
|
||||
// update file info
|
||||
err = o.update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Changed = false
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -785,14 +748,13 @@ func (o *AddressObject) save(force bool) error {
|
|||
|
||||
// update file info
|
||||
func (o *AddressObject) update() error {
|
||||
// update file info
|
||||
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 = path.Join(o.BookPath, addressFileInfo.Name())
|
||||
o.Data.Path = PathJoinWithSlash(o.BookPath, addressFileInfo.Name())
|
||||
o.Data.ModTime = addressFileInfo.ModTime()
|
||||
o.Data.ContentLength = addressFileInfo.Size()
|
||||
o.Data.ETag = fmt.Sprintf("%x-%x", addressFileInfo.ModTime(), addressFileInfo.Size())
|
||||
|
|
65
kernel/model/dav.go
Normal file
65
kernel/model/dav.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/emersion/go-webdav/caldav"
|
||||
"github.com/emersion/go-webdav/carddav"
|
||||
"github.com/siyuan-note/logging"
|
||||
"github.com/siyuan-note/siyuan/kernel/util"
|
||||
)
|
||||
|
||||
// PathJoinWithSlash joins the elements to a path with slash ('/') character
|
||||
func PathJoinWithSlash(elems ...string) string {
|
||||
return filepath.ToSlash(filepath.Join(elems...))
|
||||
}
|
||||
|
||||
// PathCleanWithSlash cleans the path
|
||||
func PathCleanWithSlash(p string) string {
|
||||
return filepath.ToSlash(filepath.Clean(p))
|
||||
}
|
||||
|
||||
// DavPath2DirectoryPath converts CalDAV/CardDAV path to absolute path of the file system
|
||||
func DavPath2DirectoryPath(davPath string) string {
|
||||
return PathJoinWithSlash(util.DataDir, "storage", davPath)
|
||||
}
|
||||
|
||||
func SaveMetaData[T []*caldav.Calendar | []*carddav.AddressBook](metaData T, metaDataFilePath string) error {
|
||||
data, err := gulu.JSON.MarshalIndentJSON(metaData, "", " ")
|
||||
if err != nil {
|
||||
logging.LogErrorf("marshal address books meta data failed: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
dirPath := path.Dir(metaDataFilePath)
|
||||
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
||||
logging.LogErrorf("create directory [%s] failed: %s", dirPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(metaDataFilePath, data, 0755); err != nil {
|
||||
logging.LogErrorf("write file [%s] failed: %s", metaDataFilePath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Reference in a new issue