1use anyhow::Result;
2use gpui::{AppContext, AsyncAppContext, Context, Global, Model, ModelContext, SharedString, Task};
3use rpc::{
4 proto::{self, DevServerStatus},
5 TypedEnvelope,
6};
7use std::{collections::HashMap, sync::Arc};
8
9use client::{Client, ProjectId};
10pub use client::{DevServerId, DevServerProjectId};
11
12pub struct Store {
13 dev_server_projects: HashMap<DevServerProjectId, DevServerProject>,
14 dev_servers: HashMap<DevServerId, DevServer>,
15 _subscriptions: Vec<client::Subscription>,
16 client: Arc<Client>,
17}
18
19#[derive(Debug, Clone)]
20pub struct DevServerProject {
21 pub id: DevServerProjectId,
22 pub project_id: Option<ProjectId>,
23 pub paths: Vec<SharedString>,
24 pub dev_server_id: DevServerId,
25}
26
27impl From<proto::DevServerProject> for DevServerProject {
28 fn from(project: proto::DevServerProject) -> Self {
29 Self {
30 id: DevServerProjectId(project.id),
31 project_id: project.project_id.map(|id| ProjectId(id)),
32 paths: project.paths.into_iter().map(|path| path.into()).collect(),
33 dev_server_id: DevServerId(project.dev_server_id),
34 }
35 }
36}
37
38#[derive(Debug, Clone)]
39pub struct DevServer {
40 pub id: DevServerId,
41 pub name: SharedString,
42 pub ssh_connection_string: Option<SharedString>,
43 pub status: DevServerStatus,
44}
45
46impl From<proto::DevServer> for DevServer {
47 fn from(dev_server: proto::DevServer) -> Self {
48 Self {
49 id: DevServerId(dev_server.dev_server_id),
50 status: dev_server.status(),
51 name: dev_server.name.into(),
52 ssh_connection_string: dev_server.ssh_connection_string.map(|s| s.into()),
53 }
54 }
55}
56
57struct GlobalStore(Model<Store>);
58
59impl Global for GlobalStore {}
60
61pub fn init(client: Arc<Client>, cx: &mut AppContext) {
62 let store = cx.new_model(|cx| Store::new(client, cx));
63 cx.set_global(GlobalStore(store));
64}
65
66impl Store {
67 pub fn global(cx: &AppContext) -> Model<Store> {
68 cx.global::<GlobalStore>().0.clone()
69 }
70
71 pub fn new(client: Arc<Client>, cx: &ModelContext<Self>) -> Self {
72 Self {
73 dev_server_projects: Default::default(),
74 dev_servers: Default::default(),
75 _subscriptions: vec![client
76 .add_message_handler(cx.weak_model(), Self::handle_dev_server_projects_update)],
77 client,
78 }
79 }
80
81 pub fn projects_for_server(&self, id: DevServerId) -> Vec<DevServerProject> {
82 let mut projects: Vec<DevServerProject> = self
83 .dev_server_projects
84 .values()
85 .filter(|project| project.dev_server_id == id)
86 .cloned()
87 .collect();
88 projects.sort_by_key(|p| (p.paths.clone(), p.id));
89 projects
90 }
91
92 pub fn dev_servers(&self) -> Vec<DevServer> {
93 let mut dev_servers: Vec<DevServer> = self.dev_servers.values().cloned().collect();
94 dev_servers.sort_by_key(|d| (d.status == DevServerStatus::Offline, d.name.clone(), d.id));
95 dev_servers
96 }
97
98 pub fn dev_server(&self, id: DevServerId) -> Option<&DevServer> {
99 self.dev_servers.get(&id)
100 }
101
102 pub fn dev_server_status(&self, id: DevServerId) -> DevServerStatus {
103 self.dev_server(id)
104 .map(|server| server.status)
105 .unwrap_or(DevServerStatus::Offline)
106 }
107
108 pub fn dev_server_projects(&self) -> Vec<DevServerProject> {
109 let mut projects: Vec<DevServerProject> =
110 self.dev_server_projects.values().cloned().collect();
111 projects.sort_by_key(|p| (p.paths.clone(), p.id));
112 projects
113 }
114
115 pub fn dev_server_project(&self, id: DevServerProjectId) -> Option<&DevServerProject> {
116 self.dev_server_projects.get(&id)
117 }
118
119 pub fn dev_server_for_project(&self, id: DevServerProjectId) -> Option<&DevServer> {
120 self.dev_server_project(id)
121 .and_then(|project| self.dev_server(project.dev_server_id))
122 }
123
124 async fn handle_dev_server_projects_update(
125 this: Model<Self>,
126 envelope: TypedEnvelope<proto::DevServerProjectsUpdate>,
127 mut cx: AsyncAppContext,
128 ) -> Result<()> {
129 this.update(&mut cx, |this, cx| {
130 this.dev_servers = envelope
131 .payload
132 .dev_servers
133 .into_iter()
134 .map(|dev_server| (DevServerId(dev_server.dev_server_id), dev_server.into()))
135 .collect();
136 this.dev_server_projects = envelope
137 .payload
138 .dev_server_projects
139 .into_iter()
140 .map(|project| (DevServerProjectId(project.id), project.into()))
141 .collect();
142
143 cx.notify();
144 })?;
145 Ok(())
146 }
147
148 pub fn create_dev_server_project(
149 &mut self,
150 dev_server_id: DevServerId,
151 path: String,
152 cx: &mut ModelContext<Self>,
153 ) -> Task<Result<proto::CreateDevServerProjectResponse>> {
154 let client = self.client.clone();
155 cx.background_executor().spawn(async move {
156 client
157 .request(proto::CreateDevServerProject {
158 dev_server_id: dev_server_id.0,
159 path,
160 })
161 .await
162 })
163 }
164
165 pub fn create_dev_server(
166 &mut self,
167 name: String,
168 ssh_connection_string: Option<String>,
169 cx: &mut ModelContext<Self>,
170 ) -> Task<Result<proto::CreateDevServerResponse>> {
171 let client = self.client.clone();
172 cx.background_executor().spawn(async move {
173 let result = client
174 .request(proto::CreateDevServer {
175 name,
176 ssh_connection_string,
177 })
178 .await?;
179 Ok(result)
180 })
181 }
182
183 pub fn rename_dev_server(
184 &mut self,
185 dev_server_id: DevServerId,
186 name: String,
187 ssh_connection_string: Option<String>,
188 cx: &mut ModelContext<Self>,
189 ) -> Task<Result<()>> {
190 let client = self.client.clone();
191 cx.background_executor().spawn(async move {
192 client
193 .request(proto::RenameDevServer {
194 dev_server_id: dev_server_id.0,
195 name,
196 ssh_connection_string,
197 })
198 .await?;
199 Ok(())
200 })
201 }
202
203 pub fn regenerate_dev_server_token(
204 &mut self,
205 dev_server_id: DevServerId,
206 cx: &mut ModelContext<Self>,
207 ) -> Task<Result<proto::RegenerateDevServerTokenResponse>> {
208 let client = self.client.clone();
209 cx.background_executor().spawn(async move {
210 client
211 .request(proto::RegenerateDevServerToken {
212 dev_server_id: dev_server_id.0,
213 })
214 .await
215 })
216 }
217
218 pub fn delete_dev_server(
219 &mut self,
220 id: DevServerId,
221 cx: &mut ModelContext<Self>,
222 ) -> Task<Result<()>> {
223 let client = self.client.clone();
224 cx.background_executor().spawn(async move {
225 client
226 .request(proto::DeleteDevServer {
227 dev_server_id: id.0,
228 })
229 .await?;
230 Ok(())
231 })
232 }
233
234 pub fn delete_dev_server_project(
235 &mut self,
236 id: DevServerProjectId,
237 cx: &mut ModelContext<Self>,
238 ) -> Task<Result<()>> {
239 let client = self.client.clone();
240 cx.background_executor().spawn(async move {
241 client
242 .request(proto::DeleteDevServerProject {
243 dev_server_project_id: id.0,
244 })
245 .await?;
246 Ok(())
247 })
248 }
249}