diff --git a/app/storage.py b/app/storage.py index 3cf5ff6..6bf3a96 100644 --- a/app/storage.py +++ b/app/storage.py @@ -1905,6 +1905,8 @@ class ObjectStorage: def _read_index_entry(self, bucket_name: str, key: Path) -> Optional[Dict[str, Any]]: index_path, entry_name = self._index_file_for_key(bucket_name, key) + if _HAS_RUST: + return _rc.read_index_entry(str(index_path), entry_name) if not index_path.exists(): return None try: diff --git a/myfsio_core/Cargo.toml b/myfsio_core/Cargo.toml index 476d2be..5c9fa0e 100644 --- a/myfsio_core/Cargo.toml +++ b/myfsio_core/Cargo.toml @@ -14,6 +14,7 @@ sha2 = "0.10" md-5 = "0.10" hex = "0.4" unicode-normalization = "0.1" +serde_json = "1" regex = "1" lru = "0.14" parking_lot = "0.12" diff --git a/myfsio_core/src/lib.rs b/myfsio_core/src/lib.rs index 532f1a0..1321077 100644 --- a/myfsio_core/src/lib.rs +++ b/myfsio_core/src/lib.rs @@ -1,4 +1,5 @@ mod hashing; +mod metadata; mod sigv4; mod validation; @@ -25,6 +26,8 @@ mod myfsio_core { m.add_function(wrap_pyfunction!(validation::validate_object_key, m)?)?; m.add_function(wrap_pyfunction!(validation::validate_bucket_name, m)?)?; + m.add_function(wrap_pyfunction!(metadata::read_index_entry, m)?)?; + Ok(()) } } diff --git a/myfsio_core/src/metadata.rs b/myfsio_core/src/metadata.rs new file mode 100644 index 0000000..67d09f8 --- /dev/null +++ b/myfsio_core/src/metadata.rs @@ -0,0 +1,71 @@ +use pyo3::exceptions::PyValueError; +use pyo3::prelude::*; +use pyo3::types::{PyDict, PyList, PyString}; +use serde_json::Value; +use std::fs; + +const MAX_DEPTH: u32 = 64; + +fn value_to_py(py: Python<'_>, v: &Value, depth: u32) -> PyResult> { + if depth > MAX_DEPTH { + return Err(PyValueError::new_err("JSON nesting too deep")); + } + match v { + Value::Null => Ok(py.None()), + Value::Bool(b) => Ok((*b).into_pyobject(py)?.to_owned().into_any().unbind()), + Value::Number(n) => { + if let Some(i) = n.as_i64() { + Ok(i.into_pyobject(py)?.into_any().unbind()) + } else if let Some(f) = n.as_f64() { + Ok(f.into_pyobject(py)?.into_any().unbind()) + } else { + Ok(py.None()) + } + } + Value::String(s) => Ok(PyString::new(py, s).into_any().unbind()), + Value::Array(arr) => { + let list = PyList::empty(py); + for item in arr { + list.append(value_to_py(py, item, depth + 1)?)?; + } + Ok(list.into_any().unbind()) + } + Value::Object(map) => { + let dict = PyDict::new(py); + for (k, val) in map { + dict.set_item(k, value_to_py(py, val, depth + 1)?)?; + } + Ok(dict.into_any().unbind()) + } + } +} + +#[pyfunction] +pub fn read_index_entry( + py: Python<'_>, + path: &str, + entry_name: &str, +) -> PyResult>> { + let path_owned = path.to_owned(); + let entry_owned = entry_name.to_owned(); + + let entry: Option = py.detach(move || -> PyResult> { + let content = match fs::read_to_string(&path_owned) { + Ok(c) => c, + Err(_) => return Ok(None), + }; + let parsed: Value = match serde_json::from_str(&content) { + Ok(v) => v, + Err(_) => return Ok(None), + }; + match parsed { + Value::Object(mut map) => Ok(map.remove(&entry_owned)), + _ => Ok(None), + } + })?; + + match entry { + Some(val) => Ok(Some(value_to_py(py, &val, 0)?)), + None => Ok(None), + } +}