use super::*;
impl App {
pub(super) fn persist_connections(&self) {
if let Ok(store) = crate::core::storage::ConnectionStore::new() {
let _ = store.save(&self.state.dialogs.saved_connections, "");
}
}
pub(super) fn persist_groups(&self) {
if let Ok(store) = crate::core::storage::ConnectionStore::new() {
let groups: Vec<String> = self
.state
.sidebar
.tree
.iter()
.filter_map(|n| {
if let TreeNode::Group { name, .. } = n {
return Some(name.clone());
}
None
})
.collect();
let _ = store.save_groups(&groups);
}
}
pub(super) fn save_current_connection(&mut self) {
let config = self.state.dialogs.connection_form.to_connection_config();
if config.name.is_empty() {
self.state.dialogs.connection_form.error_message =
"Name is required to save".to_string();
return;
}
self.save_connection_config(&config);
self.state.status_message = format!("Connection '{}' saved", config.name);
}
pub fn load_saved_connections(&mut self) {
if let Ok(store) = crate::core::storage::ConnectionStore::new()
&& let Ok(configs) = store.load("")
{
self.state.dialogs.saved_connections = configs.clone();
let mut seen = std::collections::HashSet::new();
let mut groups_order: Vec<String> = Vec::new();
if let Ok(persisted_groups) = store.load_groups() {
for g in persisted_groups {
if seen.insert(g.clone()) {
groups_order.push(g);
}
}
}
for config in &configs {
if seen.insert(config.group.clone()) {
groups_order.push(config.group.clone());
}
}
if groups_order.is_empty() && !configs.is_empty() {
groups_order.push("Default".to_string());
}
for group in &groups_order {
let group_conns: Vec<_> = configs.iter().filter(|c| &c.group == group).collect();
if group_conns.is_empty() && !seen.contains(group) {
continue;
}
self.state.sidebar.tree.push(TreeNode::Group {
name: group.clone(),
expanded: false,
});
for config in group_conns {
self.state.sidebar.tree.push(TreeNode::Connection {
name: config.name.clone(),
expanded: false,
status: crate::ui::state::ConnStatus::Disconnected,
});
}
}
if !configs.is_empty() {
self.state.status_message =
format!("{} connection(s) loaded - expand to connect", configs.len());
}
}
self.load_object_filter();
self.refresh_scripts_list();
}
pub(super) fn load_object_filter(&mut self) {
if let Ok(dir) = crate::core::storage::ConnectionStore::new()
&& let Ok(data) = std::fs::read_to_string(dir.dir_path().join("object_filters.json"))
&& let Ok(filters) = serde_json::from_str::<HashMap<String, Vec<String>>>(&data)
{
for (key, names) in filters {
let set: HashSet<String> = names.into_iter().collect();
if !set.is_empty() {
self.state.sidebar.object_filter.filters.insert(key, set);
}
}
}
}
pub fn save_object_filter(&mut self) {
if let Ok(dir) = crate::core::storage::ConnectionStore::new() {
let filter_path = dir.dir_path().join("object_filters.json");
let serializable: HashMap<&String, Vec<&String>> = self
.state
.sidebar
.object_filter
.filters
.iter()
.filter(|(_, set)| !set.is_empty())
.map(|(k, set)| (k, set.iter().collect()))
.collect();
match std::fs::write(
&filter_path,
serde_json::to_string_pretty(&serializable).unwrap_or_default(),
) {
Ok(()) => {
let total: usize = self
.state
.sidebar
.object_filter
.filters
.values()
.map(|s| s.len())
.sum();
if total > 0 {
self.state.status_message = format!("Filters saved ({total} rules)");
}
}
Err(e) => {
self.state.status_message = format!("Error saving filter: {e}");
}
}
}
}
pub(super) fn handle_export(&mut self) {
let dialog = match self.state.dialogs.export_dialog.take() {
Some(d) => d,
None => return,
};
let options = crate::core::storage::ExportOptions {
include_credentials: dialog.include_credentials,
password: dialog.password,
};
let path = std::path::Path::new(&dialog.path);
match crate::core::storage::export_bundle(path, &options) {
Ok(manifest) => {
let cred = if manifest.includes_credentials {
"with credentials"
} else {
"without credentials"
};
self.state.status_message = format!(
"Exported {} connections, {} scripts ({cred}) → {}",
manifest.connection_count, manifest.script_count, dialog.path
);
}
Err(e) => {
self.state.status_message = format!("Export failed: {e}");
}
}
}
pub(super) fn handle_import(&mut self) {
let dialog = match self.state.dialogs.import_dialog.take() {
Some(d) => d,
None => return,
};
let path = std::path::Path::new(&dialog.path);
let result = match crate::core::storage::import_bundle(path, &dialog.password) {
Ok(r) => r,
Err(e) => {
self.state.status_message = format!("Import failed: {e}");
return;
}
};
let mut conn_added = 0usize;
let mut script_added = 0usize;
let existing_names: std::collections::HashSet<String> = self
.state
.dialogs
.saved_connections
.iter()
.map(|c| c.name.clone())
.collect();
for conn in result.connections {
if !existing_names.contains(&conn.name) {
self.save_connection_config(&conn);
let insert_idx = self.find_or_create_group_insert_idx(&conn.group);
self.state.sidebar.tree.insert(
insert_idx,
TreeNode::Connection {
name: conn.name.clone(),
expanded: false,
status: crate::ui::state::ConnStatus::Disconnected,
},
);
conn_added += 1;
}
}
let existing_groups: std::collections::HashSet<String> = self
.state
.sidebar
.tree
.iter()
.filter_map(|n| {
if let TreeNode::Group { name, .. } = n {
Some(name.clone())
} else {
None
}
})
.collect();
for group in &result.groups {
if !existing_groups.contains(group) {
self.state.sidebar.tree.push(TreeNode::Group {
name: group.clone(),
expanded: false,
});
}
}
if let Ok(script_store) = crate::core::storage::ScriptStore::new() {
for (path, content) in &result.scripts {
let full_path = script_store.scripts_dir().join(path);
if !full_path.exists() {
if let Some(parent) = full_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let _ = std::fs::write(&full_path, content);
script_added += 1;
}
}
}
for (key, values) in &result.object_filters {
if !self.state.sidebar.object_filter.filters.contains_key(key) {
self.state
.sidebar
.object_filter
.filters
.insert(key.clone(), values.iter().cloned().collect());
}
}
for (script, conn) in &result.script_connections {
if load_script_connection(script).is_none() {
save_script_connection(script, conn);
}
}
let existing_vars = load_bind_variable_values();
let new_vars: Vec<(String, String)> = result
.bind_variables
.iter()
.filter(|(k, _)| !existing_vars.contains_key(*k))
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
if !new_vars.is_empty() {
save_bind_variable_values(&new_vars);
}
self.persist_connections();
self.persist_groups();
self.save_object_filter();
self.refresh_scripts_list();
let cred = if result.manifest.includes_credentials {
"with credentials"
} else {
"without credentials"
};
self.state.status_message =
format!("Imported {conn_added} connections, {script_added} scripts ({cred})");
}
pub(super) fn refresh_scripts_list(&mut self) {
use crate::ui::state::ScriptNode;
if let Ok(store) = crate::core::storage::ScriptStore::new()
&& let Ok(tree) = store.list_tree()
{
let mut nodes = Vec::new();
for coll in &tree.collections {
let was_expanded = self.state.scripts.tree.iter().any(|n| {
matches!(n, ScriptNode::Collection { name, expanded: true }
if *name == coll.name)
});
nodes.push(ScriptNode::Collection {
name: coll.name.clone(),
expanded: was_expanded,
});
for script in &coll.scripts {
let base = script.strip_suffix(".sql").unwrap_or(script).to_string();
nodes.push(ScriptNode::Script {
name: base,
collection: Some(coll.name.clone()),
file_path: format!("{}/{script}", coll.name),
});
}
}
for script in &tree.root_scripts {
let base = script.strip_suffix(".sql").unwrap_or(script).to_string();
nodes.push(ScriptNode::Script {
name: base,
collection: None,
file_path: script.clone(),
});
}
self.state.scripts.tree = nodes;
let visible_count = self.state.scripts.visible_scripts().len();
if self.state.scripts.cursor >= visible_count && visible_count > 0 {
self.state.scripts.cursor = visible_count - 1;
}
}
}
pub(super) fn save_active_script(&mut self) {
if let Some(tab) = self.state.active_tab()
&& let TabKind::Script {
ref file_path,
ref name,
..
} = tab.kind
&& file_path.is_none()
{
self.state.scripts.save_name = Some(name.clone());
self.state.overlay = Some(Overlay::SaveScriptName);
return;
}
self.do_save_script(None);
}
pub(super) fn do_save_script(&mut self, new_name: Option<&str>) {
if let Some(tab) = self.state.active_tab_mut()
&& let TabKind::Script {
ref mut name,
ref mut file_path,
..
} = tab.kind
{
let save_path = if let Some(new) = new_name {
new.to_string()
} else if let Some(fp) = file_path.as_ref() {
fp.strip_suffix(".sql").unwrap_or(fp).to_string()
} else {
name.clone()
};
let content = tab.editor.as_ref().map(|e| e.content()).unwrap_or_default();
if let Ok(store) = crate::core::storage::ScriptStore::new() {
match store.save(&save_path, &content) {
Ok(()) => {
if let Some(new) = new_name {
*name = new.to_string();
}
*file_path = Some(format!("{save_path}.sql"));
if let Some(editor) = tab.editor.as_mut() {
editor.modified = false;
}
self.state.status_message = format!("Script '{}' saved", name);
}
Err(e) => {
self.state.status_message = format!("Error saving script: {e}");
}
}
}
}
if let Some(tab) = self.state.active_tab_mut() {
tab.mark_saved();
}
self.refresh_scripts_list();
}
}
pub(super) fn load_script_connection(script_name: &str) -> Option<String> {
let dir = crate::core::storage::ConnectionStore::new().ok()?;
let path = dir.dir_path().join("script_connections.json");
let data = std::fs::read_to_string(&path).ok()?;
let map: std::collections::HashMap<String, String> = serde_json::from_str(&data).ok()?;
map.get(script_name).cloned()
}
pub(super) fn save_script_connection(script_name: &str, conn_name: &str) {
let dir = match crate::core::storage::ConnectionStore::new() {
Ok(d) => d,
Err(_) => return,
};
let path = dir.dir_path().join("script_connections.json");
let mut map: std::collections::HashMap<String, String> = std::fs::read_to_string(&path)
.ok()
.and_then(|data| serde_json::from_str(&data).ok())
.unwrap_or_default();
map.insert(script_name.to_string(), conn_name.to_string());
if let Ok(json) = serde_json::to_string_pretty(&map) {
let _ = std::fs::write(&path, json);
}
}
pub fn load_bind_variable_values() -> std::collections::HashMap<String, String> {
let dir = match crate::core::storage::ConnectionStore::new() {
Ok(d) => d,
Err(_) => return std::collections::HashMap::new(),
};
let path = dir.dir_path().join("bind_variables.json");
std::fs::read_to_string(&path)
.ok()
.and_then(|data| serde_json::from_str(&data).ok())
.unwrap_or_default()
}
pub fn save_bind_variable_values(vars: &[(String, String)]) {
let dir = match crate::core::storage::ConnectionStore::new() {
Ok(d) => d,
Err(_) => return,
};
let path = dir.dir_path().join("bind_variables.json");
let mut map: std::collections::HashMap<String, String> = std::fs::read_to_string(&path)
.ok()
.and_then(|data| serde_json::from_str(&data).ok())
.unwrap_or_default();
for (name, value) in vars {
if !value.is_empty() {
map.insert(name.clone(), value.clone());
}
}
if let Ok(json) = serde_json::to_string_pretty(&map) {
let _ = std::fs::write(&path, json);
}
}