main.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package main
  2. import (
  3. "bufio"
  4. "errors"
  5. "flag"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "os"
  10. "path/filepath"
  11. "strings"
  12. "sync"
  13. "time"
  14. "github.com/klauspost/compress/s2"
  15. "github.com/klauspost/compress/s2/cmd/internal/readahead"
  16. )
  17. var (
  18. safe = flag.Bool("safe", false, "Do not overwrite output files")
  19. stdout = flag.Bool("c", false, "Write all output to stdout. Multiple input files will be concatenated")
  20. remove = flag.Bool("rm", false, "Delete source file(s) after successful decompression")
  21. quiet = flag.Bool("q", false, "Don't write any output to terminal, except errors")
  22. bench = flag.Int("bench", 0, "Run benchmark n times. No output will be written")
  23. help = flag.Bool("help", false, "Display help")
  24. version = "(dev)"
  25. date = "(unknown)"
  26. )
  27. func main() {
  28. flag.Parse()
  29. r := s2.NewReader(nil)
  30. // No args, use stdin/stdout
  31. args := flag.Args()
  32. if len(args) == 0 || *help {
  33. _, _ = fmt.Fprintf(os.Stderr, "s2 decompress v%v, built at %v.\n\n", version, date)
  34. _, _ = fmt.Fprintf(os.Stderr, "Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n"+
  35. "Copyright (c) 2019 Klaus Post. All rights reserved.\n\n")
  36. _, _ = fmt.Fprintln(os.Stderr, `Usage: s2d [options] file1 file2
  37. Decompresses all files supplied as input. Input files must end with '.s2' or '.snappy'.
  38. Output file names have the extension removed. By default output files will be overwritten.
  39. Use - as the only file name to read from stdin and write to stdout.
  40. Wildcards are accepted: testdir/*.txt will compress all files in testdir ending with .txt
  41. Directories can be wildcards as well. testdir/*/*.txt will match testdir/subdir/b.txt
  42. Options:`)
  43. flag.PrintDefaults()
  44. }
  45. if len(args) == 1 && args[0] == "-" {
  46. r.Reset(os.Stdin)
  47. _, err := io.Copy(os.Stdout, r)
  48. exitErr(err)
  49. return
  50. }
  51. var files []string
  52. for _, pattern := range args {
  53. found, err := filepath.Glob(pattern)
  54. exitErr(err)
  55. if len(found) == 0 {
  56. exitErr(fmt.Errorf("unable to find file %v", pattern))
  57. }
  58. files = append(files, found...)
  59. }
  60. *quiet = *quiet || *stdout
  61. allFiles := files
  62. for i := 0; i < *bench; i++ {
  63. files = append(files, allFiles...)
  64. }
  65. for _, filename := range files {
  66. dstFilename := filename
  67. switch {
  68. case strings.HasSuffix(filename, ".s2"):
  69. dstFilename = strings.TrimSuffix(filename, ".s2")
  70. case strings.HasSuffix(filename, ".snappy"):
  71. dstFilename = strings.TrimSuffix(filename, ".snappy")
  72. default:
  73. fmt.Println("Skipping", filename)
  74. continue
  75. }
  76. if *bench > 0 {
  77. dstFilename = "(discarded)"
  78. }
  79. func() {
  80. var closeOnce sync.Once
  81. if !*quiet {
  82. fmt.Println("Decompressing", filename, "->", dstFilename)
  83. }
  84. // Input file.
  85. file, err := os.Open(filename)
  86. exitErr(err)
  87. defer closeOnce.Do(func() { file.Close() })
  88. rc := rCounter{in: file}
  89. src, err := readahead.NewReaderSize(&rc, 2, 4<<20)
  90. exitErr(err)
  91. defer src.Close()
  92. finfo, err := file.Stat()
  93. exitErr(err)
  94. mode := finfo.Mode() // use the same mode for the output file
  95. if *safe {
  96. _, err := os.Stat(dstFilename)
  97. if !os.IsNotExist(err) {
  98. exitErr(errors.New("destination files exists"))
  99. }
  100. }
  101. var out io.Writer
  102. switch {
  103. case *bench > 0:
  104. out = ioutil.Discard
  105. case *stdout:
  106. out = os.Stdout
  107. default:
  108. dstFile, err := os.OpenFile(dstFilename, os.O_CREATE|os.O_WRONLY, mode)
  109. exitErr(err)
  110. defer dstFile.Close()
  111. bw := bufio.NewWriterSize(dstFile, 4<<20)
  112. defer bw.Flush()
  113. out = bw
  114. }
  115. r.Reset(src)
  116. start := time.Now()
  117. output, err := io.Copy(out, r)
  118. exitErr(err)
  119. if !*quiet {
  120. elapsed := time.Since(start)
  121. mbPerSec := (float64(output) / (1024 * 1024)) / (float64(elapsed) / (float64(time.Second)))
  122. pct := float64(output) * 100 / float64(rc.n)
  123. fmt.Printf("%d -> %d [%.02f%%]; %.01fMB/s\n", rc.n, output, pct, mbPerSec)
  124. }
  125. if *remove {
  126. closeOnce.Do(func() {
  127. file.Close()
  128. err := os.Remove(filename)
  129. exitErr(err)
  130. })
  131. }
  132. }()
  133. }
  134. }
  135. func exitErr(err error) {
  136. if err != nil {
  137. fmt.Fprintln(os.Stderr, "ERROR:", err.Error())
  138. os.Exit(2)
  139. }
  140. }
  141. type rCounter struct {
  142. n int
  143. in io.Reader
  144. }
  145. func (w *rCounter) Read(p []byte) (n int, err error) {
  146. n, err = w.in.Read(p)
  147. w.n += n
  148. return n, err
  149. }