├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── std_compare.rs ├── scripts └── coverage.sh └── src └── lib.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | 4 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: rust 4 | cache: cargo 5 | os: 6 | - linux 7 | 8 | env: 9 | - RUST_BACKTRACE=1 10 | 11 | rust: 12 | - stable 13 | - beta 14 | - nightly 15 | 16 | matrix: 17 | allow_failures: 18 | - rust: nightly 19 | 20 | addons: 21 | apt: 22 | packages: 23 | - libelf-dev 24 | - libdw-dev 25 | - binutils-dev 26 | 27 | # load travis-cargo 28 | before_script: 29 | - | 30 | pip install 'travis-cargo<0.2' --user && 31 | export PATH=$HOME/.local/bin:$PATH 32 | 33 | # the main build 34 | script: 35 | - | 36 | travis-cargo clean && 37 | travis-cargo build && 38 | travis-cargo test 39 | after_success: 40 | # measure code coverage and upload to coveralls.io (the verify 41 | # argument mitigates kcov crashes due to malformed debuginfo, at the 42 | # cost of some speed ) 43 | - | 44 | if [[ "$TRAVIS_OS_NAME" == "linux" ]] \ 45 | && (! rustc --version | grep -q -E 'beta|nightly') 46 | then 47 | travis-cargo coveralls --no-sudo --verify # (can) fails 48 | # NOTE: doc tests aren't included 49 | KCOV=./kcov/build/src/kcov ./scripts/coverage.sh 50 | fi 51 | 52 | env: 53 | global: 54 | - TRAVIS_CARGO_NIGHTLY_FEATURE="" 55 | - RUST_BACKTRACE=1 56 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "seek_bufread" 3 | version = "1.2.2" 4 | authors = ["gcarq "] 5 | 6 | documentation = "https://gcarq.github.io/seek_bufread" 7 | repository = "https://github.com/gcarq/seek_bufread" 8 | 9 | readme = "README.md" 10 | license = "Apache-2.0" 11 | 12 | keywords = ["seek", "bufread", "io", "buffer", "reader"] 13 | 14 | description = "A drop-in replacement for std::io::BufReader with seeking support." 15 | 16 | 17 | [dependencies] 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # seek_bufread 2 | 3 | [![Build Status](https://travis-ci.org/gcarq/seek_bufread.svg?branch=master)](https://travis-ci.org/gcarq/seek_bufread) [![Coverage Status](https://coveralls.io/repos/github/gcarq/seek_bufread/badge.svg?branch=master)](https://coveralls.io/github/gcarq/seek_bufread?branch=master) [![Crates.io](https://img.shields.io/crates/v/seek_bufread.svg)](https://crates.io/crates/seek_bufread/) 4 | 5 | **24.04.2019 NOTE:** This library is no longer needed since BufReader provides [seek_relative()](https://doc.rust-lang.org/std/io/struct.BufReader.html#method.seek_relative) with the same functionality. (See [rust#31100](https://github.com/rust-lang/rust/issues/31100)). 6 | 7 | 8 | A drop-in replacement for `std::io::BufReader` with seeking support. 9 | 10 | [Full Documentation](https://gcarq.github.io/seek_bufread) 11 | 12 | ## Quick Example 13 | 14 | ```rust 15 | use std::io::{self, Cursor, Read, Seek, SeekFrom}; 16 | use seek_bufread::BufReader; 17 | 18 | let inner = Cursor::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); 19 | let mut reader = BufReader::new(inner); 20 | 21 | reader.seek(SeekFrom::Current(4)).unwrap(); 22 | let mut buf = [0; 8]; 23 | 24 | // read bytes from internal buffer 25 | reader.read(&mut buf).unwrap(); 26 | assert_eq!(buf, [4, 5, 6, 7, 8, 9, 10, 11]); 27 | ``` 28 | 29 | ## Usage 30 | 31 | Put this in your `Cargo.toml`: 32 | 33 | ```toml 34 | [dependencies] 35 | seek_bufread = "~1.2" 36 | ``` 37 | 38 | And this in your crate root: 39 | 40 | ```rust 41 | extern crate seek_bufread; 42 | ``` 43 | 44 | ## Benchmarks 45 | 46 | Tests with the suffix `_std` are using the standard `std::io::BufRead` 47 | implementation. The Overall performance without seek operations is 48 | quite similar between both. With seek operations ``seek_bufread::BufRead`` 49 | is significantly faster. 50 | 51 | ``` 52 | test tests::read_10mb_default_from_cursor ... bench: 6,044,915 ns/iter (+/- 275,518) 53 | test tests::read_10mb_default_from_cursor_std ... bench: 6,038,466 ns/iter (+/- 227,145) 54 | test tests::read_10mb_default_from_file ... bench: 4,213,179 ns/iter (+/- 116,043) 55 | test tests::read_10mb_default_from_file_std ... bench: 4,224,658 ns/iter (+/- 132,629) 56 | test tests::read_10mb_fullbuf_from_file ... bench: 7,280,470 ns/iter (+/- 209,827) 57 | test tests::read_10mb_fullbuf_from_file_std ... bench: 7,448,666 ns/iter (+/- 2,720,199) 58 | test tests::read_10mb_halfbuf_from_file ... bench: 5,962,017 ns/iter (+/- 415,952) 59 | test tests::read_10mb_halfbuf_from_file_std ... bench: 5,904,902 ns/iter (+/- 240,471) 60 | test tests::read_seek_10mb_default_from_file ... bench: 6,621 ns/iter (+/- 145) 61 | test tests::read_seek_10mb_default_from_file_std ... bench: 59,651 ns/iter (+/- 1,993) 62 | test tests::read_seek_10mb_halfbuf_from_file ... bench: 1,168,511 ns/iter (+/- 63,956) 63 | test tests::read_seek_10mb_halfbuf_from_file_std ... bench: 62,872,335 ns/iter (+/- 5,344,766) 64 | ``` 65 | 66 | ## License 67 | 68 | Apache-2.0 69 | -------------------------------------------------------------------------------- /benches/std_compare.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate seek_bufread; 4 | extern crate test; 5 | 6 | use seek_bufread::BufReader; 7 | 8 | use test::Bencher; 9 | use std::fs::{self, File}; 10 | use std::io::{self, Cursor, Read, Write, Seek, SeekFrom}; 11 | 12 | #[bench] 13 | fn read_10mb_default_from_cursor(b: &mut Bencher) { 14 | b.iter(|| { 15 | let inner = Cursor::new(vec![1; 10000000]); 16 | let mut reader = BufReader::new(inner); 17 | 18 | let mut buf: Vec = Vec::with_capacity(10000000); 19 | reader.read_to_end(&mut buf).unwrap(); 20 | }); 21 | } 22 | 23 | #[bench] 24 | fn read_10mb_default_from_cursor_std(b: &mut Bencher) { 25 | b.iter(|| { 26 | let inner = Cursor::new(vec![1; 10000000]); 27 | let mut reader = io::BufReader::new(inner); 28 | 29 | let mut buf: Vec = Vec::with_capacity(10000000); 30 | reader.read_to_end(&mut buf).unwrap(); 31 | }); 32 | } 33 | 34 | #[bench] 35 | fn read_10mb_default_from_file(b: &mut Bencher) { 36 | let mut f = File::create("foo.txt").unwrap(); 37 | f.write_all(&vec![0; 10000000]).unwrap(); 38 | b.iter(|| { 39 | let mut reader = BufReader::new(File::open("foo.txt").unwrap()); 40 | 41 | let mut buf: Vec = Vec::with_capacity(10000000); 42 | reader.read_to_end(&mut buf).unwrap(); 43 | }); 44 | fs::remove_file("foo.txt").unwrap(); 45 | } 46 | 47 | #[bench] 48 | fn read_10mb_default_from_file_std(b: &mut Bencher) { 49 | let mut f = File::create("foo.txt").unwrap(); 50 | f.write_all(&vec![0; 10000000]).unwrap(); 51 | b.iter(|| { 52 | let mut reader = io::BufReader::new(File::open("foo.txt").unwrap()); 53 | 54 | let mut buf: Vec = Vec::with_capacity(10000000); 55 | reader.read_to_end(&mut buf).unwrap(); 56 | }); 57 | fs::remove_file("foo.txt").unwrap(); 58 | } 59 | 60 | #[bench] 61 | fn read_10mb_halfbuf_from_file(b: &mut Bencher) { 62 | let mut f = File::create("foo.txt").unwrap(); 63 | f.write_all(&vec![0; 10000000]).unwrap(); 64 | b.iter(|| { 65 | let mut reader = BufReader::with_capacity(5000000, File::open("foo.txt").unwrap()); 66 | 67 | let mut buf: Vec = Vec::with_capacity(10000000); 68 | reader.read_to_end(&mut buf).unwrap(); 69 | }); 70 | fs::remove_file("foo.txt").unwrap(); 71 | } 72 | 73 | #[bench] 74 | fn read_10mb_halfbuf_from_file_std(b: &mut Bencher) { 75 | let mut f = File::create("foo.txt").unwrap(); 76 | f.write_all(&vec![0; 10000000]).unwrap(); 77 | b.iter(|| { 78 | let mut reader = io::BufReader::with_capacity(5000000, File::open("foo.txt").unwrap()); 79 | 80 | let mut buf: Vec = Vec::with_capacity(10000000); 81 | reader.read_to_end(&mut buf).unwrap(); 82 | }); 83 | fs::remove_file("foo.txt").unwrap(); 84 | } 85 | 86 | #[bench] 87 | fn read_10mb_fullbuf_from_file(b: &mut Bencher) { 88 | let mut f = File::create("foo.txt").unwrap(); 89 | f.write_all(&vec![0; 10000000]).unwrap(); 90 | b.iter(|| { 91 | let mut reader = BufReader::with_capacity(10000000, File::open("foo.txt").unwrap()); 92 | 93 | let mut buf: Vec = Vec::with_capacity(10000000); 94 | reader.read_to_end(&mut buf).unwrap(); 95 | }); 96 | fs::remove_file("foo.txt").unwrap(); 97 | } 98 | 99 | #[bench] 100 | fn read_10mb_fullbuf_from_file_std(b: &mut Bencher) { 101 | let mut f = File::create("foo.txt").unwrap(); 102 | f.write_all(&vec![0; 10000000]).unwrap(); 103 | b.iter(|| { 104 | let mut reader = io::BufReader::with_capacity(10000000, File::open("foo.txt").unwrap()); 105 | 106 | let mut buf: Vec = Vec::with_capacity(10000000); 107 | reader.read_to_end(&mut buf).unwrap(); 108 | }); 109 | fs::remove_file("foo.txt").unwrap(); 110 | } 111 | 112 | #[bench] 113 | fn read_seek_10mb_halfbuf_from_file(b: &mut Bencher) { 114 | let mut f = File::create("foo.txt").unwrap(); 115 | f.write_all(&vec![0; 10000000]).unwrap(); 116 | b.iter(|| { 117 | let mut reader = BufReader::with_capacity(5000000, File::open("foo.txt").unwrap()); 118 | let mut buf: Vec = Vec::with_capacity(100000); 119 | for i in 0..100 { 120 | reader.seek(SeekFrom::Current(i * 100)).unwrap(); 121 | reader.read(&mut buf).unwrap(); 122 | } 123 | }); 124 | fs::remove_file("foo.txt").unwrap(); 125 | } 126 | 127 | #[bench] 128 | fn read_seek_10mb_halfbuf_from_file_std(b: &mut Bencher) { 129 | let mut f = File::create("foo.txt").unwrap(); 130 | f.write_all(&vec![0; 10000000]).unwrap(); 131 | b.iter(|| { 132 | let mut reader = io::BufReader::with_capacity(5000000, File::open("foo.txt").unwrap()); 133 | let mut buf: Vec = Vec::with_capacity(100000); 134 | for i in 0..100 { 135 | reader.seek(SeekFrom::Current(i * 100)).unwrap(); 136 | reader.read(&mut buf).unwrap(); 137 | } 138 | }); 139 | fs::remove_file("foo.txt").unwrap(); 140 | } 141 | 142 | #[bench] 143 | fn read_seek_10mb_default_from_file(b: &mut Bencher) { 144 | let mut f = File::create("foo.txt").unwrap(); 145 | f.write_all(&vec![0; 10000000]).unwrap(); 146 | b.iter(|| { 147 | let mut reader = BufReader::new(File::open("foo.txt").unwrap()); 148 | let mut buf: Vec = Vec::with_capacity(100000); 149 | for i in 0..100 { 150 | reader.seek(SeekFrom::Current(i * 100)).unwrap(); 151 | reader.read(&mut buf).unwrap(); 152 | } 153 | }); 154 | fs::remove_file("foo.txt").unwrap(); 155 | } 156 | 157 | #[bench] 158 | fn read_seek_10mb_default_from_file_std(b: &mut Bencher) { 159 | let mut f = File::create("foo.txt").unwrap(); 160 | f.write_all(&vec![0; 10000000]).unwrap(); 161 | b.iter(|| { 162 | let mut reader = io::BufReader::new(File::open("foo.txt").unwrap()); 163 | let mut buf: Vec = Vec::with_capacity(100000); 164 | for i in 0..100 { 165 | reader.seek(SeekFrom::Current(i * 100)).unwrap(); 166 | reader.read(&mut buf).unwrap(); 167 | } 168 | }); 169 | fs::remove_file("foo.txt").unwrap(); 170 | } 171 | -------------------------------------------------------------------------------- /scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Usage: 4 | # ./coverage 5 | # 6 | # Run kcov on the tests, and merge the results. 7 | # 8 | # Environment variables: 9 | # TRAVIS_JOB_ID - id for coveralls, defaults to none 10 | # KCOV - path to kcov, defaults to 'kcov' 11 | 12 | [ -n "$TRAVIS_JOB_ID" ] && COVERALLS_ID="--coveralls-id=$TRAVIS_JOB_ID" 13 | [ -z "$KCOV" ] && KCOV=kcov 14 | 15 | # Rebuild tests with dead code included, and get a list of the filenames. 16 | export RUSTFLAGS="-C link-dead-code" 17 | TEST_FILES=$(cargo test 2>&1 >/dev/null | awk '/^ Running target\/debug\// { print $2 }') 18 | 19 | KCOV_OPTS="--verify --exclude-pattern=/.cargo --include-path $(pwd)" 20 | OUT_DIR=target/kcov 21 | 22 | for f in $TEST_FILES; do 23 | "$KCOV" $KCOV_OPTS "$OUT_DIR" $f 24 | done 25 | "$KCOV" --merge $KCOV_OPTS $COVERALLS_ID "$OUT_DIR" "$OUT_DIR" 26 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Original work Copyright 2013 The Rust Project Developers. 2 | // Modified work Copyright 2016 gcarq. 3 | // See the LICENSE file at the top-level directory of this distribution. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | //! The `BufReader` is a drop-in replacement for `std::io::BufReader` with seeking support. 11 | //! 12 | //! If `.seek(SeekFrom::Current(n))` is called and `n` is in range of the internal buffer the 13 | //! underlying reader is not invoked. This has the side effect that you can no longer access 14 | //! the underlying buffer directly after being consumed by `BufReader`, 15 | //! because its position could be out of sync. 16 | //! 17 | //! # Examples 18 | //! 19 | //! ``` 20 | //! use std::io::{self, Cursor, Read, Seek, SeekFrom}; 21 | //! use seek_bufread::BufReader; 22 | //! 23 | //! let inner = Cursor::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); 24 | //! let mut reader = BufReader::new(inner); 25 | //! 26 | //! reader.seek(SeekFrom::Current(4)).unwrap(); 27 | //! let mut buf = [0; 8]; 28 | //! 29 | //! // read bytes from internal buffer 30 | //! reader.read(&mut buf).unwrap(); 31 | //! assert_eq!(buf, [4, 5, 6, 7, 8, 9, 10, 11]); 32 | //! ``` 33 | 34 | use std::fmt; 35 | use std::io::{self, BufRead, Read, Seek, SeekFrom}; 36 | 37 | const DEFAULT_BUF_SIZE: usize = 8 * 1024; 38 | 39 | /// The `BufReader` struct adds buffering to any reader. 40 | /// 41 | /// It can be excessively inefficient to work directly with a `Read` instance. 42 | /// For example, every call to `read` on `TcpStream` results in a system call. 43 | /// A `BufReader` performs large, infrequent reads on the underlying `Read` 44 | /// and maintains an in-memory buffer of the results. 45 | /// 46 | /// # Examples 47 | /// 48 | /// ``` 49 | /// use std::io::prelude::*; 50 | /// use std::fs::File; 51 | /// use seek_bufread::BufReader; 52 | /// 53 | /// # fn foo() -> std::io::Result<()> { 54 | /// let mut f = try!(File::open("log.txt")); 55 | /// let mut reader = BufReader::new(f); 56 | /// 57 | /// let mut line = String::new(); 58 | /// let len = try!(reader.read_line(&mut line)); 59 | /// println!("First line is {} bytes long", len); 60 | /// # Ok(()) 61 | /// # } 62 | /// ``` 63 | pub struct BufReader { 64 | inner: R, // internal reader 65 | buf: Box<[u8]>, // internal buffer 66 | buf_pos: usize, // position within buf 67 | cap: usize, // buf capacity 68 | absolute_pos: u64, // absolute position 69 | } 70 | 71 | impl BufReader { 72 | 73 | /// Creates a new `BufReader` with a default buffer capacity (8192 bytes). 74 | /// 75 | /// # Examples 76 | /// 77 | /// ``` 78 | /// use std::fs::File; 79 | /// use seek_bufread::BufReader; 80 | /// 81 | /// # fn foo() -> std::io::Result<()> { 82 | /// let mut f = try!(File::open("log.txt")); 83 | /// let mut reader = BufReader::new(f); 84 | /// # Ok(()) 85 | /// # } 86 | /// ``` 87 | pub fn new(inner: R) -> BufReader { 88 | BufReader::with_capacity(DEFAULT_BUF_SIZE, inner) 89 | } 90 | 91 | /// Creates a new `BufReader` with the specified buffer capacity. 92 | /// 93 | /// # Examples 94 | /// 95 | /// Creating a buffer with ten bytes of capacity: 96 | /// 97 | /// ``` 98 | /// use std::fs::File; 99 | /// use seek_bufread::BufReader; 100 | /// 101 | /// # fn foo() -> std::io::Result<()> { 102 | /// let mut f = try!(File::open("log.txt")); 103 | /// let mut reader = BufReader::with_capacity(10, f); 104 | /// # Ok(()) 105 | /// # } 106 | /// ``` 107 | pub fn with_capacity(cap: usize, inner: R) -> BufReader { 108 | BufReader { 109 | inner: inner, 110 | buf: vec![0; cap].into_boxed_slice(), 111 | buf_pos: 0, 112 | cap: 0, 113 | absolute_pos: 0, 114 | } 115 | } 116 | 117 | /// Returns the absolute file pointer position. 118 | pub fn position(&self) -> u64 { self.absolute_pos } 119 | 120 | /// Returns the total buffer capacity. 121 | pub fn capacity(&self) -> usize { self.cap } 122 | 123 | /// Returns the current number of remaining bytes available in the buffer. 124 | pub fn available(&self) -> usize { 125 | self.cap.checked_sub(self.buf_pos).unwrap_or(0) 126 | } 127 | 128 | /// Consumes `self`, synchronizes the inner reader position and returns the inner reader. 129 | pub fn into_inner(mut self) -> io::Result { 130 | // Sync position of internal reader 131 | try!(self.inner.seek(SeekFrom::Start(self.absolute_pos))); 132 | Ok(self.inner) 133 | } 134 | 135 | /// Syncs the position of our underlying reader and empties the buffer 136 | fn sync_and_flush(&mut self, pos: SeekFrom) -> io::Result { 137 | self.buf_pos = self.cap; 138 | self.absolute_pos = try!(self.inner.seek(pos)); 139 | Ok(self.absolute_pos) 140 | } 141 | 142 | /// Seeks `n` bytes backwards from current position 143 | fn seek_backward(&mut self, n: i64) -> io::Result { 144 | let n_abs = n.abs() as usize; 145 | if self.buf_pos.checked_sub(n_abs).is_some() { 146 | // Seek our internal buffer 147 | self.absolute_pos -= n_abs as u64; 148 | self.buf_pos -= n_abs; 149 | Ok(self.absolute_pos) 150 | } else { 151 | // Out of scope. Seek inner reader to new position and reset buffer 152 | let new_pos = self.absolute_pos - n_abs as u64; 153 | self.sync_and_flush(SeekFrom::Start(new_pos)) 154 | } 155 | } 156 | 157 | /// Seeks `n` bytes forwards from current position 158 | fn seek_forward(&mut self, n: usize) -> io::Result { 159 | if self.available().checked_sub(n).is_some() { 160 | self.consume(n); 161 | Ok(self.absolute_pos) 162 | } else { 163 | // Out of scope. Seek inner reader to new position and reset buffer 164 | let new_pos = self.absolute_pos + n as u64; 165 | self.sync_and_flush(SeekFrom::Start(new_pos)) 166 | } 167 | } 168 | } 169 | 170 | impl Read for BufReader { 171 | /// Reads the next available bytes from buffer or inner stream. 172 | /// Doesn't guarantee the whole buffer is filled. 173 | /// Returns number of read bytes. 174 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 175 | let n_exp = buf.len(); 176 | let mut n_total = 0; 177 | while n_total < n_exp { 178 | let n_read = try!(try!(self.fill_buf()).read(&mut buf[n_total..])); 179 | if n_read == 0 { 180 | break; 181 | } 182 | self.consume(n_read); 183 | n_total += n_read; 184 | } 185 | Ok(n_total) 186 | } 187 | } 188 | 189 | impl BufRead for BufReader { 190 | fn fill_buf(&mut self) -> io::Result<&[u8]> { 191 | // If we've reached the end of our internal buffer then we need to fetch 192 | // some more data from the underlying reader. 193 | if self.cap == self.buf_pos { 194 | self.cap = try!(self.inner.read(&mut self.buf)); 195 | self.buf_pos = 0; 196 | } 197 | Ok(&self.buf[self.buf_pos..self.cap]) 198 | } 199 | 200 | fn consume(&mut self, amt: usize) { 201 | self.buf_pos += amt; 202 | self.absolute_pos += amt as u64; 203 | } 204 | } 205 | 206 | impl Seek for BufReader { 207 | /// Seek to an offset, in bytes, in the buffer or the underlying reader. 208 | /// 209 | /// The position used for seeking with `SeekFrom::Current(_)` is the 210 | /// current position of the underlying reader plus the current position 211 | /// in the internal buffer. 212 | /// 213 | /// Calling `.unwrap()` immediately after a seek doesn't guarantee 214 | /// the underlying reader at the same position! 215 | /// 216 | /// See `std::io::Seek` for more details. 217 | fn seek(&mut self, pos: SeekFrom) -> io::Result { 218 | match pos { 219 | SeekFrom::Current(n) => { 220 | match n >= 0 { 221 | true => self.seek_forward(n as usize), 222 | false => self.seek_backward(n) 223 | } 224 | } 225 | SeekFrom::Start(n) => { 226 | // Check difference between actual and requested position 227 | match n.checked_sub(self.absolute_pos) { 228 | Some(n_bytes) => self.seek_forward(n_bytes as usize), 229 | None => self.sync_and_flush(pos) 230 | } 231 | } 232 | _ => self.sync_and_flush(pos) 233 | } 234 | } 235 | } 236 | 237 | impl fmt::Debug for BufReader where R: fmt::Debug + Read + Seek { 238 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 239 | fmt.debug_struct("BufReader") 240 | .field("reader", &self.inner) 241 | .field("available", &self.available()) 242 | .field("capacity", &self.cap) 243 | .field("position", &self.absolute_pos) 244 | .finish() 245 | } 246 | } 247 | 248 | #[cfg(test)] 249 | mod tests { 250 | use super::*; 251 | use std::io::{self, Cursor, Read, Seek, SeekFrom}; 252 | 253 | #[test] 254 | fn default_behaviour() { 255 | let mut reader = BufReader::new(Cursor::new([5, 6, 7, 0, 1, 2, 3, 4])); 256 | 257 | let mut buf = [0; 8]; 258 | reader.read(&mut buf).unwrap(); 259 | assert_eq!(buf, [5, 6, 7, 0, 1, 2, 3, 4]); 260 | 261 | let mut buf = [0; 8]; 262 | reader.read(&mut buf).unwrap(); 263 | assert_eq!(buf, [0, 0, 0, 0, 0, 0, 0, 0]); 264 | } 265 | 266 | #[test] 267 | fn default_behaviour_std() { 268 | let mut reader = io::BufReader::new(Cursor::new([5, 6, 7, 0, 1, 2, 3, 4])); 269 | 270 | let mut buf = [0; 8]; 271 | reader.read(&mut buf).unwrap(); 272 | assert_eq!(buf, [5, 6, 7, 0, 1, 2, 3, 4]); 273 | 274 | let mut buf = [0; 8]; 275 | reader.read(&mut buf).unwrap(); 276 | assert_eq!(buf, [0, 0, 0, 0, 0, 0, 0, 0]); 277 | } 278 | 279 | #[test] 280 | fn small_capacity() { 281 | let inner = Cursor::new([5, 6, 7, 0, 1, 2, 3, 4]); 282 | let mut reader = BufReader::with_capacity(2, inner); 283 | 284 | let mut buf = [0, 0, 0]; 285 | reader.read(&mut buf).unwrap(); 286 | assert_eq!(buf, [5, 6, 7]); 287 | 288 | let mut buf = [0, 0]; 289 | reader.read(&mut buf).unwrap(); 290 | assert_eq!(buf, [0, 1]); 291 | 292 | let mut buf = [0]; 293 | reader.read(&mut buf).unwrap(); 294 | assert_eq!(buf, [2]); 295 | } 296 | 297 | #[test] 298 | fn small_capacity_std() { 299 | let inner = Cursor::new([5, 6, 7, 0, 1, 2, 3, 4]); 300 | let mut reader = io::BufReader::with_capacity(2, inner); 301 | 302 | let mut buf = [0, 0, 0]; 303 | reader.read(&mut buf).unwrap(); 304 | assert_eq!(buf, [5, 6, 7]); 305 | 306 | let mut buf = [0, 0]; 307 | reader.read(&mut buf).unwrap(); 308 | assert_eq!(buf, [0, 1]); 309 | 310 | let mut buf = [0]; 311 | reader.read(&mut buf).unwrap(); 312 | assert_eq!(buf, [2]); 313 | } 314 | 315 | #[test] 316 | fn seek_start() { 317 | let inner = Cursor::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); 318 | let mut reader = BufReader::with_capacity(10, inner); 319 | 320 | reader.seek(SeekFrom::Start(3)).unwrap(); 321 | let mut buf = [0; 8]; 322 | reader.read(&mut buf).unwrap(); 323 | assert_eq!(buf, [3, 4, 5, 6, 7, 8, 9, 10]); 324 | 325 | reader.seek(SeekFrom::Start(0)).unwrap(); 326 | let mut buf = [0; 8]; 327 | reader.read(&mut buf).unwrap(); 328 | assert_eq!(buf, [0, 1, 2, 3, 4, 5, 6, 7]); 329 | 330 | reader.seek(SeekFrom::Start(13)).unwrap(); 331 | let mut buf = [0; 8]; 332 | reader.read(&mut buf).unwrap(); 333 | assert_eq!(buf, [13, 14, 15, 16, 0, 0, 0, 0]); 334 | 335 | reader.seek(SeekFrom::Start(0)).unwrap(); 336 | let mut buf = [0; 8]; 337 | reader.read(&mut buf).unwrap(); 338 | assert_eq!(buf, [0, 1, 2, 3, 4, 5, 6, 7]); 339 | } 340 | 341 | #[test] 342 | fn seek_start_std() { 343 | let inner = Cursor::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); 344 | let mut reader = io::BufReader::with_capacity(10, inner); 345 | 346 | reader.seek(SeekFrom::Start(3)).unwrap(); 347 | let mut buf = [0; 8]; 348 | reader.read(&mut buf).unwrap(); 349 | assert_eq!(buf, [3, 4, 5, 6, 7, 8, 9, 10]); 350 | 351 | reader.seek(SeekFrom::Start(0)).unwrap(); 352 | let mut buf = [0; 8]; 353 | reader.read(&mut buf).unwrap(); 354 | assert_eq!(buf, [0, 1, 2, 3, 4, 5, 6, 7]); 355 | 356 | reader.seek(SeekFrom::Start(13)).unwrap(); 357 | let mut buf = [0; 8]; 358 | reader.read(&mut buf).unwrap(); 359 | assert_eq!(buf, [13, 14, 15, 16, 0, 0, 0, 0]); 360 | 361 | reader.seek(SeekFrom::Start(0)).unwrap(); 362 | let mut buf = [0; 8]; 363 | reader.read(&mut buf).unwrap(); 364 | assert_eq!(buf, [0, 1, 2, 3, 4, 5, 6, 7]); 365 | } 366 | 367 | #[test] 368 | fn seek_current_positive() { 369 | let inner = Cursor::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); 370 | let mut reader = BufReader::with_capacity(20, inner); 371 | 372 | reader.seek(SeekFrom::Current(2)).unwrap(); 373 | let mut buf = [0; 8]; 374 | reader.read(&mut buf).unwrap(); 375 | assert_eq!(buf, [2, 3, 4, 5, 6, 7, 8, 9]); 376 | 377 | reader.seek(SeekFrom::Current(6)).unwrap(); 378 | let mut buf = [0; 8]; 379 | reader.read(&mut buf).unwrap(); 380 | assert_eq!(buf, [16, 0, 0, 0, 0, 0, 0, 0]); 381 | } 382 | 383 | #[test] 384 | fn seek_current_positive_std() { 385 | let inner = Cursor::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); 386 | let mut reader = io::BufReader::with_capacity(20, inner); 387 | 388 | reader.seek(SeekFrom::Current(2)).unwrap(); 389 | let mut buf = [0; 8]; 390 | reader.read(&mut buf).unwrap(); 391 | assert_eq!(buf, [2, 3, 4, 5, 6, 7, 8, 9]); 392 | 393 | reader.seek(SeekFrom::Current(6)).unwrap(); 394 | let mut buf = [0; 8]; 395 | reader.read(&mut buf).unwrap(); 396 | assert_eq!(buf, [16, 0, 0, 0, 0, 0, 0, 0]); 397 | } 398 | 399 | #[test] 400 | fn seek_current_negative() { 401 | let inner = Cursor::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); 402 | let mut reader = BufReader::with_capacity(3, inner); 403 | 404 | reader.seek(SeekFrom::Current(4)).unwrap(); 405 | let mut buf = [0; 4]; 406 | reader.read(&mut buf).unwrap(); 407 | assert_eq!(buf, [4, 5, 6, 7]); 408 | 409 | reader.seek(SeekFrom::Current(-2)).unwrap(); 410 | let mut buf = [0; 4]; 411 | reader.read(&mut buf).unwrap(); 412 | assert_eq!(buf, [6, 7, 8, 9]); 413 | 414 | reader.seek(SeekFrom::Current(-4)).unwrap(); 415 | let mut buf = [0; 4]; 416 | reader.read(&mut buf).unwrap(); 417 | assert_eq!(buf, [6, 7, 8, 9]); 418 | } 419 | 420 | #[test] 421 | fn seek_current_negative_std() { 422 | let inner = Cursor::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); 423 | let mut reader = io::BufReader::with_capacity(3, inner); 424 | 425 | reader.seek(SeekFrom::Current(4)).unwrap(); 426 | let mut buf = [0; 4]; 427 | reader.read(&mut buf).unwrap(); 428 | assert_eq!(buf, [4, 5, 6, 7]); 429 | 430 | reader.seek(SeekFrom::Current(-2)).unwrap(); 431 | let mut buf = [0; 4]; 432 | reader.read(&mut buf).unwrap(); 433 | assert_eq!(buf, [6, 7, 8, 9]); 434 | 435 | reader.seek(SeekFrom::Current(-4)).unwrap(); 436 | let mut buf = [0; 4]; 437 | reader.read(&mut buf).unwrap(); 438 | assert_eq!(buf, [6, 7, 8, 9]); 439 | } 440 | 441 | #[test] 442 | fn seek_end() { 443 | let inner = Cursor::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); 444 | let mut reader = BufReader::with_capacity(2, inner); 445 | 446 | reader.seek(SeekFrom::End(-6)).unwrap(); 447 | let mut buf = [0; 8]; 448 | reader.read(&mut buf).unwrap(); 449 | assert_eq!(buf, [11, 12, 13, 14, 15, 16, 0, 0]); 450 | 451 | reader.seek(SeekFrom::End(0)).unwrap(); 452 | let mut buf = [0; 8]; 453 | reader.read(&mut buf).unwrap(); 454 | assert_eq!(buf, [0, 0, 0, 0, 0, 0, 0, 0]); 455 | } 456 | 457 | #[test] 458 | fn seek_end_std() { 459 | let inner = Cursor::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); 460 | let mut reader = io::BufReader::with_capacity(2, inner); 461 | 462 | reader.seek(SeekFrom::End(-6)).unwrap(); 463 | let mut buf = [0; 8]; 464 | reader.read(&mut buf).unwrap(); 465 | assert_eq!(buf, [11, 12, 13, 14, 15, 16, 0, 0]); 466 | 467 | reader.seek(SeekFrom::End(0)).unwrap(); 468 | let mut buf = [0; 8]; 469 | reader.read(&mut buf).unwrap(); 470 | assert_eq!(buf, [0, 0, 0, 0, 0, 0, 0, 0]); 471 | } 472 | 473 | #[test] 474 | fn into_inner() { 475 | let inner = Cursor::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); 476 | let mut reader = BufReader::with_capacity(4, inner); 477 | 478 | reader.seek(SeekFrom::Current(5)).unwrap(); 479 | let mut buf = [0; 8]; 480 | reader.read(&mut buf).unwrap(); 481 | assert_eq!(buf, [5, 6, 7, 8, 9, 10, 11, 12]); 482 | reader.seek(SeekFrom::Current(-2)).unwrap(); 483 | 484 | let mut buf = [0; 2]; 485 | reader.read(&mut buf).unwrap(); 486 | assert_eq!(buf, [11, 12]); 487 | 488 | let mut inner = reader.into_inner().unwrap(); 489 | let mut buf = [0; 8]; 490 | inner.read(&mut buf).unwrap(); 491 | assert_eq!(buf, [13, 14, 15, 16, 0, 0, 0, 0]); 492 | } 493 | } 494 | --------------------------------------------------------------------------------