1use std::{error::Error, io::Write, path::Path};
4
5use fs_extra::dir::{CopyOptions, move_dir};
6use reqwest::blocking::Client;
7use tempfile::{NamedTempFile, TempDir};
8use zip::{ZipArchive, read::root_dir_common_filter};
9
10mod github;
11
12const ROOT_GITIGNORE: &str = "\
13*
14";
15
16pub fn resolve_download_url<S>(client: &Client, url: S) -> Result<String, Box<dyn Error>>
17where
18 S: Into<String>,
19{
20 let url = url.into();
21
22 if let Some(github_url) = github::resolve_download_url(client, &url)? {
23 Ok(github_url)
24 } else {
25 Ok(url)
26 }
27}
28
29pub fn install<S, P, N>(
32 client: &Client,
33 download_url: S,
34 path: P,
35 nested_path: N,
36) -> Result<(), Box<dyn Error>>
37where
38 S: AsRef<str>,
39 P: AsRef<Path>,
40 N: AsRef<Path>,
41{
42 let download_url = download_url.as_ref();
43 let path = path.as_ref();
44 let nested_path = nested_path.as_ref();
45
46 fs_err::create_dir_all(path)?;
47
48 let mut file = NamedTempFile::with_suffix("dxm-resource-archive")?;
49 let bytes = client
50 .get(download_url)
51 .send()?
52 .error_for_status()?
53 .bytes()?;
54 file.write_all(&bytes)?;
55
56 log::debug!("extracting resource into {}", nested_path.display());
57 let dir = TempDir::with_suffix("dxm-resource")?;
58 ZipArchive::new(file.reopen()?)?.extract_unwrapped_root_dir(&dir, root_dir_common_filter)?;
59
60 let copy_options = CopyOptions::new().content_only(true);
61 let mut original_dir: Option<TempDir> = None;
62
63 if is_dir_with_files(path)? {
64 let tempdir = TempDir::with_suffix("dxm-resource-original")?;
65 move_dir(path, &tempdir, ©_options)?;
66 original_dir = Some(tempdir);
67 }
68
69 let result = move_dir(dir.path().join(nested_path), path, ©_options);
70 if result.is_err()
71 && let Some(original_dir) = original_dir
72 {
73 move_dir(original_dir, path, ©_options)?;
74 }
75 result?;
76
77 fs_err::write(path.join(".gitignore"), ROOT_GITIGNORE)?;
78
79 Ok(())
80}
81
82fn is_dir_with_files<P>(path: P) -> std::io::Result<bool>
83where
84 P: AsRef<Path>,
85{
86 let path = path.as_ref();
87
88 if !path.is_dir() {
89 return Ok(false);
90 };
91
92 for entry in fs_err::read_dir(path)? {
93 if entry?.file_type()?.is_file() {
94 return Ok(true);
95 }
96 }
97
98 Ok(false)
99}