#file-path #glob-pattern #hash #checksum #md5 #sha #file-reader

构建 fshasher

扫描目标文件夹并对所有文件进行哈希处理,以获取目录的当前状态

6个版本

0.3.2 2024年7月21日
0.3.1 2024年7月16日
0.2.0 2024年6月22日
0.1.1 2024年6月16日

254文件系统

Download history 268/week @ 2024-06-13 212/week @ 2024-06-20 17/week @ 2024-06-27 1/week @ 2024-07-04 178/week @ 2024-07-11 172/week @ 2024-07-18 57/week @ 2024-07-25 12/week @ 2024-08-01

每月下载量353次

Apache-2.0

220KB
4.5K SLoC

LICENSE Crates.io

fshasher 允许快速计算目标文件夹中所有文件的一个共同哈希值(递归)。

目录

  1. 简介
  1. 配置
  1. 哈希器 & 读取器
  1. 其他
  1. 行为、错误、日志

简介

它做什么?

fshasher 执行两个主要任务

  • 收集:从目标文件夹收集文件的路径。
  • 哈希:计算每个文件的哈希值以及所有文件的一个共同哈希值。

特性

  • fshasher 会为收集文件和进一步哈希生成多个线程,从而实现高速;然而,性能取决于文件系统和CPU性能(包括核心数量)。
  • fshasher 提供灵活的配置,使用户能够在性能和CPU/文件系统负载之间找到最佳平衡。可以根据文件大小定义不同的文件读取方法(分块读取、完整读取或内存映射文件)。fshasher 还引入了ReaderHasher特质以实现自定义读取器和哈希器。
  • fshasher 支持过滤文件和文件夹,允许只包含必要的文件在哈希中,或排除其他文件。过滤基于glob模式。
  • fshasher 执行像哈希这样的昂贵且持续的操作,并允许取消收集和哈希操作。
  • fshasher 包含一个嵌入通道来共享收集文件和哈希的进度。
  • fshasher 支持不同级别的错误容忍度,可以在跳过一些文件(例如由于权限问题)的同时,仍然获得剩余文件的哈希。
  • 具有“跟踪”功能的 fshasher 会保存最近检查的信息,并在每次后续计算中检测变化。

在哪里可以使用它?

通用用例包括 fshasher

  • 构建脚本/任务:通过检查文件夹中的更改并决定是否执行某些构建操作来减少不必要的构建步骤。
  • 跟踪更改:快速检测目标文件夹中的更改并触发必要的操作。
  • 其他用例:任何依赖于目标文件夹中文件状态的行动。

基本用法示例

use fshasher::{Options, Entry, Tolerance, hasher, reader};
use std::env::temp_dir;
///
let mut walker = Options::new()
    .entry(Entry::from(temp_dir()).unwrap()).unwrap()
    .tolerance(Tolerance::LogErrors)
    .walker().unwrap();
let hash = walker.collect().unwrap()
    .hash::<hasher::blake::Blake, reader::buffering::Buffering>().unwrap();
println!("Hash of {}: {:?}", temp_dir().display(), hash);

配置

通用

要配置 fshasher,请使用 Options 结构体。它提供了几个有用的方法

  • reading_strategy(ReadingStrategy) - 设置读取策略。
  • threads(usize) - 设置收集器和哈希器可以创建的系统线程数(默认值等于核心数)。
  • progress(usize) - 激活进度跟踪;作为参数,您可以定义通道队列的容量。
  • tolerance(Tolerance) - 设置错误容忍度;默认情况下,收集器和哈希器不会因错误而停止工作,但会报告它们。
  • path(AsRef<Path>) - 添加要包含在哈希中的目标文件夹;包括文件夹而不进行过滤。
  • entry(Entry) - 添加要包含在哈希中的目标文件夹;包括带有过滤的文件夹。
  • include(Filter) - 为所有条目添加全局正面过滤器。
  • exclude(Filter) - 为所有条目添加全局负面过滤器。
  • storage(AsRef<Path>) - 仅在“跟踪”功能中可用。设置存储最近计算哈希数据的路径。

过滤

要设置全局过滤器,这些过滤器将应用于所有条目,请使用 Options.include(Filter)Options.exclude(Filter) 来设置正面和/或负面过滤器。对于过滤,fshasher 使用 glob 模式。

以下示例

  • 包括条目路径:“/music/2023” 和 “/music/2024”。
  • 包括名称中包含"星号"以及扩展名为"flac"的文件。
  • 忽略名称中包含"Bieber"的文件夹中的文件。
    let walker = Options::new()
        .path("/music/2023")?
        .path("/music/2024")?
        .include(Filter::Files("*star*"))?
        .include(Filter::Files("*.flac"))?
        .exclude("*Bieber*")?.
        .walker(..)?;

使用Filter,可以将glob模式应用于文件名或文件夹名,而正则glob模式应用于完整路径。这可以实现更精确的过滤。

  • Filter::Folders(AsRef<str>) - 仅应用于文件夹名的glob模式。
  • Filter::Files(AsRef<str>) - 仅应用于文件名的glob模式。
  • Filter::Common(AsRef<str>) - 应用于完整路径(glob模式的常规使用)。

要创建与条目关联的过滤器,请使用Entry

以下示例

  • 接受条目路径:"/music/2023"和"/music/2024"。
  • 包括名称中包含"星号"且扩展名为"flac"的文件,这两个条目都有。
  • 忽略"/music/2023"中位于名称包含"Bieber"的文件夹中的文件。
  • 忽略"/music/2024"中位于名称包含"Taylor Swift"的文件夹中的文件。
    let music_2023 = Entry::from("music/2023")?.exclude(Filter::Folders("*Bieber*"))?;
    let music_2024 = Entry::from("music/2023")?.exclude(Filter::Folders("*Taylor Swift*"))?;
    let walker = Options::new()
        .entry(music_2023)?
        .entry(music_2024)?
        .include(Filter::Files("*star*"))?
        .include(Filter::Files("*.flac"))?
        .walker(..);

注意:排除Filter优先于包含Filter。如果排除Filter匹配,则不会检查包含Filter

模式

虽然Filter将glob模式专门应用于文件名或文件路径,但PatternFilter将glob模式应用于完整路径(包括路径的文件名),即使用glob模式的常规方式。

  • PatternFilter::Ignore(AsRef<str>) - 如果给定的glob模式匹配,则将忽略路径。
  • PatternFilter::Accept(AsRef<str>) - 如果给定的glob模式匹配,则将包含路径。
  • PatternFilter::Cmb(Vec<PatternFilter<AsRef<str>>>) - 允许定义一组PatternFilterPatternFilter::Cmb(..)不支持嵌套组合;尝试在内部嵌套另一个PatternFilter::Cmb(..)将导致错误。

以下示例

  • 接受条目路径:"/music/2023"和"/music/2024"。
  • 在两个条目中包含扩展名为"flac"或"mp3"的文件。
  • 如果完整文件名包含"Bieber",则忽略"/music/2023"中的文件。
  • 如果完整文件名包含"Taylor Swift",则忽略"/music/2024"中的文件。
    let music_2023 = Entry::from("music/2023")?
        .pattern(PatternFilter::Accept("*.flac"))?
        .pattern(PatternFilter::Accept("*.mp3"))?
        .pattern(PatternFilter::Ignore("*Bieber*"))?;
    let music_2024 = Entry::from("music/2023")?
        .pattern(PatternFilter::Accept("*.flac"))?
        .pattern(PatternFilter::Accept("*.mp3"))?
        .pattern(PatternFilter::Ignore("*Taylor Swift*"))?;
    let walker = Options::new()
        .entry(music_2023)?
        .entry(music_2024)?
        .walker(..);

注意 PatternFilterFilter 具有更高的优先级。如果已定义 PatternFilter,则任何 Filter 都会被忽略。

PatternFilter 的另一个变体是 PatternFilter::Cmb(Vec<PatternFilter<AsRef<str>>>)。您可以使用它将 PatternFilterAND 条件结合使用。

下面是一个示例

  • 作为入口路径:"/music/2023" 和 "/music/2024";
  • 从 "/music/2023" 中收集扩展名为 "flac" 的文件,且完整文件名不包含 "Bieber";
  • 从 "/music/2024" 中收集扩展名为 "flac" 的文件,且完整文件名不包含 "Taylor Swift";
    let music_2023 = Entry::from("music/2023")?.pattern(PatternFilter::Cmb(vec![
        PatternFilter::Accept("*.flac"),
        PatternFilter::Ignore("*Bieber*"),
    ]))?;
    let music_2024 = Entry::from("music/2023")?.pattern(PatternFilter::Cmb(vec![
        PatternFilter::Accept("*.flac"),
        PatternFilter::Ignore("*Taylor Swift*"),
    ]))?;
    let walker = Options::new()
        .entry(music_2023)?
        .entry(music_2024)?
        .walker(..);

当然!以下是您文档的改进版本,以 Markdown (MD) 格式呈现

文件中的规则

您可以让 fshasher 考虑来自文件的规则(如 .gitignore)。fshasher 将检查每个文件夹的规则文件,并解析它以提取所有glob模式。

use fshasher::{Entry, Options, ContextFile};
use std::path::PathBuf;

let mut opt = Options::new();
let mut walker = Options::new().entry(
    Entry::new()
        .entry(PathBuf::from("my/entry/path"))
        .unwrap()
        .context(
            ContextFile::Ignore(".gitignore")
        )
    ).unwrap().walker();
  • ContextFile::Ignore - 文件中的所有规则都将用作忽略规则。如果路径匹配,则会被忽略。忽略规则经常使用。这意味着规则将应用于完整路径:既检查文件夹路径也检查文件路径。
  • ContextFile::Accept - 文件中的所有规则都将用作接受规则。如果路径匹配,则会被接受。如果文件中的此规则不匹配,则文件会被忽略。接受规则以非常规方式使用。这意味着规则仅应用于文件路径;将跳过文件夹路径检查。

读取策略

配置读取策略有助于优化哈希过程以匹配特定系统的能力。一方面,文件读取得越快,其哈希就可以开始得越早。另一方面,一次性读取过多数据可能会降低性能或超负荷CPU。为了找到平衡点,可以使用 ReadingStrategy

  • ReadingStrategy::Buffer - 每个文件将以“经典”方式通过有限大小的缓冲区分块读取,直到文件末尾。哈希器将接收小数据块来计算文件的哈希值。此策略不会大量加载CPU,但它涉及到许多IO操作。
  • ReadingStrategy::Complete - 使用此策略,首先读取文件,然后将完整文件的内容传递给哈希器以计算哈希值。此策略涉及较少的IO操作,但会更多地加载CPU。
  • ReadingStrategy::MemoryMapped - 与传统读取文件不同,此策略将文件映射到内存中,并为哈希器提供完整内容。
  • ReadingStrategy::Scenario(Vec<(Range<u64>, Box<ReadingStrategy>)>) - 场景策略允许根据文件的大小组合不同的策略。

在以下示例中

  • 对于小于1024KB的文件,使用 ReadingStrategy::MemoryMapped 策略。
  • 对于大于1024KB的文件,请使用ReadingStrategy::Buffer策略。
    use fshasher::{collector::Tolerance, hasher, reader, Options, ReadingStrategy};
    use std::env::temp_dir;

    let mut walker = Options::from(temp_dir())
        .unwrap()
        .reading_strategy(ReadingStrategy::Scenario(vec![
            (0..1024 * 1024, Box::new(ReadingStrategy::MemoryMapped)),
            (1024 * 1024..u64::MAX, Box::new(ReadingStrategy::Buffer)),
        ]))
        .unwrap()
        .tolerance(Tolerance::LogErrors)
        .walker()
        .unwrap();
    let hash = walker.collect()
        .unwrap()
        .hash::<hasher::blake::Blake, reader::mapping::Mapping>()
        .unwrap()
        .to_vec();
    assert!(!hash.is_empty());

注意:使用ReadingStrategy有可能找到提高性能的方法,但就CPU负载而言,差异可能非常显著。

哈希器与读取器

默认

默认情况下,fshasher包含了以下读取器

  • reader::buffering::Buffering - 一种“经典”读取器,逐块读取文件直到文件末尾。它不支持将文件映射到内存中(不能与ReadingStrategy::MemoryMapped一起使用)。
  • reader::mapping::Mapping - 支持将文件映射到内存中(可以与ReadingStrategy::MemoryMapped一起使用)并“经典”地逐块读取文件直到文件末尾。
  • reader::md::Md - 此读取器不是读取文件,而是创建一个包含文件最后修改日期和其大小的字节切片。显然,这个读取器将给出非常快的速度,但只有在确信检查元数据足以得出正确结论时才应使用。

fshasher默认只包含一个哈希器

  • hasher::blake::Blake - 基于blake3 crate的哈希器。

哈希器作为特性

启用use_sha2允许使用以下哈希器(基于sha2 crate)

  • hasher::sha256::Sha256 - 更灵活,通常用于资源有限或需要与32位系统兼容的系统。
  • hasher::sha512::Sha512 - 适用于64位架构的系统。
[dependencies]
fshasher = { version = "0.1", features = ["use_sha2"] }

扩展

通过实现Hasher trait来实现自定义hasher,同样,实现自定义reader需要实现Reader trait。

以下是一些示例

其他

跟踪更改

使用“跟踪”功能,fshasher将创建用于保存最近计算的哈希信息的存储。使用is_same()方法,将能够检测是否发生了任何更改。

由于数据是永久保存在磁盘上的,所以is_same()方法(在Walker实现中)将在应用程序运行之间提供准确的信息。

强烈建议设置(使用Options)您自己的路径,以便fshasher保存最近计算的哈希信息。如果没有设置路径,将使用默认路径.fshasher,这可能会使用户感到困惑。

use fshasher::{hasher, reader, Entry, Options, Tolerance, Tracking};
use std::env::temp_dir;
    ///
let mut walker = Options::new()
    .entry(Entry::from(temp_dir()).unwrap())
    .unwrap()
    .tolerance(Tolerance::LogErrors)
    .walker()
    .unwrap();
// false - because never checked before
println!(
    "First check: {}",
    walker
        .is_same::<hasher::blake::Blake, reader::buffering::Buffering>()
        .unwrap()
);
// true - because checked before
println!(
    "Second check: {}",
    walker
        .is_same::<hasher::blake::Blake, reader::buffering::Buffering>()
        .unwrap()
);

行为、错误、日志

错误处理

在某些情况下,对大量文件进行哈希可能会不可预测。例如,权限问题可能导致错误,或者文件夹内容可能在哈希计算过程中发生变化。fshasher提供了对错误容忍度的控制。它有以下级别

  • Tolerance::LogErrors:将错误记录,但不会停止收集和哈希过程。
  • Tolerance::DoNotLogErrors:忽略错误,但不会停止收集和哈希过程。
  • Tolerance::StopOnErrors:在出现任何I/O错误或与哈希器或读取器相关的错误时,将停止收集和哈希过程。

为什么可以忽略错误?

如果某些文件导致权限错误,这不是文件收集器的“问题”,因为收集器在给定上下文中以给定的权限工作。如果用户计算包含子文件夹的文件夹的哈希值,而这些子文件夹没有适当的权限,这可能是用户的选择。

另一种情况是,在计算哈希值时收集到的文件列表发生变化。在这种情况下,hash()函数仍然可以返回一个反映变化的哈希值(例如,如果某些文件已被删除)。

同时,导致错误的文件列表将在Walker中可用,但HashItem将包括错误而不是文件的哈希值。

最终,是否忽略错误取决于开发者的选择。

日志

fshasher使用log crate,它是Rust的轻量级日志包装器。它与env_logger一起使用。以下shell命令将使一些日志可见:

export RUST_LOG=debug

贡献

欢迎贡献!请阅读简短的贡献指南

依赖关系

~2–13MB
~116K SLoC