feat: import/export for config and backups
- Add export_config/import_config/export_backup/import_backup to ServerStore - Add 4 buttons row in Setup tab UI with file dialogs - Add i18n keys for EN/RU/ZH (16 keys each) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -237,6 +237,80 @@ class ServerStore:
|
||||
self._notify()
|
||||
log.info(f"Restored from: {filename}")
|
||||
|
||||
# ── Import / Export ──────────────────────────────
|
||||
|
||||
def export_config(self, dest_path: str) -> str:
|
||||
text = json.dumps(self._data, indent=2, ensure_ascii=False)
|
||||
with open(dest_path, "w", encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
log.info(f"Config exported to: {dest_path}")
|
||||
return dest_path
|
||||
|
||||
def import_config(self, src_path: str):
|
||||
if not os.path.exists(src_path):
|
||||
raise FileNotFoundError(f"File not found: {src_path}")
|
||||
with open(src_path, "rb") as f:
|
||||
raw = f.read()
|
||||
try:
|
||||
if is_encrypted(raw):
|
||||
text = decrypt(raw)
|
||||
data = json.loads(text)
|
||||
else:
|
||||
data = json.loads(raw.decode("utf-8"))
|
||||
except Exception as e:
|
||||
raise ValueError(f"Invalid config file: {e}")
|
||||
if not isinstance(data, dict) or not isinstance(data.get("servers"), list):
|
||||
raise ValueError("Invalid config structure: missing 'servers' list")
|
||||
self._data = data
|
||||
self._save()
|
||||
self._notify()
|
||||
log.info(f"Config imported from: {src_path}")
|
||||
|
||||
def export_backup(self, filename: str, dest_path: str) -> str:
|
||||
src = os.path.join(BACKUP_DIR, filename)
|
||||
if not os.path.exists(src):
|
||||
raise FileNotFoundError(f"Backup not found: {filename}")
|
||||
with open(src, "rb") as f:
|
||||
raw = f.read()
|
||||
if is_encrypted(raw):
|
||||
text = decrypt(raw)
|
||||
data = json.loads(text)
|
||||
else:
|
||||
data = json.loads(raw.decode("utf-8"))
|
||||
with open(dest_path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, indent=2, ensure_ascii=False, fp=f)
|
||||
log.info(f"Backup exported to: {dest_path}")
|
||||
return dest_path
|
||||
|
||||
def import_backup(self, src_path: str) -> str:
|
||||
if not os.path.exists(src_path):
|
||||
raise FileNotFoundError(f"File not found: {src_path}")
|
||||
with open(src_path, "rb") as f:
|
||||
raw = f.read()
|
||||
try:
|
||||
if is_encrypted(raw):
|
||||
text = decrypt(raw)
|
||||
data = json.loads(text)
|
||||
else:
|
||||
data = json.loads(raw.decode("utf-8"))
|
||||
except Exception as e:
|
||||
raise ValueError(f"Invalid backup file: {e}")
|
||||
if not isinstance(data, dict) or not isinstance(data.get("servers"), list):
|
||||
raise ValueError("Invalid backup structure: missing 'servers' list")
|
||||
name = os.path.basename(src_path)
|
||||
dest = os.path.join(BACKUP_DIR, name)
|
||||
if os.path.exists(dest):
|
||||
stem, ext = os.path.splitext(name)
|
||||
suffix = datetime.now().strftime("_%H%M%S")
|
||||
name = stem + suffix + ext
|
||||
dest = os.path.join(BACKUP_DIR, name)
|
||||
os.makedirs(BACKUP_DIR, exist_ok=True)
|
||||
encrypted = encrypt(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
with open(dest, "wb") as f:
|
||||
f.write(encrypted)
|
||||
log.info(f"Backup imported: {name}")
|
||||
return name
|
||||
|
||||
# ── Observer ──────────────────────────────────────
|
||||
|
||||
def _notify(self):
|
||||
|
||||
Reference in New Issue
Block a user