Clean up handling of serialized ssh connection ids (#36781)
Small follow-up to #36714 Release Notes: - N/A
This commit is contained in:
parent
bc566fe18e
commit
153724aad3
4 changed files with 93 additions and 97 deletions
|
@ -52,11 +52,6 @@ use util::{
|
||||||
paths::{PathStyle, RemotePathBuf},
|
paths::{PathStyle, RemotePathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(
|
|
||||||
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
|
|
||||||
)]
|
|
||||||
pub struct SshProjectId(pub u64);
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SshSocket {
|
pub struct SshSocket {
|
||||||
connection_options: SshConnectionOptions,
|
connection_options: SshConnectionOptions,
|
||||||
|
|
|
@ -9,13 +9,13 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, bail};
|
use anyhow::{Context as _, Result, bail};
|
||||||
|
use collections::HashMap;
|
||||||
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
|
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
|
||||||
use gpui::{Axis, Bounds, Task, WindowBounds, WindowId, point, size};
|
use gpui::{Axis, Bounds, Task, WindowBounds, WindowId, point, size};
|
||||||
use project::debugger::breakpoint_store::{BreakpointState, SourceBreakpoint};
|
use project::debugger::breakpoint_store::{BreakpointState, SourceBreakpoint};
|
||||||
|
|
||||||
use language::{LanguageName, Toolchain};
|
use language::{LanguageName, Toolchain};
|
||||||
use project::WorktreeId;
|
use project::WorktreeId;
|
||||||
use remote::ssh_session::SshProjectId;
|
|
||||||
use sqlez::{
|
use sqlez::{
|
||||||
bindable::{Bind, Column, StaticColumnCount},
|
bindable::{Bind, Column, StaticColumnCount},
|
||||||
statement::{SqlType, Statement},
|
statement::{SqlType, Statement},
|
||||||
|
@ -33,7 +33,7 @@ use crate::{
|
||||||
|
|
||||||
use model::{
|
use model::{
|
||||||
GroupId, ItemId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup,
|
GroupId, ItemId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup,
|
||||||
SerializedSshConnection, SerializedWorkspace,
|
SerializedSshConnection, SerializedWorkspace, SshConnectionId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::model::{DockStructure, SerializedWorkspaceLocation};
|
use self::model::{DockStructure, SerializedWorkspaceLocation};
|
||||||
|
@ -615,7 +615,7 @@ impl WorkspaceDb {
|
||||||
pub(crate) fn ssh_workspace_for_roots<P: AsRef<Path>>(
|
pub(crate) fn ssh_workspace_for_roots<P: AsRef<Path>>(
|
||||||
&self,
|
&self,
|
||||||
worktree_roots: &[P],
|
worktree_roots: &[P],
|
||||||
ssh_project_id: SshProjectId,
|
ssh_project_id: SshConnectionId,
|
||||||
) -> Option<SerializedWorkspace> {
|
) -> Option<SerializedWorkspace> {
|
||||||
self.workspace_for_roots_internal(worktree_roots, Some(ssh_project_id))
|
self.workspace_for_roots_internal(worktree_roots, Some(ssh_project_id))
|
||||||
}
|
}
|
||||||
|
@ -623,7 +623,7 @@ impl WorkspaceDb {
|
||||||
pub(crate) fn workspace_for_roots_internal<P: AsRef<Path>>(
|
pub(crate) fn workspace_for_roots_internal<P: AsRef<Path>>(
|
||||||
&self,
|
&self,
|
||||||
worktree_roots: &[P],
|
worktree_roots: &[P],
|
||||||
ssh_connection_id: Option<SshProjectId>,
|
ssh_connection_id: Option<SshConnectionId>,
|
||||||
) -> Option<SerializedWorkspace> {
|
) -> Option<SerializedWorkspace> {
|
||||||
// paths are sorted before db interactions to ensure that the order of the paths
|
// paths are sorted before db interactions to ensure that the order of the paths
|
||||||
// doesn't affect the workspace selection for existing workspaces
|
// doesn't affect the workspace selection for existing workspaces
|
||||||
|
@ -762,15 +762,21 @@ impl WorkspaceDb {
|
||||||
/// that used this workspace previously
|
/// that used this workspace previously
|
||||||
pub(crate) async fn save_workspace(&self, workspace: SerializedWorkspace) {
|
pub(crate) async fn save_workspace(&self, workspace: SerializedWorkspace) {
|
||||||
let paths = workspace.paths.serialize();
|
let paths = workspace.paths.serialize();
|
||||||
let ssh_connection_id = match &workspace.location {
|
|
||||||
SerializedWorkspaceLocation::Local => None,
|
|
||||||
SerializedWorkspaceLocation::Ssh(serialized_ssh_connection) => {
|
|
||||||
Some(serialized_ssh_connection.id.0)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
log::debug!("Saving workspace at location: {:?}", workspace.location);
|
log::debug!("Saving workspace at location: {:?}", workspace.location);
|
||||||
self.write(move |conn| {
|
self.write(move |conn| {
|
||||||
conn.with_savepoint("update_worktrees", || {
|
conn.with_savepoint("update_worktrees", || {
|
||||||
|
let ssh_connection_id = match &workspace.location {
|
||||||
|
SerializedWorkspaceLocation::Local => None,
|
||||||
|
SerializedWorkspaceLocation::Ssh(connection) => {
|
||||||
|
Some(Self::get_or_create_ssh_connection_query(
|
||||||
|
conn,
|
||||||
|
connection.host.clone(),
|
||||||
|
connection.port,
|
||||||
|
connection.user.clone(),
|
||||||
|
)?.0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Clear out panes and pane_groups
|
// Clear out panes and pane_groups
|
||||||
conn.exec_bound(sql!(
|
conn.exec_bound(sql!(
|
||||||
DELETE FROM pane_groups WHERE workspace_id = ?1;
|
DELETE FROM pane_groups WHERE workspace_id = ?1;
|
||||||
|
@ -893,39 +899,34 @@ impl WorkspaceDb {
|
||||||
host: String,
|
host: String,
|
||||||
port: Option<u16>,
|
port: Option<u16>,
|
||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
) -> Result<SshProjectId> {
|
) -> Result<SshConnectionId> {
|
||||||
if let Some(id) = self
|
self.write(move |conn| Self::get_or_create_ssh_connection_query(conn, host, port, user))
|
||||||
.get_ssh_connection(host.clone(), port, user.clone())
|
.await
|
||||||
.await?
|
}
|
||||||
|
|
||||||
|
fn get_or_create_ssh_connection_query(
|
||||||
|
this: &Connection,
|
||||||
|
host: String,
|
||||||
|
port: Option<u16>,
|
||||||
|
user: Option<String>,
|
||||||
|
) -> Result<SshConnectionId> {
|
||||||
|
if let Some(id) = this.select_row_bound(sql!(
|
||||||
|
SELECT id FROM ssh_connections WHERE host IS ? AND port IS ? AND user IS ? LIMIT 1
|
||||||
|
))?((host.clone(), port, user.clone()))?
|
||||||
{
|
{
|
||||||
Ok(SshProjectId(id))
|
Ok(SshConnectionId(id))
|
||||||
} else {
|
} else {
|
||||||
log::debug!("Inserting SSH project at host {host}");
|
log::debug!("Inserting SSH project at host {host}");
|
||||||
let id = self
|
let id = this.select_row_bound(sql!(
|
||||||
.insert_ssh_connection(host, port, user)
|
INSERT INTO ssh_connections (
|
||||||
.await?
|
host,
|
||||||
.context("failed to insert ssh project")?;
|
port,
|
||||||
Ok(SshProjectId(id))
|
user
|
||||||
}
|
) VALUES (?1, ?2, ?3)
|
||||||
}
|
RETURNING id
|
||||||
|
))?((host, port, user))?
|
||||||
query! {
|
.context("failed to insert ssh project")?;
|
||||||
async fn get_ssh_connection(host: String, port: Option<u16>, user: Option<String>) -> Result<Option<u64>> {
|
Ok(SshConnectionId(id))
|
||||||
SELECT id
|
|
||||||
FROM ssh_connections
|
|
||||||
WHERE host IS ? AND port IS ? AND user IS ?
|
|
||||||
LIMIT 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query! {
|
|
||||||
async fn insert_ssh_connection(host: String, port: Option<u16>, user: Option<String>) -> Result<Option<u64>> {
|
|
||||||
INSERT INTO ssh_connections (
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
user
|
|
||||||
) VALUES (?1, ?2, ?3)
|
|
||||||
RETURNING id
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -963,7 +964,7 @@ impl WorkspaceDb {
|
||||||
fn session_workspaces(
|
fn session_workspaces(
|
||||||
&self,
|
&self,
|
||||||
session_id: String,
|
session_id: String,
|
||||||
) -> Result<Vec<(PathList, Option<u64>, Option<SshProjectId>)>> {
|
) -> Result<Vec<(PathList, Option<u64>, Option<SshConnectionId>)>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.session_workspaces_query(session_id)?
|
.session_workspaces_query(session_id)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -971,7 +972,7 @@ impl WorkspaceDb {
|
||||||
(
|
(
|
||||||
PathList::deserialize(&SerializedPathList { paths, order }),
|
PathList::deserialize(&SerializedPathList { paths, order }),
|
||||||
window_id,
|
window_id,
|
||||||
ssh_connection_id.map(SshProjectId),
|
ssh_connection_id.map(SshConnectionId),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
|
@ -1001,15 +1002,15 @@ impl WorkspaceDb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ssh_connections(&self) -> Result<Vec<SerializedSshConnection>> {
|
fn ssh_connections(&self) -> Result<HashMap<SshConnectionId, SerializedSshConnection>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.ssh_connections_query()?
|
.ssh_connections_query()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(id, host, port, user)| SerializedSshConnection {
|
.map(|(id, host, port, user)| {
|
||||||
id: SshProjectId(id),
|
(
|
||||||
host,
|
SshConnectionId(id),
|
||||||
port,
|
SerializedSshConnection { host, port, user },
|
||||||
user,
|
)
|
||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
@ -1021,19 +1022,18 @@ impl WorkspaceDb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ssh_connection(&self, id: SshProjectId) -> Result<SerializedSshConnection> {
|
pub(crate) fn ssh_connection(&self, id: SshConnectionId) -> Result<SerializedSshConnection> {
|
||||||
let row = self.ssh_connection_query(id.0)?;
|
let row = self.ssh_connection_query(id.0)?;
|
||||||
Ok(SerializedSshConnection {
|
Ok(SerializedSshConnection {
|
||||||
id: SshProjectId(row.0),
|
host: row.0,
|
||||||
host: row.1,
|
port: row.1,
|
||||||
port: row.2,
|
user: row.2,
|
||||||
user: row.3,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
query! {
|
query! {
|
||||||
fn ssh_connection_query(id: u64) -> Result<(u64, String, Option<u16>, Option<String>)> {
|
fn ssh_connection_query(id: u64) -> Result<(String, Option<u16>, Option<String>)> {
|
||||||
SELECT id, host, port, user
|
SELECT host, port, user
|
||||||
FROM ssh_connections
|
FROM ssh_connections
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
}
|
}
|
||||||
|
@ -1075,10 +1075,8 @@ impl WorkspaceDb {
|
||||||
let ssh_connections = self.ssh_connections()?;
|
let ssh_connections = self.ssh_connections()?;
|
||||||
|
|
||||||
for (id, paths, ssh_connection_id) in self.recent_workspaces()? {
|
for (id, paths, ssh_connection_id) in self.recent_workspaces()? {
|
||||||
if let Some(ssh_connection_id) = ssh_connection_id.map(SshProjectId) {
|
if let Some(ssh_connection_id) = ssh_connection_id.map(SshConnectionId) {
|
||||||
if let Some(ssh_connection) =
|
if let Some(ssh_connection) = ssh_connections.get(&ssh_connection_id) {
|
||||||
ssh_connections.iter().find(|rp| rp.id == ssh_connection_id)
|
|
||||||
{
|
|
||||||
result.push((
|
result.push((
|
||||||
id,
|
id,
|
||||||
SerializedWorkspaceLocation::Ssh(ssh_connection.clone()),
|
SerializedWorkspaceLocation::Ssh(ssh_connection.clone()),
|
||||||
|
@ -2340,12 +2338,10 @@ mod tests {
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(host, user)| async {
|
.map(|(host, user)| async {
|
||||||
let id = db
|
db.get_or_create_ssh_connection(host.to_string(), None, Some(user.to_string()))
|
||||||
.get_or_create_ssh_connection(host.to_string(), None, Some(user.to_string()))
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
SerializedSshConnection {
|
SerializedSshConnection {
|
||||||
id,
|
|
||||||
host: host.into(),
|
host: host.into(),
|
||||||
port: None,
|
port: None,
|
||||||
user: Some(user.into()),
|
user: Some(user.into()),
|
||||||
|
@ -2501,26 +2497,34 @@ mod tests {
|
||||||
let stored_projects = db.ssh_connections().unwrap();
|
let stored_projects = db.ssh_connections().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stored_projects,
|
stored_projects,
|
||||||
&[
|
[
|
||||||
SerializedSshConnection {
|
(
|
||||||
id: ids[0],
|
ids[0],
|
||||||
host: "example.com".into(),
|
SerializedSshConnection {
|
||||||
port: None,
|
host: "example.com".into(),
|
||||||
user: None,
|
port: None,
|
||||||
},
|
user: None,
|
||||||
SerializedSshConnection {
|
}
|
||||||
id: ids[1],
|
),
|
||||||
host: "anotherexample.com".into(),
|
(
|
||||||
port: Some(123),
|
ids[1],
|
||||||
user: Some("user2".into()),
|
SerializedSshConnection {
|
||||||
},
|
host: "anotherexample.com".into(),
|
||||||
SerializedSshConnection {
|
port: Some(123),
|
||||||
id: ids[2],
|
user: Some("user2".into()),
|
||||||
host: "yetanother.com".into(),
|
}
|
||||||
port: Some(345),
|
),
|
||||||
user: None,
|
(
|
||||||
},
|
ids[2],
|
||||||
|
SerializedSshConnection {
|
||||||
|
host: "yetanother.com".into(),
|
||||||
|
port: Some(345),
|
||||||
|
user: None,
|
||||||
|
}
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<HashMap<_, _>>(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ use db::sqlez::{
|
||||||
use gpui::{AsyncWindowContext, Entity, WeakEntity};
|
use gpui::{AsyncWindowContext, Entity, WeakEntity};
|
||||||
|
|
||||||
use project::{Project, debugger::breakpoint_store::SourceBreakpoint};
|
use project::{Project, debugger::breakpoint_store::SourceBreakpoint};
|
||||||
use remote::ssh_session::SshProjectId;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
|
@ -22,9 +21,13 @@ use std::{
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
|
||||||
|
)]
|
||||||
|
pub(crate) struct SshConnectionId(pub u64);
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct SerializedSshConnection {
|
pub struct SerializedSshConnection {
|
||||||
pub id: SshProjectId,
|
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub port: Option<u16>,
|
pub port: Option<u16>,
|
||||||
pub user: Option<String>,
|
pub user: Option<String>,
|
||||||
|
|
|
@ -74,10 +74,7 @@ use project::{
|
||||||
DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId,
|
DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId,
|
||||||
debugger::{breakpoint_store::BreakpointStoreEvent, session::ThreadStatus},
|
debugger::{breakpoint_store::BreakpointStoreEvent, session::ThreadStatus},
|
||||||
};
|
};
|
||||||
use remote::{
|
use remote::{SshClientDelegate, SshConnectionOptions, ssh_session::ConnectionIdentifier};
|
||||||
SshClientDelegate, SshConnectionOptions,
|
|
||||||
ssh_session::{ConnectionIdentifier, SshProjectId},
|
|
||||||
};
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use session::AppSession;
|
use session::AppSession;
|
||||||
|
@ -1128,7 +1125,6 @@ pub struct Workspace {
|
||||||
terminal_provider: Option<Box<dyn TerminalProvider>>,
|
terminal_provider: Option<Box<dyn TerminalProvider>>,
|
||||||
debugger_provider: Option<Arc<dyn DebuggerProvider>>,
|
debugger_provider: Option<Arc<dyn DebuggerProvider>>,
|
||||||
serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
|
serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
|
||||||
serialized_ssh_connection_id: Option<SshProjectId>,
|
|
||||||
_items_serializer: Task<Result<()>>,
|
_items_serializer: Task<Result<()>>,
|
||||||
session_id: Option<String>,
|
session_id: Option<String>,
|
||||||
scheduled_tasks: Vec<Task<()>>,
|
scheduled_tasks: Vec<Task<()>>,
|
||||||
|
@ -1461,7 +1457,7 @@ impl Workspace {
|
||||||
serializable_items_tx,
|
serializable_items_tx,
|
||||||
_items_serializer,
|
_items_serializer,
|
||||||
session_id: Some(session_id),
|
session_id: Some(session_id),
|
||||||
serialized_ssh_connection_id: None,
|
|
||||||
scheduled_tasks: Vec::new(),
|
scheduled_tasks: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5288,11 +5284,9 @@ impl Workspace {
|
||||||
|
|
||||||
fn serialize_workspace_location(&self, cx: &App) -> WorkspaceLocation {
|
fn serialize_workspace_location(&self, cx: &App) -> WorkspaceLocation {
|
||||||
let paths = PathList::new(&self.root_paths(cx));
|
let paths = PathList::new(&self.root_paths(cx));
|
||||||
let connection = self.project.read(cx).ssh_connection_options(cx);
|
if let Some(connection) = self.project.read(cx).ssh_connection_options(cx) {
|
||||||
if let Some((id, connection)) = self.serialized_ssh_connection_id.zip(connection) {
|
|
||||||
WorkspaceLocation::Location(
|
WorkspaceLocation::Location(
|
||||||
SerializedWorkspaceLocation::Ssh(SerializedSshConnection {
|
SerializedWorkspaceLocation::Ssh(SerializedSshConnection {
|
||||||
id,
|
|
||||||
host: connection.host,
|
host: connection.host,
|
||||||
port: connection.port,
|
port: connection.port,
|
||||||
user: connection.username,
|
user: connection.username,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue