#include "blob.h" #include "util.h" int pysqlite_blob_init(pysqlite_Blob *self, pysqlite_Connection* connection, sqlite3_blob *blob) { Py_INCREF(connection); self->connection = connection; self->offset = 0; self->blob = blob; self->in_weakreflist = NULL; Py_BEGIN_ALLOW_THREADS self->length = sqlite3_blob_bytes(self->blob); Py_END_ALLOW_THREADS if (!pysqlite_check_thread(self->connection)) { return -1; } return 0; } static void remove_blob_from_connection_blob_list(pysqlite_Blob *self) { Py_ssize_t i; PyObject *item; for (i = 0; i < PyList_GET_SIZE(self->connection->blobs); i++) { item = PyList_GET_ITEM(self->connection->blobs, i); if (PyWeakref_GetObject(item) == (PyObject *)self) { PyList_SetSlice(self->connection->blobs, i, i+1, NULL); break; } } } static void _close_blob_inner(pysqlite_Blob* self) { sqlite3_blob *blob; /* close the blob */ blob = self->blob; self->blob = NULL; if (blob) { Py_BEGIN_ALLOW_THREADS sqlite3_blob_close(blob); Py_END_ALLOW_THREADS } /* remove from connection weaklist */ remove_blob_from_connection_blob_list(self); if (self->in_weakreflist != NULL) { PyObject_ClearWeakRefs((PyObject*)self); } } static void pysqlite_blob_dealloc(pysqlite_Blob* self) { _close_blob_inner(self); Py_XDECREF(self->connection); Py_TYPE(self)->tp_free((PyObject*)self); } /* * Checks if a blob object is usable (i. e. not closed). * * 0 => error; 1 => ok */ int pysqlite_check_blob(pysqlite_Blob *blob) { if (!blob->blob) { PyErr_SetString(pysqlite_ProgrammingError, "Cannot operate on a closed blob."); return 0; } else if (!pysqlite_check_connection(blob->connection) || !pysqlite_check_thread(blob->connection)) { return 0; } else { return 1; } } PyObject* pysqlite_blob_close(pysqlite_Blob *self) { if (!pysqlite_check_blob(self)) { return NULL; } _close_blob_inner(self); Py_RETURN_NONE; }; static Py_ssize_t pysqlite_blob_length(pysqlite_Blob *self) { if (!pysqlite_check_blob(self)) { return -1; } return self->length; }; static PyObject* inner_read(pysqlite_Blob *self, int read_length, int offset) { PyObject *buffer; char *raw_buffer; int rc; buffer = PyBytes_FromStringAndSize(NULL, read_length); if (!buffer) { return NULL; } raw_buffer = PyBytes_AS_STRING(buffer); Py_BEGIN_ALLOW_THREADS rc = sqlite3_blob_read(self->blob, raw_buffer, read_length, self->offset); Py_END_ALLOW_THREADS if (rc != SQLITE_OK){ Py_DECREF(buffer); /* For some reason after modifying blob the error is not set on the connection db. */ if (rc == SQLITE_ABORT) { PyErr_SetString(pysqlite_OperationalError, "Cannot operate on modified blob"); } else { _pysqlite_seterror(self->connection->db, NULL); } return NULL; } return buffer; } PyObject* pysqlite_blob_read(pysqlite_Blob *self, PyObject *args) { int read_length = -1; PyObject *buffer; if (!PyArg_ParseTuple(args, "|i", &read_length)) { return NULL; } if (!pysqlite_check_blob(self)) { return NULL; } if (read_length < 0) { /* same as file read. */ read_length = self->length; } /* making sure we don't read more then blob size */ if (read_length > self->length - self->offset) { read_length = self->length - self->offset; } buffer = inner_read(self, read_length, self->offset); if (buffer != NULL) { /* update offset on sucess. */ self->offset += read_length; } return buffer; }; static int write_inner(pysqlite_Blob *self, const void *buf, Py_ssize_t len, int offset) { int rc; Py_BEGIN_ALLOW_THREADS rc = sqlite3_blob_write(self->blob, buf, len, offset); Py_END_ALLOW_THREADS if (rc != SQLITE_OK) { /* For some reason after modifying blob the error is not set on the connection db. */ if (rc == SQLITE_ABORT) { PyErr_SetString(pysqlite_OperationalError, "Cannot operate on modified blob"); } else { _pysqlite_seterror(self->connection->db, NULL); } return -1; } return 0; } PyObject* pysqlite_blob_write(pysqlite_Blob *self, PyObject *data) { Py_buffer data_buffer; int rc; if (PyObject_GetBuffer(data, &data_buffer, PyBUF_SIMPLE) < 0) { return NULL; } if (data_buffer.len > INT_MAX) { PyErr_SetString(PyExc_OverflowError, "data longer than INT_MAX bytes"); PyBuffer_Release(&data_buffer); return NULL; } if (!pysqlite_check_blob(self)) { PyBuffer_Release(&data_buffer); return NULL; } /* TODO: throw better error on data bigger then blob. */ rc = write_inner(self, data_buffer.buf, data_buffer.len, self->offset); if (rc == 0) { self->offset += (int)data_buffer.len; PyBuffer_Release(&data_buffer); Py_RETURN_NONE; } else { PyBuffer_Release(&data_buffer); return NULL; } } PyObject* pysqlite_blob_seek(pysqlite_Blob *self, PyObject *args) { int offset, from_what = 0; if (!PyArg_ParseTuple(args, "i|i", &offset, &from_what)) { return NULL; } if (!pysqlite_check_blob(self)) { return NULL; } switch (from_what) { case 0: // relative to blob begin break; case 1: // relative to current position if (offset > INT_MAX - self->offset) { goto overflow; } offset = self->offset + offset; break; case 2: // relative to blob end if (offset > INT_MAX - self->length) { goto overflow; } offset = self->length + offset; break; default: return PyErr_Format(PyExc_ValueError, "from_what should be 0, 1 or 2"); } if (offset < 0 || offset > self->length) { return PyErr_Format(PyExc_ValueError, "offset out of blob range"); } self->offset = offset; Py_RETURN_NONE; overflow: return PyErr_Format(PyExc_OverflowError, "seek offset result in overflow"); } PyObject* pysqlite_blob_tell(pysqlite_Blob *self) { if (!pysqlite_check_blob(self)) { return NULL; } return PyLong_FromLong(self->offset); } PyObject* pysqlite_blob_enter(pysqlite_Blob *self) { if (!pysqlite_check_blob(self)) { return NULL; } Py_INCREF(self); return (PyObject *)self; } PyObject* pysqlite_blob_exit(pysqlite_Blob *self, PyObject *args) { PyObject *res; if (!pysqlite_check_blob(self)) { return NULL; } res = pysqlite_blob_close(self); if (!res) { return NULL; } Py_XDECREF(res); Py_RETURN_FALSE; } static PyObject* pysqlite_blob_concat(pysqlite_Blob *self, PyObject *args) { if (pysqlite_check_blob(self)) { PyErr_SetString(PyExc_SystemError, "Blob don't support concatenation"); } return NULL; } static PyObject* pysqlite_blob_repeat(pysqlite_Blob *self, PyObject *args) { if (pysqlite_check_blob(self)) { PyErr_SetString(PyExc_SystemError, "Blob don't support repeat operation"); } return NULL; } static int pysqlite_blob_contains(pysqlite_Blob *self, PyObject *args) { if (pysqlite_check_blob(self)) { PyErr_SetString(PyExc_SystemError, "Blob don't support cotains operation"); } return -1; } static PyObject* pysqlite_blob_item(pysqlite_Blob *self, Py_ssize_t i) { if (!pysqlite_check_blob(self)) { return NULL; } if (i < 0 || i >= self->length) { PyErr_SetString(PyExc_IndexError, "Blob index out of range"); return NULL; } return inner_read(self, 1, i); } static int pysqlite_blob_ass_item(pysqlite_Blob *self, Py_ssize_t i, PyObject *v) { const char *buf; if (!pysqlite_check_blob(self)) { return -1; } if (i < 0 || i >= self->length) { PyErr_SetString(PyExc_IndexError, "Blob index out of range"); return -1; } if (v == NULL) { PyErr_SetString(PyExc_TypeError, "Blob object doesn't support item deletion"); return -1; } if (! (PyBytes_Check(v) && PyBytes_Size(v)==1) ) { PyErr_SetString(PyExc_IndexError, "Blob assignment must be length-1 bytes()"); return -1; } buf = PyBytes_AsString(v); return write_inner(self, buf, 1, i); } static PyObject * pysqlite_blob_subscript(pysqlite_Blob *self, PyObject *item) { if (!pysqlite_check_blob(self)) { return NULL; } if (PyIndex_Check(item)) { Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError); if (i == -1 && PyErr_Occurred()) return NULL; if (i < 0) i += self->length; if (i < 0 || i >= self->length) { PyErr_SetString(PyExc_IndexError, "Blob index out of range"); return NULL; } // TODO: I am not sure... return inner_read(self, 1, i); } else if (PySlice_Check(item)) { Py_ssize_t start, stop, step, slicelen; if (PySlice_GetIndicesEx(item, self->length, &start, &stop, &step, &slicelen) < 0) { return NULL; } if (slicelen <= 0) return PyBytes_FromStringAndSize("", 0); else if (step == 1) return inner_read(self, slicelen, start); else { char *result_buf = (char *)PyMem_Malloc(slicelen); char *data_buff = NULL; Py_ssize_t cur, i; PyObject *result; int rc; if (result_buf == NULL) return PyErr_NoMemory(); data_buff = (char *)PyMem_Malloc(stop - start); if (data_buff == NULL) { PyMem_Free(result_buf); return PyErr_NoMemory(); } Py_BEGIN_ALLOW_THREADS rc = sqlite3_blob_read(self->blob, data_buff, stop - start, start); Py_END_ALLOW_THREADS if (rc != SQLITE_OK){ /* For some reason after modifying blob the error is not set on the connection db. */ if (rc == SQLITE_ABORT) { PyErr_SetString(pysqlite_OperationalError, "Cannot operate on modified blob"); } else { _pysqlite_seterror(self->connection->db, NULL); } PyMem_Free(result_buf); PyMem_Free(data_buff); return NULL; } for (cur = 0, i = 0; i < slicelen; cur += step, i++) { result_buf[i] = data_buff[cur]; } result = PyBytes_FromStringAndSize(result_buf, slicelen); PyMem_Free(result_buf); PyMem_Free(data_buff); return result; } } else { PyErr_SetString(PyExc_TypeError, "Blob indices must be integers"); return NULL; } } static int pysqlite_blob_ass_subscript(pysqlite_Blob *self, PyObject *item, PyObject *value) { int rc; if (!pysqlite_check_blob(self)) { return -1; } if (PyIndex_Check(item)) { Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError); const char *buf; if (i == -1 && PyErr_Occurred()) return -1; if (i < 0) i += self->length; if (i < 0 || i >= self->length) { PyErr_SetString(PyExc_IndexError, "Blob index out of range"); return -1; } if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Blob doesn't support item deletion"); return -1; } if (! (PyBytes_Check(value) && PyBytes_Size(value)==1) ) { PyErr_SetString(PyExc_IndexError, "Blob assignment must be length-1 bytes()"); return -1; } buf = PyBytes_AsString(value); return write_inner(self, buf, 1, i); } else if (PySlice_Check(item)) { Py_ssize_t start, stop, step, slicelen; Py_buffer vbuf; if (PySlice_GetIndicesEx(item, self->length, &start, &stop, &step, &slicelen) < 0) { return -1; } if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Blob object doesn't support slice deletion"); return -1; } if (PyObject_GetBuffer(value, &vbuf, PyBUF_SIMPLE) < 0) return -1; if (vbuf.len != slicelen) { PyErr_SetString(PyExc_IndexError, "Blob slice assignment is wrong size"); PyBuffer_Release(&vbuf); return -1; } if (slicelen == 0) { } else if (step == 1) { rc = write_inner(self, vbuf.buf, slicelen, start); } else { Py_ssize_t cur, i; char *data_buff; data_buff = (char *)PyMem_Malloc(stop - start); if (data_buff == NULL) { PyErr_NoMemory(); return -1; } Py_BEGIN_ALLOW_THREADS rc = sqlite3_blob_read(self->blob, data_buff, stop - start, start); Py_END_ALLOW_THREADS if (rc != SQLITE_OK){ /* For some reason after modifying blob the error is not set on the connection db. */ if (rc == SQLITE_ABORT) { PyErr_SetString(pysqlite_OperationalError, "Cannot operate on modified blob"); } else { _pysqlite_seterror(self->connection->db, NULL); } PyMem_Free(data_buff); rc = -1; } for (cur = 0, i = 0; i < slicelen; cur += step, i++) { data_buff[cur] = ((char *)vbuf.buf)[i]; } Py_BEGIN_ALLOW_THREADS rc = sqlite3_blob_write(self->blob, data_buff, stop - start, start); Py_END_ALLOW_THREADS if (rc != SQLITE_OK){ /* For some reason after modifying blob the error is not set on the connection db. */ if (rc == SQLITE_ABORT) { PyErr_SetString(pysqlite_OperationalError, "Cannot operate on modified blob"); } else { _pysqlite_seterror(self->connection->db, NULL); } PyMem_Free(data_buff); rc = -1; } rc = 0; } PyBuffer_Release(&vbuf); return rc; } else { PyErr_SetString(PyExc_TypeError, "mmap indices must be integer"); return -1; } } static PyMethodDef blob_methods[] = { {"read", (PyCFunction)pysqlite_blob_read, METH_VARARGS, PyDoc_STR("read data from blob")}, {"write", (PyCFunction)pysqlite_blob_write, METH_O, PyDoc_STR("write data to blob")}, {"close", (PyCFunction)pysqlite_blob_close, METH_NOARGS, PyDoc_STR("close blob")}, {"seek", (PyCFunction)pysqlite_blob_seek, METH_VARARGS, PyDoc_STR("change blob current offset")}, {"tell", (PyCFunction)pysqlite_blob_tell, METH_NOARGS, PyDoc_STR("return blob current offset")}, {"__enter__", (PyCFunction)pysqlite_blob_enter, METH_NOARGS, PyDoc_STR("blob context manager enter")}, {"__exit__", (PyCFunction)pysqlite_blob_exit, METH_VARARGS, PyDoc_STR("blob context manager exit")}, {NULL, NULL} }; static PySequenceMethods blob_sequence_methods = { .sq_length = (lenfunc)pysqlite_blob_length, .sq_concat = (binaryfunc)pysqlite_blob_concat, .sq_repeat = (ssizeargfunc)pysqlite_blob_repeat, .sq_item = (ssizeargfunc)pysqlite_blob_item, .sq_ass_item = (ssizeobjargproc)pysqlite_blob_ass_item, .sq_contains = (objobjproc)pysqlite_blob_contains, }; static PyMappingMethods blob_mapping_methods = { (lenfunc)pysqlite_blob_length, (binaryfunc)pysqlite_blob_subscript, (objobjargproc)pysqlite_blob_ass_subscript, }; PyTypeObject pysqlite_BlobType = { PyVarObject_HEAD_INIT(NULL, 0) MODULE_NAME ".Blob", .tp_basicsize = sizeof(pysqlite_Blob), .tp_dealloc = (destructor)pysqlite_blob_dealloc, .tp_as_sequence = &blob_sequence_methods, .tp_as_mapping = &blob_mapping_methods, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_weaklistoffset = offsetof(pysqlite_Blob, in_weakreflist), .tp_methods = blob_methods, }; extern int pysqlite_blob_setup_types(void) { pysqlite_BlobType.tp_new = PyType_GenericNew; return PyType_Ready(&pysqlite_BlobType); }