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  }