nkshirsa commited on
Commit
7f84cd9
·
verified ·
1 Parent(s): 6cd8c77

Add phd_research_os/backup.py

Browse files
Files changed (1) hide show
  1. phd_research_os/backup.py +93 -0
phd_research_os/backup.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PhD Research OS — Backup & Recovery (Phase 6)
3
+ ===============================================
4
+ Automated backup and restore for SQLite database.
5
+ """
6
+
7
+ import os
8
+ import sqlite3
9
+ import shutil
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+
13
+
14
+ def backup_database(db_path: str = "data/research_os.db",
15
+ backup_dir: str = "data/backups") -> str:
16
+ """
17
+ Create a timestamped backup of the database using SQLite's backup API.
18
+ Returns the backup file path.
19
+ """
20
+ os.makedirs(backup_dir, exist_ok=True)
21
+
22
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
23
+ backup_path = os.path.join(backup_dir, f"research_os_{timestamp}.db")
24
+
25
+ # Use SQLite's built-in backup (safe, handles WAL)
26
+ source = sqlite3.connect(db_path)
27
+ dest = sqlite3.connect(backup_path)
28
+ source.backup(dest)
29
+ source.close()
30
+ dest.close()
31
+
32
+ # Verify backup
33
+ conn = sqlite3.connect(backup_path)
34
+ claim_count = conn.execute("SELECT COUNT(*) FROM claims").fetchone()[0]
35
+ conn.close()
36
+
37
+ print(f"Backup created: {backup_path} ({claim_count} claims)")
38
+ return backup_path
39
+
40
+
41
+ def restore_database(backup_path: str, db_path: str = "data/research_os.db") -> bool:
42
+ """
43
+ Restore database from backup.
44
+ Creates a safety backup of current DB first.
45
+ """
46
+ if not os.path.exists(backup_path):
47
+ print(f"Error: Backup file not found: {backup_path}")
48
+ return False
49
+
50
+ # Safety backup of current
51
+ if os.path.exists(db_path):
52
+ safety = db_path + ".pre_restore"
53
+ shutil.copy2(db_path, safety)
54
+ print(f"Safety backup: {safety}")
55
+
56
+ # Restore
57
+ source = sqlite3.connect(backup_path)
58
+ dest = sqlite3.connect(db_path)
59
+ source.backup(dest)
60
+ source.close()
61
+ dest.close()
62
+
63
+ # Verify
64
+ conn = sqlite3.connect(db_path)
65
+ claim_count = conn.execute("SELECT COUNT(*) FROM claims").fetchone()[0]
66
+ conn.close()
67
+
68
+ print(f"Restored from {backup_path} ({claim_count} claims)")
69
+ return True
70
+
71
+
72
+ def list_backups(backup_dir: str = "data/backups") -> list:
73
+ """List available backups with metadata."""
74
+ if not os.path.exists(backup_dir):
75
+ return []
76
+
77
+ backups = []
78
+ for f in sorted(Path(backup_dir).glob("research_os_*.db"), reverse=True):
79
+ conn = sqlite3.connect(str(f))
80
+ try:
81
+ claim_count = conn.execute("SELECT COUNT(*) FROM claims").fetchone()[0]
82
+ except:
83
+ claim_count = -1
84
+ conn.close()
85
+
86
+ backups.append({
87
+ "path": str(f),
88
+ "size_mb": f.stat().st_size / 1024 / 1024,
89
+ "claims": claim_count,
90
+ "created": datetime.fromtimestamp(f.stat().st_mtime).isoformat()
91
+ })
92
+
93
+ return backups