mirror of
https://github.com/1f349/go-webdav.git
synced 2024-12-23 00:34:23 +00:00
carddav: PUT support
This commit is contained in:
parent
64f8396654
commit
2a7d999100
@ -21,12 +21,14 @@ type AddressBookInfo struct {
|
|||||||
|
|
||||||
type AddressObject interface {
|
type AddressObject interface {
|
||||||
ID() string
|
ID() string
|
||||||
Card() (vcard.Card, error)
|
|
||||||
Stat() (os.FileInfo, error) // can return nil, nil
|
Stat() (os.FileInfo, error) // can return nil, nil
|
||||||
|
Card() (vcard.Card, error)
|
||||||
|
SetCard(vcard.Card) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddressBook interface {
|
type AddressBook interface {
|
||||||
Info() (*AddressBookInfo, error)
|
Info() (*AddressBookInfo, error)
|
||||||
GetAddressObject(id string) (AddressObject, error)
|
GetAddressObject(id string) (AddressObject, error)
|
||||||
ListAddressObjects() ([]AddressObject, error)
|
ListAddressObjects() ([]AddressObject, error)
|
||||||
|
CreateAddressObject(vcard.Card) (AddressObject, error)
|
||||||
}
|
}
|
||||||
|
135
carddav/fs.go
135
carddav/fs.go
@ -38,6 +38,10 @@ var (
|
|||||||
addressBookHomeSet = xml.Name{Space: nsCardDAV, Local: "addressbook-home-set"}
|
addressBookHomeSet = xml.Name{Space: nsCardDAV, Local: "addressbook-home-set"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func addressObjectName(ao AddressObject) string {
|
||||||
|
return ao.ID() + ".vcf"
|
||||||
|
}
|
||||||
|
|
||||||
type fileInfo struct {
|
type fileInfo struct {
|
||||||
name string
|
name string
|
||||||
size int64
|
size int64
|
||||||
@ -45,6 +49,13 @@ type fileInfo struct {
|
|||||||
modTime time.Time
|
modTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addressObjectFileInfo(ao AddressObject) *fileInfo {
|
||||||
|
return &fileInfo{
|
||||||
|
name: addressObjectName(ao),
|
||||||
|
mode: os.ModePerm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (fi *fileInfo) Name() string {
|
func (fi *fileInfo) Name() string {
|
||||||
return fi.name
|
return fi.name
|
||||||
}
|
}
|
||||||
@ -70,18 +81,34 @@ func (fi *fileInfo) Sys() interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type file struct {
|
type file struct {
|
||||||
*bytes.Reader
|
r *bytes.Reader
|
||||||
|
w *bytes.Buffer
|
||||||
fs *fileSystem
|
fs *fileSystem
|
||||||
name string
|
|
||||||
ao AddressObject
|
ao AddressObject
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) Close() error {
|
func (f *file) Close() error {
|
||||||
|
if f.w != nil {
|
||||||
|
defer func() {
|
||||||
|
f.w = nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
card, err := vcard.NewDecoder(f.w).Decode()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.ao.SetCard(card); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f.r = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) Read(b []byte) (int, error) {
|
func (f *file) Read(b []byte) (int, error) {
|
||||||
if f.Reader == nil {
|
if f.r == nil {
|
||||||
card, err := f.ao.Card()
|
card, err := f.ao.Card()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@ -92,24 +119,27 @@ func (f *file) Read(b []byte) (int, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Reader = bytes.NewReader(b.Bytes())
|
f.r = bytes.NewReader(b.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.Reader.Read(b)
|
return f.r.Read(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) Write(b []byte) (int, error) {
|
func (f *file) Write(b []byte) (int, error) {
|
||||||
return 0, errUnsupported
|
if f.w == nil {
|
||||||
|
f.w = &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
return f.w.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) Seek(offset int64, whence int) (int64, error) {
|
func (f *file) Seek(offset int64, whence int) (int64, error) {
|
||||||
if f.Reader == nil {
|
if f.r == nil {
|
||||||
if _, err := f.Read(nil); err != nil {
|
if _, err := f.Read(nil); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.Reader.Seek(offset, whence)
|
return f.r.Seek(offset, whence)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) Readdir(count int) ([]os.FileInfo, error) {
|
func (f *file) Readdir(count int) ([]os.FileInfo, error) {
|
||||||
@ -121,15 +151,71 @@ func (f *file) Stat() (os.FileInfo, error) {
|
|||||||
if info != nil || err != nil {
|
if info != nil || err != nil {
|
||||||
return info, err
|
return info, err
|
||||||
}
|
}
|
||||||
|
return addressObjectFileInfo(f.ao), nil
|
||||||
return &fileInfo{
|
|
||||||
name: f.name,
|
|
||||||
mode: os.ModePerm,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: getcontenttype for file
|
// TODO: getcontenttype for file
|
||||||
|
|
||||||
|
type newFile struct {
|
||||||
|
buf bytes.Buffer
|
||||||
|
fs *fileSystem
|
||||||
|
ao AddressObject
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *newFile) Close() error {
|
||||||
|
if f.ao == nil {
|
||||||
|
defer f.buf.Reset()
|
||||||
|
|
||||||
|
card, err := vcard.NewDecoder(&f.buf).Decode()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ao, err := f.fs.ab.CreateAddressObject(card)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.ao = ao
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *newFile) Read(b []byte) (int, error) {
|
||||||
|
return 0, errUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *newFile) Write(b []byte) (int, error) {
|
||||||
|
// TODO: limit amount of data in f.buf
|
||||||
|
if f.ao != nil {
|
||||||
|
return 0, errUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.buf.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *newFile) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
return 0, errUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *newFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||||
|
return nil, errUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *newFile) Stat() (os.FileInfo, error) {
|
||||||
|
// Only available after a successful call to Close
|
||||||
|
if f.ao == nil {
|
||||||
|
return nil, errUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := f.ao.Stat()
|
||||||
|
if info != nil || err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
return addressObjectFileInfo(f.ao), nil
|
||||||
|
}
|
||||||
|
|
||||||
type dir struct {
|
type dir struct {
|
||||||
fs *fileSystem
|
fs *fileSystem
|
||||||
name string
|
name string
|
||||||
@ -163,18 +249,7 @@ func (d *dir) Readdir(count int) ([]os.FileInfo, error) {
|
|||||||
|
|
||||||
d.files = make([]os.FileInfo, len(aos))
|
d.files = make([]os.FileInfo, len(aos))
|
||||||
for i, ao := range aos {
|
for i, ao := range aos {
|
||||||
f := &file{
|
d.files[i] = addressObjectFileInfo(ao)
|
||||||
fs: d.fs,
|
|
||||||
name: ao.ID() + ".vcf",
|
|
||||||
ao: ao,
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.files[i] = info
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,13 +337,14 @@ func (fs *fileSystem) OpenFile(ctx context.Context, name string, flag int, perm
|
|||||||
|
|
||||||
id := fs.addressObjectID(name)
|
id := fs.addressObjectID(name)
|
||||||
ao, err := fs.ab.GetAddressObject(id)
|
ao, err := fs.ab.GetAddressObject(id)
|
||||||
if err != nil {
|
if err == ErrNotFound && flag & os.O_CREATE != 0 {
|
||||||
|
return &newFile{fs: fs}, nil
|
||||||
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &file{
|
return &file{
|
||||||
fs: fs,
|
fs: fs,
|
||||||
name: name,
|
|
||||||
ao: ao,
|
ao: ao,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -300,8 +376,5 @@ func (fs *fileSystem) Stat(ctx context.Context, name string) (os.FileInfo, error
|
|||||||
return info, err
|
return info, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &fileInfo{
|
return addressObjectFileInfo(ao), nil
|
||||||
name: name,
|
|
||||||
mode: os.ModePerm,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
15
webdav.go
15
webdav.go
@ -282,24 +282,31 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, copyErr := io.Copy(f, r.Body)
|
_, copyErr := io.Copy(f, r.Body)
|
||||||
fi, statErr := f.Stat()
|
|
||||||
closeErr := f.Close()
|
closeErr := f.Close()
|
||||||
|
fi, statErr := f.Stat()
|
||||||
// TODO(rost): Returning 405 Method Not Allowed might not be appropriate.
|
// TODO(rost): Returning 405 Method Not Allowed might not be appropriate.
|
||||||
if copyErr != nil {
|
if copyErr != nil {
|
||||||
return http.StatusMethodNotAllowed, copyErr
|
return http.StatusMethodNotAllowed, copyErr
|
||||||
}
|
}
|
||||||
if statErr != nil {
|
|
||||||
return http.StatusMethodNotAllowed, statErr
|
|
||||||
}
|
|
||||||
if closeErr != nil {
|
if closeErr != nil {
|
||||||
return http.StatusMethodNotAllowed, closeErr
|
return http.StatusMethodNotAllowed, closeErr
|
||||||
}
|
}
|
||||||
|
if statErr != nil {
|
||||||
|
return http.StatusMethodNotAllowed, statErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.Base(reqPath) != fi.Name() {
|
||||||
|
w.Header().Set("Location", path.Join(path.Dir(reqPath), fi.Name()))
|
||||||
|
}
|
||||||
|
|
||||||
etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi)
|
etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
w.Header().Set("ETag", etag)
|
w.Header().Set("ETag", etag)
|
||||||
|
|
||||||
return http.StatusCreated, nil
|
return http.StatusCreated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user