Add selective import/export for Ctx Manager with checkbox tree UI
Export dialog lets users pick specific projects, entries, summaries, and shared context to save as JSON. Import dialog previews file contents with checkboxes and supports overwrite/skip conflict mode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a7077c7987
commit
31fed163d0
1 changed files with 596 additions and 0 deletions
596
bterminal.py
596
bterminal.py
|
|
@ -2303,6 +2303,482 @@ class SessionSidebar(Gtk.Box):
|
|||
dlg.destroy()
|
||||
|
||||
|
||||
# ─── Ctx Import / Export ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class _CtxExportDialog(Gtk.Dialog):
|
||||
"""Dialog for selectively exporting ctx data to a JSON file."""
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
title="Export Context",
|
||||
transient_for=parent,
|
||||
modal=True,
|
||||
destroy_with_parent=True,
|
||||
)
|
||||
self.add_buttons(
|
||||
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
||||
"Export", Gtk.ResponseType.OK,
|
||||
)
|
||||
self.set_default_size(500, 450)
|
||||
self.set_default_response(Gtk.ResponseType.OK)
|
||||
|
||||
box = self.get_content_area()
|
||||
box.set_border_width(12)
|
||||
box.set_spacing(8)
|
||||
|
||||
# Select all / Deselect all
|
||||
sel_box = Gtk.Box(spacing=8)
|
||||
btn_all = Gtk.Button(label="Select All")
|
||||
btn_all.connect("clicked", lambda _: self._set_all(True))
|
||||
btn_none = Gtk.Button(label="Deselect All")
|
||||
btn_none.connect("clicked", lambda _: self._set_all(False))
|
||||
sel_box.pack_start(btn_all, False, False, 0)
|
||||
sel_box.pack_start(btn_none, False, False, 0)
|
||||
box.pack_start(sel_box, False, False, 0)
|
||||
|
||||
# Tree with checkboxes: toggle, icon, name, data_type, data_key
|
||||
self.store = Gtk.TreeStore(bool, str, str, str, str)
|
||||
self.tree = Gtk.TreeView(model=self.store)
|
||||
self.tree.set_headers_visible(False)
|
||||
|
||||
col = Gtk.TreeViewColumn()
|
||||
cell_toggle = Gtk.CellRendererToggle()
|
||||
cell_toggle.connect("toggled", self._on_toggled)
|
||||
col.pack_start(cell_toggle, False)
|
||||
col.add_attribute(cell_toggle, "active", 0)
|
||||
|
||||
cell_icon = Gtk.CellRendererText()
|
||||
col.pack_start(cell_icon, False)
|
||||
col.add_attribute(cell_icon, "text", 1)
|
||||
|
||||
cell_name = Gtk.CellRendererText()
|
||||
cell_name.set_property("ellipsize", Pango.EllipsizeMode.END)
|
||||
col.pack_start(cell_name, True)
|
||||
col.add_attribute(cell_name, "text", 2)
|
||||
|
||||
self.tree.append_column(col)
|
||||
|
||||
scroll = Gtk.ScrolledWindow()
|
||||
scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
||||
scroll.add(self.tree)
|
||||
box.pack_start(scroll, True, True, 0)
|
||||
|
||||
self._load_data()
|
||||
self.show_all()
|
||||
|
||||
def _load_data(self):
|
||||
import sqlite3
|
||||
if not os.path.exists(CTX_DB):
|
||||
return
|
||||
db = sqlite3.connect(CTX_DB)
|
||||
db.row_factory = sqlite3.Row
|
||||
|
||||
projects = db.execute(
|
||||
"SELECT name FROM sessions ORDER BY name"
|
||||
).fetchall()
|
||||
for proj in projects:
|
||||
pname = proj["name"]
|
||||
proj_iter = self.store.append(None, [
|
||||
True, "\U0001f4c1", pname, "project", pname,
|
||||
])
|
||||
entries = db.execute(
|
||||
"SELECT key FROM contexts WHERE project = ? ORDER BY key",
|
||||
(pname,),
|
||||
).fetchall()
|
||||
for entry in entries:
|
||||
self.store.append(proj_iter, [
|
||||
True, " ", entry["key"], "entry", entry["key"],
|
||||
])
|
||||
scount = db.execute(
|
||||
"SELECT COUNT(*) as c FROM summaries WHERE project = ?",
|
||||
(pname,),
|
||||
).fetchone()["c"]
|
||||
if scount:
|
||||
self.store.append(proj_iter, [
|
||||
True, "\U0001f4cb", f"Summaries ({scount})", "summaries", pname,
|
||||
])
|
||||
|
||||
shared = db.execute("SELECT key FROM shared ORDER BY key").fetchall()
|
||||
if shared:
|
||||
shared_iter = self.store.append(None, [
|
||||
True, "\U0001f517", "Shared", "shared", "",
|
||||
])
|
||||
for entry in shared:
|
||||
self.store.append(shared_iter, [
|
||||
True, " ", entry["key"], "shared_entry", entry["key"],
|
||||
])
|
||||
|
||||
db.close()
|
||||
self.tree.expand_all()
|
||||
|
||||
def _on_toggled(self, renderer, path):
|
||||
it = self.store.get_iter(path)
|
||||
new_val = not self.store.get_value(it, 0)
|
||||
self.store.set_value(it, 0, new_val)
|
||||
# Propagate to children
|
||||
child = self.store.iter_children(it)
|
||||
while child:
|
||||
self.store.set_value(child, 0, new_val)
|
||||
child = self.store.iter_next(child)
|
||||
# Update parent based on children
|
||||
parent = self.store.iter_parent(it)
|
||||
if parent:
|
||||
any_checked = False
|
||||
child = self.store.iter_children(parent)
|
||||
while child:
|
||||
if self.store.get_value(child, 0):
|
||||
any_checked = True
|
||||
break
|
||||
child = self.store.iter_next(child)
|
||||
self.store.set_value(parent, 0, any_checked)
|
||||
|
||||
def _set_all(self, val):
|
||||
def _walk(it):
|
||||
while it:
|
||||
self.store.set_value(it, 0, val)
|
||||
child = self.store.iter_children(it)
|
||||
if child:
|
||||
_walk(child)
|
||||
it = self.store.iter_next(it)
|
||||
root = self.store.get_iter_first()
|
||||
if root:
|
||||
_walk(root)
|
||||
|
||||
def get_export_data(self):
|
||||
"""Collect checked items and return export dict."""
|
||||
import sqlite3
|
||||
if not os.path.exists(CTX_DB):
|
||||
return None
|
||||
db = sqlite3.connect(CTX_DB)
|
||||
db.row_factory = sqlite3.Row
|
||||
data = {"sessions": [], "contexts": [], "shared": [], "summaries": []}
|
||||
|
||||
root = self.store.get_iter_first()
|
||||
while root:
|
||||
dtype = self.store.get_value(root, 3)
|
||||
dkey = self.store.get_value(root, 4)
|
||||
|
||||
if dtype == "project":
|
||||
proj_name = dkey
|
||||
child = self.store.iter_children(root)
|
||||
checked_entries = []
|
||||
include_summaries = False
|
||||
while child:
|
||||
if self.store.get_value(child, 0):
|
||||
ctype = self.store.get_value(child, 3)
|
||||
ckey = self.store.get_value(child, 4)
|
||||
if ctype == "entry":
|
||||
checked_entries.append(ckey)
|
||||
elif ctype == "summaries":
|
||||
include_summaries = True
|
||||
child = self.store.iter_next(child)
|
||||
|
||||
if checked_entries or include_summaries or self.store.get_value(root, 0):
|
||||
row = db.execute(
|
||||
"SELECT * FROM sessions WHERE name = ?", (proj_name,)
|
||||
).fetchone()
|
||||
if row:
|
||||
data["sessions"].append(dict(row))
|
||||
|
||||
for ekey in checked_entries:
|
||||
row = db.execute(
|
||||
"SELECT project, key, value, updated_at FROM contexts "
|
||||
"WHERE project = ? AND key = ?",
|
||||
(proj_name, ekey),
|
||||
).fetchone()
|
||||
if row:
|
||||
data["contexts"].append(dict(row))
|
||||
|
||||
if include_summaries:
|
||||
rows = db.execute(
|
||||
"SELECT project, summary, created_at FROM summaries "
|
||||
"WHERE project = ?",
|
||||
(proj_name,),
|
||||
).fetchall()
|
||||
data["summaries"].extend(dict(r) for r in rows)
|
||||
|
||||
elif dtype == "shared":
|
||||
child = self.store.iter_children(root)
|
||||
while child:
|
||||
if self.store.get_value(child, 0):
|
||||
skey = self.store.get_value(child, 4)
|
||||
row = db.execute(
|
||||
"SELECT * FROM shared WHERE key = ?", (skey,)
|
||||
).fetchone()
|
||||
if row:
|
||||
data["shared"].append(dict(row))
|
||||
child = self.store.iter_next(child)
|
||||
|
||||
root = self.store.iter_next(root)
|
||||
db.close()
|
||||
|
||||
data = {k: v for k, v in data.items() if v}
|
||||
if not data:
|
||||
return None
|
||||
data["_export_version"] = 1
|
||||
return data
|
||||
|
||||
|
||||
class _CtxImportDialog(Gtk.Dialog):
|
||||
"""Dialog for importing ctx data from a JSON file."""
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
title="Import Context",
|
||||
transient_for=parent,
|
||||
modal=True,
|
||||
destroy_with_parent=True,
|
||||
)
|
||||
self.add_buttons(
|
||||
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
||||
"Import", Gtk.ResponseType.OK,
|
||||
)
|
||||
self.set_default_size(500, 450)
|
||||
self.set_default_response(Gtk.ResponseType.OK)
|
||||
self.set_response_sensitive(Gtk.ResponseType.OK, False)
|
||||
|
||||
box = self.get_content_area()
|
||||
box.set_border_width(12)
|
||||
box.set_spacing(8)
|
||||
|
||||
# File chooser
|
||||
file_box = Gtk.Box(spacing=8)
|
||||
file_box.pack_start(Gtk.Label(label="File:"), False, False, 0)
|
||||
self.file_entry = Gtk.Entry(hexpand=True)
|
||||
self.file_entry.set_placeholder_text("Select JSON file\u2026")
|
||||
self.file_entry.set_editable(False)
|
||||
file_box.pack_start(self.file_entry, True, True, 0)
|
||||
btn_browse = Gtk.Button(label="Browse\u2026")
|
||||
btn_browse.connect("clicked", self._on_browse)
|
||||
file_box.pack_start(btn_browse, False, False, 0)
|
||||
box.pack_start(file_box, False, False, 0)
|
||||
|
||||
# Select all / Deselect all
|
||||
sel_box = Gtk.Box(spacing=8)
|
||||
btn_all = Gtk.Button(label="Select All")
|
||||
btn_all.connect("clicked", lambda _: self._set_all(True))
|
||||
btn_none = Gtk.Button(label="Deselect All")
|
||||
btn_none.connect("clicked", lambda _: self._set_all(False))
|
||||
sel_box.pack_start(btn_all, False, False, 0)
|
||||
sel_box.pack_start(btn_none, False, False, 0)
|
||||
box.pack_start(sel_box, False, False, 0)
|
||||
|
||||
# Preview tree: toggle, icon, name, data_type, data_key
|
||||
self.store = Gtk.TreeStore(bool, str, str, str, str)
|
||||
self.tree = Gtk.TreeView(model=self.store)
|
||||
self.tree.set_headers_visible(False)
|
||||
|
||||
col = Gtk.TreeViewColumn()
|
||||
cell_toggle = Gtk.CellRendererToggle()
|
||||
cell_toggle.connect("toggled", self._on_toggled)
|
||||
col.pack_start(cell_toggle, False)
|
||||
col.add_attribute(cell_toggle, "active", 0)
|
||||
|
||||
cell_icon = Gtk.CellRendererText()
|
||||
col.pack_start(cell_icon, False)
|
||||
col.add_attribute(cell_icon, "text", 1)
|
||||
|
||||
cell_name = Gtk.CellRendererText()
|
||||
cell_name.set_property("ellipsize", Pango.EllipsizeMode.END)
|
||||
col.pack_start(cell_name, True)
|
||||
col.add_attribute(cell_name, "text", 2)
|
||||
|
||||
self.tree.append_column(col)
|
||||
|
||||
scroll = Gtk.ScrolledWindow()
|
||||
scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
||||
scroll.add(self.tree)
|
||||
box.pack_start(scroll, True, True, 0)
|
||||
|
||||
# Overwrite option
|
||||
self.chk_overwrite = Gtk.CheckButton(label="Overwrite existing entries")
|
||||
box.pack_start(self.chk_overwrite, False, False, 0)
|
||||
|
||||
self.import_data = None
|
||||
self.show_all()
|
||||
|
||||
def _on_browse(self, button):
|
||||
dlg = Gtk.FileChooserDialog(
|
||||
title="Select context file",
|
||||
parent=self,
|
||||
action=Gtk.FileChooserAction.OPEN,
|
||||
)
|
||||
dlg.add_buttons(
|
||||
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
||||
Gtk.STOCK_OPEN, Gtk.ResponseType.OK,
|
||||
)
|
||||
filt = Gtk.FileFilter()
|
||||
filt.set_name("JSON files")
|
||||
filt.add_pattern("*.json")
|
||||
dlg.add_filter(filt)
|
||||
filt_all = Gtk.FileFilter()
|
||||
filt_all.set_name("All files")
|
||||
filt_all.add_pattern("*")
|
||||
dlg.add_filter(filt_all)
|
||||
if dlg.run() == Gtk.ResponseType.OK:
|
||||
path = dlg.get_filename()
|
||||
self.file_entry.set_text(path)
|
||||
self._load_preview(path)
|
||||
dlg.destroy()
|
||||
|
||||
def _load_preview(self, path):
|
||||
self.store.clear()
|
||||
self.import_data = None
|
||||
self.set_response_sensitive(Gtk.ResponseType.OK, False)
|
||||
try:
|
||||
with open(path, "r") as f:
|
||||
data = json.load(f)
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
dlg = Gtk.MessageDialog(
|
||||
transient_for=self,
|
||||
modal=True,
|
||||
message_type=Gtk.MessageType.ERROR,
|
||||
buttons=Gtk.ButtonsType.OK,
|
||||
text=f"Failed to load file: {e}",
|
||||
)
|
||||
dlg.run()
|
||||
dlg.destroy()
|
||||
return
|
||||
|
||||
self.import_data = data
|
||||
|
||||
# Group by project
|
||||
sessions = {s["name"]: s for s in data.get("sessions", [])}
|
||||
contexts_by_proj = {}
|
||||
for ctx in data.get("contexts", []):
|
||||
contexts_by_proj.setdefault(ctx["project"], []).append(ctx)
|
||||
summaries_by_proj = {}
|
||||
for s in data.get("summaries", []):
|
||||
summaries_by_proj.setdefault(s["project"], []).append(s)
|
||||
|
||||
all_projects = sorted(
|
||||
set(sessions) | set(contexts_by_proj) | set(summaries_by_proj)
|
||||
)
|
||||
for proj_name in all_projects:
|
||||
proj_iter = self.store.append(None, [
|
||||
True, "\U0001f4c1", proj_name, "project", proj_name,
|
||||
])
|
||||
for ctx in contexts_by_proj.get(proj_name, []):
|
||||
self.store.append(proj_iter, [
|
||||
True, " ", ctx["key"], "entry", ctx["key"],
|
||||
])
|
||||
scount = len(summaries_by_proj.get(proj_name, []))
|
||||
if scount:
|
||||
self.store.append(proj_iter, [
|
||||
True, "\U0001f4cb", f"Summaries ({scount})", "summaries", proj_name,
|
||||
])
|
||||
|
||||
shared = data.get("shared", [])
|
||||
if shared:
|
||||
shared_iter = self.store.append(None, [
|
||||
True, "\U0001f517", "Shared", "shared", "",
|
||||
])
|
||||
for entry in shared:
|
||||
self.store.append(shared_iter, [
|
||||
True, " ", entry["key"], "shared_entry", entry["key"],
|
||||
])
|
||||
|
||||
self.tree.expand_all()
|
||||
self.set_response_sensitive(Gtk.ResponseType.OK, True)
|
||||
|
||||
def _on_toggled(self, renderer, path):
|
||||
it = self.store.get_iter(path)
|
||||
new_val = not self.store.get_value(it, 0)
|
||||
self.store.set_value(it, 0, new_val)
|
||||
child = self.store.iter_children(it)
|
||||
while child:
|
||||
self.store.set_value(child, 0, new_val)
|
||||
child = self.store.iter_next(child)
|
||||
parent = self.store.iter_parent(it)
|
||||
if parent:
|
||||
any_checked = False
|
||||
child = self.store.iter_children(parent)
|
||||
while child:
|
||||
if self.store.get_value(child, 0):
|
||||
any_checked = True
|
||||
break
|
||||
child = self.store.iter_next(child)
|
||||
self.store.set_value(parent, 0, any_checked)
|
||||
|
||||
def _set_all(self, val):
|
||||
def _walk(it):
|
||||
while it:
|
||||
self.store.set_value(it, 0, val)
|
||||
child = self.store.iter_children(it)
|
||||
if child:
|
||||
_walk(child)
|
||||
it = self.store.iter_next(it)
|
||||
root = self.store.get_iter_first()
|
||||
if root:
|
||||
_walk(root)
|
||||
|
||||
def get_selected_data(self):
|
||||
"""Return (filtered_data_dict, overwrite_bool) or (None, False)."""
|
||||
if not self.import_data:
|
||||
return None, False
|
||||
|
||||
data = self.import_data
|
||||
overwrite = self.chk_overwrite.get_active()
|
||||
sessions_map = {s["name"]: s for s in data.get("sessions", [])}
|
||||
contexts_by_proj = {}
|
||||
for ctx in data.get("contexts", []):
|
||||
contexts_by_proj.setdefault(ctx["project"], []).append(ctx)
|
||||
summaries_by_proj = {}
|
||||
for s in data.get("summaries", []):
|
||||
summaries_by_proj.setdefault(s["project"], []).append(s)
|
||||
shared_map = {s["key"]: s for s in data.get("shared", [])}
|
||||
|
||||
result = {"sessions": [], "contexts": [], "shared": [], "summaries": []}
|
||||
|
||||
root = self.store.get_iter_first()
|
||||
while root:
|
||||
dtype = self.store.get_value(root, 3)
|
||||
dkey = self.store.get_value(root, 4)
|
||||
|
||||
if dtype == "project":
|
||||
proj_name = dkey
|
||||
child = self.store.iter_children(root)
|
||||
checked_entries = []
|
||||
include_summaries = False
|
||||
while child:
|
||||
if self.store.get_value(child, 0):
|
||||
ctype = self.store.get_value(child, 3)
|
||||
ckey = self.store.get_value(child, 4)
|
||||
if ctype == "entry":
|
||||
checked_entries.append(ckey)
|
||||
elif ctype == "summaries":
|
||||
include_summaries = True
|
||||
child = self.store.iter_next(child)
|
||||
|
||||
if checked_entries or include_summaries:
|
||||
if proj_name in sessions_map:
|
||||
result["sessions"].append(sessions_map[proj_name])
|
||||
for ekey in checked_entries:
|
||||
for ctx in contexts_by_proj.get(proj_name, []):
|
||||
if ctx["key"] == ekey:
|
||||
result["contexts"].append(ctx)
|
||||
break
|
||||
if include_summaries:
|
||||
result["summaries"].extend(
|
||||
summaries_by_proj.get(proj_name, [])
|
||||
)
|
||||
|
||||
elif dtype == "shared":
|
||||
child = self.store.iter_children(root)
|
||||
while child:
|
||||
if self.store.get_value(child, 0):
|
||||
skey = self.store.get_value(child, 4)
|
||||
if skey in shared_map:
|
||||
result["shared"].append(shared_map[skey])
|
||||
child = self.store.iter_next(child)
|
||||
|
||||
root = self.store.iter_next(root)
|
||||
|
||||
result = {k: v for k, v in result.items() if v}
|
||||
return (result if result else None), overwrite
|
||||
|
||||
|
||||
# ─── CtxManagerPanel ──────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
|
@ -2398,10 +2874,24 @@ class CtxManagerPanel(Gtk.Box):
|
|||
btn_refresh.set_tooltip_text("Refresh")
|
||||
btn_refresh.connect("clicked", lambda _: self.refresh())
|
||||
|
||||
btn_more = Gtk.MenuButton(label="\u22ee")
|
||||
btn_more.get_style_context().add_class("sidebar-btn")
|
||||
btn_more.set_tooltip_text("More actions")
|
||||
more_menu = Gtk.Menu()
|
||||
item_export = Gtk.MenuItem(label="Export\u2026")
|
||||
item_export.connect("activate", lambda _: self._on_export())
|
||||
more_menu.append(item_export)
|
||||
item_import = Gtk.MenuItem(label="Import\u2026")
|
||||
item_import.connect("activate", lambda _: self._on_import())
|
||||
more_menu.append(item_import)
|
||||
more_menu.show_all()
|
||||
btn_more.set_popup(more_menu)
|
||||
|
||||
btn_box.pack_start(btn_add, True, True, 0)
|
||||
btn_box.pack_start(btn_edit, True, True, 0)
|
||||
btn_box.pack_start(btn_del, True, True, 0)
|
||||
btn_box.pack_start(btn_refresh, False, False, 0)
|
||||
btn_box.pack_start(btn_more, False, False, 0)
|
||||
self.pack_start(btn_box, False, False, 0)
|
||||
|
||||
# Signals
|
||||
|
|
@ -2793,6 +3283,112 @@ class CtxManagerPanel(Gtk.Box):
|
|||
self.refresh()
|
||||
dlg.destroy()
|
||||
|
||||
def _on_export(self):
|
||||
dlg = _CtxExportDialog(self.app)
|
||||
if dlg.run() == Gtk.ResponseType.OK:
|
||||
data = dlg.get_export_data()
|
||||
if data:
|
||||
save_dlg = Gtk.FileChooserDialog(
|
||||
title="Save export file",
|
||||
parent=self.app,
|
||||
action=Gtk.FileChooserAction.SAVE,
|
||||
)
|
||||
save_dlg.add_buttons(
|
||||
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
||||
Gtk.STOCK_SAVE, Gtk.ResponseType.OK,
|
||||
)
|
||||
save_dlg.set_do_overwrite_confirmation(True)
|
||||
save_dlg.set_current_name("ctx_export.json")
|
||||
filt = Gtk.FileFilter()
|
||||
filt.set_name("JSON files")
|
||||
filt.add_pattern("*.json")
|
||||
save_dlg.add_filter(filt)
|
||||
if save_dlg.run() == Gtk.ResponseType.OK:
|
||||
path = save_dlg.get_filename()
|
||||
try:
|
||||
with open(path, "w") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
except OSError as e:
|
||||
err = Gtk.MessageDialog(
|
||||
transient_for=self.app,
|
||||
modal=True,
|
||||
message_type=Gtk.MessageType.ERROR,
|
||||
buttons=Gtk.ButtonsType.OK,
|
||||
text=f"Failed to save: {e}",
|
||||
)
|
||||
err.run()
|
||||
err.destroy()
|
||||
save_dlg.destroy()
|
||||
dlg.destroy()
|
||||
|
||||
def _on_import(self):
|
||||
dlg = _CtxImportDialog(self.app)
|
||||
if dlg.run() == Gtk.ResponseType.OK:
|
||||
data, overwrite = dlg.get_selected_data()
|
||||
if data:
|
||||
self._do_import(data, overwrite)
|
||||
self.refresh()
|
||||
dlg.destroy()
|
||||
|
||||
def _do_import(self, data, overwrite):
|
||||
import sqlite3
|
||||
# Ensure database and tables exist
|
||||
subprocess.run(["ctx", "list"], capture_output=True, text=True)
|
||||
if not os.path.exists(CTX_DB):
|
||||
return
|
||||
|
||||
db = sqlite3.connect(CTX_DB)
|
||||
mode = "REPLACE" if overwrite else "IGNORE"
|
||||
|
||||
for session in data.get("sessions", []):
|
||||
db.execute(
|
||||
f"INSERT OR {mode} INTO sessions (name, description, work_dir, created_at) "
|
||||
"VALUES (?, ?, ?, ?)",
|
||||
(
|
||||
session["name"],
|
||||
session.get("description", ""),
|
||||
session.get("work_dir", ""),
|
||||
session.get("created_at", ""),
|
||||
),
|
||||
)
|
||||
|
||||
for ctx in data.get("contexts", []):
|
||||
db.execute(
|
||||
f"INSERT OR {mode} INTO contexts (project, key, value, updated_at) "
|
||||
"VALUES (?, ?, ?, ?)",
|
||||
(
|
||||
ctx["project"],
|
||||
ctx["key"],
|
||||
ctx["value"],
|
||||
ctx.get("updated_at", ""),
|
||||
),
|
||||
)
|
||||
|
||||
for shared in data.get("shared", []):
|
||||
db.execute(
|
||||
f"INSERT OR {mode} INTO shared (key, value, updated_at) "
|
||||
"VALUES (?, ?, ?)",
|
||||
(
|
||||
shared["key"],
|
||||
shared["value"],
|
||||
shared.get("updated_at", ""),
|
||||
),
|
||||
)
|
||||
|
||||
for summary in data.get("summaries", []):
|
||||
db.execute(
|
||||
"INSERT INTO summaries (project, summary, created_at) "
|
||||
"VALUES (?, ?, ?)",
|
||||
(
|
||||
summary["project"],
|
||||
summary["summary"],
|
||||
summary.get("created_at", ""),
|
||||
),
|
||||
)
|
||||
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
|
||||
# ─── BTerminalApp ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue