Skip to main content

same_content/
lib.rs

1/*!
2# Same Content
3
4Determine whether data from different sources are the same.
5
6## Example
7
8```rust
9use std::fs::File;
10
11use same_content::*;
12
13assert!(!same_content_from_files(&mut File::open("tests/data/P1140310.jpg").unwrap(), &mut File::open("tests/data/P1140558.jpg").unwrap()).unwrap());
14```
15
16## Change the Buffer Size
17
18The default buffer size for the comparison functions is 8192 bytes per stream.
19
20Use a `*_with_buffer_size` function to select a different size with a const generic argument.
21
22```rust
23use std::fs::File;
24
25use same_content::*;
26
27assert!(!same_content_from_files_with_buffer_size::<4096>(&mut File::open("tests/data/P1140310.jpg").unwrap(), &mut File::open("tests/data/P1140558.jpg").unwrap()).unwrap());
28```
29
30## Asynchronous APIs
31
32You may want to use async APIs with your async runtime. This crate supports `tokio`, currently.
33
34```toml
35[dependencies.same-content]
36version = "*"
37features = ["tokio"]
38```
39
40After enabling the async feature, the async functions are available.
41*/
42
43#![cfg_attr(docsrs, feature(doc_cfg))]
44
45use std::{
46    fs::File,
47    io::{self, ErrorKind, Read, Seek, SeekFrom},
48};
49
50#[cfg(feature = "tokio")]
51use tokio::fs::File as AsyncFile;
52#[cfg(feature = "tokio")]
53use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt};
54
55const DEFAULT_BUFFER_SIZE: usize = 8192;
56
57/// Determines whether two files have the same content using the default buffer size.
58///
59/// When the file lengths match, both files are rewound to the beginning before comparison.
60#[inline]
61pub fn same_content_from_files(a: &mut File, b: &mut File) -> Result<bool, io::Error> {
62    same_content_from_files_with_buffer_size::<DEFAULT_BUFFER_SIZE>(a, b)
63}
64
65/// Determines whether two files have the same content using `BUFFER_SIZE` bytes per stream.
66///
67/// `BUFFER_SIZE` must be greater than zero, and both files are rewound to the beginning when their lengths match.
68#[inline]
69pub fn same_content_from_files_with_buffer_size<const BUFFER_SIZE: usize>(
70    a: &mut File,
71    b: &mut File,
72) -> Result<bool, io::Error> {
73    const { assert!(BUFFER_SIZE > 0, "BUFFER_SIZE must be greater than zero") }
74
75    let metadata_a = a.metadata()?;
76    let metadata_b = b.metadata()?;
77
78    if metadata_a.len() != metadata_b.len() {
79        return Ok(false);
80    }
81
82    a.seek(SeekFrom::Start(0))?;
83    b.seek(SeekFrom::Start(0))?;
84
85    same_content_from_readers_with_buffer_size::<BUFFER_SIZE>(a, b)
86}
87
88/// Determines whether two readers have the same remaining content using the default buffer size.
89///
90/// Reading starts at each reader's current position.
91#[inline]
92pub fn same_content_from_readers(a: &mut dyn Read, b: &mut dyn Read) -> Result<bool, io::Error> {
93    same_content_from_readers_with_buffer_size::<DEFAULT_BUFFER_SIZE>(a, b)
94}
95
96/// Determines whether two readers have the same remaining content using `BUFFER_SIZE` bytes per stream.
97///
98/// `BUFFER_SIZE` must be greater than zero, and reading starts at each reader's current position.
99pub fn same_content_from_readers_with_buffer_size<const BUFFER_SIZE: usize>(
100    a: &mut dyn Read,
101    b: &mut dyn Read,
102) -> Result<bool, io::Error> {
103    const { assert!(BUFFER_SIZE > 0, "BUFFER_SIZE must be greater than zero") }
104
105    let mut buffer1 = [0u8; BUFFER_SIZE];
106    let mut buffer2 = [0u8; BUFFER_SIZE];
107
108    loop {
109        let ca = read_retry(a, &mut buffer1)?;
110
111        if ca == 0 {
112            let cb = read_try_exact(b, &mut buffer2[..1])?;
113
114            return Ok(cb == 0);
115        } else {
116            let cb = read_try_exact(b, &mut buffer2[..ca])?;
117
118            if ca != cb {
119                return Ok(false);
120            }
121
122            if buffer1[..ca] != buffer2[..ca] {
123                return Ok(false);
124            }
125        }
126    }
127}
128
129#[inline]
130fn read_retry(a: &mut dyn Read, buffer: &mut [u8]) -> Result<usize, io::Error> {
131    loop {
132        match a.read(buffer) {
133            Ok(n) => return Ok(n),
134            Err(e) if e.kind() == ErrorKind::Interrupted => {},
135            Err(e) => return Err(e),
136        }
137    }
138}
139
140fn read_try_exact(a: &mut dyn Read, mut buffer: &mut [u8]) -> Result<usize, io::Error> {
141    let mut sum = 0;
142
143    while !buffer.is_empty() {
144        let n = read_retry(a, buffer)?;
145
146        if n == 0 {
147            break;
148        }
149
150        buffer = &mut buffer[n..];
151        sum += n;
152    }
153
154    Ok(sum)
155}
156
157/// Determines whether two Tokio files have the same content using the default buffer size.
158///
159/// When the file lengths match, both files are rewound to the beginning before comparison.
160#[cfg(feature = "tokio")]
161#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
162#[inline]
163pub async fn same_content_from_files_async(
164    a: &mut AsyncFile,
165    b: &mut AsyncFile,
166) -> Result<bool, io::Error> {
167    same_content_from_files_async_with_buffer_size::<DEFAULT_BUFFER_SIZE>(a, b).await
168}
169
170/// Determines whether two Tokio files have the same content using `BUFFER_SIZE` bytes per stream.
171///
172/// `BUFFER_SIZE` must be greater than zero, and both files are rewound to the beginning when their lengths match.
173#[cfg(feature = "tokio")]
174#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
175#[inline]
176pub async fn same_content_from_files_async_with_buffer_size<const BUFFER_SIZE: usize>(
177    a: &mut AsyncFile,
178    b: &mut AsyncFile,
179) -> Result<bool, io::Error> {
180    const { assert!(BUFFER_SIZE > 0, "BUFFER_SIZE must be greater than zero") }
181
182    let metadata_a = a.metadata().await?;
183    let metadata_b = b.metadata().await?;
184
185    if metadata_a.len() != metadata_b.len() {
186        return Ok(false);
187    }
188
189    a.seek(SeekFrom::Start(0)).await?;
190    b.seek(SeekFrom::Start(0)).await?;
191
192    same_content_from_readers_async_with_buffer_size::<BUFFER_SIZE>(a, b).await
193}
194
195/// Determines whether two Tokio readers have the same remaining content using the default buffer size.
196///
197/// Reading starts at each reader's current position.
198#[cfg(feature = "tokio")]
199#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
200#[inline]
201pub async fn same_content_from_readers_async(
202    a: &mut (dyn AsyncRead + Unpin),
203    b: &mut (dyn AsyncRead + Unpin),
204) -> Result<bool, io::Error> {
205    same_content_from_readers_async_with_buffer_size::<DEFAULT_BUFFER_SIZE>(a, b).await
206}
207
208/// Determines whether two Tokio readers have the same remaining content using `BUFFER_SIZE` bytes per stream.
209///
210/// `BUFFER_SIZE` must be greater than zero, and reading starts at each reader's current position.
211#[cfg(feature = "tokio")]
212#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
213pub async fn same_content_from_readers_async_with_buffer_size<const BUFFER_SIZE: usize>(
214    a: &mut (dyn AsyncRead + Unpin),
215    b: &mut (dyn AsyncRead + Unpin),
216) -> Result<bool, io::Error> {
217    const { assert!(BUFFER_SIZE > 0, "BUFFER_SIZE must be greater than zero") }
218
219    let mut buffer1 = [0u8; BUFFER_SIZE];
220    let mut buffer2 = [0u8; BUFFER_SIZE];
221
222    loop {
223        let ca = read_retry_async(a, &mut buffer1).await?;
224
225        if ca == 0 {
226            let cb = read_try_exact_async(b, &mut buffer2[..1]).await?;
227
228            return Ok(cb == 0);
229        } else {
230            let cb = read_try_exact_async(b, &mut buffer2[..ca]).await?;
231
232            if ca != cb {
233                return Ok(false);
234            }
235
236            if buffer1[..ca] != buffer2[..ca] {
237                return Ok(false);
238            }
239        }
240    }
241}
242
243#[cfg(feature = "tokio")]
244async fn read_retry_async(
245    a: &mut (dyn AsyncRead + Unpin),
246    buffer: &mut [u8],
247) -> Result<usize, io::Error> {
248    loop {
249        match a.read(buffer).await {
250            Ok(n) => return Ok(n),
251            Err(e) if e.kind() == ErrorKind::Interrupted => {},
252            Err(e) => return Err(e),
253        }
254    }
255}
256
257#[cfg(feature = "tokio")]
258async fn read_try_exact_async(
259    a: &mut (dyn AsyncRead + Unpin),
260    mut buffer: &mut [u8],
261) -> Result<usize, io::Error> {
262    let mut sum = 0;
263
264    while !buffer.is_empty() {
265        let n = read_retry_async(a, buffer).await?;
266
267        if n == 0 {
268            break;
269        }
270
271        buffer = &mut buffer[n..];
272        sum += n;
273    }
274
275    Ok(sum)
276}