lighty_version/lighty_builder.rs
1use std::path::{Path, PathBuf};
2
3use lighty_core::AppState;
4use lighty_loaders::types::{Loader, VersionInfo};
5use lighty_modsloader::{ModRequest, WithMods};
6
7#[cfg(any(feature = "modrinth", feature = "curseforge"))]
8use lighty_modsloader::ModpackSource;
9
10/// Builder for LightyUpdater-managed instances.
11///
12/// Unlike [`super::VersionBuilder`], `loader_version` here holds the
13/// LightyUpdater server URL: the actual loader and Minecraft version
14/// are fetched from that server at install time. Default paths come
15/// from the global [`AppState`] (call [`AppState::init`] first).
16///
17/// User-attached Modrinth / CurseForge mods can be layered on top of
18/// the server-pushed metadata via [`with_mod`]. Modpacks are
19/// intentionally not supported here — LightyUpdater is itself the
20/// modpack source of truth, mixing in a second modpack would conflict
21/// on the loader / Minecraft version.
22///
23/// [`with_mod`]: LightyVersionBuilder::with_mod
24#[derive(Debug, Clone)]
25pub struct LightyVersionBuilder {
26 pub name: String,
27 pub server_url: String,
28 pub minecraft_version: Option<String>,
29 pub loader: Option<Loader>,
30 pub game_dirs: PathBuf,
31 pub java_dirs: PathBuf,
32 pub mod_requests: Vec<ModRequest>,
33}
34
35impl LightyVersionBuilder {
36 /// Creates a new `LightyVersionBuilder`.
37 ///
38 /// `server_url` is the LightyUpdater server endpoint; the loader
39 /// and Minecraft version are resolved from its response at install
40 /// time. Panics if [`AppState::init`] hasn't been called.
41 ///
42 /// # Example
43 /// ```rust,no_run
44 /// use lighty_core::AppState;
45 /// use lighty_version::LightyVersionBuilder;
46 ///
47 /// AppState::init("MyLauncher").unwrap();
48 /// let builder = LightyVersionBuilder::new("my-server", "https://updater.example.com");
49 /// ```
50 pub fn new(name: &str, server_url: &str) -> Self {
51 Self {
52 name: name.to_string(),
53 server_url: server_url.to_string(),
54 minecraft_version: None,
55 loader: None,
56 game_dirs: AppState::data_dir().join(name),
57 java_dirs: AppState::config_dir().join("jre"),
58 mod_requests: Vec::new(),
59 }
60 }
61
62 /// Opens the user-mods sub-builder. Modpacks are not exposed on
63 /// purpose — see the struct doc.
64 ///
65 /// # Example
66 /// ```rust,no_run
67 /// # use lighty_core::AppState;
68 /// # use lighty_version::LightyVersionBuilder;
69 /// # AppState::init("MyLauncher").unwrap();
70 /// let builder = LightyVersionBuilder::new("server", "https://updater.example.com")
71 /// .with_mod()
72 /// .done();
73 /// ```
74 pub fn with_mod(self) -> LightyModSourcesBuilder {
75 LightyModSourcesBuilder {
76 parent: self,
77 pending: Vec::new(),
78 }
79 }
80}
81
82impl VersionInfo for LightyVersionBuilder {
83 type LoaderType = Loader;
84
85 fn name(&self) -> &str {
86 &self.name
87 }
88
89 fn loader_version(&self) -> &str {
90 &self.server_url
91 }
92
93 fn minecraft_version(&self) -> &str {
94 self.minecraft_version.as_ref().map_or("", String::as_str)
95 }
96
97 fn game_dirs(&self) -> &Path {
98 &self.game_dirs
99 }
100
101 fn java_dirs(&self) -> &Path {
102 &self.java_dirs
103 }
104
105 fn loader(&self) -> &Self::LoaderType {
106 self.loader.as_ref().unwrap_or(&Loader::LightyUpdater)
107 }
108}
109
110impl<'b> VersionInfo for &'b LightyVersionBuilder {
111 type LoaderType = Loader;
112
113 fn name(&self) -> &str {
114 &self.name
115 }
116
117 fn loader_version(&self) -> &str {
118 &self.server_url
119 }
120
121 fn minecraft_version(&self) -> &str {
122 self.minecraft_version.as_ref().map_or("", String::as_str)
123 }
124
125 fn game_dirs(&self) -> &Path {
126 &self.game_dirs
127 }
128
129 fn java_dirs(&self) -> &Path {
130 &self.java_dirs
131 }
132
133 fn loader(&self) -> &Self::LoaderType {
134 self.loader.as_ref().unwrap_or(&Loader::LightyUpdater)
135 }
136}
137
138impl WithMods for LightyVersionBuilder {
139 fn mod_requests(&self) -> &[ModRequest] {
140 &self.mod_requests
141 }
142
143 #[cfg(any(feature = "modrinth", feature = "curseforge"))]
144 fn modpack(&self) -> Option<&ModpackSource> {
145 None
146 }
147}
148
149impl<'b> WithMods for &'b LightyVersionBuilder {
150 fn mod_requests(&self) -> &[ModRequest] {
151 &self.mod_requests
152 }
153
154 #[cfg(any(feature = "modrinth", feature = "curseforge"))]
155 fn modpack(&self) -> Option<&ModpackSource> {
156 None
157 }
158}
159
160/// Sub-builder accumulating user-attached Modrinth / CurseForge mods
161/// on a [`LightyVersionBuilder`]. No modpack methods — LightyUpdater
162/// is already the modpack source.
163pub struct LightyModSourcesBuilder {
164 parent: LightyVersionBuilder,
165 pending: Vec<ModRequest>,
166}
167
168impl LightyModSourcesBuilder {
169 /// Adds Modrinth mod requests on top of the LightyUpdater-pushed metadata.
170 ///
171 /// Each tuple is `(project-slug-or-id, optional-mod-version-id)`.
172 ///
173 /// # Example
174 /// ```rust,no_run
175 /// # use lighty_core::AppState;
176 /// # use lighty_version::LightyVersionBuilder;
177 /// # AppState::init("MyLauncher").unwrap();
178 /// let builder = LightyVersionBuilder::new("server", "https://updater.example.com")
179 /// .with_mod()
180 /// .with_modrinth_mods(vec![
181 /// ("sodium-extra", None),
182 /// ("iris", None),
183 /// ])
184 /// .done();
185 /// ```
186 #[cfg(feature = "modrinth")]
187 pub fn with_modrinth_mods<S>(mut self, list: Vec<(S, Option<String>)>) -> Self
188 where
189 S: Into<String>,
190 {
191 for (id_or_slug, version) in list {
192 self.pending.push(ModRequest::Modrinth {
193 id_or_slug: id_or_slug.into(),
194 version,
195 });
196 }
197 self
198 }
199
200 /// Adds CurseForge mod requests on top of the LightyUpdater-pushed metadata.
201 ///
202 /// Requires [`lighty_modsloader::curseforge::set_api_key`] to have
203 /// been called before `.run()`.
204 ///
205 /// # Example
206 /// ```rust,no_run
207 /// # use lighty_core::AppState;
208 /// # use lighty_version::LightyVersionBuilder;
209 /// # AppState::init("MyLauncher").unwrap();
210 /// let builder = LightyVersionBuilder::new("server", "https://updater.example.com")
211 /// .with_mod()
212 /// .with_curseforge_mods(vec![(238222, None)])
213 /// .done();
214 /// ```
215 #[cfg(feature = "curseforge")]
216 pub fn with_curseforge_mods(mut self, list: Vec<(u32, Option<u32>)>) -> Self {
217 for (mod_id, file_id) in list {
218 self.pending.push(ModRequest::CurseForge { mod_id, file_id });
219 }
220 self
221 }
222
223 /// Threads the accumulated mod requests back into the parent builder.
224 pub fn done(self) -> LightyVersionBuilder {
225 let mut parent = self.parent;
226 let mut pending = self.pending;
227 parent.mod_requests.append(&mut pending);
228 parent
229 }
230}