remoting: Allow Add/Remove remote folder (#14532)

Release Notes:

- remoting (alpha only): Allow add/remove folders to projects

---------

Co-authored-by: Max <max@zed.dev>
This commit is contained in:
Conrad Irwin 2024-07-16 12:01:59 -06:00 committed by GitHub
parent be1387fee6
commit 62ab6e1a11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 612 additions and 239 deletions

View file

@ -5,7 +5,7 @@ use rpc::{
};
use sea_orm::{
ActiveModelTrait, ActiveValue, ColumnTrait, Condition, DatabaseTransaction, EntityTrait,
ModelTrait, QueryFilter,
IntoActiveModel, ModelTrait, QueryFilter,
};
use crate::db::ProjectId;
@ -56,12 +56,7 @@ impl Database {
.await?;
Ok(servers
.into_iter()
.map(|(dev_server_project, project)| proto::DevServerProject {
id: dev_server_project.id.to_proto(),
project_id: project.map(|p| p.id.to_proto()),
dev_server_id: dev_server_project.dev_server_id.to_proto(),
path: dev_server_project.path,
})
.map(|(dev_server_project, project)| dev_server_project.to_proto(project))
.collect())
}
@ -134,7 +129,7 @@ impl Database {
let project = dev_server_project::Entity::insert(dev_server_project::ActiveModel {
id: ActiveValue::NotSet,
dev_server_id: ActiveValue::Set(dev_server_id),
path: ActiveValue::Set(path.to_string()),
paths: ActiveValue::Set(dev_server_project::JSONPaths(vec![path.to_string()])),
})
.exec_with_returning(&*tx)
.await?;
@ -148,6 +143,38 @@ impl Database {
.await
}
pub async fn update_dev_server_project(
&self,
id: DevServerProjectId,
paths: &Vec<String>,
user_id: UserId,
) -> crate::Result<(dev_server_project::Model, proto::DevServerProjectsUpdate)> {
self.transaction(move |tx| async move {
let paths = paths.clone();
let Some((project, Some(dev_server))) = dev_server_project::Entity::find_by_id(id)
.find_also_related(dev_server::Entity)
.one(&*tx)
.await?
else {
return Err(anyhow!("no such dev server project"))?;
};
if dev_server.user_id != user_id {
return Err(anyhow!("not your dev server"))?;
}
let mut project = project.into_active_model();
project.paths = ActiveValue::Set(dev_server_project::JSONPaths(paths));
let project = project.update(&*tx).await?;
let status = self
.dev_server_projects_update_internal(user_id, &tx)
.await?;
Ok((project, status))
})
.await
}
pub async fn delete_dev_server_project(
&self,
dev_server_project_id: DevServerProjectId,
@ -258,7 +285,6 @@ impl Database {
dev_server_id: DevServerId,
connection: ConnectionId,
) -> crate::Result<Vec<ResharedProject>> {
// todo!() project_transaction? (maybe we can make the lock per-dev-server instead of per-project?)
self.transaction(|tx| async move {
let mut ret = Vec::new();
for reshared_project in reshared_projects {
@ -322,7 +348,6 @@ impl Database {
user_id: UserId,
connection_id: ConnectionId,
) -> crate::Result<Vec<RejoinedProject>> {
// todo!() project_transaction? (maybe we can make the lock per-dev-server instead of per-project?)
self.transaction(|tx| async move {
let mut ret = Vec::new();
for rejoined_project in rejoined_projects {

View file

@ -19,6 +19,28 @@ impl Database {
.await
}
pub async fn get_dev_server_for_user(
&self,
dev_server_id: DevServerId,
user_id: UserId,
) -> crate::Result<dev_server::Model> {
self.transaction(|tx| async move {
let server = dev_server::Entity::find_by_id(dev_server_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow::anyhow!("no dev server with id {}", dev_server_id))?;
if server.user_id != user_id {
return Err(anyhow::anyhow!(
"dev server {} is not owned by user {}",
dev_server_id,
user_id
))?;
}
Ok(server)
})
.await
}
pub async fn get_dev_servers(&self, user_id: UserId) -> crate::Result<Vec<dev_server::Model>> {
self.transaction(|tx| async move {
Ok(dev_server::Entity::find()

View file

@ -1,7 +1,8 @@
use super::project;
use crate::db::{DevServerId, DevServerProjectId};
use rpc::proto;
use sea_orm::entity::prelude::*;
use sea_orm::{entity::prelude::*, FromJsonQueryResult};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "dev_server_projects")]
@ -9,9 +10,12 @@ pub struct Model {
#[sea_orm(primary_key)]
pub id: DevServerProjectId,
pub dev_server_id: DevServerId,
pub path: String,
pub paths: JSONPaths,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct JSONPaths(pub Vec<String>);
impl ActiveModelBehavior for ActiveModel {}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
@ -44,7 +48,12 @@ impl Model {
id: self.id.to_proto(),
project_id: project.map(|p| p.id.to_proto()),
dev_server_id: self.dev_server_id.to_proto(),
path: self.path.clone(),
path: self.paths().get(0).cloned().unwrap_or_default(),
paths: self.paths().clone(),
}
}
pub fn paths(&self) -> &Vec<String> {
&self.paths.0
}
}

View file

@ -431,11 +431,13 @@ impl Server {
.add_request_handler(user_handler(join_hosted_project))
.add_request_handler(user_handler(rejoin_dev_server_projects))
.add_request_handler(user_handler(create_dev_server_project))
.add_request_handler(user_handler(update_dev_server_project))
.add_request_handler(user_handler(delete_dev_server_project))
.add_request_handler(user_handler(create_dev_server))
.add_request_handler(user_handler(regenerate_dev_server_token))
.add_request_handler(user_handler(rename_dev_server))
.add_request_handler(user_handler(delete_dev_server))
.add_request_handler(user_handler(list_remote_directory))
.add_request_handler(dev_server_handler(share_dev_server_project))
.add_request_handler(dev_server_handler(shutdown_dev_server))
.add_request_handler(dev_server_handler(reconnect_dev_server))
@ -2313,6 +2315,69 @@ async fn join_hosted_project(
join_project_internal(response, session, &mut project, &replica_id)
}
async fn list_remote_directory(
request: proto::ListRemoteDirectory,
response: Response<proto::ListRemoteDirectory>,
session: UserSession,
) -> Result<()> {
let dev_server_id = DevServerId(request.dev_server_id as i32);
let dev_server_connection_id = session
.connection_pool()
.await
.dev_server_connection_id_supporting(dev_server_id, ZedVersion::with_list_directory())?;
session
.db()
.await
.get_dev_server_for_user(dev_server_id, session.user_id())
.await?;
response.send(
session
.peer
.forward_request(session.connection_id, dev_server_connection_id, request)
.await?,
)?;
Ok(())
}
async fn update_dev_server_project(
request: proto::UpdateDevServerProject,
response: Response<proto::UpdateDevServerProject>,
session: UserSession,
) -> Result<()> {
let dev_server_project_id = DevServerProjectId(request.dev_server_project_id as i32);
let (dev_server_project, update) = session
.db()
.await
.update_dev_server_project(dev_server_project_id, &request.paths, session.user_id())
.await?;
let projects = session
.db()
.await
.get_projects_for_dev_server(dev_server_project.dev_server_id)
.await?;
let dev_server_connection_id = session
.connection_pool()
.await
.dev_server_connection_id_supporting(
dev_server_project.dev_server_id,
ZedVersion::with_list_directory(),
)?;
session.peer.send(
dev_server_connection_id,
proto::DevServerInstructions { projects },
)?;
send_dev_server_projects_update(session.user_id(), update, &session).await;
response.send(proto::Ack {})
}
async fn create_dev_server_project(
request: proto::CreateDevServerProject,
response: Response<proto::CreateDevServerProject>,

View file

@ -38,6 +38,10 @@ impl ZedVersion {
pub fn with_save_as() -> ZedVersion {
ZedVersion(SemanticVersion::new(0, 134, 0))
}
pub fn with_list_directory() -> ZedVersion {
ZedVersion(SemanticVersion::new(0, 145, 0))
}
}
pub trait VersionedMessage {
@ -187,6 +191,18 @@ impl ConnectionPool {
self.connected_dev_servers.get(&dev_server_id).copied()
}
pub fn dev_server_connection_id_supporting(
&self,
dev_server_id: DevServerId,
required: ZedVersion,
) -> Result<ConnectionId> {
match self.connected_dev_servers.get(&dev_server_id) {
Some(cid) if self.connections[cid].zed_version >= required => Ok(*cid),
Some(_) => Err(anyhow!(proto::ErrorCode::RemoteUpgradeRequired)),
None => Err(anyhow!(proto::ErrorCode::DevServerOffline)),
}
}
pub fn channel_user_ids(
&self,
channel_id: ChannelId,

View file

@ -66,7 +66,7 @@ async fn test_dev_server(cx: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppC
.update(cx, |store, cx| {
let projects = store.dev_server_projects();
assert_eq!(projects.len(), 1);
assert_eq!(projects[0].path, "/remote");
assert_eq!(projects[0].paths, vec!["/remote"]);
workspace::join_dev_server_project(
projects[0].id,
projects[0].project_id.unwrap(),
@ -206,7 +206,7 @@ async fn create_dev_server_project(
.update(cx, |store, cx| {
let projects = store.dev_server_projects();
assert_eq!(projects.len(), 1);
assert_eq!(projects[0].path, "/remote");
assert_eq!(projects[0].paths, vec!["/remote"]);
workspace::join_dev_server_project(
projects[0].id,
projects[0].project_id.unwrap(),

View file

@ -1528,7 +1528,7 @@ async fn test_project_reconnect(
});
let (worktree_a2, _) = project_a1
.update(cx_a, |p, cx| {
p.find_or_create_local_worktree("/root-1/dir2", true, cx)
p.find_or_create_worktree("/root-1/dir2", true, cx)
})
.await
.unwrap();
@ -1601,7 +1601,7 @@ async fn test_project_reconnect(
});
let (worktree_a3, _) = project_a1
.update(cx_a, |p, cx| {
p.find_or_create_local_worktree("/root-1/dir3", true, cx)
p.find_or_create_worktree("/root-1/dir3", true, cx)
})
.await
.unwrap();
@ -1725,7 +1725,7 @@ async fn test_project_reconnect(
// While client B is disconnected, add and remove worktrees from client A's project.
let (worktree_a4, _) = project_a1
.update(cx_a, |p, cx| {
p.find_or_create_local_worktree("/root-1/dir4", true, cx)
p.find_or_create_worktree("/root-1/dir4", true, cx)
})
.await
.unwrap();
@ -4887,7 +4887,7 @@ async fn test_project_search(
let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
let (worktree_2, _) = project_a
.update(cx_a, |p, cx| {
p.find_or_create_local_worktree("/root/dir-2", true, cx)
p.find_or_create_worktree("/root/dir-2", true, cx)
})
.await
.unwrap();

View file

@ -581,7 +581,7 @@ impl RandomizedTest for ProjectCollaborationTest {
}
project
.update(cx, |project, cx| {
project.find_or_create_local_worktree(&new_root_path, true, cx)
project.find_or_create_worktree(&new_root_path, true, cx)
})
.await
.unwrap();

View file

@ -805,9 +805,7 @@ impl TestClient {
) -> (Model<Project>, WorktreeId) {
let project = self.build_empty_local_project(cx);
let (worktree, _) = project
.update(cx, |p, cx| {
p.find_or_create_local_worktree(root_path, true, cx)
})
.update(cx, |p, cx| p.find_or_create_worktree(root_path, true, cx))
.await
.unwrap();
worktree