github.com/orangebees/go-oneutils@v0.0.10/GlobalStore/FileStore.go (about)

     1  package GlobalStore
     2  
     3  import (
     4  	"errors"
     5  	"github.com/orangebees/go-oneutils/Fetch"
     6  	"github.com/orangebees/go-oneutils/PathHandle"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  )
    11  
    12  const Separator = string(filepath.Separator)
    13  const EmptyString = ""
    14  const (
    15  	hashStrMod = iota
    16  	hashStrPrefix
    17  )
    18  
    19  type FileStore struct {
    20  	//工作根目录
    21  	root string
    22  	//元数据目录(在根目录下的相对目录)
    23  	metadata string
    24  	//构建目录
    25  	build string
    26  	//存储目录
    27  	store string
    28  	//桶数量,建议为16的次方
    29  	bucketCount int
    30  	//桶数量指数,16的指数次方
    31  	bucketCountIndexNumber int
    32  	//桶分配方式 hashStrMod hashStrPrefix
    33  	bucketAllocationMethod int
    34  	//hash算法 建议sha512
    35  	bucketHashType string
    36  	//忽略规则方法
    37  	ignoreFunc []IgnoreFunc
    38  }
    39  type FileStoreConfig struct {
    40  	//工作根目录
    41  	Root string
    42  	//元数据目录(在根目录下的相对目录)
    43  	Metadata string
    44  	//构建目录(在根目录下的相对目录)
    45  	Build string
    46  	//存储目录(在根目录下的相对目录)
    47  	Store string
    48  	//桶数量指数,16的指数次方
    49  	BucketCountIndexNumber int
    50  	//桶分配方式 hashStrMod,hashStrPrefix
    51  	BucketAllocationMethod string
    52  	//hash算法 建议sha512
    53  	BucketHashType string
    54  }
    55  
    56  // IgnoreFunc 忽略文件的规则 返回真为忽略
    57  type IgnoreFunc func(relPath string, info os.FileInfo) bool
    58  
    59  func IgnoreDotGitPath(relPath string, info os.FileInfo) bool {
    60  	if strings.HasPrefix(relPath, ".git"+Separator) {
    61  		return true
    62  	}
    63  	return false
    64  }
    65  
    66  type FileInfo struct {
    67  	//日期
    68  	CheckedAt int64 `json:"checked_at"`
    69  	//完整性校验
    70  	Integrity Integrity `json:"integrity"`
    71  	//权限
    72  	Mode int `json:"mode"`
    73  	//文件大小
    74  	Size int64 `json:"size"`
    75  }
    76  
    77  type Metadata interface {
    78  	GetFileInfoMap() FileInfoMap
    79  	SetFileInfoMap(fim FileInfoMap)
    80  	Fetch.EasyJsonSerialization
    81  }
    82  
    83  // NewFileStore 初始化文件存储
    84  func NewFileStore(cfg FileStoreConfig, ignoreFunc ...IgnoreFunc) (*FileStore, error) {
    85  	f := FileStore{
    86  		root:       cfg.Root,
    87  		metadata:   cfg.Root + Separator + "metadata",
    88  		build:      cfg.Root + Separator + "build",
    89  		store:      cfg.Root + Separator + "store",
    90  		ignoreFunc: ignoreFunc,
    91  	}
    92  	if cfg.Metadata != "" {
    93  		f.metadata = cfg.Root + Separator + cfg.Metadata
    94  	}
    95  	if cfg.Build != "" {
    96  		f.build = cfg.Root + Separator + cfg.Build
    97  	}
    98  	if cfg.Store != "" {
    99  		f.store = cfg.Root + Separator + cfg.Store
   100  	}
   101  	err := PathHandle.KeepDirsExist(
   102  		f.root,
   103  		f.metadata,
   104  		f.build,
   105  		f.store,
   106  	)
   107  	if err != nil {
   108  		return nil, nil
   109  	}
   110  	switch cfg.BucketCountIndexNumber {
   111  	case 3:
   112  		f.bucketCount = 4096
   113  		f.bucketCountIndexNumber = 3
   114  	default:
   115  		f.bucketCount = 256
   116  		f.bucketCountIndexNumber = 2
   117  	}
   118  	switch cfg.BucketAllocationMethod {
   119  	case "hashStrPrefix":
   120  		f.bucketAllocationMethod = hashStrPrefix
   121  	default:
   122  		//hashStrMod
   123  		f.bucketAllocationMethod = hashStrMod
   124  	}
   125  	switch cfg.BucketHashType {
   126  	case "md5":
   127  		f.bucketHashType = "md5"
   128  	case "sha1":
   129  		f.bucketHashType = "sha1"
   130  	case "sha256":
   131  		f.bucketHashType = "sha256"
   132  	default:
   133  		//case "sha512":
   134  		f.bucketHashType = "sha512"
   135  	}
   136  	return &f, nil
   137  }
   138  
   139  // contains  判断path1是否包含path2
   140  func contains(path1, path2 string) bool {
   141  	if strings.HasPrefix(path1, path2) {
   142  		return true
   143  	}
   144  	tmp, tmp2 := strings.Split(path2, Separator), ""
   145  	for i := 0; i < len(tmp); i++ {
   146  		tmp2 += tmp[i]
   147  		//判断是否相等
   148  		if tmp2 == path1 {
   149  			return true
   150  		}
   151  		tmp2 += Separator
   152  	}
   153  	return false
   154  }
   155  
   156  // AddDir 添加目录到全局文件存储
   157  func (s FileStore) AddDir(absPath string) (FileInfoMap, error) {
   158  	//路径不应该为root目录的父目录,及不应该为相对路径
   159  	if !filepath.IsAbs(absPath) {
   160  		return nil, errors.New("the path is not absolute")
   161  	}
   162  	if contains(absPath, s.store) {
   163  		return nil, errors.New("absPath cannot contain store root directory")
   164  	}
   165  	fim := AcquireFileInfoMap()
   166  	err := filepath.Walk(absPath,
   167  		func(path string, info os.FileInfo, err error) error {
   168  			if err != nil {
   169  				return err
   170  			}
   171  			//获取相对路径到结构体切片
   172  			if info.IsDir() {
   173  				//跳过文件夹
   174  				return nil
   175  			}
   176  			rel, err := filepath.Rel(absPath, path)
   177  			if err != nil {
   178  				return err
   179  			}
   180  			for i := 0; i < len(s.ignoreFunc); i++ {
   181  				if s.ignoreFunc[i](rel, info) {
   182  					return nil
   183  				}
   184  			}
   185  			file, err2 := os.ReadFile(path)
   186  			if err2 != nil {
   187  				return err2
   188  			}
   189  
   190  			rp := []byte(rel)
   191  			//统一为Linux下的分隔符
   192  			PathHandle.UnifyPathSlashSeparator(rp)
   193  			//添加文件信息
   194  			it := NewIntegrity(s.bucketHashType, file)
   195  			afi := AcquireFileInfo()
   196  			afi.CheckedAt = info.ModTime().Unix()
   197  			afi.Integrity = it
   198  			afi.Mode = int(info.Mode())
   199  			afi.Size = info.Size()
   200  			fim[string(rp)] = afi
   201  			switch s.bucketAllocationMethod {
   202  			case hashStrPrefix:
   203  
   204  				hashString, err := it.GetRawHashString()
   205  				if err != nil {
   206  					return err
   207  				}
   208  				//println(hashString, hashString[:2], hashString[2:])
   209  				bucketpath := s.store + Separator + hashString[:s.bucketCountIndexNumber]
   210  				err = PathHandle.KeepDirExist(bucketpath)
   211  				if err != nil {
   212  					return err
   213  				}
   214  				err = os.WriteFile(bucketpath+Separator+hashString[s.bucketCountIndexNumber:], file, 0777)
   215  				if err != nil {
   216  					return err
   217  				}
   218  
   219  			default:
   220  				//case :"hashStrMod"
   221  
   222  				//添加文件到全局存储桶
   223  				rawhashstring, mod, err := it.GetRawHashStringAndModFast(uint64(s.bucketCount))
   224  				if err != nil {
   225  					return err
   226  				}
   227  				bucketpath := s.store + Separator + mod
   228  				err = PathHandle.KeepDirExist(bucketpath)
   229  				if err != nil {
   230  					return err
   231  				}
   232  				err = os.WriteFile(bucketpath+Separator+rawhashstring, file, 0777)
   233  				if err != nil {
   234  					return err
   235  				}
   236  			}
   237  			return nil
   238  		})
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  	return fim, nil
   243  }
   244  
   245  // NewFileInfoMapFromDir 仅从文件夹生成FileInfoMap
   246  func (s FileStore) NewFileInfoMapFromDir(absPath string) (FileInfoMap, error) {
   247  	//路径不应该为root目录
   248  	if !filepath.IsAbs(absPath) {
   249  		return nil, errors.New("the path is not absolute")
   250  	}
   251  	if contains(absPath, s.store) {
   252  		return nil, errors.New("absPath cannot contain store root directory")
   253  	}
   254  	fim := AcquireFileInfoMap()
   255  	err := filepath.Walk(absPath,
   256  		func(path string, info os.FileInfo, err error) error {
   257  			if err != nil {
   258  				return err
   259  			}
   260  			//获取相对路径到结构体切片
   261  			if info.IsDir() {
   262  				//跳过文件夹
   263  				return nil
   264  			}
   265  			rel, err := filepath.Rel(absPath, path)
   266  			if err != nil {
   267  				return err
   268  			}
   269  			for i := 0; i < len(s.ignoreFunc); i++ {
   270  				if s.ignoreFunc[i](rel, info) {
   271  					return nil
   272  				}
   273  			}
   274  			file, err2 := os.ReadFile(path)
   275  			if err2 != nil {
   276  				return err2
   277  			}
   278  
   279  			rp := []byte(rel)
   280  			//统一为Linux下的分隔符
   281  			PathHandle.UnifyPathSlashSeparator(rp)
   282  			//添加文件信息
   283  			afi := AcquireFileInfo()
   284  			afi.CheckedAt = info.ModTime().Unix()
   285  			afi.Integrity = NewIntegrity(s.bucketHashType, file)
   286  			afi.Mode = int(info.Mode())
   287  			afi.Size = info.Size()
   288  			fim[string(rp)] = afi
   289  			return nil
   290  		})
   291  	if err != nil {
   292  		return nil, err
   293  	}
   294  	return fim, nil
   295  }
   296  
   297  // BuildDir 通过FileInfoMap构建包目录在工作区构建目录下
   298  func (s FileStore) BuildDir(fim FileInfoMap, pkgDir string) error {
   299  	//构建目录
   300  	buildpath := s.build + Separator + PathHandle.URLToLocalDirPath(pkgDir)
   301  	exists, err := PathHandle.DirExist(buildpath)
   302  	if err != nil {
   303  		return err
   304  	}
   305  	if exists {
   306  		return errors.New("path exist")
   307  	}
   308  	err = os.MkdirAll(buildpath, os.ModePerm)
   309  	if err != nil {
   310  		return err
   311  	}
   312  	for s2, info := range fim {
   313  		d, _ := filepath.Split(s2)
   314  		if d != "" {
   315  			err = PathHandle.KeepDirExist(buildpath + Separator + d)
   316  			if err != nil {
   317  				err = os.RemoveAll(buildpath)
   318  				if err != nil {
   319  					return err
   320  				}
   321  				return err
   322  			}
   323  		}
   324  		switch s.bucketAllocationMethod {
   325  		case hashStrPrefix:
   326  			hashString, err := info.Integrity.GetRawHashString()
   327  			if err != nil {
   328  				err = os.RemoveAll(buildpath)
   329  				if err != nil {
   330  					return err
   331  				}
   332  				return err
   333  			}
   334  			hashpath := s.store + Separator + hashString[:s.bucketCountIndexNumber] + Separator + hashString[s.bucketCountIndexNumber:]
   335  			err = os.Link(hashpath, buildpath+Separator+s2)
   336  			if err != nil {
   337  				err = os.RemoveAll(buildpath)
   338  				if err != nil {
   339  					return err
   340  				}
   341  				return err
   342  			}
   343  		default:
   344  			//case :"hashStrMod"
   345  			hashString, mod, err := info.Integrity.GetRawHashStringAndModFast(uint64(s.bucketCount))
   346  			if err != nil {
   347  				err = os.RemoveAll(buildpath)
   348  				if err != nil {
   349  					return err
   350  				}
   351  				return err
   352  			}
   353  			hashpath := s.store + Separator + mod + Separator + hashString
   354  			err = os.Link(hashpath, buildpath+Separator+s2)
   355  			if err != nil {
   356  				err = os.RemoveAll(buildpath)
   357  				if err != nil {
   358  					return err
   359  				}
   360  				return err
   361  			}
   362  		}
   363  	}
   364  	return nil
   365  }
   366  
   367  // Link 链接包目录到目标目录
   368  func (s FileStore) Link(pkgDir, targetAbsPath string) error {
   369  	if !filepath.IsAbs(targetAbsPath) {
   370  		return errors.New("the path is not absolute")
   371  	}
   372  	var tmp string
   373  	for i := len(targetAbsPath) - 1; i >= 0; i-- {
   374  		if targetAbsPath[i] == filepath.Separator {
   375  			tmp = targetAbsPath[i+1:]
   376  		}
   377  	}
   378  	err := PathHandle.KeepDirExist(tmp)
   379  	if err != nil {
   380  		return err
   381  	}
   382  	err = os.Symlink(s.build+Separator+PathHandle.URLToLocalDirPath(pkgDir), targetAbsPath)
   383  	if err != nil {
   384  		return err
   385  	}
   386  	return nil
   387  }
   388  
   389  // VerifyDir 验证文件目录
   390  func (s FileStore) VerifyDir(fim FileInfoMap, absPath string) (bool, error) {
   391  	if !filepath.IsAbs(absPath) {
   392  		return false, errors.New("the path is not absolute")
   393  	}
   394  	nfim, err := s.NewFileInfoMapFromDir(absPath)
   395  	if err != nil {
   396  		return false, err
   397  	}
   398  	result := fim.FileEqual(nfim)
   399  	ReleaseFileInfoMap(nfim)
   400  	return result, nil
   401  }
   402  
   403  // GetMetadataPath 获取metadata的理论绝对路径
   404  func (s FileStore) GetMetadataPath(pkgDir string) (string, error) {
   405  	p := s.metadata + Separator + PathHandle.URLToLocalDirPath(pkgDir)
   406  	dir, _ := filepath.Split(p)
   407  	err := PathHandle.KeepDirExist(dir)
   408  	if err != nil {
   409  		return "", err
   410  	}
   411  	return p, nil
   412  }
   413  
   414  // GetDirPath 获取pkgDir的理论绝对路径
   415  func (s FileStore) GetDirPath(pkgDir string) (string, error) {
   416  	p := s.build + Separator + PathHandle.URLToLocalDirPath(pkgDir)
   417  	dir, _ := filepath.Split(p)
   418  	err := PathHandle.KeepDirExist(dir)
   419  	if err != nil {
   420  		return "", err
   421  	}
   422  	return p, nil
   423  }
   424  
   425  // DirIsExist 检测pkgDir存在
   426  func (s FileStore) DirIsExist(pkgDir string) (bool, error) {
   427  	return PathHandle.DirExist(s.build + Separator + PathHandle.URLToLocalDirPath(pkgDir))
   428  }