pykeramics 0.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pykeramics-0.0.0/Cargo.toml +17 -0
- pykeramics-0.0.0/MANIFEST.in +2 -0
- pykeramics-0.0.0/PKG-INFO +8 -0
- pykeramics-0.0.0/pyproject.toml +2 -0
- pykeramics-0.0.0/setup.cfg +16 -0
- pykeramics-0.0.0/setup.py +25 -0
- pykeramics-0.0.0/src/datetime.rs +113 -0
- pykeramics-0.0.0/src/lib.rs +34 -0
- pykeramics-0.0.0/src/pykeramics.egg-info/PKG-INFO +8 -0
- pykeramics-0.0.0/src/pykeramics.egg-info/SOURCES.txt +14 -0
- pykeramics-0.0.0/src/pykeramics.egg-info/dependency_links.txt +1 -0
- pykeramics-0.0.0/src/pykeramics.egg-info/top_level.txt +1 -0
- pykeramics-0.0.0/src/vfs.rs +296 -0
- pykeramics-0.0.0/tests/test_module.py +19 -0
- pykeramics-0.0.0/tests/test_vfs.py +64 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
authors.workspace = true
|
|
3
|
+
description = "Keramics Python bindings"
|
|
4
|
+
edition.workspace = true
|
|
5
|
+
license.workspace = true
|
|
6
|
+
name = "keramics-python"
|
|
7
|
+
repository.workspace = true
|
|
8
|
+
version.workspace = true
|
|
9
|
+
|
|
10
|
+
[lib]
|
|
11
|
+
name = "pykeramics"
|
|
12
|
+
crate-type = ["cdylib"]
|
|
13
|
+
|
|
14
|
+
[dependencies]
|
|
15
|
+
keramics-datetime = { version = "0.0.0", path = "../keramics-datetime" }
|
|
16
|
+
keramics-vfs = { version = "0.0.0", path = "../keramics-vfs" }
|
|
17
|
+
pyo3 = { version = "0.26.0", features = ["extension-module"] }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = pykeramics
|
|
3
|
+
version = 0.0.0
|
|
4
|
+
license = "Apache-2.0"
|
|
5
|
+
classifiers =
|
|
6
|
+
Development Status :: 2 - Pre-Alpha
|
|
7
|
+
Programming Language :: Python
|
|
8
|
+
Programming Language :: Rust
|
|
9
|
+
|
|
10
|
+
[options]
|
|
11
|
+
python_requires = >=3.8
|
|
12
|
+
|
|
13
|
+
[egg_info]
|
|
14
|
+
tag_build =
|
|
15
|
+
tag_date = 0
|
|
16
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright 2024-2025 Joachim Metz <joachim.metz@gmail.com>
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License. You may
|
|
5
|
+
# obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
9
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
10
|
+
# License for the specific language governing permissions and limitations
|
|
11
|
+
# under the License.
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
from setuptools import setup
|
|
16
|
+
from setuptools_rust import RustExtension
|
|
17
|
+
|
|
18
|
+
setup(
|
|
19
|
+
rust_extensions=[
|
|
20
|
+
RustExtension(
|
|
21
|
+
"pykeramics",
|
|
22
|
+
debug=os.environ.get("BUILD_DEBUG") == "1",
|
|
23
|
+
)
|
|
24
|
+
],
|
|
25
|
+
)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/* Copyright 2024-2025 Joachim Metz <joachim.metz@gmail.com>
|
|
2
|
+
*
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may
|
|
5
|
+
* obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
9
|
+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
10
|
+
* License for the specific language governing permissions and limitations
|
|
11
|
+
* under the License.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
use pyo3::exceptions::PyRuntimeError;
|
|
15
|
+
use pyo3::prelude::*;
|
|
16
|
+
use pyo3::types::{PyAny, PyNone};
|
|
17
|
+
|
|
18
|
+
use keramics_datetime::{DateTime, Filetime, PosixTime32, PosixTime64Ns};
|
|
19
|
+
|
|
20
|
+
pub struct PyDateTime {}
|
|
21
|
+
|
|
22
|
+
impl PyDateTime {
|
|
23
|
+
pub fn new(date_time: &DateTime) -> PyResult<Py<PyAny>> {
|
|
24
|
+
Python::attach(|py| -> PyResult<_> {
|
|
25
|
+
match date_time {
|
|
26
|
+
DateTime::Filetime(filetime) => {
|
|
27
|
+
let py_filetime: PyFiletime = PyFiletime {
|
|
28
|
+
filetime: filetime.clone(),
|
|
29
|
+
};
|
|
30
|
+
Ok(Py::new(py, py_filetime)?.into_any())
|
|
31
|
+
}
|
|
32
|
+
DateTime::NotSet => {
|
|
33
|
+
let py_none: Borrowed<'_, '_, PyNone> = PyNone::get(py);
|
|
34
|
+
Ok(py_none.to_owned().unbind().into_any())
|
|
35
|
+
}
|
|
36
|
+
DateTime::PosixTime32(posix_time) => {
|
|
37
|
+
let py_posix_time: PyPosixTime32 = PyPosixTime32 {
|
|
38
|
+
posix_time: posix_time.clone(),
|
|
39
|
+
};
|
|
40
|
+
Ok(Py::new(py, py_posix_time)?.into_any())
|
|
41
|
+
}
|
|
42
|
+
DateTime::PosixTime64Ns(posix_time) => {
|
|
43
|
+
let py_posix_time: PyPosixTime64Ns = PyPosixTime64Ns {
|
|
44
|
+
posix_time: posix_time.clone(),
|
|
45
|
+
};
|
|
46
|
+
Ok(Py::new(py, py_posix_time)?.into_any())
|
|
47
|
+
}
|
|
48
|
+
_ => {
|
|
49
|
+
todo!();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#[pyclass]
|
|
57
|
+
#[pyo3(name = "Filetime")]
|
|
58
|
+
#[derive(Clone)]
|
|
59
|
+
struct PyFiletime {
|
|
60
|
+
filetime: Filetime,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[pymethods]
|
|
64
|
+
impl PyFiletime {
|
|
65
|
+
#[getter]
|
|
66
|
+
pub fn timestamp(&self) -> PyResult<u64> {
|
|
67
|
+
Ok(self.filetime.timestamp)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[pyclass]
|
|
72
|
+
#[pyo3(name = "PosixTime32")]
|
|
73
|
+
#[derive(Clone)]
|
|
74
|
+
struct PyPosixTime32 {
|
|
75
|
+
posix_time: PosixTime32,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#[pymethods]
|
|
79
|
+
impl PyPosixTime32 {
|
|
80
|
+
#[getter]
|
|
81
|
+
pub fn timestamp(&self) -> PyResult<i32> {
|
|
82
|
+
Ok(self.posix_time.timestamp)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#[pyclass]
|
|
87
|
+
#[pyo3(name = "PosixTime64Ns")]
|
|
88
|
+
#[derive(Clone)]
|
|
89
|
+
struct PyPosixTime64Ns {
|
|
90
|
+
posix_time: PosixTime64Ns,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#[pymethods]
|
|
94
|
+
impl PyPosixTime64Ns {
|
|
95
|
+
#[getter]
|
|
96
|
+
pub fn fraction(&self) -> PyResult<u32> {
|
|
97
|
+
Ok(self.posix_time.fraction)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#[getter]
|
|
101
|
+
pub fn timestamp(&self) -> PyResult<i64> {
|
|
102
|
+
Ok(self.posix_time.timestamp)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#[pymodule]
|
|
107
|
+
pub fn datetime(module: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
108
|
+
module.add_class::<PyFiletime>()?;
|
|
109
|
+
module.add_class::<PyPosixTime32>()?;
|
|
110
|
+
module.add_class::<PyPosixTime64Ns>()?;
|
|
111
|
+
|
|
112
|
+
Ok(())
|
|
113
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* Copyright 2024-2025 Joachim Metz <joachim.metz@gmail.com>
|
|
2
|
+
*
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may
|
|
5
|
+
* obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
9
|
+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
10
|
+
* License for the specific language governing permissions and limitations
|
|
11
|
+
* under the License.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
use pyo3::prelude::*;
|
|
15
|
+
use pyo3::types::PyDict;
|
|
16
|
+
use pyo3::wrap_pymodule;
|
|
17
|
+
|
|
18
|
+
mod datetime;
|
|
19
|
+
mod vfs;
|
|
20
|
+
|
|
21
|
+
/// Keramics Python module.
|
|
22
|
+
#[pymodule]
|
|
23
|
+
fn pykeramics(python: Python<'_>, module: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
24
|
+
module.add("__version__", env!("CARGO_PKG_VERSION"))?;
|
|
25
|
+
module.add_wrapped(wrap_pymodule!(datetime::datetime))?;
|
|
26
|
+
module.add_wrapped(wrap_pymodule!(vfs::vfs))?;
|
|
27
|
+
|
|
28
|
+
let sys = PyModule::import(python, "sys")?;
|
|
29
|
+
let sys_modules: Bound<'_, PyDict> = sys.getattr("modules")?.downcast_into()?;
|
|
30
|
+
sys_modules.set_item("pykeramics.datetime", module.getattr("datetime")?)?;
|
|
31
|
+
sys_modules.set_item("pykeramics.vfs", module.getattr("vfs")?)?;
|
|
32
|
+
|
|
33
|
+
Ok(())
|
|
34
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Cargo.toml
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.cfg
|
|
5
|
+
setup.py
|
|
6
|
+
src/datetime.rs
|
|
7
|
+
src/lib.rs
|
|
8
|
+
src/vfs.rs
|
|
9
|
+
src/pykeramics.egg-info/PKG-INFO
|
|
10
|
+
src/pykeramics.egg-info/SOURCES.txt
|
|
11
|
+
src/pykeramics.egg-info/dependency_links.txt
|
|
12
|
+
src/pykeramics.egg-info/top_level.txt
|
|
13
|
+
tests/test_module.py
|
|
14
|
+
tests/test_vfs.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/* Copyright 2024-2025 Joachim Metz <joachim.metz@gmail.com>
|
|
2
|
+
*
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may
|
|
5
|
+
* obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
9
|
+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
10
|
+
* License for the specific language governing permissions and limitations
|
|
11
|
+
* under the License.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
use std::sync::Arc;
|
|
15
|
+
|
|
16
|
+
use pyo3::exceptions::PyRuntimeError;
|
|
17
|
+
use pyo3::prelude::*;
|
|
18
|
+
|
|
19
|
+
use keramics_vfs::{
|
|
20
|
+
VfsFileEntry, VfsFileSystemReference, VfsFileType, VfsLocation, VfsPath, VfsResolver,
|
|
21
|
+
VfsResolverReference, VfsString, VfsType,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
use super::datetime::PyDateTime;
|
|
25
|
+
|
|
26
|
+
#[pyclass]
|
|
27
|
+
#[pyo3(name = "VfsFileEntry")]
|
|
28
|
+
#[derive(Clone)]
|
|
29
|
+
struct PyVfsFileEntry {
|
|
30
|
+
file_entry: Arc<VfsFileEntry>,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[pymethods]
|
|
34
|
+
impl PyVfsFileEntry {
|
|
35
|
+
#[getter]
|
|
36
|
+
pub fn access_time(&self) -> PyResult<Option<Py<PyAny>>> {
|
|
37
|
+
match self.file_entry.get_access_time() {
|
|
38
|
+
Some(date_time) => Ok(Some(PyDateTime::new(date_time)?)),
|
|
39
|
+
None => Ok(None),
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[getter]
|
|
44
|
+
pub fn change_time(&self) -> PyResult<Option<Py<PyAny>>> {
|
|
45
|
+
match self.file_entry.get_change_time() {
|
|
46
|
+
Some(date_time) => Ok(Some(PyDateTime::new(date_time)?)),
|
|
47
|
+
None => Ok(None),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[getter]
|
|
52
|
+
pub fn creation_time(&self) -> PyResult<Option<Py<PyAny>>> {
|
|
53
|
+
match self.file_entry.get_creation_time() {
|
|
54
|
+
Some(date_time) => Ok(Some(PyDateTime::new(date_time)?)),
|
|
55
|
+
None => Ok(None),
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#[getter]
|
|
60
|
+
pub fn name(&self) -> PyResult<Option<PyVfsString>> {
|
|
61
|
+
match self.file_entry.get_name() {
|
|
62
|
+
Some(name) => Ok(Some(PyVfsString {
|
|
63
|
+
string: Arc::new(name),
|
|
64
|
+
})),
|
|
65
|
+
None => Ok(None),
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#[getter]
|
|
70
|
+
pub fn modification_time(&self) -> PyResult<Option<Py<PyAny>>> {
|
|
71
|
+
match self.file_entry.get_modification_time() {
|
|
72
|
+
Some(date_time) => Ok(Some(PyDateTime::new(date_time)?)),
|
|
73
|
+
None => Ok(None),
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#[pyclass]
|
|
79
|
+
#[pyo3(name = "VfsFileSystem")]
|
|
80
|
+
#[derive(Clone)]
|
|
81
|
+
struct PyVfsFileSystem {
|
|
82
|
+
file_system: VfsFileSystemReference,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#[pymethods]
|
|
86
|
+
impl PyVfsFileSystem {}
|
|
87
|
+
|
|
88
|
+
#[pyclass(eq)]
|
|
89
|
+
#[pyo3(name = "VfsFileType")]
|
|
90
|
+
#[derive(Clone, PartialEq)]
|
|
91
|
+
pub enum PyVfsFileType {
|
|
92
|
+
#[pyo3(name = "BLOCK_DEVICE")]
|
|
93
|
+
BlockDevice,
|
|
94
|
+
#[pyo3(name = "CHARACTER_DEVICE")]
|
|
95
|
+
CharacterDevice,
|
|
96
|
+
#[pyo3(name = "DEVICE")]
|
|
97
|
+
Device,
|
|
98
|
+
#[pyo3(name = "DIRECTORY")]
|
|
99
|
+
Directory,
|
|
100
|
+
#[pyo3(name = "FILE")]
|
|
101
|
+
File,
|
|
102
|
+
#[pyo3(name = "NAMED_PIPE")]
|
|
103
|
+
NamedPipe,
|
|
104
|
+
#[pyo3(name = "SOCKET")]
|
|
105
|
+
Socket,
|
|
106
|
+
#[pyo3(name = "SYMBOLIC_LINK")]
|
|
107
|
+
SymbolicLink,
|
|
108
|
+
#[pyo3(name = "WHITEOUT")]
|
|
109
|
+
Whiteout,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#[pyclass]
|
|
113
|
+
#[pyo3(name = "VfsLocation")]
|
|
114
|
+
#[derive(Clone)]
|
|
115
|
+
struct PyVfsLocation {
|
|
116
|
+
location: Arc<VfsLocation>,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#[pymethods]
|
|
120
|
+
impl PyVfsLocation {
|
|
121
|
+
#[new]
|
|
122
|
+
#[pyo3(signature = (path_type, path))]
|
|
123
|
+
pub fn new(path_type: PyVfsType, path: PyVfsPath) -> PyResult<Self> {
|
|
124
|
+
let vfs_type: VfsType = match &path_type {
|
|
125
|
+
PyVfsType::Apm => VfsType::Apm,
|
|
126
|
+
PyVfsType::Ext => VfsType::Ext,
|
|
127
|
+
PyVfsType::Ewf => VfsType::Ewf,
|
|
128
|
+
PyVfsType::Fake => VfsType::Fake,
|
|
129
|
+
PyVfsType::Gpt => VfsType::Gpt,
|
|
130
|
+
PyVfsType::Mbr => VfsType::Mbr,
|
|
131
|
+
PyVfsType::Os => VfsType::Os,
|
|
132
|
+
PyVfsType::Qcow => VfsType::Qcow,
|
|
133
|
+
PyVfsType::Vhd => VfsType::Vhd,
|
|
134
|
+
PyVfsType::Vhdx => VfsType::Vhdx,
|
|
135
|
+
};
|
|
136
|
+
let vfs_path: &VfsPath = path.path.as_ref();
|
|
137
|
+
Ok(Self {
|
|
138
|
+
location: Arc::new(VfsLocation::new_base(&vfs_type, vfs_path.clone())),
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
pub fn new_with_layer(&self, path_type: PyVfsType, path: PyVfsPath) -> PyResult<Self> {
|
|
143
|
+
let vfs_type: VfsType = match &path_type {
|
|
144
|
+
PyVfsType::Apm => VfsType::Apm,
|
|
145
|
+
PyVfsType::Ext => VfsType::Ext,
|
|
146
|
+
PyVfsType::Ewf => VfsType::Ewf,
|
|
147
|
+
PyVfsType::Fake => VfsType::Fake,
|
|
148
|
+
PyVfsType::Gpt => VfsType::Gpt,
|
|
149
|
+
PyVfsType::Mbr => VfsType::Mbr,
|
|
150
|
+
PyVfsType::Os => VfsType::Os,
|
|
151
|
+
PyVfsType::Qcow => VfsType::Qcow,
|
|
152
|
+
PyVfsType::Vhd => VfsType::Vhd,
|
|
153
|
+
PyVfsType::Vhdx => VfsType::Vhdx,
|
|
154
|
+
};
|
|
155
|
+
let vfs_path: &VfsPath = path.path.as_ref();
|
|
156
|
+
Ok(Self {
|
|
157
|
+
location: Arc::new(self.location.new_with_layer(&vfs_type, vfs_path.clone())),
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#[pyclass]
|
|
163
|
+
#[pyo3(name = "VfsResolver")]
|
|
164
|
+
#[derive(Clone)]
|
|
165
|
+
struct PyVfsResolver {
|
|
166
|
+
resolver: VfsResolverReference,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[pymethods]
|
|
170
|
+
impl PyVfsResolver {
|
|
171
|
+
#[new]
|
|
172
|
+
pub fn new() -> PyResult<Self> {
|
|
173
|
+
Ok(Self {
|
|
174
|
+
resolver: VfsResolver::current(),
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
pub fn get_file_entry_by_location(
|
|
179
|
+
&self,
|
|
180
|
+
location: PyVfsLocation,
|
|
181
|
+
) -> PyResult<Option<PyVfsFileEntry>> {
|
|
182
|
+
match self.resolver.get_file_entry_by_location(&location.location) {
|
|
183
|
+
Ok(result) => match result {
|
|
184
|
+
Some(file_entry) => Ok(Some(PyVfsFileEntry {
|
|
185
|
+
file_entry: Arc::new(file_entry),
|
|
186
|
+
})),
|
|
187
|
+
None => {
|
|
188
|
+
return Ok(None);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
Err(error) => {
|
|
192
|
+
return Err(PyErr::new::<PyRuntimeError, String>(format!(
|
|
193
|
+
"Unable to retrieve file entry with error: {}",
|
|
194
|
+
error.to_string()
|
|
195
|
+
)));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
pub fn open_file_system(&self, location: PyVfsLocation) -> PyResult<PyVfsFileSystem> {
|
|
201
|
+
match self.resolver.open_file_system(&location.location) {
|
|
202
|
+
Ok(file_system) => Ok(PyVfsFileSystem {
|
|
203
|
+
file_system: file_system,
|
|
204
|
+
}),
|
|
205
|
+
Err(error) => {
|
|
206
|
+
return Err(PyErr::new::<PyRuntimeError, String>(format!(
|
|
207
|
+
"Unable to open file system with error: {}",
|
|
208
|
+
error.to_string()
|
|
209
|
+
)));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#[pyclass]
|
|
216
|
+
#[pyo3(name = "VfsString")]
|
|
217
|
+
#[derive(Clone)]
|
|
218
|
+
struct PyVfsString {
|
|
219
|
+
string: Arc<VfsString>,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#[pymethods]
|
|
223
|
+
impl PyVfsString {
|
|
224
|
+
pub fn to_string(&self) -> String {
|
|
225
|
+
self.string.to_string()
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
#[pyclass]
|
|
230
|
+
#[pyo3(name = "VfsPath")]
|
|
231
|
+
#[derive(Clone)]
|
|
232
|
+
struct PyVfsPath {
|
|
233
|
+
path: Arc<VfsPath>,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
#[pymethods]
|
|
237
|
+
impl PyVfsPath {
|
|
238
|
+
#[new]
|
|
239
|
+
#[pyo3(signature = (path_type, path))]
|
|
240
|
+
pub fn new(path_type: PyVfsType, path: &str) -> PyResult<Self> {
|
|
241
|
+
let vfs_type: VfsType = match &path_type {
|
|
242
|
+
PyVfsType::Apm => VfsType::Apm,
|
|
243
|
+
PyVfsType::Ext => VfsType::Ext,
|
|
244
|
+
PyVfsType::Ewf => VfsType::Ewf,
|
|
245
|
+
PyVfsType::Fake => VfsType::Fake,
|
|
246
|
+
PyVfsType::Gpt => VfsType::Gpt,
|
|
247
|
+
PyVfsType::Mbr => VfsType::Mbr,
|
|
248
|
+
PyVfsType::Os => VfsType::Os,
|
|
249
|
+
PyVfsType::Qcow => VfsType::Qcow,
|
|
250
|
+
PyVfsType::Vhd => VfsType::Vhd,
|
|
251
|
+
PyVfsType::Vhdx => VfsType::Vhdx,
|
|
252
|
+
};
|
|
253
|
+
Ok(Self {
|
|
254
|
+
path: Arc::new(VfsPath::from_path(&vfs_type, path)),
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#[pyclass(eq)]
|
|
260
|
+
#[pyo3(name = "VfsType")]
|
|
261
|
+
#[derive(Clone, PartialEq)]
|
|
262
|
+
pub enum PyVfsType {
|
|
263
|
+
#[pyo3(name = "APM")]
|
|
264
|
+
Apm,
|
|
265
|
+
#[pyo3(name = "EXT")]
|
|
266
|
+
Ext,
|
|
267
|
+
#[pyo3(name = "EWF")]
|
|
268
|
+
Ewf,
|
|
269
|
+
#[pyo3(name = "FAKE")]
|
|
270
|
+
Fake,
|
|
271
|
+
#[pyo3(name = "GPT")]
|
|
272
|
+
Gpt,
|
|
273
|
+
#[pyo3(name = "MBR")]
|
|
274
|
+
Mbr,
|
|
275
|
+
#[pyo3(name = "OS")]
|
|
276
|
+
Os,
|
|
277
|
+
#[pyo3(name = "QCOW")]
|
|
278
|
+
Qcow,
|
|
279
|
+
#[pyo3(name = "VHD")]
|
|
280
|
+
Vhd,
|
|
281
|
+
#[pyo3(name = "VHDX")]
|
|
282
|
+
Vhdx,
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
#[pymodule]
|
|
286
|
+
pub fn vfs(module: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
287
|
+
module.add_class::<PyVfsFileEntry>()?;
|
|
288
|
+
module.add_class::<PyVfsFileSystem>()?;
|
|
289
|
+
module.add_class::<PyVfsLocation>()?;
|
|
290
|
+
module.add_class::<PyVfsPath>()?;
|
|
291
|
+
module.add_class::<PyVfsResolver>()?;
|
|
292
|
+
module.add_class::<PyVfsString>()?;
|
|
293
|
+
module.add_class::<PyVfsType>()?;
|
|
294
|
+
|
|
295
|
+
Ok(())
|
|
296
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
# Copyright 2024-2025 Joachim Metz <joachim.metz@gmail.com>
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License. You may
|
|
7
|
+
# obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
11
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
12
|
+
# License for the specific language governing permissions and limitations
|
|
13
|
+
# under the License.
|
|
14
|
+
|
|
15
|
+
import pykeramics
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_version() -> None:
|
|
19
|
+
assert pykeramics.__version__ == "0.0.0"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
# Copyright 2024-2025 Joachim Metz <joachim.metz@gmail.com>
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License. You may
|
|
7
|
+
# obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
11
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
12
|
+
# License for the specific language governing permissions and limitations
|
|
13
|
+
# under the License.
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from pykeramics import datetime
|
|
18
|
+
from pykeramics import vfs
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_get_file_entry_by_location() -> None:
|
|
22
|
+
resolver = vfs.VfsResolver()
|
|
23
|
+
|
|
24
|
+
os_location = vfs.VfsLocation(
|
|
25
|
+
vfs.VfsType.OS, vfs.VfsPath(vfs.VfsType.OS, "../test_data/qcow/ext2.qcow2")
|
|
26
|
+
)
|
|
27
|
+
qcow_location = os_location.new_with_layer(
|
|
28
|
+
vfs.VfsType.QCOW, vfs.VfsPath(vfs.VfsType.QCOW, "/qcow1")
|
|
29
|
+
)
|
|
30
|
+
ext_location = qcow_location.new_with_layer(
|
|
31
|
+
vfs.VfsType.EXT, vfs.VfsPath(vfs.VfsType.EXT, "/testdir1/testfile1")
|
|
32
|
+
)
|
|
33
|
+
file_entry = resolver.get_file_entry_by_location(ext_location)
|
|
34
|
+
|
|
35
|
+
assert file_entry is not None
|
|
36
|
+
assert file_entry.name.to_string() == "testfile1"
|
|
37
|
+
assert file_entry.access_time.timestamp == 1735977482
|
|
38
|
+
assert file_entry.change_time.timestamp == 1735977481
|
|
39
|
+
assert file_entry.creation_time is None
|
|
40
|
+
assert file_entry.modification_time.timestamp == 1735977481
|
|
41
|
+
|
|
42
|
+
ext_location = qcow_location.new_with_layer(
|
|
43
|
+
vfs.VfsType.EXT, vfs.VfsPath(vfs.VfsType.EXT, "/bogus")
|
|
44
|
+
)
|
|
45
|
+
file_entry = resolver.get_file_entry_by_location(ext_location)
|
|
46
|
+
|
|
47
|
+
assert file_entry is None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_open_file_system() -> None:
|
|
51
|
+
resolver = vfs.VfsResolver()
|
|
52
|
+
|
|
53
|
+
os_location = vfs.VfsLocation(
|
|
54
|
+
vfs.VfsType.OS, vfs.VfsPath(vfs.VfsType.OS, "../test_data/qcow/ext2.qcow2")
|
|
55
|
+
)
|
|
56
|
+
qcow_location = os_location.new_with_layer(
|
|
57
|
+
vfs.VfsType.QCOW, vfs.VfsPath(vfs.VfsType.QCOW, "/qcow1")
|
|
58
|
+
)
|
|
59
|
+
ext_location = qcow_location.new_with_layer(
|
|
60
|
+
vfs.VfsType.EXT, vfs.VfsPath(vfs.VfsType.EXT, "/testdir1/testfile1")
|
|
61
|
+
)
|
|
62
|
+
file_system = resolver.open_file_system(ext_location)
|
|
63
|
+
|
|
64
|
+
assert file_system is not None
|