tidalcycles_rs/
supercollider_sc3_plugins.rs1use std::fs;
2use std::fs::File;
3use std::io;
4use std::path::Path;
5use std::process::Command;
6use zip::ZipArchive;
7
8fn download_sc3_plugins(tmp_dir: &str) -> io::Result<String> {
9 fs::create_dir_all(tmp_dir)?;
11
12 if let None = crate::install::ensure_gh_installed() {
13 return Err(io::Error::new(
14 io::ErrorKind::Other,
15 "gh CLI is not installed",
16 ));
17 }
18
19 let status = Command::new("gh")
21 .args([
22 "release",
23 "download",
24 "--repo",
25 "supercollider/sc3-plugins",
26 "--pattern",
27 "*Windows-64bit.zip",
28 "--dir",
29 tmp_dir,
30 "--clobber",
31 ])
32 .status()?;
33
34 if !status.success() {
35 return Err(io::Error::new(
36 io::ErrorKind::Other,
37 "Failed to download sc3-plugins",
38 ));
39 }
40
41 let zip_file = fs::read_dir(tmp_dir)?
43 .filter_map(|entry| entry.ok())
44 .find(|entry| {
45 entry
46 .file_name()
47 .to_string_lossy()
48 .ends_with("Windows-64bit.zip")
49 })
50 .map(|entry| entry.path())
51 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Zip file not found"))?;
52
53 Ok(zip_file.to_string_lossy().to_string())
54}
55
56fn extract_zip(zip_path: &str, extract_to: &str) -> io::Result<()> {
57 let file = File::open(zip_path)?;
58 let mut archive = ZipArchive::new(file)?;
59 fs::create_dir_all(extract_to)?;
60
61 for i in 0..archive.len() {
62 let mut file = archive.by_index(i)?;
63 let outpath = Path::new(extract_to).join(file.name());
64
65 if file.is_dir() {
66 fs::create_dir_all(&outpath)?;
67 } else {
68 if let Some(parent) = outpath.parent() {
69 fs::create_dir_all(parent)?;
70 }
71 let mut outfile = File::create(&outpath)?;
72 io::copy(&mut file, &mut outfile)?;
73 }
74 }
75 Ok(())
76}
77
78fn copy_sc3_plugins(extracted_dir: &str, sc_user_plugins_dir: &str) -> io::Result<()> {
79 fs::create_dir_all(sc_user_plugins_dir)?;
81 for entry in fs::read_dir(extracted_dir)? {
82 let entry = entry?;
83 let path = entry.path();
84 if path.is_dir() {
85 let dest = Path::new(sc_user_plugins_dir).join(entry.file_name());
86 if dest.exists() {
87 fs::remove_dir_all(&dest)?;
88 }
89 fs::rename(&path, &dest)?;
90 }
91 }
92 Ok(())
93}
94
95pub fn install_sc3_plugins() -> io::Result<()> {
97 let tmp_dir = "tmp_gh";
98 let sc_user_plugins_dir = dirs::home_dir()
99 .expect("Could not find home directory")
100 .join("AppData/Local/SuperCollider/Extensions/sc3-plugins");
101
102 let result = (|| {
104 let zip_path = download_sc3_plugins(tmp_dir)?;
105 let extract_to = format!("{}/extracted", tmp_dir);
106 extract_zip(&zip_path, &extract_to)?;
107 copy_sc3_plugins(&extract_to, sc_user_plugins_dir.to_str().unwrap())?;
108 Ok(())
109 })();
110
111 if let Err(e) = fs::remove_dir_all(tmp_dir) {
113 eprintln!("Warning: failed to remove temp dir {}: {}", tmp_dir, e);
114 }
115
116 if result.is_ok() {
117 println!("sc3-plugins installed successfully.");
118 }
119 result
120}
121
122pub fn is_sc3_plugins_installed() -> bool {
123 let sc_user_plugins_dir = match dirs::home_dir() {
124 Some(home) => home.join("AppData/Local/SuperCollider/Extensions/sc3-plugins"),
125 None => return false,
126 };
127 sc_user_plugins_dir.exists()
128 && sc_user_plugins_dir
129 .read_dir()
130 .map(|mut d| d.next().is_some())
131 .unwrap_or(false)
132}