comparison python/src/lib.rs @ 781:5b43c42fa680

Stop exposing PBG3 to Python
author Link Mauve <linkmauve@linkmauve.fr>
date Sun, 09 Nov 2025 19:50:35 +0100
parents ee09657d3789
children a30ce01b9154
comparison
equal deleted inserted replaced
780:1ada4036ab88 781:5b43c42fa680
5 use touhou_formats::th06::std as stage; 5 use touhou_formats::th06::std as stage;
6 use std::collections::HashMap; 6 use std::collections::HashMap;
7 use std::fs::File; 7 use std::fs::File;
8 use std::io::BufReader; 8 use std::io::BufReader;
9 use std::path::PathBuf; 9 use std::path::PathBuf;
10 use std::sync::{Arc, Mutex};
10 11
11 #[cfg(feature = "glide")] 12 #[cfg(feature = "glide")]
12 mod glide; 13 mod glide;
13 14
14 #[pyclass(module = "libtouhou")] 15 #[pyclass(module = "libtouhou")]
72 73
73 self.inner.script.iter().map(|call| call_to_python(py, call).unwrap()).collect() 74 self.inner.script.iter().map(|call| call_to_python(py, call).unwrap()).collect()
74 } 75 }
75 } 76 }
76 77
77 #[pyclass(module = "libtouhou")]
78 struct PBG3 {
79 filename: PathBuf,
80 inner: pbg3::PBG3<BufReader<File>>,
81 }
82
83 impl PBG3 {
84 fn get_file_internal(&mut self, name: &str) -> Vec<u8> {
85 self.inner.get_file(name, true).unwrap()
86 }
87 }
88
89 #[pymethods]
90 impl PBG3 {
91 #[staticmethod]
92 fn from_filename(filename: PathBuf) -> PyResult<PBG3> {
93 let inner = pbg3::from_path_buffered(&filename)?;
94 Ok(PBG3 {
95 filename,
96 inner
97 })
98 }
99
100 fn __repr__(&self) -> String {
101 format!("PBG3({})", self.filename.to_str().unwrap())
102 }
103
104 #[getter]
105 fn file_list(&self) -> Vec<String> {
106 self.inner.list_files().cloned().collect()
107 }
108
109 fn list_files(&self) -> Vec<String> {
110 self.inner.list_files().cloned().collect()
111 }
112
113 fn get_file(&mut self, py: Python, name: &str) -> Py<PyBytes> {
114 let data = self.get_file_internal(name);
115 PyBytes::new(py, &data).into()
116 }
117 }
118
119 /// A loader for Touhou files. 78 /// A loader for Touhou files.
120 #[pyclass(module = "libtouhou", get_all, subclass)] 79 #[pyclass(module = "libtouhou", subclass)]
121 #[derive(Default)] 80 #[derive(Default)]
122 struct Loader { 81 struct Loader {
123 /// The file names to the possible executable. 82 /// The file names to the possible executable.
83 #[pyo3(get)]
124 exe_files: Vec<PathBuf>, 84 exe_files: Vec<PathBuf>,
125 85
126 /// The path to the game directory. 86 /// The path to the game directory.
87 #[pyo3(get)]
127 game_dir: Option<PathBuf>, 88 game_dir: Option<PathBuf>,
128 89
129 /// A map from inner filenames to the archive containing them. 90 /// A map from inner filenames to the archive containing them.
130 known_files: HashMap<String, Py<PBG3>>, 91 known_files: HashMap<String, Arc<Mutex<pbg3::PBG3<BufReader<File>>>>>,
131 } 92 }
132 93
133 #[pymethods] 94 #[pymethods]
134 impl Loader { 95 impl Loader {
135 /// Create a new Loader for the given game_dir. 96 /// Create a new Loader for the given game_dir.
144 105
145 /// Scan the game_dir for archives. 106 /// Scan the game_dir for archives.
146 /// 107 ///
147 /// paths_lists is a list of ':'-separated glob patterns, the first matching file will be used 108 /// paths_lists is a list of ':'-separated glob patterns, the first matching file will be used
148 /// and the other ones ignored. 109 /// and the other ones ignored.
149 fn scan_archives(&mut self, py: Python, paths_lists: Vec<String>) -> PyResult<()> { 110 fn scan_archives(&mut self, paths_lists: Vec<String>) -> PyResult<()> {
150 for paths in paths_lists.iter() { 111 for paths in paths_lists.iter() {
151 let found_paths: Vec<_> = paths.split(':').map(|path| { 112 let found_paths: Vec<_> = paths.split(':').map(|path| {
152 glob::glob(if let Some(game_dir) = self.game_dir.as_ref() { 113 glob::glob(if let Some(game_dir) = self.game_dir.as_ref() {
153 game_dir.join(path) 114 game_dir.join(path)
154 } else { 115 } else {
160 } 121 }
161 let path = &found_paths[0]; 122 let path = &found_paths[0];
162 if let Some(extension) = path.extension() && extension == "exe" { 123 if let Some(extension) = path.extension() && extension == "exe" {
163 self.exe_files.extend(found_paths); 124 self.exe_files.extend(found_paths);
164 } else { 125 } else {
165 let pbg3 = PBG3::from_filename(path.to_owned())?; 126 let pbg3 = pbg3::from_path_buffered(path)?;
166 let filenames = pbg3.list_files(); 127 let filenames: Vec<_> = pbg3.list_files().cloned().collect();
167 let pbg3 = Py::new(py, pbg3)?; 128 let pbg3 = Arc::new(Mutex::new(pbg3));
168 for name in filenames { 129 for name in filenames {
169 self.known_files.insert(name.clone(), Py::clone_ref(&pbg3, py)); 130 self.known_files.insert(name.clone(), Arc::clone(&pbg3));
170 } 131 }
171 } 132 }
172 } 133 }
173 Ok(()) 134 Ok(())
174 } 135 }
175 136
176 /// Return the given file as an io.BytesIO object. 137 /// Return the given file as a Vec<u8>.
177 fn get_file(&self, py: Python, name: String) -> PyResult<Py<PyAny>> { 138 fn get_file_internal(&self, name: &str) -> PyResult<Vec<u8>> {
178 if let Some(archive) = self.known_files.get(&name) { 139 if let Some(archive) = self.known_files.get(name) {
179 let mut archive = archive.borrow_mut(py); 140 let mut archive = archive.lock().unwrap();
180 let bytes = archive.get_file(py, &name); 141 let bytes = archive.get_file(name, true)?;
181 let io = py.import("io")?; 142 Ok(bytes)
182 let bytesio_class = io.dict().get_item("BytesIO")?.unwrap();
183 let file = bytesio_class.call1((bytes,))?;
184 Ok(file.unbind())
185 } else { 143 } else {
186 Err(PyKeyError::new_err(format!("Unknown file {name:?}"))) 144 Err(PyKeyError::new_err(format!("Unknown file {name:?}")))
187 } 145 }
188 } 146 }
189 147
148 /// Return the given file as an io.BytesIO object.
149 fn get_file(&self, py: Python, name: String) -> PyResult<Py<PyAny>> {
150 let vec = self.get_file_internal(&name)?;
151 let bytes = PyBytes::new(py, &vec);
152 let io = py.import("io")?;
153 let bytesio_class = io.dict().get_item("BytesIO")?.unwrap();
154 let file = bytesio_class.call1((bytes,))?;
155 Ok(file.unbind())
156 }
157
190 fn get_stage(&self, py: Python, name: String) -> PyResult<Py<PyStage>> { 158 fn get_stage(&self, py: Python, name: String) -> PyResult<Py<PyStage>> {
191 let archive = self.known_files.get(&name).unwrap(); 159 let vec = self.get_file_internal(&name)?;
192 let mut archive = archive.borrow_mut(py); 160 let (_, inner) = stage::Stage::from_slice(&vec).unwrap();
193 let bytes = archive.get_file_internal(&name);
194 let (_, inner) = stage::Stage::from_slice(&bytes).unwrap();
195 Ok(Py::new(py, PyStage { inner })?) 161 Ok(Py::new(py, PyStage { inner })?)
196 } 162 }
197 } 163 }
198 164
199 #[pymodule] 165 #[pymodule]