This project crawls configured folders, extracts file and directory metadata, buffers it as JSON, and flushes batches to disk with checkpoint recovery.
The crawler prioritizes deterministic output and recoverable long-running scans. It favors explicit checkpoints over implicit retry logic, uses immutable metadata records for serialization, and separates traversal from extraction so that file system IO can continue while metadata is buffered and written. The design is intentionally pragmatic: prefer simple concurrency patterns (a bounded queue and a fixed thread pool) to keep recovery and output predictable.
The codebase is intentionally small and centers around a few abstractions:
- CrawlerEngine: orchestrates traversal, parallel file metadata extraction, buffering, and checkpointing.
- JsonBuffer: batches metadata envelopes and writes sequential JSON files.
- CheckpointManager + RunState: persist and reload crawl progress.
- IdRegistry: provides stable numeric IDs for paths during a run.
- FileMetadataExtractor: encapsulates filesystem reads, MIME detection, and hashing.
- DirectoryStats + DirectoryStatsSnapshot: track and serialize aggregated directory totals.
- MetadataEnvelope: normalizes file and directory records into a single output stream.
- ProcessingLimiter: allows early stopping for testing and recovery validation.
- App loads a JSON config and wires the engine, checkpoint manager, and optional limiter.
- ConfigLoader reads the JSON payload and applies defaults (buffer sizes, threads, checkpoint path).
- CrawlerEngine restores the prior RunState (if present), seeds the directory queue, and spawns:
- a fixed worker pool to FileMetadataExtractor, and
- a consumer thread that streams MetadataEnvelope objects into JsonBuffer.
- While traversing, DirectoryStats are updated for each file and persisted into the checkpoint via DirectoryStatsSnapshot.
- JsonBuffer flushes metadata arrays into
metadata_*.jsonfiles when the threshold is met. - On completion, CrawlerEngine emits directory metadata, flushes, and writes a final checkpoint.
+-------------------+
| config.json |
+-------------------+
|
v
+-----------------------+
| ConfigLoader |
+-----------------------+
|
v
+-----------------------+
| CrawlerEngine |
+-----------------------+
/ | \
/ | \
v v v
+-------------------+ +-------------------+ +----------------------+
| CheckpointManager | | Worker Pool | | Consumer Thread |
| + RunState | | FileMetadataExtr. | | JsonBuffer |
+-------------------+ +-------------------+ +----------------------+
| | |
v v v
checkpoint.json MetadataEnvelope metadata_*.json
What this means: CrawlerEngine is the hub. It reads configuration, restores a prior run if needed, seeds traversal, and fans out work. The worker pool extracts metadata and produces MetadataEnvelope objects, while the consumer thread batches them into JSON files. CheckpointManager is consulted periodically to persist the current queue, counters, and directory stats so a crash or pause can be resumed deterministically.
Roots from config
|
v
+------------------+
| Directory Queue |<-------------------------------+
+------------------+ |
| |
v |
+------------------+ files + subdirs +--------+-------+
| Traversal Loop |------------------------>| IdRegistry |
| (CrawlerEngine) | +----------------+
+------------------+ |
| |
+-------+-------------------+ |
| | |
v v |
+---------------+ +-------------------+ |
| Worker Pool | | DirectoryStats | |
| (Extractor) | | (per directory) | |
+---------------+ +-------------------+ |
| | |
v v |
MetadataEnvelope DirectoryStatsSnapshot |
| | |
+------------+ | |
v v |
+-------------------------+ |
| Bounded Envelope Queue |<--------------+
+-------------------------+
|
v
+---------------+
| JsonBuffer |
| (flush batch) |
+---------------+
|
v
metadata_*.json
Step-by-step explanation:
- Seed traversal:
CrawlerEnginereads the configured roots and pushes the initial directories onto the in-memory queue. - Traverse and register IDs: Each directory is popped, assigned a stable numeric ID via
IdRegistry, and inspected for subdirectories and files. - Dispatch extraction: File paths are submitted to the worker pool, where
FileMetadataExtractorperforms filesystem reads, MIME detection, hashing, and size/timestamp collection. - Aggregate directory stats: As files are visited,
DirectoryStatsaccumulates totals (counts, bytes, and hashes) and snapshots are created for checkpoint persistence. - Buffer envelopes: Extracted file and directory records are wrapped in
MetadataEnvelopeobjects and placed on the bounded queue for the consumer. - Flush output:
JsonBufferconsumes envelopes in batches and writesmetadata_000001.json,metadata_000002.json, etc., ensuring deterministic ordering and segment sizes.
+-------------------+
Start --------->| Checkpoint exists?|--No--> Fresh run
+-------------------+
|
Yes
v
+-------------------+
| Load RunState |
| (queue, counters, |
| stats snapshots) |
+-------------------+
|
v
+-------------------+
| Resume traversal |
| + continue batch |
| numbering |
+-------------------+
Why this is safe: every N entries (configurable by checkpointEntryInterval), the crawler persists the directory queue, sequence numbers, and DirectoryStatsSnapshot so that a resumed run can continue from the same logical position without re-emitting already-flushed metadata batches.
mvn testmvn -q -DskipTests package
java -jar target/file-crawler-1.0.0.jar path/to/config.json{
"roots": [
"C:/data",
"D:/archive"
],
"outputDirectory": "C:/crawler-output",
"bufferEntryThreshold": 1000,
"checkpointEntryInterval": 2000,
"threadCount": 8,
"followLinks": false,
"checkpointFile": "C:/crawler-output/checkpoint.json",
"s3SyncEnabled": false,
"s3Bucket": "example-bucket",
"s3Prefix": "crawler-output",
"s3Region": "us-east-1"
"fileRetryAttempts": 3,
"excludeFilePatterns": [
"*.tmp",
"*.bak"
],
"excludeDirectoryPatterns": [
"**/node_modules",
"**/.git"
]
}maxEntries can be set to limit the number of files processed before a checkpoint is written (useful for testing recovery).
When s3SyncEnabled is true, each generated metadata_*.json file is uploaded to the configured bucket using a dedicated single-threaded sync worker. Supply s3Bucket to enable uploads and optionally set s3Prefix and s3Region to control the destination key and AWS region.
Each flush writes a JSON array of MetadataEnvelope objects to metadata_000001.json, metadata_000002.json, etc. Each envelope contains a type (file or directory) and the related metadata payload.
Failed file extractions are written to failed_000001.json, failed_000002.json, etc. Each record includes the file path, the total attempts, and a list of retry attempts (timestamps and error messages).
On startup the crawler checks for a checkpoint file. If it exists, the crawl resumes from the last saved queue, and sequence numbers continue from the last flushed batch.