# How OpenLogi's Device Probing Cache Manages On-Disk Enumeration State

> Discover how OpenLogi's device probing cache uses a file-backed JSON cache to store HID device enumeration results, optimizing startup times and reducing redundant hardware probes.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: internals
- Published: 2026-09-02

---

**OpenLogi stores HID device enumeration results in a file-backed JSON cache to eliminate redundant hardware probing and accelerate startup times.**

The device probing cache in OpenLogi is a lightweight persistence layer that solves a critical performance problem: HID device enumeration is slow and should not repeat every session. This article examines how the cache manages on-disk enumeration state through its trait-based abstraction, file-backed implementation, and seamless integration with the device inventory system.

## Core Cache Abstraction: The ProbeCacheStore Trait

OpenLogi defines its cache contract in [`crates/openlogi-device/src/inventory/persist.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/inventory/persist.rs). The **`ProbeCacheStore`** trait provides a minimal, two-method interface for persistence:

- **`load()`** — Returns a `ProbeCacheSnapshot` representing the immutable enumeration state
- **`save(snapshot)`** — Writes the snapshot to persistent storage

This abstraction allows the device inventory to remain agnostic about storage backends while ensuring the on-disk enumeration state can be recovered across sessions.

## File-Backed Implementation in FileProbeCacheStore

The concrete implementation lives in [`crates/openlogi-hid/src/probe_cache.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/probe_cache.rs). The **`FileProbeCacheStore`** struct handles platform-specific data directory resolution and atomic JSON serialization.

### Platform-Specific Cache Location

`FileProbeCacheStore::in_data_dir()` resolves to:

| Platform | Cache Directory |
|----------|-----------------|
| Linux | `$XDG_DATA_HOME/OpenLogi` or `~/.local/share/OpenLogi` |
| Windows | `%APPDATA%\OpenLogi` |
| macOS | `~/Library/Application Support/OpenLogi` |

### Atomic Read and Write Operations

The cache file ([`probe-cache.json`](https://github.com/AprilNEA/OpenLogi/blob/main/probe-cache.json)) is managed with defensive I/O patterns:

```rust
// Load: Deserialize or fall back to empty snapshot
let snapshot = store.load();  // ProbeCacheSnapshot

// Save: Atomic write with directory creation
store.save(&ProbeCacheSnapshot::of(&inventory.cache))?;

```

The **`load()`** method tolerates missing or corrupted files by returning an empty snapshot. The **`save()`** method uses `std::fs::write` for atomic writes and automatically creates parent directories.

## Integration with Device Inventory

The device inventory in [`crates/openlogi-device/src/inventory.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/inventory.rs) orchestrates cache usage through its **`with_probe_cache`** constructor method.

### Startup: Cache Hydration

When constructed with a cache store, the inventory immediately calls `store.load()` to populate its in-memory state, bypassing fresh HID enumeration.

### Runtime: Cache Persistence

Following any mutation—device pairing, profile updates, or configuration changes—the inventory persists the new state:

```rust
inventory.store.save(&ProbeCacheSnapshot::of(&inventory.cache))?;

```

This ensures the on-disk enumeration state remains synchronized with runtime changes without blocking the critical path.

## Schema Versioning and Immutability

The **`ProbeCacheSnapshot`** includes a `SCHEMA_VERSION` constant for forward compatibility. When the on-disk format changes:

1. Older caches are detected by version mismatch
2. They are silently ignored (treated as empty)
3. A new cache is built from fresh enumeration

The snapshot itself is **immutable**—mutations produce new snapshots rather than modifying existing ones, preventing accidental state corruption.

## Error Handling with ProbeCacheError

All I/O failures are wrapped in **`ProbeCacheError`** with contextual path information via `self.path.display()`. This design choice ensures that:

- Cache read failures degrade gracefully to empty enumeration
- Cache write failures log diagnostic context without crashing the inventory
- The rest of the system continues operating regardless of disk state

## Complete Working Example

```rust
use std::sync::Arc;
use openlogi_hid::FileProbeCacheStore;
use openlogi_device::inventory::{Inventory, ProbeCacheSnapshot};

// Initialize cache in OS-specific data directory
let cache = FileProbeCacheStore::in_data_dir();

// Inject cache into inventory (implicitly calls load())
let mut inventory = Inventory::default()
    .with_probe_cache(Arc::new(cache));

// ... perform device operations ...

// Persist updated state after mutations
inventory
    .store
    .save(&ProbeCacheSnapshot::of(&inventory.cache))
    .expect("Failed to write probe cache");

```

## Summary

- **Trait abstraction**: `ProbeCacheStore` in [`crates/openlogi-device/src/inventory/persist.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/inventory/persist.rs) decouples storage from business logic
- **File implementation**: `FileProbeCacheStore` in [`crates/openlogi-hid/src/probe_cache.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/probe_cache.rs) provides platform-aware JSON persistence
- **Atomic operations**: `load()` with fallback, `save()` with directory creation ensure reliability
- **Version safety**: `SCHEMA_VERSION` enables format evolution without breaking existing installations
- **Graceful degradation**: `ProbeCacheError` wrapping allows the system to function despite disk issues

## Frequently Asked Questions

### Where does OpenLogi store the device probing cache on disk?

The cache resides in a JSON file named [`probe-cache.json`](https://github.com/AprilNEA/OpenLogi/blob/main/probe-cache.json) within a platform-specific data directory. On Linux, this is `$XDG_DATA_HOME/OpenLogi` (typically `~/.local/share/OpenLogi`); on Windows, `%APPDATA%\OpenLogi`; on macOS, `~/Library/Application Support/OpenLogi`. The `FileProbeCacheStore::in_data_dir()` method handles this resolution automatically.

### What happens if the probe cache file is corrupted or deleted?

`FileProbeCacheStore::load()` detects missing or unreadable files and returns an empty `ProbeCacheSnapshot`. The inventory then performs fresh HID enumeration and populates a new cache. This graceful fallback ensures OpenLogi remains functional even with disk failures.

### How does OpenLogi handle changes to the cache file format?

The `ProbeCacheSnapshot` struct includes a `SCHEMA_VERSION` constant. When loading, the implementation checks this version against the expected value. Version mismatches trigger the same fallback as missing files—empty snapshot, fresh enumeration, and new cache creation—allowing seamless format upgrades.

### Can multiple OpenLogi instances share the same probe cache?

The current implementation does not include file locking or coordination mechanisms. Concurrent access from multiple processes could result in race conditions during `save()` operations. For multi-instance deployments, each instance should use isolated cache directories or external coordination should be implemented.