github.com/webx-top/com@v1.2.12/file.go (about) 1 // Copyright 2013 com authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 // not use this file except in compliance with the License. You may obtain 5 // a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations 13 // under the License. 14 15 package com 16 17 import ( 18 "archive/tar" 19 "archive/zip" 20 "bufio" 21 "bytes" 22 "compress/gzip" 23 "errors" 24 "fmt" 25 "io" 26 "log" 27 "math" 28 "os" 29 "path/filepath" 30 "regexp" 31 "strconv" 32 "strings" 33 "time" 34 ) 35 36 // Storage unit constants. 37 const ( 38 Byte = 1 39 KByte = Byte * 1024 40 MByte = KByte * 1024 41 GByte = MByte * 1024 42 TByte = GByte * 1024 43 PByte = TByte * 1024 44 EByte = PByte * 1024 45 ) 46 47 func logn(n, b float64) float64 { 48 return math.Log(n) / math.Log(b) 49 } 50 51 func humanateBytes(s uint64, base float64, sizes []string) string { 52 if s < 10 { 53 return fmt.Sprintf("%dB", s) 54 } 55 e := math.Floor(logn(float64(s), base)) 56 suffix := sizes[int(e)] 57 val := float64(s) / math.Pow(base, math.Floor(e)) 58 f := "%.0f" 59 if val < 10 { 60 f = "%.1f" 61 } 62 63 return fmt.Sprintf(f+"%s", val, suffix) 64 } 65 66 // HumaneFileSize calculates the file size and generate user-friendly string. 67 func HumaneFileSize(s uint64) string { 68 sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"} 69 return humanateBytes(s, 1024, sizes) 70 } 71 72 // FileMTime returns file modified time and possible error. 73 func FileMTime(file string) (int64, error) { 74 f, err := os.Stat(file) 75 if err != nil { 76 return 0, err 77 } 78 return f.ModTime().Unix(), nil 79 } 80 81 // TrimFileName trim the file name 82 func TrimFileName(ppath string) string { 83 if len(ppath) == 0 { 84 return ppath 85 } 86 for i := len(ppath) - 1; i >= 0; i-- { 87 if ppath[i] == '/' || ppath[i] == '\\' { 88 if i+1 < len(ppath) { 89 return ppath[0 : i+1] 90 } 91 return ppath 92 } 93 } 94 return `` 95 } 96 97 func BaseFileName(ppath string) string { 98 if len(ppath) == 0 { 99 return ppath 100 } 101 for i := len(ppath) - 1; i >= 0; i-- { 102 if ppath[i] == '/' || ppath[i] == '\\' { 103 if i+1 < len(ppath) { 104 return ppath[i+1:] 105 } 106 return `` 107 } 108 } 109 return ppath 110 } 111 112 func HasPathSeperatorPrefix(ppath string) bool { 113 return strings.HasPrefix(ppath, `/`) || strings.HasPrefix(ppath, `\`) 114 } 115 116 func HasPathSeperatorSuffix(ppath string) bool { 117 return strings.HasSuffix(ppath, `/`) || strings.HasSuffix(ppath, `\`) 118 } 119 120 var pathSeperatorRegex = regexp.MustCompile(`(\\|/)`) 121 var winPathSeperatorRegex = regexp.MustCompile(`[\\]+`) 122 123 func GetPathSeperator(ppath string) string { 124 matches := pathSeperatorRegex.FindAllStringSubmatch(ppath, 1) 125 if len(matches) > 0 && len(matches[0]) > 1 { 126 return matches[0][1] 127 } 128 return `` 129 } 130 131 func RealPath(fullPath string) string { 132 if len(fullPath) == 0 { 133 return fullPath 134 } 135 var root string 136 var pathSeperator string 137 if strings.HasPrefix(fullPath, `/`) { 138 pathSeperator = `/` 139 } else { 140 pathSeperator = GetPathSeperator(fullPath) 141 if pathSeperator == `/` { 142 fullPath = `/` + fullPath 143 } else { 144 cleanedFullPath := winPathSeperatorRegex.ReplaceAllString(fullPath, `\`) 145 parts := strings.SplitN(cleanedFullPath, `\`, 2) 146 if !strings.HasSuffix(parts[0], `:`) { 147 if len(parts[0]) > 0 { 148 parts[0] = `c:\` + parts[0] 149 } else { 150 parts[0] = `c:` 151 } 152 } 153 root = parts[0] + `\` 154 if len(parts) != 2 { 155 return fullPath 156 } 157 fullPath = parts[1] 158 } 159 } 160 parts := pathSeperatorRegex.Split(fullPath, -1) 161 result := make([]string, 0, len(parts)) 162 for _, part := range parts { 163 if part == `.` { 164 continue 165 } 166 if part == `..` { 167 if len(result) <= 1 { 168 continue 169 } 170 result = result[0 : len(result)-1] 171 continue 172 } 173 result = append(result, part) 174 } 175 return root + strings.Join(result, pathSeperator) 176 } 177 178 func SplitFileDirAndName(ppath string) (dir string, name string) { 179 if len(ppath) == 0 { 180 return 181 } 182 for i := len(ppath) - 1; i >= 0; i-- { 183 if ppath[i] == '/' || ppath[i] == '\\' { 184 if i+1 < len(ppath) { 185 return ppath[0:i], ppath[i+1:] 186 } 187 dir = ppath[0:i] 188 return 189 } 190 } 191 name = ppath 192 return 193 } 194 195 // FileSize returns file size in bytes and possible error. 196 func FileSize(file string) (int64, error) { 197 f, err := os.Stat(file) 198 if err != nil { 199 return 0, err 200 } 201 return f.Size(), nil 202 } 203 204 // Copy copies file from source to target path. 205 func Copy(src, dest string) error { 206 // Gather file information to set back later. 207 si, err := os.Lstat(src) 208 if err != nil { 209 return fmt.Errorf("couldn't open source file: %w", err) 210 } 211 212 // Handle symbolic link. 213 if si.Mode()&os.ModeSymlink != 0 { 214 target, err := os.Readlink(src) 215 if err != nil { 216 return err 217 } 218 // NOTE: os.Chmod and os.Chtimes don't recoganize symbolic link, 219 // which will lead "no such file or directory" error. 220 return os.Symlink(target, dest) 221 } 222 223 sr, err := os.Open(src) 224 if err != nil { 225 return fmt.Errorf("couldn't open source file: %w", err) 226 } 227 defer sr.Close() 228 229 dw, err := os.Create(dest) 230 if err != nil { 231 return fmt.Errorf("couldn't open dest file: %w", err) 232 } 233 defer dw.Close() 234 235 if _, err = io.Copy(dw, sr); err != nil { 236 return fmt.Errorf("writing to output file failed: %w", err) 237 } 238 dw.Sync() 239 240 // Set back file information. 241 if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil { 242 return err 243 } 244 return os.Chmod(dest, si.Mode()) 245 } 246 247 func Remove(name string) error { 248 err := os.Remove(name) 249 if err == nil || os.IsNotExist(err) { 250 return nil 251 } 252 return err 253 } 254 255 /* 256 GoLang: os.Rename() give error "invalid cross-device link" for Docker container with Volumes. 257 Rename(source, destination) will work moving file between folders 258 */ 259 func Rename(src, dest string) error { 260 err := os.Rename(src, dest) 261 if err == nil { 262 return nil 263 } 264 if !strings.HasSuffix(err.Error(), `invalid cross-device link`) { 265 return err 266 } 267 err = Copy(src, dest) 268 if err != nil { 269 if !strings.HasSuffix(err.Error(), `operation not permitted`) { 270 return err 271 } 272 } 273 // The copy was successful, so now delete the original file 274 err = Remove(src) 275 if err != nil { 276 return fmt.Errorf("failed removing original file: %w", err) 277 } 278 return nil 279 } 280 281 // WriteFile writes data to a file named by filename. 282 // If the file does not exist, WriteFile creates it 283 // and its upper level paths. 284 func WriteFile(filename string, data []byte) error { 285 os.MkdirAll(filepath.Dir(filename), os.ModePerm) 286 return os.WriteFile(filename, data, 0655) 287 } 288 289 // CreateFile create file 290 func CreateFile(filename string) (fp *os.File, err error) { 291 fp, err = os.Create(filename) 292 if err != nil { 293 if !os.IsNotExist(err) { 294 return 295 } 296 err = MkdirAll(filepath.Dir(filename), os.ModePerm) 297 if err != nil { 298 return 299 } 300 fp, err = os.Create(filename) 301 } 302 return 303 } 304 305 // IsFile returns true if given path is a file, 306 // or returns false when it's a directory or does not exist. 307 func IsFile(filePath string) bool { 308 f, e := os.Stat(filePath) 309 if e != nil { 310 return false 311 } 312 return !f.IsDir() 313 } 314 315 // IsExist checks whether a file or directory exists. 316 // It returns false when the file or directory does not exist. 317 func IsExist(path string) bool { 318 _, err := os.Stat(path) 319 return err == nil || os.IsExist(err) 320 } 321 322 func Unlink(file string) bool { 323 return os.Remove(file) == nil 324 } 325 326 // SaveFile saves content type '[]byte' to file by given path. 327 // It returns error when fail to finish operation. 328 func SaveFile(filePath string, b []byte) (int, error) { 329 os.MkdirAll(filepath.Dir(filePath), os.ModePerm) 330 fw, err := os.Create(filePath) 331 if err != nil { 332 return 0, err 333 } 334 defer fw.Close() 335 return fw.Write(b) 336 } 337 338 // SaveFileS saves content type 'string' to file by given path. 339 // It returns error when fail to finish operation. 340 func SaveFileS(filePath string, s string) (int, error) { 341 return SaveFile(filePath, []byte(s)) 342 } 343 344 // ReadFile reads data type '[]byte' from file by given path. 345 // It returns error when fail to finish operation. 346 func ReadFile(filePath string) ([]byte, error) { 347 b, err := os.ReadFile(filePath) 348 if err != nil { 349 return []byte(""), err 350 } 351 return b, nil 352 } 353 354 // ReadFileS reads data type 'string' from file by given path. 355 // It returns error when fail to finish operation. 356 func ReadFileS(filePath string) (string, error) { 357 b, err := ReadFile(filePath) 358 return string(b), err 359 } 360 361 // Zip 压缩为zip 362 func Zip(srcDirPath string, destFilePath string, args ...*regexp.Regexp) (n int64, err error) { 363 root, err := filepath.Abs(srcDirPath) 364 if err != nil { 365 return 0, err 366 } 367 368 f, err := os.Create(destFilePath) 369 if err != nil { 370 return 371 } 372 defer f.Close() 373 374 w := zip.NewWriter(f) 375 var regexpIgnoreFile, regexpFileName *regexp.Regexp 376 argLen := len(args) 377 if argLen > 1 { 378 regexpIgnoreFile = args[1] 379 regexpFileName = args[0] 380 } else if argLen == 1 { 381 regexpFileName = args[0] 382 } 383 err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 384 if err != nil { 385 return err 386 } 387 name := info.Name() 388 nameBytes := []byte(name) 389 if regexpIgnoreFile != nil && regexpIgnoreFile.Match(nameBytes) { 390 if info.IsDir() { 391 return filepath.SkipDir 392 } 393 return nil 394 } else if info.IsDir() { 395 return nil 396 } 397 if regexpFileName != nil && !regexpFileName.Match(nameBytes) { 398 return nil 399 } 400 relativePath := strings.TrimPrefix(path, root) 401 relativePath = strings.Replace(relativePath, `\`, `/`, -1) 402 relativePath = strings.TrimPrefix(relativePath, `/`) 403 f, err := w.Create(relativePath) 404 if err != nil { 405 return err 406 } 407 sf, err := os.Open(path) 408 if err != nil { 409 return err 410 } 411 defer sf.Close() 412 _, err = io.Copy(f, sf) 413 return err 414 }) 415 416 err = w.Close() 417 if err != nil { 418 return 0, err 419 } 420 421 fi, err := f.Stat() 422 if err != nil { 423 n = fi.Size() 424 } 425 return 426 } 427 428 // Unzip unzips .zip file to 'destPath'. 429 // It returns error when fail to finish operation. 430 func Unzip(srcPath, destPath string) error { 431 // Open a zip archive for reading 432 r, err := zip.OpenReader(srcPath) 433 if err != nil { 434 return err 435 } 436 defer r.Close() 437 438 // Iterate through the files in the archive 439 for _, f := range r.File { 440 // Get files from archive 441 rc, err := f.Open() 442 if err != nil { 443 return err 444 } 445 446 dir := filepath.Dir(f.Name) 447 // Create directory before create file 448 os.MkdirAll(destPath+"/"+dir, os.ModePerm) 449 450 if f.FileInfo().IsDir() { 451 continue 452 } 453 454 // Write data to file 455 fw, _ := os.Create(filepath.Join(destPath, f.Name)) 456 if err != nil { 457 return err 458 } 459 _, err = io.Copy(fw, rc) 460 461 if fw != nil { 462 fw.Close() 463 } 464 if err != nil { 465 return err 466 } 467 } 468 return nil 469 } 470 471 func TarGz(srcDirPath string, destFilePath string) error { 472 fw, err := os.Create(destFilePath) 473 if err != nil { 474 return err 475 } 476 defer fw.Close() 477 478 // Gzip writer 479 gw := gzip.NewWriter(fw) 480 defer gw.Close() 481 482 // Tar writer 483 tw := tar.NewWriter(gw) 484 defer tw.Close() 485 486 // Check if it's a file or a directory 487 f, err := os.Open(srcDirPath) 488 if err != nil { 489 return err 490 } 491 fi, err := f.Stat() 492 if err != nil { 493 return err 494 } 495 if fi.IsDir() { 496 // handle source directory 497 fmt.Println("Cerating tar.gz from directory...") 498 if err := tarGzDir(srcDirPath, filepath.Base(srcDirPath), tw); err != nil { 499 return err 500 } 501 } else { 502 // handle file directly 503 fmt.Println("Cerating tar.gz from " + fi.Name() + "...") 504 if err := tarGzFile(srcDirPath, fi.Name(), tw, fi); err != nil { 505 return err 506 } 507 } 508 fmt.Println("Well done!") 509 return err 510 } 511 512 // Deal with directories 513 // if find files, handle them with tarGzFile 514 // Every recurrence append the base path to the recPath 515 // recPath is the path inside of tar.gz 516 func tarGzDir(srcDirPath string, recPath string, tw *tar.Writer) error { 517 // Open source diretory 518 dir, err := os.Open(srcDirPath) 519 if err != nil { 520 return err 521 } 522 defer dir.Close() 523 524 // Get file info slice 525 fis, err := dir.Readdir(0) 526 if err != nil { 527 return err 528 } 529 for _, fi := range fis { 530 // Append path 531 curPath := srcDirPath + "/" + fi.Name() 532 // Check it is directory or file 533 if fi.IsDir() { 534 // Directory 535 // (Directory won't add unitl all subfiles are added) 536 fmt.Printf("Adding path...%s\n", curPath) 537 tarGzDir(curPath, recPath+"/"+fi.Name(), tw) 538 } else { 539 // File 540 fmt.Printf("Adding file...%s\n", curPath) 541 } 542 543 tarGzFile(curPath, recPath+"/"+fi.Name(), tw, fi) 544 } 545 return err 546 } 547 548 // Deal with files 549 func tarGzFile(srcFile string, recPath string, tw *tar.Writer, fi os.FileInfo) error { 550 if fi.IsDir() { 551 // Create tar header 552 hdr := new(tar.Header) 553 // if last character of header name is '/' it also can be directory 554 // but if you don't set Typeflag, error will occur when you untargz 555 hdr.Name = recPath + "/" 556 hdr.Typeflag = tar.TypeDir 557 hdr.Size = 0 558 //hdr.Mode = 0755 | c_ISDIR 559 hdr.Mode = int64(fi.Mode()) 560 hdr.ModTime = fi.ModTime() 561 562 // Write hander 563 err := tw.WriteHeader(hdr) 564 if err != nil { 565 return err 566 } 567 } else { 568 // File reader 569 fr, err := os.Open(srcFile) 570 if err != nil { 571 return err 572 } 573 defer fr.Close() 574 575 // Create tar header 576 hdr := new(tar.Header) 577 hdr.Name = recPath 578 hdr.Size = fi.Size() 579 hdr.Mode = int64(fi.Mode()) 580 hdr.ModTime = fi.ModTime() 581 582 // Write hander 583 err = tw.WriteHeader(hdr) 584 if err != nil { 585 return err 586 } 587 588 // Write file data 589 _, err = io.Copy(tw, fr) 590 if err != nil { 591 return err 592 } 593 } 594 return nil 595 } 596 597 // UnTarGz ungzips and untars .tar.gz file to 'destPath' and returns sub-directories. 598 // It returns error when fail to finish operation. 599 func UnTarGz(srcFilePath string, destDirPath string) ([]string, error) { 600 // Create destination directory 601 os.Mkdir(destDirPath, os.ModePerm) 602 603 fr, err := os.Open(srcFilePath) 604 if err != nil { 605 return nil, err 606 } 607 defer fr.Close() 608 609 // Gzip reader 610 gr, err := gzip.NewReader(fr) 611 if err != nil { 612 return nil, err 613 } 614 defer gr.Close() 615 616 // Tar reader 617 tr := tar.NewReader(gr) 618 619 dirs := make([]string, 0, 5) 620 for { 621 hdr, err := tr.Next() 622 if err == io.EOF { 623 // End of tar archive 624 break 625 } 626 627 // Check if it is directory or file 628 if hdr.Typeflag != tar.TypeDir { 629 // Get files from archive 630 // Create directory before create file 631 dir := filepath.Dir(hdr.Name) 632 os.MkdirAll(destDirPath+"/"+dir, os.ModePerm) 633 dirs = AppendStr(dirs, dir) 634 635 // Write data to file 636 fw, _ := os.Create(destDirPath + "/" + hdr.Name) 637 if err != nil { 638 return nil, err 639 } 640 _, err = io.Copy(fw, tr) 641 if err != nil { 642 return nil, err 643 } 644 } 645 } 646 return dirs, nil 647 } 648 649 var ( 650 selfPath string 651 selfDir string 652 ) 653 654 func SelfPath() string { 655 if len(selfPath) == 0 { 656 selfPath, _ = filepath.Abs(os.Args[0]) 657 } 658 return selfPath 659 } 660 661 func SelfDir() string { 662 if len(selfDir) == 0 { 663 selfDir = filepath.Dir(SelfPath()) 664 } 665 return selfDir 666 } 667 668 // FileExists reports whether the named file or directory exists. 669 func FileExists(name string) bool { 670 if _, err := os.Stat(name); err != nil { 671 if os.IsNotExist(err) { 672 return false 673 } 674 } 675 return true 676 } 677 678 // SearchFile search a file in paths. 679 // this is offen used in search config file in /etc ~/ 680 func SearchFile(filename string, paths ...string) (fullpath string, err error) { 681 for _, path := range paths { 682 if fullpath = filepath.Join(path, filename); FileExists(fullpath) { 683 return 684 } 685 } 686 err = errors.New(fullpath + " not found in paths") 687 return 688 } 689 690 // GrepFile like command grep -E 691 // for example: GrepFile(`^hello`, "hello.txt") 692 // \n is striped while read 693 func GrepFile(patten string, filename string) (lines []string, err error) { 694 re, err := regexp.Compile(patten) 695 if err != nil { 696 return 697 } 698 699 lines = make([]string, 0) 700 err = SeekFileLines(filename, func(line string) error { 701 if re.MatchString(line) { 702 lines = append(lines, line) 703 } 704 return nil 705 }) 706 return lines, err 707 } 708 709 func SeekFileLines(filename string, callback func(string) error) error { 710 fd, err := os.Open(filename) 711 if err != nil { 712 return err 713 } 714 defer fd.Close() 715 return SeekLines(fd, callback) 716 } 717 718 type LineReader interface { 719 ReadLine() (line []byte, isPrefix bool, err error) 720 } 721 722 func SeekLines(r io.Reader, callback func(string) error) (err error) { 723 var reader LineReader 724 var prefix string 725 if rd, ok := r.(LineReader); ok { 726 reader = rd 727 } else { 728 reader = bufio.NewReader(r) 729 } 730 for { 731 byteLine, isPrefix, er := reader.ReadLine() 732 if er != nil && er != io.EOF { 733 return er 734 } 735 line := string(byteLine) 736 if isPrefix { 737 prefix += line 738 continue 739 } 740 line = prefix + line 741 if e := callback(line); e != nil { 742 return e 743 } 744 prefix = "" 745 if er == io.EOF { 746 break 747 } 748 } 749 return nil 750 } 751 752 func Readbuf(r io.Reader, length int) ([]byte, error) { 753 buf := make([]byte, length) 754 offset := 0 755 756 for offset < length { 757 read, err := r.Read(buf[offset:]) 758 if err != nil { 759 return buf, err 760 } 761 762 offset += read 763 } 764 765 return buf, nil 766 } 767 768 func Bytes2readCloser(b []byte) io.ReadCloser { 769 return io.NopCloser(bytes.NewBuffer(b)) 770 } 771 772 const HalfSecond = 500 * time.Millisecond // 0.5秒 773 var debugFileIsCompleted bool 774 775 // FileIsCompleted 等待文件有数据且已写完 776 // 费时操作 放在子线程中执行 777 // @param file 文件 778 // @param start 需要传入 time.Now.Local(),用于兼容遍历的情况 779 // @return true:已写完 false:外部程序阻塞或者文件不存在 780 // 翻译自:https://blog.csdn.net/northernice/article/details/115986671 781 func FileIsCompleted(file *os.File, start time.Time) (bool, error) { 782 var ( 783 fileLength int64 784 i int 785 waitTime = HalfSecond 786 lastModTime time.Time 787 finished int 788 ) 789 for { 790 fi, err := file.Stat() 791 if err != nil { 792 return false, err 793 } 794 if debugFileIsCompleted { 795 fmt.Printf("FileIsCompleted> size:%d (time:%s) [finished:%d]\n", fi.Size(), fi.ModTime(), finished) 796 } 797 //文件在外部一直在填充数据,每次进入循环体时,文件大小都会改变,一直到不改变时,说明文件数据填充完毕 或者文件大小一直都是0(外部程序阻塞) 798 //判断文件大小是否有改变 799 if fi.Size() > fileLength { //有改变说明还未写完 800 fileLength = fi.Size() 801 if i%120 == 0 { //每隔1分钟输出一次日志 (i为120时:120*500/1000=60秒) 802 log.Println("文件: " + fi.Name() + " 正在被填充,请稍候...") 803 } 804 time.Sleep(waitTime) //半秒后再循环一次 805 lastModTime = fi.ModTime() 806 } else { //否则:只能等于 不会小于,等于有两种情况,一种是数据写完了,一种是外部程序阻塞了,导致文件大小一直为0 807 if lastModTime.IsZero() { 808 lastModTime = fi.ModTime() 809 } else { 810 if fileLength != fi.Size() { 811 fileLength = fi.Size() 812 } else if lastModTime.Equal(fi.ModTime()) { 813 if fi.Size() != 0 { 814 if finished < 3 { 815 time.Sleep(waitTime) 816 finished++ 817 continue 818 } 819 return true, nil 820 } 821 } 822 } 823 //等待外部程序开始写 只等60秒 120*500/1000=60秒 824 //每隔1分钟输出一次日志 (i为120时:120*500/1000=60秒) 825 if i%120 == 0 { 826 log.Println("文件: " + fi.Name() + " 大小为" + strconv.FormatInt(fi.Size(), 10) + ",正在等待外部程序填充,已等待:" + time.Since(start).String()) 827 } 828 829 //如果一直(i为120时:120*500/1000=60秒)等于0,说明外部程序阻塞了 830 if i >= 3600 { //120为1分钟 3600为30分钟 831 log.Println("文件: " + fi.Name() + " 大小在:" + time.Since(start).String() + " 内始终为" + strconv.FormatInt(fi.Size(), 10) + ",说明:在[程序监测时间内]文件写入进程依旧在运行,程序监测时间结束") //入库未完成或发生阻塞 832 return false, nil 833 } 834 835 time.Sleep(waitTime) 836 } 837 if finished > 0 { 838 finished = 0 839 } 840 i++ 841 } 842 }