Introduction

FsPulse logo

FsPulse is a Rust-based command-line tool designed to capture and analyze directory states, detect changes over time, validate file integrity and formats, and query results using a powerful and intuitive query syntax.

Key Capabilities

  • Directory Scanning — Track additions, deletions, and modifications of files and directories.
  • Content Validation — Validate file formats such as FLAC, JPEG, GIF, BMP, PNG, TIFF, and PDF.
  • SHA2 Hashing — Optionally detect file content changes beyond metadata.
  • Powerful Querying — Access scan results directly with flexible, SQL-like queries.
  • Interactive Mode — Navigate and explore scan results using a menu-driven shell interface.

FsPulse is designed to scale across large file systems while maintaining clarity and control for the user.

Getting Started

FsPulse can be installed in one of three ways:

  1. Install via crates.io
  2. Clone and build from source
  3. Download a pre-built release binary from GitHub

Choose the method that works best for your platform and preferences.


1. Install via Crates.io

The easiest way to get FsPulse is via crates.io:

cargo install fspulse

This will download, compile, and install the latest version of FsPulse into Cargo’s bin directory, typically ~/.cargo/bin. That directory is usually already in your PATH. If it's not, you may need to add it manually.

Then run:

fspulse --help

To upgrade to the latest version later:

cargo install fspulse --force

2. Clone and Build from Source

If you prefer working directly with the source code (for example, to contribute or try out development versions):

git clone https://github.com/gtunes-dev/fspulse.git
cd fspulse
cargo build --release

Then run it from the release build directory:

./target/release/fspulse --help

3. Download Pre-Built Release Binaries

Pre-built release binaries for Linux, macOS, and Windows are available on the GitHub Releases page:

  1. Visit the releases page.
  2. Download the appropriate archive for your operating system.
  3. Unpack the archive.
  4. Optionally move the fspulse binary to a directory included in your PATH.

For example, on Unix systems:

mv fspulse /usr/local/bin/

Then confirm it's working:

fspulse --help

First Scan

To scan a directory:

fspulse scan --root-path /some/directory

Interactive Exploration

After scanning, you can explore results in an interactive shell:

fspulse interact

Querying

Use flexible, SQL-like queries to retrieve and filter scan results:

# Items whose path contains 'reports'
fspulse query "items where item_path:('reports')"

# Changes involving items detected as invalid
fspulse query "changes where val_new:(I) show default, val_old, val_new order by change_id desc"

See the Query Syntax page for more examples.

Scanning

FsPulse scans are at the core of how it tracks changes to the file system over time. A scan creates a snapshot of a root directory and analyzes changes compared to previous scans. This page explains how to initiate scans, how incomplete scans are handled, and the phases involved in each scan.


Initiating a Scan

You can start a scan in one of two ways:

  • Command line:
    fspulse scan --root-path /your/path
    
  • Interactive mode: From the interactive menu, select Scan to re-scan a root path that has previously been scanned.

Interactive mode only supports scanning previously scanned paths. To scan a new root for the first time, use the command line.

Once a scan on a root has begun, it must complete or be explicitly stopped before another scan on the same root can be started. Scans on other roots can run independently.


Hashing

Hashing is a key capabilities of FsPulse.

FsPulse uses the standard SHA2 (256) message-digest algorithm to compute digital fingerprints of file contents. The intent of hashing is to enable the detection of changes to file content in cases where the modification date and file size have not changed. One example of a case where this might occur is data decay. FsPulse can be used to create a hash baseline by scanning with the "hash" option. By default, a "hash" scan will compute hashes for items that have never been hashed or whose file size or modification date has changed. If the "hash-all" flag is passed along with the "hash" flag, FsPulse will hash all files, including those that have been previously hashed. If a hash is detected to have changed, a change record will be created.

The query below, for example, will find and show changes where file metadata has not changed, but the file's hash has changed.

fspulse query 'changes where meta_change:(F), hash_change:(T) show default, item_path order by change_id desc'

Validating

FsPulse can attempt to assess the "validity" of files.

FsPulse uses community-contributed libraries to "validate" files. Validation is implemented as opening and reading or traversing the file. These community libraries raise a variety of "errors" when invalid content is encountered.

FsPulse's ability to validate files is limited to the capabilities of the libraries that it uses and these libraries vary in terms of completeness and accuracy. In some cases, such as FsPulse's use of lopdf to validate PDF files, false positive "errors" may be detected as a consequence of lopdf encountering PDF file contents it does not yet understand. Despite these limitations, FsPulse offers a unique and effective view into potential validity issues in files.

As with hash options, FsPulse has two command-line flags related to validation: "validate" and "validate-all".

Passing "validate" will cause FsPulse to perform a validation pass on all files that have never been validated or have changed in terms of modification date or size. Passing "validate-all" will cause FsPulse to validate all files.

Validation states are stored in the database as:

  • U: Unknown. No validation has been performed
  • N: No Validator. No validator exists for this file type
  • V: Valid. Validation was performed and no errors were encountered
  • I: Invalid. Validation was performed and an error was encountered

In the case of 'I', the validation error will be stored as val_error on the Item alongside the validation state, which is stored as val. When an item's validation state changes in any way, the change is recorded on a change record and the old and new states are both available on that record.

If a validation pass produces an error which is identical to the previously seen error, no change is recorded.

An example of a query that displays validation state changes is:

 fspulse query 'changes where val_change:(T) show default, item_path order by change_id desc'

Additional queries can be easily composed which filter on specific old and new validation states.


In-Progress Scans

FsPulse is designed to be resilient to interruptions like system crashes or power loss. If a scan stops before completing, FsPulse saves its state so it can be resumed later.

To resume or discard an in-progress scan:

fspulse scan --root-path /your/path

If a scan is in progress, FsPulse will prompt you to:

  • Resume the scan from where it left off
  • Stop the scan and discard its partial results

Stopping a scan reverts the database to its pre-scan state. All detected changes, computed hashes, and validations from that partial scan will be discarded.


Phases of a Scan

Each scan proceeds in three main phases:

1. Discovery

The directory tree is deeply traversed. For each file or folder encountered:

  • If not seen before:
    • A new item is created
    • An Add change is recorded
  • If seen before:
    • FsPulse compares current file system metadata:
      • Modification date (files and folders)
      • File size (files only)
    • If metadata differs, the item is updated and a Modify change is recorded
  • If the path matches a tombstoned item (previously deleted):
    • If type matches (file/folder), the tombstone is reactivated and an Add change is created
    • If type differs, FsPulse creates a new item and new Add change

Files and folders are treated as distinct types. A single path that appears as both a file and folder at different times results in two separate items.


2. Sweep

FsPulse identifies items not seen during the current scan:

  • Any item that:
    • Is not a tombstone, and
    • Was not visited in the scan

...is marked as a tombstone, and a Delete change is created.

Moved files appear as deletes and adds, as FsPulse does not yet track move operations.


3. Analysis

This phase runs only if the scan is started with --hash and/or --validate.

  • Hashing — Computes a SHA2 hash of file contents
  • Validation — Uses file-type-specific validators to check content integrity

If either the hash or validation result changes:

  • If an Add or Modify change already exists, the new data is attached to it
  • Otherwise, a new Modify change is created

Each change stores both the old and new values for comparison.


Performance and Threading

The analysis phase runs in parallel:


Summary of Phases

PhasePurpose
DiscoveryFinds and records new or modified items
SweepMarks missing items as tombstones and records deletions
AnalysisComputes hashes/validations and records changes if values differ

Each scan provides a consistent view of the file system at a moment in time and captures important differences across revisions.

Configuration

FsPulse supports persistent, user-defined configuration through a file named config.toml. This file allows you to control logging behavior and analysis settings such as thread usage.


Finding config.toml

FsPulse uses the directories crate to determine the appropriate location for configuration files and logs based on your operating system. Specifically, FsPulse uses the local data directory (ProjectDirs::data_local_dir()), which resolves to platform-specific standard paths:

Where it's stored:

PlatformLocation DescriptionExample Path
Linux$XDG_DATA_HOME/home/alice/.local/share/fspulse
macOSApplication Support/Users/alice/Library/Application Support/fspulse
WindowsLocal AppDataC:\Users\Alice\AppData\Local\fspulse

These are all managed internally via the directories crate. On first run, FsPulse will automatically create this directory and write a default config.toml there if one doesn't exist.

Tip: You can delete config.toml at any time to regenerate it with defaults. Newly introduced settings will not automatically be added to an existing file.


Configuration Settings

Here are the current available settings and their default values:

[logging]
fspulse = "info"
lopdf = "error"

[analysis]
threads = 8
hash = "sha2"

Logging

FsPulse uses the Rust log crate, and so does the PDF validation crate lopdf. You can configure logging levels independently for each subsystem in the [logging] section.

Supported log levels:

  • error – only critical errors
  • warn – warnings and errors
  • info – general status messages (default for FsPulse)
  • debug – verbose output for debugging
  • trace – extremely detailed logs

Log File Behavior

  • Logs are written to a logs/ folder inside the same local data directory as config.toml
  • Each run of FsPulse creates a new log file, named using the current date and time
  • FsPulse retains up to 100 log files; older files are automatically deleted

Analysis Settings

The [analysis] section controls how many threads are used during the analysis phase of scanning (for hashing and validation).

  • threads: number of worker threads (default: 8)

You can adjust this based on your system's CPU count or performance needs.

  • hash: hash function to use when hashing files. Values can be sha2 or md5 (default: sha2)

Sha2 is more secure but is slower. It is appropriate for most users.


New Settings and Restoring Defaults

FsPulse may expand its configuration options over time. When new settings are introduced, they won't automatically appear in your existing config.toml. To take advantage of new options, either:

  • Manually add new settings to your config file
  • Delete the file to allow FsPulse to regenerate it with all current defaults

Query Syntax

FsPulse provides a flexible, SQL-like query language for exploring scan results. This language supports filtering, custom column selection, ordering, and limiting the number of results.


Query Structure

Each query begins with one of the four supported domains:

  • roots
  • scans
  • items
  • changes

You can then add any of the following optional clauses:

DOMAIN [WHERE ...] [SHOW ...] [ORDER BY ...] [LIMIT ...]

Column Availability

roots Domain

All queries that retrieve root information begin with the keyword roots:

roots [WHERE ...] [SHOW ...] [ORDER BY ...] [LIMIT ...]
PropertyType
root_idInteger
root_pathPath

scans Domain

All queries that retrieve scan information begin with the keyword scans:

scans [WHERE ...] [SHOW ...] [ORDER BY ...] [LIMIT ...]
PropertyType
scan_idInteger
root_idInteger
root_pathPath
scan_timeDate
is_hashBoolean
hash_allBoolean
is_valBoolean
val_allBoolean
addsInteger
modifiesInteger
deletesInteger

items Domain

All queries that retrieve item information begin with the keyword items:

items [WHERE ...] [SHOW ...] [ORDER BY ...] [LIMIT ...]
PropertyType
item_idInteger
scan_idInteger
root_idInteger
item_pathPath
root_pathPath
file_sizeInteger
file_hashString
valValidation Status
val_errorString
mod_dateDate
item_typeItem Type Enum

changes Domain

All queries that retrieve change history begin with the keyword changes:

changes [WHERE ...] [SHOW ...] [ORDER BY ...] [LIMIT ...]
PropertyType
change_idInteger
scan_idInteger
item_idInteger
root_idInteger
item_pathPath
root_pathPath
file_sizeInteger
file_hashString
val_oldValidation Status
val_newValidation Status
val_error_oldString
val_error_newString
mod_date_oldDate
mod_date_newDate
item_typeItem Type Enum
change_typeChange Type Enum
meta_changeBoolean

The WHERE Clause

The WHERE clause filters results using one or more filters. Each filter has the structure:

column_name:(value1, value2, ...)

Values must match the column’s type. You can use individual values, ranges (when supported), or a comma-separated combination. Values are not quoted unless explicitly shown.

TypeExamplesNotes
Integer5, 1..5, 3, 5, 7..9, null, not null, NULL, NOT NULLSupports ranges and nullability. Ranges are inclusive.
Date2024-01-01, 2024-01-01..2024-06-30, null, not null, NULL, NOT NULLUse YYYY-MM-DD. Ranges are inclusive.
Booleantrue, false, T, F, null, not null, NULL, NOT NULLValues are unquoted. Null values are allowed in all-lower or all-upper case.
String'example', 'error: missing EOF', null, NULLQuoted strings. Null values are allowed in all-lower or all-upper case.
Path'photos/reports', 'file.txt'Must be quoted. Null values are not supported.
Validation StatusV, I, N, U, null, not null, NULL, NOT NULLValid (V), Invalid (I), No Validator (N), Unknown (U). Unquoted. Ranges not supported.
Item Type EnumF, D, null, not null, NULL, NOT NULLFile (F), Directory (D). Unquoted. Ranges not supported.
Change Type EnumA, D, M, null, not null, NULL, NOT NULLAdd (A), Delete (D), Modify (M). Unquoted. Ranges not supported.

Combining Filters

When specifying multiple values within a single filter, the match is logically OR. When specifying multiple filters across different columns, the match is logically AND.

For example:

scans where scan_time:(2025-01-01..2025-01-07, 2025-02-01..2025-02-07), hashing:(T)

This query matches scans that:

  • Occurred in either the first week of January 2025 or the first week of February 2025
  • AND were performed with hashing enabled

The SHOW Clause

The SHOW clause controls which columns are displayed and how some of them are formatted. If omitted, a default column set is used.

You may specify:

  • A list of column names
  • The keyword default to insert the default set
  • The keyword all to show all available columns

Formatting modifiers can be applied using the @ symbol:

item_path@name, mod_date@short

Format Specifiers by Type

TypeAllowed Format Modifiers
Datefull, short, nodisplay
Pathfull, relative, short, name, nodisplay
Validation / Enum / Booleanfull, short, nodisplay
Integer / String(no formatting options)

The ORDER BY Clause

Specifies sort order for the results:

items order by mod_date desc, item_path asc

If direction is omitted, ASC is assumed.


The LIMIT Clause

Restricts the number of rows returned:

items limit 50

Examples

# Items whose path contains 'reports'
items where item_path:('reports')

# Changes involving validation failures
changes where val_new:(I) show default, val_old, val_new order by change_id desc

See also: Interactive Mode · Validators · Configuration

Command-Line Interface

FsPulse provides two primary modes of operation:

  • A powerful command-line interface (CLI)
  • An interactive, menu-driven interface described in the Interactive Mode section

This page documents the full CLI, including top-level commands, available subcommands, and commonly used options.


Getting Help

At any time, you can get help from the CLI using:

fspulse --help
fspulse <command> --help

For example:

fspulse scan --help
fspulse report items --help

Top-Level Commands

interact

Launches FsPulse in interactive mode.

fspulse interact [--db-path <path>]
  • Allows users to choose scan or report actions through a guided menu
  • Only usable for roots that have already been scanned

scan

Performs a filesystem scan.

fspulse scan [--db-path <path>] [--root-id <id> | --root-path <path> | --last] [--hash] [--validate]
  • --root-id — scan an existing root by ID
  • --root-path — scan a new or existing root by its path
  • --last — scan the most recently scanned root
  • --hash — compute SHA2 hashes on new or changed files
  • --hash-all — compute SHA2 hashes on all files (requires --hash)
  • --validate — validate new or changed files with known formats (see Validators)
  • --validate-all — validate all files (requires --validate)

report

Generates prebuilt reports about roots, scans, items, or changes.

fspulse report <subcommand> [options]

Available subcommands:

roots

fspulse report roots [--db-path <path>] [--root-id <id> | --root-path <path>]

scans

fspulse report scans [--db-path <path>] [--scan-id <id> | --last <N>]

items

fspulse report items [--db-path <path>] [--item-id <id> | --item-path <path> | --root-id <id>] [--invalid]

changes

fspulse report changes [--db-path <path>] [--change-id <id> | --item-id <id> | --scan-id <id>]

Notes:

  • --invalid on items requires --root-id

query

Executes a structured query using FsPulse's flexible syntax.

fspulse query [--db-path <path>] "<query string>"

Example:

fspulse query "items where item_path:('docs') order by mod_date desc limit 10"

See Query Syntax for full details.


Shared Option: --db-path

Many commands support the optional --db-path parameter to specify a custom SQLite database location.

  • If omitted, FsPulse defaults to the appropriate system-specific location using the directories crate
  • The database file is always named fspulse.db

Examples:

fspulse scan --root-path /home/user/data --db-path /tmp/fspulse
fspulse query --db-path /var/fspulse "changes where val_new:(I)"

See also: Interactive Mode · Query Syntax · Configuration

Interactive Mode

FsPulse includes an interactive mode that provides a menu-driven interface for common tasks.

Interactive menu

To launch interactive mode:

fspulse interact

Overview

The interactive menu offers the following options:

  • Scan — Re-scan a previously scanned root
  • Query — Run custom queries using the query language
  • Report — View predefined summary reports
  • Exit — Close interactive mode

Scan

This option lets you scan a folder that has already been scanned.

⚠️ You must first perform a scan from the command line using:

fspulse scan --root-path /your/path

Once a root has been scanned at least once, it becomes available in the interactive menu.

Interactive scans allow you to toggle:

  • Hashing — compute SHA2 file hashes
  • Validation — check file content integrity for supported types

Query

Allows you to enter queries using FsPulse’s query syntax.

  • Use full expressions like:
    items where item_path:('photos')
    changes where val_new:(I) show default, val_old, val_new
    
  • Queries may be repeated until you type q or exit
  • Query errors provide detailed syntax feedback
  • Use the ↑ and ↓ arrow keys to scroll through previous entries in your session

Report

Provides quick access to common predefined reports:

  • List all roots
  • Show recent scans
  • Display invalid items
  • View changes from the latest scan

Reports are internally implemented as saved queries and will expand over time.


Interactive mode is especially helpful for exploring your data once scans are available, and for learning the query syntax interactively.

Validators

FsPulse can optionally validate file contents during the analysis phase of a scan. To enable validation, pass the --validate flag when initiating a scan.

fspulse scan --root-path /your/path --validate

Validation allows FsPulse to go beyond basic metadata inspection and attempt to decode the file's contents using format-specific logic. This helps detect corruption or formatting issues in supported file types.


Validation Status Codes

Each item in the database has an associated validation status:

Status CodeMeaning
UUnknown — item has never been included in a validation scan
VValid — most recent validation scan found no issues
IInvalid — validation failed; see validation_error field
NNo Validator — FsPulse does not currently support this file type

The validation_error field contains the error message returned by the validator only if the item was marked invalid. This field is empty for valid items or items with no validator.

Note: Some validation "errors" surfaced by the underlying libraries may not indicate corruption, but rather unsupported edge cases or metadata formatting. Always review the error messages before assuming a file is damaged.


Supported Validators

FsPulse relies on external Rust crates for performing format-specific validation. We gratefully acknowledge the work of the developers behind these crates for making them available to the Rust community.

File TypesCrateLink
FLAC audio (.flac)claxonclaxon on GitHub
Images (.jpg, .jpeg, .png, .gif, .tiff, .bmp)imageimage on GitHub
PDF documents (.pdf)lopdflopdf on GitHub

Validation support may expand in future versions of FsPulse to cover additional file types such as ZIP archives, audio metadata, or XML/JSON files.


$1 See the Query Syntax page for full details on query clauses and supported filters.

Concepts

FsPulse is centered around tracking and understanding the state of the file system over time. The core entities in FsPulse — roots, scans, items, and changes — represent a layered model of this information.


Scans

Scans are the units of work performed by FsPulse. A scan is performed on a file system tree specified by a path. A scan deeply traverses the specified path and its children, recording information on the files and directories discovered. The details of scanning are explained in Scanning.


Root

A root is the starting point for a scan. It represents a specific path on the file system that you explicitly tell FsPulse to track.

Each root is stored persistently in the database, and every scan you perform refers back to a root.

  • Paths are stored as absolute paths.
  • Each root has a unique ID.
  • You can scan a root multiple times over time.

Scan

A scan is a snapshot of a root directory at a specific point in time.

Each scan records metadata about:

  • The time the scan was performed
  • Whether hashing and validation were enabled
  • The collection of items (files and folders) found during the scan

Scans are always tied to a root via root_id, and are ordered chronologically by scan_time.


Item

An item represents a single file or folder discovered during a scan.

Each item includes metadata such as:

  • Path
  • Whether it's a file or directory
  • Last modified date
  • Size
  • Optional hash and validation info

Items are created when newly seen, and marked with a tombstone (is_ts = true) if they were present in previous scans but no longer exist.


Change

A change represents a detected difference in an item between the current scan and a previous one.

Changes may reflect:

  • File additions
  • File deletions
  • Metadata or content modifications

Each change is associated with both the scan and the item it affects.


Entity Flow

A simplified representation of how the entities relate:

Root
 └── Scan (per run)
      └── Item (files and folders)
           └── Change (if the item changed)

These concepts form the foundation of FsPulse’s scan and query capabilities. Understanding them will help you make the most of both interactive and command-line modes.

Database

FsPulse uses an embedded SQLite database to store all scan-related data. The database schema mirrors the core domain concepts used in FsPulse: roots, scans, items, and changes.


Database Name and Location

The database file is always named:

fspulse.db

By default, FsPulse stores the database in the root of the user's home directory, as determined by the directories crate.

PlatformBase LocationExample
Linux$HOME/home/alice
macOS$HOME/Users/Alice
Windows{FOLDERID_Profile}C:\Users\Alice

The full path might look like:

/home/alice/fspulse.db

Custom Database Path

You can override the default location using the --db-path option:

fspulse --db-path /some/other/folder

In this case, FsPulse will look for (or create) a file named fspulse.db inside the specified folder. The filename cannot be changed — only the directory is configurable.


Schema Overview

The database schema is implemented using Rust and reflects the same logical structure used by the query interface:

  • roots — scanned root directories
  • scans — individual scan snapshots
  • items — discovered files and folders with metadata
  • changes — additions, deletions, and modifications between scans

The schema is versioned to allow future upgrades without requiring a full reset.


Exploring the Database

Because FsPulse uses SQLite, you can inspect the database using any compatible tool, such as:

  • DB Browser for SQLite
  • The sqlite3 command-line tool
  • SQLite integrations in many IDEs and database browsers

⚠️ Caution: Making manual changes to the database may affect FsPulse's behavior or stability. Read-only access is recommended.


FsPulse manages all internal data access automatically. Most users will not need to interact with the database directly.

Development

FsPulse is under active development, with regular improvements being made to both its functionality and documentation.

At this time, FsPulse is not open for public contribution. This may change in the future as the project matures and its architecture stabilizes. If you're interested in the project, you're encouraged to:

  • Explore the source code
  • Open GitHub issues to report bugs or request features
  • Follow the project for updates

Your interest and feedback are appreciated.


License

FsPulse is released under the MIT License. You are free to use, modify, and distribute the software under the terms of this license.


Acknowledged Dependencies

FsPulse relies on several open source Rust crates. We gratefully acknowledge the work of these maintainers, particularly for enabling file format validation:

  • claxon — FLAC audio decoding
  • image — image decoding for JPG, PNG, GIF, TIFF, BMP
  • lopdf — PDF parsing and validation

Additional dependencies are listed in the project’s Cargo.toml.


If contribution opportunities open in the future, setup instructions and contribution guidelines will be added here.

Roadmap

FsPulse is an actively evolving project. This roadmap outlines current priorities and areas under consideration for future development. Some features listed here are planned, while others are exploratory.


Planned Enhancements

These areas are actively being developed or prioritized:

🔍 Expanded Query Capabilities

  • Support for additional filter types and data expressions
  • More flexible clause ordering and nesting
  • Improved error reporting and suggestions
  • Customizable output formats in the show clause

📂 Additional Validators

  • Add support for additional file types, such as:
    • ZIP and archive formats
    • Audio metadata (ID3, Vorbis Comments)
    • Structured text formats (JSON, XML)
  • Improve clarity of validation errors

Possible Future Directions

📊 Reporting and Output

  • Saved or user-defined reports
  • Aggregated summaries
  • Export formats: CSV, JSON, GPX

💻 Interactive Experience

  • Query bookmarks or saved history
  • Persistent history across sessions
  • Scrollable/full-screen output views

🌐 Integration & Automation

  • JSON export for pipelines and automation
  • Integration with backup or monitoring systems
  • CLI-friendly structured output

🧰 UI and Visualization Tools

  • Optional terminal-based dashboard (TUI)
  • Future possibility of a lightweight web viewer

Community Involvement (Future)

While FsPulse is not currently accepting contributions, this may change. Potential areas for future help include:

  • Adding validators for more file types
  • Improving query engine features
  • Translations and localization
  • Performance optimization for large-scale scans

If you have ideas, feature requests, or feedback, please open a GitHub issue — community input is welcome.