Initial Codes
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# config package
|
||||
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
Connection profile persistence.
|
||||
Profiles are stored as JSON in ~/.dbclient/connections.json.
|
||||
Passwords are stored separately in the OS keychain via keyring.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from app.models.connection_model import ConnectionProfile
|
||||
|
||||
_CONN_FILE = Path.home() / ".dbclient" / "connections.json"
|
||||
|
||||
try:
|
||||
import keyring
|
||||
_KEYRING_OK = True
|
||||
except ImportError:
|
||||
_KEYRING_OK = False
|
||||
|
||||
_SERVICE = "DBClient"
|
||||
|
||||
|
||||
# ── Keyring helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
def save_password(profile_id: str, password: str) -> None:
|
||||
if _KEYRING_OK and password:
|
||||
keyring.set_password(_SERVICE, profile_id, password)
|
||||
|
||||
|
||||
def load_password(profile_id: str) -> str:
|
||||
if _KEYRING_OK:
|
||||
try:
|
||||
return keyring.get_password(_SERVICE, profile_id) or ""
|
||||
except Exception:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
|
||||
def delete_password(profile_id: str) -> None:
|
||||
if _KEYRING_OK:
|
||||
try:
|
||||
keyring.delete_password(_SERVICE, profile_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# ── Profile CRUD ─────────────────────────────────────────────────────────────
|
||||
|
||||
def _load_raw() -> list:
|
||||
if _CONN_FILE.exists():
|
||||
try:
|
||||
with open(_CONN_FILE, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return []
|
||||
return []
|
||||
|
||||
|
||||
def _save_raw(data: list) -> None:
|
||||
_CONN_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(_CONN_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
|
||||
def load_profiles() -> list:
|
||||
"""Return list of ConnectionProfile with passwords injected from keyring."""
|
||||
profiles = []
|
||||
for raw in _load_raw():
|
||||
p = ConnectionProfile.from_dict(raw)
|
||||
p.password = load_password(p.id)
|
||||
profiles.append(p)
|
||||
return profiles
|
||||
|
||||
|
||||
def save_profile(profile: ConnectionProfile) -> None:
|
||||
"""Upsert a profile (save or update)."""
|
||||
raw_list = _load_raw()
|
||||
# Replace existing or append
|
||||
found = False
|
||||
for i, raw in enumerate(raw_list):
|
||||
if raw.get("id") == profile.id:
|
||||
raw_list[i] = profile.to_dict()
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
raw_list.append(profile.to_dict())
|
||||
_save_raw(raw_list)
|
||||
save_password(profile.id, profile.password)
|
||||
|
||||
|
||||
def delete_profile(profile_id: str) -> None:
|
||||
"""Remove a profile by ID."""
|
||||
raw_list = [r for r in _load_raw() if r.get("id") != profile_id]
|
||||
_save_raw(raw_list)
|
||||
delete_password(profile_id)
|
||||
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
App-wide settings: persists to ~/.dbclient/settings.json
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
APP_DIR = Path.home() / ".dbclient"
|
||||
SETTINGS_FILE = APP_DIR / "settings.json"
|
||||
|
||||
DEFAULTS = {
|
||||
"theme": "dark",
|
||||
"font_family": "Consolas",
|
||||
"font_size": 13,
|
||||
"result_page_size": 1000,
|
||||
"query_timeout": 60,
|
||||
"auto_commit": True,
|
||||
"show_row_numbers": True,
|
||||
"word_wrap": False,
|
||||
"max_history": 500,
|
||||
}
|
||||
|
||||
|
||||
class Settings:
|
||||
"""Thin wrapper around a JSON settings file."""
|
||||
|
||||
def __init__(self):
|
||||
self._data: dict = {}
|
||||
self._load()
|
||||
|
||||
def _load(self) -> None:
|
||||
APP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
if SETTINGS_FILE.exists():
|
||||
try:
|
||||
with open(SETTINGS_FILE, "r", encoding="utf-8") as f:
|
||||
self._data = json.load(f)
|
||||
except Exception:
|
||||
self._data = {}
|
||||
# Fill in missing defaults
|
||||
for k, v in DEFAULTS.items():
|
||||
self._data.setdefault(k, v)
|
||||
|
||||
def save(self) -> None:
|
||||
APP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
with open(SETTINGS_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(self._data, f, indent=2)
|
||||
|
||||
def get(self, key: str, fallback=None):
|
||||
return self._data.get(key, DEFAULTS.get(key, fallback))
|
||||
|
||||
def set(self, key: str, value) -> None:
|
||||
self._data[key] = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.get(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.set(key, value)
|
||||
|
||||
|
||||
# Singleton
|
||||
_settings: Settings | None = None
|
||||
|
||||
|
||||
def get_settings() -> Settings:
|
||||
global _settings
|
||||
if _settings is None:
|
||||
_settings = Settings()
|
||||
return _settings
|
||||
Reference in New Issue
Block a user