diff --git a/cmd/stbak/cmd/archive.go b/cmd/stbak/cmd/archive.go index a142d68..be1cb9f 100644 --- a/cmd/stbak/cmd/archive.go +++ b/cmd/stbak/cmd/archive.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strconv" + "github.com/andybalholm/brotli" "github.com/klauspost/compress/zstd" "github.com/klauspost/pgzip" "github.com/pierrec/lz4/v4" @@ -291,6 +292,40 @@ func archive( hdr.Size = int64(fileSizeCounter.BytesRead) hdr.Name += compressionFormatZStandardSuffix + case compressionFormatBrotliKey: + // Get the compressed size for the header + file, err := os.Open(path) + if err != nil { + return err + } + + fileSizeCounter := counters.CounterWriter{ + Writer: io.Discard, + } + + br := brotli.NewWriter(&fileSizeCounter) + + if _, err := io.Copy(br, file); err != nil { + return err + } + + if err := br.Flush(); err != nil { + return err + } + if err := br.Close(); err != nil { + return err + } + if err := file.Close(); err != nil { + return err + } + + if hdr.PAXRecords == nil { + hdr.PAXRecords = map[string]string{} + } + hdr.PAXRecords[pax.STFSRecordUncompressedSize] = strconv.Itoa(int(hdr.Size)) + hdr.Size = int64(fileSizeCounter.BytesRead) + + hdr.Name += compressionFormatBrotliSuffix case compressionFormatNoneKey: default: return errUnsupportedCompressionFormat @@ -426,6 +461,39 @@ func archive( if err := file.Close(); err != nil { return err } + case compressionFormatBrotliKey: + // Compress and write the file + file, err := os.Open(path) + if err != nil { + return err + } + + br := brotli.NewWriter(tw) + + if _, err := io.Copy(br, file); err != nil { + return err + } + + if isRegular { + if _, err := io.Copy(br, file); err != nil { + return err + } + } else { + buf := make([]byte, controllers.BlockSize*recordSize) + if _, err := io.CopyBuffer(br, file, buf); err != nil { + return err + } + } + + if err := br.Flush(); err != nil { + return err + } + if err := br.Close(); err != nil { + return err + } + if err := file.Close(); err != nil { + return err + } case compressionFormatNoneKey: // Write the file file, err := os.Open(path) diff --git a/cmd/stbak/cmd/recovery_fetch.go b/cmd/stbak/cmd/recovery_fetch.go index 8a601cd..8fdfd99 100644 --- a/cmd/stbak/cmd/recovery_fetch.go +++ b/cmd/stbak/cmd/recovery_fetch.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" + "github.com/andybalholm/brotli" "github.com/klauspost/compress/zstd" "github.com/klauspost/pgzip" "github.com/pierrec/lz4/v4" @@ -170,6 +171,12 @@ func restoreFromRecordAndBlock( if _, err := io.Copy(dstFile, zz); err != nil { return err } + case compressionFormatBrotliKey: + br := brotli.NewReader(tr) + + if _, err := io.Copy(dstFile, br); err != nil { + return err + } case compressionFormatNoneKey: if _, err := io.Copy(dstFile, tr); err != nil { return err diff --git a/cmd/stbak/cmd/recovery_index.go b/cmd/stbak/cmd/recovery_index.go index bd17d22..0a77e21 100644 --- a/cmd/stbak/cmd/recovery_index.go +++ b/cmd/stbak/cmd/recovery_index.go @@ -277,6 +277,8 @@ func indexHeader( hdr.Name = strings.TrimSuffix(hdr.Name, compressionFormatLZ4Suffix) case compressionFormatZStandardKey: hdr.Name = strings.TrimSuffix(hdr.Name, compressionFormatZStandardSuffix) + case compressionFormatBrotliKey: + hdr.Name = strings.TrimSuffix(hdr.Name, compressionFormatBrotliSuffix) case compressionFormatNoneKey: default: return errUnsupportedCompressionFormat diff --git a/cmd/stbak/cmd/root.go b/cmd/stbak/cmd/root.go index b80fa60..e1160eb 100644 --- a/cmd/stbak/cmd/root.go +++ b/cmd/stbak/cmd/root.go @@ -29,10 +29,13 @@ const ( compressionFormatZStandardKey = "zstandard" compressionFormatZStandardSuffix = ".zst" + + compressionFormatBrotliKey = "brotli" + compressionFormatBrotliSuffix = ".br" ) var ( - knownCompressionFormats = []string{compressionFormatNoneKey, compressionFormatGZipKey, compressionFormatParallelGZipKey, compressionFormatLZ4Key, compressionFormatZStandardKey} + knownCompressionFormats = []string{compressionFormatNoneKey, compressionFormatGZipKey, compressionFormatParallelGZipKey, compressionFormatLZ4Key, compressionFormatZStandardKey, compressionFormatBrotliKey} errUnknownCompressionFormat = errors.New("unknown compression format") errUnsupportedCompressionFormat = errors.New("unsupported compression format") diff --git a/go.mod b/go.mod index d80eabe..3f3a2a7 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( ) require ( + github.com/andybalholm/brotli v1.0.4 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/gofrs/uuid v3.2.0+incompatible // indirect github.com/hashicorp/hcl v1.0.0 // indirect diff --git a/go.sum b/go.sum index d8faa69..60cf68f 100644 --- a/go.sum +++ b/go.sum @@ -53,6 +53,8 @@ github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuN github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apmckinlay/gsuneido v0.0.0-20180907175622-1f10244968e3/go.mod h1:hJnaqxrCRgMCTWtpNz9XUFkBCREiQdlcyK6YNmOfroM= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=