1use std::fs;
12use std::path::{Path, PathBuf};
13
14use thiserror::Error;
15
16use rakata_core::ResourceIdentifier;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct ResourceFile {
21 path: PathBuf,
22 identifier: ResourceIdentifier,
23}
24
25impl ResourceFile {
26 pub fn open(path: impl AsRef<Path>) -> Result<Self, FileResourceError> {
28 let path = path.as_ref();
29 let metadata = fs::metadata(path).map_err(|source| FileResourceError::io(path, source))?;
30 if !metadata.is_file() {
31 return Err(FileResourceError::NotAFile {
32 path: path.to_path_buf(),
33 });
34 }
35 Ok(Self {
36 path: path.to_path_buf(),
37 identifier: ResourceIdentifier::from_path(path),
38 })
39 }
40
41 pub fn path(&self) -> &Path {
43 &self.path
44 }
45
46 pub fn identifier(&self) -> &ResourceIdentifier {
48 &self.identifier
49 }
50
51 pub fn read_bytes(&self) -> Result<Vec<u8>, FileResourceError> {
53 fs::read(&self.path).map_err(|source| FileResourceError::io(&self.path, source))
54 }
55}
56
57pub fn read_resource_file(path: impl AsRef<Path>) -> Result<ResourceFileData, FileResourceError> {
59 let file = ResourceFile::open(path)?;
60 let data = file.read_bytes()?;
61 Ok(ResourceFileData {
62 path: file.path,
63 identifier: file.identifier,
64 data,
65 })
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct ResourceFileData {
71 pub path: PathBuf,
73 pub identifier: ResourceIdentifier,
75 pub data: Vec<u8>,
77}
78
79#[derive(Debug, Error)]
81pub enum FileResourceError {
82 #[error("I/O failure for `{path}`: {source}")]
84 Io {
85 path: PathBuf,
87 #[source]
89 source: std::io::Error,
90 },
91 #[error("path is not a regular file: `{path}`")]
93 NotAFile {
94 path: PathBuf,
96 },
97}
98
99impl FileResourceError {
100 fn io(path: &Path, source: std::io::Error) -> Self {
101 Self::Io {
102 path: path.to_path_buf(),
103 source,
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 use tempfile::TempDir;
113
114 #[test]
115 fn reads_loose_resource_file() {
116 let temp = TempDir::new().expect("create tempdir");
117 let path = temp.path().join("p_bastila.utc");
118 fs::write(&path, b"abc").expect("write fixture");
119
120 let file = ResourceFile::open(&path).expect("open file");
121 assert_eq!(file.identifier().resname(), "p_bastila");
122 assert_eq!(file.identifier().to_string(), "p_bastila.utc");
123 assert_eq!(file.read_bytes().expect("read bytes"), b"abc");
124 }
125
126 #[test]
127 fn read_resource_file_returns_data_and_identifier() {
128 let temp = TempDir::new().expect("create tempdir");
129 let path = temp.path().join("dialog.tlk");
130 fs::write(&path, b"tlk").expect("write fixture");
131
132 let loaded = read_resource_file(&path).expect("read resource file");
133 assert_eq!(loaded.identifier.resname(), "dialog");
134 assert_eq!(loaded.data, b"tlk");
135 }
136}