unity_hub/unity/hub/
editors.rs

1use std::cmp::Ordering;
2use super::*;
3use crate::unity;
4use crate::unity::hub::paths;
5use crate::unity::installation::{UnityInstallation, Installation, FromInstallation};
6use serde_json;
7use std::collections::HashMap;
8use std::fs;
9use std::fs::File;
10use std::io::Write;
11use std::iter::{IntoIterator, Iterator};
12use std::path::PathBuf;
13use log::{debug, trace};
14use serde::{Deserialize, Serialize};
15use unity_version::Version;
16
17#[derive(Debug)]
18pub struct Editors {
19    map: HashMap<Version, EditorInstallation>,
20}
21
22impl Editors {
23    pub fn load() -> Result<Editors> {
24        let path = paths::editors_config_path()
25            .ok_or_else(|| (UnityHubError::ConfigDirectoryNotFound))?;
26
27        let map: HashMap<Version, EditorInstallation> = if path.exists() {
28            debug!("load hub editors from file: {}", path.display());
29            File::open(path)
30                .map_err(|source| {
31                    UnityHubError::ReadConfigError{ config: "editors.json".to_string(), source: source.into() }
32                })
33                .and_then(|f| {
34                    serde_json::from_reader(f).map_err(|source| {
35                        UnityHubError::ReadConfigError { config: "editors.json".to_string(), source: source.into()}
36                    })
37                })?
38        } else {
39            debug!("hub editors file doesn't exist return empty map");
40            HashMap::new()
41        };
42        trace!("loaded editors map: {:?}", map);
43        Ok(Editors::create(map))
44    }
45
46    pub fn create(mut map: HashMap<Version, EditorInstallation>) -> Editors {
47        trace!("create Editors map");
48        map.retain(|version, installation| {
49            trace!(
50                "filter: version: {} - installaton: {:?}",
51                version,
52                installation
53            );
54            let check_installation = UnityInstallation::new(installation.location.to_path_buf());
55            if let Ok(check_installation) = check_installation {
56                trace!(
57                    "Found api installation at with version {} at location: {}",
58                    check_installation.version(),
59                    installation.location.display()
60                );
61                trace!(
62                    "Installation has correct version: {}",
63                    check_installation.version() == version
64                );
65                check_installation.version() == version
66            } else {
67                trace!(
68                    "No installtion found at location: {}",
69                    installation.location.display()
70                );
71                false
72            }
73        });
74        Editors { map }
75    }
76
77    pub fn add(&mut self, editor: &EditorInstallation) -> Option<EditorInstallation> {
78        self.map.insert(editor.version.clone(), editor.clone())
79    }
80
81    pub fn remove(&mut self, editor: &EditorInstallation) -> Option<EditorInstallation> {
82        self.map.remove(&editor.version)
83    }
84
85    pub fn remove_version(&mut self, version: &Version) -> Option<EditorInstallation> {
86        self.map.remove(&version)
87    }
88
89    pub fn flush(&self) -> Result<()> {
90        let config_path =
91            paths::config_path().ok_or_else(|| (UnityHubError::ConfigDirectoryNotFound))?;
92
93        let path = paths::editors_config_path()
94            .ok_or_else(|| UnityHubError::ConfigNotFound("editors.json".to_string()))?;
95
96        fs::create_dir_all(config_path).map_err(|source| UnityHubError::FailedToCreateConfigDirectory{source})?;
97        let mut file = File::create(path).map_err(|source| UnityHubError::FailedToCreateConfig{config: "editors.json".to_string(), source})?;
98
99        let j = serde_json::to_string(&self.map).map_err(|source| {
100            UnityHubError::WriteConfigError {config: "editors.json".to_string(), source: source.into()}
101        })?;
102        write!(file, "{}", &j).map_err(|source| {
103            UnityHubError::WriteConfigError {config: "editors.json".to_string(), source: source.into()}
104        })
105    }
106}
107
108impl IntoIterator for Editors {
109    type Item = EditorInstallation;
110    type IntoIter = ::std::vec::IntoIter<EditorInstallation>;
111
112    fn into_iter(self) -> Self::IntoIter {
113        self.map
114            .values()
115            .cloned()
116            .collect::<Vec<Self::Item>>()
117            .into_iter()
118    }
119}
120
121#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
122pub struct EditorInstallation {
123    version: Version,
124    #[serde(with = "editor_value_location")]
125    location: PathBuf,
126    manual: bool,
127}
128
129trait EditorInstallationMarker {}
130
131impl EditorInstallationMarker for EditorInstallation {}
132
133impl Installation for EditorInstallation {
134    fn path(&self) -> &PathBuf {
135        &self.location()
136    }
137
138    fn version(&self) -> &Version {
139        &self.version
140    }
141}
142
143impl EditorInstallation {
144    pub fn new(version: Version, location: PathBuf) -> EditorInstallation {
145        trace!("create new editor installation: {} at {}", version, location.display());
146        EditorInstallation {
147            version,
148            location,
149            manual: true,
150        }
151    }
152
153    pub fn version(&self) -> &Version {
154        &self.version
155    }
156
157    pub fn location(&self) -> &PathBuf {
158        &self.location
159    }
160}
161
162pub mod editor_value_location {
163    use serde::de::Unexpected;
164    use serde::ser::SerializeSeq;
165    use serde::{self, Deserialize, Deserializer, Serializer};
166    use std::path::{Path, PathBuf};
167    use log::trace;
168
169    pub fn deserialize<'de, D>(deserializer: D) -> Result<PathBuf, D::Error>
170    where
171        D: Deserializer<'de>,
172    {
173        let paths: Vec<String> = Vec::deserialize(deserializer)?;
174        let path = paths
175            .first()
176            .ok_or_else(|| serde::de::Error::invalid_length(0, &"1"))?;
177        let location = Path::new(&path)
178            .parent()
179            .and_then(|location| {
180                if cfg!(target_os = "windows") || cfg!(target_os = "linux") {
181                    return location.parent();
182                }
183                Some(location)
184            })
185            .ok_or_else(|| {
186                serde::de::Error::invalid_value(
187                    Unexpected::Other("location with empty parent"),
188                    &"valid api location",
189                )
190            })?;
191        trace!("found Editor location: {}", location.display());
192        Ok(location.to_path_buf())
193    }
194
195    pub fn serialize<S>(location: &PathBuf, serializer: S) -> Result<S::Ok, S::Error>
196    where
197        S: Serializer,
198    {
199        let mut seq = serializer.serialize_seq(Some(1))?;
200
201        #[cfg(target_os = "windows")]
202        seq.serialize_element(&location.join("Editors\\Unity.exe"))?;
203        #[cfg(target_os = "linux")]
204        seq.serialize_element(&location.join("Editors/Unity"))?;
205        #[cfg(target_os = "macos")]
206        seq.serialize_element(&location.join("Unity.app"))?;
207        #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
208        seq.serialize_element(&location)?;
209
210        seq.end()
211    }
212}
213
214impl Ord for EditorInstallation {
215    fn cmp(&self, other: &EditorInstallation) -> Ordering {
216        self.version.cmp(&other.version)
217    }
218}
219
220impl PartialOrd for EditorInstallation {
221    fn partial_cmp(&self, other: &EditorInstallation) -> Option<Ordering> {
222        Some(self.cmp(other))
223    }
224}
225
226// const INSTALLATION_BINARY: &str = "Unity.app";
227//
228// impl From<UnityInstallation> for EditorInstallation {
229//     fn from(installation: UnityInstallation) -> Self {
230//         let location = installation.path().join(INSTALLATION_BINARY);
231//         let version = installation.into_version();
232//         EditorInstallation {
233//             version,
234//             location,
235//             manual: true,
236//         }
237//     }
238// }
239
240impl<I> FromInstallation<I> for EditorInstallation
241where I: Installation + Sized {
242    fn from_installation(value: I) -> Self {
243        EditorInstallation {
244            location: value.location().to_path_buf(),
245            version: value.version().clone(),
246            manual: true,
247        }
248    }
249}
250
251
252#[cfg(test)]
253mod tests {
254    use std::collections::HashMap;
255    use std::path::Path;
256    use unity_version::{ReleaseType, Version};
257    use crate::unity::hub::editors::EditorInstallation;
258
259    #[test]
260    fn parse_editors() {
261        #[cfg(target_os = "macos")]
262        let data = r#"{
263                        "2018.2.5f1": { "version": "2018.2.5f1", "location": ["/Applications/Unity-2018.2.5f1/Unity.app"], "manual": true },
264                        "2017.1.0f3": { "version": "2017.1.0f3", "location": ["/Applications/Unity-2017.1.0f3/Unity.app"], "manual": true }
265                  }"#;
266
267        #[cfg(target_os = "windows")]
268        let data = r#"{
269                      "2018.2.5f1": { "version": "2018.2.5f1", "location": ["C:\\Program Files\\Unity-2018.2.5f1\\Editor\\Unity.exe"], "manual": true },
270                      "2017.1.0f3": { "version": "2017.1.0f3", "location": ["C:\\Program Files\\Unity-2017.1.0f3\\Editor\\Unity.exe"], "manual": true }
271                }"#;
272
273        #[cfg(target_os = "linux")]
274        let data = r#"{
275                        "2018.2.5f1": { "version": "2018.2.5f1", "location": ["/homce/ci/.local/share/Unity-2018.2.5f1/Editor/Unity"], "manual": true },
276                        "2017.1.0f3": { "version": "2017.1.0f3", "location": ["/homce/ci/.local/share/Unity-2017.1.0f3/Editor/Unity"], "manual": true }
277                  }"#;
278
279        let editors: HashMap<Version, EditorInstallation> =
280            serde_json::from_str(data).unwrap();
281
282        let v = Version::new(2018, 2, 5, ReleaseType::Final, 1);
283
284        #[cfg(target_os = "macos")]
285        let p = Path::new("/Applications/Unity-2018.2.5f1");
286        #[cfg(target_os = "windows")]
287        let p = Path::new("C:\\Program Files\\Unity-2018.2.5f1");
288        #[cfg(target_os = "linux")]
289        let p = Path::new("/homce/ci/.local/share/Unity-2018.2.5f1");
290
291        assert_eq!(
292            &editors[&v],
293            &EditorInstallation {
294                version: v,
295                location: p.to_path_buf(),
296                manual: true
297            }
298        );
299    }
300
301    #[test]
302    fn write_editors() {
303        let v = Version::new(2018, 2, 5, ReleaseType::Final, 1);
304
305        #[cfg(target_os = "macos")]
306        let p = Path::new("/Applications/Unity-2018.2.5f1");
307        #[cfg(target_os = "windows")]
308        let p = Path::new("C:\\Program Files\\Unity-2018.2.5f1");
309        #[cfg(target_os = "linux")]
310        let p = Path::new("/homce/ci/.local/share/Unity-2018.2.5f1");
311
312        #[cfg(target_os = "macos")]
313        let expected_result = r#"{"2018.2.5f1":{"version":"2018.2.5f1","location":["/Applications/Unity-2018.2.5f1/Unity.app"],"manual":true}}"#;
314
315        #[cfg(target_os = "windows")]
316        let expected_result = r#"{"2018.2.5f1":{"version":"2018.2.5f1","location":["C:\\Program Files\\Unity-2018.2.5f1\\Editor\\Unity.exe"],"manual":true}}"#;
317
318        #[cfg(target_os = "linux")]
319        let expected_result = r#"{"2018.2.5f1":{"version":"2018.2.5f1","location":["/homce/ci/.local/share/Unity-2018.2.5f1/Editor/Unity"],"manual":true}}"#;
320
321        let i = EditorInstallation {
322            version: v.clone(),
323            location: p.to_path_buf(),
324            manual: true,
325        };
326
327        let expected_editors: HashMap<Version, EditorInstallation> =
328            serde_json::from_str(&expected_result).unwrap();
329
330        let mut editors: HashMap<Version, EditorInstallation> = HashMap::new();
331        editors.insert(v, i);
332        let json = serde_json::to_string(&editors).expect("convert editors map to json");
333        let written_editors: HashMap<Version, EditorInstallation> =
334            serde_json::from_str(&json).unwrap();
335
336        assert_eq!(
337            written_editors,
338            expected_editors
339        );
340    }
341}