Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var db *gorm.DB

func Init(d *gorm.DB) {
db = d
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode), new(model.TaskItem), new(model.SSHPublicKey), new(model.SharingDB))
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode), new(model.TaskItem), new(model.SSHPublicKey), new(model.SharingDB), new(model.WebDAVProperty))
if err != nil {
log.Fatalf("failed migrate database: %s", err.Error())
}
Expand Down
42 changes: 28 additions & 14 deletions internal/fs/copy_move.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func (t *FileTransferTask) SetRetry(retry int, maxRetry int) {
}
}

func transfer(ctx context.Context, taskType taskType, srcObjPath, dstDirPath string, skipHook ...bool) (task.TaskExtensionInfo, error) {
func transfer(ctx context.Context, taskType taskType, srcObjPath, dstDirPath, dstName string, skipHook ...bool) (task.TaskExtensionInfo, error) {
srcStorage, srcObjActualPath, err := op.GetStorageAndActualPath(srcObjPath)
if err != nil {
return nil, errors.WithMessage(err, "failed get src storage")
Expand All @@ -114,15 +114,19 @@ func transfer(ctx context.Context, taskType taskType, srcObjPath, dstDirPath str
if utils.IsBool(skipHook...) {
ctx = context.WithValue(ctx, conf.SkipHookKey, struct{}{})
}
if taskType == copy || taskType == merge {
err = op.Copy(ctx, srcStorage, srcObjActualPath, dstDirActualPath)
if !errors.Is(err, errs.NotImplement) && !errors.Is(err, errs.NotSupport) {
return nil, err
}
} else {
err = op.Move(ctx, srcStorage, srcObjActualPath, dstDirActualPath)
if !errors.Is(err, errs.NotImplement) && !errors.Is(err, errs.NotSupport) {
return nil, err
// A named copy cannot use the driver's destination-name-independent Copy
// operation. Fall back to the transfer task so the target name is kept.
if dstName == "" {
if taskType == copy || taskType == merge {
err = op.Copy(ctx, srcStorage, srcObjActualPath, dstDirActualPath)
if !errors.Is(err, errs.NotImplement) && !errors.Is(err, errs.NotSupport) {
return nil, err
}
} else {
err = op.Move(ctx, srcStorage, srcObjActualPath, dstDirActualPath)
if !errors.Is(err, errs.NotImplement) && !errors.Is(err, errs.NotSupport) {
return nil, err
}
}
}
}
Expand All @@ -134,6 +138,7 @@ func transfer(ctx context.Context, taskType taskType, srcObjPath, dstDirPath str
DstStorage: dstStorage,
SrcActualPath: srcObjActualPath,
DstActualPath: dstDirActualPath,
DstName: dstName,
SrcStorageMp: srcStorage.GetStorage().MountPath,
DstStorageMp: dstStorage.GetStorage().MountPath,
},
Expand Down Expand Up @@ -189,9 +194,14 @@ func (t *FileTransferTask) RunWithNextTaskCallback(f func(nextTask *FileTransfer
if err != nil {
return errors.WithMessagef(err, "failed list src [%s] objs", t.SrcActualPath)
}
dstActualPath := stdpath.Join(t.DstActualPath, srcObj.GetName())
task_group.TransferCoordinator.AppendPayload(t.groupID, task_group.DstPathToHook(dstActualPath))

dstName := srcObj.GetName()
if t.DstName != "" {
dstName = t.DstName
}
dstActualPath := stdpath.Join(t.DstActualPath, dstName)
if err := op.MakeDir(t.Ctx(), t.DstStorage, dstActualPath); err != nil {
return errors.WithMessagef(err, "failed create dst dir [%s]", dstActualPath)
}
existedObjs := make(map[string]bool)
if t.TaskType == merge {
dstObjs, err := op.List(t.Ctx(), t.DstStorage, dstActualPath, model.ListArgs{})
Expand Down Expand Up @@ -250,8 +260,12 @@ func (t *FileTransferTask) RunWithNextTaskCallback(f func(nextTask *FileTransfer
return errors.WithMessagef(err, "failed get [%s] link", t.SrcActualPath)
}
// any link provided is seekable
streamObj := srcObj
if t.DstName != "" {
streamObj = &model.ObjWrapName{Name: t.DstName, Obj: srcObj}
}
ss, err := stream.NewSeekableStream(&stream.FileStream{
Obj: srcObj,
Obj: streamObj,
Ctx: t.Ctx(),
}, link)
if err != nil {
Expand Down
15 changes: 12 additions & 3 deletions internal/fs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,32 @@ func MakeDir(ctx context.Context, path string) error {
}

func Move(ctx context.Context, srcPath, dstDirPath string, skipHook ...bool) (task.TaskExtensionInfo, error) {
req, err := transfer(ctx, move, srcPath, dstDirPath, skipHook...)
req, err := transfer(ctx, move, srcPath, dstDirPath, "", skipHook...)
if err != nil {
log.Errorf("failed move %s to %s: %+v", srcPath, dstDirPath, err)
}
return req, err
}

func Copy(ctx context.Context, srcObjPath, dstDirPath string, skipHook ...bool) (task.TaskExtensionInfo, error) {
res, err := transfer(ctx, copy, srcObjPath, dstDirPath, skipHook...)
res, err := transfer(ctx, copy, srcObjPath, dstDirPath, "", skipHook...)
if err != nil {
log.Errorf("failed copy %s to %s: %+v", srcObjPath, dstDirPath, err)
}
return res, err
}

// CopyTo copies a file or directory to dstDirPath using dstName as its name.
func CopyTo(ctx context.Context, srcObjPath, dstDirPath, dstName string, skipHook ...bool) (task.TaskExtensionInfo, error) {
res, err := transfer(ctx, copy, srcObjPath, dstDirPath, dstName, skipHook...)
if err != nil {
log.Errorf("failed copy %s to %s as %s: %+v", srcObjPath, dstDirPath, dstName, err)
}
return res, err
}

func Merge(ctx context.Context, srcObjPath, dstDirPath string, skipHook ...bool) (task.TaskExtensionInfo, error) {
res, err := transfer(ctx, merge, srcObjPath, dstDirPath, skipHook...)
res, err := transfer(ctx, merge, srcObjPath, dstDirPath, "", skipHook...)
if err != nil {
log.Errorf("failed merge %s to %s: %+v", srcObjPath, dstDirPath, err)
}
Expand Down
1 change: 1 addition & 0 deletions internal/fs/other.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type TaskData struct {
Status string `json:"-"` //don't save status to save space
SrcActualPath string `json:"src_path"`
DstActualPath string `json:"dst_path"`
DstName string `json:"dst_name,omitempty"`
SrcStorage driver.Driver `json:"-"`
DstStorage driver.Driver `json:"-"`
SrcStorageMp string `json:"src_storage_mp"`
Expand Down
11 changes: 11 additions & 0 deletions internal/model/webdav_property.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package model

// WebDAVProperty stores a dead WebDAV property for a resource.
type WebDAVProperty struct {
ID uint `json:"id" gorm:"primaryKey"`
Path string `json:"path" gorm:"uniqueIndex:idx_webdav_property"`
Namespace string `json:"namespace" gorm:"uniqueIndex:idx_webdav_property"`
Name string `json:"name" gorm:"uniqueIndex:idx_webdav_property"`
Lang string `json:"lang"`
InnerXML []byte `json:"inner_xml"`
}
56 changes: 49 additions & 7 deletions server/webdav/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,41 @@ func moveFiles(ctx context.Context, src, dst string, overwrite bool) (status int
if !common.CanWrite(user, srcMeta, srcDir) || !common.CanWrite(user, dstMeta, dstDir) {
return http.StatusForbidden, nil
}
if _, err = fs.Get(ctx, dstDir, &fs.GetArgs{}); err != nil {
if errs.IsObjectNotFound(err) {
return http.StatusConflict, err
}
return http.StatusMethodNotAllowed, err
}
Comment thread
devLythen marked this conversation as resolved.
Outdated
dstExisted := false
if _, err = fs.Get(ctx, dst, &fs.GetArgs{}); err == nil {
dstExisted = true
if !overwrite {
return http.StatusPreconditionFailed, nil
}
if err = fs.Remove(ctx, dst); err != nil {
return http.StatusInternalServerError, err
}
} else if !errs.IsObjectNotFound(err) {
return http.StatusInternalServerError, err
}
if srcDir == dstDir {
err = fs.Rename(ctx, src, dstName)
} else {
_, err = fs.Move(context.WithValue(ctx, conf.NoTaskKey, struct{}{}), src, dstDir)
if err != nil {
return http.StatusInternalServerError, err
}
if srcName != dstName {
if err == nil && srcName != dstName {
err = fs.Rename(ctx, path.Join(dstDir, srcName), dstName)
}
}
if err != nil {
return http.StatusInternalServerError, err
}
// TODO if there are no files copy, should return 204
if err = moveDeadProps(src, dst); err != nil {
return http.StatusInternalServerError, err
}
if dstExisted {
return http.StatusNoContent, nil
}
return http.StatusCreated, nil
}

Expand All @@ -80,6 +100,7 @@ func moveFiles(ctx context.Context, src, dst string, overwrite bool) (status int
func copyFiles(ctx context.Context, src, dst string, overwrite bool) (status int, err error) {
srcDir := path.Dir(src)
dstDir := path.Dir(dst)
dstName := path.Base(dst)
user := ctx.Value(conf.UserKey).(*model.User)
if !user.CanCopy() {
return http.StatusForbidden, nil
Expand All @@ -98,11 +119,32 @@ func copyFiles(ctx context.Context, src, dst string, overwrite bool) (status int
if !common.CanWrite(user, dstMeta, dstDir) {
return http.StatusForbidden, nil
}
_, err = fs.Copy(context.WithValue(ctx, conf.NoTaskKey, struct{}{}), src, dstDir)
if _, err = fs.Get(ctx, dstDir, &fs.GetArgs{}); err != nil {
if errs.IsObjectNotFound(err) {
return http.StatusConflict, err
}
return http.StatusMethodNotAllowed, err
}
dstExisted := false
if _, err = fs.Get(ctx, dst, &fs.GetArgs{}); err == nil {
dstExisted = true
if !overwrite {
return http.StatusPreconditionFailed, nil
}
if err = fs.Remove(ctx, dst); err != nil {
return http.StatusInternalServerError, err
}
} else if !errs.IsObjectNotFound(err) {
return http.StatusInternalServerError, err
}

_, err = fs.CopyTo(context.WithValue(ctx, conf.NoTaskKey, struct{}{}), src, dstDir, dstName)
if err != nil {
return http.StatusInternalServerError, err
}
// TODO if there are no files copy, should return 204
if dstExisted {
return http.StatusNoContent, nil
}
return http.StatusCreated, nil
}

Expand Down
40 changes: 31 additions & 9 deletions server/webdav/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ type LockDetails struct {
// ZeroDepth is whether the lock has zero depth. If it does not have zero
// depth, it has infinite depth.
ZeroDepth bool
// Shared is whether the lock may coexist with other shared locks on Root.
Shared bool
}

// NewMemLS returns a new in-memory LockSystem.
Expand Down Expand Up @@ -184,15 +186,10 @@ func (m *memLS) Confirm(now time.Time, name0, name1 string, conditions ...Condit
}, nil
}

// lookup returns the node n that locks the named resource, provided that n
// matches at least one of the given conditions and that lock isn't held by
// another party. Otherwise, it returns nil.
//
// n may be a parent of the named resource, if n is an infinite depth lock.
func (m *memLS) lookup(name string, conditions ...Condition) (n *memLSNode) {
func (m *memLS) lookup(name string, conditions ...Condition) *memLSNode {
// TODO: support Condition.Not and Condition.ETag.
for _, c := range conditions {
n = m.byToken[c.Token]
n := m.byToken[c.Token]
if n == nil || n.held {
continue
}
Expand Down Expand Up @@ -235,13 +232,24 @@ func (m *memLS) Create(now time.Time, details LockDetails) (string, error) {
m.collectExpiredNodes(now)
details.Root = slashClean(details.Root)

if details.Shared {
if n := m.byName[details.Root]; n != nil && n.token != "" && n.details.Shared && n.details.ZeroDepth == details.ZeroDepth && !n.held {
token := m.nextToken()
n.sharedTokens[token] = struct{}{}
m.byToken[token] = n
return token, nil
}
}
if !m.canCreate(details.Root, details.ZeroDepth) {
return "", ErrLocked
}
n := m.create(details.Root)
n.token = m.nextToken()
m.byToken[n.token] = n
n.details = details
if details.Shared {
n.sharedTokens = map[string]struct{}{n.token: {}}
}
if n.details.Duration >= 0 {
n.expiry = now.Add(n.details.Duration)
heap.Push(&m.byExpiry, n)
Expand Down Expand Up @@ -284,6 +292,13 @@ func (m *memLS) Unlock(now time.Time, token string) error {
if n.held {
return ErrLocked
}
if n.details.Shared {
delete(m.byToken, token)
delete(n.sharedTokens, token)
if len(n.sharedTokens) != 0 {
return nil
}
}
m.remove(n)
return nil
}
Expand Down Expand Up @@ -334,7 +349,13 @@ func (m *memLS) create(name string) (ret *memLSNode) {
}

func (m *memLS) remove(n *memLSNode) {
delete(m.byToken, n.token)
if n.details.Shared {
for token := range n.sharedTokens {
delete(m.byToken, token)
}
} else {
delete(m.byToken, n.token)
}
n.token = ""
walkToRoot(n.details.Root, func(name0 string, first bool) bool {
x := m.byName[name0]
Expand Down Expand Up @@ -380,7 +401,8 @@ type memLSNode struct {
// if this node does not expire, or has expired.
byExpiryIndex int
// held is whether this node's lock is actively held by a Confirm call.
held bool
held bool
sharedTokens map[string]struct{}
}

type byExpiry []*memLSNode
Expand Down
Loading