github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/pkg/webdav/file.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package webdav 6 7 import ( 8 "context" 9 "net/http" 10 "path" 11 "path/filepath" 12 "strconv" 13 "time" 14 15 model "github.com/cloudreve/Cloudreve/v3/models" 16 "github.com/cloudreve/Cloudreve/v3/pkg/filesystem" 17 "github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx" 18 ) 19 20 // slashClean is equivalent to but slightly more efficient than 21 // path.Clean("/" + name). 22 func slashClean(name string) string { 23 if name == "" || name[0] != '/' { 24 name = "/" + name 25 } 26 return path.Clean(name) 27 } 28 29 // 更新Copy或Move后的修改时间 30 func updateCopyMoveModtime(req *http.Request, fs *filesystem.FileSystem, dst string) error { 31 var modtime time.Time 32 if timeVal := req.Header.Get("X-OC-Mtime"); timeVal != "" { 33 timeUnix, err := strconv.ParseInt(timeVal, 10, 64) 34 if err == nil { 35 modtime = time.Unix(timeUnix, 0) 36 } 37 } 38 39 if modtime.IsZero() { 40 return nil 41 } 42 43 ok, fi := isPathExist(req.Context(), fs, dst) 44 if !ok { 45 return nil 46 } 47 48 if fi.IsDir() { 49 return model.DB.Model(fi.(*model.Folder)).UpdateColumn("updated_at", modtime).Error 50 } 51 return model.DB.Model(fi.(*model.File)).UpdateColumn("updated_at", modtime).Error 52 } 53 54 // moveFiles moves files and/or directories from src to dst. 55 // 56 // See section 9.9.4 for when various HTTP status codes apply. 57 func moveFiles(ctx context.Context, fs *filesystem.FileSystem, src FileInfo, dst string, overwrite bool) (status int, err error) { 58 59 var ( 60 fileIDs []uint 61 folderIDs []uint 62 ) 63 if src.IsDir() { 64 folderIDs = []uint{src.(*model.Folder).ID} 65 } else { 66 fileIDs = []uint{src.(*model.File).ID} 67 } 68 69 if overwrite { 70 if err := _checkOverwriteFile(ctx, fs, src, dst); err != nil { 71 return http.StatusInternalServerError, err 72 } 73 } 74 75 // 判断是否需要移动 76 if src.GetPosition() != path.Dir(dst) { 77 err = fs.Move( 78 context.WithValue(ctx, fsctx.WebdavDstName, path.Base(dst)), 79 folderIDs, 80 fileIDs, 81 src.GetPosition(), 82 path.Dir(dst), 83 ) 84 } else if src.GetName() != path.Base(dst) { 85 // 判断是否需要重命名 86 err = fs.Rename( 87 ctx, 88 folderIDs, 89 fileIDs, 90 path.Base(dst), 91 ) 92 } 93 94 if err != nil { 95 return http.StatusInternalServerError, err 96 } 97 return http.StatusNoContent, nil 98 } 99 100 // copyFiles copies files and/or directories from src to dst. 101 // 102 // See section 9.8.5 for when various HTTP status codes apply. 103 func copyFiles(ctx context.Context, fs *filesystem.FileSystem, src FileInfo, dst string, overwrite bool, depth int, recursion int) (status int, err error) { 104 if recursion == 1000 { 105 return http.StatusInternalServerError, errRecursionTooDeep 106 } 107 recursion++ 108 109 var ( 110 fileIDs []uint 111 folderIDs []uint 112 ) 113 114 if overwrite { 115 if err := _checkOverwriteFile(ctx, fs, src, dst); err != nil { 116 return http.StatusInternalServerError, err 117 } 118 } 119 120 if src.IsDir() { 121 folderIDs = []uint{src.(*model.Folder).ID} 122 } else { 123 fileIDs = []uint{src.(*model.File).ID} 124 } 125 126 err = fs.Copy( 127 context.WithValue(ctx, fsctx.WebdavDstName, path.Base(dst)), 128 folderIDs, 129 fileIDs, 130 src.GetPosition(), 131 path.Dir(dst), 132 ) 133 if err != nil { 134 return http.StatusInternalServerError, err 135 } 136 137 return http.StatusNoContent, nil 138 } 139 140 // 判断目标 文件/夹 是否已经存在,存在则先删除目标文件/夹 141 func _checkOverwriteFile(ctx context.Context, fs *filesystem.FileSystem, src FileInfo, dst string) error { 142 if src.IsDir() { 143 ok, folder := fs.IsPathExist(dst) 144 if ok { 145 return fs.Delete(ctx, []uint{folder.ID}, []uint{}, false, false) 146 } 147 } else { 148 ok, file := fs.IsFileExist(dst) 149 if ok { 150 return fs.Delete(ctx, []uint{}, []uint{file.ID}, false, false) 151 } 152 } 153 return nil 154 } 155 156 // walkFS traverses filesystem fs starting at name up to depth levels. 157 // 158 // Allowed values for depth are 0, 1 or infiniteDepth. For each visited node, 159 // walkFS calls walkFn. If a visited file system node is a directory and 160 // walkFn returns filepath.SkipDir, walkFS will skip traversal of this node. 161 func walkFS( 162 ctx context.Context, 163 fs *filesystem.FileSystem, 164 depth int, 165 name string, 166 info FileInfo, 167 walkFn func(reqPath string, info FileInfo, err error) error) error { 168 // This implementation is based on Walk's code in the standard path/filepath package. 169 err := walkFn(name, info, nil) 170 if err != nil { 171 if info.IsDir() && err == filepath.SkipDir { 172 return nil 173 } 174 return err 175 } 176 if !info.IsDir() || depth == 0 { 177 return nil 178 } 179 if depth == 1 { 180 depth = 0 181 } 182 183 dirs, _ := info.(*model.Folder).GetChildFolder() 184 files, _ := info.(*model.Folder).GetChildFiles() 185 186 for _, fileInfo := range files { 187 filename := path.Join(name, fileInfo.Name) 188 err = walkFS(ctx, fs, depth, filename, &fileInfo, walkFn) 189 if err != nil { 190 if !fileInfo.IsDir() || err != filepath.SkipDir { 191 return err 192 } 193 } 194 } 195 196 for _, fileInfo := range dirs { 197 filename := path.Join(name, fileInfo.Name) 198 err = walkFS(ctx, fs, depth, filename, &fileInfo, walkFn) 199 if err != nil { 200 if !fileInfo.IsDir() || err != filepath.SkipDir { 201 return err 202 } 203 } 204 } 205 return nil 206 }