Mercurial > touhou
comparison python/src/lib.rs @ 778:816e1f01d650
Partially replace the Loader with a Rust one
| author | Link Mauve <linkmauve@linkmauve.fr> |
|---|---|
| date | Sat, 08 Nov 2025 18:26:01 +0100 |
| parents | 28d8b892fd06 |
| children | ee09657d3789 |
comparison
equal
deleted
inserted
replaced
| 777:11249e4b4e03 | 778:816e1f01d650 |
|---|---|
| 1 use pyo3::exceptions::PyIOError; | |
| 1 use pyo3::prelude::*; | 2 use pyo3::prelude::*; |
| 2 use pyo3::types::PyBytes; | 3 use pyo3::types::PyBytes; |
| 3 use touhou_formats::th06::pbg3; | 4 use touhou_formats::th06::pbg3; |
| 5 use std::collections::HashMap; | |
| 4 use std::fs::File; | 6 use std::fs::File; |
| 5 use std::io::BufReader; | 7 use std::io::BufReader; |
| 8 use std::path::PathBuf; | |
| 6 | 9 |
| 7 #[cfg(feature = "glide")] | 10 #[cfg(feature = "glide")] |
| 8 mod glide; | 11 mod glide; |
| 9 | 12 |
| 10 #[pyclass] | 13 #[pyclass(module = "libtouhou")] |
| 11 struct PBG3 { | 14 struct PBG3 { |
| 15 filename: PathBuf, | |
| 12 inner: pbg3::PBG3<BufReader<File>>, | 16 inner: pbg3::PBG3<BufReader<File>>, |
| 17 } | |
| 18 | |
| 19 impl PBG3 { | |
| 20 fn get_file_internal(&mut self, name: &str) -> Vec<u8> { | |
| 21 self.inner.get_file(name, true).unwrap() | |
| 22 } | |
| 13 } | 23 } |
| 14 | 24 |
| 15 #[pymethods] | 25 #[pymethods] |
| 16 impl PBG3 { | 26 impl PBG3 { |
| 17 #[staticmethod] | 27 #[staticmethod] |
| 18 fn from_filename(filename: &str) -> PyResult<PBG3> { | 28 fn from_filename(filename: PathBuf) -> PyResult<PBG3> { |
| 19 let inner = pbg3::from_path_buffered(filename)?; | 29 let inner = pbg3::from_path_buffered(&filename)?; |
| 20 Ok(PBG3 { | 30 Ok(PBG3 { |
| 31 filename, | |
| 21 inner | 32 inner |
| 22 }) | 33 }) |
| 34 } | |
| 35 | |
| 36 fn __repr__(&self) -> String { | |
| 37 format!("PBG3({})", self.filename.to_str().unwrap()) | |
| 23 } | 38 } |
| 24 | 39 |
| 25 #[getter] | 40 #[getter] |
| 26 fn file_list(&self) -> Vec<String> { | 41 fn file_list(&self) -> Vec<String> { |
| 27 self.inner.list_files().cloned().collect() | 42 self.inner.list_files().cloned().collect() |
| 29 | 44 |
| 30 fn list_files(&self) -> Vec<String> { | 45 fn list_files(&self) -> Vec<String> { |
| 31 self.inner.list_files().cloned().collect() | 46 self.inner.list_files().cloned().collect() |
| 32 } | 47 } |
| 33 | 48 |
| 34 fn get_file(&mut self, py: Python, name: &str) -> Py<PyAny> { | 49 fn get_file(&mut self, py: Python, name: &str) -> Py<PyBytes> { |
| 35 let data = self.inner.get_file(name, true).unwrap(); | 50 let data = self.get_file_internal(name); |
| 36 PyBytes::new(py, &data).into() | 51 PyBytes::new(py, &data).into() |
| 52 } | |
| 53 } | |
| 54 | |
| 55 /// A loader for Touhou files. | |
| 56 #[pyclass(module = "libtouhou", get_all, subclass)] | |
| 57 #[derive(Default)] | |
| 58 struct Loader { | |
| 59 /// The file names to the possible executable. | |
| 60 exe_files: Vec<PathBuf>, | |
| 61 | |
| 62 /// The path to the game directory. | |
| 63 game_dir: Option<PathBuf>, | |
| 64 | |
| 65 /// A map from inner filenames to the archive containing them. | |
| 66 known_files: HashMap<String, Py<PBG3>>, | |
| 67 } | |
| 68 | |
| 69 #[pymethods] | |
| 70 impl Loader { | |
| 71 /// Create a new Loader for the given game_dir. | |
| 72 #[new] | |
| 73 fn new(game_dir: Option<PathBuf>) -> Loader { | |
| 74 Loader { | |
| 75 exe_files: Vec::new(), | |
| 76 game_dir, | |
| 77 known_files: HashMap::new(), | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 /// Scan the game_dir for archives. | |
| 82 /// | |
| 83 /// paths_lists is a list of ':'-separated glob patterns, the first matching file will be used | |
| 84 /// and the other ones ignored. | |
| 85 fn scan_archives(&mut self, py: Python, paths_lists: Vec<String>) -> PyResult<()> { | |
| 86 for paths in paths_lists.iter() { | |
| 87 let found_paths: Vec<_> = paths.split(':').map(|path| { | |
| 88 glob::glob(if let Some(game_dir) = self.game_dir.as_ref() { | |
| 89 game_dir.join(path) | |
| 90 } else { | |
| 91 PathBuf::from(path) | |
| 92 }.to_str().unwrap()).unwrap() | |
| 93 }).flatten().map(Result::unwrap).map(PathBuf::from).collect(); | |
| 94 if found_paths.is_empty() { | |
| 95 return Err(PyIOError::new_err(format!("No path found for {paths:?}"))); | |
| 96 } | |
| 97 let path = &found_paths[0]; | |
| 98 if let Some(extension) = path.extension() && extension == "exe" { | |
| 99 self.exe_files.extend(found_paths); | |
| 100 } else { | |
| 101 let pbg3 = PBG3::from_filename(path.to_owned())?; | |
| 102 let filenames = pbg3.list_files(); | |
| 103 let pbg3 = Py::new(py, pbg3)?; | |
| 104 for name in filenames { | |
| 105 self.known_files.insert(name.clone(), Py::clone_ref(&pbg3, py)); | |
| 106 } | |
| 107 } | |
| 108 } | |
| 109 Ok(()) | |
| 110 } | |
| 111 | |
| 112 /// Return the given file as an io.BytesIO object. | |
| 113 fn get_file(&self, py: Python, name: String) -> PyResult<Py<PyAny>> { | |
| 114 let archive = self.known_files.get(&name).unwrap(); | |
| 115 let mut archive = archive.borrow_mut(py); | |
| 116 let bytes = archive.get_file(py, &name); | |
| 117 let io = py.import("io")?; | |
| 118 let bytesio_class = io.dict().get_item("BytesIO")?.unwrap(); | |
| 119 let file = bytesio_class.call1((bytes,))?; | |
| 120 Ok(file.unbind()) | |
| 37 } | 121 } |
| 38 } | 122 } |
| 39 | 123 |
| 40 #[pymodule] | 124 #[pymodule] |
| 41 mod libtouhou { | 125 mod libtouhou { |
| 42 #[pymodule_export] | 126 #[pymodule_export] |
| 43 use super::PBG3; | 127 use super::Loader; |
| 44 | 128 |
| 45 #[cfg(feature = "glide")] | 129 #[cfg(feature = "glide")] |
| 46 #[pymodule_export] | 130 #[pymodule_export] |
| 47 use super::glide::module; | 131 use super::glide::module; |
| 48 } | 132 } |
