A newer version of the Gradio SDK is available:
6.2.0
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
$HOMEwith 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:
[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
{
"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:
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:
keyringlibrary - C / C++:
libsecretAPI - Electron / Node:
keytarmodule
4. Atomic Writes & Safety
- Atomic saves: write to a temp file →
fsync→renameinto place. - Locking: use advisory file locks for concurrent writes.
- Backup rotation: keep one previous version (e.g.,
config.toml.bak). - Versioning: include a
config_versionkey and handle migrations on load.
5. Language Integration Examples
Python Example
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
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.