1use std::collections::HashMap;
2use std::path::PathBuf;
3use thiserror::Error;
4use crate::CryptoError;
5use crate::parser::file::{FileParseError, FileReconstructionError};
6use crate::parser::library::{LibraryParseError, LibraryReconstructionError};
7use crate::parser::logic::{LogicParseError, LogicReconstructionError};
8use crate::parser::resource::{ResourceParseError, ResourceReconstructionError};
9use crate::parser::view::{ViewParseError, ViewReconstructionError};
10
11pub mod project;
12pub mod file;
13pub mod library;
14pub mod resource;
15pub mod view;
16pub mod logic;
17pub(crate) mod serde_util;
18
19pub trait Parsable
21where Self: Sized {
22 type ParseError;
23 type ReconstructionError;
24
25 fn parse(decrypted_content: &str) -> Result<Self, Self::ParseError>;
27
28 fn reconstruct(&self) -> Result<String, Self::ReconstructionError> {
31 unimplemented!()
32 }
33}
34
35#[derive(Debug, Clone, PartialEq)]
37pub struct RawSketchwareProject {
38 pub project: String,
39 pub file: String,
40 pub library: String,
41 pub resource: String,
42 pub view: String,
43 pub logic: String,
44
45 pub resource_files: Option<Vec<ResourceFileWrapper>>
49}
50
51impl RawSketchwareProject {
52 pub fn new(
54 project: String,
55 file: String,
56 library: String,
57 resource: String,
58 view: String,
59 logic: String,
60 resource_files: Vec<ResourceFileWrapper>
61 ) -> Self {
62 RawSketchwareProject { project, file, library, resource, view, logic, resource_files: Some(resource_files) }
63 }
64
65 pub fn new_wo_res(
68 project: String,
69 file: String,
70 library: String,
71 resource: String,
72 view: String,
73 logic: String,
74 ) -> Self {
75 RawSketchwareProject { project, file, library, resource, view, logic, resource_files: None }
76 }
77
78 pub fn from_encrypted(
79 project: Vec<u8>,
80 file: Vec<u8>,
81 library: Vec<u8>,
82 resource: Vec<u8>,
83 view: Vec<u8>,
84 logic: Vec<u8>,
85 resource_files: Vec<ResourceFileWrapper>
86 ) -> Result<Self, CryptoError> {
87 macro_rules! decrypt {
88 ($name_ident:ident, $name:expr) => {
89 String::from_utf8(super::decrypt_sw_encrypted(&$name_ident)?)
90 .map_err(CryptoError::FromUtf8Error)?
91 }
92 }
93
94 Ok(RawSketchwareProject {
95 project: decrypt!(project, "project"),
96 file: decrypt!(file, "file"),
97 library: decrypt!(library, "library"),
98 resource: decrypt!(resource, "resource"),
99 view: decrypt!(view, "view"),
100 logic: decrypt!(logic, "logic"),
101 resource_files: Some(resource_files)
102 })
103 }
104
105 pub fn from_encrypted_wo_res(
108 project: Vec<u8>,
109 file: Vec<u8>,
110 library: Vec<u8>,
111 resource: Vec<u8>,
112 view: Vec<u8>,
113 logic: Vec<u8>,
114 ) -> Result<Self, CryptoError> {
115 macro_rules! decrypt {
116 ($name_ident:ident, $name:expr) => {
117 String::from_utf8(super::decrypt_sw_encrypted(&$name_ident)?)
118 .map_err(CryptoError::FromUtf8Error)?
119 }
120 }
121
122 Ok(RawSketchwareProject {
123 project: decrypt!(project, "project"),
124 file: decrypt!(file, "file"),
125 library: decrypt!(library, "library"),
126 resource: decrypt!(resource, "resource"),
127 view: decrypt!(view, "view"),
128 logic: decrypt!(logic, "logic"),
129 resource_files: None
130 })
131 }
132}
133
134#[derive(Debug, Clone, PartialEq)]
138pub struct SketchwareProject {
139 pub project: project::Project,
140 pub file: file::File,
141 pub library: library::Library,
142 pub resource: resource::Resource,
143 pub view: view::View,
144 pub logic: logic::Logic,
145
146 pub resource_files: Option<ResourceFiles>,
149}
150
151impl SketchwareProject {
152 pub fn parse_from(raw_swproj: RawSketchwareProject) -> Result<Self, SketchwareProjectParseError> {
154 Ok(SketchwareProject {
155 project: project::Project::parse(raw_swproj.project.as_str())
156 .map_err(SketchwareProjectParseError::ProjectParseError)?,
157
158 file: file::File::parse(raw_swproj.file.as_str())
159 .map_err(SketchwareProjectParseError::FileParseError)?,
160
161 library: library::Library::parse(raw_swproj.library.as_str())
162 .map_err(SketchwareProjectParseError::LibraryParseError)?,
163
164 resource: resource::Resource::parse(raw_swproj.resource.as_str())
165 .map_err(SketchwareProjectParseError::ResourceParseError)?,
166
167 view: view::View::parse(raw_swproj.view.as_str())
168 .map_err(SketchwareProjectParseError::ViewParseError)?,
169
170 logic: logic::Logic::parse(raw_swproj.logic.as_str())
171 .map_err(SketchwareProjectParseError::LogicParseError)?,
172
173 resource_files: raw_swproj.resource_files
174 .map(|r| r.try_into())
175 .transpose()
176 .map_err(SketchwareProjectParseError::ResourceFilesParseError)?,
177 })
178 }
179
180 pub fn parse(project: String, file: String, library: String, resource: String, view: String,
182 logic: String, resource_files: Vec<ResourceFileWrapper>)
183 -> Result<Self, SketchwareProjectParseError> {
184
185 SketchwareProject::parse_from(RawSketchwareProject {
186 project,
187 file,
188 library,
189 resource,
190 view,
191 logic,
192 resource_files: Some(resource_files)
193 })
194 }
195
196 pub fn parse_wo_res(project: String, file: String, library: String, resource: String,
199 view: String, logic: String)
200 -> Result<Self, SketchwareProjectParseError> {
201
202 SketchwareProject::parse_from(RawSketchwareProject {
203 project,
204 file,
205 library,
206 resource,
207 view,
208 logic,
209 resource_files: None
210 })
211 }
212}
213
214impl TryInto<RawSketchwareProject> for SketchwareProject {
215 type Error = SketchwareProjectReconstructionError;
216
217 fn try_into(self) -> Result<RawSketchwareProject, Self::Error> {
218 Ok(RawSketchwareProject {
219 project: self.project.reconstruct()
220 .map_err(SketchwareProjectReconstructionError::ProjectReconstructionError)?,
221
222 file: self.file.reconstruct()
223 .map_err(SketchwareProjectReconstructionError::FileReconstructionError)?,
224
225 library: self.library.reconstruct()
226 .map_err(SketchwareProjectReconstructionError::LibraryReconstructionError)?,
227
228 resource: self.resource.reconstruct()
229 .map_err(SketchwareProjectReconstructionError::ResourceReconstructionError)?,
230
231 view: self.view.reconstruct()
232 .map_err(SketchwareProjectReconstructionError::ViewReconstructionError)?,
233
234 logic: self.logic.reconstruct()
235 .map_err(SketchwareProjectReconstructionError::LogicReconstructionError)?,
236
237 resource_files: self.resource_files.map(|r| r.into())
238 })
239 }
240}
241
242#[derive(Error, Debug)]
243pub enum SketchwareProjectParseError {
244 #[error("failed to parse the data file `project`")]
245 ProjectParseError(#[from] serde_json::Error),
246
247 #[error("failed to parse the data file `file`")]
248 FileParseError(#[from] FileParseError),
249
250 #[error("failed to parse the data file `library`")]
251 LibraryParseError(#[from] LibraryParseError),
252
253 #[error("failed to parse the data file `resource`")]
254 ResourceParseError(#[from] ResourceParseError),
255
256 #[error("failed to parse the data file `view`")]
257 ViewParseError(#[from] ViewParseError),
258
259 #[error("failed to parse the data file `logic`")]
260 LogicParseError(#[from] LogicParseError),
261
262 #[error("failed retrieve resource files")]
263 ResourceFilesParseError(#[from] ResourceFilesParseError)
264}
265
266#[derive(Error, Debug)]
268pub enum SketchwareProjectReconstructionError {
269 #[error("failed to reconstruct the data file `project`")]
270 ProjectReconstructionError(#[from] serde_json::Error),
271
272 #[error("failed to reconstruct the data file `file`")]
273 FileReconstructionError(#[from] FileReconstructionError),
274
275 #[error("failed to reconstruct the data file `library`")]
276 LibraryReconstructionError(#[from] LibraryReconstructionError),
277
278 #[error("failed to reconstruct the data file `resource`")]
279 ResourceReconstructionError(#[from] ResourceReconstructionError),
280
281 #[error("failed to reconstruct the data file `view`")]
282 ViewReconstructionError(#[from] ViewReconstructionError),
283
284 #[error("failed to reconstruct the data file `logic`")]
285 LogicReconstructionError(#[from] LogicReconstructionError),
286}
287
288#[derive(Debug, Clone, PartialEq, Eq)]
297pub enum ResourceFileWrapper {
298 Path(PathBuf),
302
303 StringId {
305 id: String,
306
307 res_full_name: String,
312 res_type: ResourceType
313 },
314
315 U32Id {
317 id: u32,
318
319 res_full_name: String,
324 res_type: ResourceType
325 },
326}
327
328impl ResourceFileWrapper {
329 pub fn get_full_name(&self) -> String {
330 match &self {
331 ResourceFileWrapper::Path(path) => {
332 path.file_name().unwrap().to_str().unwrap().to_string() }
334
335 ResourceFileWrapper::StringId { res_full_name, .. } => { res_full_name.to_owned() }
336 ResourceFileWrapper::U32Id { res_full_name, .. } => { res_full_name.to_owned() }
337 }
338 }
339
340 pub fn make_random_id(res_full_name: String, res_type: ResourceType) -> ResourceFileWrapper {
342 ResourceFileWrapper::U32Id {
343 id: rand::random(),
344 res_full_name,
345 res_type
346 }
347 }
348}
349
350#[derive(Debug, Copy, Clone, PartialEq, Eq)]
351pub enum ResourceType { Image, Sound, Font, CustomIcon }
352
353#[derive(Debug, Clone, PartialEq)]
357pub struct ResourceFiles {
358 pub custom_icon: Option<ResourceFileWrapper>,
359 pub images: HashMap<String, ResourceFileWrapper>,
360 pub sounds: HashMap<String, ResourceFileWrapper>,
361 pub fonts: HashMap<String, ResourceFileWrapper>,
362}
363
364impl TryFrom<Vec<ResourceFileWrapper>> for ResourceFiles {
365 type Error = ResourceFilesParseError;
366
367 fn try_from(value: Vec<ResourceFileWrapper>) -> Result<Self, Self::Error> {
368 let mut images = HashMap::new();
369 let mut sounds = HashMap::new();
370 let mut fonts = HashMap::new();
371 let mut custom_icon = None;
372
373 for path in value {
374 match &path {
375 ResourceFileWrapper::Path(file) => {
376 if !file.exists() {
377 return Err(ResourceFilesParseError::FileDoesntExist { path: file.clone() });
378 }
379
380 let res_type = file
386 .parent().ok_or_else(|| ResourceFilesParseError::InvalidPath { path: file.clone() })?
387 .parent().ok_or_else(|| ResourceFilesParseError::InvalidPath { path: file.clone() })?
388 .file_name().ok_or_else(|| ResourceFilesParseError::InvalidPath { path: file.clone() })?
389 .to_str().ok_or_else(|| ResourceFilesParseError::InvalidPath { path: file.clone() })?;
390
391 let res_full_name = file.file_name()
392 .ok_or_else(|| ResourceFilesParseError::InvalidPath { path: file.clone() })?
393 .to_str().ok_or_else(|| ResourceFilesParseError::InvalidPath { path: file.clone() })?
394 .to_string();
395
396 match res_type {
397 "images" => { images.insert(res_full_name, path); },
398 "sounds" => { sounds.insert(res_full_name, path); },
399 "fonts" => { fonts.insert(res_full_name, path); },
400 "icons" => { custom_icon = Some(path); },
401
402 _ => Err(ResourceFilesParseError::InvalidPath { path: file.clone() })?
403 }
404 }
405
406 ResourceFileWrapper::StringId { res_type, res_full_name, .. } => {
407 match res_type {
408 ResourceType::Image => { images.insert(res_full_name.to_owned(), path); },
409 ResourceType::Sound => { sounds.insert(res_full_name.to_owned(), path); },
410 ResourceType::Font => { fonts.insert(res_full_name.to_owned(), path); },
411 ResourceType::CustomIcon => { custom_icon = Some(path); }
412 }
413 }
414
415 ResourceFileWrapper::U32Id { res_type, res_full_name, .. } => {
416 match res_type {
417 ResourceType::Image => { images.insert(res_full_name.to_owned(), path); },
418 ResourceType::Sound => { sounds.insert(res_full_name.to_owned(), path); },
419 ResourceType::Font => { fonts.insert(res_full_name.to_owned(), path); },
420 ResourceType::CustomIcon => { custom_icon = Some(path); }
421 }
422 }
423 }
424 }
425
426 Ok(ResourceFiles { custom_icon, images, sounds, fonts })
427 }
428}
429
430#[derive(Error, Debug)]
431pub enum ResourceFilesParseError {
432 #[error("file `{path:?}` does not exist")]
433 FileDoesntExist {
434 path: PathBuf
435 },
436 #[error("path given `{path:?}` is invalid (are you sure its pointing to a sketchware's resources folder?)")]
437 InvalidPath {
438 path: PathBuf
439 }
440}
441
442impl Into<Vec<ResourceFileWrapper>> for ResourceFiles {
443 fn into(self) -> Vec<ResourceFileWrapper> {
444 let mut result = Vec::new();
445
446 if let Some(custom_icon) = self.custom_icon {
447 result.push(custom_icon);
448 }
449
450 result.append(&mut self.images.into_values().collect());
451 result.append(&mut self.sounds.into_values().collect());
452 result.append(&mut self.fonts.into_values().collect());
453
454 result
455 }
456}
457
458impl Default for ResourceFiles {
459 fn default() -> Self {
460 ResourceFiles {
461 custom_icon: None,
462 images: HashMap::new(),
463 sounds: HashMap::new(),
464 fonts: HashMap::new()
465 }
466 }
467}