uvm_install2/install/
utils.rs

1use cluFlock::{ExclusiveFlock, FlockLock};
2use reqwest::blocking::Client;
3use reqwest::header::{USER_AGENT, CONTENT_DISPOSITION};
4use reqwest::Url;
5use std::fs::File;
6use std::io;
7use std::path::Path;
8#[cfg(windows)]
9use std::path::{Component, Prefix, PathBuf};
10use log::{debug, trace};
11
12pub fn lock_process_or_wait<'a>(lock_file: &'a File) -> io::Result<FlockLock<&'a File>> {
13    match ExclusiveFlock::try_lock(lock_file) {
14        Ok(lock) => {
15            trace!("aquired process lock.");
16            Ok(lock)
17        }
18        Err(_) => {
19            debug!("progress lock already aquired.");
20            debug!("wait for other process to finish.");
21            let lock = lock_file.wait_lock()?;
22            Ok(lock)
23        }
24        //Err(err) => Err(err),
25    }
26}
27
28#[macro_export]
29macro_rules! lock_process {
30    ($lock_path:expr) => {
31        let lock_file = fs::File::create($lock_path)?;
32        let _lock = utils::lock_process_or_wait(&lock_file)?;
33    };
34}
35
36pub(crate) use lock_process;
37
38#[cfg(windows)]
39fn get_path_prefix(path: &Path) -> Prefix {
40    match path.components().next().unwrap() {
41        Component::Prefix(prefix_component) => prefix_component.kind(),
42        _ => panic!(),
43    }
44}
45
46#[cfg(windows)]
47pub fn prepend_long_path_support<P:AsRef<Path>>(path:P) -> PathBuf {
48    use std::ffi::OsString;
49
50    let path = path.as_ref();
51    if (path.has_root() && !path.is_absolute()) || (path.is_absolute() && !get_path_prefix(path).is_verbatim()) {
52        trace!(r#"prepend path with \\?\"#);
53        let mut components = path.components();
54        let mut new_prefix = OsString::new();
55        let mut new_path = PathBuf::new();
56
57        new_prefix.push(r"\\?\");
58        new_prefix.push(components.next().unwrap());
59
60        new_path.push(new_prefix);
61        while let Some(component) = components.next() {
62            new_path.push(component);
63        }
64        new_path
65    } else {
66        path.to_path_buf()
67    }
68}
69
70pub struct UrlUtils {}
71
72impl UrlUtils {
73    fn get_final_file_name_from_url(url: &Url) -> io::Result<String> {
74        let client = Client::new();
75        let response = client
76            .head(url.clone())
77            .header(USER_AGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36")
78            .send()
79            .map_err(|err| {
80                io::Error::new(io::ErrorKind::Other, err)
81            })?;
82
83        response
84            .headers()
85            .get(CONTENT_DISPOSITION)
86            .and_then(|disposition| {
87                if disposition.is_empty() {
88                    None
89                } else {
90                    Some(disposition)
91                }
92            })
93            .and_then(|disposition| {
94                let disposition = disposition.to_str().ok()?;
95                trace!("disposition header value: {}", disposition);
96                let parts = disposition.split(';');
97                parts
98                    .map(|s| s.trim())
99                    .fold(None, {
100                        |filename: Option<String>, part| {
101                            if part.starts_with("filename=") {
102                                let part = part.replace("filename=", "");
103                                let part = &part.trim_start_matches('"').trim_end_matches('"');
104                                Some(part.to_string())
105                            } else {
106                                filename
107                            }
108                        }
109                    })
110                    .map(|name| {
111                        trace!("after header disposition replacement");
112                        trace!("{}", &name);
113                        name
114                    })
115            })
116            .or_else(|| {
117                response
118                    .url()
119                    .as_str()
120                    .rsplit('/')
121                    .next()
122                    .map(|s| s.to_string())
123            })
124            .ok_or_else(|| {
125                io::Error::new(io::ErrorKind::InvalidData, "unable to parse final filename")
126            })
127    }
128
129    pub fn get_file_name_from_url(url: &Url) -> io::Result<String> {
130        let test_path = Path::new(url.as_ref());
131        if test_path.extension().is_some() {
132            url.as_str()
133                .rsplit('/')
134                .next()
135                .map(|s| s.to_string())
136                .ok_or_else(|| {
137                    io::Error::new(
138                        io::ErrorKind::NotFound,
139                        format!("unable to read filename from url {}", url),
140                    )
141                })
142        } else {
143            Self::get_final_file_name_from_url(url)
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use reqwest::Url;
152
153    #[test]
154    fn parse_file_name_from_url_with_file_name_part() {
155        let url = Url::parse("https://beta.unity3d.com/download/8ea4afdbfa47/MacEditorTargetInstaller/UnitySetup-Android-Support-for-Editor-2019.3.0a8.pkg").unwrap();
156        assert_eq!(UrlUtils::get_file_name_from_url(&url).unwrap(), "UnitySetup-Android-Support-for-Editor-2019.3.0a8.pkg".to_string());
157    }
158
159    #[test]
160    fn parse_file_name_from_url_without_file_name_part_and_content_disposition() {
161        let url = Url::parse("https://go.microsoft.com/fwlink/?linkid=2086937").unwrap();
162        assert!(UrlUtils::get_file_name_from_url(&url).unwrap().starts_with("visualstudioformac-"));
163    }
164
165    #[test]
166    fn parse_file_name_from_url_without_file_name_part_and_content_disposition2() {
167        let url = Url::parse("https://go.microsoft.com/fwlink/?linkid=2087047").unwrap();
168        assert!(UrlUtils::get_file_name_from_url(&url).unwrap().starts_with("monoframework-mdk-"));
169    }
170
171    #[test]
172    fn parse_file_name_from_url_without_file_name_part_and_content_disposition3() {
173        let url = Url::parse("https://new-translate.unity3d.jp/v1/live/54/2019.3/zh-hant").unwrap();
174        assert_eq!(UrlUtils::get_file_name_from_url(&url).unwrap(), "zh-hant.po".to_string());
175    }
176
177    #[cfg(windows)]
178    #[test]
179    fn prepend_long_path_prefix_when_missing() {
180        let path = Path::new(r#"c:/path/to/some/file.txt"#);
181        let new_path = prepend_long_path_support(&path);
182        assert!(new_path.to_string_lossy().starts_with(r#"\\?\c:\"#));
183    }
184
185    #[cfg(windows)]
186    #[test]
187    fn prepend_long_path_prefix_when_missing2() {
188        let path = Path::new(r#"/path/to/some/file.txt"#);
189        let new_path = prepend_long_path_support(&path);
190        assert!(new_path.to_string_lossy().starts_with(r#"\\?\"#));
191    }
192
193    #[cfg(windows)]
194    #[test]
195    fn prepend_long_path_changes_path_separator() {
196        let path = Path::new(r#"c:/path/to/some/file.txt"#);
197        let new_path = prepend_long_path_support(&path);
198        assert_eq!(new_path.to_string_lossy() , r#"\\?\c:\path\to\some\file.txt"#);
199    }
200
201    #[cfg(windows)]
202    #[test]
203    fn prepend_long_path_prefix_only_absolute_paths() {
204        let path = Path::new(r#"./some/file.txt"#);
205        let new_path = prepend_long_path_support(&path);
206        assert!(!new_path.to_string_lossy().starts_with(r#"\\?\"#));
207    }
208
209    #[cfg(windows)]
210    #[test]
211    fn prepend_long_path_prefix_returns_same_path_when_already_prefixed() {
212        let path = Path::new(r#"\\?\c:/path/to/some/file.txt"#);
213        let new_path = prepend_long_path_support(&path);
214        assert_eq!(path.to_str(), new_path.to_str());
215    }
216}