Skip to content

Releases

v2.6.0

Choose a tag to compare

@naut54 naut54 released this 16 Sep 12:49

serde feature — Serialize for this crate's return types: Progress, AnalysisProgress, OperationOutcome, SyncOutcome, RemoveOutcome, AnalysisReport and the types it aggregates (Entry, ExtensionStats, MimeStats, AgeBuckets, DuplicateGroup), StopReason, WatchEvent/WatchEventKind, and Error. Builders/config types are deliberately not covered — this is for output, not input. Error's impl is hand-written (it wraps foreign, non-Serialize error types) and flattens to {"code", ...fields..., "message"}, where code is the same FE_* string as this crate's own errors.toml. DuplicateGroup::hash serializes as a lowercase hex string, not the default JSON array of 32 numbers.

See CHANGELOG.md for full details.

v2.5.0

Choose a tag to compare

@naut54 naut54 released this 09 Sep 21:24

AnalyzeBuilder::estimate_total(bool) — off by default. When enabled,
.start() walks the tree twice: once applying the same filters
(extension/size/modified-time) to count how many files will match, then
again to build the report. New AnalysisProgress::Started {
estimated_entries } is now always emitted first, before any
EntryAnalyzed/EntryHashed — Some(count) only when .estimate_total(true)
is set, None otherwise — so a caller can render a determinate progress
bar or ETA instead of only learning the total once the walk finishes.
Existing code that matches on AnalysisProgress variants needs a
wildcard arm, since it's #[non_exhaustive].

.detect_duplicates(true) no longer hashes empty files. Every empty file
is byte-identical to every other by construction, so a size-0 group of
candidates is now folded directly into the duplicate set using a
precomputed hash of the empty input, skipping the open+read+hash
entirely for it.

Duplicate detection now stops promptly on cancellation. .cancel()
during .detect_duplicates(true) previously only took effect between
spawning hash tasks — once the last candidate was queued, cancellation
had no effect until every in-flight hash finished. Cancellation is now
also checked while draining completed hashes, so a cancel request
interrupts a large batch already in flight instead of waiting it out.

Fewer redundant allocations while tracking .top_n_largest(). Analyzing a
tree used to clone every matched Entry that qualified for the top-N
heap, even when nothing else needed a second owned copy. That entry now
moves into the heap directly in the common case, cloning only when both
the heap and duplicate-candidate collection genuinely need their own
copy.

See CHANGELOG.md for full details.

v2.4.0

Choose a tag to compare

@naut54 naut54 released this 09 Sep 21:25

FileEngine::remove(path)/RemoveBuilder (new remove feature) — deletes
files under path matching a set of criteria (extension, size range,
modified-time range, exclude globs — same filter shape analyze() uses),
rather than an entire path outright. Two defaults set it apart from
every other builder in the crate, both biased toward safety given this
is the one irreversible operation the crate offers: .dry_run() defaults
to true (.start() only previews matches, in RemoveOutcome::previewed,
until told otherwise), and .hard_delete() defaults to false (matched
entries go to the platform trash/recycle bin rather than being unlinked
outright — a platform with no trash service fails per-entry with
Error::TrashFailed rather than silently falling back to a permanent
delete). Also refuses to run with no filter criteria set at all
(Error::RemoveCriteriaRequired) unless .allow_unfiltered_delete(true)
opts in — an unconfigured filter matching everything under the root
should never be an accident.

Reuses the existing Profiler/Planner/Dispatcher pipeline (concurrency,
ErrorStrategy, progress stream) rather than a bespoke delete path.
.max_depth() and .follow_symlinks() bound the underlying walk the same
way AnalyzeBuilder's do — backed by a new ScanOptions on the shared
internal scan() (walkdir's own max_depth/follow_links), which
copy/move/sync/compress continue to call with the original hardcoded
behavior (unbounded depth, symlinks never followed).

See CHANGELOG.md for full details.

v2.3.0

Choose a tag to compare

@naut54 naut54 released this 09 Sep 09:27

FileEngine::move_many(sources, dest)/MoveManyBuilder moves several
independent sources into one destination directory as a single batched
operation — one shared ErrorStrategy, concurrency pool, and progress
stream across all of them, rather than one .move_path() call per source.

.skip_if_identical(bool) (checksum feature) on CopyBuilder/MoveBuilder/
MoveManyBuilder compares content instead of failing outright when the
destination already exists: an identical destination is left in place
(a move still removes the now-redundant source), a genuinely different
one still fails with Error::DestExists.

Also fixes MoveBuilder's atomic-rename fast path: it now creates a
missing destination parent directory (previously misreported as
Error::SourceNotFound), and now actually enforces .overwrite(false)
instead of silently deferring to rename(2)'s native overwrite semantics.

Internally, the ten near-identical classify_io_error() copies scattered
across operations/profiler/analysis were consolidated into one.

See CHANGELOG.md for full details.

v2.2.0

Choose a tag to compare

@naut54 naut54 released this 09 Sep 09:28

analyze()'s tree walk is now multithreaded. Replaced walkdir with jwalk
under the analyze feature: directory reads and stat() calls now run
across a worker pool instead of one at a time on a single thread.
Aggregation (the largest-files heap, extension/MIME stats, age buckets)
still happens serially on the consuming task, so no locking was
introduced there — only the underlying directory traversal is
parallelized. On a 330k-file/138k-dir local tree this cut wall time from
12.8s (single-threaded, 45% CPU) to 9.0s (194% CPU) with a warm disk
cache; the gap widens further on cold caches or network filesystems.
profiler::scan (feature operations) is unaffected — it still uses
walkdir.

AnalyzeBuilder::walk_concurrency(n) — worker-thread count for the
parallel walk, defaulting to available_parallelism(), matching
.hash_concurrency().

See CHANGELOG.md for full details.

v2.1.0

Choose a tag to compare

@naut54 naut54 released this 09 Sep 09:28

FileEngine::analyze() — read-only tree inspection: file/directory
counts, total size, the largest N files, size and count grouped by
extension, an age histogram (last-modified bucketed into
under-a-day/week/month/year/older), plus filters (extension, glob-based
excludes, size range, modified-time range, max depth, symlink-following).
Feature analyze, on by default.

Follows the same builder shape as every other operation
(FileEngine::analyze(path)...start()), but returns a dedicated
AnalysisHandle/AnalysisProgress pair rather than the existing
Handle/Progress, since both of those live behind the operations
feature, which analyze deliberately doesn't require.

.detect_mime_types(bool) (feature analyze) and .detect_duplicates(bool)
(feature checksum) on AnalyzeBuilder — both off by default, since either
turns the walk from metadata-only into a full extra read per matched
file. Duplicate detection groups candidates by size first and only
blake3-hashes files that actually collide.

AnalysisReport::errors/errors_total and (feature checksum)
AnalysisReport::duplicates/duplicate_groups_total/duplicate_bytes_wasted.

Additive, no breaking changes. See CHANGELOG.md for full details.