compressionwriter.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. /**
  2. * Copyright (c) 2016-present, Gregory Szorc
  3. * All rights reserved.
  4. *
  5. * This software may be modified and distributed under the terms
  6. * of the BSD license. See the LICENSE file for details.
  7. */
  8. #include "python-zstandard.h"
  9. extern PyObject* ZstdError;
  10. PyDoc_STRVAR(ZstdCompresssionWriter__doc__,
  11. """A context manager used for writing compressed output to a writer.\n"
  12. );
  13. static void ZstdCompressionWriter_dealloc(ZstdCompressionWriter* self) {
  14. Py_XDECREF(self->compressor);
  15. Py_XDECREF(self->writer);
  16. PyMem_Free(self->output.dst);
  17. self->output.dst = NULL;
  18. PyObject_Del(self);
  19. }
  20. static PyObject* ZstdCompressionWriter_enter(ZstdCompressionWriter* self) {
  21. if (self->closed) {
  22. PyErr_SetString(PyExc_ValueError, "stream is closed");
  23. return NULL;
  24. }
  25. if (self->entered) {
  26. PyErr_SetString(ZstdError, "cannot __enter__ multiple times");
  27. return NULL;
  28. }
  29. self->entered = 1;
  30. Py_INCREF(self);
  31. return (PyObject*)self;
  32. }
  33. static PyObject* ZstdCompressionWriter_exit(ZstdCompressionWriter* self, PyObject* args) {
  34. PyObject* exc_type;
  35. PyObject* exc_value;
  36. PyObject* exc_tb;
  37. if (!PyArg_ParseTuple(args, "OOO:__exit__", &exc_type, &exc_value, &exc_tb)) {
  38. return NULL;
  39. }
  40. self->entered = 0;
  41. if (exc_type == Py_None && exc_value == Py_None && exc_tb == Py_None) {
  42. PyObject* result = PyObject_CallMethod((PyObject*)self, "close", NULL);
  43. if (NULL == result) {
  44. return NULL;
  45. }
  46. }
  47. Py_RETURN_FALSE;
  48. }
  49. static PyObject* ZstdCompressionWriter_memory_size(ZstdCompressionWriter* self) {
  50. return PyLong_FromSize_t(ZSTD_sizeof_CCtx(self->compressor->cctx));
  51. }
  52. static PyObject* ZstdCompressionWriter_write(ZstdCompressionWriter* self, PyObject* args, PyObject* kwargs) {
  53. static char* kwlist[] = {
  54. "data",
  55. NULL
  56. };
  57. PyObject* result = NULL;
  58. Py_buffer source;
  59. size_t zresult;
  60. ZSTD_inBuffer input;
  61. PyObject* res;
  62. Py_ssize_t totalWrite = 0;
  63. #if PY_MAJOR_VERSION >= 3
  64. if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y*:write",
  65. #else
  66. if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s*:write",
  67. #endif
  68. kwlist, &source)) {
  69. return NULL;
  70. }
  71. if (!PyBuffer_IsContiguous(&source, 'C') || source.ndim > 1) {
  72. PyErr_SetString(PyExc_ValueError,
  73. "data buffer should be contiguous and have at most one dimension");
  74. goto finally;
  75. }
  76. if (self->closed) {
  77. PyErr_SetString(PyExc_ValueError, "stream is closed");
  78. return NULL;
  79. }
  80. self->output.pos = 0;
  81. input.src = source.buf;
  82. input.size = source.len;
  83. input.pos = 0;
  84. while (input.pos < (size_t)source.len) {
  85. Py_BEGIN_ALLOW_THREADS
  86. zresult = ZSTD_compressStream2(self->compressor->cctx, &self->output, &input, ZSTD_e_continue);
  87. Py_END_ALLOW_THREADS
  88. if (ZSTD_isError(zresult)) {
  89. PyErr_Format(ZstdError, "zstd compress error: %s", ZSTD_getErrorName(zresult));
  90. goto finally;
  91. }
  92. /* Copy data from output buffer to writer. */
  93. if (self->output.pos) {
  94. #if PY_MAJOR_VERSION >= 3
  95. res = PyObject_CallMethod(self->writer, "write", "y#",
  96. #else
  97. res = PyObject_CallMethod(self->writer, "write", "s#",
  98. #endif
  99. self->output.dst, self->output.pos);
  100. Py_XDECREF(res);
  101. totalWrite += self->output.pos;
  102. self->bytesCompressed += self->output.pos;
  103. }
  104. self->output.pos = 0;
  105. }
  106. if (self->writeReturnRead) {
  107. result = PyLong_FromSize_t(input.pos);
  108. }
  109. else {
  110. result = PyLong_FromSsize_t(totalWrite);
  111. }
  112. finally:
  113. PyBuffer_Release(&source);
  114. return result;
  115. }
  116. static PyObject* ZstdCompressionWriter_flush(ZstdCompressionWriter* self, PyObject* args, PyObject* kwargs) {
  117. static char* kwlist[] = {
  118. "flush_mode",
  119. NULL
  120. };
  121. size_t zresult;
  122. ZSTD_inBuffer input;
  123. PyObject* res;
  124. Py_ssize_t totalWrite = 0;
  125. unsigned flush_mode = 0;
  126. ZSTD_EndDirective flush;
  127. if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|I:flush",
  128. kwlist, &flush_mode)) {
  129. return NULL;
  130. }
  131. switch (flush_mode) {
  132. case 0:
  133. flush = ZSTD_e_flush;
  134. break;
  135. case 1:
  136. flush = ZSTD_e_end;
  137. break;
  138. default:
  139. PyErr_Format(PyExc_ValueError, "unknown flush_mode: %d", flush_mode);
  140. return NULL;
  141. }
  142. if (self->closed) {
  143. PyErr_SetString(PyExc_ValueError, "stream is closed");
  144. return NULL;
  145. }
  146. self->output.pos = 0;
  147. input.src = NULL;
  148. input.size = 0;
  149. input.pos = 0;
  150. while (1) {
  151. Py_BEGIN_ALLOW_THREADS
  152. zresult = ZSTD_compressStream2(self->compressor->cctx, &self->output, &input, flush);
  153. Py_END_ALLOW_THREADS
  154. if (ZSTD_isError(zresult)) {
  155. PyErr_Format(ZstdError, "zstd compress error: %s", ZSTD_getErrorName(zresult));
  156. return NULL;
  157. }
  158. /* Copy data from output buffer to writer. */
  159. if (self->output.pos) {
  160. #if PY_MAJOR_VERSION >= 3
  161. res = PyObject_CallMethod(self->writer, "write", "y#",
  162. #else
  163. res = PyObject_CallMethod(self->writer, "write", "s#",
  164. #endif
  165. self->output.dst, self->output.pos);
  166. Py_XDECREF(res);
  167. totalWrite += self->output.pos;
  168. self->bytesCompressed += self->output.pos;
  169. }
  170. self->output.pos = 0;
  171. if (!zresult) {
  172. break;
  173. }
  174. }
  175. return PyLong_FromSsize_t(totalWrite);
  176. }
  177. static PyObject* ZstdCompressionWriter_close(ZstdCompressionWriter* self) {
  178. PyObject* result;
  179. if (self->closed) {
  180. Py_RETURN_NONE;
  181. }
  182. result = PyObject_CallMethod((PyObject*)self, "flush", "I", 1);
  183. self->closed = 1;
  184. if (NULL == result) {
  185. return NULL;
  186. }
  187. /* Call close on underlying stream as well. */
  188. if (PyObject_HasAttrString(self->writer, "close")) {
  189. return PyObject_CallMethod(self->writer, "close", NULL);
  190. }
  191. Py_RETURN_NONE;
  192. }
  193. static PyObject* ZstdCompressionWriter_fileno(ZstdCompressionWriter* self) {
  194. if (PyObject_HasAttrString(self->writer, "fileno")) {
  195. return PyObject_CallMethod(self->writer, "fileno", NULL);
  196. }
  197. else {
  198. PyErr_SetString(PyExc_OSError, "fileno not available on underlying writer");
  199. return NULL;
  200. }
  201. }
  202. static PyObject* ZstdCompressionWriter_tell(ZstdCompressionWriter* self) {
  203. return PyLong_FromUnsignedLongLong(self->bytesCompressed);
  204. }
  205. static PyObject* ZstdCompressionWriter_writelines(PyObject* self, PyObject* args) {
  206. PyErr_SetNone(PyExc_NotImplementedError);
  207. return NULL;
  208. }
  209. static PyObject* ZstdCompressionWriter_false(PyObject* self, PyObject* args) {
  210. Py_RETURN_FALSE;
  211. }
  212. static PyObject* ZstdCompressionWriter_true(PyObject* self, PyObject* args) {
  213. Py_RETURN_TRUE;
  214. }
  215. static PyObject* ZstdCompressionWriter_unsupported(PyObject* self, PyObject* args, PyObject* kwargs) {
  216. PyObject* iomod;
  217. PyObject* exc;
  218. iomod = PyImport_ImportModule("io");
  219. if (NULL == iomod) {
  220. return NULL;
  221. }
  222. exc = PyObject_GetAttrString(iomod, "UnsupportedOperation");
  223. if (NULL == exc) {
  224. Py_DECREF(iomod);
  225. return NULL;
  226. }
  227. PyErr_SetNone(exc);
  228. Py_DECREF(exc);
  229. Py_DECREF(iomod);
  230. return NULL;
  231. }
  232. static PyMethodDef ZstdCompressionWriter_methods[] = {
  233. { "__enter__", (PyCFunction)ZstdCompressionWriter_enter, METH_NOARGS,
  234. PyDoc_STR("Enter a compression context.") },
  235. { "__exit__", (PyCFunction)ZstdCompressionWriter_exit, METH_VARARGS,
  236. PyDoc_STR("Exit a compression context.") },
  237. { "close", (PyCFunction)ZstdCompressionWriter_close, METH_NOARGS, NULL },
  238. { "fileno", (PyCFunction)ZstdCompressionWriter_fileno, METH_NOARGS, NULL },
  239. { "isatty", (PyCFunction)ZstdCompressionWriter_false, METH_NOARGS, NULL },
  240. { "readable", (PyCFunction)ZstdCompressionWriter_false, METH_NOARGS, NULL },
  241. { "readline", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL },
  242. { "readlines", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL },
  243. { "seek", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL },
  244. { "seekable", ZstdCompressionWriter_false, METH_NOARGS, NULL },
  245. { "truncate", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL },
  246. { "writable", ZstdCompressionWriter_true, METH_NOARGS, NULL },
  247. { "writelines", ZstdCompressionWriter_writelines, METH_VARARGS, NULL },
  248. { "read", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL },
  249. { "readall", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL },
  250. { "readinto", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL },
  251. { "memory_size", (PyCFunction)ZstdCompressionWriter_memory_size, METH_NOARGS,
  252. PyDoc_STR("Obtain the memory size of the underlying compressor") },
  253. { "write", (PyCFunction)ZstdCompressionWriter_write, METH_VARARGS | METH_KEYWORDS,
  254. PyDoc_STR("Compress data") },
  255. { "flush", (PyCFunction)ZstdCompressionWriter_flush, METH_VARARGS | METH_KEYWORDS,
  256. PyDoc_STR("Flush data and finish a zstd frame") },
  257. { "tell", (PyCFunction)ZstdCompressionWriter_tell, METH_NOARGS,
  258. PyDoc_STR("Returns current number of bytes compressed") },
  259. { NULL, NULL }
  260. };
  261. static PyMemberDef ZstdCompressionWriter_members[] = {
  262. { "closed", T_BOOL, offsetof(ZstdCompressionWriter, closed), READONLY, NULL },
  263. { NULL }
  264. };
  265. PyTypeObject ZstdCompressionWriterType = {
  266. PyVarObject_HEAD_INIT(NULL, 0)
  267. "zstd.ZstdCompressionWriter", /* tp_name */
  268. sizeof(ZstdCompressionWriter), /* tp_basicsize */
  269. 0, /* tp_itemsize */
  270. (destructor)ZstdCompressionWriter_dealloc, /* tp_dealloc */
  271. 0, /* tp_print */
  272. 0, /* tp_getattr */
  273. 0, /* tp_setattr */
  274. 0, /* tp_compare */
  275. 0, /* tp_repr */
  276. 0, /* tp_as_number */
  277. 0, /* tp_as_sequence */
  278. 0, /* tp_as_mapping */
  279. 0, /* tp_hash */
  280. 0, /* tp_call */
  281. 0, /* tp_str */
  282. 0, /* tp_getattro */
  283. 0, /* tp_setattro */
  284. 0, /* tp_as_buffer */
  285. Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
  286. ZstdCompresssionWriter__doc__, /* tp_doc */
  287. 0, /* tp_traverse */
  288. 0, /* tp_clear */
  289. 0, /* tp_richcompare */
  290. 0, /* tp_weaklistoffset */
  291. 0, /* tp_iter */
  292. 0, /* tp_iternext */
  293. ZstdCompressionWriter_methods, /* tp_methods */
  294. ZstdCompressionWriter_members, /* tp_members */
  295. 0, /* tp_getset */
  296. 0, /* tp_base */
  297. 0, /* tp_dict */
  298. 0, /* tp_descr_get */
  299. 0, /* tp_descr_set */
  300. 0, /* tp_dictoffset */
  301. 0, /* tp_init */
  302. 0, /* tp_alloc */
  303. PyType_GenericNew, /* tp_new */
  304. };
  305. void compressionwriter_module_init(PyObject* mod) {
  306. Py_TYPE(&ZstdCompressionWriterType) = &PyType_Type;
  307. if (PyType_Ready(&ZstdCompressionWriterType) < 0) {
  308. return;
  309. }
  310. }