same-content 0.2.0

Determine whether data from different sources are the same.
Documentation
/*!
# Same Content

Determine whether data from different sources are the same.

## Example

```rust
use std::fs::File;

use same_content::*;

assert!(!same_content_from_files(&mut File::open("tests/data/P1140310.jpg").unwrap(), &mut File::open("tests/data/P1140558.jpg").unwrap()).unwrap());
```

## Change the Buffer Size

The default buffer size for the comparison functions is 8192 bytes per stream.

Use a `*_with_buffer_size` function to select a different size with a const generic argument.

```rust
use std::fs::File;

use same_content::*;

assert!(!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());
```

## Asynchronous APIs

You may want to use async APIs with your async runtime. This crate supports `tokio`, currently.

```toml
[dependencies.same-content]
version = "*"
features = ["tokio"]
```

After enabling the async feature, the async functions are available.
*/

#![cfg_attr(docsrs, feature(doc_cfg))]

use std::{
    fs::File,
    io::{self, ErrorKind, Read, Seek, SeekFrom},
};

#[cfg(feature = "tokio")]
use tokio::fs::File as AsyncFile;
#[cfg(feature = "tokio")]
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt};

const DEFAULT_BUFFER_SIZE: usize = 8192;

/// Determines whether two files have the same content using the default buffer size.
///
/// When the file lengths match, both files are rewound to the beginning before comparison.
#[inline]
pub fn same_content_from_files(a: &mut File, b: &mut File) -> Result<bool, io::Error> {
    same_content_from_files_with_buffer_size::<DEFAULT_BUFFER_SIZE>(a, b)
}

/// Determines whether two files have the same content using `BUFFER_SIZE` bytes per stream.
///
/// `BUFFER_SIZE` must be greater than zero, and both files are rewound to the beginning when their lengths match.
#[inline]
pub fn same_content_from_files_with_buffer_size<const BUFFER_SIZE: usize>(
    a: &mut File,
    b: &mut File,
) -> Result<bool, io::Error> {
    const { assert!(BUFFER_SIZE > 0, "BUFFER_SIZE must be greater than zero") }

    let metadata_a = a.metadata()?;
    let metadata_b = b.metadata()?;

    if metadata_a.len() != metadata_b.len() {
        return Ok(false);
    }

    a.seek(SeekFrom::Start(0))?;
    b.seek(SeekFrom::Start(0))?;

    same_content_from_readers_with_buffer_size::<BUFFER_SIZE>(a, b)
}

/// Determines whether two readers have the same remaining content using the default buffer size.
///
/// Reading starts at each reader's current position.
#[inline]
pub fn same_content_from_readers(a: &mut dyn Read, b: &mut dyn Read) -> Result<bool, io::Error> {
    same_content_from_readers_with_buffer_size::<DEFAULT_BUFFER_SIZE>(a, b)
}

/// Determines whether two readers have the same remaining content using `BUFFER_SIZE` bytes per stream.
///
/// `BUFFER_SIZE` must be greater than zero, and reading starts at each reader's current position.
pub fn same_content_from_readers_with_buffer_size<const BUFFER_SIZE: usize>(
    a: &mut dyn Read,
    b: &mut dyn Read,
) -> Result<bool, io::Error> {
    const { assert!(BUFFER_SIZE > 0, "BUFFER_SIZE must be greater than zero") }

    let mut buffer1 = [0u8; BUFFER_SIZE];
    let mut buffer2 = [0u8; BUFFER_SIZE];

    loop {
        let ca = read_retry(a, &mut buffer1)?;

        if ca == 0 {
            let cb = read_try_exact(b, &mut buffer2[..1])?;

            return Ok(cb == 0);
        } else {
            let cb = read_try_exact(b, &mut buffer2[..ca])?;

            if ca != cb {
                return Ok(false);
            }

            if buffer1[..ca] != buffer2[..ca] {
                return Ok(false);
            }
        }
    }
}

#[inline]
fn read_retry(a: &mut dyn Read, buffer: &mut [u8]) -> Result<usize, io::Error> {
    loop {
        match a.read(buffer) {
            Ok(n) => return Ok(n),
            Err(e) if e.kind() == ErrorKind::Interrupted => {},
            Err(e) => return Err(e),
        }
    }
}

fn read_try_exact(a: &mut dyn Read, mut buffer: &mut [u8]) -> Result<usize, io::Error> {
    let mut sum = 0;

    while !buffer.is_empty() {
        let n = read_retry(a, buffer)?;

        if n == 0 {
            break;
        }

        buffer = &mut buffer[n..];
        sum += n;
    }

    Ok(sum)
}

/// Determines whether two Tokio files have the same content using the default buffer size.
///
/// When the file lengths match, both files are rewound to the beginning before comparison.
#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
#[inline]
pub async fn same_content_from_files_async(
    a: &mut AsyncFile,
    b: &mut AsyncFile,
) -> Result<bool, io::Error> {
    same_content_from_files_async_with_buffer_size::<DEFAULT_BUFFER_SIZE>(a, b).await
}

/// Determines whether two Tokio files have the same content using `BUFFER_SIZE` bytes per stream.
///
/// `BUFFER_SIZE` must be greater than zero, and both files are rewound to the beginning when their lengths match.
#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
#[inline]
pub async fn same_content_from_files_async_with_buffer_size<const BUFFER_SIZE: usize>(
    a: &mut AsyncFile,
    b: &mut AsyncFile,
) -> Result<bool, io::Error> {
    const { assert!(BUFFER_SIZE > 0, "BUFFER_SIZE must be greater than zero") }

    let metadata_a = a.metadata().await?;
    let metadata_b = b.metadata().await?;

    if metadata_a.len() != metadata_b.len() {
        return Ok(false);
    }

    a.seek(SeekFrom::Start(0)).await?;
    b.seek(SeekFrom::Start(0)).await?;

    same_content_from_readers_async_with_buffer_size::<BUFFER_SIZE>(a, b).await
}

/// Determines whether two Tokio readers have the same remaining content using the default buffer size.
///
/// Reading starts at each reader's current position.
#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
#[inline]
pub async fn same_content_from_readers_async(
    a: &mut (dyn AsyncRead + Unpin),
    b: &mut (dyn AsyncRead + Unpin),
) -> Result<bool, io::Error> {
    same_content_from_readers_async_with_buffer_size::<DEFAULT_BUFFER_SIZE>(a, b).await
}

/// Determines whether two Tokio readers have the same remaining content using `BUFFER_SIZE` bytes per stream.
///
/// `BUFFER_SIZE` must be greater than zero, and reading starts at each reader's current position.
#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
pub async fn same_content_from_readers_async_with_buffer_size<const BUFFER_SIZE: usize>(
    a: &mut (dyn AsyncRead + Unpin),
    b: &mut (dyn AsyncRead + Unpin),
) -> Result<bool, io::Error> {
    const { assert!(BUFFER_SIZE > 0, "BUFFER_SIZE must be greater than zero") }

    let mut buffer1 = [0u8; BUFFER_SIZE];
    let mut buffer2 = [0u8; BUFFER_SIZE];

    loop {
        let ca = read_retry_async(a, &mut buffer1).await?;

        if ca == 0 {
            let cb = read_try_exact_async(b, &mut buffer2[..1]).await?;

            return Ok(cb == 0);
        } else {
            let cb = read_try_exact_async(b, &mut buffer2[..ca]).await?;

            if ca != cb {
                return Ok(false);
            }

            if buffer1[..ca] != buffer2[..ca] {
                return Ok(false);
            }
        }
    }
}

#[cfg(feature = "tokio")]
async fn read_retry_async(
    a: &mut (dyn AsyncRead + Unpin),
    buffer: &mut [u8],
) -> Result<usize, io::Error> {
    loop {
        match a.read(buffer).await {
            Ok(n) => return Ok(n),
            Err(e) if e.kind() == ErrorKind::Interrupted => {},
            Err(e) => return Err(e),
        }
    }
}

#[cfg(feature = "tokio")]
async fn read_try_exact_async(
    a: &mut (dyn AsyncRead + Unpin),
    mut buffer: &mut [u8],
) -> Result<usize, io::Error> {
    let mut sum = 0;

    while !buffer.is_empty() {
        let n = read_retry_async(a, buffer).await?;

        if n == 0 {
            break;
        }

        buffer = &mut buffer[n..];
        sum += n;
    }

    Ok(sum)
}