@@ -685,7 +685,8 @@ The 10 byte 'stream identifier' of the second stream can optionally be stripped,
685
685
686
686
Blocks can be concatenated using the ` ConcatBlocks ` function.
687
687
688
- Snappy blocks/streams can safely be concatenated with S2 blocks and streams.
688
+ Snappy blocks/streams can safely be concatenated with S2 blocks and streams.
689
+ Streams with indexes (see below) will currently not work on concatenated streams.
689
690
690
691
# Stream Seek Index
691
692
@@ -701,9 +702,27 @@ so the output remains compatible with other decoders.
701
702
To automatically add an index to a stream, add ` WriterAddIndex() ` option to your writer.
702
703
Then the index will be added to the stream when ` Close() ` is called.
703
704
705
+ ```
706
+ // Add Index to stream...
707
+ enc := s2.NewWriter(w, s2.WriterAddIndex())
708
+ io.Copy(enc, r)
709
+ enc.Close()
710
+ ```
711
+
704
712
If you want to store the index separately, you can use ` CloseIndex() ` instead of the regular ` Close() ` .
705
713
This will return the index. Note that ` CloseIndex() ` should only be called once, and you shouldn't call ` Close() ` .
706
714
715
+ ```
716
+ // Get index for separate storage...
717
+ enc := s2.NewWriter(w)
718
+ io.Copy(enc, r)
719
+ index, err := enc.CloseIndex()
720
+ ```
721
+
722
+ The ` index ` can then be used needing to read from the stream.
723
+ This means the index can be used without needing to seek to the end of the stream
724
+ or for manually forwarding streams. See below.
725
+
707
726
## Using Indexes
708
727
709
728
To use indexes there is a ` ReadSeeker(random bool, index []byte) (*ReadSeeker, error) ` function available.
@@ -713,15 +732,83 @@ Calling ReadSeeker will return an [io.ReadSeeker](https://pkg.go.dev/io#ReadSeek
713
732
If 'random' is specified the returned io.Seeker can be used for random seeking, otherwise only forward seeking is supported.
714
733
Enabling random seeking requires the original input to support the [ io.Seeker] ( https://pkg.go.dev/io#Seeker ) interface.
715
734
735
+ ```
736
+ dec := s2.NewReader(r)
737
+ rs, err := dec.ReadSeeker(false, nil)
738
+ rs.Seek(wantOffset, io.SeekStart)
739
+ ```
740
+
741
+ Get a seeker to seek forward. Since no index is provided, the index is read from the stream.
742
+ This requires that an index was added and that ` r ` supports the [ io.Seeker] ( https://pkg.go.dev/io#Seeker ) interface.
743
+
716
744
A custom index can be specified which will be used if supplied.
717
745
When using a custom index, it will not be read from the input stream.
718
746
747
+ ```
748
+ dec := s2.NewReader(r)
749
+ rs, err := dec.ReadSeeker(false, index)
750
+ rs.Seek(wantOffset, io.SeekStart)
751
+ ```
752
+
753
+ This will read the index from ` index ` . Since we specify non-random (forward only) seeking ` r ` does not have to be an io.Seeker
754
+
755
+ ```
756
+ dec := s2.NewReader(r)
757
+ rs, err := dec.ReadSeeker(true, index)
758
+ rs.Seek(wantOffset, io.SeekStart)
759
+ ```
760
+
761
+ Finally, since we specify that we want to do random seeking ` r ` must be an io.Seeker.
762
+
719
763
The returned [ ReadSeeker] ( https://pkg.go.dev/github.com/klauspost/compress/s2#ReadSeeker ) contains a shallow reference to the existing Reader,
720
764
meaning changes performed to one is reflected in the other.
721
765
766
+ ## Manually Forwarding Streams
767
+
722
768
Indexes can also be read outside the decoder using the [ Index] ( https://pkg.go.dev/github.com/klauspost/compress/s2#Index ) type.
723
769
This can be used for parsing indexes, either separate or in streams.
724
770
771
+ In some cases it may not be possible to serve a seekable stream.
772
+ This can for instance be an HTTP stream, where the Range request
773
+ is sent at the start of the stream.
774
+
775
+ With a little bit of extra code it is still possible to forward
776
+
777
+ It is possible to load the index manually like this:
778
+ ```
779
+ var index s2.Index
780
+ _, err = index.Load(idxBytes)
781
+ ```
782
+
783
+ This can be used to figure out how much to offset the compressed stream:
784
+
785
+ ```
786
+ compressedOffset, uncompressedOffset, err := index.Find(wantOffset)
787
+ ```
788
+
789
+ The ` compressedOffset ` is the number of bytes that should be skipped
790
+ from the beginning of the compressed file.
791
+
792
+ The ` uncompressedOffset ` will then be offset of the uncompressed bytes returned
793
+ when decoding from that position. This will always be <= wantOffset.
794
+
795
+ When creating a decoder it must be specified that it should * not* expect a frame header
796
+ at the beginning of the stream. Assuming the io.Reader ` r ` has been forwarded to ` compressedOffset `
797
+ we create the decoder like this:
798
+
799
+ ```
800
+ dec := s2.NewReader(r, s2.ReaderIgnoreFrameHeader())
801
+ ```
802
+
803
+ We are not completely done. We still need to forward the stream the uncompressed bytes we didn't want.
804
+ This is done using the regular "Skip" function:
805
+
806
+ ```
807
+ err = dec.Skip(wantOffset - uncompressedOffset)
808
+ ```
809
+
810
+ This will ensure that we are at exactly the offset we want, and reading from ` dec ` will start at the requested offset.
811
+
725
812
## Index Format:
726
813
727
814
Each block is structured as a snappy skippable block, with the chunk ID 0x99.
0 commit comments