github.com/PandaGoAdmin/utils@v0.0.0-20211208134815-d5461603a00f/file.go (about) 1 package kgo 2 3 import ( 4 "archive/tar" 5 "archive/zip" 6 "bufio" 7 "bytes" 8 "compress/gzip" 9 "crypto/md5" 10 "encoding/hex" 11 "errors" 12 "fmt" 13 "io" 14 "mime" 15 "net/http" 16 "os" 17 "path" 18 "path/filepath" 19 "regexp" 20 "strings" 21 ) 22 23 // GetExt 获取文件的小写扩展名,不包括点"." . 24 func (kf *LkkFile) GetExt(fpath string) string { 25 suffix := filepath.Ext(fpath) 26 if suffix != "" { 27 return strings.ToLower(suffix[1:]) 28 } 29 return suffix 30 } 31 32 // ReadFile 读取文件内容. 33 func (kf *LkkFile) ReadFile(fpath string) ([]byte, error) { 34 data, err := os.ReadFile(fpath) 35 return data, err 36 } 37 38 // ReadInArray 把整个文件读入一个数组中,每行作为一个元素. 39 func (kf *LkkFile) ReadInArray(fpath string) ([]string, error) { 40 data, err := os.ReadFile(fpath) 41 if err != nil { 42 return nil, err 43 } 44 45 return strings.Split(string(data), "\n"), nil 46 } 47 48 // ReadFirstLine 读取文件首行. 49 func (kf *LkkFile) ReadFirstLine(fpath string) []byte { 50 var res []byte 51 fh, err := os.Open(fpath) 52 if err == nil { 53 scanner := bufio.NewScanner(fh) 54 for scanner.Scan() { 55 res = scanner.Bytes() 56 break 57 } 58 } 59 defer func() { 60 _ = fh.Close() 61 }() 62 63 return res 64 } 65 66 // ReadLastLine 读取文件末行. 67 func (kf *LkkFile) ReadLastLine(fpath string) []byte { 68 var res []byte 69 fh, err := os.Open(fpath) 70 if err == nil { 71 var lastLineSize int 72 reader := bufio.NewReader(fh) 73 74 for { 75 bs, err := reader.ReadBytes('\n') 76 lastLineSize = len(bs) 77 if err == io.EOF { 78 break 79 } 80 } 81 82 fileInfo, _ := os.Stat(fpath) 83 84 // make a buffer size according to the lastLineSize 85 buffer := make([]byte, lastLineSize) 86 offset := fileInfo.Size() - int64(lastLineSize) 87 numRead, _ := fh.ReadAt(buffer, offset) 88 res = buffer[:numRead] 89 } 90 defer func() { 91 _ = fh.Close() 92 }() 93 94 return res 95 } 96 97 // WriteFile 将内容写入文件. 98 // fpath为文件路径;data为内容;perm为权限,默认为0655. 99 func (kf *LkkFile) WriteFile(fpath string, data []byte, perm ...os.FileMode) error { 100 dir := path.Dir(fpath) 101 if err := os.MkdirAll(dir, os.ModePerm); err != nil { 102 return err 103 } 104 105 var p os.FileMode = 0777 106 if len(perm) > 0 { 107 p = perm[0] 108 } 109 110 return os.WriteFile(fpath, data, p) 111 } 112 113 // GetFileMode 获取路径的权限模式. 114 func (kf *LkkFile) GetFileMode(fpath string) (os.FileMode, error) { 115 finfo, err := os.Lstat(fpath) 116 if err != nil { 117 return 0, err 118 } 119 return finfo.Mode(), nil 120 } 121 122 // AppendFile 插入文件内容. 123 func (kf *LkkFile) AppendFile(fpath string, data []byte) error { 124 if fpath == "" { 125 return errors.New("[AppendFile] no path provided") 126 } 127 128 var file *os.File 129 filePerm, err := kf.GetFileMode(fpath) 130 if err != nil { 131 // create the file 132 file, err = os.Create(fpath) 133 } else { 134 // open for append 135 file, err = os.OpenFile(fpath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, filePerm) 136 } 137 if err != nil { 138 // failed to create or open-for-append the file 139 return err 140 } 141 142 defer func() { 143 _ = file.Close() 144 }() 145 146 _, err = file.Write(data) 147 148 return err 149 } 150 151 // GetMime 获取文件mime类型;fast为true时根据后缀快速获取;为false时读取文件头获取. 152 func (kf *LkkFile) GetMime(fpath string, fast bool) string { 153 var res string 154 if fast { 155 suffix := filepath.Ext(fpath) 156 //当unix系统中没有相关的mime.types文件时,将返回空 157 res = mime.TypeByExtension(suffix) 158 } else { 159 srcFile, err := os.Open(fpath) 160 if err != nil { 161 return res 162 } 163 164 buffer := make([]byte, 512) 165 _, err = srcFile.Read(buffer) 166 if err != nil { 167 return res 168 } 169 170 res = http.DetectContentType(buffer) 171 } 172 173 return res 174 } 175 176 // FileSize 获取文件大小(bytes字节);注意:文件不存在或无法访问时返回-1 . 177 func (kf *LkkFile) FileSize(fpath string) int64 { 178 f, err := os.Stat(fpath) 179 if nil != err { 180 return -1 181 } 182 return f.Size() 183 } 184 185 // DirSize 获取目录大小(bytes字节). 186 func (kf *LkkFile) DirSize(fpath string) int64 { 187 var size int64 188 //filepath.Walk压测很慢 189 _ = filepath.Walk(fpath, func(_ string, info os.FileInfo, err error) error { 190 if err != nil { 191 return err 192 } 193 if !info.IsDir() { 194 size += info.Size() 195 } 196 return err 197 }) 198 return size 199 } 200 201 // IsExist 路径(文件/目录)是否存在. 202 func (kf *LkkFile) IsExist(fpath string) bool { 203 _, err := os.Stat(fpath) 204 return err == nil || os.IsExist(err) 205 } 206 207 // IsLink 是否链接文件(软链接,且存在). 208 func (kf *LkkFile) IsLink(fpath string) bool { 209 f, err := os.Lstat(fpath) 210 if err != nil { 211 return false 212 } 213 214 return f.Mode()&os.ModeSymlink == os.ModeSymlink 215 } 216 217 // IsFile 是否(某类型)文件,且存在. 218 // ftype为枚举(FILE_TYPE_ANY、FILE_TYPE_LINK、FILE_TYPE_REGULAR、FILE_TYPE_COMMON),默认FILE_TYPE_ANY; 219 func (kf *LkkFile) IsFile(fpath string, ftype ...LkkFileType) (res bool) { 220 var t LkkFileType = FILE_TYPE_ANY 221 if len(ftype) > 0 { 222 t = ftype[0] 223 } 224 225 var f os.FileInfo 226 var e error 227 var musLink, musRegular bool 228 229 if t == FILE_TYPE_LINK { 230 musLink = true 231 } else if t == FILE_TYPE_REGULAR { 232 musRegular = true 233 } else if t == FILE_TYPE_COMMON { 234 musLink = true 235 musRegular = true 236 } 237 238 if (!musLink && !musRegular) || musRegular { 239 f, e := os.Stat(fpath) 240 if musRegular { 241 res = (e == nil) && f.Mode().IsRegular() 242 } else { 243 res = (e == nil) && !f.IsDir() 244 } 245 } 246 247 if !res && musLink { 248 f, e = os.Lstat(fpath) 249 res = (e == nil) && (f.Mode()&os.ModeSymlink == os.ModeSymlink) 250 } 251 252 return 253 } 254 255 // IsDir 是否目录(且存在). 256 func (kf *LkkFile) IsDir(fpath string) bool { 257 f, err := os.Lstat(fpath) 258 if os.IsNotExist(err) || nil != err { 259 return false 260 } 261 return f.IsDir() 262 } 263 264 // IsBinary 是否二进制文件(且存在). 265 func (kf *LkkFile) IsBinary(fpath string) bool { 266 cont, err := kf.ReadFile(fpath) 267 if err != nil { 268 return false 269 } 270 271 return isBinary(string(cont)) 272 } 273 274 // IsImg 是否图片文件(仅检查后缀). 275 func (kf *LkkFile) IsImg(fpath string) bool { 276 ext := kf.GetExt(fpath) 277 switch ext { 278 case "jpg", "jpeg", "bmp", "gif", "png", "svg", "ico", "webp": 279 return true 280 default: 281 return false 282 } 283 } 284 285 // Mkdir 创建目录,允许多级. 286 func (kf *LkkFile) Mkdir(fpath string, mode os.FileMode) error { 287 return os.MkdirAll(fpath, mode) 288 } 289 290 // AbsPath 获取绝对路径,path可允许不存在. 291 func (kf *LkkFile) AbsPath(fpath string) string { 292 fullPath := "" 293 res, err := filepath.Abs(fpath) // filepath.Abs最终使用到os.Getwd()检查 294 if err != nil { 295 fullPath = filepath.Clean(filepath.Join(`/`, fpath)) 296 } else { 297 fullPath = res 298 } 299 300 return fullPath 301 } 302 303 // RealPath 返回规范化的真实绝对路径名.path必须存在,若路径不存在则返回空字符串. 304 func (kf *LkkFile) RealPath(fpath string) string { 305 fullPath := fpath 306 if !filepath.IsAbs(fpath) { 307 wd, err := os.Getwd() 308 if err != nil { 309 return "" 310 } 311 fullPath = filepath.Clean(wd + `/` + fpath) 312 } 313 314 _, err := os.Stat(fullPath) 315 if err != nil { 316 return "" 317 } 318 319 return fullPath 320 } 321 322 // Touch 快速创建指定大小的文件,size为字节. 323 func (kf *LkkFile) Touch(fpath string, size int64) bool { 324 //创建目录 325 destDir := filepath.Dir(fpath) 326 if err := os.MkdirAll(destDir, 0777); err != nil { 327 return false 328 } 329 330 fd, err := os.OpenFile(fpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777) 331 if err != nil { 332 return false 333 } 334 defer func() { 335 _ = fd.Close() 336 }() 337 338 if size > 1 { 339 _, _ = fd.Seek(size-1, 0) 340 _, _ = fd.Write([]byte{0}) 341 } 342 343 return true 344 } 345 346 // Rename 重命名(或移动)文件/目录. 347 func (kf *LkkFile) Rename(oldname, newname string) error { 348 return os.Rename(oldname, newname) 349 } 350 351 // Unlink 删除文件. 352 func (kf *LkkFile) Unlink(fpath string) error { 353 return os.Remove(fpath) 354 } 355 356 // CopyFile 拷贝源文件到目标文件,cover为枚举(FILE_COVER_ALLOW、FILE_COVER_IGNORE、FILE_COVER_DENY). 357 func (kf *LkkFile) CopyFile(source string, dest string, cover LkkFileCover) (int64, error) { 358 if source == dest { 359 return 0, nil 360 } 361 362 sourceStat, err := os.Stat(source) 363 if err != nil { 364 return 0, err 365 } else if !sourceStat.Mode().IsRegular() { 366 return 0, fmt.Errorf("[CopyFile]`source %s is not a regular file", source) 367 } 368 369 if cover != FILE_COVER_ALLOW { 370 if _, err := os.Stat(dest); err == nil { 371 if cover == FILE_COVER_IGNORE { 372 return 0, nil 373 } else if cover == FILE_COVER_DENY { 374 return 0, fmt.Errorf("[CopyFile]`dest File %s already exists", dest) 375 } 376 } 377 } 378 379 sourceFile, _ := os.Open(source) 380 defer func() { 381 _ = sourceFile.Close() 382 }() 383 384 //源目录 385 srcDirStat, _ := os.Stat(filepath.Dir(source)) 386 387 //创建目录 388 destDir := filepath.Dir(dest) 389 if err = os.MkdirAll(destDir, srcDirStat.Mode()); err != nil { 390 return 0, err 391 } 392 393 destFile, err := os.Create(dest) 394 if err != nil { 395 return 0, err 396 } 397 defer func() { 398 _ = destFile.Close() 399 }() 400 401 var nBytes int64 402 sourceSize := sourceStat.Size() 403 if sourceSize <= 1048576 { //1M以内小文件使用buffer拷贝 404 var total int 405 var bufferSize int = 102400 406 if sourceSize < 524288 { 407 bufferSize = 51200 408 } 409 410 buf := make([]byte, bufferSize) 411 for { 412 n, err := sourceFile.Read(buf) 413 if err != nil && err != io.EOF { 414 return int64(total), err 415 } else if n == 0 { 416 break 417 } 418 419 if _, err := destFile.Write(buf[:n]); err != nil || !kf.IsExist(dest) { 420 return int64(total), err 421 } 422 423 total += n 424 } 425 nBytes = int64(total) 426 } else { 427 nBytes, err = io.Copy(destFile, sourceFile) 428 if err == nil { 429 err = os.Chmod(dest, sourceStat.Mode()) 430 } 431 } 432 433 return nBytes, err 434 } 435 436 // FastCopy 快速拷贝源文件到目标文件,不做安全检查. 437 func (kf *LkkFile) FastCopy(source string, dest string) (int64, error) { 438 sourceFile, err := os.Open(source) 439 if err != nil { 440 return 0, err 441 } 442 defer func() { 443 _ = sourceFile.Close() 444 }() 445 446 //创建目录 447 destDir := filepath.Dir(dest) 448 if err = os.MkdirAll(destDir, 0777); err != nil { 449 return 0, err 450 } 451 452 destFile, err := os.Create(dest) 453 if err != nil { 454 return 0, err 455 } 456 defer func() { 457 _ = destFile.Close() 458 }() 459 460 var bufferSize int = 32768 461 var nBytes int 462 buf := make([]byte, bufferSize) 463 for { 464 n, err := sourceFile.Read(buf) 465 if err != nil && err != io.EOF { 466 return int64(nBytes), err 467 } else if n == 0 { 468 break 469 } 470 471 if _, err := destFile.Write(buf[:n]); err != nil || !kf.IsExist(dest) { 472 return int64(nBytes), err 473 } 474 475 nBytes += n 476 } 477 478 return int64(nBytes), err 479 } 480 481 // CopyLink 拷贝链接. 482 func (kf *LkkFile) CopyLink(source string, dest string) error { 483 if source == dest { 484 return nil 485 } 486 487 source, err := os.Readlink(source) 488 if err != nil { 489 return err 490 } 491 492 //移除已存在的目标文件 493 _, err = os.Lstat(dest) 494 if err == nil { 495 _ = os.Remove(dest) 496 } 497 498 //创建目录 499 destDir := filepath.Dir(dest) 500 if err := os.MkdirAll(destDir, 0777); err != nil { 501 return err 502 } 503 504 return os.Symlink(source, dest) 505 } 506 507 // CopyDir 拷贝源目录到目标目录,cover为枚举(FILE_COVER_ALLOW、FILE_COVER_IGNORE、FILE_COVER_DENY). 508 func (kf *LkkFile) CopyDir(source string, dest string, cover LkkFileCover) (int64, error) { 509 var total, nBytes int64 510 var err error 511 512 if source == "" || source == dest { 513 return 0, nil 514 } 515 516 sourceInfo, err := os.Stat(source) 517 if err != nil { 518 return 0, err 519 } else if !sourceInfo.IsDir() { 520 return 0, fmt.Errorf("[CopyDir]`source %s is not a directory", source) 521 } 522 523 // create dest dir 524 if err = os.MkdirAll(dest, sourceInfo.Mode()); err != nil { 525 return 0, err 526 } 527 528 directory, _ := os.Open(source) 529 defer func() { 530 _ = directory.Close() 531 }() 532 533 objects, err := directory.Readdir(-1) 534 if err != nil { 535 return 0, err 536 } 537 538 for _, obj := range objects { 539 srcFilePath := filepath.Join(source, obj.Name()) 540 destFilePath := filepath.Join(dest, obj.Name()) 541 542 if obj.IsDir() { 543 // create sub-directories - recursively 544 nBytes, err = kf.CopyDir(srcFilePath, destFilePath, cover) 545 } else { 546 destFileInfo, err := os.Stat(destFilePath) 547 if err == nil { 548 if cover != FILE_COVER_ALLOW || os.SameFile(obj, destFileInfo) { 549 continue 550 } 551 } 552 553 if obj.Mode()&os.ModeSymlink != 0 { 554 // a link 555 _ = kf.CopyLink(srcFilePath, destFilePath) 556 } else { 557 nBytes, err = kf.CopyFile(srcFilePath, destFilePath, cover) 558 } 559 } 560 561 if err == nil { 562 total += nBytes 563 } 564 } 565 566 return total, err 567 } 568 569 // DelDir 删除目录.delete为true时连该目录一起删除;为false时只清空该目录. 570 func (kf *LkkFile) DelDir(dir string, delete bool) error { 571 realPath := kf.AbsPath(dir) 572 if !kf.IsDir(realPath) { 573 return fmt.Errorf("[DelDir]`dir %s not exists", dir) 574 } 575 576 names, err := os.ReadDir(realPath) 577 if err != nil { 578 return err 579 } 580 581 for _, entery := range names { 582 file := path.Join([]string{realPath, entery.Name()}...) 583 err = os.RemoveAll(file) 584 } 585 586 //删除目录 587 if delete { 588 err = os.RemoveAll(realPath) 589 } 590 591 return err 592 } 593 594 // Img2Base64 读取图片文件,并转换为base64字符串. 595 func (kf *LkkFile) Img2Base64(fpath string) (string, error) { 596 if !kf.IsImg(fpath) { 597 return "", fmt.Errorf("[Img2Base64]`fpath %s is not a image", fpath) 598 } 599 600 imgBuffer, err := os.ReadFile(fpath) 601 if err != nil { 602 return "", err 603 } 604 605 ext := kf.GetExt(fpath) 606 return img2Base64(imgBuffer, ext), nil 607 } 608 609 // FileTree 获取目录的文件树列表. 610 // ftype为枚举(FILE_TREE_ALL、FILE_TREE_DIR、FILE_TREE_FILE); 611 // recursive为是否递归; 612 // filters为一个或多个文件过滤器函数,FileFilter类型. 613 func (kf *LkkFile) FileTree(fpath string, ftype LkkFileTree, recursive bool, filters ...FileFilter) []string { 614 var trees []string 615 616 if kf.IsFile(fpath, FILE_TYPE_ANY) { 617 if ftype != FILE_TREE_DIR { 618 trees = append(trees, fpath) 619 } 620 return trees 621 } 622 623 fpath = strings.TrimRight(fpath, "/\\") 624 files, err := filepath.Glob(filepath.Join(fpath, "*")) 625 if err != nil || len(files) == 0 { 626 return trees 627 } 628 629 for _, file := range files { 630 file = strings.ReplaceAll(file, "\\", "/") 631 if kf.IsDir(file) { 632 if ftype != FILE_TREE_FILE { 633 trees = append(trees, file) 634 } 635 636 if recursive { 637 subs := kf.FileTree(file, ftype, recursive, filters...) 638 trees = append(trees, subs...) 639 } 640 } else if ftype != FILE_TREE_DIR { 641 //文件过滤 642 chk := true 643 if len(filters) > 0 { 644 for _, filter := range filters { 645 chk = filter(file) 646 if !chk { 647 break 648 } 649 } 650 } 651 if !chk { 652 continue 653 } 654 655 trees = append(trees, file) 656 } 657 } 658 659 return trees 660 } 661 662 // FormatDir 格式化目录,将"\","//"替换为"/",且以"/"结尾. 663 func (kf *LkkFile) FormatDir(fpath string) string { 664 return formatDir(fpath) 665 } 666 667 // Md5 获取文件md5值,length指定结果长度32/16. 668 func (kf *LkkFile) Md5(fpath string, length uint8) (string, error) { 669 var res string 670 f, err := os.Open(fpath) 671 if err != nil { 672 return res, err 673 } 674 defer func() { 675 _ = f.Close() 676 }() 677 678 hash := md5.New() 679 if _, err := io.Copy(hash, f); err != nil { 680 return res, err 681 } 682 683 hashInBytes := hash.Sum(nil) 684 if length > 0 && length < 32 { 685 dst := make([]byte, hex.EncodedLen(len(hashInBytes))) 686 hex.Encode(dst, hashInBytes) 687 res = string(dst[:length]) 688 } else { 689 res = hex.EncodeToString(hashInBytes) 690 } 691 692 return res, nil 693 } 694 695 // ShaX 计算文件的 shaX 散列值,x为1/256/512. 696 func (kf *LkkFile) ShaX(fpath string, x uint16) (string, error) { 697 data, err := os.ReadFile(fpath) 698 if err != nil { 699 return "", err 700 } 701 702 return string(shaXByte(data, x)), nil 703 } 704 705 // Pathinfo 获取文件路径的信息. 706 // option为要返回的信息,枚举值如下: 707 // -1: all; 1: dirname; 2: basename; 4: extension; 8: filename; 708 // 若要查看某几项,则为它们之间的和. 709 func (kf *LkkFile) Pathinfo(fpath string, option int) map[string]string { 710 if option == -1 { 711 option = 1 | 2 | 4 | 8 712 } 713 res := make(map[string]string) 714 if (option & 1) == 1 { 715 res["dirname"] = filepath.Dir(fpath) 716 } 717 if (option & 2) == 2 { 718 res["basename"] = filepath.Base(fpath) 719 } 720 if ((option & 4) == 4) || ((option & 8) == 8) { 721 basename := "" 722 if (option & 2) == 2 { 723 basename, _ = res["basename"] 724 } else { 725 basename = filepath.Base(fpath) 726 } 727 p := strings.LastIndex(basename, ".") 728 filename, extension := "", "" 729 if p > 0 { 730 filename, extension = basename[:p], basename[p+1:] 731 } else if p == -1 { 732 filename = basename 733 } else if p == 0 { 734 extension = basename[p+1:] 735 } 736 if (option & 4) == 4 { 737 res["extension"] = extension 738 } 739 if (option & 8) == 8 { 740 res["filename"] = filename 741 } 742 } 743 return res 744 } 745 746 // Basename 返回路径中的文件名部分(包括后缀),空路径时返回".". 747 func (kf *LkkFile) Basename(fpath string) string { 748 return filepath.Base(fpath) 749 } 750 751 // Dirname 返回路径中的目录部分,注意空路径或无目录的返回".". 752 func (kf *LkkFile) Dirname(fpath string) string { 753 return filepath.Dir(fpath) 754 } 755 756 // GetModTime 获取文件的修改时间戳,秒. 757 func (kf *LkkFile) GetModTime(fpath string) (res int64) { 758 fileinfo, err := os.Stat(fpath) 759 if err == nil { 760 res = fileinfo.ModTime().Unix() 761 } 762 return 763 } 764 765 // Glob 寻找与模式匹配的文件路径. 766 func (kf *LkkFile) Glob(pattern string) ([]string, error) { 767 return filepath.Glob(pattern) 768 } 769 770 // SafeFileName 将文件名转换为安全可用的字符串. 771 func (kf *LkkFile) SafeFileName(str string) string { 772 name := path.Clean(path.Base(str)) 773 name = strings.Trim(name, " ") 774 separators, err := regexp.Compile(`[ &_=+:]`) 775 if err == nil { 776 name = separators.ReplaceAllString(name, "-") 777 } 778 legal, err := regexp.Compile(`[^[:alnum:]-.]`) 779 if err == nil { 780 name = legal.ReplaceAllString(name, "") 781 } 782 for strings.Contains(name, "--") { 783 name = strings.Replace(name, "--", "-", -1) 784 } 785 return name 786 } 787 788 // TarGz 打包压缩tar.gz. 789 // src为源文件或目录,dstTar为打包的路径名,ignorePatterns为要忽略的文件正则. 790 func (kf *LkkFile) TarGz(src string, dstTar string, ignorePatterns ...string) (bool, error) { 791 //过滤器,检查要忽略的文件 792 var filter = func(file string) bool { 793 res := true 794 for _, pattern := range ignorePatterns { 795 re, err := regexp.Compile(pattern) 796 if err != nil { 797 continue 798 } 799 chk := re.MatchString(file) 800 if chk { 801 res = false 802 break 803 } 804 } 805 return res 806 } 807 808 src = kf.AbsPath(src) 809 dstTar = kf.AbsPath(dstTar) 810 811 dstDir := kf.Dirname(dstTar) 812 if !kf.IsDir(dstDir) { 813 err := os.MkdirAll(dstDir, os.ModePerm) 814 if err != nil { 815 return false, err 816 } 817 } 818 819 files := kf.FileTree(src, FILE_TREE_ALL, true, filter) 820 if len(files) == 0 { 821 return false, fmt.Errorf("[TarGz]`src no files to tar.gz") 822 } 823 824 // dest file write 825 fw, err := os.Create(dstTar) 826 if err != nil { 827 return false, err 828 } 829 defer func() { 830 _ = fw.Close() 831 }() 832 // gzip write 833 gw := gzip.NewWriter(fw) 834 defer func() { 835 _ = gw.Close() 836 }() 837 838 // tar write 839 tw := tar.NewWriter(gw) 840 defer func() { 841 _ = tw.Close() 842 }() 843 844 parentDir := filepath.Dir(src) 845 for _, file := range files { 846 if file == dstTar { 847 continue 848 } 849 fi, err := os.Stat(file) 850 if err != nil { 851 continue 852 } 853 newName := strings.Replace(file, parentDir, "", -1) 854 newName = strings.ReplaceAll(newName, ":", "") //防止wins下 tmp/D: 创建失败 855 856 // Create tar header 857 hdr := new(tar.Header) 858 hdr.Format = tar.FormatGNU 859 860 if fi.IsDir() { 861 // if last character of header name is '/' it also can be directory 862 // but if you don't set Typeflag, error will occur when you untargz 863 hdr.Name = newName + "/" 864 hdr.Typeflag = tar.TypeDir 865 hdr.Size = 0 866 //hdr.Mode = 0755 | c_ISDIR 867 hdr.Mode = int64(fi.Mode()) 868 hdr.ModTime = fi.ModTime() 869 870 // Write hander 871 err := tw.WriteHeader(hdr) 872 if err != nil { 873 return false, fmt.Errorf("[TarGz] DirErr: %s file:%s\n", err.Error(), file) 874 } 875 } else { 876 // File reader 877 fr, err := os.Open(file) 878 if err != nil { 879 return false, fmt.Errorf("[TarGz] OpenErr: %s file:%s\n", err.Error(), file) 880 } 881 defer func() { 882 _ = fr.Close() 883 }() 884 885 hdr.Name = newName 886 hdr.Size = fi.Size() 887 hdr.Mode = int64(fi.Mode()) 888 hdr.ModTime = fi.ModTime() 889 890 // Write hander 891 err = tw.WriteHeader(hdr) 892 if err != nil { 893 return false, fmt.Errorf("[TarGz] FileErr: %s file:%s\n", err.Error(), file) 894 } 895 896 // Write file data 897 _, err = io.Copy(tw, fr) 898 if err != nil { 899 return false, fmt.Errorf("[TarGz] CopyErr: %s file:%s\n", err.Error(), file) 900 } 901 _ = fr.Close() 902 } 903 } 904 905 return true, nil 906 } 907 908 // UnTarGz 将tar.gz文件解压缩. 909 // srcTar为压缩包,dstDir为解压目录. 910 func (kf *LkkFile) UnTarGz(srcTar, dstDir string) (bool, error) { 911 fr, err := os.Open(srcTar) 912 if err != nil { 913 return false, err 914 } 915 defer func() { 916 _ = fr.Close() 917 }() 918 919 dstDir = strings.TrimRight(kf.AbsPath(dstDir), "/\\") 920 if !kf.IsDir(dstDir) { 921 err := os.MkdirAll(dstDir, os.ModePerm) 922 if err != nil { 923 return false, err 924 } 925 } 926 927 // Gzip reader 928 gr, err := gzip.NewReader(fr) 929 if err != nil { 930 return false, err 931 } 932 933 // Tar reader 934 tr := tar.NewReader(gr) 935 for { 936 hdr, err := tr.Next() 937 if err == io.EOF { 938 // End of tar archive 939 break 940 } else if err != nil { 941 return false, err 942 } 943 944 // Create diretory before create file 945 newPath := dstDir + "/" + strings.TrimLeft(hdr.Name, "/\\") 946 parentDir := path.Dir(newPath) 947 if !kf.IsDir(parentDir) { 948 err := os.MkdirAll(parentDir, os.ModePerm) 949 if err != nil { 950 return false, err 951 } 952 } 953 954 if hdr.Typeflag != tar.TypeDir { 955 // Write data to file 956 fw, err := os.Create(newPath) 957 if err != nil { 958 return false, fmt.Errorf("[UnTarGz] CreateErr: %s file:%s\n", err.Error(), newPath) 959 } 960 961 _, err = io.Copy(fw, tr) 962 if err != nil { 963 return false, fmt.Errorf("[UnTarGz] CopyErr: %s file:%s\n", err.Error(), newPath) 964 } 965 966 _ = fw.Close() 967 } 968 } 969 970 return true, nil 971 } 972 973 // ChmodBatch 批量改变路径权限模式(包括子目录和所属文件). 974 // filemode为文件权限模式,dirmode为目录权限模式. 975 func (kf *LkkFile) ChmodBatch(fpath string, filemode, dirmode os.FileMode) (res bool) { 976 var err error 977 err = filepath.Walk(fpath, func(fpath string, f os.FileInfo, err error) error { 978 if f == nil { 979 return err 980 } 981 982 if f.IsDir() { 983 err = os.Chmod(fpath, dirmode) 984 } else { 985 err = os.Chmod(fpath, filemode) 986 } 987 988 return err 989 }) 990 991 if err == nil { 992 res = true 993 } 994 995 return 996 } 997 998 // CountLines 统计文件行数.buffLength为缓冲长度,kb. 999 func (kf *LkkFile) CountLines(fpath string, buffLength int) (int, error) { 1000 fh, err := os.Open(fpath) 1001 if err != nil { 1002 return -1, err 1003 } 1004 defer func() { 1005 _ = fh.Close() 1006 }() 1007 1008 count := 0 1009 lineSep := []byte{'\n'} 1010 1011 if buffLength <= 0 { 1012 buffLength = 32 1013 } 1014 1015 r := bufio.NewReader(fh) 1016 buf := make([]byte, buffLength*1024) 1017 for { 1018 c, err := r.Read(buf) 1019 count += bytes.Count(buf[:c], lineSep) 1020 1021 switch { 1022 case err == io.EOF: 1023 return count, nil 1024 case err != nil: 1025 return count, err 1026 } 1027 } 1028 } 1029 1030 // Zip 将文件或目录进行zip打包.fpaths为源文件或目录的路径. 1031 func (kf *LkkFile) Zip(dst string, fpaths ...string) (bool, error) { 1032 dst = kf.AbsPath(dst) 1033 dstDir := kf.Dirname(dst) 1034 if !kf.IsDir(dstDir) { 1035 err := os.MkdirAll(dstDir, os.ModePerm) 1036 if err != nil { 1037 return false, err 1038 } 1039 } 1040 1041 fzip, err := os.Create(dst) 1042 if err != nil { 1043 return false, err 1044 } 1045 defer func() { 1046 _ = fzip.Close() 1047 }() 1048 1049 if len(fpaths) == 0 { 1050 return false, errors.New("[Zip] no input files.") 1051 } 1052 1053 var allfiles, files []string 1054 var fpath string 1055 for _, fpath = range fpaths { 1056 if kf.IsDir(fpath) { 1057 files = kf.FileTree(fpath, FILE_TREE_FILE, true) 1058 if len(files) != 0 { 1059 allfiles = append(allfiles, files...) 1060 } 1061 } else if fpath != "" { 1062 allfiles = append(allfiles, fpath) 1063 } 1064 } 1065 1066 if len(allfiles) == 0 { 1067 return false, errors.New("[Zip] no exist files.") 1068 } 1069 1070 zipw := zip.NewWriter(fzip) 1071 defer func() { 1072 _ = zipw.Close() 1073 }() 1074 1075 keys := make(map[string]bool) 1076 for _, fpath = range allfiles { 1077 if _, ok := keys[fpath]; ok || kf.AbsPath(fpath) == dst { 1078 continue 1079 } 1080 1081 fileToZip, err := os.Open(fpath) 1082 if err != nil { 1083 return false, fmt.Errorf("[Zip] failed to open %s: %s", fpath, err) 1084 } 1085 defer func() { 1086 _ = fileToZip.Close() 1087 }() 1088 1089 wr, _ := zipw.Create(fpath) 1090 keys[fpath] = true 1091 if _, err := io.Copy(wr, fileToZip); err != nil { 1092 return false, fmt.Errorf("[Zip] failed to write %s to zip: %s", fpath, err) 1093 } 1094 } 1095 1096 return true, nil 1097 } 1098 1099 // UnZip 解压zip文件.srcZip为zip文件路径,dstDir为解压目录. 1100 func (kf *LkkFile) UnZip(srcZip, dstDir string) (bool, error) { 1101 reader, err := zip.OpenReader(srcZip) 1102 if err != nil { 1103 return false, err 1104 } 1105 defer func() { 1106 _ = reader.Close() 1107 }() 1108 1109 dstDir = strings.TrimRight(kf.AbsPath(dstDir), "/\\") 1110 if !kf.IsDir(dstDir) { 1111 err := os.MkdirAll(dstDir, os.ModePerm) 1112 if err != nil { 1113 return false, err 1114 } 1115 } 1116 1117 // 迭代压缩文件中的文件 1118 for _, f := range reader.File { 1119 // Create diretory before create file 1120 //newPath := dstDir + string(os.PathSeparator) + strings.TrimLeft(f.Name, string(os.PathSeparator)) 1121 newPath := dstDir + "/" + strings.TrimLeft(f.Name, "/\\") 1122 parentDir := path.Dir(newPath) 1123 if !kf.IsDir(parentDir) { 1124 err := os.MkdirAll(parentDir, os.ModePerm) 1125 if err != nil { 1126 return false, err 1127 } 1128 } 1129 1130 if !f.FileInfo().IsDir() { 1131 if fcreate, err := os.Create(newPath); err == nil { 1132 if rc, err := f.Open(); err == nil { 1133 _, _ = io.Copy(fcreate, rc) 1134 _ = rc.Close() //不要用defer来关闭,如果文件太多的话,会报too many open files 的错误 1135 _ = fcreate.Close() 1136 } else { 1137 _ = fcreate.Close() 1138 return false, err 1139 } 1140 } else { 1141 return false, err 1142 } 1143 } 1144 } 1145 1146 return true, nil 1147 } 1148 1149 // IsZip 是否zip文件. 1150 func (kf *LkkFile) IsZip(fpath string) bool { 1151 ext := kf.GetExt(fpath) 1152 if ext != "zip" { 1153 return false 1154 } 1155 1156 f, err := os.Open(fpath) 1157 if err != nil { 1158 return false 1159 } 1160 defer func() { 1161 _ = f.Close() 1162 }() 1163 1164 buf := make([]byte, 4) 1165 n, err := f.Read(buf) 1166 1167 return err == nil && n == 4 && bytes.Equal(buf, []byte("PK\x03\x04")) 1168 }