Add a streaming base64 encoder

This commit is contained in:
Kovid Goyal
2024-07-29 22:08:13 +05:30
parent 8b54d19326
commit d2ced8d6e1
2 changed files with 62 additions and 0 deletions

View File

@@ -108,6 +108,7 @@ pybase64_decode(PyObject UNUSED *self, PyObject *args) {
typedef struct StreamingBase64Decoder {
PyObject_HEAD
struct base64_state state;
bool add_trailing_bytes;
} StreamingBase64Decoder;
static int
@@ -154,6 +155,58 @@ static PyTypeObject StreamingBase64Decoder_Type = {
.tp_init = StreamingBase64Decoder_init,
};
static int
StreamingBase64Encoder_init(PyObject *s, PyObject *args, PyObject *kwds UNUSED) {
StreamingBase64Decoder *self = (StreamingBase64Decoder*)s;
self->add_trailing_bytes = true;
switch (PyTuple_GET_SIZE(args)) {
case 0: break;
case 1: self->add_trailing_bytes = PyObject_IsTrue(PyTuple_GET_ITEM(args, 0)); break;
default: PyErr_SetString(PyExc_TypeError, "constructor takes no more than one argument"); return -1;
}
base64_stream_encode_init(&self->state, 0);
return 0;
}
static PyObject*
StreamingBase64Encoder_encode(StreamingBase64Decoder *self, PyObject *a) {
RAII_PY_BUFFER(data);
if (PyObject_GetBuffer(a, &data, PyBUF_SIMPLE) != 0) return NULL;
if (!data.buf || !data.len) return PyBytes_FromStringAndSize(NULL, 0);
size_t sz = required_buffer_size_for_base64_encode(data.len);
RAII_PyObject(ans, PyBytes_FromStringAndSize(NULL, sz));
if (!ans) return NULL;
base64_stream_encode(&self->state, data.buf, data.len, PyBytes_AS_STRING(ans), &sz);
if (_PyBytes_Resize(&ans, sz) != 0) return NULL;
return Py_NewRef(ans);
}
static PyObject*
StreamingBase64Encoder_reset(StreamingBase64Decoder *self, PyObject *args UNUSED) {
char trailer[4];
size_t sz;
base64_stream_encode_final(&self->state, trailer, &sz);
base64_stream_encode_init(&self->state, 0);
if (!self->add_trailing_bytes) { while(sz && trailer[sz-1] == '=') sz--; }
return PyBytes_FromStringAndSize(trailer, sz);
}
static PyTypeObject StreamingBase64Encoder_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "kitty.fast_data_types.StreamingBase64Encoder",
.tp_basicsize = sizeof(StreamingBase64Decoder),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "StreamingBase64Encoder",
.tp_methods = (PyMethodDef[]){
{"encode", (PyCFunction)StreamingBase64Encoder_encode, METH_O, ""},
{"reset", (PyCFunction)StreamingBase64Encoder_reset, METH_NOARGS, ""},
{NULL, NULL, 0, NULL},
},
.tp_new = PyType_GenericNew,
.tp_init = StreamingBase64Encoder_init,
};
static PyObject*
pyset_iutf8(PyObject UNUSED *self, PyObject *args) {
int fd, on;
@@ -661,6 +714,8 @@ PyInit_fast_data_types(void) {
if (PyType_Ready(&StreamingBase64Decoder_Type) < 0) return NULL;
if (PyModule_AddObject(m, "StreamingBase64Decoder", (PyObject *) &StreamingBase64Decoder_Type) < 0) return NULL;
if (PyType_Ready(&StreamingBase64Encoder_Type) < 0) return NULL;
if (PyModule_AddObject(m, "StreamingBase64Encoder", (PyObject *) &StreamingBase64Encoder_Type) < 0) return NULL;
return m;
}

View File

@@ -1709,6 +1709,13 @@ class StreamingBase64Decoder:
def reset(self) -> None: ... # reset the state to empty to start decoding a new stream
class StreamingBase64Encodeer:
def __init__(self, add_trailing_bytes: bool = True) -> None: ...
def encode(self, data: ReadOnlyBuffer) -> bytes: ... # decode the specified data
def reset(self) -> bytes: ... # reset the state to empty to start decoding a new stream, return any trailing bytes
class DiskCache:
small_hole_threshold: int
defrag_factor: int