mirror of
https://github.com/pe1hvh/meshcore-gui.git
synced 2026-07-06 18:01:23 +02:00
333 lines
8.3 KiB
Markdown
333 lines
8.3 KiB
Markdown
# Message & Metadata Persistence
|
|
|
|
**Version:** 1.0
|
|
**Author:** PE1HVH
|
|
**Date:** 2026-02-07
|
|
|
|
## Overview
|
|
|
|
This feature implements persistent storage for all incoming messages, RX log entries, and contacts with configurable retention periods. The system uses a dual-layer architecture to balance real-time UI performance with comprehensive data retention.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ SharedData (in-memory buffer) │
|
|
│ - Last 100 messages (UI) │
|
|
│ - Last 50 rx_log (UI) │
|
|
│ - Thread-safe via Lock │
|
|
└──────────────┬──────────────────────┘
|
|
│ (on every add)
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ MessageArchive (persistent) │
|
|
│ - All messages (JSON) │
|
|
│ - All rx_log (JSON) │
|
|
│ - Retention filtering │
|
|
│ - Automatic cleanup (daily) │
|
|
│ - Separate Lock (no contention) │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
### Design Principles
|
|
|
|
1. **Separation of Concerns**: SharedData handles real-time UI updates, MessageArchive handles persistence
|
|
2. **Thread Safety**: Independent locks prevent contention between UI and archiving
|
|
3. **Batch Writes**: Buffered writes reduce disk I/O (flushes every 10 items or 60 seconds)
|
|
4. **Configurable Retention**: Automatic cleanup based on configurable periods
|
|
5. **Backward Compatibility**: SharedData API unchanged, archive is optional
|
|
|
|
## Storage Format
|
|
|
|
### Messages Archive
|
|
**Location:** `~/.meshcore-gui/archive/<ADDRESS>_messages.json`
|
|
|
|
```json
|
|
{
|
|
"version": 1,
|
|
"address": "literal:AA:BB:CC:DD:EE:FF",
|
|
"last_updated": "2026-02-07T12:34:56.123456Z",
|
|
"messages": [
|
|
{
|
|
"time": "12:34:56",
|
|
"timestamp_utc": "2026-02-07T12:34:56.123456Z",
|
|
"sender": "PE1HVH",
|
|
"text": "Hello mesh!",
|
|
"channel": 0,
|
|
"direction": "in",
|
|
"snr": 8.5,
|
|
"path_len": 2,
|
|
"sender_pubkey": "abc123...",
|
|
"path_hashes": ["a1", "b2"],
|
|
"message_hash": "def456..."
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### RX Log Archive
|
|
**Location:** `~/.meshcore-gui/archive/<ADDRESS>_rxlog.json`
|
|
|
|
```json
|
|
{
|
|
"version": 1,
|
|
"address": "literal:AA:BB:CC:DD:EE:FF",
|
|
"last_updated": "2026-02-07T12:34:56Z",
|
|
"entries": [
|
|
{
|
|
"time": "12:34:56",
|
|
"timestamp_utc": "2026-02-07T12:34:56Z",
|
|
"snr": 8.5,
|
|
"rssi": -95.0,
|
|
"payload_type": "MSG",
|
|
"hops": 2,
|
|
"message_hash": "def456..."
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Note:** The `message_hash` field enables correlation between RX log entries and messages. It will be empty for packets that are not messages (e.g., announcements, broadcasts).
|
|
|
|
## Configuration
|
|
|
|
Add to `meshcore_gui/config.py`:
|
|
|
|
```python
|
|
# Retention period for archived messages (in days)
|
|
MESSAGE_RETENTION_DAYS: int = 30
|
|
|
|
# Retention period for RX log entries (in days)
|
|
RXLOG_RETENTION_DAYS: int = 7
|
|
|
|
# Retention period for contacts (in days)
|
|
CONTACT_RETENTION_DAYS: int = 90
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Basic Usage
|
|
|
|
The archive is automatically initialized when SharedData is created with a device identifier (serial port):
|
|
|
|
```python
|
|
from meshcore_gui.core.shared_data import SharedData
|
|
|
|
# With archive (normal use)
|
|
shared = SharedData("literal:AA:BB:CC:DD:EE:FF")
|
|
|
|
# Without archive (backward compatible)
|
|
shared = SharedData() # archive will be None
|
|
```
|
|
|
|
### Adding Data
|
|
|
|
All data added to SharedData is automatically archived:
|
|
|
|
```python
|
|
from meshcore_gui.core.models import Message, RxLogEntry
|
|
|
|
# Add message (goes to both SharedData and archive)
|
|
msg = Message(
|
|
time="12:34:56",
|
|
sender="PE1HVH",
|
|
text="Hello!",
|
|
channel=0,
|
|
direction="in",
|
|
)
|
|
shared.add_message(msg)
|
|
|
|
# Add RX log entry (goes to both SharedData and archive)
|
|
entry = RxLogEntry(
|
|
time="12:34:56",
|
|
snr=8.5,
|
|
rssi=-95.0,
|
|
payload_type="MSG",
|
|
hops=2,
|
|
)
|
|
shared.add_rx_log(entry)
|
|
```
|
|
|
|
### Getting Statistics
|
|
|
|
```python
|
|
# Get archive statistics
|
|
stats = shared.get_archive_stats()
|
|
if stats:
|
|
print(f"Total messages: {stats['total_messages']}")
|
|
print(f"Total RX log: {stats['total_rxlog']}")
|
|
print(f"Pending writes: {stats['pending_messages']}")
|
|
```
|
|
|
|
### Manual Flush
|
|
|
|
Archive writes are normally batched. To force immediate write:
|
|
|
|
```python
|
|
if shared.archive:
|
|
shared.archive.flush()
|
|
```
|
|
|
|
### Manual Cleanup
|
|
|
|
Cleanup runs automatically daily, but can be triggered manually:
|
|
|
|
```python
|
|
if shared.archive:
|
|
shared.archive.cleanup_old_data()
|
|
```
|
|
|
|
## Performance Characteristics
|
|
|
|
### Write Performance
|
|
- Batch writes: 10 messages or 60 seconds (whichever comes first)
|
|
- Write time: ~10ms for 1000 messages
|
|
- Memory overhead: Minimal (only buffer in memory, ~10 messages)
|
|
|
|
### Startup Performance
|
|
- Archive loading: <500ms for 10,000 messages
|
|
- Archive is counted, not loaded into memory
|
|
- No impact on UI responsiveness
|
|
|
|
### Storage Size
|
|
With default retention (30 days messages, 7 days rxlog):
|
|
- Typical message: ~200 bytes JSON
|
|
- 100 messages/day → ~6KB/day → ~180KB/month
|
|
- Expected archive size: <10MB
|
|
|
|
## Automatic Cleanup
|
|
|
|
The worker runs cleanup daily (every 86400 seconds):
|
|
|
|
1. **Message Cleanup**: Removes messages older than `MESSAGE_RETENTION_DAYS`
|
|
2. **RxLog Cleanup**: Removes entries older than `RXLOG_RETENTION_DAYS`
|
|
3. **Contact Cleanup**: Removes contacts not seen for `CONTACT_RETENTION_DAYS`
|
|
|
|
Cleanup is non-blocking and runs in the background worker thread.
|
|
|
|
## Thread Safety
|
|
|
|
### Lock Ordering
|
|
1. SharedData acquires its lock
|
|
2. SharedData calls MessageArchive methods
|
|
3. MessageArchive acquires its own lock
|
|
|
|
This ordering prevents deadlocks.
|
|
|
|
### Concurrent Access
|
|
- SharedData lock: Protects in-memory buffers
|
|
- MessageArchive lock: Protects file writes and batch buffers
|
|
- Independent locks prevent contention
|
|
|
|
## Error Handling
|
|
|
|
### Disk Write Failures
|
|
- Atomic writes using temp file + rename
|
|
- If write fails: buffer retained for retry
|
|
- Logged to debug output
|
|
- Application continues normally
|
|
|
|
### Corrupt Archives
|
|
- Version checking on load
|
|
- Invalid JSON → skip and start fresh
|
|
- Corrupted data → logged, not loaded
|
|
|
|
### Missing Directory
|
|
- Archive directory created automatically
|
|
- Parent directories created if needed
|
|
|
|
## Testing
|
|
|
|
### Unit Tests
|
|
```bash
|
|
python -m unittest tests.test_message_archive
|
|
```
|
|
|
|
Tests cover:
|
|
- Message and RxLog archiving
|
|
- Batch write behavior
|
|
- Retention cleanup
|
|
- Thread safety
|
|
- JSON serialization
|
|
|
|
### Integration Tests
|
|
```bash
|
|
python -m unittest tests.test_integration_archive
|
|
```
|
|
|
|
Tests cover:
|
|
- SharedData + Archive flow
|
|
- Buffer limits with archiving
|
|
- Persistence across restarts
|
|
- Backward compatibility
|
|
|
|
### Running All Tests
|
|
```bash
|
|
python -m unittest discover tests
|
|
```
|
|
|
|
## Migration Guide
|
|
|
|
### From v5.1 to v5.2
|
|
|
|
No migration needed! The feature is fully backward compatible:
|
|
|
|
1. Existing SharedData code works unchanged
|
|
2. Archive is optional (requires device identifier)
|
|
3. First run creates archive files automatically
|
|
4. No data loss from existing cache
|
|
|
|
### Upgrading Existing Installation
|
|
|
|
```bash
|
|
# No special steps needed
|
|
python meshcore_gui.py literal:AA:BB:CC:DD:EE:FF
|
|
```
|
|
|
|
Archive files will be created automatically on first message/rxlog.
|
|
|
|
## Future Enhancements (Out of Scope for v1.0)
|
|
|
|
- Full-text search in archive
|
|
- Export to CSV/JSON
|
|
- Compression of old messages
|
|
- Cloud sync / multi-device sync
|
|
- Web interface for archive browsing
|
|
- Advanced filtering and queries
|
|
|
|
## Troubleshooting
|
|
|
|
### Archive Not Created
|
|
**Problem:** No `~/.meshcore-gui/archive/` directory
|
|
|
|
**Solution:**
|
|
- Check that SharedData was initialized with device identifier
|
|
- Check disk permissions
|
|
- Enable debug mode: `--debug-on`
|
|
|
|
### Cleanup Not Running
|
|
**Problem:** Old messages not removed
|
|
|
|
**Solution:**
|
|
- Cleanup runs every 24 hours
|
|
- Manually trigger: `shared.archive.cleanup_old_data()`
|
|
- Check retention config values
|
|
|
|
### High Disk Usage
|
|
**Problem:** Archive files growing too large
|
|
|
|
**Solution:**
|
|
- Reduce `MESSAGE_RETENTION_DAYS` in config
|
|
- Run manual cleanup
|
|
- Check for misconfigured retention values
|
|
|
|
## Support
|
|
|
|
For issues or questions:
|
|
- GitHub: [PE1HVH/meshcore-gui](https://github.com/PE1HVH/meshcore-gui)
|
|
- Email: pe1hvh@example.com
|
|
|
|
## License
|
|
|
|
MIT License - Copyright (c) 2026 PE1HVH
|