File size: 6,200 Bytes
292d92c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
This repo contains a Linux desktop app which requires the persistent storage of user provided variables.
We should integrate a proper backend that adheres to best standards in Linux desktop app development:
## Best Practices Overview
Persistent storage in Linux desktop apps should:
- Respect the **XDG Base Directory Specification**
- Use **atomic writes** to avoid corruption
- Separate **config**, **state**, and **cache** data
- Use **SQLite** or structured files (TOML, JSON, etc.) as appropriate
- Avoid polluting `$HOME` with dotfiles
- Be resilient to multiple instances and safe during shutdown
---
## 2. Directory Layout (XDG Spec)
Follow the XDG Base Directory Specification for storing user-specific files.
| Type | Default Path | Example Usage |
|------------------|---------------------------------------|------------------------------------|
| Config | `~/.config/<app>/` | User preferences, themes |
| Data (state) | `~/.local/share/<app>/` | Databases, runtime state |
| Cache | `~/.cache/<app>/` | Rebuildable cache, temp files |
| Logs | `~/.local/state/<app>/logs/` (optional) | Log files |
| System defaults | `/etc/xdg/<app>/` | System-wide default configs |
Respect environment overrides:
`$XDG_CONFIG_HOME`, `$XDG_DATA_HOME`, `$XDG_CACHE_HOME`.
---
## 3. Storage Mechanisms
### **3.1 Lightweight Configuration (human-editable)**
Use **TOML** or **INI** for settings users may edit manually.
Example: `~/.config/myapp/config.toml`
**TOML Example:**
```toml
[ui]
theme = "dark"
font_size = 12
[network]
timeout = 10
use_proxy = false
````
---
### **3.2 Machine State (internal JSON)**
For app-managed state, store as JSON under `~/.local/share/<app>/`.
Example: `~/.local/share/myapp/state.json`
```json
{
"last_session": "2025-10-20T10:00:00Z",
"window_size": [1280, 720],
"recent_files": ["/home/daniel/project1", "/home/daniel/project2"]
}
```
---
### **3.3 Structured / Relational Data**
Use **SQLite** for larger or structured data (history, cached objects, indexed content, etc.).
Example: `~/.local/share/myapp/appdata.sqlite`
* Lightweight and dependency-free
* Excellent read/write concurrency for desktop workloads
* Supports migrations (e.g., via Alembic, SQLAlchemy, or manual schema versioning)
Schema example:
```sql
CREATE TABLE IF NOT EXISTS user_prefs (
key TEXT PRIMARY KEY,
value TEXT,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
```
---
### **3.4 Caches**
For rebuildable or transient data, use `~/.cache/<app>/`.
Examples: compiled thumbnails, temp files, download cache.
Always assume cache is **deletable at any time**.
---
### **3.5 Secrets / Tokens**
Do **not** store sensitive data in your config files.
Use **GNOME Keyring** or **libsecret** via bindings:
* Python: `keyring` library
* C / C++: `libsecret` API
* Electron / Node: `keytar` module
---
## 4. Atomic Writes & Safety
* **Atomic saves:** write to a temp file → `fsync` → `rename` into place.
* **Locking:** use advisory file locks for concurrent writes.
* **Backup rotation:** keep one previous version (e.g., `config.toml.bak`).
* **Versioning:** include a `config_version` key and handle migrations on load.
---
## 5. Language Integration Examples
### **Python Example**
```python
from platformdirs import PlatformDirs
import tomllib, tomli_w, json, sqlite3, tempfile, os
from pathlib import Path
APP = "myapp"
dirs = PlatformDirs(APP)
cfg_dir = Path(dirs.user_config_dir)
data_dir = Path(dirs.user_data_dir)
cfg_dir.mkdir(parents=True, exist_ok=True)
data_dir.mkdir(parents=True, exist_ok=True)
# Config TOML
cfg_path = cfg_dir / "config.toml"
config = {"ui": {"theme": "dark"}, "network": {"timeout": 10}}
if cfg_path.exists():
config.update(tomllib.loads(cfg_path.read_bytes()))
tmp = tempfile.NamedTemporaryFile(delete=False, dir=cfg_dir)
tmp.write(tomli_w.dumps(config).encode()); tmp.flush(); os.fsync(tmp.fileno()); tmp.close()
os.replace(tmp.name, cfg_path)
# JSON state
state_path = data_dir / "state.json"
state_path.write_text(json.dumps({"last_run": "2025-10-20"}))
# SQLite database
db_path = data_dir / "myapp.sqlite"
conn = sqlite3.connect(db_path)
conn.execute("CREATE TABLE IF NOT EXISTS items(id INTEGER PRIMARY KEY, name TEXT)")
conn.commit(); conn.close()
```
---
### **Node.js Example**
```js
import fs from "fs/promises";
import { join } from "path";
import os from "os";
const home = process.env.XDG_CONFIG_HOME || join(os.homedir(), ".config");
const cfgDir = join(home, "myapp");
await fs.mkdir(cfgDir, { recursive: true });
const cfgPath = join(cfgDir, "config.toml");
const tmpPath = join(cfgDir, `.config.toml.tmp-${process.pid}`);
await fs.writeFile(tmpPath, 'ui = { theme = "dark" }\n');
await fs.rename(tmpPath, cfgPath); // atomic replace
```
---
## 6. Decision Guide
| Use Case | Recommended Storage |
| ------------------------- | ------------------- |
| User preferences (simple) | TOML / INI |
| Internal app state | JSON |
| Complex structured data | SQLite |
| Temporary data | Cache directory |
| Secrets / tokens | Keyring (libsecret) |
---
## 7. Packaging Considerations
If you later package as **Snap** or **Flatpak**, continue to use these paths relative to `$XDG_*` variables.
The sandbox will remap them internally, preserving user data between updates.
---
## 8. Checklist
✅ Follows XDG directory spec
✅ Uses atomic file operations
✅ Distinguishes config/data/cache
✅ Supports JSON, TOML, and SQLite
✅ Uses system keyring for secrets
✅ User-editable, safe, recoverable
---
## 9. Future Expansion
Later, the app can add:
* Schema migrations for SQLite
* Config version auto-upgrades
* CLI flags or ENV var overrides
* Background sync to cloud storage
* Keyring-based authentication tokens
---
**Goal:** Clean, predictable, Linux-native persistence that works with backups, sync, and sandboxed environments.
|