compress.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "io"
  6. "os"
  7. "sync/atomic"
  8. "code.cloudfoundry.org/bytefmt"
  9. "github.com/schollz/progressbar/v3"
  10. "github.com/pierrec/cmdflag"
  11. "github.com/pierrec/lz4/v4"
  12. )
  13. // Compress compresses a set of files or from stdin to stdout.
  14. func Compress(fs *flag.FlagSet) cmdflag.Handler {
  15. var blockMaxSize string
  16. fs.StringVar(&blockMaxSize, "size", "4M", "block max size [64K,256K,1M,4M]")
  17. var blockChecksum bool
  18. fs.BoolVar(&blockChecksum, "bc", false, "enable block checksum")
  19. var streamChecksum bool
  20. fs.BoolVar(&streamChecksum, "sc", false, "disable stream checksum")
  21. var level int
  22. fs.IntVar(&level, "l", 0, "compression level (0=fastest)")
  23. var concurrency int
  24. fs.IntVar(&concurrency, "c", -1, "concurrency (default=all CPUs")
  25. return func(args ...string) (int, error) {
  26. sz, err := bytefmt.ToBytes(blockMaxSize)
  27. if err != nil {
  28. return 0, err
  29. }
  30. zw := lz4.NewWriter(nil)
  31. options := []lz4.Option{
  32. lz4.BlockChecksumOption(blockChecksum),
  33. lz4.BlockSizeOption(lz4.BlockSize(sz)),
  34. lz4.ChecksumOption(streamChecksum),
  35. lz4.CompressionLevelOption(lz4.CompressionLevel(level)),
  36. lz4.ConcurrencyOption(concurrency),
  37. }
  38. if err := zw.Apply(options...); err != nil {
  39. return 0, err
  40. }
  41. // Use stdin/stdout if no file provided.
  42. if len(args) == 0 {
  43. zw.Reset(os.Stdout)
  44. _, err := io.Copy(zw, os.Stdin)
  45. if err != nil {
  46. return 0, err
  47. }
  48. return 0, zw.Close()
  49. }
  50. for fidx, filename := range args {
  51. // Input file.
  52. file, err := os.Open(filename)
  53. if err != nil {
  54. return fidx, err
  55. }
  56. finfo, err := file.Stat()
  57. if err != nil {
  58. return fidx, err
  59. }
  60. mode := finfo.Mode() // use the same mode for the output file
  61. // Accumulate compressed bytes num.
  62. var (
  63. zsize int64
  64. size = finfo.Size()
  65. )
  66. if size > 0 {
  67. // Progress bar setup.
  68. numBlocks := int(size) / int(sz)
  69. bar := progressbar.NewOptions(numBlocks,
  70. // File transfers are usually slow, make sure we display the bar at 0%.
  71. progressbar.OptionSetRenderBlankState(true),
  72. // Display the filename.
  73. progressbar.OptionSetDescription(filename),
  74. progressbar.OptionClearOnFinish(),
  75. )
  76. err = zw.Apply(
  77. lz4.OnBlockDoneOption(func(n int) {
  78. _ = bar.Add(1)
  79. atomic.AddInt64(&zsize, int64(n))
  80. }),
  81. )
  82. if err != nil {
  83. return 0, err
  84. }
  85. }
  86. // Output file.
  87. zfilename := fmt.Sprintf("%s%s", filename, lz4Extension)
  88. zfile, err := os.OpenFile(zfilename, os.O_CREATE|os.O_WRONLY, mode)
  89. if err != nil {
  90. return fidx, err
  91. }
  92. zw.Reset(zfile)
  93. // Compress.
  94. _, err = io.Copy(zw, file)
  95. if err != nil {
  96. return fidx, err
  97. }
  98. for _, c := range []io.Closer{zw, zfile} {
  99. err := c.Close()
  100. if err != nil {
  101. return fidx, err
  102. }
  103. }
  104. if size > 0 {
  105. fmt.Printf("%s %.02f%%\n", zfilename, float64(zsize)*100/float64(size))
  106. }
  107. }
  108. return len(args), nil
  109. }
  110. }