Skip to content

Commit c29fcd5

Browse files
committed
feat!: report hardlinks to progress
1 parent 1bbb6ef commit c29fcd5

14 files changed

Lines changed: 136 additions & 53 deletions

File tree

src/app.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -180,18 +180,24 @@ impl App {
180180
}
181181
}
182182

183-
trait HardlinkDeduplicationSystem<const DEDUPLICATE_HARDLINKS: bool>: GetSizeUtils {
184-
type Hook: hook::Hook<Self::Size> + sub::DeduplicateHardlinkSizes<Self::Size>;
183+
trait HardlinkDeduplicationSystem<
184+
const DEDUPLICATE_HARDLINKS: bool,
185+
const REPORT_PROGRESS: bool,
186+
>: CreateReporter<REPORT_PROGRESS>
187+
{
188+
type Hook: hook::Hook<Self::Size, Self::Reporter>
189+
+ sub::DeduplicateHardlinkSizes<Self::Size>;
185190
fn create_hook(
186191
record: <Self::Hook as sub::DeduplicateHardlinkSizes<Self::Size>>::HardlinkRecord,
187192
) -> Self::Hook;
188193
fn init_hardlink_record(
189194
) -> <Self::Hook as sub::DeduplicateHardlinkSizes<Self::Size>>::HardlinkRecord;
190195
}
191196

192-
impl<SizeGetter> HardlinkDeduplicationSystem<false> for SizeGetter
197+
impl<const REPORT_PROGRESS: bool, SizeGetter>
198+
HardlinkDeduplicationSystem<false, REPORT_PROGRESS> for SizeGetter
193199
where
194-
SizeGetter: GetSizeUtils,
200+
SizeGetter: CreateReporter<REPORT_PROGRESS>,
195201
SizeGetter::Size: Send + Sync,
196202
{
197203
type Hook = hook::DoNothing;
@@ -202,10 +208,12 @@ impl App {
202208
}
203209

204210
#[cfg(unix)]
205-
impl<SizeGetter> HardlinkDeduplicationSystem<true> for SizeGetter
211+
impl<const REPORT_PROGRESS: bool, SizeGetter>
212+
HardlinkDeduplicationSystem<true, REPORT_PROGRESS> for SizeGetter
206213
where
207-
SizeGetter: GetSizeUtils,
214+
SizeGetter: CreateReporter<REPORT_PROGRESS>,
208215
SizeGetter::Size: Send + Sync + 'static,
216+
SizeGetter::Reporter: crate::reporter::Reporter<SizeGetter::Size>,
209217
{
210218
type Hook = hook::RecordHardLink<'static, Self::Size>;
211219
fn create_hook(record: &'static hook::RecordHardLinkStorage<Self::Size>) -> Self::Hook {
@@ -239,8 +247,8 @@ impl App {
239247
..
240248
} => {
241249
const DEDUPLICATE_HARDLINKS: bool = cfg!(unix) && $deduplicate_hardlinks;
242-
let hardlink_record = <$size_getter as HardlinkDeduplicationSystem<DEDUPLICATE_HARDLINKS>>::init_hardlink_record();
243-
let hook = <$size_getter as HardlinkDeduplicationSystem<DEDUPLICATE_HARDLINKS>>::create_hook(hardlink_record);
250+
let hardlink_record = <$size_getter as HardlinkDeduplicationSystem<DEDUPLICATE_HARDLINKS, $progress>>::init_hardlink_record();
251+
let hook = <$size_getter as HardlinkDeduplicationSystem<DEDUPLICATE_HARDLINKS, $progress>>::create_hook(hardlink_record);
244252

245253
Sub {
246254
direction: Direction::from_top_down(top_down),

src/app/sub.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ where
2121
Report: ParallelReporter<Size> + Sync,
2222
Size: size::Size + Into<u64> + Serialize + Send + Sync,
2323
SizeGetter: GetSize<Size = Size> + Copy + Sync,
24-
Hook: hook::Hook<Size> + DeduplicateHardlinkSizes<Size> + Copy + Sync,
24+
Hook: hook::Hook<Size, Report> + DeduplicateHardlinkSizes<Size> + Copy + Sync,
2525
DataTreeReflection<String, Size>: Into<UnitAndTree>,
2626
{
2727
/// List of files and/or directories.
@@ -57,7 +57,7 @@ where
5757
Size: size::Size + Into<u64> + Serialize + Send + Sync,
5858
Report: ParallelReporter<Size> + Sync,
5959
SizeGetter: GetSize<Size = Size> + Copy + Sync,
60-
Hook: hook::Hook<Size> + DeduplicateHardlinkSizes<Size> + Copy + Sync,
60+
Hook: hook::Hook<Size, Report> + DeduplicateHardlinkSizes<Size> + Copy + Sync,
6161
DataTreeReflection<String, Size>: Into<UnitAndTree>,
6262
{
6363
/// Run the sub program.

src/fs_tree_builder.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,18 @@ use std::{
3131
/// root: std::env::current_dir().unwrap(),
3232
/// hook: hook::DoNothing,
3333
/// size_getter: GetApparentSize,
34-
/// reporter: ErrorOnlyReporter::new(ErrorReport::SILENT),
34+
/// reporter: &ErrorOnlyReporter::new(ErrorReport::SILENT),
3535
/// max_depth: 10,
3636
/// };
3737
/// let data_tree: DataTree<OsStringDisplay, Bytes> = builder.into();
3838
/// ```
3939
#[derive(Debug)]
40-
pub struct FsTreeBuilder<Size, SizeGetter, Hook, Report>
40+
pub struct FsTreeBuilder<'a, Size, SizeGetter, Hook, Report>
4141
where
42-
Report: Reporter<Size> + Sync,
42+
Report: Reporter<Size> + Sync + ?Sized,
4343
Size: size::Size + Send + Sync,
4444
SizeGetter: GetSize<Size = Size> + Sync,
45-
Hook: hook::Hook<Size> + Sync,
45+
Hook: hook::Hook<Size, Report> + Sync,
4646
{
4747
/// Root of the directory tree.
4848
pub root: PathBuf,
@@ -51,18 +51,18 @@ where
5151
/// Hook to run after [`Self::size_getter`].
5252
pub hook: Hook,
5353
/// Reports progress to external system.
54-
pub reporter: Report,
54+
pub reporter: &'a Report,
5555
/// Deepest level of descendent display in the graph. The sizes beyond the max depth still count toward total.
5656
pub max_depth: u64,
5757
}
5858

59-
impl<Size, SizeGetter, Hook, Report> From<FsTreeBuilder<Size, SizeGetter, Hook, Report>>
59+
impl<'a, Size, SizeGetter, Hook, Report> From<FsTreeBuilder<'a, Size, SizeGetter, Hook, Report>>
6060
for DataTree<OsStringDisplay, Size>
6161
where
62-
Report: Reporter<Size> + Sync,
62+
Report: Reporter<Size> + Sync + ?Sized,
6363
Size: size::Size + Send + Sync,
6464
SizeGetter: GetSize<Size = Size> + Sync,
65-
Hook: hook::Hook<Size> + Sync,
65+
Hook: hook::Hook<Size, Report> + Sync,
6666
{
6767
/// Create a [`DataTree`] from an [`FsTreeBuilder`].
6868
fn from(builder: FsTreeBuilder<Size, SizeGetter, Hook, Report>) -> Self {
@@ -97,7 +97,7 @@ where
9797
let is_dir = stats.is_dir();
9898
let size = size_getter.get_size(&stats);
9999
reporter.report(Event::ReceiveData(size));
100-
hook.run_hook(HookArgument::new(path, &stats, size));
100+
hook.run_hook(HookArgument::new(path, &stats, size, reporter));
101101
(is_dir, size)
102102
}
103103
};

src/hook.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,39 @@ use std::{fs::Metadata, path::Path};
22

33
/// Argument to pass to [`Hook::run_hook`].
44
#[derive(Debug, Clone, Copy)]
5-
pub struct HookArgument<'a, Size> {
5+
pub struct HookArgument<'a, Size, Report: ?Sized> {
66
pub path: &'a Path,
77
pub stats: &'a Metadata,
88
pub size: Size,
9+
pub reporter: &'a Report,
910
}
1011

11-
impl<'a, Size> HookArgument<'a, Size> {
12-
pub(crate) fn new(path: &'a Path, stats: &'a Metadata, size: Size) -> Self {
13-
HookArgument { path, stats, size }
12+
impl<'a, Size, Report: ?Sized> HookArgument<'a, Size, Report> {
13+
pub(crate) fn new(
14+
path: &'a Path,
15+
stats: &'a Metadata,
16+
size: Size,
17+
reporter: &'a Report,
18+
) -> Self {
19+
HookArgument {
20+
path,
21+
stats,
22+
size,
23+
reporter,
24+
}
1425
}
1526
}
1627

1728
/// Hook to run with a [`Path`] and its corresponding [`Metadata`] and size.
18-
pub trait Hook<Size> {
19-
fn run_hook(&self, argument: HookArgument<Size>);
29+
pub trait Hook<Size, Reporter: ?Sized> {
30+
fn run_hook(&self, argument: HookArgument<Size, Reporter>);
2031
}
2132

2233
/// A [hook](Hook) that does nothing.
2334
#[derive(Debug, Clone, Copy)]
2435
pub struct DoNothing;
25-
impl<Size> Hook<Size> for DoNothing {
26-
fn run_hook(&self, _: HookArgument<Size>) {}
36+
impl<Size, Reporter> Hook<Size, Reporter> for DoNothing {
37+
fn run_hook(&self, _: HookArgument<Size, Reporter>) {}
2738
}
2839

2940
// `RecordHardlink` is POSIX-exclusive, because whilst Windows does have `MetadataExt::number_of_links`, it requires Nightly.

src/hook/record_hardlink.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
use super::{Hook, HookArgument};
2+
use crate::{
3+
reporter::{event::EncounterHardlink, Event, Reporter},
4+
size,
5+
};
26
use dashmap::DashMap;
37
use std::{fmt::Debug, os::unix::fs::MetadataExt, path::PathBuf};
48

@@ -19,14 +23,35 @@ impl<'a, Size> RecordHardLink<'a, Size> {
1923
}
2024
}
2125

22-
impl<'a, Size: Eq + Debug> Hook<Size> for RecordHardLink<'a, Size> {
23-
fn run_hook(&self, argument: HookArgument<Size>) {
24-
let HookArgument { path, stats, size } = argument;
26+
impl<'a, Size, Report> Hook<Size, Report> for RecordHardLink<'a, Size>
27+
where
28+
Size: size::Size + Eq + Debug,
29+
Report: Reporter<Size> + ?Sized,
30+
{
31+
fn run_hook(&self, argument: HookArgument<Size, Report>) {
32+
let HookArgument {
33+
path,
34+
stats,
35+
size,
36+
reporter,
37+
} = argument;
2538

26-
if stats.is_dir() || stats.nlink() <= 1 {
39+
if stats.is_dir() {
2740
return;
2841
}
2942

43+
let links = stats.nlink();
44+
if links <= 1 {
45+
return;
46+
}
47+
48+
reporter.report(Event::EncounterHardlink(EncounterHardlink {
49+
path,
50+
stats,
51+
size,
52+
links,
53+
}));
54+
3055
self.storage
3156
.entry(stats.ino())
3257
.and_modify(|(expected_size, paths)| {

src/reporter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub trait ParallelReporter<Size: size::Size>: Reporter<Size> {
2929
impl<Size, Target> Reporter<Size> for &Target
3030
where
3131
Size: size::Size,
32-
Target: Reporter<Size>,
32+
Target: Reporter<Size> + ?Sized,
3333
{
3434
fn report(&self, event: Event<Size>) {
3535
Target::report(*self, event)

src/reporter/event.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
use super::ErrorReport;
22
use crate::size;
3+
use std::{fs::Metadata, path::Path};
34

45
/// Report trigger event.
56
#[derive(Debug)]
67
pub enum Event<'a, Size: size::Size> {
78
ReceiveData(Size),
89
EncounterError(ErrorReport<'a>),
10+
EncounterHardlink(EncounterHardlink<'a, Size>),
11+
}
12+
13+
/// Data of [`Event::EncounterHardlink`].
14+
#[derive(Debug, Clone, Copy)]
15+
pub struct EncounterHardlink<'a, Size: size::Size> {
16+
/// Path of the detected hardlink.
17+
pub path: &'a Path,
18+
/// Stats of the detected hardlink.
19+
pub stats: &'a Metadata,
20+
/// Size of the file.
21+
pub size: Size,
22+
/// Number of links, including this one.
23+
pub links: u64,
924
}

src/reporter/progress_and_error_reporter.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ where
9898
report_error(error_report);
9999
bump!(errors += 1);
100100
}
101+
EncounterHardlink(info) => {
102+
bump!(linked += info.links);
103+
bump!(shared += info.size.into());
104+
}
101105
}
102106
}
103107
}

src/reporter/progress_and_error_reporter/progress_report_state.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ pub struct ProgressReportState {
1515
pub total: AtomicU64,
1616
/// Number of occurred errors.
1717
pub errors: AtomicU64,
18+
/// Total number of detected hardlinks.
19+
pub linked: AtomicU64,
20+
/// Total size of detected hardlinks.
21+
pub shared: AtomicU64,
1822
}
1923

2024
impl ProgressReportState {
@@ -38,10 +42,14 @@ impl ProgressReportState {
3842
let items = load!(items);
3943
let total = load!(total).into();
4044
let errors = load!(errors);
45+
let linked = load!(linked);
46+
let shared = load!(shared);
4147
ControlFlow::Continue(ProgressReport {
4248
items,
4349
total,
4450
errors,
51+
linked,
52+
shared,
4553
})
4654
}
4755
}

src/reporter/progress_report.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ pub struct ProgressReport<Size: size::Size> {
1010
pub total: Size,
1111
/// Number of occurred errors.
1212
pub errors: u64,
13+
/// Total number of detected hardlinks.
14+
pub linked: u64,
15+
/// Total size of detected hardlinks.
16+
pub shared: u64,
1317
}
1418

1519
impl<Size: size::Size + Into<u64>> ProgressReport<Size> {
@@ -19,6 +23,8 @@ impl<Size: size::Size + Into<u64>> ProgressReport<Size> {
1923
items,
2024
total,
2125
errors,
26+
linked,
27+
shared,
2228
} = report;
2329
let mut text = String::new();
2430
write!(
@@ -31,6 +37,12 @@ impl<Size: size::Size + Into<u64>> ProgressReport<Size> {
3137
if errors != 0 {
3238
write!(text, ", erred {errors}").unwrap();
3339
}
40+
if linked != 0 {
41+
write!(text, ", linked {linked}").unwrap();
42+
}
43+
if shared != 0 {
44+
write!(text, ", shared {shared}").unwrap();
45+
}
3446
write!(text, ")").unwrap();
3547
GLOBAL_STATUS_BOARD.temporary_message(&text);
3648
};

0 commit comments

Comments
 (0)