gemstone-py-native 0.1.1__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.
- gemstone_py_native-0.1.1/Cargo.lock +156 -0
- gemstone_py_native-0.1.1/Cargo.toml +18 -0
- gemstone_py_native-0.1.1/PKG-INFO +55 -0
- gemstone_py_native-0.1.1/README.md +30 -0
- gemstone_py_native-0.1.1/pyproject.toml +38 -0
- gemstone_py_native-0.1.1/python/gemstone_py_native/__init__.py +5 -0
- gemstone_py_native-0.1.1/src/lib.rs +706 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "cfg-if"
|
|
7
|
+
version = "1.0.4"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "gemstone-py-native"
|
|
13
|
+
version = "0.1.1"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"libloading",
|
|
16
|
+
"pyo3",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[[package]]
|
|
20
|
+
name = "heck"
|
|
21
|
+
version = "0.5.0"
|
|
22
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
23
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
24
|
+
|
|
25
|
+
[[package]]
|
|
26
|
+
name = "libc"
|
|
27
|
+
version = "0.2.186"
|
|
28
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
29
|
+
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
|
30
|
+
|
|
31
|
+
[[package]]
|
|
32
|
+
name = "libloading"
|
|
33
|
+
version = "0.8.9"
|
|
34
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
35
|
+
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
|
36
|
+
dependencies = [
|
|
37
|
+
"cfg-if",
|
|
38
|
+
"windows-link",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[[package]]
|
|
42
|
+
name = "once_cell"
|
|
43
|
+
version = "1.21.4"
|
|
44
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
45
|
+
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
46
|
+
|
|
47
|
+
[[package]]
|
|
48
|
+
name = "portable-atomic"
|
|
49
|
+
version = "1.13.1"
|
|
50
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
51
|
+
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
|
52
|
+
|
|
53
|
+
[[package]]
|
|
54
|
+
name = "proc-macro2"
|
|
55
|
+
version = "1.0.106"
|
|
56
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
57
|
+
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
58
|
+
dependencies = [
|
|
59
|
+
"unicode-ident",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[[package]]
|
|
63
|
+
name = "pyo3"
|
|
64
|
+
version = "0.28.3"
|
|
65
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
66
|
+
checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12"
|
|
67
|
+
dependencies = [
|
|
68
|
+
"libc",
|
|
69
|
+
"once_cell",
|
|
70
|
+
"portable-atomic",
|
|
71
|
+
"pyo3-build-config",
|
|
72
|
+
"pyo3-ffi",
|
|
73
|
+
"pyo3-macros",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
[[package]]
|
|
77
|
+
name = "pyo3-build-config"
|
|
78
|
+
version = "0.28.3"
|
|
79
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
80
|
+
checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e"
|
|
81
|
+
dependencies = [
|
|
82
|
+
"target-lexicon",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
[[package]]
|
|
86
|
+
name = "pyo3-ffi"
|
|
87
|
+
version = "0.28.3"
|
|
88
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
89
|
+
checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e"
|
|
90
|
+
dependencies = [
|
|
91
|
+
"libc",
|
|
92
|
+
"pyo3-build-config",
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[[package]]
|
|
96
|
+
name = "pyo3-macros"
|
|
97
|
+
version = "0.28.3"
|
|
98
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
99
|
+
checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813"
|
|
100
|
+
dependencies = [
|
|
101
|
+
"proc-macro2",
|
|
102
|
+
"pyo3-macros-backend",
|
|
103
|
+
"quote",
|
|
104
|
+
"syn",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
[[package]]
|
|
108
|
+
name = "pyo3-macros-backend"
|
|
109
|
+
version = "0.28.3"
|
|
110
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
111
|
+
checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb"
|
|
112
|
+
dependencies = [
|
|
113
|
+
"heck",
|
|
114
|
+
"proc-macro2",
|
|
115
|
+
"pyo3-build-config",
|
|
116
|
+
"quote",
|
|
117
|
+
"syn",
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
[[package]]
|
|
121
|
+
name = "quote"
|
|
122
|
+
version = "1.0.45"
|
|
123
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
124
|
+
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
|
125
|
+
dependencies = [
|
|
126
|
+
"proc-macro2",
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
[[package]]
|
|
130
|
+
name = "syn"
|
|
131
|
+
version = "2.0.117"
|
|
132
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
133
|
+
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
|
134
|
+
dependencies = [
|
|
135
|
+
"proc-macro2",
|
|
136
|
+
"quote",
|
|
137
|
+
"unicode-ident",
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
[[package]]
|
|
141
|
+
name = "target-lexicon"
|
|
142
|
+
version = "0.13.5"
|
|
143
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
144
|
+
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
145
|
+
|
|
146
|
+
[[package]]
|
|
147
|
+
name = "unicode-ident"
|
|
148
|
+
version = "1.0.24"
|
|
149
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
150
|
+
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
151
|
+
|
|
152
|
+
[[package]]
|
|
153
|
+
name = "windows-link"
|
|
154
|
+
version = "0.2.1"
|
|
155
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
156
|
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "gemstone-py-native"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
description = "Optional PyO3 native fast path for gemstone-py"
|
|
7
|
+
homepage = "https://github.com/unicompute/gemstone-py"
|
|
8
|
+
repository = "https://github.com/unicompute/gemstone-py"
|
|
9
|
+
documentation = "https://github.com/unicompute/gemstone-py/tree/main/gemstone-py-native"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
|
|
12
|
+
[lib]
|
|
13
|
+
name = "gemstone_py_native_gci"
|
|
14
|
+
crate-type = ["cdylib"]
|
|
15
|
+
|
|
16
|
+
[dependencies]
|
|
17
|
+
libloading = "0.8"
|
|
18
|
+
pyo3 = { version = "0.28", features = ["abi3-py311", "extension-module"] }
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gemstone-py-native
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Classifier: Development Status :: 4 - Beta
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Operating System :: MacOS
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Rust
|
|
13
|
+
Classifier: Topic :: Database
|
|
14
|
+
Requires-Dist: gemstone-py>=0.2.6
|
|
15
|
+
Summary: Optional PyO3 native fast path for gemstone-py.
|
|
16
|
+
Home-Page: https://github.com/unicompute/gemstone-py
|
|
17
|
+
Author: gemstone-py contributors
|
|
18
|
+
License-Expression: MIT
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
21
|
+
Project-URL: Homepage, https://github.com/unicompute/gemstone-py
|
|
22
|
+
Project-URL: Issues, https://github.com/unicompute/gemstone-py/issues
|
|
23
|
+
Project-URL: Repository, https://github.com/unicompute/gemstone-py
|
|
24
|
+
|
|
25
|
+
# gemstone-py-native
|
|
26
|
+
|
|
27
|
+
Optional PyO3 extension for `gemstone-py`.
|
|
28
|
+
|
|
29
|
+
The extension exports `gemstone_py_native._gci`, which matches the Python
|
|
30
|
+
`gemstone_py._gci` shim surface. It loads `libgcirpc` with `libloading`,
|
|
31
|
+
exposes a `NativeGciLibrary` object with Rust-backed GCI methods, releases the
|
|
32
|
+
GIL around blocking GCI calls, and replaces hot OOP tag helpers with native
|
|
33
|
+
implementations. Wheels are built with the Python 3.11 stable ABI.
|
|
34
|
+
|
|
35
|
+
Build locally:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
python -m pip install maturin
|
|
39
|
+
cd gemstone-py-native
|
|
40
|
+
maturin develop
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Package wheels:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cd gemstone-py-native
|
|
47
|
+
maturin build --release
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The repository workflow `Native Wheels` builds platform wheels for Linux x86_64,
|
|
51
|
+
Linux aarch64, Linux ARMv7, macOS x86_64, macOS aarch64, Windows x86_64, and Windows ARM64.
|
|
52
|
+
Manual workflow runs can publish the merged wheel set to TestPyPI or PyPI using trusted
|
|
53
|
+
publishing. The workflow also builds the generated native sdist back into a
|
|
54
|
+
wheel before uploading it, so missing source files fail before publish.
|
|
55
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# gemstone-py-native
|
|
2
|
+
|
|
3
|
+
Optional PyO3 extension for `gemstone-py`.
|
|
4
|
+
|
|
5
|
+
The extension exports `gemstone_py_native._gci`, which matches the Python
|
|
6
|
+
`gemstone_py._gci` shim surface. It loads `libgcirpc` with `libloading`,
|
|
7
|
+
exposes a `NativeGciLibrary` object with Rust-backed GCI methods, releases the
|
|
8
|
+
GIL around blocking GCI calls, and replaces hot OOP tag helpers with native
|
|
9
|
+
implementations. Wheels are built with the Python 3.11 stable ABI.
|
|
10
|
+
|
|
11
|
+
Build locally:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
python -m pip install maturin
|
|
15
|
+
cd gemstone-py-native
|
|
16
|
+
maturin develop
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Package wheels:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd gemstone-py-native
|
|
23
|
+
maturin build --release
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The repository workflow `Native Wheels` builds platform wheels for Linux x86_64,
|
|
27
|
+
Linux aarch64, Linux ARMv7, macOS x86_64, macOS aarch64, Windows x86_64, and Windows ARM64.
|
|
28
|
+
Manual workflow runs can publish the merged wheel set to TestPyPI or PyPI using trusted
|
|
29
|
+
publishing. The workflow also builds the generated native sdist back into a
|
|
30
|
+
wheel before uploading it, so missing source files fail before publish.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["maturin>=1.7,<2"]
|
|
3
|
+
build-backend = "maturin"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gemstone-py-native"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Optional PyO3 native fast path for gemstone-py."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
authors = [{name = "gemstone-py contributors"}]
|
|
12
|
+
license = "MIT"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: MacOS",
|
|
18
|
+
"Operating System :: Microsoft :: Windows",
|
|
19
|
+
"Operating System :: POSIX :: Linux",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Rust",
|
|
23
|
+
"Topic :: Database",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"gemstone-py>=0.2.6",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/unicompute/gemstone-py"
|
|
31
|
+
Repository = "https://github.com/unicompute/gemstone-py"
|
|
32
|
+
Issues = "https://github.com/unicompute/gemstone-py/issues"
|
|
33
|
+
|
|
34
|
+
[tool.maturin]
|
|
35
|
+
features = ["pyo3/abi3-py311", "pyo3/extension-module"]
|
|
36
|
+
module-name = "gemstone_py_native._gci"
|
|
37
|
+
python-source = "python"
|
|
38
|
+
strip = true
|
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
use libloading::{Library, Symbol};
|
|
2
|
+
use pyo3::exceptions::{PyOSError, PyTypeError, PyValueError};
|
|
3
|
+
use pyo3::prelude::*;
|
|
4
|
+
use pyo3::types::{PyAny, PyList, PyModule};
|
|
5
|
+
use pyo3::Bound;
|
|
6
|
+
use std::env;
|
|
7
|
+
use std::ffi::{c_char, c_double, c_int, c_uint, c_void, CString};
|
|
8
|
+
use std::fs;
|
|
9
|
+
use std::path::{Path, PathBuf};
|
|
10
|
+
use std::sync::{Arc, Mutex, OnceLock};
|
|
11
|
+
|
|
12
|
+
const OOP_ILLEGAL: u64 = 0x01;
|
|
13
|
+
const OOP_NIL: u64 = 0x14;
|
|
14
|
+
const OOP_FALSE: u64 = 0x0C;
|
|
15
|
+
const OOP_TRUE: u64 = 0x10C;
|
|
16
|
+
const OOP_ASCII_NUL: u64 = 0x1C;
|
|
17
|
+
const GCI_INVALID_SESSION: u64 = 0;
|
|
18
|
+
const GCI_ENCRYPT_BUF_SIZE: u64 = 1024;
|
|
19
|
+
const GCI_LOGIN_PW_ENCRYPTED: u64 = 0x1;
|
|
20
|
+
const GCI_LOGIN_IS_GCSTS: u64 = 0x2;
|
|
21
|
+
const GCI_ERR_STR_SIZE: u64 = 1024;
|
|
22
|
+
const GCI_MAX_ERR_ARGS: u64 = 10;
|
|
23
|
+
|
|
24
|
+
const TAG_SMALLINT: u64 = 0x2;
|
|
25
|
+
const TAG_SMALLDOUBLE: u64 = 0x6;
|
|
26
|
+
const TAG_SPECIAL: u64 = 0x4;
|
|
27
|
+
const SMALLINT_SHIFT: i32 = 3;
|
|
28
|
+
const CHAR_TAG_BYTE: u64 = 0x1C;
|
|
29
|
+
|
|
30
|
+
static LOADED_LIBRARIES: OnceLock<Mutex<Vec<Arc<Library>>>> = OnceLock::new();
|
|
31
|
+
|
|
32
|
+
#[pyclass]
|
|
33
|
+
struct NativeGciLibrary {
|
|
34
|
+
library: Arc<Library>,
|
|
35
|
+
path: PathBuf,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#[pymethods]
|
|
39
|
+
impl NativeGciLibrary {
|
|
40
|
+
#[getter]
|
|
41
|
+
fn path(&self) -> String {
|
|
42
|
+
self.path.display().to_string()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#[pyo3(name = "GciInit")]
|
|
46
|
+
fn gci_init(&self, py: Python<'_>) -> PyResult<c_int> {
|
|
47
|
+
let init: Symbol<unsafe extern "C" fn() -> c_int> = self.symbol(b"GciInit")?;
|
|
48
|
+
Ok(py.detach(|| unsafe { init() }))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[pyo3(name = "GciSetNet")]
|
|
52
|
+
fn gci_set_net(
|
|
53
|
+
&self,
|
|
54
|
+
py: Python<'_>,
|
|
55
|
+
stone_nrs: &Bound<'_, PyAny>,
|
|
56
|
+
host_username: &Bound<'_, PyAny>,
|
|
57
|
+
encrypted_host_password: &Bound<'_, PyAny>,
|
|
58
|
+
gem_service: &Bound<'_, PyAny>,
|
|
59
|
+
) -> PyResult<()> {
|
|
60
|
+
let stone_nrs = py_cstring(stone_nrs)?;
|
|
61
|
+
let host_username = py_cstring(host_username)?;
|
|
62
|
+
let gem_service = py_cstring(gem_service)?;
|
|
63
|
+
let encrypted_host_password = ctypes_address(py, encrypted_host_password)?;
|
|
64
|
+
let set_net: Symbol<
|
|
65
|
+
unsafe extern "C" fn(*const c_char, *const c_char, *const c_char, *const c_char),
|
|
66
|
+
> = self.symbol(b"GciSetNet")?;
|
|
67
|
+
py.detach(|| unsafe {
|
|
68
|
+
set_net(
|
|
69
|
+
stone_nrs.as_ptr(),
|
|
70
|
+
host_username.as_ptr(),
|
|
71
|
+
encrypted_host_password as *const c_char,
|
|
72
|
+
gem_service.as_ptr(),
|
|
73
|
+
)
|
|
74
|
+
});
|
|
75
|
+
Ok(())
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#[pyo3(name = "GciEncrypt")]
|
|
79
|
+
fn gci_encrypt(
|
|
80
|
+
&self,
|
|
81
|
+
py: Python<'_>,
|
|
82
|
+
password: &Bound<'_, PyAny>,
|
|
83
|
+
buffer: &Bound<'_, PyAny>,
|
|
84
|
+
buffer_size: &Bound<'_, PyAny>,
|
|
85
|
+
) -> PyResult<()> {
|
|
86
|
+
let password = py_cstring(password)?;
|
|
87
|
+
let buffer = ctypes_address(py, buffer)?;
|
|
88
|
+
let buffer_size = py_u32(buffer_size)?;
|
|
89
|
+
let encrypt: Symbol<
|
|
90
|
+
unsafe extern "C" fn(*const c_char, *mut c_char, c_uint) -> *mut c_char,
|
|
91
|
+
> = self.symbol(b"GciEncrypt")?;
|
|
92
|
+
py.detach(|| unsafe {
|
|
93
|
+
encrypt(password.as_ptr(), buffer as *mut c_char, buffer_size);
|
|
94
|
+
});
|
|
95
|
+
Ok(())
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#[pyo3(name = "GciLoginEx")]
|
|
99
|
+
fn gci_login_ex(
|
|
100
|
+
&self,
|
|
101
|
+
py: Python<'_>,
|
|
102
|
+
username: &Bound<'_, PyAny>,
|
|
103
|
+
password: &Bound<'_, PyAny>,
|
|
104
|
+
flags: &Bound<'_, PyAny>,
|
|
105
|
+
halt_on_error: &Bound<'_, PyAny>,
|
|
106
|
+
) -> PyResult<c_int> {
|
|
107
|
+
let username = py_cstring(username)?;
|
|
108
|
+
let password = py_cstring(password)?;
|
|
109
|
+
let flags = py_u32(flags)?;
|
|
110
|
+
let halt_on_error = py_i32(halt_on_error)?;
|
|
111
|
+
let login: Symbol<
|
|
112
|
+
unsafe extern "C" fn(*const c_char, *const c_char, c_uint, c_int) -> c_int,
|
|
113
|
+
> = self.symbol(b"GciLoginEx")?;
|
|
114
|
+
Ok(py.detach(|| unsafe {
|
|
115
|
+
login(username.as_ptr(), password.as_ptr(), flags, halt_on_error)
|
|
116
|
+
}))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#[pyo3(name = "GciLogout")]
|
|
120
|
+
fn gci_logout(&self, py: Python<'_>) -> PyResult<c_int> {
|
|
121
|
+
let logout: Symbol<unsafe extern "C" fn() -> c_int> = self.symbol(b"GciLogout")?;
|
|
122
|
+
Ok(py.detach(|| unsafe { logout() }))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#[pyo3(name = "GciCommit")]
|
|
126
|
+
fn gci_commit(&self, py: Python<'_>, err: &Bound<'_, PyAny>) -> PyResult<c_int> {
|
|
127
|
+
let err = ctypes_address(py, err)?;
|
|
128
|
+
let commit: Symbol<unsafe extern "C" fn(*mut c_void) -> c_int> =
|
|
129
|
+
self.symbol(b"GciCommit")?;
|
|
130
|
+
Ok(py.detach(|| unsafe { commit(err as *mut c_void) }))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#[pyo3(name = "GciAbort")]
|
|
134
|
+
fn gci_abort(&self, py: Python<'_>, err: &Bound<'_, PyAny>) -> PyResult<c_int> {
|
|
135
|
+
let err = ctypes_address(py, err)?;
|
|
136
|
+
let abort: Symbol<unsafe extern "C" fn(*mut c_void) -> c_int> = self.symbol(b"GciAbort")?;
|
|
137
|
+
Ok(py.detach(|| unsafe { abort(err as *mut c_void) }))
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[pyo3(name = "GciErr")]
|
|
141
|
+
fn gci_err(&self, py: Python<'_>, err: &Bound<'_, PyAny>) -> PyResult<c_int> {
|
|
142
|
+
let err = ctypes_address(py, err)?;
|
|
143
|
+
let gci_err: Symbol<unsafe extern "C" fn(*mut c_void) -> c_int> = self.symbol(b"GciErr")?;
|
|
144
|
+
Ok(py.detach(|| unsafe { gci_err(err as *mut c_void) }))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#[pyo3(name = "GciExecuteStr")]
|
|
148
|
+
fn gci_execute_str(
|
|
149
|
+
&self,
|
|
150
|
+
py: Python<'_>,
|
|
151
|
+
source: &Bound<'_, PyAny>,
|
|
152
|
+
receiver: &Bound<'_, PyAny>,
|
|
153
|
+
) -> PyResult<u64> {
|
|
154
|
+
let source = py_cstring(source)?;
|
|
155
|
+
let receiver = py_u64(receiver)?;
|
|
156
|
+
let execute: Symbol<unsafe extern "C" fn(*const c_char, u64) -> u64> =
|
|
157
|
+
self.symbol(b"GciExecuteStr")?;
|
|
158
|
+
Ok(py.detach(|| unsafe { execute(source.as_ptr(), receiver) }))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[pyo3(name = "GciNewString")]
|
|
162
|
+
fn gci_new_string(&self, py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<u64> {
|
|
163
|
+
let value = py_cstring(value)?;
|
|
164
|
+
let new_string: Symbol<unsafe extern "C" fn(*const c_char) -> u64> =
|
|
165
|
+
self.symbol(b"GciNewString")?;
|
|
166
|
+
Ok(py.detach(|| unsafe { new_string(value.as_ptr()) }))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[pyo3(name = "GciNewSymbol")]
|
|
170
|
+
fn gci_new_symbol(&self, py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<u64> {
|
|
171
|
+
let value = py_cstring(value)?;
|
|
172
|
+
let new_symbol: Symbol<unsafe extern "C" fn(*const c_char) -> u64> =
|
|
173
|
+
self.symbol(b"GciNewSymbol")?;
|
|
174
|
+
Ok(py.detach(|| unsafe { new_symbol(value.as_ptr()) }))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#[pyo3(name = "GciFltToOop")]
|
|
178
|
+
fn gci_flt_to_oop(&self, py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<u64> {
|
|
179
|
+
let value = py_f64(value)?;
|
|
180
|
+
let flt_to_oop: Symbol<unsafe extern "C" fn(c_double) -> u64> =
|
|
181
|
+
self.symbol(b"GciFltToOop")?;
|
|
182
|
+
Ok(py.detach(|| unsafe { flt_to_oop(value) }))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#[pyo3(name = "GciOopToFlt_")]
|
|
186
|
+
fn gci_oop_to_flt(
|
|
187
|
+
&self,
|
|
188
|
+
py: Python<'_>,
|
|
189
|
+
oop: &Bound<'_, PyAny>,
|
|
190
|
+
value: &Bound<'_, PyAny>,
|
|
191
|
+
) -> PyResult<c_int> {
|
|
192
|
+
let oop = py_u64(oop)?;
|
|
193
|
+
let value = ctypes_address(py, value)?;
|
|
194
|
+
let oop_to_flt: Symbol<unsafe extern "C" fn(u64, *mut c_double) -> c_int> =
|
|
195
|
+
self.symbol(b"GciOopToFlt_")?;
|
|
196
|
+
Ok(py.detach(|| unsafe { oop_to_flt(oop, value as *mut c_double) }))
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#[pyo3(name = "GciFetchSize_")]
|
|
200
|
+
fn gci_fetch_size(&self, py: Python<'_>, oop: &Bound<'_, PyAny>) -> PyResult<i64> {
|
|
201
|
+
let oop = py_u64(oop)?;
|
|
202
|
+
let fetch_size: Symbol<unsafe extern "C" fn(u64) -> i64> = self.symbol(b"GciFetchSize_")?;
|
|
203
|
+
Ok(py.detach(|| unsafe { fetch_size(oop) }))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#[pyo3(name = "GciFetchBytes_")]
|
|
207
|
+
fn gci_fetch_bytes(
|
|
208
|
+
&self,
|
|
209
|
+
py: Python<'_>,
|
|
210
|
+
oop: &Bound<'_, PyAny>,
|
|
211
|
+
start: &Bound<'_, PyAny>,
|
|
212
|
+
buffer: &Bound<'_, PyAny>,
|
|
213
|
+
count: &Bound<'_, PyAny>,
|
|
214
|
+
) -> PyResult<i64> {
|
|
215
|
+
let oop = py_u64(oop)?;
|
|
216
|
+
let start = py_i64(start)?;
|
|
217
|
+
let buffer = ctypes_address(py, buffer)?;
|
|
218
|
+
let count = py_i64(count)?;
|
|
219
|
+
let fetch_bytes: Symbol<unsafe extern "C" fn(u64, i64, *mut c_char, i64) -> i64> =
|
|
220
|
+
self.symbol(b"GciFetchBytes_")?;
|
|
221
|
+
Ok(py.detach(|| unsafe { fetch_bytes(oop, start, buffer as *mut c_char, count) }))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#[pyo3(name = "GciFetchClass")]
|
|
225
|
+
fn gci_fetch_class(&self, py: Python<'_>, oop: &Bound<'_, PyAny>) -> PyResult<u64> {
|
|
226
|
+
let oop = py_u64(oop)?;
|
|
227
|
+
let fetch_class: Symbol<unsafe extern "C" fn(u64) -> u64> =
|
|
228
|
+
self.symbol(b"GciFetchClass")?;
|
|
229
|
+
Ok(py.detach(|| unsafe { fetch_class(oop) }))
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#[pyo3(name = "GciPerform")]
|
|
233
|
+
fn gci_perform(
|
|
234
|
+
&self,
|
|
235
|
+
py: Python<'_>,
|
|
236
|
+
receiver: &Bound<'_, PyAny>,
|
|
237
|
+
selector: &Bound<'_, PyAny>,
|
|
238
|
+
args: &Bound<'_, PyAny>,
|
|
239
|
+
argc: &Bound<'_, PyAny>,
|
|
240
|
+
) -> PyResult<u64> {
|
|
241
|
+
let receiver = py_u64(receiver)?;
|
|
242
|
+
let selector = py_cstring(selector)?;
|
|
243
|
+
let args = ctypes_address(py, args)?;
|
|
244
|
+
let argc = py_i32(argc)?;
|
|
245
|
+
let perform: Symbol<unsafe extern "C" fn(u64, *const c_char, *const u64, c_int) -> u64> =
|
|
246
|
+
self.symbol(b"GciPerform")?;
|
|
247
|
+
Ok(py.detach(|| unsafe { perform(receiver, selector.as_ptr(), args as *const u64, argc) }))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#[pyo3(name = "GciNewOop")]
|
|
251
|
+
fn gci_new_oop(&self, py: Python<'_>, class_oop: &Bound<'_, PyAny>) -> PyResult<u64> {
|
|
252
|
+
let class_oop = py_u64(class_oop)?;
|
|
253
|
+
let new_oop: Symbol<unsafe extern "C" fn(u64) -> u64> = self.symbol(b"GciNewOop")?;
|
|
254
|
+
Ok(py.detach(|| unsafe { new_oop(class_oop) }))
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#[pyo3(name = "GciResolveSymbol")]
|
|
258
|
+
fn gci_resolve_symbol(
|
|
259
|
+
&self,
|
|
260
|
+
py: Python<'_>,
|
|
261
|
+
name: &Bound<'_, PyAny>,
|
|
262
|
+
symbol_list: &Bound<'_, PyAny>,
|
|
263
|
+
) -> PyResult<u64> {
|
|
264
|
+
let name = py_cstring(name)?;
|
|
265
|
+
let symbol_list = py_u64(symbol_list)?;
|
|
266
|
+
let resolve: Symbol<unsafe extern "C" fn(*const c_char, u64) -> u64> =
|
|
267
|
+
self.symbol(b"GciResolveSymbol")?;
|
|
268
|
+
Ok(py.detach(|| unsafe { resolve(name.as_ptr(), symbol_list) }))
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
#[pyo3(name = "GciSymDictAtPut")]
|
|
272
|
+
fn gci_sym_dict_at_put(
|
|
273
|
+
&self,
|
|
274
|
+
py: Python<'_>,
|
|
275
|
+
dict: &Bound<'_, PyAny>,
|
|
276
|
+
key: &Bound<'_, PyAny>,
|
|
277
|
+
value: &Bound<'_, PyAny>,
|
|
278
|
+
) -> PyResult<()> {
|
|
279
|
+
let dict = py_u64(dict)?;
|
|
280
|
+
let key = py_cstring(key)?;
|
|
281
|
+
let value = py_u64(value)?;
|
|
282
|
+
let sym_dict_at_put: Symbol<unsafe extern "C" fn(u64, *const c_char, u64)> =
|
|
283
|
+
self.symbol(b"GciSymDictAtPut")?;
|
|
284
|
+
py.detach(|| unsafe { sym_dict_at_put(dict, key.as_ptr(), value) });
|
|
285
|
+
Ok(())
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#[pyo3(name = "GciSymDictAtObjPut")]
|
|
289
|
+
fn gci_sym_dict_at_obj_put(
|
|
290
|
+
&self,
|
|
291
|
+
py: Python<'_>,
|
|
292
|
+
dict: &Bound<'_, PyAny>,
|
|
293
|
+
key: &Bound<'_, PyAny>,
|
|
294
|
+
value: &Bound<'_, PyAny>,
|
|
295
|
+
) -> PyResult<()> {
|
|
296
|
+
let dict = py_u64(dict)?;
|
|
297
|
+
let key = py_u64(key)?;
|
|
298
|
+
let value = py_u64(value)?;
|
|
299
|
+
let sym_dict_at_obj_put: Symbol<unsafe extern "C" fn(u64, u64, u64)> =
|
|
300
|
+
self.symbol(b"GciSymDictAtObjPut")?;
|
|
301
|
+
py.detach(|| unsafe { sym_dict_at_obj_put(dict, key, value) });
|
|
302
|
+
Ok(())
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#[pyo3(name = "GciStrKeyValueDictAtPut")]
|
|
306
|
+
fn gci_str_key_value_dict_at_put(
|
|
307
|
+
&self,
|
|
308
|
+
py: Python<'_>,
|
|
309
|
+
dict: &Bound<'_, PyAny>,
|
|
310
|
+
key: &Bound<'_, PyAny>,
|
|
311
|
+
value: &Bound<'_, PyAny>,
|
|
312
|
+
) -> PyResult<()> {
|
|
313
|
+
let dict = py_u64(dict)?;
|
|
314
|
+
let key = py_cstring(key)?;
|
|
315
|
+
let value = py_u64(value)?;
|
|
316
|
+
let at_put: Symbol<unsafe extern "C" fn(u64, *const c_char, u64)> =
|
|
317
|
+
self.symbol(b"GciStrKeyValueDictAtPut")?;
|
|
318
|
+
py.detach(|| unsafe { at_put(dict, key.as_ptr(), value) });
|
|
319
|
+
Ok(())
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
#[pyo3(name = "GciStrKeyValueDictAt")]
|
|
323
|
+
fn gci_str_key_value_dict_at(
|
|
324
|
+
&self,
|
|
325
|
+
py: Python<'_>,
|
|
326
|
+
dict: &Bound<'_, PyAny>,
|
|
327
|
+
key: &Bound<'_, PyAny>,
|
|
328
|
+
value: &Bound<'_, PyAny>,
|
|
329
|
+
) -> PyResult<()> {
|
|
330
|
+
let dict = py_u64(dict)?;
|
|
331
|
+
let key = py_cstring(key)?;
|
|
332
|
+
let value = ctypes_address(py, value)?;
|
|
333
|
+
let at: Symbol<unsafe extern "C" fn(u64, *const c_char, *mut u64)> =
|
|
334
|
+
self.symbol(b"GciStrKeyValueDictAt")?;
|
|
335
|
+
py.detach(|| unsafe { at(dict, key.as_ptr(), value as *mut u64) });
|
|
336
|
+
Ok(())
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
#[pyo3(name = "GciSymDictAt")]
|
|
340
|
+
fn gci_sym_dict_at(
|
|
341
|
+
&self,
|
|
342
|
+
py: Python<'_>,
|
|
343
|
+
dict: &Bound<'_, PyAny>,
|
|
344
|
+
key: &Bound<'_, PyAny>,
|
|
345
|
+
value: &Bound<'_, PyAny>,
|
|
346
|
+
assoc: &Bound<'_, PyAny>,
|
|
347
|
+
) -> PyResult<()> {
|
|
348
|
+
let dict = py_u64(dict)?;
|
|
349
|
+
let key = py_cstring(key)?;
|
|
350
|
+
let value = ctypes_address(py, value)?;
|
|
351
|
+
let assoc = ctypes_address(py, assoc)?;
|
|
352
|
+
let at: Symbol<unsafe extern "C" fn(u64, *const c_char, *mut u64, *mut u64)> =
|
|
353
|
+
self.symbol(b"GciSymDictAt")?;
|
|
354
|
+
py.detach(|| unsafe { at(dict, key.as_ptr(), value as *mut u64, assoc as *mut u64) });
|
|
355
|
+
Ok(())
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
#[pyo3(name = "GciGetSessionId")]
|
|
359
|
+
fn gci_get_session_id(&self, py: Python<'_>) -> PyResult<c_int> {
|
|
360
|
+
let get_session_id: Symbol<unsafe extern "C" fn() -> c_int> =
|
|
361
|
+
self.symbol(b"GciGetSessionId")?;
|
|
362
|
+
Ok(py.detach(|| unsafe { get_session_id() }))
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
#[pyo3(name = "GciSetSessionId")]
|
|
366
|
+
fn gci_set_session_id(&self, py: Python<'_>, session_id: &Bound<'_, PyAny>) -> PyResult<()> {
|
|
367
|
+
let session_id = py_i32(session_id)?;
|
|
368
|
+
let set_session_id: Symbol<unsafe extern "C" fn(c_int)> =
|
|
369
|
+
self.symbol(b"GciSetSessionId")?;
|
|
370
|
+
py.detach(|| unsafe { set_session_id(session_id) });
|
|
371
|
+
Ok(())
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
#[pyo3(name = "GciNeedsCommit")]
|
|
375
|
+
fn gci_needs_commit(&self, py: Python<'_>) -> PyResult<c_int> {
|
|
376
|
+
let needs_commit: Symbol<unsafe extern "C" fn() -> c_int> =
|
|
377
|
+
self.symbol(b"GciNeedsCommit")?;
|
|
378
|
+
Ok(py.detach(|| unsafe { needs_commit() }))
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
#[pyo3(name = "GciInTransaction")]
|
|
382
|
+
fn gci_in_transaction(&self, py: Python<'_>) -> PyResult<c_int> {
|
|
383
|
+
let in_transaction: Symbol<unsafe extern "C" fn() -> c_int> =
|
|
384
|
+
self.symbol(b"GciInTransaction")?;
|
|
385
|
+
Ok(py.detach(|| unsafe { in_transaction() }))
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
#[pyo3(name = "GciAddOopToExportSet")]
|
|
389
|
+
fn gci_add_oop_to_export_set(&self, py: Python<'_>, oop: &Bound<'_, PyAny>) -> PyResult<()> {
|
|
390
|
+
self.call_optional_oop_export(py, b"GciAddOopToExportSet", py_u64(oop)?)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
#[pyo3(name = "GciRemoveOopFromExportSet")]
|
|
394
|
+
fn gci_remove_oop_from_export_set(
|
|
395
|
+
&self,
|
|
396
|
+
py: Python<'_>,
|
|
397
|
+
oop: &Bound<'_, PyAny>,
|
|
398
|
+
) -> PyResult<()> {
|
|
399
|
+
self.call_optional_oop_export(py, b"GciRemoveOopFromExportSet", py_u64(oop)?)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
#[pyo3(name = "GciAddObjToExportSet")]
|
|
403
|
+
fn gci_add_obj_to_export_set(&self, py: Python<'_>, oop: &Bound<'_, PyAny>) -> PyResult<()> {
|
|
404
|
+
self.call_optional_oop_export(py, b"GciAddObjToExportSet", py_u64(oop)?)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
#[pyo3(name = "GciRemoveObjFromExportSet")]
|
|
408
|
+
fn gci_remove_obj_from_export_set(
|
|
409
|
+
&self,
|
|
410
|
+
py: Python<'_>,
|
|
411
|
+
oop: &Bound<'_, PyAny>,
|
|
412
|
+
) -> PyResult<()> {
|
|
413
|
+
self.call_optional_oop_export(py, b"GciRemoveObjFromExportSet", py_u64(oop)?)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
fn __repr__(&self) -> String {
|
|
417
|
+
format!("<NativeGciLibrary path='{}'>", self.path.display())
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
impl NativeGciLibrary {
|
|
422
|
+
fn symbol<T>(&self, name: &[u8]) -> PyResult<Symbol<'_, T>> {
|
|
423
|
+
unsafe { self.library.get(name) }.map_err(|err| {
|
|
424
|
+
PyOSError::new_err(format!(
|
|
425
|
+
"{} not found in {}: {err}",
|
|
426
|
+
String::from_utf8_lossy(name),
|
|
427
|
+
self.path.display()
|
|
428
|
+
))
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
fn call_optional_oop_export(&self, py: Python<'_>, name: &[u8], oop: u64) -> PyResult<()> {
|
|
433
|
+
let symbol = unsafe { self.library.get::<unsafe extern "C" fn(u64)>(name) };
|
|
434
|
+
if let Ok(function) = symbol {
|
|
435
|
+
py.detach(|| unsafe { function(oop) });
|
|
436
|
+
}
|
|
437
|
+
Ok(())
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
#[pyfunction]
|
|
442
|
+
fn _is_smallint(oop: u64) -> bool {
|
|
443
|
+
(oop & 0x7) == TAG_SMALLINT
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
#[pyfunction]
|
|
447
|
+
fn _is_smalldouble(oop: u64) -> bool {
|
|
448
|
+
(oop & 0x7) == TAG_SMALLDOUBLE
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
#[pyfunction]
|
|
452
|
+
fn _smallint_to_python(oop: u64) -> i64 {
|
|
453
|
+
(oop as i64) >> SMALLINT_SHIFT
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
#[pyfunction]
|
|
457
|
+
fn _python_to_smallint(value: i64) -> u64 {
|
|
458
|
+
((value << SMALLINT_SHIFT) as u64) | TAG_SMALLINT
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
#[pyfunction]
|
|
462
|
+
fn _is_char(oop: u64) -> bool {
|
|
463
|
+
(oop & 0xFF) == CHAR_TAG_BYTE && (oop & 0x6) == TAG_SPECIAL
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
#[pyfunction]
|
|
467
|
+
fn _char_to_python(oop: u64) -> PyResult<String> {
|
|
468
|
+
let codepoint = ((oop >> 8) & 0x1F_FFFF) as u32;
|
|
469
|
+
let ch = char::from_u32(codepoint)
|
|
470
|
+
.ok_or_else(|| PyValueError::new_err(format!("invalid GemStone character OOP {oop}")))?;
|
|
471
|
+
Ok(ch.to_string())
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
#[pyfunction]
|
|
475
|
+
fn native_implementation() -> &'static str {
|
|
476
|
+
"pyo3"
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
#[pyfunction]
|
|
480
|
+
#[pyo3(signature = (lib_path=None))]
|
|
481
|
+
fn gci_init(lib_path: Option<String>) -> PyResult<i32> {
|
|
482
|
+
let path = resolve_library_path(lib_path)?;
|
|
483
|
+
let library = Arc::new(
|
|
484
|
+
unsafe { Library::new(&path) }
|
|
485
|
+
.map_err(|err| PyOSError::new_err(format!("cannot load {}: {err}", path.display())))?,
|
|
486
|
+
);
|
|
487
|
+
let result = unsafe {
|
|
488
|
+
let init: Symbol<unsafe extern "C" fn() -> i32> = library
|
|
489
|
+
.get(b"GciInit")
|
|
490
|
+
.map_err(|err| PyOSError::new_err(format!("GciInit not found: {err}")))?;
|
|
491
|
+
init()
|
|
492
|
+
};
|
|
493
|
+
LOADED_LIBRARIES
|
|
494
|
+
.get_or_init(|| Mutex::new(Vec::new()))
|
|
495
|
+
.lock()
|
|
496
|
+
.map_err(|_| PyOSError::new_err("native GCI library lock is poisoned"))?
|
|
497
|
+
.push(library);
|
|
498
|
+
Ok(result)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
#[pyfunction]
|
|
502
|
+
#[pyo3(signature = (lib_path=None))]
|
|
503
|
+
fn _load_library(lib_path: Option<String>) -> PyResult<NativeGciLibrary> {
|
|
504
|
+
let path = resolve_library_path(lib_path)?;
|
|
505
|
+
let library = Arc::new(
|
|
506
|
+
unsafe { Library::new(&path) }
|
|
507
|
+
.map_err(|err| PyOSError::new_err(format!("cannot load {}: {err}", path.display())))?,
|
|
508
|
+
);
|
|
509
|
+
LOADED_LIBRARIES
|
|
510
|
+
.get_or_init(|| Mutex::new(Vec::new()))
|
|
511
|
+
.lock()
|
|
512
|
+
.map_err(|_| PyOSError::new_err("native GCI library lock is poisoned"))?
|
|
513
|
+
.push(Arc::clone(&library));
|
|
514
|
+
Ok(NativeGciLibrary { library, path })
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
#[pyfunction]
|
|
518
|
+
fn _bind(_library: &Bound<'_, PyAny>) {
|
|
519
|
+
// NativeGciLibrary methods already carry their Rust signatures, so the
|
|
520
|
+
// ctypes-style binding step remains a compatibility no-op.
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
#[pymodule]
|
|
524
|
+
fn _gci(py: Python<'_>, module: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
525
|
+
copy_ctypes_fallback_surface(py, module)?;
|
|
526
|
+
|
|
527
|
+
module.add("OOP_ILLEGAL", OOP_ILLEGAL)?;
|
|
528
|
+
module.add("OOP_NIL", OOP_NIL)?;
|
|
529
|
+
module.add("OOP_FALSE", OOP_FALSE)?;
|
|
530
|
+
module.add("OOP_TRUE", OOP_TRUE)?;
|
|
531
|
+
module.add("OOP_ASCII_NUL", OOP_ASCII_NUL)?;
|
|
532
|
+
module.add("GCI_ERR_STR_SIZE", GCI_ERR_STR_SIZE)?;
|
|
533
|
+
module.add("GCI_MAX_ERR_ARGS", GCI_MAX_ERR_ARGS)?;
|
|
534
|
+
module.add("GCI_INVALID_SESSION", GCI_INVALID_SESSION)?;
|
|
535
|
+
module.add("GCI_ENCRYPT_BUF_SIZE", GCI_ENCRYPT_BUF_SIZE)?;
|
|
536
|
+
module.add("GCI_LOGIN_PW_ENCRYPTED", GCI_LOGIN_PW_ENCRYPTED)?;
|
|
537
|
+
module.add("GCI_LOGIN_IS_GCSTS", GCI_LOGIN_IS_GCSTS)?;
|
|
538
|
+
module.add("IMPLEMENTATION", "native")?;
|
|
539
|
+
module.add_class::<NativeGciLibrary>()?;
|
|
540
|
+
|
|
541
|
+
module.add_function(wrap_pyfunction!(_is_smallint, module)?)?;
|
|
542
|
+
module.add_function(wrap_pyfunction!(_is_smalldouble, module)?)?;
|
|
543
|
+
module.add_function(wrap_pyfunction!(_smallint_to_python, module)?)?;
|
|
544
|
+
module.add_function(wrap_pyfunction!(_python_to_smallint, module)?)?;
|
|
545
|
+
module.add_function(wrap_pyfunction!(_is_char, module)?)?;
|
|
546
|
+
module.add_function(wrap_pyfunction!(_char_to_python, module)?)?;
|
|
547
|
+
module.add_function(wrap_pyfunction!(native_implementation, module)?)?;
|
|
548
|
+
module.add_function(wrap_pyfunction!(gci_init, module)?)?;
|
|
549
|
+
module.add_function(wrap_pyfunction!(_load_library, module)?)?;
|
|
550
|
+
module.add_function(wrap_pyfunction!(_bind, module)?)?;
|
|
551
|
+
|
|
552
|
+
let exports = PyList::new(
|
|
553
|
+
py,
|
|
554
|
+
[
|
|
555
|
+
"OOP_ILLEGAL",
|
|
556
|
+
"OOP_NIL",
|
|
557
|
+
"OOP_FALSE",
|
|
558
|
+
"OOP_TRUE",
|
|
559
|
+
"OOP_ASCII_NUL",
|
|
560
|
+
"GCI_ERR_STR_SIZE",
|
|
561
|
+
"GCI_MAX_ERR_ARGS",
|
|
562
|
+
"GCI_INVALID_SESSION",
|
|
563
|
+
"GCI_ENCRYPT_BUF_SIZE",
|
|
564
|
+
"GCI_LOGIN_PW_ENCRYPTED",
|
|
565
|
+
"GCI_LOGIN_IS_GCSTS",
|
|
566
|
+
"GciErrSType",
|
|
567
|
+
"NativeGciLibrary",
|
|
568
|
+
"_is_smallint",
|
|
569
|
+
"_is_smalldouble",
|
|
570
|
+
"_smallint_to_python",
|
|
571
|
+
"_python_to_smallint",
|
|
572
|
+
"_is_char",
|
|
573
|
+
"_char_to_python",
|
|
574
|
+
"_load_library",
|
|
575
|
+
"_bind",
|
|
576
|
+
"gci_init",
|
|
577
|
+
"native_implementation",
|
|
578
|
+
],
|
|
579
|
+
)?;
|
|
580
|
+
module.add("__all__", exports)?;
|
|
581
|
+
Ok(())
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
fn copy_ctypes_fallback_surface(py: Python<'_>, module: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
585
|
+
let fallback = PyModule::import(py, "gemstone_py._gci_ctypes")?;
|
|
586
|
+
for name in ["GciErrSType"] {
|
|
587
|
+
module.add(name, fallback.getattr(name)?)?;
|
|
588
|
+
}
|
|
589
|
+
Ok(())
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
fn py_u64(value: &Bound<'_, PyAny>) -> PyResult<u64> {
|
|
593
|
+
if let Ok(number) = value.extract::<u64>() {
|
|
594
|
+
return Ok(number);
|
|
595
|
+
}
|
|
596
|
+
if let Ok(inner) = value.getattr("value") {
|
|
597
|
+
return py_u64(&inner);
|
|
598
|
+
}
|
|
599
|
+
Err(PyTypeError::new_err("expected an integer-compatible value"))
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
fn py_i64(value: &Bound<'_, PyAny>) -> PyResult<i64> {
|
|
603
|
+
if let Ok(number) = value.extract::<i64>() {
|
|
604
|
+
return Ok(number);
|
|
605
|
+
}
|
|
606
|
+
if let Ok(inner) = value.getattr("value") {
|
|
607
|
+
return py_i64(&inner);
|
|
608
|
+
}
|
|
609
|
+
Err(PyTypeError::new_err("expected an integer-compatible value"))
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
fn py_i32(value: &Bound<'_, PyAny>) -> PyResult<c_int> {
|
|
613
|
+
let number = py_i64(value)?;
|
|
614
|
+
c_int::try_from(number).map_err(|_| PyValueError::new_err("integer does not fit in c_int"))
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
fn py_u32(value: &Bound<'_, PyAny>) -> PyResult<c_uint> {
|
|
618
|
+
let number = py_u64(value)?;
|
|
619
|
+
c_uint::try_from(number).map_err(|_| PyValueError::new_err("integer does not fit in c_uint"))
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
fn py_f64(value: &Bound<'_, PyAny>) -> PyResult<c_double> {
|
|
623
|
+
if let Ok(number) = value.extract::<c_double>() {
|
|
624
|
+
return Ok(number);
|
|
625
|
+
}
|
|
626
|
+
if let Ok(inner) = value.getattr("value") {
|
|
627
|
+
return py_f64(&inner);
|
|
628
|
+
}
|
|
629
|
+
Err(PyTypeError::new_err("expected a float-compatible value"))
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
fn py_cstring(value: &Bound<'_, PyAny>) -> PyResult<CString> {
|
|
633
|
+
if let Ok(bytes) = value.extract::<Vec<u8>>() {
|
|
634
|
+
return CString::new(bytes).map_err(|_| PyValueError::new_err("string contains NUL byte"));
|
|
635
|
+
}
|
|
636
|
+
if let Ok(text) = value.extract::<String>() {
|
|
637
|
+
return CString::new(text).map_err(|_| PyValueError::new_err("string contains NUL byte"));
|
|
638
|
+
}
|
|
639
|
+
if let Ok(inner) = value.getattr("value") {
|
|
640
|
+
return py_cstring(&inner);
|
|
641
|
+
}
|
|
642
|
+
Err(PyTypeError::new_err(
|
|
643
|
+
"expected bytes, str, or ctypes string value",
|
|
644
|
+
))
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
fn ctypes_address(py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<usize> {
|
|
648
|
+
let target = value
|
|
649
|
+
.getattr("_obj")
|
|
650
|
+
.unwrap_or_else(|_| value.clone().into_any());
|
|
651
|
+
let ctypes = PyModule::import(py, "ctypes")?;
|
|
652
|
+
ctypes.getattr("addressof")?.call1((target,))?.extract()
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
fn resolve_library_path(lib_path: Option<String>) -> PyResult<PathBuf> {
|
|
656
|
+
if let Some(path) = lib_path {
|
|
657
|
+
return Ok(PathBuf::from(path));
|
|
658
|
+
}
|
|
659
|
+
if let Ok(path) = env::var("GS_LIB_PATH") {
|
|
660
|
+
if !path.is_empty() {
|
|
661
|
+
return Ok(PathBuf::from(path));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if let Ok(dir) = env::var("GS_LIB") {
|
|
665
|
+
if !dir.is_empty() {
|
|
666
|
+
if let Some(path) = find_gcirpc_in_dir(Path::new(&dir))? {
|
|
667
|
+
return Ok(path);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if let Ok(gemstone) = env::var("GEMSTONE") {
|
|
672
|
+
if !gemstone.is_empty() {
|
|
673
|
+
let lib_dir = Path::new(&gemstone).join("lib");
|
|
674
|
+
if let Some(path) = find_gcirpc_in_dir(&lib_dir)? {
|
|
675
|
+
return Ok(path);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
Err(PyOSError::new_err(
|
|
680
|
+
"Cannot find libgcirpc. Pass lib_path or set GS_LIB_PATH/GS_LIB/GEMSTONE.",
|
|
681
|
+
))
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
fn find_gcirpc_in_dir(dir: &Path) -> PyResult<Option<PathBuf>> {
|
|
685
|
+
if !dir.is_dir() {
|
|
686
|
+
return Ok(None);
|
|
687
|
+
}
|
|
688
|
+
let mut candidates = Vec::new();
|
|
689
|
+
for entry in fs::read_dir(dir)
|
|
690
|
+
.map_err(|err| PyOSError::new_err(format!("cannot read {}: {err}", dir.display())))?
|
|
691
|
+
{
|
|
692
|
+
let entry = entry
|
|
693
|
+
.map_err(|err| PyOSError::new_err(format!("cannot read {}: {err}", dir.display())))?;
|
|
694
|
+
let path = entry.path();
|
|
695
|
+
let Some(name) = path.file_name().and_then(|value| value.to_str()) else {
|
|
696
|
+
continue;
|
|
697
|
+
};
|
|
698
|
+
if name.starts_with("libgcirpc")
|
|
699
|
+
&& (name.ends_with(".dylib") || name.ends_with(".so") || name.ends_with(".dll"))
|
|
700
|
+
{
|
|
701
|
+
candidates.push(path);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
candidates.sort();
|
|
705
|
+
Ok(candidates.pop())
|
|
706
|
+
}
|