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, 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 }
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}