uvm_install2/install/
utils.rs1use 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}