Storage Layer
Audience: End users, system administrators Last Updated: 2026-04-05 Version: 2.0.0
Overview
The Storage Layer is Llamafin's persistent data foundation. It saves your settings, authentication state, playback preferences, download queues, and scheduler jobs so that everything is restored when you reopen the app — even after a restart or update.
Built on IndexedDB (the modern browser standard for client-side storage), it provides a reliable key-value store that works across all platforms: iOS, Android, Windows, macOS, and Linux.
What It Does
- Persists Your Session: Keeps you logged in between app restarts
- Saves Settings: All preferences, themes, audio settings, and configurations
- Restores Playback State: Remembers what you were listening to, shuffle/repeat mode
- Maintains Download Queues: Offline downloads survive app closures
- Preserves Scheduler Jobs: Background tasks are saved and resume on restart
- Stores Device Metrics: Historical performance data for analysis
- Caches Library State: Active library selection and browsing position
Key Concepts
IndexedDB Storage
Llamafin uses IndexedDB as its primary storage engine. This is the same storage technology used by modern web browsers and mobile app web views for large-scale client-side data persistence.
| Property | Value |
|---|---|
| Storage Type | IndexedDB (via Ionic Storage library) |
| Fallback | localStorage (if IndexedDB unavailable) |
| Data Format | JSON objects (key-value pairs) |
| Capacity | Typically 50MB-1GB+ depending on platform |
| Persistence | Survives app restarts and browser closes |
Key-Value Model
All data is stored as simple key-value pairs, like a dictionary:
| Concept | Description | Example |
|---|---|---|
| Key | A unique identifier string | "settings", "auth", "downloads" |
| Value | Any JSON-serializable data | Objects, arrays, strings, numbers, booleans |
Startup initialisation
Storage is the first feature initialised when the app loads:
- App starts → Storage initialisation begins
- IndexedDB connection established
storageReadysignal sent- Core services begin loading (settings, downloads, network)
- App continues startup
If storage fails to initialise within 10 seconds, the app startup times out and reports an error.
Configuration
The Storage Layer operates transparently with no user-facing configuration. However, several application-level settings interact with storage:
Version-Based Migration
The app tracks schema versions for major features:
| Version Key | Current Version | Purpose |
|---|---|---|
llamafin_settings_db_version | 49 | Settings schema version |
llamafin_downloads_db_version | 49 | Downloads schema version |
When these version numbers increase (via app updates), migration logic runs automatically to update stored data to the new format.
Storage Keys
The app uses 29 distinct storage keys across all features:
| Category | Keys | Purpose |
|---|---|---|
| Authentication | auth, user_id, hashed_user_id | Session and identity |
| Settings | settings, download_settings, device_details | User preferences |
| Playback | current_item, shuffle_status, repeat_status, playback_target | Player state |
| Library | music_id, active_library_id, offline_library | Browsing state |
| Downloads | downloads, llamafin_downloads_db_version | Download queues |
| Network | network | Speed history |
| DJ Mode | dj | Active mode |
| Scheduler | scheduler | Background jobs |
| Device Metrics | device-metrics | Performance history |
| Player UI | player-ui | Interface preferences |
| Playstate | playstate | Playback reports |
| System | jellyfin_url, llamafin_app_version | Server and version |
| Device | mobile_performance_settings_applied, hardware_specs | Device info |
| Connect | playback_target | Remote control target |
| Security | llamafin_device_identity | Public device identity |
| UI | last_tip_index | Loading screen tips |
How It Works
Storage initialisation Flow
App Launch
│
├── Storage initialisation triggered
│
├── IndexedDB connection opened
│ │
│ └── Wait for connection ready
│
├── storageReady signal dispatched
│
├── App version check
│ └── Clear cache if version changed
│
└── Core services begin loading
├── Settings loaded from storage
├── Download queues restored
└── Network listeners activated
Data Access Pattern
Every storage operation follows this pattern:
- Check Ready: Is the storage instance initialised?
- Wait if Needed: If not ready, wait (blocks until ready)
- Execute Operation: Read or write the data
- Return Result: Resolve with the value or error
This ensures that even if data is requested immediately after app launch, it will wait for storage to be ready rather than fail.
Persistence Strategy
| Operation | Behaviour |
|---|---|
| Write | Data is immediately written to IndexedDB |
| Read | Data is read directly from IndexedDB |
| Delete | Key is removed from IndexedDB |
| Clear | All keys are wiped from IndexedDB |
There is no caching layer between the app and IndexedDB — every read/write goes directly to the database.
Storage Lifecycle
┌─────────────────────────────────────────────────────┐
│ Storage Lifecycle │
│ │
│ App Start │
│ ↓ │
│ ┌──────────────────────────┐ │
│ │ Initialise IndexedDB │ │
│ └──────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────┐ │
│ │ storageReady signal │ │
│ └──────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────┐ │
│ │ Load settings, downloads │ │
│ │ network from storage │ │
│ └──────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────┐ │
│ │ App is running │ │
│ │ - Reads/writes on demand │ │
│ │ - No caching layer │ │
│ └──────────────────────────┘ │
│ ↓ │
│ App Close (no cleanup needed) │
│ Data persists in IndexedDB │
└─────────────────────────────────────────────────────┘
Integration with Other Features
Authentication
Auth data is stored on login and restored on startup:
| Key | Content | Purpose |
|---|---|---|
auth | Full authentication object | Session restoration |
user_id | User identifier | Quick user identification |
hashed_user_id | SHA-256 hash of user ID | Anonymous identification |
Settings System
All user preferences are persisted under the settings key. This includes:
- Account preferences
- Server configuration
- Language settings
- Appearance (themes, fonts, visualizers)
- Playback settings (EQ, audio effects)
- Music quality preferences
- Download settings
- Notification preferences
- Advanced settings
- Scheduler configuration
Downloads System
Download queues and metadata are persisted:
| Key | Content |
|---|---|
downloads | Active download queue |
llamafin_downloads_db_version | Schema version for migration |
Job Scheduler
Background jobs are saved and restored on restart:
| Key | Content |
|---|---|
scheduler | All jobs (pending, running, completed) |
This means interrupted downloads or analysis jobs resume when the app restarts.
Player State
Playback state is persisted for seamless restoration:
| Key | Content |
|---|---|
current_item | Currently playing track |
shuffle_status | Shuffle enabled/disabled |
repeat_status | Repeat mode (off, all, one) |
playback_target | remote device control target |
Device Metrics
Historical performance data is stored for trend analysis:
| Key | Content |
|---|---|
device-metrics | Historical CPU, RAM, battery, network data |
Network Management
Network speed history is persisted:
| Key | Content |
|---|---|
network | Historical speed measurements (up to 10,000 entries) |
CryptoService (Security)
Device identity for P2P connections uses secure storage:
| Key | Content | Storage Location |
|---|---|---|
llamafin_device_identity | Public key + device ID | Standard IndexedDB |
| Private key | ECDH/RSA key pair | Native secure keystore (iOS Keychain / Android Keystore) |
The private key is never stored in IndexedDB — it stays in the platform's secure keystore.
Error Handling & Reliability
Self-Healing Mechanisms
| Scenario | Behaviour |
|---|---|
| Storage not ready | Operations wait (block) until storage is initialised |
| Storage initialisation failure | App startup times out after 10 seconds with error |
| Corrupted data | Features handle errors individually with fallbacks |
| Storage full | Errors propagate to individual features for handling |
| IndexedDB unavailable | Falls back to localStorage automatically |
Known Limitations
| Limitation | Impact |
|---|---|
| No encryption at rest | Auth tokens and settings are stored as plain JSON in IndexedDB |
| No built-in versioning | Schema migrations handled individually by each feature |
| No centralized key management | Storage keys are scattered as magic strings across the codebase |
| No storage quota monitoring | App does not track or warn about approaching storage limits |
| No data export/import | Users cannot backup or restore settings manually |
Startup Timeout
If storage fails to initialise within 10 seconds, the app loading sequence times out. This prevents the app from hanging indefinitely on a broken storage system.
Technical Specifications
| Specification | Value |
|---|---|
| Storage engine | IndexedDB (via Ionic Storage + localForage) |
| Fallback chain | IndexedDB → WebSQL → localStorage |
| Data format | JSON (key-value pairs) |
| Access pattern | Async (Promise-based) |
| initialisation | Lazy (on first access) |
| Startup timeout | 10 seconds |
| Total storage keys | 29 distinct keys |
| Consuming features | 18+ features/services |
| Package version | @ionic/storage-angular v4.0.0-next.4 |
| LocalForage version | Bundled with Ionic Storage |
| Encryption | None at rest (except native secure keystore for private keys) |
| Migration | Per-feature version keys (settings v49, downloads v49) |
Security Considerations
What is Stored
| Data Type | Encrypted? | Location |
|---|---|---|
| Auth tokens | No | IndexedDB (plain JSON) |
| User ID | No | IndexedDB (plain text) |
| Settings | No | IndexedDB (plain JSON) |
| Playback state | No | IndexedDB (plain JSON) |
| Download queues | No | IndexedDB (plain JSON) |
| Private crypto keys | Yes | Native secure keystore only |
| Public crypto identity | No | IndexedDB (plain JSON) |
What is Protected
- Private keys for P2P encryption are stored in the platform's secure keystore (iOS Keychain or Android Keystore) — never in IndexedDB
- User ID hashing uses SHA-256 to create an anonymous identifier for tracking purposes
Recommendations
For users concerned about data security:
- Use device-level encryption (FileVault on macOS, BitLocker on Windows)
- Enable screen lock on mobile devices
- Clear app data before selling or disposing of a device
- Log out when using shared devices
Related Documentation
- Authentication & Security - Session persistence
- Settings System - Settings persistence
- Downloads System - Download queue persistence
- Job Scheduler - Background job persistence
- State Management (NgRx) - Startup orchestration
Last Updated: 2026-04-05 Version: 2.0.0