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