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 path: 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 path: project.path.into(),
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.path.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.path.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 _: Arc<Client>,
128 mut cx: AsyncAppContext,
129 ) -> Result<()> {
130 this.update(&mut cx, |this, cx| {
131 this.dev_servers = envelope
132 .payload
133 .dev_servers
134 .into_iter()
135 .map(|dev_server| (DevServerId(dev_server.dev_server_id), dev_server.into()))
136 .collect();
137 this.dev_server_projects = envelope
138 .payload
139 .dev_server_projects
140 .into_iter()
141 .map(|project| (DevServerProjectId(project.id), project.into()))
142 .collect();
143
144 cx.notify();
145 })?;
146 Ok(())
147 }
148
149 pub fn create_dev_server_project(
150 &mut self,
151 dev_server_id: DevServerId,
152 path: String,
153 cx: &mut ModelContext<Self>,
154 ) -> Task<Result<proto::CreateDevServerProjectResponse>> {
155 let client = self.client.clone();
156 cx.background_executor().spawn(async move {
157 client
158 .request(proto::CreateDevServerProject {
159 dev_server_id: dev_server_id.0,
160 path,
161 })
162 .await
163 })
164 }
165
166 pub fn create_dev_server(
167 &mut self,
168 name: String,
169 ssh_connection_string: Option<String>,
170 cx: &mut ModelContext<Self>,
171 ) -> Task<Result<proto::CreateDevServerResponse>> {
172 let client = self.client.clone();
173 cx.background_executor().spawn(async move {
174 let result = client
175 .request(proto::CreateDevServer {
176 name,
177 ssh_connection_string,
178 })
179 .await?;
180 Ok(result)
181 })
182 }
183
184 pub fn rename_dev_server(
185 &mut self,
186 dev_server_id: DevServerId,
187 name: String,
188 ssh_connection_string: Option<String>,
189 cx: &mut ModelContext<Self>,
190 ) -> Task<Result<()>> {
191 let client = self.client.clone();
192 cx.background_executor().spawn(async move {
193 client
194 .request(proto::RenameDevServer {
195 dev_server_id: dev_server_id.0,
196 name,
197 ssh_connection_string,
198 })
199 .await?;
200 Ok(())
201 })
202 }
203
204 pub fn regenerate_dev_server_token(
205 &mut self,
206 dev_server_id: DevServerId,
207 cx: &mut ModelContext<Self>,
208 ) -> Task<Result<proto::RegenerateDevServerTokenResponse>> {
209 let client = self.client.clone();
210 cx.background_executor().spawn(async move {
211 client
212 .request(proto::RegenerateDevServerToken {
213 dev_server_id: dev_server_id.0,
214 })
215 .await
216 })
217 }
218
219 pub fn delete_dev_server(
220 &mut self,
221 id: DevServerId,
222 cx: &mut ModelContext<Self>,
223 ) -> Task<Result<()>> {
224 let client = self.client.clone();
225 cx.background_executor().spawn(async move {
226 client
227 .request(proto::DeleteDevServer {
228 dev_server_id: id.0,
229 })
230 .await?;
231 Ok(())
232 })
233 }
234
235 pub fn delete_dev_server_project(
236 &mut self,
237 id: DevServerProjectId,
238 cx: &mut ModelContext<Self>,
239 ) -> Task<Result<()>> {
240 let client = self.client.clone();
241 cx.background_executor().spawn(async move {
242 client
243 .request(proto::DeleteDevServerProject {
244 dev_server_project_id: id.0,
245 })
246 .await?;
247 Ok(())
248 })
249 }
250}