Restructure persistence of remote workspaces to make room for WSL and other non-ssh remote projects (#36714)
This is another pure refactor, to prepare for adding direct WSL support. ### Todo * [x] Represent `paths` in the same way for all workspaces, instead of having a completely separate SSH representation * [x] Adjust sqlite tables * [x] `ssh_projects` -> `ssh_connections` (drop paths) * [x] `workspaces.local_paths` -> `paths` * [x] remove duplicate path columns on `workspaces` * [x] Add migrations for backward-compatibility Release Notes: - N/A --------- Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
parent
639417c2bc
commit
f649c31bf9
12 changed files with 784 additions and 1080 deletions
|
@ -6,6 +6,7 @@ mod modal_layer;
|
|||
pub mod notifications;
|
||||
pub mod pane;
|
||||
pub mod pane_group;
|
||||
mod path_list;
|
||||
mod persistence;
|
||||
pub mod searchable;
|
||||
pub mod shared_screen;
|
||||
|
@ -18,6 +19,7 @@ mod workspace_settings;
|
|||
|
||||
pub use crate::notifications::NotificationFrame;
|
||||
pub use dock::Panel;
|
||||
pub use path_list::PathList;
|
||||
pub use toast_layer::{ToastAction, ToastLayer, ToastView};
|
||||
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
|
@ -62,20 +64,20 @@ use notifications::{
|
|||
};
|
||||
pub use pane::*;
|
||||
pub use pane_group::*;
|
||||
use persistence::{
|
||||
DB, SerializedWindowBounds,
|
||||
model::{SerializedSshProject, SerializedWorkspace},
|
||||
};
|
||||
use persistence::{DB, SerializedWindowBounds, model::SerializedWorkspace};
|
||||
pub use persistence::{
|
||||
DB as WORKSPACE_DB, WorkspaceDb, delete_unloaded_items,
|
||||
model::{ItemId, LocalPaths, SerializedWorkspaceLocation},
|
||||
model::{ItemId, SerializedSshConnection, SerializedWorkspaceLocation},
|
||||
};
|
||||
use postage::stream::Stream;
|
||||
use project::{
|
||||
DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId,
|
||||
debugger::{breakpoint_store::BreakpointStoreEvent, session::ThreadStatus},
|
||||
};
|
||||
use remote::{SshClientDelegate, SshConnectionOptions, ssh_session::ConnectionIdentifier};
|
||||
use remote::{
|
||||
SshClientDelegate, SshConnectionOptions,
|
||||
ssh_session::{ConnectionIdentifier, SshProjectId},
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use session::AppSession;
|
||||
|
@ -1042,7 +1044,7 @@ pub enum OpenVisible {
|
|||
|
||||
enum WorkspaceLocation {
|
||||
// Valid local paths or SSH project to serialize
|
||||
Location(SerializedWorkspaceLocation),
|
||||
Location(SerializedWorkspaceLocation, PathList),
|
||||
// No valid location found hence clear session id
|
||||
DetachFromSession,
|
||||
// No valid location found to serialize
|
||||
|
@ -1126,7 +1128,7 @@ pub struct Workspace {
|
|||
terminal_provider: Option<Box<dyn TerminalProvider>>,
|
||||
debugger_provider: Option<Arc<dyn DebuggerProvider>>,
|
||||
serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
|
||||
serialized_ssh_project: Option<SerializedSshProject>,
|
||||
serialized_ssh_connection_id: Option<SshProjectId>,
|
||||
_items_serializer: Task<Result<()>>,
|
||||
session_id: Option<String>,
|
||||
scheduled_tasks: Vec<Task<()>>,
|
||||
|
@ -1175,8 +1177,6 @@ impl Workspace {
|
|||
|
||||
project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded(_) => {
|
||||
this.update_window_title(window, cx);
|
||||
this.update_ssh_paths(cx);
|
||||
this.serialize_ssh_paths(window, cx);
|
||||
this.serialize_workspace(window, cx);
|
||||
// This event could be triggered by `AddFolderToProject` or `RemoveFromProject`.
|
||||
this.update_history(cx);
|
||||
|
@ -1461,7 +1461,7 @@ impl Workspace {
|
|||
serializable_items_tx,
|
||||
_items_serializer,
|
||||
session_id: Some(session_id),
|
||||
serialized_ssh_project: None,
|
||||
serialized_ssh_connection_id: None,
|
||||
scheduled_tasks: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
@ -1501,20 +1501,9 @@ impl Workspace {
|
|||
let serialized_workspace =
|
||||
persistence::DB.workspace_for_roots(paths_to_open.as_slice());
|
||||
|
||||
let workspace_location = serialized_workspace
|
||||
.as_ref()
|
||||
.map(|ws| &ws.location)
|
||||
.and_then(|loc| match loc {
|
||||
SerializedWorkspaceLocation::Local(_, order) => {
|
||||
Some((loc.sorted_paths(), order.order()))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if let Some((paths, order)) = workspace_location {
|
||||
paths_to_open = paths.iter().cloned().collect();
|
||||
|
||||
if order.iter().enumerate().any(|(i, &j)| i != j) {
|
||||
if let Some(paths) = serialized_workspace.as_ref().map(|ws| &ws.paths) {
|
||||
paths_to_open = paths.paths().to_vec();
|
||||
if !paths.is_lexicographically_ordered() {
|
||||
project_handle
|
||||
.update(cx, |project, cx| {
|
||||
project.set_worktrees_reordered(true, cx);
|
||||
|
@ -2034,14 +2023,6 @@ impl Workspace {
|
|||
self.debugger_provider.clone()
|
||||
}
|
||||
|
||||
pub fn serialized_ssh_project(&self) -> Option<SerializedSshProject> {
|
||||
self.serialized_ssh_project.clone()
|
||||
}
|
||||
|
||||
pub fn set_serialized_ssh_project(&mut self, serialized_ssh_project: SerializedSshProject) {
|
||||
self.serialized_ssh_project = Some(serialized_ssh_project);
|
||||
}
|
||||
|
||||
pub fn prompt_for_open_path(
|
||||
&mut self,
|
||||
path_prompt_options: PathPromptOptions,
|
||||
|
@ -5088,59 +5069,12 @@ impl Workspace {
|
|||
self.session_id.clone()
|
||||
}
|
||||
|
||||
fn local_paths(&self, cx: &App) -> Option<Vec<Arc<Path>>> {
|
||||
pub fn root_paths(&self, cx: &App) -> Vec<Arc<Path>> {
|
||||
let project = self.project().read(cx);
|
||||
|
||||
if project.is_local() {
|
||||
Some(
|
||||
project
|
||||
.visible_worktrees(cx)
|
||||
.map(|worktree| worktree.read(cx).abs_path())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn update_ssh_paths(&mut self, cx: &App) {
|
||||
let project = self.project().read(cx);
|
||||
if !project.is_local() {
|
||||
let paths: Vec<String> = project
|
||||
.visible_worktrees(cx)
|
||||
.map(|worktree| worktree.read(cx).abs_path().to_string_lossy().to_string())
|
||||
.collect();
|
||||
if let Some(ssh_project) = &mut self.serialized_ssh_project {
|
||||
ssh_project.paths = paths;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_ssh_paths(&mut self, window: &mut Window, cx: &mut Context<Workspace>) {
|
||||
if self._schedule_serialize_ssh_paths.is_none() {
|
||||
self._schedule_serialize_ssh_paths =
|
||||
Some(cx.spawn_in(window, async move |this, cx| {
|
||||
cx.background_executor()
|
||||
.timer(SERIALIZATION_THROTTLE_TIME)
|
||||
.await;
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
let task = if let Some(ssh_project) = &this.serialized_ssh_project {
|
||||
let ssh_project_id = ssh_project.id;
|
||||
let ssh_project_paths = ssh_project.paths.clone();
|
||||
window.spawn(cx, async move |_| {
|
||||
persistence::DB
|
||||
.update_ssh_project_paths(ssh_project_id, ssh_project_paths)
|
||||
.await
|
||||
})
|
||||
} else {
|
||||
Task::ready(Err(anyhow::anyhow!("No SSH project to serialize")))
|
||||
};
|
||||
task.detach();
|
||||
this._schedule_serialize_ssh_paths.take();
|
||||
})
|
||||
.log_err();
|
||||
}));
|
||||
}
|
||||
project
|
||||
.visible_worktrees(cx)
|
||||
.map(|worktree| worktree.read(cx).abs_path())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn remove_panes(&mut self, member: Member, window: &mut Window, cx: &mut Context<Workspace>) {
|
||||
|
@ -5313,7 +5247,7 @@ impl Workspace {
|
|||
}
|
||||
|
||||
match self.serialize_workspace_location(cx) {
|
||||
WorkspaceLocation::Location(location) => {
|
||||
WorkspaceLocation::Location(location, paths) => {
|
||||
let breakpoints = self.project.update(cx, |project, cx| {
|
||||
project
|
||||
.breakpoint_store()
|
||||
|
@ -5327,6 +5261,7 @@ impl Workspace {
|
|||
let serialized_workspace = SerializedWorkspace {
|
||||
id: database_id,
|
||||
location,
|
||||
paths,
|
||||
center_group,
|
||||
window_bounds,
|
||||
display: Default::default(),
|
||||
|
@ -5352,13 +5287,21 @@ impl Workspace {
|
|||
}
|
||||
|
||||
fn serialize_workspace_location(&self, cx: &App) -> WorkspaceLocation {
|
||||
if let Some(ssh_project) = &self.serialized_ssh_project {
|
||||
WorkspaceLocation::Location(SerializedWorkspaceLocation::Ssh(ssh_project.clone()))
|
||||
} else if let Some(local_paths) = self.local_paths(cx) {
|
||||
if !local_paths.is_empty() {
|
||||
WorkspaceLocation::Location(SerializedWorkspaceLocation::from_local_paths(
|
||||
local_paths,
|
||||
))
|
||||
let paths = PathList::new(&self.root_paths(cx));
|
||||
let connection = self.project.read(cx).ssh_connection_options(cx);
|
||||
if let Some((id, connection)) = self.serialized_ssh_connection_id.zip(connection) {
|
||||
WorkspaceLocation::Location(
|
||||
SerializedWorkspaceLocation::Ssh(SerializedSshConnection {
|
||||
id,
|
||||
host: connection.host,
|
||||
port: connection.port,
|
||||
user: connection.username,
|
||||
}),
|
||||
paths,
|
||||
)
|
||||
} else if self.project.read(cx).is_local() {
|
||||
if !paths.is_empty() {
|
||||
WorkspaceLocation::Location(SerializedWorkspaceLocation::Local, paths)
|
||||
} else {
|
||||
WorkspaceLocation::DetachFromSession
|
||||
}
|
||||
|
@ -5371,13 +5314,13 @@ impl Workspace {
|
|||
let Some(id) = self.database_id() else {
|
||||
return;
|
||||
};
|
||||
let location = match self.serialize_workspace_location(cx) {
|
||||
WorkspaceLocation::Location(location) => location,
|
||||
_ => return,
|
||||
};
|
||||
if !self.project.read(cx).is_local() {
|
||||
return;
|
||||
}
|
||||
if let Some(manager) = HistoryManager::global(cx) {
|
||||
let paths = PathList::new(&self.root_paths(cx));
|
||||
manager.update(cx, |this, cx| {
|
||||
this.update_history(id, HistoryManagerEntry::new(id, &location), cx);
|
||||
this.update_history(id, HistoryManagerEntry::new(id, &paths), cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -6843,14 +6786,14 @@ impl WorkspaceHandle for Entity<Workspace> {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> {
|
||||
pub async fn last_opened_workspace_location() -> Option<(SerializedWorkspaceLocation, PathList)> {
|
||||
DB.last_workspace().await.log_err().flatten()
|
||||
}
|
||||
|
||||
pub fn last_session_workspace_locations(
|
||||
last_session_id: &str,
|
||||
last_session_window_stack: Option<Vec<WindowId>>,
|
||||
) -> Option<Vec<SerializedWorkspaceLocation>> {
|
||||
) -> Option<Vec<(SerializedWorkspaceLocation, PathList)>> {
|
||||
DB.last_session_workspace_locations(last_session_id, last_session_window_stack)
|
||||
.log_err()
|
||||
}
|
||||
|
@ -7353,7 +7296,7 @@ pub fn open_ssh_project_with_new_connection(
|
|||
cx: &mut App,
|
||||
) -> Task<Result<()>> {
|
||||
cx.spawn(async move |cx| {
|
||||
let (serialized_ssh_project, workspace_id, serialized_workspace) =
|
||||
let (workspace_id, serialized_workspace) =
|
||||
serialize_ssh_project(connection_options.clone(), paths.clone(), cx).await?;
|
||||
|
||||
let session = match cx
|
||||
|
@ -7387,7 +7330,6 @@ pub fn open_ssh_project_with_new_connection(
|
|||
open_ssh_project_inner(
|
||||
project,
|
||||
paths,
|
||||
serialized_ssh_project,
|
||||
workspace_id,
|
||||
serialized_workspace,
|
||||
app_state,
|
||||
|
@ -7407,13 +7349,12 @@ pub fn open_ssh_project_with_existing_connection(
|
|||
cx: &mut AsyncApp,
|
||||
) -> Task<Result<()>> {
|
||||
cx.spawn(async move |cx| {
|
||||
let (serialized_ssh_project, workspace_id, serialized_workspace) =
|
||||
let (workspace_id, serialized_workspace) =
|
||||
serialize_ssh_project(connection_options.clone(), paths.clone(), cx).await?;
|
||||
|
||||
open_ssh_project_inner(
|
||||
project,
|
||||
paths,
|
||||
serialized_ssh_project,
|
||||
workspace_id,
|
||||
serialized_workspace,
|
||||
app_state,
|
||||
|
@ -7427,7 +7368,6 @@ pub fn open_ssh_project_with_existing_connection(
|
|||
async fn open_ssh_project_inner(
|
||||
project: Entity<Project>,
|
||||
paths: Vec<PathBuf>,
|
||||
serialized_ssh_project: SerializedSshProject,
|
||||
workspace_id: WorkspaceId,
|
||||
serialized_workspace: Option<SerializedWorkspace>,
|
||||
app_state: Arc<AppState>,
|
||||
|
@ -7480,7 +7420,6 @@ async fn open_ssh_project_inner(
|
|||
|
||||
let mut workspace =
|
||||
Workspace::new(Some(workspace_id), project, app_state.clone(), window, cx);
|
||||
workspace.set_serialized_ssh_project(serialized_ssh_project);
|
||||
workspace.update_history(cx);
|
||||
|
||||
if let Some(ref serialized) = serialized_workspace {
|
||||
|
@ -7517,28 +7456,18 @@ fn serialize_ssh_project(
|
|||
connection_options: SshConnectionOptions,
|
||||
paths: Vec<PathBuf>,
|
||||
cx: &AsyncApp,
|
||||
) -> Task<
|
||||
Result<(
|
||||
SerializedSshProject,
|
||||
WorkspaceId,
|
||||
Option<SerializedWorkspace>,
|
||||
)>,
|
||||
> {
|
||||
) -> Task<Result<(WorkspaceId, Option<SerializedWorkspace>)>> {
|
||||
cx.background_spawn(async move {
|
||||
let serialized_ssh_project = persistence::DB
|
||||
.get_or_create_ssh_project(
|
||||
let ssh_connection_id = persistence::DB
|
||||
.get_or_create_ssh_connection(
|
||||
connection_options.host.clone(),
|
||||
connection_options.port,
|
||||
paths
|
||||
.iter()
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
connection_options.username.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let serialized_workspace =
|
||||
persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
|
||||
persistence::DB.ssh_workspace_for_roots(&paths, ssh_connection_id);
|
||||
|
||||
let workspace_id = if let Some(workspace_id) =
|
||||
serialized_workspace.as_ref().map(|workspace| workspace.id)
|
||||
|
@ -7548,7 +7477,7 @@ fn serialize_ssh_project(
|
|||
persistence::DB.next_id().await?
|
||||
};
|
||||
|
||||
Ok((serialized_ssh_project, workspace_id, serialized_workspace))
|
||||
Ok((workspace_id, serialized_workspace))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -8095,18 +8024,15 @@ pub fn ssh_workspace_position_from_db(
|
|||
paths_to_open: &[PathBuf],
|
||||
cx: &App,
|
||||
) -> Task<Result<WorkspacePosition>> {
|
||||
let paths = paths_to_open
|
||||
.iter()
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let paths = paths_to_open.to_vec();
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let serialized_ssh_project = persistence::DB
|
||||
.get_or_create_ssh_project(host, port, paths, user)
|
||||
let ssh_connection_id = persistence::DB
|
||||
.get_or_create_ssh_connection(host, port, user)
|
||||
.await
|
||||
.context("fetching serialized ssh project")?;
|
||||
let serialized_workspace =
|
||||
persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
|
||||
persistence::DB.ssh_workspace_for_roots(&paths, ssh_connection_id);
|
||||
|
||||
let (window_bounds, display) = if let Some(bounds) = window_bounds_env_override() {
|
||||
(Some(WindowBounds::Windowed(bounds)), None)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue