Add python module (note: must be compiled seperately)
authorfredrik <fredrik@959494ce-11ee-0310-bf91-de5d638817bd>
Wed, 22 Nov 2006 13:03:26 +0000 (13:03 +0000)
committerfredrik <fredrik@959494ce-11ee-0310-bf91-de5d638817bd>
Wed, 22 Nov 2006 13:03:26 +0000 (13:03 +0000)
git-svn-id: svn+ssh://svn.dolda2000.com/srv/svn/repos/src/doldaconnect@746 959494ce-11ee-0310-bf91-de5d638817bd

lib/python/dolcon/__init__.py [new file with mode: 0644]
lib/python/dolmod.c [new file with mode: 0644]
lib/python/setup.py [new file with mode: 0755]

diff --git a/lib/python/dolcon/__init__.py b/lib/python/dolcon/__init__.py
new file mode 100644 (file)
index 0000000..e7adb29
--- /dev/null
@@ -0,0 +1,41 @@
+from dolmod import *
+
+def login(useauthless = True, **kw):
+    result = [None]
+    def mycb(*v):
+        result[0] = v
+    loginasync(mycb, useauthless, **kw)
+    while result[0] is None:
+        select()
+    return result[0]
+
+def mustconnect(host, port = -1):
+    connect(host, port)
+    while True:
+        resp = getresp()
+        if resp is not None and resp.getcmd() == u".connect":
+            break
+        select()
+    if resp.getcode() != 200:
+        raise RuntimeError, resp.intresp()[0][0]
+
+def cnl(host, port = -1, useauthless = True, **kw):
+    mustconnect(host, port)
+    err, reason = login(useauthless, **kw)
+    if err != "success":
+        raise RuntimeError, (err, reason)
+    
+def ecmd(*args):
+    tag = qcmd(*args)
+    while True:
+        resp = getresp(tag)
+        if resp is not None:
+            break;
+        select()
+    return resp
+
+def ecmda(code, *args):
+    resp = ecmd(*args)
+    if resp.getcode() != code:
+        raise ValueError, resp.getcode()
+    return resp
diff --git a/lib/python/dolmod.c b/lib/python/dolmod.c
new file mode 100644 (file)
index 0000000..edae7ae
--- /dev/null
@@ -0,0 +1,409 @@
+/*
+ *  Dolda Connect - Modular multiuser Direct Connect-style client
+ *  Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *  
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+#include <Python.h>
+#include <sys/poll.h>
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/uimisc.h>
+#include <doldaconnect/utils.h>
+#include <wchar.h>
+
+struct respobj {
+    PyObject_HEAD
+    struct dc_response *resp;
+};
+
+static int fd = -1;
+
+static void resptype_del(struct respobj *self)
+{
+    dc_freeresp(self->resp);
+    self->ob_type->tp_free((PyObject *)self);
+}
+
+static PyObject *resp_getcode(struct respobj *self)
+{
+    return(Py_BuildValue("i", self->resp->code));
+}
+
+static PyObject *resp_gettag(struct respobj *self)
+{
+    return(Py_BuildValue("i", self->resp->tag));
+}
+
+static PyObject *resp_getcmd(struct respobj *self)
+{
+    return(PyUnicode_FromWideChar(self->resp->cmdname, wcslen(self->resp->cmdname)));
+}
+
+static PyObject *resp_extract(struct respobj *self)
+{
+    int i, o;
+    PyObject *ret, *sl;
+    wchar_t *c;
+    
+    ret = PyList_New(self->resp->numlines);
+    for(i = 0; i < self->resp->numlines; i++) {
+       sl = PyList_New(self->resp->rlines[i].argc);
+       for(o = 0; o < self->resp->rlines[i].argc; o++) {
+           c = self->resp->rlines[i].argv[o];
+           PyList_SetItem(sl, o, PyUnicode_FromWideChar(c, wcslen(c)));
+       }
+       PyList_SetItem(ret, i, sl);
+    }
+    return(ret);
+}
+
+static PyObject *resp_intresp(struct respobj *self)
+{
+    int i;
+    PyObject *ret, *sl;
+    struct dc_intresp *ires;
+    
+    ret = PyList_New(0);
+    self->resp->curline = 0;
+    while((ires = dc_interpret(self->resp)) != NULL) {
+       sl = PyList_New(ires->argc);
+       for(i = 0; i < ires->argc; i++) {
+           switch(ires->argv[i].type) {
+           case 1:
+               PyList_SetItem(sl, i, PyUnicode_FromWideChar(ires->argv[i].val.str, wcslen(ires->argv[i].val.str)));
+               break;
+           case 2:
+               PyList_SetItem(sl, i, PyInt_FromLong(ires->argv[i].val.num));
+               break;
+           case 3:
+               PyList_SetItem(sl, i, PyFloat_FromDouble(ires->argv[i].val.flnum));
+               break;
+           }
+       }
+       dc_freeires(ires);
+       PyList_Append(ret, sl);
+    }
+    return(ret);
+}
+
+static PyMethodDef respmethods[] = {
+    {"getcode", (PyCFunction)resp_getcode, METH_NOARGS,
+     "Get the numerical code from the response"},
+    {"gettag", (PyCFunction)resp_gettag, METH_NOARGS,
+     "Get the tag from the response"},
+    {"getcmd", (PyCFunction)resp_getcmd, METH_NOARGS,
+     "Get the command name from the response"},
+    {"extract", (PyCFunction)resp_extract, METH_NOARGS,
+     "Extract the lines and columns from the response"},
+    {"intresp", (PyCFunction)resp_intresp, METH_NOARGS,
+     "Interpret all the lines from the response with native datatypes"},
+    {NULL, NULL, 0, NULL}
+};
+
+static PyTypeObject resptype = {
+    PyObject_HEAD_INIT(NULL)
+    .tp_name = "dolcon.Response",
+    .tp_basicsize = sizeof(struct respobj),
+    .tp_flags = Py_TPFLAGS_DEFAULT,
+    .tp_doc = "Dolda Connect response objects",
+    .tp_dealloc = (destructor)resptype_del,
+    .tp_methods = respmethods,
+};
+
+static struct respobj *makeresp(struct dc_response *resp)
+{
+    struct respobj *new;
+    
+    new = (struct respobj *)resptype.tp_alloc(&resptype, 0);
+    new->resp = resp;
+    return(new);
+}
+
+static PyObject *mod_connect(PyObject *self, PyObject *args)
+{
+    char *host;
+    int port;
+    
+    port = -1;
+    if(!PyArg_ParseTuple(args, "s|i", &host, &port))
+       return(NULL);
+    if(fd >= 0)
+       dc_disconnect();
+    if((fd = dc_connect(host, port)) < 0) {
+       PyErr_SetFromErrno(PyExc_OSError);
+       return(NULL);
+    }
+    return(Py_BuildValue("i", fd));
+}
+
+static PyObject *mod_disconnect(PyObject *self, PyObject *args)
+{
+    if(fd != -1) {
+       dc_disconnect();
+       fd = -1;
+    }
+    Py_RETURN_NONE;
+}
+
+static PyObject *mod_connected(PyObject *self, PyObject *args)
+{
+    if(fd == -1)
+       Py_RETURN_FALSE;
+    else
+       Py_RETURN_TRUE;
+}
+
+static PyObject *mod_select(PyObject *self, PyObject *args)
+{
+    struct pollfd pfd;
+    int timeout, ret;
+    
+    timeout = -1;
+    if(!PyArg_ParseTuple(args, "|i", &timeout))
+       return(NULL);
+    if(fd < 0) {
+       PyErr_SetString(PyExc_RuntimeError, "Not connected");
+       return(NULL);
+    }
+    pfd.fd = fd;
+    pfd.events = POLLIN;
+    if(dc_wantwrite())
+       pfd.events |= POLLOUT;
+    if((ret = poll(&pfd, 1, timeout)) < 0) {
+       if(errno == EINTR)
+           Py_RETURN_FALSE;
+       dc_disconnect();
+       fd = -1;
+       PyErr_SetFromErrno(PyExc_OSError);
+       return(NULL);
+    }
+    if(((pfd.revents & POLLIN) && dc_handleread()) || ((pfd.revents & POLLOUT) && dc_handlewrite())) {
+       if(errno == 0) {
+           fd = -1;
+           Py_RETURN_FALSE;
+       }
+       PyErr_SetFromErrno(PyExc_OSError);
+    }
+    if(ret > 0)
+       Py_RETURN_TRUE;
+    Py_RETURN_FALSE;
+}
+
+static PyObject *mod_getresp(PyObject *self, PyObject *args)
+{
+    int tag;
+    struct dc_response *resp;
+    
+    tag = -1;
+    if(!PyArg_ParseTuple(args, "|i", &tag))
+       return(NULL);
+    if(tag == -1)
+       resp = dc_getresp();
+    else
+       resp = dc_gettaggedresp(tag);
+    if(resp == NULL)
+       Py_RETURN_NONE;
+    return((PyObject *)makeresp(resp));
+}
+
+static int qcmd_cb(struct dc_response *resp)
+{
+    PyObject *pycb, *args, *ret;
+    
+    pycb = resp->data;
+    args = Py_BuildValue("(N)", makeresp(resp));
+    ret = PyObject_Call(pycb, args, NULL);
+    Py_DECREF(args);
+    Py_DECREF(ret);
+    Py_DECREF(pycb);
+    return(2);
+}
+
+static PyObject *mod_qcmd(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    int i, tag;
+    wchar_t **toks, *tok, *cmd;
+    size_t tokssize, toksdata, toksize;
+    PyObject *c, *n, *cb, *ret;
+    
+    toks = NULL;
+    tokssize = toksdata = 0;
+    cmd = NULL;
+    for(i = 0; i < PySequence_Size(args); i++) {
+       c = PySequence_GetItem(args, i);
+       if(!PyUnicode_Check(c)) {
+           n = PyUnicode_FromObject(c);
+           Py_DECREF(c);
+           c = n;
+       }
+       tok = smalloc((toksize = (PyUnicode_GetSize(c) + 1)) * sizeof(*tok));
+       tok[PyUnicode_AsWideChar((PyUnicodeObject *)c, tok, toksize)] = L'\0';
+       Py_DECREF(c);
+       if(cmd == NULL)
+           cmd = tok;
+       else
+           addtobuf(toks, tok);
+    }
+    if(cmd == NULL) {
+       PyErr_SetString(PyExc_TypeError, "qcmd needs at least 1 argument");
+       return(NULL);
+    }
+    addtobuf(toks, NULL);
+    ret = NULL;
+    if(PyMapping_HasKeyString(kwargs, "cb")) {
+       cb = PyMapping_GetItemString(kwargs, "cb");
+       if(PyCallable_Check(cb)) {
+           ret = PyInt_FromLong(dc_queuecmd(qcmd_cb, cb, cmd, L"%%a", toks, NULL));
+       } else {
+           PyErr_SetString(PyExc_TypeError, "Callback must be callable");
+           Py_DECREF(cb);
+       }
+    } else {
+       ret = PyInt_FromLong(dc_queuecmd(NULL, NULL, cmd, L"%%a", toks, NULL));
+    }
+    dc_freewcsarr(toks);
+    free(cmd);
+    return(ret);
+}
+
+static void login_cb(int err, wchar_t *reason, PyObject *cb)
+{
+    char *errstr;
+    PyObject *args, *pyerr, *pyreason, *ret;
+    
+    switch(err) {
+    case DC_LOGIN_ERR_SUCCESS:
+       errstr = "success";
+       break;
+    case DC_LOGIN_ERR_NOLOGIN:
+       errstr = "nologin";
+       break;
+    case DC_LOGIN_ERR_SERVER:
+       errstr = "server";
+       break;
+    case DC_LOGIN_ERR_USER:
+       errstr = "user";
+       break;
+    case DC_LOGIN_ERR_CONV:
+       errstr = "conv";
+       break;
+    case DC_LOGIN_ERR_AUTHFAIL:
+       errstr = "authfail";
+       break;
+    }
+    pyerr = PyString_FromString(errstr);
+    if(reason == NULL)
+       Py_INCREF(pyreason = Py_None);
+    else
+       pyreason = PyUnicode_FromWideChar(reason, wcslen(reason));
+    args = PyTuple_Pack(2, pyerr, pyreason);
+    Py_DECREF(pyerr); Py_DECREF(pyreason);
+    ret = PyObject_Call(cb, args, NULL);
+    Py_DECREF(cb);
+    Py_DECREF(args);
+    Py_DECREF(ret);
+}
+
+static PyObject *mod_loginasync(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    int useauthless;
+    char *username;
+    PyObject *o, *cb, *conv;
+    
+    username = NULL;
+    conv = NULL;
+    useauthless = 1;
+    if(!PyArg_ParseTuple(args, "O|i", &cb, &useauthless))
+       return(NULL);
+    if(!PyCallable_Check(cb)) {
+       PyErr_SetString(PyExc_TypeError, "Callback must be callable");
+       return(NULL);
+    }
+    if(PyMapping_HasKeyString(kwargs, "username")) {
+       o = PyMapping_GetItemString(kwargs, "username");
+       username = PyString_AsString(o);
+       Py_DECREF(o);
+       if(username == NULL)
+           return(NULL);
+    }
+    if(PyMapping_HasKeyString(kwargs, "conv")) {
+       conv = PyMapping_GetItemString(kwargs, "conv");
+       if(!PyCallable_Check(conv)) {
+           PyErr_SetString(PyExc_TypeError, "Conv callback must be callable");
+           return(NULL);
+       }
+       PyErr_SetString(PyExc_NotImplementedError, "Custom conv functions are not yet supported by the Python interface");
+       return(NULL);
+    }
+    Py_INCREF(cb);
+    dc_loginasync(username, useauthless, NULL, (void (*)(int, wchar_t *, void *))login_cb, cb);
+    Py_RETURN_NONE;
+}
+
+static PyObject *mod_lexsexpr(PyObject *self, PyObject *args)
+{
+    PyObject *arg, *se, *ret;
+    wchar_t **arr, **ap, *buf;
+    size_t bufsize;
+    
+    if(!PyArg_ParseTuple(args, "O", &arg))
+       return(NULL);
+    if((se = PyUnicode_FromObject(arg)) == NULL)
+       return(NULL);
+    buf = smalloc((bufsize = (PyUnicode_GetSize(se) + 1)) * sizeof(*buf));
+    buf[PyUnicode_AsWideChar((PyUnicodeObject *)se, buf, bufsize)] = L'\0';
+    arr = dc_lexsexpr(buf);
+    free(buf);
+    Py_DECREF(se);
+    ret = PyList_New(0);
+    if(arr != NULL) {
+       for(ap = arr; *ap; ap++)
+           PyList_Append(ret, PyUnicode_FromWideChar(*ap, wcslen(*ap)));
+       dc_freewcsarr(arr);
+    }
+    return(ret);
+}
+
+static PyMethodDef methods[] = {
+    {"connect", mod_connect, METH_VARARGS,
+     "Connect to a Dolda Connect server"},
+    {"disconnect", mod_disconnect, METH_VARARGS,
+     "Disconnect from the server"},
+    {"connected", mod_connected, METH_VARARGS,
+     "Return a boolean indicated whether there currently is a connection to a server"},
+    {"select", mod_select, METH_VARARGS,
+     "Handle data from the server connection, optionally blocking until something happens"},
+    {"getresp", mod_getresp, METH_VARARGS,
+     "Get a queued response object"},
+    {"qcmd", (PyCFunction)mod_qcmd, METH_VARARGS | METH_KEYWORDS,
+     "Queue a command to be sent to the server"},
+    {"loginasync", (PyCFunction)mod_loginasync, METH_VARARGS | METH_KEYWORDS,
+     "Perform an asynchronous login procedure"},
+    {"lexsexpr", mod_lexsexpr, METH_VARARGS,
+     "Use a standard algorithm to lex a search expression"},
+    {NULL, NULL, 0, NULL}
+};
+
+PyMODINIT_FUNC initdolmod(void)
+{
+    PyObject *m;
+    
+    if(PyType_Ready(&resptype) < 0)
+       return;
+    m = Py_InitModule("dolmod", methods);
+    Py_INCREF(&resptype);
+    PyModule_AddObject(m, "Response", (PyObject *)&resptype);
+    dc_init();
+}
diff --git a/lib/python/setup.py b/lib/python/setup.py
new file mode 100755 (executable)
index 0000000..e2e20d0
--- /dev/null
@@ -0,0 +1,13 @@
+#!/usr/bin/python
+
+from distutils.core import setup, Extension
+
+dolmod = Extension("dolmod", ["dolmod.c"],
+                   libraries = ["dcui"])
+
+setup(name = "dolcon",
+      version = "0.3",
+      description = "Python glue module for libdolcon",
+      ext_modules = [dolmod],
+      packages = ["dolcon"],
+      license = "GPL-2")