vrc_get_vpm/environment/
settings.rs1use std::collections::HashSet;
2use std::fmt::Write;
3use std::path::{Path, PathBuf};
4
5use indexmap::IndexMap;
6use url::Url;
7
8use crate::environment::vpm_settings::VpmSettings;
9use crate::environment::vrc_get_settings::VrcGetSettings;
10use crate::environment::{AddUserPackageResult, PackageCollection};
11use crate::io::DefaultEnvironmentIo;
12use crate::package_manifest::LooseManifest;
13use crate::repository::RemoteRepository;
14use crate::utils::{normalize_path, try_load_json};
15use crate::{UserRepoSetting, io};
16
17#[derive(Debug, Clone)]
18pub struct Settings {
19 vpm: VpmSettings,
21 vrc_get: VrcGetSettings,
22}
23
24impl Settings {
25 pub async fn load(io: &DefaultEnvironmentIo) -> io::Result<Self> {
26 let settings = VpmSettings::load(io).await?;
27 let vrc_get_settings = VrcGetSettings::load(io).await?;
28
29 Ok(Self {
30 vpm: settings,
31 vrc_get: vrc_get_settings,
32 })
33 }
34
35 pub async fn save(&self, io: &DefaultEnvironmentIo) -> io::Result<()> {
36 self.vpm.save(io).await?;
37
38 Ok(())
39 }
40}
41
42impl Settings {
44 pub fn show_prerelease_packages(&self) -> bool {
45 self.vpm.show_prerelease_packages()
46 }
47
48 pub fn set_show_prerelease_packages(&mut self, value: bool) {
49 self.vpm.set_show_prerelease_packages(value);
50 }
51
52 pub fn default_project_path(&self) -> Option<&str> {
53 self.vpm.default_project_path()
54 }
55
56 pub fn set_default_project_path(&mut self, value: &str) {
57 self.vpm.set_default_project_path(value);
58 }
59
60 pub fn project_backup_path(&self) -> Option<&str> {
61 self.vpm.project_backup_path()
62 }
63
64 pub fn set_project_backup_path(&mut self, value: &str) {
65 self.vpm.set_project_backup_path(value);
66 }
67
68 pub fn unity_hub_path(&self) -> &str {
69 self.vpm.unity_hub()
70 }
71
72 pub fn set_unity_hub_path(&mut self, value: &str) {
73 self.vpm.set_unity_hub(value);
74 }
75}
76
77#[cfg(feature = "experimental-project-management")]
78impl Settings {
79 pub fn user_projects(&self) -> Option<&[Box<str>]> {
80 self.vpm.user_projects()
81 }
82
83 pub fn retain_user_projects(&mut self, f: impl FnMut(&str) -> bool) -> Option<Vec<Box<str>>> {
84 self.vpm.retain_user_projects(f)
85 }
86
87 pub fn add_user_project(&mut self, path: &str) {
88 self.vpm.add_user_project(path);
89 }
90
91 pub fn remove_user_project(&mut self, path: &str) {
92 self.vpm.remove_user_project(path);
93 }
94
95 pub fn load_from_db(&mut self, connection: &super::VccDatabaseConnection) -> io::Result<()> {
96 let projects = connection.get_projects();
97 let mut project_paths = projects
98 .iter()
99 .filter_map(|x| x.path())
100 .collect::<HashSet<_>>();
101
102 self.vpm
104 .retain_user_projects(|x| project_paths.contains(&x));
105
106 if let Some(user_projects) = self.vpm.user_projects() {
109 for x in user_projects {
110 project_paths.remove(x.as_ref());
111 }
112 }
113
114 for x in project_paths {
115 self.vpm.add_user_project(x);
116 }
117
118 Ok(())
119 }
120}
121
122impl Settings {
124 pub fn ignore_curated_repository(&self) -> bool {
125 self.vrc_get.ignore_curated_repository()
126 }
127
128 pub fn ignore_official_repository(&self) -> bool {
129 self.vrc_get.ignore_official_repository()
130 }
131}
132
133impl Settings {
135 pub fn user_package_folders(&self) -> &[PathBuf] {
136 self.vpm.user_package_folders()
137 }
138
139 pub fn remove_user_package(&mut self, pkg_path: &Path) {
140 self.vpm.remove_user_package_folder(pkg_path);
141 }
142
143 pub async fn add_user_package(
144 &mut self,
145 pkg_path: &Path,
146 io: &DefaultEnvironmentIo,
147 ) -> AddUserPackageResult {
148 if !pkg_path.is_absolute() {
149 return AddUserPackageResult::NonAbsolute;
150 }
151
152 for x in self.vpm.user_package_folders() {
153 if x == pkg_path {
154 return AddUserPackageResult::AlreadyAdded;
155 }
156 }
157
158 match try_load_json::<LooseManifest>(io, &pkg_path.join("package.json")).await {
159 Ok(Some(LooseManifest(package_json))) => package_json,
160 _ => {
161 return AddUserPackageResult::BadPackage;
162 }
163 };
164
165 self.vpm.add_user_package_folder(pkg_path.to_owned());
166
167 AddUserPackageResult::Success
168 }
169}
170
171impl Settings {
173 pub fn get_user_repos(&self) -> &[UserRepoSetting] {
174 self.vpm.user_repos()
175 }
176
177 pub fn can_add_remote_repo(&self, url: &Url, remote_repo: &RemoteRepository) -> bool {
178 let user_repos = self.get_user_repos();
179 if user_repos.iter().any(|x| x.url() == Some(url)) {
180 return false;
181 }
182 if !self.ignore_curated_repository()
184 && url.as_str() == "https://packages.vrchat.com/curated?download"
185 {
186 return false;
187 }
188 if !self.ignore_official_repository()
189 && url.as_str() == "https://packages.vrchat.com/official?download"
190 {
191 return false;
192 }
193
194 if let Some(repo_id) = remote_repo.id() {
195 if user_repos.iter().any(|x| x.id() == Some(repo_id)) {
197 return false;
198 }
199 if repo_id == "com.vrchat.repos.official" && !self.vrc_get.ignore_official_repository()
200 {
201 return false;
202 }
203 if repo_id == "com.vrchat.repos.curated" && !self.vrc_get.ignore_curated_repository() {
204 return false;
205 }
206 }
207
208 true
209 }
210
211 pub fn add_remote_repo(
212 &mut self,
213 url: &Url,
214 name: Option<&str>,
215 headers: IndexMap<Box<str>, Box<str>>,
216 remote_repo: &RemoteRepository,
217 path_buf: &Path,
218 ) -> bool {
219 if !self.can_add_remote_repo(url, remote_repo) {
220 return false;
221 }
222
223 let repo_name = name.or(remote_repo.name()).map(Into::into);
224 let repo_id = remote_repo.id().map(Into::into);
225
226 let mut repo_setting = UserRepoSetting::new(
227 path_buf.to_path_buf().into_boxed_path(),
228 repo_name,
229 Some(url.clone()),
230 repo_id,
231 );
232 repo_setting.headers = headers;
233
234 self.vpm.add_user_repo(repo_setting);
235 true
236 }
237
238 pub fn add_local_repo(&mut self, path: &Path, name: Option<&str>) -> bool {
239 let path = normalize_path(path);
240
241 if self.get_user_repos().iter().any(|x| x.local_path() == path) {
242 return false;
243 }
244
245 self.vpm.add_user_repo(UserRepoSetting::new(
246 path.into(),
247 name.map(Into::into),
248 None,
249 None,
250 ));
251 true
252 }
253
254 pub fn remove_repo(
255 &mut self,
256 condition: impl Fn(&UserRepoSetting) -> bool,
257 ) -> Vec<UserRepoSetting> {
258 self.vpm.retain_user_repos(|x| !condition(x))
259 }
260
261 pub fn remove_id_duplication(&mut self) -> Vec<UserRepoSetting> {
268 let user_repos = self.get_user_repos();
269 if user_repos.is_empty() {
270 return vec![];
271 }
272
273 let mut used_ids = HashSet::new();
274
275 self.vpm.retain_user_repos(|repo| {
278 let mut to_add = true;
279 if let Some(id) = repo.id() {
280 to_add = used_ids.insert(id.to_owned());
281 }
282 if to_add {
283 true
285 } else {
286 false
287 }
288 })
289 }
290
291 pub fn update_id(&mut self, loaded: &PackageCollection) -> bool {
292 self.vpm.update_id(loaded)
293 }
294
295 pub fn export_repositories(&self) -> String {
296 let mut builder = String::new();
297
298 for setting in self.get_user_repos() {
299 let Some(url) = setting.url() else { continue };
300 if setting.headers().is_empty() {
301 writeln!(builder, "{url}").unwrap();
302 } else {
303 let mut add_url = Url::parse("vcc://vpm/addRepo").unwrap();
304 let mut query_builder = add_url.query_pairs_mut();
305 query_builder.clear();
306 query_builder.append_pair("url", url.as_str());
307
308 for (header_name, value) in setting.headers() {
309 query_builder.append_pair("headers[]", &format!("{header_name}:{value}"));
310 }
311 drop(query_builder);
312
313 writeln!(builder, "{add_url}").unwrap();
314 }
315 }
316
317 builder
318 }
319}