aliyunpan/internal/localfile/symlink.go
2022-07-03 10:32:39 +08:00

204 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package localfile
import (
"fmt"
"io/fs"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
)
// SymlinkFile 软链接文件Linux/macOS的lnWindows的mklink命令创建的文件链接。对于非软链接文件而言真实的路径和逻辑路径是一样的。
type SymlinkFile struct {
// LogicPath 逻辑路径
LogicPath string `json:"logicPath"`
// RealPath 真正的文件路径,即文件的本体
RealPath string `json:"realPath"`
}
func (s *SymlinkFile) String() string {
sb := &strings.Builder{}
fmt.Fprintf(sb, "{\"logicPath\":%s, \"realPath\": %s}", s.LogicPath, s.RealPath)
return sb.String()
}
func NewSymlinkFile(filePath string) SymlinkFile {
p := CleanPath(filePath)
return SymlinkFile{
LogicPath: p,
RealPath: p,
}
}
type MyWalkFunc func(path SymlinkFile, info fs.FileInfo, err error) error
// CleanPath 规范化文件路径分隔符全部转换成Unix文件分隔符"/"。同时清除后缀无用的"/"路径
func CleanPath(p string) string {
if p == "" || p == "/" {
return p
}
p = path.Clean(strings.ReplaceAll(p, "\\", "/"))
if p == "." {
p = ""
}
return p
}
// GetSuffixPath 获取相对路径。即fullFilePath相对rootFilePath的相对路径
func GetSuffixPath(fullFilePath, rootFilePath string) string {
rootFilePath = CleanPath(rootFilePath)
fullFilePath = CleanPath(fullFilePath)
return strings.TrimPrefix(strings.TrimPrefix(fullFilePath, rootFilePath), "/")
}
// RetrieveRealPath 递归调用找到软链接文件的真实文件对应的路径信息
func RetrieveRealPath(file SymlinkFile) (SymlinkFile, os.FileInfo, error) {
info, err := os.Lstat(file.RealPath)
if err == nil {
if info.Mode()&os.ModeSymlink != 0 {
// 软链接文件
if f, e := os.Readlink(file.RealPath); e == nil {
file.RealPath = strings.ReplaceAll(f, "\\", "/")
return RetrieveRealPath(file)
}
}
}
return file, info, err
}
// RetrieveRealPathFromLogicPath 遍历路径获取逻辑路径真正的文件路径。如果逻辑路径不完全存在,则返回已经存在的那部分路径
//
// logicFilePath - 目标逻辑路径
// 由于目标逻辑路径期间有可能会经过多次符号逻辑文件,同时有部分逻辑路径可能是不存在的,所以需要按照逻辑文件起始开始进行遍历,直至完成逻辑文件路径的所有遍历,
// 或者直到不存在的逻辑文件部分,然后返回。
//
// 例如逻辑文件路径:/Volumes/Downloads/dev/sync_drive_config.json。
//
// 如果/Volumes/Downloads存在而后面部分不存在则最终结果返回/Volumes/Downloads对应的文件信息同时返回error
func RetrieveRealPathFromLogicPath(logicFilePath string) (SymlinkFile, os.FileInfo, error) {
logicFilePath = CleanPath(logicFilePath)
if logicFilePath == "/" {
return RetrieveRealPath(NewSymlinkFile(logicFilePath))
}
pathParts := strings.Split(logicFilePath, "/")
exitedSymlinkFile := NewSymlinkFile("")
var exitedFileInfo os.FileInfo
sf := NewSymlinkFile("")
var fi os.FileInfo
var err error
for _, p := range pathParts {
if p == "" {
continue
}
if strings.Contains(p, ":") {
// windows volume label, e.g: C:/ D:/
sf.LogicPath += p
sf.RealPath += p
exitedSymlinkFile = sf
continue
}
sf.LogicPath += "/" + p
sf.RealPath += "/" + p
sf, fi, err = RetrieveRealPath(sf)
if err != nil {
// may be permission deny or not existed
return exitedSymlinkFile, exitedFileInfo, err
}
exitedSymlinkFile = sf
exitedFileInfo = fi
}
return exitedSymlinkFile, exitedFileInfo, nil
}
// RetrieveRealPathFromLogicSuffixPath 通过指定根目录和后缀逻辑路径,获取文件对应的真实路径信息
//
// 整体功能和 RetrieveRealPathFromLogicPath 一致,将逻辑路径分成根路径和后缀路径两部分,并且根路径必须存在。
func RetrieveRealPathFromLogicSuffixPath(rootFile SymlinkFile, logicSuffixPath string) (SymlinkFile, os.FileInfo, error) {
if rootFile.LogicPath == "" || rootFile.RealPath == "" {
return rootFile, nil, fmt.Errorf("root file path error")
}
rootFile.LogicPath = CleanPath(rootFile.LogicPath)
rootFile.RealPath = CleanPath(rootFile.RealPath)
rootFileInfo, e := os.Lstat(rootFile.RealPath)
if e != nil {
return rootFile, nil, e
}
logicSuffixPath = CleanPath(logicSuffixPath)
pathParts := strings.Split(logicSuffixPath, "/")
exitedSymlinkFile := rootFile
exitedFileInfo := rootFileInfo
sf := rootFile
var fi os.FileInfo
var err error
for _, p := range pathParts {
if p == "" {
continue
}
sf.LogicPath += "/" + p
sf.RealPath += "/" + p
sf, fi, err = RetrieveRealPath(sf)
if err != nil {
// may be permission deny or not existed
return exitedSymlinkFile, exitedFileInfo, err
}
exitedSymlinkFile = sf
exitedFileInfo = fi
}
return exitedSymlinkFile, exitedFileInfo, nil
}
// WalkAllFile 遍历本地文件,支持软链接(符号逻辑)文件(Linux & Windows & macOS)
func WalkAllFile(file SymlinkFile, walkFn MyWalkFunc) error {
file.LogicPath = path.Clean(strings.ReplaceAll(file.LogicPath, "\\", "/"))
file.RealPath = path.Clean(strings.ReplaceAll(file.RealPath, "\\", "/"))
file, info, err := RetrieveRealPath(file)
if err != nil {
err = walkFn(file, nil, err)
} else {
err = walkAllFile(file, info, walkFn)
}
return err
}
func walkAllFile(file SymlinkFile, info os.FileInfo, walkFn MyWalkFunc) error {
if !info.IsDir() {
return walkFn(file, info, nil)
}
files, err1 := ioutil.ReadDir(file.RealPath)
if err1 != nil {
return walkFn(file, nil, err1)
}
for _, fi := range files {
subFile := SymlinkFile{
LogicPath: path.Join(path.Clean(file.LogicPath), "/", fi.Name()),
RealPath: path.Join(path.Clean(file.RealPath), "/", fi.Name()),
}
subFile, fi, err1 = RetrieveRealPath(subFile)
err := walkFn(subFile, fi, err1)
if err != nil && err != filepath.SkipDir {
return err
}
if fi == nil {
continue
}
if fi.IsDir() {
if err == filepath.SkipDir {
continue
}
err = walkAllFile(subFile, fi, walkFn)
if err != nil {
return err
}
}
}
return nil
}