1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
use crate::error::{Error, Result};
use clap::Parser;
use crossbeam_channel::{unbounded, Sender};
use log::warn;
use std::{env, path::PathBuf};
use xvc_config::{FromConfigKey, UpdateFromXvcConfig, XvcConfig, XvcConfigInitParams};
use xvc_core::{
util::file::{path_metadata_channel, pipe_filter_path_errors},
HashAlgorithm, XvcDigest, XvcRoot,
};
use xvc_logging::{watch, XvcOutputLine};
use xvc_walker::AbsolutePath;
use crate::common::{calc_digest, pipe_path_digest};
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
#[command(version, author)]
pub struct HashCLI {
#[arg(short, long)]
algorithm: Option<HashAlgorithm>,
#[arg(long)]
text_file: bool,
targets: Vec<PathBuf>,
}
impl UpdateFromXvcConfig for HashCLI {
fn update_from_conf(self, conf: &XvcConfig) -> xvc_config::error::Result<Box<Self>> {
let algorithm = self
.algorithm
.unwrap_or_else(|| HashAlgorithm::from_conf(conf));
Ok(Box::new(Self {
algorithm: Some(algorithm),
text_file: self.text_file,
targets: self.targets.clone(),
}))
}
}
pub fn cmd_hash(
output_snd: Sender<XvcOutputLine>,
xvc_root: Option<&XvcRoot>,
opts: HashCLI,
) -> Result<()> {
let conf = match xvc_root {
Some(xvc_root) => xvc_root.config().clone(),
None => XvcConfig::new(XvcConfigInitParams {
default_configuration: xvc_core::default_project_config(false),
current_dir: AbsolutePath::from(env::current_dir()?),
include_system_config: true,
include_user_config: false,
project_config_path: None,
local_config_path: None,
include_environment_config: true,
command_line_config: None,
})?,
};
let opts = opts.update_from_conf(&conf)?;
let algorithm = opts.algorithm.unwrap_or(HashAlgorithm::Blake3);
let text_file = opts.text_file;
let targets = opts.targets;
let send_output = |path: PathBuf, digest: XvcDigest| {
output_snd
.send(XvcOutputLine::Output(format!(
"{}\t{}",
digest,
path.to_string_lossy()
)))
.unwrap();
};
for t in targets {
watch!(t);
if !t.exists() {
Error::FileNotFound { path: t }.error();
continue;
}
if t.is_dir() {
let (path_snd, path_rec) = unbounded();
path_metadata_channel(path_snd, &t)?;
let (filtered_path_snd, filtered_path_rec) = unbounded();
pipe_filter_path_errors(path_rec, filtered_path_snd)?;
let (digest_snd, digest_rec) = unbounded();
pipe_path_digest(filtered_path_rec, digest_snd, &algorithm, text_file)?;
for (path, digest) in digest_rec {
watch!(path);
watch!(digest);
send_output(path, digest);
}
} else if t.is_file() {
let digest = calc_digest(&t, &algorithm, text_file)?;
send_output(t, digest);
} else {
warn!("Unsupported FS Type: {:?}", t);
}
}
Ok(())
}