fix: cannot overwrite file to slave policy / fix: remove lock system for webdav to resolve Windows Explorer issue.

pull/1239/head
HFO4 2 years ago
parent f8b7e086ba
commit b1803fa51f

@ -30,7 +30,7 @@ const (
// Client to operate uploading to remote slave server // Client to operate uploading to remote slave server
type Client interface { type Client interface {
// CreateUploadSession creates remote upload session // CreateUploadSession creates remote upload session
CreateUploadSession(ctx context.Context, session *serializer.UploadSession, ttl int64) error CreateUploadSession(ctx context.Context, session *serializer.UploadSession, ttl int64, overwrite bool) error
// GetUploadURL signs an url for uploading file // GetUploadURL signs an url for uploading file
GetUploadURL(ttl int64, sessionID string) (string, string, error) GetUploadURL(ttl int64, sessionID string) (string, string, error)
// Upload uploads file to remote server // Upload uploads file to remote server
@ -82,12 +82,11 @@ func (c *remoteClient) Upload(ctx context.Context, file fsctx.FileHeader) error
} }
// Create upload session // Create upload session
if err := c.CreateUploadSession(ctx, session, int64(ttl)); err != nil { overwrite := fileInfo.Mode&fsctx.Overwrite == fsctx.Overwrite
if err := c.CreateUploadSession(ctx, session, int64(ttl), overwrite); err != nil {
return fmt.Errorf("failed to create upload session: %w", err) return fmt.Errorf("failed to create upload session: %w", err)
} }
overwrite := fileInfo.Mode&fsctx.Overwrite == fsctx.Overwrite
// Initial chunk groups // Initial chunk groups
chunks := chunk.NewChunkGroup(file, c.policy.OptionsSerialized.ChunkSize, &backoff.ConstantBackoff{ chunks := chunk.NewChunkGroup(file, c.policy.OptionsSerialized.ChunkSize, &backoff.ConstantBackoff{
Max: model.GetIntSetting("chunk_retries", 5), Max: model.GetIntSetting("chunk_retries", 5),
@ -130,10 +129,11 @@ func (c *remoteClient) DeleteUploadSession(ctx context.Context, sessionID string
return nil return nil
} }
func (c *remoteClient) CreateUploadSession(ctx context.Context, session *serializer.UploadSession, ttl int64) error { func (c *remoteClient) CreateUploadSession(ctx context.Context, session *serializer.UploadSession, ttl int64, overwrite bool) error {
reqBodyEncoded, err := json.Marshal(map[string]interface{}{ reqBodyEncoded, err := json.Marshal(map[string]interface{}{
"session": session, "session": session,
"ttl": ttl, "ttl": ttl,
"overwrite": overwrite,
}) })
if err != nil { if err != nil {
return err return err

@ -281,7 +281,7 @@ func (handler *Driver) Token(ctx context.Context, ttl int64, uploadSession *seri
// 在从机端创建上传会话 // 在从机端创建上传会话
uploadSession.Callback = apiURL.String() uploadSession.Callback = apiURL.String()
if err := handler.uploadClient.CreateUploadSession(ctx, uploadSession, ttl); err != nil { if err := handler.uploadClient.CreateUploadSession(ctx, uploadSession, ttl, false); err != nil {
return nil, err return nil, err
} }

@ -14,6 +14,7 @@ import (
"path" "path"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
model "github.com/cloudreve/Cloudreve/v3/models" model "github.com/cloudreve/Cloudreve/v3/models"
@ -30,6 +31,7 @@ type Handler struct {
// Logger is an optional error logger. If non-nil, it will be called // Logger is an optional error logger. If non-nil, it will be called
// for all HTTP requests. // for all HTTP requests.
Logger func(*http.Request, error) Logger func(*http.Request, error)
Mutex *sync.Mutex
} }
func (h *Handler) stripPrefix(p string, uid uint) (string, int, error) { func (h *Handler) stripPrefix(p string, uid uint) (string, int, error) {
@ -60,13 +62,19 @@ func isPathExist(ctx context.Context, fs *filesystem.FileSystem, path string) (b
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) { func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) {
status, err := http.StatusBadRequest, errUnsupportedMethod status, err := http.StatusBadRequest, errUnsupportedMethod
h.Mutex.Lock()
if h.LockSystem == nil { if h.LockSystem == nil {
h.Mutex.Unlock()
status, err = http.StatusInternalServerError, errNoLockSystem status, err = http.StatusInternalServerError, errNoLockSystem
} else { } else {
// 检查并新建LockSystem // 检查并新建 LockSystem
if _, ok := h.LockSystem[fs.User.ID]; !ok { ls, ok := h.LockSystem[fs.User.ID]
if !ok {
h.LockSystem[fs.User.ID] = NewMemLS() h.LockSystem[fs.User.ID] = NewMemLS()
ls = h.LockSystem[fs.User.ID]
} }
h.Mutex.Unlock()
switch r.Method { switch r.Method {
case "OPTIONS": case "OPTIONS":
status, err = h.handleOptions(w, r, fs) status, err = h.handleOptions(w, r, fs)
@ -81,13 +89,13 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, fs *filesyst
case "COPY", "MOVE": case "COPY", "MOVE":
status, err = h.handleCopyMove(w, r, fs) status, err = h.handleCopyMove(w, r, fs)
case "LOCK": case "LOCK":
status, err = h.handleLock(w, r, fs) status, err = h.handleLock(w, r, fs, ls)
case "UNLOCK": case "UNLOCK":
status, err = h.handleUnlock(w, r, fs) status, err = h.handleUnlock(w, r, fs, ls)
case "PROPFIND": case "PROPFIND":
status, err = h.handlePropfind(w, r, fs) status, err = h.handlePropfind(w, r, fs, ls)
case "PROPPATCH": case "PROPPATCH":
status, err = h.handleProppatch(w, r, fs) status, err = h.handleProppatch(w, r, fs, ls)
} }
} }
@ -103,98 +111,111 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, fs *filesyst
} }
// OK // OK
func (h *Handler) lock(now time.Time, root string, fs *filesystem.FileSystem) (token string, status int, err error) { func (h *Handler) lock(now time.Time, root string, fs *filesystem.FileSystem, ls LockSystem) (token string, status int, err error) {
token, err = h.LockSystem[fs.User.ID].Create(now, LockDetails{ //token, err = ls.Create(now, LockDetails{
Root: root, // Root: root,
Duration: infiniteTimeout, // Duration: infiniteTimeout,
ZeroDepth: true, // ZeroDepth: true,
}) //})
if err != nil { //if err != nil {
if err == ErrLocked { // if err == ErrLocked {
return "", StatusLocked, err // return "", StatusLocked, err
} // }
return "", http.StatusInternalServerError, err // return "", http.StatusInternalServerError, err
} //}
return token, 0, nil
return fmt.Sprintf("%d", time.Now().Unix()), 0, nil
} }
// ok // ok
func (h *Handler) confirmLocks(r *http.Request, src, dst string, fs *filesystem.FileSystem) (release func(), status int, err error) { func (h *Handler) confirmLocks(r *http.Request, src, dst string, fs *filesystem.FileSystem) (release func(), status int, err error) {
hdr := r.Header.Get("If")
if hdr == "" {
// An empty If header means that the client hasn't previously created locks.
// Even if this client doesn't care about locks, we still need to check that
// the resources aren't locked by another client, so we create temporary
// locks that would conflict with another client's locks. These temporary
// locks are unlocked at the end of the HTTP request.
now, srcToken, dstToken := time.Now(), "", ""
if src != "" {
srcToken, status, err = h.lock(now, src, fs)
if err != nil {
return nil, status, err
}
}
if dst != "" {
dstToken, status, err = h.lock(now, dst, fs)
if err != nil {
if srcToken != "" {
h.LockSystem[fs.User.ID].Unlock(now, srcToken)
}
return nil, status, err
}
}
return func() { //hdr := r.Header.Get("If")
if dstToken != "" { //h.Mutex.Lock()
h.LockSystem[fs.User.ID].Unlock(now, dstToken) //ls,ok := h.LockSystem[fs.User.ID]
} //h.Mutex.Unlock()
if srcToken != "" { //if !ok{
h.LockSystem[fs.User.ID].Unlock(now, srcToken) // return nil, http.StatusInternalServerError, errNoLockSystem
} //}
}, 0, nil //
} //if hdr == "" {
// // An empty If header means that the client hasn't previously created locks.
// // Even if this client doesn't care about locks, we still need to check that
// // the resources aren't locked by another client, so we create temporary
// // locks that would conflict with another client's locks. These temporary
// // locks are unlocked at the end of the HTTP request.
// now, srcToken, dstToken := time.Now(), "", ""
// if src != "" {
// srcToken, status, err = h.lock(now, src, fs,ls)
// if err != nil {
// return nil, status, err
// }
// }
// if dst != "" {
// dstToken, status, err = h.lock(now, dst, fs,ls)
// if err != nil {
// if srcToken != "" {
// ls.Unlock(now, srcToken)
// }
// return nil, status, err
// }
// }
//
// return func() {
// if dstToken != "" {
// ls.Unlock(now, dstToken)
// }
// if srcToken != "" {
// ls.Unlock(now, srcToken)
// }
// }, 0, nil
//}
//
//ih, ok := parseIfHeader(hdr)
//if !ok {
// return nil, http.StatusBadRequest, errInvalidIfHeader
//}
//// ih is a disjunction (OR) of ifLists, so any ifList will do.
//for _, l := range ih.lists {
// lsrc := l.resourceTag
// if lsrc == "" {
// lsrc = src
// } else {
// u, err := url.Parse(lsrc)
// if err != nil {
// continue
// }
// //if u.Host != r.Host {
// // continue
// //}
// lsrc, status, err = h.stripPrefix(u.Path, fs.User.ID)
// if err != nil {
// return nil, status, err
// }
// }
// release, err = ls.Confirm(
// time.Now(),
// lsrc,
// dst,
// l.conditions...,
// )
// if err == ErrConfirmationFailed {
// continue
// }
// if err != nil {
// return nil, http.StatusInternalServerError, err
// }
// return release, 0, nil
//}
//// Section 10.4.1 says that "If this header is evaluated and all state lists
//// fail, then the request must fail with a 412 (Precondition Failed) status."
//// We follow the spec even though the cond_put_corrupt_token test case from
//// the litmus test warns on seeing a 412 instead of a 423 (Locked).
//return nil, http.StatusPreconditionFailed, ErrLocked
ih, ok := parseIfHeader(hdr) return func() {
if !ok {
return nil, http.StatusBadRequest, errInvalidIfHeader }, 0, nil
}
// ih is a disjunction (OR) of ifLists, so any ifList will do.
for _, l := range ih.lists {
lsrc := l.resourceTag
if lsrc == "" {
lsrc = src
} else {
u, err := url.Parse(lsrc)
if err != nil {
continue
}
//if u.Host != r.Host {
// continue
//}
lsrc, status, err = h.stripPrefix(u.Path, fs.User.ID)
if err != nil {
return nil, status, err
}
}
release, err = h.LockSystem[fs.User.ID].Confirm(
time.Now(),
lsrc,
dst,
l.conditions...,
)
if err == ErrConfirmationFailed {
continue
}
if err != nil {
return nil, http.StatusInternalServerError, err
}
return release, 0, nil
}
// Section 10.4.1 says that "If this header is evaluated and all state lists
// fail, then the request must fail with a 412 (Precondition Failed) status."
// We follow the spec even though the cond_put_corrupt_token test case from
// the litmus test warns on seeing a 412 instead of a 423 (Locked).
return nil, http.StatusPreconditionFailed, ErrLocked
} }
//OK //OK
@ -245,7 +266,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs *
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
etag, err := findETag(ctx, fs, h.LockSystem[fs.User.ID], reqPath, &fs.FileTarget[0]) etag, err := findETag(ctx, fs, nil, reqPath, &fs.FileTarget[0])
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
@ -271,6 +292,7 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request, fs *files
if err != nil { if err != nil {
return status, err return status, err
} }
release, status, err := h.confirmLocks(r, reqPath, "", fs) release, status, err := h.confirmLocks(r, reqPath, "", fs)
if err != nil { if err != nil {
return status, err return status, err
@ -373,7 +395,7 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *filesyst
return http.StatusMethodNotAllowed, err return http.StatusMethodNotAllowed, err
} }
etag, err := findETag(ctx, fs, h.LockSystem[fs.User.ID], reqPath, fileData.Model.(*model.File)) etag, err := findETag(ctx, fs, nil, reqPath, fileData.Model.(*model.File))
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
@ -494,130 +516,137 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *fil
} }
// OK // OK
func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (retStatus int, retErr error) { func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem, ls LockSystem) (retStatus int, retErr error) {
defer fs.Recycle() defer fs.Recycle()
duration, err := parseTimeout(r.Header.Get("Timeout")) duration, err := parseTimeout(r.Header.Get("Timeout"))
if err != nil { if err != nil {
return http.StatusBadRequest, err return http.StatusBadRequest, err
} }
li, status, err := readLockInfo(r.Body)
reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
if err != nil { if err != nil {
return status, err return status, err
} }
//ctx := r.Context() ////ctx := r.Context()
token, ld, now, created := "", LockDetails{}, time.Now(), false //token, ld, now, created := "", LockDetails{}, time.Now(), false
if li == (lockInfo{}) { //if li == (lockInfo{}) {
// An empty lockInfo means to refresh the lock. // // An empty lockInfo means to refresh the lock.
ih, ok := parseIfHeader(r.Header.Get("If")) // ih, ok := parseIfHeader(r.Header.Get("If"))
if !ok { // if !ok {
return http.StatusBadRequest, errInvalidIfHeader // return http.StatusBadRequest, errInvalidIfHeader
} // }
if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 { // if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 {
token = ih.lists[0].conditions[0].Token // token = ih.lists[0].conditions[0].Token
} // }
if token == "" { // if token == "" {
return http.StatusBadRequest, errInvalidLockToken // return http.StatusBadRequest, errInvalidLockToken
} // }
ld, err = h.LockSystem[fs.User.ID].Refresh(now, token, duration) // ld, err = ls.Refresh(now, token, duration)
if err != nil { // if err != nil {
if err == ErrNoSuchLock { // if err == ErrNoSuchLock {
return http.StatusPreconditionFailed, err // return http.StatusPreconditionFailed, err
} // }
return http.StatusInternalServerError, err // return http.StatusInternalServerError, err
} // }
//
//} else {
// // Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
// // then the request MUST act as if a "Depth:infinity" had been submitted."
// depth := infiniteDepth
// if hdr := r.Header.Get("Depth"); hdr != "" {
// depth = parseDepth(hdr)
// if depth != 0 && depth != infiniteDepth {
// // Section 9.10.3 says that "Values other than 0 or infinity must not be
// // used with the Depth header on a LOCK method".
// return http.StatusBadRequest, errInvalidDepth
// }
// }
// reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
// if err != nil {
// return status, err
// }
// ld = LockDetails{
// Root: reqPath,
// Duration: duration,
// OwnerXML: li.Owner.InnerXML,
// ZeroDepth: depth == 0,
// }
// token, err = ls.Create(now, ld)
// if err != nil {
// if err == ErrLocked {
// return StatusLocked, err
// }
// return http.StatusInternalServerError, err
// }
// defer func() {
// if retErr != nil {
// ls.Unlock(now, token)
// }
// }()
//
// // Create the resource if it didn't previously exist.
// //if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
// // f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
// // if err != nil {
// // // TODO: detect missing intermediate dirs and return http.StatusConflict?
// // return http.StatusInternalServerError, err
// // }
// // f.Close()
// // created = true
// //}
//
// // http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
// // Lock-Token value is a Coded-URL. We add angle brackets.
// w.Header().Set("Lock-Token", "<"+token+">")
//}
//
//w.Header().Set("Content-Type", "application/xml; charset=utf-8")
//if created {
// // This is "w.WriteHeader(http.StatusCreated)" and not "return
// // http.StatusCreated, nil" because we write our own (XML) response to w
// // and Handler.ServeHTTP would otherwise write "Created".
// w.WriteHeader(http.StatusCreated)
//}
} else { writeLockInfo(w, fmt.Sprintf("%d", time.Now().UnixNano()), LockDetails{
// Section 9.10.3 says that "If no Depth header is submitted on a LOCK request, Duration: duration,
// then the request MUST act as if a "Depth:infinity" had been submitted." OwnerXML: fs.User.Email,
depth := infiniteDepth Root: reqPath,
if hdr := r.Header.Get("Depth"); hdr != "" { })
depth = parseDepth(hdr)
if depth != 0 && depth != infiniteDepth {
// Section 9.10.3 says that "Values other than 0 or infinity must not be
// used with the Depth header on a LOCK method".
return http.StatusBadRequest, errInvalidDepth
}
}
reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
if err != nil {
return status, err
}
ld = LockDetails{
Root: reqPath,
Duration: duration,
OwnerXML: li.Owner.InnerXML,
ZeroDepth: depth == 0,
}
token, err = h.LockSystem[fs.User.ID].Create(now, ld)
if err != nil {
if err == ErrLocked {
return StatusLocked, err
}
return http.StatusInternalServerError, err
}
defer func() {
if retErr != nil {
h.LockSystem[fs.User.ID].Unlock(now, token)
}
}()
// Create the resource if it didn't previously exist.
//if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
// f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
// if err != nil {
// // TODO: detect missing intermediate dirs and return http.StatusConflict?
// return http.StatusInternalServerError, err
// }
// f.Close()
// created = true
//}
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
// Lock-Token value is a Coded-URL. We add angle brackets.
w.Header().Set("Lock-Token", "<"+token+">")
}
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
if created {
// This is "w.WriteHeader(http.StatusCreated)" and not "return
// http.StatusCreated, nil" because we write our own (XML) response to w
// and Handler.ServeHTTP would otherwise write "Created".
w.WriteHeader(http.StatusCreated)
}
writeLockInfo(w, token, ld)
return 0, nil return 0, nil
} }
// OK // OK
func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) { func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem, ls LockSystem) (status int, err error) {
defer fs.Recycle() defer fs.Recycle()
return http.StatusNoContent, err
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the //// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
// Lock-Token value is a Coded-URL. We strip its angle brackets. //// Lock-Token value is a Coded-URL. We strip its angle brackets.
t := r.Header.Get("Lock-Token") //t := r.Header.Get("Lock-Token")
if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' { //if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' {
return http.StatusBadRequest, errInvalidLockToken // return http.StatusBadRequest, errInvalidLockToken
} //}
t = t[1 : len(t)-1] //t = t[1 : len(t)-1]
//
switch err = h.LockSystem[fs.User.ID].Unlock(time.Now(), t); err { //switch err = ls.Unlock(time.Now(), t); err {
case nil: //case nil:
return http.StatusNoContent, err // return http.StatusNoContent, err
case ErrForbidden: //case ErrForbidden:
return http.StatusForbidden, err // return http.StatusForbidden, err
case ErrLocked: //case ErrLocked:
return StatusLocked, err // return StatusLocked, err
case ErrNoSuchLock: //case ErrNoSuchLock:
return http.StatusConflict, err // return http.StatusConflict, err
default: //default:
return http.StatusInternalServerError, err // return http.StatusInternalServerError, err
} //}
} }
// OK // OK
func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) { func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem, ls LockSystem) (status int, err error) {
defer fs.Recycle() defer fs.Recycle()
reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID) reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
@ -651,7 +680,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request, fs *fil
} }
var pstats []Propstat var pstats []Propstat
if pf.Propname != nil { if pf.Propname != nil {
pnames, err := propnames(ctx, fs, h.LockSystem[fs.User.ID], info) pnames, err := propnames(ctx, fs, ls, info)
if err != nil { if err != nil {
return err return err
} }
@ -661,9 +690,9 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request, fs *fil
} }
pstats = append(pstats, pstat) pstats = append(pstats, pstat)
} else if pf.Allprop != nil { } else if pf.Allprop != nil {
pstats, err = allprop(ctx, fs, h.LockSystem[fs.User.ID], info, pf.Prop) pstats, err = allprop(ctx, fs, ls, info, pf.Prop)
} else { } else {
pstats, err = props(ctx, fs, h.LockSystem[fs.User.ID], info, pf.Prop) pstats, err = props(ctx, fs, ls, info, pf.Prop)
} }
if err != nil { if err != nil {
return err return err
@ -686,7 +715,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request, fs *fil
return 0, nil return 0, nil
} }
func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) { func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem, ls LockSystem) (status int, err error) {
defer fs.Recycle() defer fs.Recycle()
reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID) reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
@ -708,7 +737,7 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request, fs *fi
if err != nil { if err != nil {
return status, err return status, err
} }
pstats, err := patch(ctx, fs, h.LockSystem[fs.User.ID], reqPath, patches) pstats, err := patch(ctx, fs, ls, reqPath, patches)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }

@ -7,6 +7,7 @@ import (
"github.com/cloudreve/Cloudreve/v3/pkg/webdav" "github.com/cloudreve/Cloudreve/v3/pkg/webdav"
"github.com/cloudreve/Cloudreve/v3/service/setting" "github.com/cloudreve/Cloudreve/v3/service/setting"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"sync"
) )
var handler *webdav.Handler var handler *webdav.Handler
@ -15,6 +16,7 @@ func init() {
handler = &webdav.Handler{ handler = &webdav.Handler{
Prefix: "/dav", Prefix: "/dav",
LockSystem: make(map[uint]webdav.LockSystem), LockSystem: make(map[uint]webdav.LockSystem),
Mutex: &sync.Mutex{},
} }
} }

@ -167,13 +167,14 @@ func CreateTransferTask(c *gin.Context, req *serializer.SlaveTransferReq) serial
// SlaveListService 从机上传会话服务 // SlaveListService 从机上传会话服务
type SlaveCreateUploadSessionService struct { type SlaveCreateUploadSessionService struct {
Session serializer.UploadSession `json:"session" binding:"required"` Session serializer.UploadSession `json:"session" binding:"required"`
TTL int64 `json:"ttl"` TTL int64 `json:"ttl"`
Overwrite bool `json:"overwrite"`
} }
// Create 从机创建上传会话 // Create 从机创建上传会话
func (service *SlaveCreateUploadSessionService) Create(ctx context.Context, c *gin.Context) serializer.Response { func (service *SlaveCreateUploadSessionService) Create(ctx context.Context, c *gin.Context) serializer.Response {
if util.Exists(service.Session.SavePath) { if !service.Overwrite && util.Exists(service.Session.SavePath) {
return serializer.Err(serializer.CodeConflict, "placeholder file already exist", nil) return serializer.Err(serializer.CodeConflict, "placeholder file already exist", nil)
} }

Loading…
Cancel
Save