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 }