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, RemoteProjectId};
11
12pub struct Store {
13 remote_projects: HashMap<RemoteProjectId, RemoteProject>,
14 dev_servers: HashMap<DevServerId, DevServer>,
15 _subscriptions: Vec<client::Subscription>,
16 client: Arc<Client>,
17}
18
19#[derive(Debug, Clone)]
20pub struct RemoteProject {
21 pub id: RemoteProjectId,
22 pub project_id: Option<ProjectId>,
23 pub path: SharedString,
24 pub dev_server_id: DevServerId,
25}
26
27impl From<proto::RemoteProject> for RemoteProject {
28 fn from(project: proto::RemoteProject) -> Self {
29 Self {
30 id: RemoteProjectId(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 status: DevServerStatus,
43}
44
45impl From<proto::DevServer> for DevServer {
46 fn from(dev_server: proto::DevServer) -> Self {
47 Self {
48 id: DevServerId(dev_server.dev_server_id),
49 status: dev_server.status(),
50 name: dev_server.name.into(),
51 }
52 }
53}
54
55struct GlobalStore(Model<Store>);
56
57impl Global for GlobalStore {}
58
59pub fn init(client: Arc<Client>, cx: &mut AppContext) {
60 let store = cx.new_model(|cx| Store::new(client, cx));
61 cx.set_global(GlobalStore(store));
62}
63
64impl Store {
65 pub fn global(cx: &AppContext) -> Model<Store> {
66 cx.global::<GlobalStore>().0.clone()
67 }
68
69 pub fn new(client: Arc<Client>, cx: &ModelContext<Self>) -> Self {
70 Self {
71 remote_projects: Default::default(),
72 dev_servers: Default::default(),
73 _subscriptions: vec![
74 client.add_message_handler(cx.weak_model(), Self::handle_remote_projects_update)
75 ],
76 client,
77 }
78 }
79
80 pub fn remote_projects_for_server(&self, id: DevServerId) -> Vec<RemoteProject> {
81 let mut projects: Vec<RemoteProject> = self
82 .remote_projects
83 .values()
84 .filter(|project| project.dev_server_id == id)
85 .cloned()
86 .collect();
87 projects.sort_by_key(|p| (p.path.clone(), p.id));
88 projects
89 }
90
91 pub fn dev_servers(&self) -> Vec<DevServer> {
92 let mut dev_servers: Vec<DevServer> = self.dev_servers.values().cloned().collect();
93 dev_servers.sort_by_key(|d| (d.status == DevServerStatus::Offline, d.name.clone(), d.id));
94 dev_servers
95 }
96
97 pub fn dev_server(&self, id: DevServerId) -> Option<&DevServer> {
98 self.dev_servers.get(&id)
99 }
100
101 pub fn dev_server_status(&self, id: DevServerId) -> DevServerStatus {
102 self.dev_server(id)
103 .map(|server| server.status)
104 .unwrap_or(DevServerStatus::Offline)
105 }
106
107 pub fn remote_projects(&self) -> Vec<RemoteProject> {
108 let mut projects: Vec<RemoteProject> = self.remote_projects.values().cloned().collect();
109 projects.sort_by_key(|p| (p.path.clone(), p.id));
110 projects
111 }
112
113 pub fn remote_project(&self, id: RemoteProjectId) -> Option<&RemoteProject> {
114 self.remote_projects.get(&id)
115 }
116
117 pub fn dev_server_for_project(&self, id: RemoteProjectId) -> Option<&DevServer> {
118 self.remote_project(id)
119 .and_then(|project| self.dev_server(project.dev_server_id))
120 }
121
122 async fn handle_remote_projects_update(
123 this: Model<Self>,
124 envelope: TypedEnvelope<proto::RemoteProjectsUpdate>,
125 _: Arc<Client>,
126 mut cx: AsyncAppContext,
127 ) -> Result<()> {
128 this.update(&mut cx, |this, cx| {
129 this.dev_servers = envelope
130 .payload
131 .dev_servers
132 .into_iter()
133 .map(|dev_server| (DevServerId(dev_server.dev_server_id), dev_server.into()))
134 .collect();
135 this.remote_projects = envelope
136 .payload
137 .remote_projects
138 .into_iter()
139 .map(|project| (RemoteProjectId(project.id), project.into()))
140 .collect();
141
142 cx.notify();
143 })?;
144 Ok(())
145 }
146
147 pub fn create_remote_project(
148 &mut self,
149 dev_server_id: DevServerId,
150 path: String,
151 cx: &mut ModelContext<Self>,
152 ) -> Task<Result<proto::CreateRemoteProjectResponse>> {
153 let client = self.client.clone();
154 cx.background_executor().spawn(async move {
155 client
156 .request(proto::CreateRemoteProject {
157 dev_server_id: dev_server_id.0,
158 path,
159 })
160 .await
161 })
162 }
163
164 pub fn create_dev_server(
165 &mut self,
166 name: String,
167 cx: &mut ModelContext<Self>,
168 ) -> Task<Result<proto::CreateDevServerResponse>> {
169 let client = self.client.clone();
170 cx.background_executor().spawn(async move {
171 let result = client.request(proto::CreateDevServer { name }).await?;
172 Ok(result)
173 })
174 }
175
176 pub fn delete_dev_server(
177 &mut self,
178 id: DevServerId,
179 cx: &mut ModelContext<Self>,
180 ) -> Task<Result<()>> {
181 let client = self.client.clone();
182 cx.background_executor().spawn(async move {
183 client
184 .request(proto::DeleteDevServer {
185 dev_server_id: id.0,
186 })
187 .await?;
188 Ok(())
189 })
190 }
191}