Bläddra i källkod

:art: Implement CalDAV calendar object manage functions

颖逸 8 månader sedan
förälder
incheckning
8e9db218b6
2 ändrade filer med 382 tillägg och 27 borttagningar
  1. 332 13
      kernel/model/caldav.go
  2. 50 14
      kernel/model/carddav.go

+ 332 - 13
kernel/model/caldav.go

@@ -23,6 +23,7 @@ import (
 	"fmt"
 	"os"
 	"path"
+	"strings"
 	"sync"
 
 	"github.com/88250/gulu"
@@ -90,6 +91,31 @@ func CalendarsMetaDataFilePath() string {
 	return DavPath2DirectoryPath(CalDavCalendarsMetaDataFilePath)
 }
 
+func GetCalDavPathDepth(urlPath string) CalDavPathDepth {
+	urlPath = PathCleanWithSlash(urlPath)
+	return CalDavPathDepth(len(strings.Split(urlPath, "/")) - 1)
+}
+
+// GetCardDavPathDepth parses
+func ParseCalendarObjectPath(objectPath string) (calendarPath string, objectID string, err error) {
+	calendarPath, objectFileName := path.Split(objectPath)
+	calendarPath = PathCleanWithSlash(calendarPath)
+	objectID = path.Base(objectFileName)
+	objectFileExt := path.Ext(objectFileName)
+
+	if GetCalDavPathDepth(calendarPath) != calDavPathDepth_Calendar {
+		err = ErrorCalDavCalendarPathInvalid
+		return
+	}
+
+	if objectFileExt != ICalendarFileExt {
+		err = ErrorCalDavCalendarObjectPathInvalid
+		return
+	}
+
+	return
+}
+
 // LoadCalendarObject loads a iCalendar file (*.ics)
 func LoadCalendarObject(filePath string) (calendar *ical.Calendar, err error) {
 	data, err := os.ReadFile(filePath)
@@ -156,6 +182,32 @@ func (c *Calendars) load() error {
 }
 
 // save all calendars
+func (c *Calendars) save(force bool) error {
+	if force || c.changed {
+		// save calendars meta data
+		if err := c.saveCalendarsMetaData(); err != nil {
+			return err
+		}
+
+		// save all calendar object to *.ics files
+		wg := &sync.WaitGroup{}
+		c.calendars.Range(func(path any, calendar any) bool {
+			wg.Add(1)
+			go func() {
+				defer wg.Done()
+				// path_ := path.(string)
+				calendar := calendar.(*Calendar)
+				calendar.save(force)
+			}()
+			return true
+		})
+		wg.Wait()
+		c.changed = false
+	}
+	return nil
+}
+
+// save all calendars meta data
 func (c *Calendars) saveCalendarsMetaData() error {
 	return SaveMetaData(c.calendarsMetaData, CalendarsMetaDataFilePath())
 }
@@ -170,6 +222,59 @@ func (c *Calendars) Load() error {
 	return nil
 }
 
+func (c *Calendars) GetObject(objectPath string) (calendar *Calendar, calendarObject *CalendarObject, err error) {
+	calendarPath, objectID, err := ParseCalendarObjectPath(objectPath)
+	if err != nil {
+		logging.LogErrorf("parse calendar object path [%s] failed: %s", objectPath, err)
+		return
+	}
+
+	if value, ok := c.calendars.Load(calendarPath); ok {
+		calendar = value.(*Calendar)
+	} else {
+		err = ErrorCalDavCalendarNotFound
+		return
+	}
+
+	if value, ok := calendar.Objects.Load(objectID); ok {
+		calendarObject = value.(*CalendarObject)
+	} else {
+		err = ErrorCalDavCalendarObjectNotFound
+		return
+	}
+
+	return
+}
+
+func (c *Calendars) DeleteObject(objectPath string) (calendar *Calendar, calendarObject *CalendarObject, err error) {
+	calendarPath, objectID, err := ParseCalendarObjectPath(objectPath)
+	if err != nil {
+		logging.LogErrorf("parse calendar object path [%s] failed: %s", objectPath, err)
+		return
+	}
+
+	if value, ok := c.calendars.Load(calendarPath); ok {
+		calendar = value.(*Calendar)
+	} else {
+		err = ErrorCalDavCalendarNotFound
+		return
+	}
+
+	if value, loaded := calendar.Objects.LoadAndDelete(objectID); loaded {
+		calendarObject = value.(*CalendarObject)
+	} else {
+		err = ErrorCalDavCalendarObjectNotFound
+		return
+	}
+
+	if err = os.Remove(calendarObject.FilePath); err != nil {
+		logging.LogErrorf("remove file [%s] failed: %s", calendarObject.FilePath, err)
+		return
+	}
+
+	return
+}
+
 func (c *Calendars) CreateCalendar(calendarMetaData *caldav.Calendar) (err error) {
 	c.lock.Lock()
 	defer c.lock.Unlock()
@@ -278,6 +383,121 @@ func (c *Calendars) DeleteCalendar(calendarPath string) (err error) {
 	return nil
 }
 
+func (c *Calendars) PutCalendarObject(objectPath string, calendarData *ical.Calendar, opts *caldav.PutCalendarObjectOptions) (calendarObject *caldav.CalendarObject, err error) {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	calendarPath, objectID, err := ParseCalendarObjectPath(objectPath)
+	if err != nil {
+		logging.LogErrorf("parse calendar object path [%s] failed: %s", objectPath, err)
+		return
+	}
+
+	var calendar *Calendar
+	if value, ok := c.calendars.Load(calendarPath); ok {
+		calendar = value.(*Calendar)
+	} else {
+		err = ErrorCalDavCalendarNotFound
+		return
+	}
+
+	// TODO: 处理 opts.IfNoneMatch (If-None-Match) 与 opts.IfMatch (If-Match)
+
+	var object *CalendarObject
+	if value, ok := calendar.Objects.Load(objectID); ok {
+		object = value.(*CalendarObject)
+		object.Data.Data = calendarData
+		object.Changed = true
+	} else {
+		object = &CalendarObject{
+			Changed:      true,
+			FilePath:     DavPath2DirectoryPath(objectPath),
+			CalendarPath: calendarPath,
+			Data: &caldav.CalendarObject{
+				Data: calendarData,
+			},
+		}
+	}
+
+	err = object.save(true)
+	if err != nil {
+		return
+	}
+
+	err = object.update()
+	if err != nil {
+		return
+	}
+
+	calendar.Objects.Store(objectID, object)
+	calendarObject = object.Data
+	return
+}
+
+func (c *Calendars) ListCalendarObjects(calendarPath string, req *caldav.CalendarCompRequest) (calendarObjects []caldav.CalendarObject, err error) {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	var calendar *Calendar
+	if value, ok := c.calendars.Load(calendarPath); ok {
+		calendar = value.(*Calendar)
+	} else {
+		err = ErrorCalDavCalendarNotFound
+		return
+	}
+
+	calendar.Objects.Range(func(id any, object any) bool {
+		// TODO: filter calendar objects' props and comps
+		calendarObjects = append(calendarObjects, *object.(*CalendarObject).Data)
+		return true
+	})
+
+	return
+}
+
+func (c *Calendars) GetCalendarObject(objectPath string, req *caldav.CalendarCompRequest) (calendarObject *caldav.CalendarObject, err error) {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	_, object, err := c.GetObject(objectPath)
+	if err != nil {
+		return
+	}
+
+	calendarObject = object.Data
+	// TODO: filter calendar object's props and comps
+	return
+}
+
+func (c *Calendars) QueryCalendarObjects(calendarPath string, query *caldav.CalendarQuery) (calendarObjects []caldav.CalendarObject, err error) {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	calendarObjects, err = c.ListCalendarObjects(calendarPath, &query.CompRequest)
+	if err != nil {
+		return
+	}
+
+	calendarObjects, err = caldav.Filter(query, calendarObjects)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+func (c *Calendars) DeleteCalendarObject(objectPath string) (err error) {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	_, _, err = c.DeleteObject(objectPath)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
 type Calendar struct {
 	Changed       bool
 	DirectoryPath string
@@ -329,6 +549,34 @@ func (c *Calendar) load() error {
 	return nil
 }
 
+// save an calendar to multiple *.ics files
+func (c *Calendar) save(force bool) error {
+	if force || c.Changed {
+		// create directory
+		if err := os.MkdirAll(c.DirectoryPath, 0755); err != nil {
+			logging.LogErrorf("create directory [%s] failed: %s", c.DirectoryPath, err)
+			return err
+		}
+
+		wg := &sync.WaitGroup{}
+		c.Objects.Range(func(id any, object any) bool {
+			wg.Add(1)
+			go func() {
+				defer wg.Done()
+				// id_ := id.(string)
+				object_ := object.(*CalendarObject)
+				object_.save(force)
+				object_.update()
+			}()
+			return true
+		})
+		wg.Wait()
+		c.Changed = false
+	}
+
+	return nil
+}
+
 type CalendarObject struct {
 	Changed      bool
 	FilePath     string
@@ -358,6 +606,36 @@ func (o *CalendarObject) load() error {
 	return nil
 }
 
+// save an object to *.ics file
+func (o *CalendarObject) save(force bool) error {
+	if force || o.Changed {
+		var objectData bytes.Buffer
+
+		// encode data
+		encoder := ical.NewEncoder(&objectData)
+		if err := encoder.Encode(o.Data.Data); err != nil {
+			logging.LogErrorf("encode iCalendar [%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, objectData.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 *CalendarObject) update() error {
 	addressFileInfo, err := os.Stat(o.FilePath)
@@ -388,6 +666,8 @@ 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)
+	calendar.Path = PathCleanWithSlash(calendar.Path)
+
 	if err = calendars.Load(); err != nil {
 		return
 	}
@@ -410,6 +690,8 @@ func (b *CalDavBackend) ListCalendars(ctx context.Context) (calendars_ []caldav.
 
 func (b *CalDavBackend) GetCalendar(ctx context.Context, calendarPath string) (calendar *caldav.Calendar, err error) {
 	logging.LogDebugf("CalDAV GetCalendar -> calendarPath: %s", calendarPath)
+	calendarPath = PathCleanWithSlash(calendarPath)
+
 	if err = calendars.Load(); err != nil {
 		return
 	}
@@ -421,6 +703,8 @@ func (b *CalDavBackend) GetCalendar(ctx context.Context, calendarPath string) (c
 
 func (b *CalDavBackend) DeleteCalendar(ctx context.Context, calendarPath string) (err error) {
 	logging.LogDebugf("CalDAV DeleteCalendar -> calendarPath: %s", calendarPath)
+	calendarPath = PathCleanWithSlash(calendarPath)
+
 	if err = calendars.Load(); err != nil {
 		return
 	}
@@ -430,32 +714,67 @@ func (b *CalDavBackend) DeleteCalendar(ctx context.Context, calendarPath string)
 	return
 }
 
-func (b *CalDavBackend) GetCalendarObject(ctx context.Context, calendarPath string, req *caldav.CalendarCompRequest) (calendarObjects *caldav.CalendarObject, err error) {
-	logging.LogDebugf("CalDAV GetCalendarObject -> calendarPath: %s, req: %#v", calendarPath, req)
-	// TODO: get calendar object
+func (b *CalDavBackend) PutCalendarObject(ctx context.Context, objectPath string, calendar *ical.Calendar, opts *caldav.PutCalendarObjectOptions) (calendarObject *caldav.CalendarObject, err error) {
+	logging.LogDebugf("CalDAV PutCalendarObject -> objectPath: %s, opts: %#v", objectPath, opts)
+	objectPath = PathCleanWithSlash(objectPath)
+
+	if err = calendars.Load(); err != nil {
+		return
+	}
+
+	calendarObject, err = calendars.PutCalendarObject(objectPath, calendar, opts)
+	logging.LogDebugf("CalDAV PutCalendarObject <- calendarObject: %#v, err: %s", calendarObject, err)
 	return
 }
 
 func (b *CalDavBackend) ListCalendarObjects(ctx context.Context, calendarPath string, req *caldav.CalendarCompRequest) (calendarObjects []caldav.CalendarObject, err error) {
 	logging.LogDebugf("CalDAV ListCalendarObjects -> calendarPath: %s, req: %#v", calendarPath, req)
-	// TODO: list calendar objects
+	calendarPath = PathCleanWithSlash(calendarPath)
+
+	if err = calendars.Load(); err != nil {
+		return
+	}
+
+	calendarObjects, err = calendars.ListCalendarObjects(calendarPath, req)
+	logging.LogDebugf("CalDAV ListCalendarObjects <- calendarObjects: %#v, err: %s", calendarObjects, err)
 	return
 }
 
-func (b *CalDavBackend) QueryCalendarObjects(ctx context.Context, calendarPath string, query *caldav.CalendarQuery) (calendarObjects []caldav.CalendarObject, err error) {
-	logging.LogDebugf("CalDAV QueryCalendarObjects -> calendarPath: %s, query: %#v", calendarPath, query)
-	// TODO: query calendar objects
+func (b *CalDavBackend) GetCalendarObject(ctx context.Context, objectPath string, req *caldav.CalendarCompRequest) (calendarObject *caldav.CalendarObject, err error) {
+	logging.LogDebugf("CalDAV GetCalendarObject -> objectPath: %s, req: %#v", objectPath, req)
+	objectPath = PathCleanWithSlash(objectPath)
+
+	if err = calendars.Load(); err != nil {
+		return
+	}
+
+	calendarObject, err = calendars.GetCalendarObject(objectPath, req)
+	logging.LogDebugf("CalDAV GetCalendarObject <- calendarObject: %#v, err: %s", calendarObject, err)
 	return
 }
 
-func (b *CalDavBackend) PutCalendarObject(ctx context.Context, calendarPath string, calendar *ical.Calendar, opts *caldav.PutCalendarObjectOptions) (calendarObject *caldav.CalendarObject, err error) {
-	logging.LogDebugf("CalDAV PutCalendarObject -> calendarPath: %s, opts: %#v", calendarPath, opts)
-	// TODO: put calendar object
+func (b *CalDavBackend) QueryCalendarObjects(ctx context.Context, calendarPath string, query *caldav.CalendarQuery) (calendarObjects []caldav.CalendarObject, err error) {
+	logging.LogDebugf("CalDAV QueryCalendarObjects -> calendarPath: %s, query: %#v", calendarPath, query)
+	calendarPath = PathCleanWithSlash(calendarPath)
+
+	if err = calendars.Load(); err != nil {
+		return
+	}
+
+	calendarObjects, err = calendars.QueryCalendarObjects(calendarPath, query)
+	logging.LogDebugf("CalDAV QueryCalendarObjects <- calendarObjects: %#v, err: %s", calendarObjects, err)
 	return
 }
 
-func (b *CalDavBackend) DeleteCalendarObject(ctx context.Context, calendarPath string) (err error) {
-	logging.LogDebugf("CalDAV DeleteCalendarObject -> calendarPath: %s", calendarPath)
-	// TODO: delete calendar object
+func (b *CalDavBackend) DeleteCalendarObject(ctx context.Context, objectPath string) (err error) {
+	logging.LogDebugf("CalDAV DeleteCalendarObject -> objectPath: %s", objectPath)
+	objectPath = PathCleanWithSlash(objectPath)
+
+	if err = calendars.Load(); err != nil {
+		return
+	}
+
+	err = calendars.DeleteCalendarObject(objectPath)
+	logging.LogDebugf("CalDAV DeleteCalendarObject <- err: %s", err)
 	return
 }

+ 50 - 14
kernel/model/carddav.go

@@ -120,7 +120,7 @@ func AddressBooksMetaDataFilePath() string {
 }
 
 func GetCardDavPathDepth(urlPath string) CardDavPathDepth {
-	urlPath = path.Clean(urlPath)
+	urlPath = PathCleanWithSlash(urlPath)
 	return CardDavPathDepth(len(strings.Split(urlPath, "/")) - 1)
 }
 
@@ -312,6 +312,35 @@ func (c *Contacts) GetAddress(addressPath string) (addressBook *AddressBook, add
 	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()
@@ -506,15 +535,11 @@ func (c *Contacts) PutAddressObject(addressPath string, card vcard.Card, opts *c
 		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)
-
-		if opts.IfNoneMatch.IsSet() {
-			addressObject = address.Data
-			return
-		}
-
 		address.Data.Card = card
 		address.Changed = true
 	} else {
@@ -547,13 +572,8 @@ func (c *Contacts) DeleteAddressObject(addressPath string) (err error) {
 	c.lock.Lock()
 	defer c.lock.Unlock()
 
-	_, address, err := c.GetAddress(addressPath)
-	if err != nil && err != ErrorCardDavAddressNotFound {
-		return
-	}
-
-	if err = os.Remove(address.FilePath); err != nil {
-		logging.LogErrorf("remove file [%s] failed: %s", address.FilePath, err)
+	_, _, err = c.DeleteAddress(addressPath)
+	if err != nil {
 		return
 	}
 
@@ -787,6 +807,8 @@ func (b *CardDavBackend) ListAddressBooks(ctx context.Context) (addressBooks []c
 
 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
 	}
@@ -798,6 +820,8 @@ func (b *CardDavBackend) GetAddressBook(ctx context.Context, bookPath string) (a
 
 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
 	}
@@ -809,6 +833,8 @@ func (b *CardDavBackend) CreateAddressBook(ctx context.Context, addressBook *car
 
 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
 	}
@@ -820,6 +846,8 @@ func (b *CardDavBackend) DeleteAddressBook(ctx context.Context, bookPath string)
 
 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
 	}
@@ -831,6 +859,8 @@ func (b *CardDavBackend) GetAddressObject(ctx context.Context, addressPath strin
 
 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
 	}
@@ -842,6 +872,8 @@ func (b *CardDavBackend) ListAddressObjects(ctx context.Context, bookPath string
 
 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
 	}
@@ -853,6 +885,8 @@ func (b *CardDavBackend) QueryAddressObjects(ctx context.Context, urlPath string
 
 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
 	}
@@ -864,6 +898,8 @@ func (b *CardDavBackend) PutAddressObject(ctx context.Context, addressPath strin
 
 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
 	}