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 }