-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdatabase.py
More file actions
230 lines (187 loc) · 7.05 KB
/
Copy pathdatabase.py
File metadata and controls
230 lines (187 loc) · 7.05 KB
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
225
226
227
228
229
230
"""
madOS Audio Equalizer - SQLite State Persistence
==================================================
Persists equalizer session state using SQLite so that gain values,
enabled state, selected preset, and language preference survive
across application restarts.
Database location: ``~/.local/share/mados-equalizer/state.db``
"""
import os
import sqlite3
from contextlib import contextmanager
# Default database path following XDG Base Directory Specification
DEFAULT_DB_DIR = os.path.join(
os.environ.get("XDG_DATA_HOME", os.path.expanduser("~/.local/share")),
"mados-equalizer",
)
DEFAULT_DB_PATH = os.path.join(DEFAULT_DB_DIR, "state.db")
# Schema version — bump when altering tables
_SCHEMA_VERSION = 1
class EqualizerStateDB:
"""SQLite-backed state persistence for the equalizer.
Stores session state as key-value pairs and band gains as a
separate table for efficient per-band updates.
Args:
db_path: Path to the SQLite database file.
Defaults to ``~/.local/share/mados-equalizer/state.db``.
"""
def __init__(self, db_path=None):
self._db_path = db_path or DEFAULT_DB_PATH
os.makedirs(os.path.dirname(self._db_path), exist_ok=True)
self._conn = sqlite3.connect(self._db_path, timeout=5)
self._conn.execute("PRAGMA journal_mode=WAL")
self._create_tables()
@contextmanager
def _transaction(self):
"""Context manager for a database transaction."""
try:
yield self._conn
self._conn.commit()
except Exception:
self._conn.rollback()
raise
def _create_tables(self):
"""Initialize the database schema."""
with self._transaction():
self._conn.executescript("""
CREATE TABLE IF NOT EXISTS session (
key TEXT PRIMARY KEY,
value TEXT
);
CREATE TABLE IF NOT EXISTS band_gains (
band INTEGER PRIMARY KEY,
gain REAL NOT NULL DEFAULT 0.0
);
""")
# ----- session key-value helpers -----
def _get_session(self, key, default=None):
"""Retrieve a session value by key.
Args:
key: The session key.
default: Value to return if key is missing.
Returns:
The stored value as a string, or *default*.
"""
cur = self._conn.execute("SELECT value FROM session WHERE key = ?", (key,))
row = cur.fetchone()
return row[0] if row else default
def _set_session(self, key, value):
"""Store a session key-value pair (upsert).
Args:
key: The session key.
value: The value to store (will be converted to string).
"""
with self._transaction():
self._conn.execute(
"INSERT INTO session (key, value) VALUES (?, ?) "
"ON CONFLICT(key) DO UPDATE SET value = excluded.value",
(key, str(value)),
)
# ----- public API -----
def save_gains(self, gains):
"""Persist the 8-band gain values.
Args:
gains: List of 8 float gain values in dB.
"""
if not isinstance(gains, (list, tuple)) or len(gains) != 8:
return
with self._transaction():
self._conn.execute("DELETE FROM band_gains")
self._conn.executemany(
"INSERT INTO band_gains (band, gain) VALUES (?, ?)",
[(i, float(g)) for i, g in enumerate(gains)],
)
def load_gains(self):
"""Load the persisted 8-band gain values.
Returns:
List of 8 float gain values, or None if no saved state.
"""
cur = self._conn.execute("SELECT band, gain FROM band_gains ORDER BY band")
rows = cur.fetchall()
if len(rows) != 8:
return None
return [row[1] for row in rows]
def save_enabled(self, enabled):
"""Persist the EQ enabled/disabled state.
Args:
enabled: True if the equalizer is active.
"""
self._set_session("enabled", "1" if enabled else "0")
def load_enabled(self):
"""Load the persisted EQ enabled state.
Returns:
True if the equalizer was enabled, False otherwise.
"""
val = self._get_session("enabled", "0")
return val == "1"
def save_preset(self, preset_key):
"""Persist the selected preset key.
Args:
preset_key: The key of the active preset (e.g. 'rock').
"""
self._set_session("preset", preset_key or "")
def load_preset(self):
"""Load the persisted preset key.
Returns:
The preset key string, or None if not saved.
"""
val = self._get_session("preset")
return val if val else None
def save_language(self, language):
"""Persist the UI language preference.
Args:
language: Language code (e.g. 'en', 'es').
"""
self._set_session("language", language or "")
def load_language(self):
"""Load the persisted language preference.
Returns:
The language code string, or None if not saved.
"""
val = self._get_session("language")
return val if val else None
def save_state(self, gains, enabled, preset_key, language):
"""Persist the full equalizer state in a single transaction.
Args:
gains: List of 8 float gain values in dB.
enabled: Whether the EQ is active.
preset_key: The active preset key.
language: The UI language code.
"""
with self._transaction():
# Gains
if isinstance(gains, (list, tuple)) and len(gains) == 8:
self._conn.execute("DELETE FROM band_gains")
self._conn.executemany(
"INSERT INTO band_gains (band, gain) VALUES (?, ?)",
[(i, float(g)) for i, g in enumerate(gains)],
)
# Session values
for key, value in [
("enabled", "1" if enabled else "0"),
("preset", preset_key or ""),
("language", language or ""),
]:
self._conn.execute(
"INSERT INTO session (key, value) VALUES (?, ?) "
"ON CONFLICT(key) DO UPDATE SET value = excluded.value",
(key, str(value)),
)
def load_state(self):
"""Load the full persisted equalizer state.
Returns:
Dictionary with keys: gains, enabled, preset, language.
Values are None/default when not previously saved.
"""
return {
"gains": self.load_gains(),
"enabled": self.load_enabled(),
"preset": self.load_preset(),
"language": self.load_language(),
}
def close(self):
"""Close the database connection."""
try:
self._conn.close()
except Exception:
pass