Files
go_bt/scripts/bitbrowser-clean-package/make_clean_db.py
T

143 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""Create a sanitized BitBrowser database while preserving extensions and kernels."""
from __future__ import annotations
import argparse
import json
import os
import sqlite3
from pathlib import Path
def quote_identifier(value: str) -> str:
return '"' + value.replace('"', '""') + '"'
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--source", required=True)
parser.add_argument("--output", required=True)
args = parser.parse_args()
source_path = Path(args.source).resolve()
output_path = Path(args.output).resolve()
if source_path == output_path:
raise RuntimeError("The output database must differ from the source database")
if not source_path.is_file():
raise FileNotFoundError(source_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
if output_path.exists():
output_path.unlink()
source = sqlite3.connect(f"file:{source_path.as_posix()}?mode=ro", uri=True)
target = sqlite3.connect(str(output_path))
try:
source.backup(target)
target.execute("PRAGMA foreign_keys = OFF")
tables = [
row[0]
for row in target.execute(
"SELECT name FROM sqlite_master "
"WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
)
]
extension_rows = []
if "local_extensions" in tables:
extension_rows = target.execute(
"SELECT name, desc, extension_id, icon, version, is_use "
"FROM local_extensions ORDER BY id"
).fetchall()
chrome_rows = []
if "chrome_versions" in tables:
chrome_rows = target.execute(
"SELECT chrome, version FROM chrome_versions ORDER BY id"
).fetchall()
firefox_rows = []
if "firefox_versions" in tables:
firefox_rows = target.execute(
"SELECT firefox, version FROM firefox_versions ORDER BY id"
).fetchall()
for table in tables:
target.execute(f"DELETE FROM {quote_identifier(table)}")
target.execute("DELETE FROM sqlite_sequence")
if extension_rows:
target.executemany(
"INSERT INTO local_extensions "
"(name, desc, extension_id, icon, version, is_use, cloud_id, main_user_id) "
"VALUES (?, ?, ?, ?, ?, ?, NULL, NULL)",
extension_rows,
)
if chrome_rows:
target.executemany(
"INSERT INTO chrome_versions (chrome, version) VALUES (?, ?)",
chrome_rows,
)
if firefox_rows:
target.executemany(
"INSERT INTO firefox_versions (firefox, version) VALUES (?, ?)",
firefox_rows,
)
target.commit()
target.execute("VACUUM")
target.execute("PRAGMA optimize")
counts = {
table: target.execute(
f"SELECT COUNT(*) FROM {quote_identifier(table)}"
).fetchone()[0]
for table in tables
}
allowed_nonempty = {"local_extensions", "chrome_versions", "firefox_versions"}
unexpected = {
table: count
for table, count in counts.items()
if count and table not in allowed_nonempty
}
if unexpected:
raise RuntimeError(f"Sanitization failed; unexpected rows remain: {unexpected}")
integrity = target.execute("PRAGMA integrity_check").fetchone()[0]
if integrity != "ok":
raise RuntimeError(f"SQLite integrity check failed: {integrity}")
report = {
"source": str(source_path),
"output": str(output_path),
"extensions": [
{
"name": row[0],
"extension_id": row[2],
"version": row[4],
"enabled": bool(row[5]),
}
for row in extension_rows
],
"chrome_versions": [
{"chrome": row[0], "version": row[1]} for row in chrome_rows
],
"firefox_versions": [
{"firefox": row[0], "version": row[1]} for row in firefox_rows
],
"table_counts": counts,
"integrity_check": integrity,
"size_bytes": os.path.getsize(output_path),
}
print(json.dumps(report, ensure_ascii=False, indent=2))
finally:
target.close()
source.close()
return 0
if __name__ == "__main__":
raise SystemExit(main())