Skip to main content

kcl_lib/project/
mod.rs

1#![allow(dead_code)]
2
3use std::sync::Arc;
4
5use indexmap::IndexMap;
6use tokio::sync::RwLock;
7
8use crate::front::Error;
9use crate::front::FileId;
10use crate::front::LifecycleApi;
11use crate::front::ProjectId;
12use crate::front::Result;
13use crate::front::SceneGraph;
14use crate::front::Version;
15
16#[derive(Debug, Clone)]
17pub struct Project {
18    id: ProjectId,
19    files: IndexMap<FileId, File>,
20    open_file: FileId,
21    cur_version: Version,
22}
23
24impl Project {
25    fn scene_graph(&self) -> SceneGraph {
26        SceneGraph::empty(self.id, self.open_file, self.cur_version)
27    }
28}
29
30#[derive(Debug, Clone)]
31struct File {
32    version: Version,
33    path: String,
34    text: String,
35}
36
37lazy_static::lazy_static! {
38        static ref PROJECT: Arc<RwLock<Option<Project>>> = Default::default();
39}
40
41#[derive(Debug, Clone)]
42pub struct ProjectManager;
43
44impl ProjectManager {
45    async fn with_project<T>(f: impl FnOnce(&Option<Project>) -> T) -> T {
46        f(&*PROJECT.read().await)
47    }
48
49    async fn with_project_mut<T>(f: impl FnOnce(&mut Option<Project>) -> T) -> T {
50        f(&mut *PROJECT.write().await)
51    }
52}
53
54impl LifecycleApi for ProjectManager {
55    async fn open_project(&self, id: ProjectId, files: Vec<crate::front::File>, open_file: FileId) -> Result<()> {
56        Self::with_project_mut(move |project| {
57            *project = Some(Project {
58                id,
59                files: files
60                    .into_iter()
61                    .map(|f| {
62                        (
63                            f.id,
64                            File {
65                                version: Version(0),
66                                path: f.path,
67                                text: f.text,
68                            },
69                        )
70                    })
71                    .collect(),
72                open_file,
73                cur_version: Version(0),
74            });
75            Ok(())
76        })
77        .await
78    }
79
80    async fn get_project(&self, id: ProjectId) -> Result<Vec<crate::front::File>> {
81        Self::with_project(move |project| {
82            let Some(project) = project else {
83                return Err(Error::bad_project(id, None));
84            };
85            if project.id != id {
86                return Err(Error::bad_project(id, Some(project.id)));
87            }
88            Ok(project
89                .files
90                .iter()
91                .map(|(file_id, file)| to_front_file(*file_id, file))
92                .collect())
93        })
94        .await
95    }
96
97    async fn add_file(&self, project_id: ProjectId, file: crate::front::File) -> Result<()> {
98        Self::with_project_mut(move |project| {
99            let Some(project) = project else {
100                return Err(Error::bad_project(project_id, None));
101            };
102            if project.id != project_id {
103                return Err(Error::bad_project(project_id, Some(project.id)));
104            }
105            if project.files.contains_key(&file.id) {
106                return Err(Error::file_id_in_use(file.id, &project.files[&file.id].path));
107            }
108            project.files.insert(
109                file.id,
110                File {
111                    version: Version(0),
112                    path: file.path,
113                    text: file.text,
114                },
115            );
116            Ok(())
117        })
118        .await
119    }
120
121    async fn get_file(&self, project_id: ProjectId, file_id: FileId) -> Result<crate::front::File> {
122        Self::with_project(move |project| {
123            let Some(project) = project else {
124                return Err(Error::bad_project(project_id, None));
125            };
126            if project.id != project_id {
127                return Err(Error::bad_project(project_id, Some(project.id)));
128            }
129            project
130                .files
131                .get(&file_id)
132                .map(|file| to_front_file(file_id, file))
133                .ok_or_else(|| Error::file_id_not_found(project_id, file_id))
134        })
135        .await
136    }
137
138    async fn remove_file(&self, project_id: ProjectId, file_id: FileId) -> Result<()> {
139        Self::with_project_mut(move |project| {
140            let Some(project) = project else {
141                return Err(Error::bad_project(project_id, None));
142            };
143            if project.id != project_id {
144                return Err(Error::bad_project(project_id, Some(project.id)));
145            }
146            let old = project.files.swap_remove(&file_id);
147            if old.is_none() {
148                return Err(Error::bad_file(file_id, None));
149            }
150            Ok(())
151        })
152        .await
153    }
154
155    async fn update_file(&self, project_id: ProjectId, file_id: FileId, text: String) -> Result<()> {
156        Self::with_project_mut(move |project| {
157            let Some(project) = project else {
158                return Err(Error::bad_project(project_id, None));
159            };
160            if project.id != project_id {
161                return Err(Error::bad_project(project_id, Some(project.id)));
162            }
163            let Some(file) = project.files.get_mut(&file_id) else {
164                return Err(Error::bad_file(file_id, None));
165            };
166            file.text = text;
167            Ok(())
168        })
169        .await
170    }
171
172    async fn switch_file(&self, project_id: ProjectId, file_id: FileId) -> Result<()> {
173        Self::with_project_mut(move |project| {
174            let Some(project) = project else {
175                return Err(Error::bad_project(project_id, None));
176            };
177            if project.id != project_id {
178                return Err(Error::bad_project(project_id, Some(project.id)));
179            }
180            let Some(file) = project.files.get(&file_id) else {
181                return Err(Error::bad_file(file_id, None));
182            };
183            project.open_file = file_id;
184            project.cur_version = file.version;
185            Ok(())
186        })
187        .await
188    }
189
190    async fn refresh(&self, project_id: ProjectId) -> Result<()> {
191        Self::with_project_mut(move |project| {
192            let Some(project) = project else {
193                return Err(Error::bad_project(project_id, None));
194            };
195            if project.id != project_id {
196                return Err(Error::bad_project(project_id, Some(project.id)));
197            }
198            Ok(())
199        })
200        .await
201    }
202}
203
204fn to_front_file(file_id: FileId, file: &File) -> crate::front::File {
205    crate::front::File {
206        id: file_id,
207        path: file.path.clone(),
208        text: file.text.clone(),
209    }
210}
211
212// Dummy struct to ensure we export the types from the API crate :-(
213#[derive(ts_rs::TS, serde::Serialize)]
214#[ts(export, export_to = "FrontendApi.ts")]
215pub struct IgnoreMe {
216    pub a: crate::front::File,
217}