Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
26 changes: 21 additions & 5 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 @@ -117,6 +117,12 @@ func transfer(ctx context.Context, taskType taskType, srcObjPath, dstDirPath str
if taskType == copy || taskType == merge {
err = op.Copy(ctx, srcStorage, srcObjActualPath, dstDirActualPath)
if !errors.Is(err, errs.NotImplement) && !errors.Is(err, errs.NotSupport) {
if err == nil && dstName != "" {
srcObjName := stdpath.Base(srcObjActualPath)
if srcObjName != dstName {
err = op.Rename(ctx, srcStorage, stdpath.Join(dstDirActualPath, srcObjName), dstName)
}
}
return nil, err
}
} else {
Expand All @@ -134,6 +140,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 +196,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 +262,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"`
}
2 changes: 1 addition & 1 deletion internal/op/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ func Copy(ctx context.Context, storage driver.Driver, srcPath, dstDirPath string
srcPath = utils.FixAndCleanPath(srcPath)
dstDirPath = utils.FixAndCleanPath(dstDirPath)
if dstDirPath == stdpath.Dir(srcPath) {
return errors.New("copy in place")
return errors.WithStack(errs.NotImplement)
}
srcRawObj, err := Get(ctx, storage, srcPath, true)
if err != nil {
Expand Down
60 changes: 53 additions & 7 deletions server/webdav/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,43 @@ 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 dstDirInfo, err := fs.Get(ctx, dstDir, &fs.GetArgs{}); err != nil {
if errs.IsObjectNotFound(err) {
return http.StatusConflict, err
}
return http.StatusMethodNotAllowed, err
} else if !dstDirInfo.IsDir() {
return http.StatusConflict, nil
}
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 +102,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 +121,34 @@ 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 dstDirInfo, err := fs.Get(ctx, dstDir, &fs.GetArgs{}); err != nil {
if errs.IsObjectNotFound(err) {
return http.StatusConflict, err
}
return http.StatusMethodNotAllowed, err
} else if !dstDirInfo.IsDir() {
return http.StatusConflict, nil
}
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