/*
 * Copyright (c) 2016-2022 Satlab A/S <satlab@satlab.com>
 * All rights reserved. Do not distribute without permission.
 */
#define PY_SSIZE_T_CLEAN
#include <Python.h>

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <dirent.h>

#include <btp/client.h>
#include <btp/types.h>
#include <btp/error.h>

#define DEFAULT_BACKEND      "file"
#define DEFAULT_COUNT        25
#define DEFAULT_BSIZE        186
#define DEFAULT_TIMEOUT      1000
#define DEFAULT_TIMEOUT_CSUM (10 * DEFAULT_TIMEOUT)

static PyObject *BTPProtocolError;

/* PropNode object */
typedef struct {
	PyObject_HEAD uint8_t node;
	uint8_t port;
	uint32_t timeout;
	uint32_t options;
	unsigned int attempts;
	const char *backend;
	uint8_t bsize;
	uint32_t count;
	uint32_t timeout_csum;
	struct btp_context *btp;
} BTPNodeObject;

static int err_to_exception(const char *msg, int ret)
{
	switch (ret) {
	case BTP_EOK:
		break;
	case BTP_EINVAL:
		PyErr_Format(PyExc_ValueError, "%s: %s", msg, btp_error(ret));
		break;
	case BTP_ENOENT:
		PyErr_Format(PyExc_FileNotFoundError, "%s: %s", msg, btp_error(ret));
		break;
	case BTP_ENOSYS:
		PyErr_Format(PyExc_NotImplementedError, "%s: %s", msg, btp_error(ret));
		break;
	case BTP_ETIMEDOUT:
		PyErr_Format(PyExc_TimeoutError, "%s: %s", msg, btp_error(ret));
		break;
	case BTP_EPROTO:
		PyErr_Format(BTPProtocolError, "%s: %s", msg, btp_error(ret));
		break;
	case BTP_ENXIO:
		PyErr_Format(PyExc_OSError, "%s: %s", msg, btp_error(ret));
		break;
	case BTP_EIO:
		PyErr_Format(PyExc_IOError, "%s: %s", msg, btp_error(ret));
		break;
	case BTP_ENOMEM:
		PyErr_Format(PyExc_MemoryError, "%s: %s", msg, btp_error(ret));
		break;
	case BTP_ESTALE:
		PyErr_Format(PyExc_IOError, "%s: %s", msg, btp_error(ret));
		break;
	case BTP_EEXIST:
		PyErr_Format(PyExc_FileExistsError, "%s: %s", msg, btp_error(ret));
		break;
	case BTP_EINTR:
		PyErr_Format(PyExc_KeyboardInterrupt, "%s: %s", msg, btp_error(ret));
		break;
	case BTP_EUNKNOWN:
		PyErr_Format(PyExc_Exception, "%s: %s", msg, btp_error(ret));
		break;
	default:
		break;
	}

	return 0;
}

static int BTP_list_cb(struct btp_entryrep *erep, void *arg)
{
	int ret = 0;

	PyGILState_STATE gstate;

	/* Grab GIL before calling back into Python */
	gstate = PyGILState_Ensure();

	PyObject *objargs;

	/* Create tuple */
	objargs = Py_BuildValue("{s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:s}",
				"type", erep->etype,
				"mode", erep->mode,
				"nlink", erep->nlink,
				"uid", erep->uid,
				"gid", erep->gid,
				"size", erep->size,
				"mtime", erep->mtime,
				"name", erep->name);

	ret = PyList_Append(arg, objargs);
	Py_DECREF(objargs);

	/* Release GIL */
	PyGILState_Release(gstate);

	return ret;
}
static int BTPNode_init(BTPNodeObject *self, PyObject *args, PyObject *kwds)
{
	static const char *kwlist[] = {
		"node",
		"timeout",
		"port",
		"count",
		"bsize",
		"options",
		"attempts",
		"timeout_csum",
		"backend",
		NULL
	};

	self->timeout = DEFAULT_TIMEOUT;
	self->port = BTP_DEFAULT_PORT;
	self->count = DEFAULT_COUNT;
	self->bsize = DEFAULT_BSIZE;
	self->options = CSP_O_NONE;
	self->attempts = 3;
	self->timeout_csum = DEFAULT_TIMEOUT_CSUM;
	self->backend = DEFAULT_BACKEND;

	self->btp = NULL;

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "b|IbIbIIIs", (char **)kwlist,
					 &self->node,
					 &self->timeout,
					 &self->port,
					 &self->count,
					 &self->bsize,
					 &self->options,
					 &self->attempts,
					 &self->timeout_csum,
					 &self->backend))
		return -1;

	return 0;
}

static int btp_disconnect(BTPNodeObject *self)
{
	btp_client_disconnect(self->btp);
	self->btp = NULL;
	return BTP_EOK;
}

PyDoc_STRVAR(btp_push_doc,
	     "Push a file to remote.\n\n"
	     "Parameters:\n"
	     "localpath (string): Path to the file to upload.\n"
	     "remotepath (string): Absolute path where file should be uploaded to.\n"
	     "force (bool) (optional): If true, force upload even if transfer is already complete (default False).\n"
	     "count (int) (optional): Number of blocks to between acks (default 25).\n"
	     "bsize (int) (optional): Number of bytes to upload per block (default 186).\n"
	     "timeout (int) (optional): Timeout in milliseconds, (default 1000).\n"
	     "timeout_server (int) (optional): Server-side timeout in milliseconds (default 10000).\n"
	     "timeout_csum (int) (optional): Timeout for calculating checksum (default 10*timeout).\n"
	     "attempts (int) (optional): Number of times to try each BTP operation (default 3).\n"
	     "csp_options (int) (optional): Extra CSP connection options (default CSP_O_NONE).\n"
	     "port (int) (optional): CSP port on host with BTP server (default 13).\n"
	     "backend (string) (optional): Backend to upload to (default \'file\').\n\n"
	     "Returns:\n"
	     "0 on success, raises exception on failure.\n\n");

static PyObject *bind_btp_push(BTPNodeObject *self, PyObject *args, PyObject *kwds)
{
	int ret;

	char *localpath = NULL;
	char *remotepath = NULL;
	int force = 0;
	uint32_t count = self->count;
	uint8_t bsize = self->bsize;
	uint32_t timeout = DEFAULT_TIMEOUT;
	uint32_t timeout_server = 10 * DEFAULT_TIMEOUT;
	uint32_t timeout_csum = self->timeout_csum;
	unsigned int attempts = self->attempts;
	uint32_t csp_options = self->options;
	uint8_t port = self->port;
	const char *backend = self->backend;

	static const char *kwlist[] = {
		"localpath",
		"remotepath",
		"force",
		"count",
		"bsize",
		"timeout",
		"timeout_server",
		"timeout_csum",
		"attempts",
		"csp_options",
		"port",
		"backend",
		NULL
	};

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss|pIbIIIIIbs", (char **)kwlist,
					 &localpath,
					 &remotepath,
					 &force,
					 &count,
					 &bsize,
					 &timeout,
					 &timeout_server,
					 &timeout_csum,
					 &attempts,
					 &csp_options,
					 &port,
					 &backend))
		return NULL;

	/* Connect to server */
	Py_BEGIN_ALLOW_THREADS
	self->btp = btp_client_connect(self->node, port, timeout, csp_options, attempts);
	Py_END_ALLOW_THREADS
	if (!self->btp) {
		err_to_exception("Could not connect to server", BTP_EDISCON);
		return NULL;
	}

	Py_BEGIN_ALLOW_THREADS
	ret = btp_client_upload(self->btp, backend, remotepath, localpath, bsize, timeout_csum,
				timeout_server, force);
	Py_END_ALLOW_THREADS
	if (ret != BTP_EOK) {
		err_to_exception("Could not upload", ret);
		goto disconnect;
	}

	/* Early exit if transfer is already complete */
	if (btp_client_finished(self->btp))
		goto complete;

	/* Get initial status */
	Py_BEGIN_ALLOW_THREADS
	ret = btp_client_request_status(self->btp);
	Py_END_ALLOW_THREADS
	if (ret != BTP_EOK) {
		err_to_exception("Could not get status", ret);
		goto disconnect;
	}

	/* Send blocks until completion or error */
	while (!btp_client_finished(self->btp)) {

		/* check for ctrl+c */
		PyErr_CheckSignals();
		if (PyErr_Occurred() != NULL) {
			if (PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) {
				btp_client_complete(self->btp, timeout_csum);
				ret = BTP_EINTR;
				err_to_exception("Caught ctrl+c", ret);
				goto disconnect;
			}
		}

		/* Send blocks */
		Py_BEGIN_ALLOW_THREADS
		ret = btp_client_send_blocks(self->btp, BTP_OFFSET_NEXT, count, NULL, NULL);
		Py_END_ALLOW_THREADS
		if (ret != BTP_EOK) {
			err_to_exception("Could not send blocks", ret);
			goto disconnect;
		}

		/* Send status request */
		Py_BEGIN_ALLOW_THREADS
		ret = btp_client_request_status(self->btp);
		Py_END_ALLOW_THREADS
		if (ret != BTP_EOK) {
			err_to_exception("Could not get status", ret);
			goto disconnect;
		}
	}

complete:
	/* Complete transfer */
	ret = btp_client_complete(self->btp, timeout_csum);
	if (ret == BTP_ESTALE)
		printf("WARNING: checksum mismatch!\n");
disconnect:
	btp_disconnect(self);
	if (ret != BTP_EOK)
		return NULL;

	return Py_BuildValue("i", ret);
}

PyDoc_STRVAR(btp_pull_doc,
	     "Pull a file from remote.\n\n"
	     "Parameters:\n"
	     "remotepath (string): Absolute path to remote file to download.\n"
	     "localpath (string): Path to where downloaded file should be stored.\n"
	     "force (bool) (optional): If true, force upload even if transfer is already complete (default False).\n"
	     "count (int) (optional): Number of blocks to between acks (default 25).\n"
	     "bsize (int) (optional): Number of bytes to upload per block (default 186).\n"
	     "timeout (int) (optional): Timeout in milliseconds, (default 1000).\n"
	     "timeout_server (int) (optional): Server-side timeout in milliseconds (default 10000).\n"
	     "timeout_csum (int) (optional): Timeout for calculating checksum (default 10*timeout).\n"
	     "attempts (int) (optional): Number of times to try each BTP operation (default 3).\n"
	     "csp_options (int) (optional): Extra CSP connection options (default CSP_O_NONE).\n"
	     "port (int) (optional): CSP port on host with BTP server (default 13).\n"
	     "backend (string) (optional): Backend to download from (default \'file\').\n\n"
	     "Returns:\n"
	     "0 on success, raises exception on failure.\n\n");

static PyObject *bind_btp_pull(BTPNodeObject *self, PyObject *args, PyObject *kwds)
{
	int ret;

	char *localpath = NULL;
	char *remotepath = NULL;
	int force = 0;
	uint32_t count = self->count;
	uint8_t bsize = self->bsize;
	uint32_t timeout = DEFAULT_TIMEOUT;
	uint32_t timeout_server = 10 * DEFAULT_TIMEOUT;
	uint32_t timeout_csum = self->timeout_csum;
	unsigned int attempts = self->attempts;
	uint32_t csp_options = self->options;
	uint8_t port = self->port;
	const char *backend = self->backend;

	static const char *kwlist[] = {
		"remotepath",
		"localpath",
		"force",
		"count",
		"bsize",
		"timeout",
		"timeout_server",
		"timeout_csum",
		"attempts",
		"csp_options",
		"port",
		"backend",
		NULL
	};

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss|pIbIIIIIbs", (char **)kwlist,
					 &remotepath,
					 &localpath,
					 &force,
					 &count,
					 &bsize,
					 &timeout,
					 &timeout_server,
					 &timeout_csum,
					 &attempts,
					 &csp_options,
					 &port,
					 &backend))
		return NULL;

	/* Connect to server */
	Py_BEGIN_ALLOW_THREADS
	self->btp = btp_client_connect(self->node, port, timeout, csp_options, attempts);
	Py_END_ALLOW_THREADS
	if (!self->btp) {
		err_to_exception("Could not connect to server", BTP_EDISCON);
		return NULL;
	}

	/* Establish connection to server */
	Py_BEGIN_ALLOW_THREADS
	ret = btp_client_download(self->btp, backend, remotepath, localpath, bsize, timeout_csum,
				  timeout_server, force);
	Py_END_ALLOW_THREADS
	if (ret != BTP_EOK) {
		err_to_exception("Could not download", ret);
		goto disconnect;
	}

	/* Mark transfer done if requested file is empty */
	if (self->btp->block_status.blocks == 0)
		self->btp->block_status.complete = true;

	/* Early exit if transfer is already complete */
	if (btp_client_finished(self->btp))
		goto complete;

	/* Request blocks while file is incomplete */
	while (!btp_client_finished(self->btp)) {

		/* check for ctrl+c */
		PyErr_CheckSignals();
		if (PyErr_Occurred() != NULL) {
			if (PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) {
				btp_client_complete(self->btp, timeout_csum);
				ret = BTP_EINTR;
				err_to_exception("Caught ctrl+c", ret);
				goto disconnect;
			}
		}

		Py_BEGIN_ALLOW_THREADS
		ret = btp_client_send_status(self->btp);
		Py_END_ALLOW_THREADS
		if (ret != BTP_EOK) {
			err_to_exception("Could not send status", ret);
			goto disconnect;
		}

		Py_BEGIN_ALLOW_THREADS
		ret = btp_client_get_blocks(self->btp, BTP_OFFSET_NEXT, count, NULL, NULL);
		Py_END_ALLOW_THREADS
		if (ret != BTP_EOK) {
			err_to_exception("Could not get blocks", ret);
			goto disconnect;
		}
	}

complete:
	/* Complete transfer */
	Py_BEGIN_ALLOW_THREADS
	ret = btp_client_complete(self->btp, timeout_csum);
	Py_END_ALLOW_THREADS
	if (ret == BTP_ESTALE) {
		printf("WARNING: checksum mismatch!\n");
	} else if (ret != BTP_EOK) {
		printf("Could not complete %s: %s\n", remotepath, btp_error(ret));
	}

disconnect:
	btp_disconnect(self);

	if (ret != BTP_EOK)
		return NULL;

	return Py_BuildValue("i", ret);
}

PyDoc_STRVAR(btp_list_doc,
	     "List the files in a remote folder.\n\n"
	     "Parameters:\n"
	     "remotepath (string): Absolute path to remote directory to list.\n"
	     "count (int) (optional): Maximum number of files to list (default 25).\n"
	     "timeout (int) (optional): Timeout in milliseconds, (default 1000).\n"
	     "attempts (int) (optional): Number of times to try each BTP operation (default 3).\n"
	     "csp_options (int) (optional): Extra CSP connection options (default CSP_O_NONE).\n"
	     "port (int) (optional): CSP port on host with BTP server (default 13).\n"
	     "backend (string) (optional): Backend to list from (default \'file\').\n\n"
	     "Returns:\n"
	     "A list of tuples, each tuple containing information on a single file in the remote folder,\n"
	     "raises exception on failure.\n\n");

static PyObject *bind_btp_list(BTPNodeObject *self, PyObject *args, PyObject *kwds)
{
	int ret;

	char *remotepath = NULL;
	uint32_t count = self->count;
	uint32_t timeout = DEFAULT_TIMEOUT;
	unsigned int attempts = self->attempts;
	uint32_t csp_options = self->options;
	uint8_t port = self->port;
	const char *backend = self->backend;

	static const char *kwlist[] = { "remotepath", "count", "timeout", "attempts",
				  "csp_options", "port", "backend", NULL };

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|IIIIbs", (char **)kwlist, &remotepath, &count,
	                                 &timeout, &attempts, &csp_options, &port, &backend))
		return NULL;

	PyObject *retlist = PyList_New(0);
	if (!retlist) {
		PyErr_Format(PyExc_MemoryError, "Unable to allocate list");
		return NULL;
	}

	Py_BEGIN_ALLOW_THREADS
	self->btp = btp_client_connect(self->node, port, timeout, csp_options, attempts);
	Py_END_ALLOW_THREADS
	if (!self->btp) {
		err_to_exception("Could not connect to server", BTP_EDISCON);
		Py_DECREF(retlist);
		return NULL;
	}

	Py_BEGIN_ALLOW_THREADS
	ret = btp_client_list(self->btp, count, backend, remotepath, BTP_list_cb, retlist);
	btp_disconnect(self);
	Py_END_ALLOW_THREADS
	if (ret != BTP_EOK) {
		err_to_exception("Could not list", ret);
		Py_DECREF(retlist);
		return NULL;
	}

	return retlist;
}

PyDoc_STRVAR(btp_remove_doc,
	     "Remove a file from remote.\n\n"
	     "Parameters:\n"
	     "remotepath (string): Absolute path to remote file to remove.\n"
	     "timeout (int) (optional): Timeout in milliseconds, (default 1000).\n"
	     "attempts (int) (optional): Number of times to try each BTP operation (default 3).\n"
	     "csp_options (int) (optional): Extra CSP connection options (default CSP_O_NONE).\n"
	     "port (int) (optional): CSP port on host with BTP server (default 13).\n"
	     "backend (string) (optional): Backend to remove file from (default \'file\').\n\n"
	     "Returns:\n"
	     "0 on success, raises exception on failure.\n\n");

static PyObject *bind_btp_remove(BTPNodeObject *self, PyObject *args, PyObject *kwds)
{
	int ret;

	char *remotepath = NULL;
	uint32_t timeout = DEFAULT_TIMEOUT;
	unsigned int attempts = self->attempts;
	uint32_t csp_options = self->options;
	uint8_t port = self->port;
	const char *backend = self->backend;

	static const char *kwlist[] = { "remotepath", "timeout", "attempts",
				  "csp_options", "port", "backend", NULL };

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|IIIbs", (char **)kwlist, &remotepath, &timeout,
	                                 &attempts, &csp_options, &port, &backend))
		return NULL;

	Py_BEGIN_ALLOW_THREADS
	self->btp = btp_client_connect(self->node, port, timeout, csp_options, attempts);
	Py_END_ALLOW_THREADS
	if (!self->btp) {
		err_to_exception("Could not connect to server", BTP_EDISCON);
		return NULL;
	}

	Py_BEGIN_ALLOW_THREADS
	ret = btp_client_remove(self->btp, backend, remotepath);
	btp_disconnect(self);
	Py_END_ALLOW_THREADS
	if (ret != BTP_EOK) {
		err_to_exception("Could not remove", ret);
		return NULL;
	}

	return Py_BuildValue("i", ret);
}

PyDoc_STRVAR(btp_move_doc,
	     "Move a file on remote.\n\n"
	     "Parameters:\n"
	     "frompath (string): Absolute path to file to move.\n"
	     "topath (string): Absolute path where file should be moved.\n"
	     "timeout (int) (optional): Timeout in milliseconds, (default 1000).\n"
	     "attempts (int) (optional): Number of times to try each BTP operation (default 3).\n"
	     "csp_options (int) (optional): Extra CSP connection options (default CSP_O_NONE).\n"
	     "port (int) (optional): CSP port on host with BTP server (default 13).\n"
	     "backend (string) (optional): Backend to move on (default \'file\').\n\n"
	     "Returns:\n"
	     "0 on success, raises exception on failure.\n\n");

static PyObject *bind_btp_move(BTPNodeObject *self, PyObject *args, PyObject *kwds)
{
	int ret;

	char *frompath = NULL;
	char *topath = NULL;
	uint32_t timeout = DEFAULT_TIMEOUT;
	unsigned int attempts = self->attempts;
	uint32_t csp_options = self->options;
	uint8_t port = self->port;
	const char *backend = self->backend;

	static const char *kwlist[] = { "frompath", "topath", "timeout", "attempts",
				  "csp_options", "port", "backend", NULL };

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss|IIIbs", (char **)kwlist, &frompath, &topath,
					 &timeout, &attempts, &csp_options, &port, &backend))
		return NULL;

	Py_BEGIN_ALLOW_THREADS
	self->btp = btp_client_connect(self->node, port, timeout, csp_options, attempts);
	Py_END_ALLOW_THREADS
	if (!self->btp) {
		err_to_exception("Could not connect to server", BTP_EDISCON);
		return NULL;
	}

	Py_BEGIN_ALLOW_THREADS
	ret = btp_client_move(self->btp, backend, frompath, topath);
	btp_disconnect(self);
	Py_END_ALLOW_THREADS
	if (ret != BTP_EOK) {
		err_to_exception("Could not move", ret);
		return NULL;
	}

	return Py_BuildValue("i", ret);
}

PyDoc_STRVAR(btp_copy_doc,
	     "Copy a file on remote.\n\n"
	     "Parameters:\n"
	     "frompath (string): Absolute path to file to copy.\n"
	     "topath (string): Absolute path where file should be copied.\n"
	     "timeout (int) (optional): Timeout in milliseconds, (default 1000).\n"
	     "attempts (int) (optional): Number of times to try each BTP operation (default 3).\n"
	     "csp_options (int) (optional): Extra CSP connection options (default CSP_O_NONE).\n"
	     "port (int) (optional): CSP port on host with BTP server (default 13).\n"
	     "backend (string) (optional): Backend to copy on (default \'file\').\n\n"
	     "Returns:\n"
	     "0 on success, raises exception on failure.\n\n");

static PyObject *bind_btp_copy(BTPNodeObject *self, PyObject *args, PyObject *kwds)
{
	int ret;

	char *frompath = NULL;
	char *topath = NULL;
	uint32_t timeout = DEFAULT_TIMEOUT;
	unsigned int attempts = self->attempts;
	uint32_t csp_options = self->options;
	uint8_t port = self->port;
	const char *backend = self->backend;

	static const char *kwlist[] = { "frompath", "topath", "timeout", "attempts",
				  "csp_options", "port", "backend", NULL };

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss|IIIbs", (char **)kwlist, &frompath, &topath,
					 &timeout, &attempts, &csp_options, &port, &backend))
		return NULL;

	Py_BEGIN_ALLOW_THREADS
	self->btp = btp_client_connect(self->node, port, timeout, csp_options, attempts);
	Py_END_ALLOW_THREADS
	if (!self->btp) {
		err_to_exception("Could not connect to server", BTP_EDISCON);
		return NULL;
	}

	Py_BEGIN_ALLOW_THREADS
	ret = btp_client_copy(self->btp, backend, frompath, topath);
	btp_disconnect(self);
	Py_END_ALLOW_THREADS
	if (ret != BTP_EOK) {
		err_to_exception("Could not copy", ret);
		return NULL;
	}

	return Py_BuildValue("i", ret);
}

PyDoc_STRVAR(btp_shell_doc,
	     "Run a shell command on remote.\n\n"
	     "Parameters:\n"
	     "cmd (string): Command to execute.\n"
	     "timeout (int) (optional): Timeout in milliseconds, (default 1000).\n"
	     "count (int) (optional): Maximum number of reply packets (default 25).\n"
	     "attempts (int) (optional): Number of times to try each BTP operation (default 3).\n"
	     "csp_options (int) (optional): Extra CSP connection options (default CSP_O_NONE).\n"
	     "port (int) (optional): CSP port on host with BTP server (default 13).\n\n"
	     "Returns:\n"
	     "0 on success, raises exception on failure.\n\n");

static PyObject *bind_btp_shell(BTPNodeObject *self, PyObject *args, PyObject *kwds)
{
	int ret;

	char *cmd;
	bool singlechar = false;
	uint32_t count = self->count;
	uint32_t timeout = DEFAULT_TIMEOUT;
	unsigned int attempts = self->attempts;
	uint32_t csp_options = self->options;
	uint8_t port = self->port;

	static const char *kwlist[] = { "cmd", "timeout", "count", "attempts",
				  "csp_options", "port", NULL };

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|IIIb", (char **)kwlist, &cmd, &timeout,
					 &count, &attempts, &csp_options, &port))
		return NULL;

	Py_BEGIN_ALLOW_THREADS
	self->btp = btp_client_connect(self->node, port, timeout, csp_options, attempts);
	Py_END_ALLOW_THREADS
	if (!self->btp) {
		err_to_exception("Could not connect to server", BTP_EDISCON);
		return NULL;
	}

	Py_BEGIN_ALLOW_THREADS
	ret = btp_client_shell(self->btp, count, cmd, singlechar);
	btp_disconnect(self);
	Py_END_ALLOW_THREADS
	if (ret != BTP_EOK) {
		err_to_exception("Could not execute", ret);
		return NULL;
	}

	return Py_BuildValue("i", ret);
}

PyDoc_STRVAR(btp_mkdir_doc,
	     "Create a directory on remote.\n\n"
	     "Parameters:\n"
	     "remotepath (string): absolute path to the directory to create.\n"
	     "timeout (int) (optional): Timeout in milliseconds, (default 1000).\n"
	     "attempts (int) (optional): Number of times to try each BTP operation (default 3).\n"
	     "csp_options (int) (optional): Extra CSP connection options (default CSP_O_NONE).\n"
	     "port (int) (optional): CSP port on host with BTP server (default 13).\n"
	     "backend (string) (optional): Backend to make directory on (default \'file\').\n\n"
	     "Returns:\n"
	     "0 on success, raises exception on failure.\n\n");

static PyObject *bind_btp_mkdir(BTPNodeObject *self, PyObject *args, PyObject *kwds)
{
	int ret;

	char *remotepath = NULL;
	uint32_t timeout = DEFAULT_TIMEOUT;
	unsigned int attempts = self->attempts;
	uint32_t csp_options = self->options;
	uint8_t port = self->port;
	const char *backend = self->backend;

	static const char *kwlist[] = { "remotepath", "timeout", "attempts",
				  "csp_options", "port", "backend", NULL };

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|IIIbs", (char **)kwlist, &remotepath,
					 &timeout, &attempts, &csp_options, &port, &backend))
		return NULL;

	Py_BEGIN_ALLOW_THREADS
	self->btp = btp_client_connect(self->node, port, timeout, csp_options, attempts);
	Py_END_ALLOW_THREADS
	if (!self->btp) {
		err_to_exception("Could not connect to server", BTP_EDISCON);
		return NULL;
	}

	Py_BEGIN_ALLOW_THREADS
	ret = btp_client_mkdir(self->btp, backend, remotepath);
	btp_disconnect(self);
	Py_END_ALLOW_THREADS
	if (ret != BTP_EOK) {
		err_to_exception("Could not mkdir", ret);
		return NULL;
	}

	return Py_BuildValue("i", ret);
}

PyDoc_STRVAR(btp_rmdir_doc,
	     "Remove a directory on remote.\n\n"
	     "Parameters:\n"
	     "remotepath (string): absolute path to the directory to remove.\n"
	     "timeout (int) (optional): Timeout in milliseconds, (default 1000).\n"
	     "attempts (int) (optional): Number of times to try each BTP operation (default 3).\n"
	     "csp_options (int) (optional): Extra CSP connection options (default CSP_O_NONE).\n"
	     "port (int) (optional): CSP port on host with BTP server (default 13).\n"
	     "backend (string) (optional): Backend to remove directory from (default \'file\').\n\n"
	     "Returns:\n"
	     "0 on success, raises exception on failure.\n\n");

static PyObject *bind_btp_rmdir(BTPNodeObject *self, PyObject *args, PyObject *kwds)
{
	int ret;

	char *remotepath = NULL;
	uint32_t timeout = DEFAULT_TIMEOUT;
	unsigned int attempts = self->attempts;
	uint32_t csp_options = self->options;
	uint8_t port = self->port;
	const char *backend = self->backend;

	static const char *kwlist[] = { "remotepath", "timeout", "attempts",
				  "csp_options", "port", "backend", NULL };

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|IIIbs", (char **)kwlist, &remotepath,
					 &timeout, &attempts, &csp_options, &port, &backend))
		return NULL;

	Py_BEGIN_ALLOW_THREADS
	self->btp = btp_client_connect(self->node, port, timeout, csp_options, attempts);
	Py_END_ALLOW_THREADS
	if (!self->btp) {
		err_to_exception("Could not connect to server", BTP_EDISCON);
		return NULL;
	}

	Py_BEGIN_ALLOW_THREADS
	ret = btp_client_rmdir(self->btp, backend, remotepath);
	btp_disconnect(self);
	Py_END_ALLOW_THREADS
	if (ret != BTP_EOK) {
		err_to_exception("Could not rmdir", ret);
		return NULL;
	}

	return Py_BuildValue("i", ret);
}

static PyMethodDef BTPNode_methods[] = {
	{ "push",   (PyCFunction)(void(*)(void))bind_btp_push,   METH_VARARGS | METH_KEYWORDS, btp_push_doc},
	{ "pull",   (PyCFunction)(void(*)(void))bind_btp_pull,   METH_VARARGS | METH_KEYWORDS, btp_pull_doc},
	{ "list",   (PyCFunction)(void(*)(void))bind_btp_list,   METH_VARARGS | METH_KEYWORDS, btp_list_doc},
	{ "remove", (PyCFunction)(void(*)(void))bind_btp_remove, METH_VARARGS | METH_KEYWORDS, btp_remove_doc},
	{ "move",   (PyCFunction)(void(*)(void))bind_btp_move,   METH_VARARGS | METH_KEYWORDS, btp_move_doc},
	{ "copy",   (PyCFunction)(void(*)(void))bind_btp_copy,   METH_VARARGS | METH_KEYWORDS, btp_copy_doc},
	{ "shell",  (PyCFunction)(void(*)(void))bind_btp_shell,  METH_VARARGS | METH_KEYWORDS, btp_shell_doc},
	{ "mkdir",  (PyCFunction)(void(*)(void))bind_btp_mkdir,  METH_VARARGS | METH_KEYWORDS, btp_mkdir_doc},
	{ "rmdir",  (PyCFunction)(void(*)(void))bind_btp_rmdir,  METH_VARARGS | METH_KEYWORDS, btp_rmdir_doc},
	{ NULL, NULL, 0, NULL } /* Sentinel */
};

static PyTypeObject BTPNodeType = {
	PyVarObject_HEAD_INIT(NULL, 0)
	.tp_name = "btp_client.BtpNode",
	.tp_doc = "BTPNode object",
	.tp_basicsize = sizeof(BTPNodeObject),
	.tp_itemsize = 0,
	.tp_flags = Py_TPFLAGS_DEFAULT,
	.tp_new = PyType_GenericNew,
	.tp_init = (initproc)BTPNode_init,
	.tp_methods = BTPNode_methods,
};

/* Module definition */
static struct PyModuleDef btp_client_module = {
	.m_base = PyModuleDef_HEAD_INIT,
	.m_name = "btp_client",
	.m_size = -1,
};

PyMODINIT_FUNC PyInit_btp_client_bindings(void)
{
	if (PyType_Ready(&BTPNodeType) < 0)
		return NULL;

	/* Create module */
	PyObject *m = PyModule_Create(&btp_client_module);
	if (!m)
		return NULL;

	/* Add PropNode object */
	Py_INCREF(&BTPNodeType);
	if (PyModule_AddObject(m, "BTPNode", (PyObject *)&BTPNodeType) < 0) {
		Py_DECREF(&BTPNodeType);
		Py_DECREF(m);
		return NULL;
	}

	BTPProtocolError = PyErr_NewException("btp_client.ProtocolError", NULL, NULL);
	Py_XINCREF(BTPProtocolError);
	if (PyModule_AddObject(m, "BTPProtocolError", BTPProtocolError) < 0) {
		Py_XDECREF(BTPProtocolError);
		Py_CLEAR(BTPProtocolError);
		Py_DECREF(&BTPNodeType);
		Py_DECREF(m);
		return NULL;
	}

	return m;
}
