adf07a24d53d95664229be4ea731035462b94df4
[doldaconnect.git] / lib / python / dolmod.c
1 /*
2  *  Dolda Connect - Modular multiuser Direct Connect-style client
3  *  Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
4  *  
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *  
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *  
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19 #include <Python.h>
20 #include <sys/poll.h>
21 #include <doldaconnect/uilib.h>
22 #include <doldaconnect/uimisc.h>
23 #include <doldaconnect/utils.h>
24 #include <wchar.h>
25
26 struct respobj {
27     PyObject_HEAD
28     struct dc_response *resp;
29 };
30
31 static int fd = -1;
32
33 static void resptype_del(struct respobj *self)
34 {
35     dc_freeresp(self->resp);
36     self->ob_type->tp_free((PyObject *)self);
37 }
38
39 static PyObject *resp_getcode(struct respobj *self)
40 {
41     return(Py_BuildValue("i", self->resp->code));
42 }
43
44 static PyObject *resp_gettag(struct respobj *self)
45 {
46     return(Py_BuildValue("i", self->resp->tag));
47 }
48
49 static PyObject *resp_getcmd(struct respobj *self)
50 {
51     return(PyUnicode_FromWideChar(self->resp->cmdname, wcslen(self->resp->cmdname)));
52 }
53
54 static PyObject *resp_extract(struct respobj *self)
55 {
56     int i, o;
57     PyObject *ret, *sl;
58     wchar_t *c;
59     
60     ret = PyList_New(self->resp->numlines);
61     for(i = 0; i < self->resp->numlines; i++) {
62         sl = PyList_New(self->resp->rlines[i].argc);
63         for(o = 0; o < self->resp->rlines[i].argc; o++) {
64             c = self->resp->rlines[i].argv[o];
65             PyList_SetItem(sl, o, PyUnicode_FromWideChar(c, wcslen(c)));
66         }
67         PyList_SetItem(ret, i, sl);
68     }
69     return(ret);
70 }
71
72 static PyObject *resp_intresp(struct respobj *self)
73 {
74     int i;
75     PyObject *ret, *sl;
76     struct dc_intresp *ires;
77     
78     ret = PyList_New(0);
79     self->resp->curline = 0;
80     while((ires = dc_interpret(self->resp)) != NULL) {
81         sl = PyList_New(ires->argc);
82         for(i = 0; i < ires->argc; i++) {
83             switch(ires->argv[i].type) {
84             case 1:
85                 PyList_SetItem(sl, i, PyUnicode_FromWideChar(ires->argv[i].val.str, wcslen(ires->argv[i].val.str)));
86                 break;
87             case 2:
88                 PyList_SetItem(sl, i, PyInt_FromLong(ires->argv[i].val.num));
89                 break;
90             case 3:
91                 PyList_SetItem(sl, i, PyFloat_FromDouble(ires->argv[i].val.flnum));
92                 break;
93             }
94         }
95         dc_freeires(ires);
96         PyList_Append(ret, sl);
97     }
98     return(ret);
99 }
100
101 static PyMethodDef respmethods[] = {
102     {"getcode", (PyCFunction)resp_getcode, METH_NOARGS,
103      "Get the numerical code from the response"},
104     {"gettag", (PyCFunction)resp_gettag, METH_NOARGS,
105      "Get the tag from the response"},
106     {"getcmd", (PyCFunction)resp_getcmd, METH_NOARGS,
107      "Get the command name from the response"},
108     {"extract", (PyCFunction)resp_extract, METH_NOARGS,
109      "Extract the lines and columns from the response"},
110     {"intresp", (PyCFunction)resp_intresp, METH_NOARGS,
111      "Interpret all the lines from the response with native datatypes"},
112     {NULL, NULL, 0, NULL}
113 };
114
115 static PyTypeObject resptype = {
116     PyObject_HEAD_INIT(NULL)
117     .tp_name = "dolcon.Response",
118     .tp_basicsize = sizeof(struct respobj),
119     .tp_flags = Py_TPFLAGS_DEFAULT,
120     .tp_doc = "Dolda Connect response objects",
121     .tp_dealloc = (destructor)resptype_del,
122     .tp_methods = respmethods,
123 };
124
125 static struct respobj *makeresp(struct dc_response *resp)
126 {
127     struct respobj *new;
128     
129     new = (struct respobj *)resptype.tp_alloc(&resptype, 0);
130     new->resp = resp;
131     return(new);
132 }
133
134 static PyObject *mod_connect(PyObject *self, PyObject *args)
135 {
136     char *host;
137     int port;
138     
139     port = -1;
140     if(!PyArg_ParseTuple(args, "s|i", &host, &port))
141         return(NULL);
142     if(fd >= 0)
143         dc_disconnect();
144     if((fd = dc_connect(host, port)) < 0) {
145         PyErr_SetFromErrno(PyExc_OSError);
146         return(NULL);
147     }
148     return(Py_BuildValue("i", fd));
149 }
150
151 static PyObject *mod_disconnect(PyObject *self, PyObject *args)
152 {
153     if(fd != -1) {
154         dc_disconnect();
155         fd = -1;
156     }
157     Py_RETURN_NONE;
158 }
159
160 static PyObject *mod_connected(PyObject *self, PyObject *args)
161 {
162     if(fd == -1)
163         Py_RETURN_FALSE;
164     else
165         Py_RETURN_TRUE;
166 }
167
168 static PyObject *mod_select(PyObject *self, PyObject *args)
169 {
170     struct pollfd pfd;
171     int timeout, ret;
172     
173     timeout = -1;
174     if(!PyArg_ParseTuple(args, "|i", &timeout))
175         return(NULL);
176     if(fd < 0) {
177         PyErr_SetString(PyExc_RuntimeError, "Not connected");
178         return(NULL);
179     }
180     pfd.fd = fd;
181     pfd.events = POLLIN;
182     if(dc_wantwrite())
183         pfd.events |= POLLOUT;
184     if((ret = poll(&pfd, 1, timeout)) < 0) {
185         if(errno == EINTR)
186             Py_RETURN_FALSE;
187         dc_disconnect();
188         fd = -1;
189         PyErr_SetFromErrno(PyExc_OSError);
190         return(NULL);
191     }
192     if(((pfd.revents & POLLIN) && dc_handleread()) || ((pfd.revents & POLLOUT) && dc_handlewrite())) {
193         if(errno == 0) {
194             fd = -1;
195             Py_RETURN_FALSE;
196         }
197         PyErr_SetFromErrno(PyExc_OSError);
198     }
199     if(ret > 0)
200         Py_RETURN_TRUE;
201     Py_RETURN_FALSE;
202 }
203
204 static PyObject *mod_getresp(PyObject *self, PyObject *args)
205 {
206     int tag;
207     struct dc_response *resp;
208     
209     tag = -1;
210     if(!PyArg_ParseTuple(args, "|i", &tag))
211         return(NULL);
212     if(tag == -1)
213         resp = dc_getresp();
214     else
215         resp = dc_gettaggedresp(tag);
216     if(resp == NULL)
217         Py_RETURN_NONE;
218     return((PyObject *)makeresp(resp));
219 }
220
221 static int qcmd_cb(struct dc_response *resp)
222 {
223     PyObject *pycb, *args, *ret;
224     
225     pycb = resp->data;
226     args = Py_BuildValue("(N)", makeresp(resp));
227     ret = PyObject_Call(pycb, args, NULL);
228     Py_DECREF(args);
229     Py_DECREF(ret);
230     Py_DECREF(pycb);
231     return(2);
232 }
233
234 static PyObject *mod_qcmd(PyObject *self, PyObject *args, PyObject *kwargs)
235 {
236     int i, tag;
237     wchar_t **toks, *tok, *cmd;
238     size_t tokssize, toksdata, toksize;
239     PyObject *c, *n, *cb, *ret;
240     
241     toks = NULL;
242     tokssize = toksdata = 0;
243     cmd = NULL;
244     ret = NULL;
245     for(i = 0; i < PySequence_Size(args); i++) {
246         if((c = PySequence_GetItem(args, i)) == NULL)
247             goto out;
248         if(!PyUnicode_Check(c)) {
249             n = PyUnicode_FromObject(c);
250             Py_DECREF(c);
251             if((c = n) == NULL)
252                 goto out;
253         }
254         tok = smalloc((toksize = (PyUnicode_GetSize(c) + 1)) * sizeof(*tok));
255         tok[PyUnicode_AsWideChar((PyUnicodeObject *)c, tok, toksize)] = L'\0';
256         Py_DECREF(c);
257         if(cmd == NULL)
258             cmd = tok;
259         else
260             addtobuf(toks, tok);
261     }
262     if(cmd == NULL) {
263         PyErr_SetString(PyExc_TypeError, "qcmd needs at least 1 argument");
264         goto out;
265     }
266     addtobuf(toks, NULL);
267     ret = NULL;
268     if(PyMapping_HasKeyString(kwargs, "cb")) {
269         cb = PyMapping_GetItemString(kwargs, "cb");
270         if(PyCallable_Check(cb)) {
271             ret = PyInt_FromLong(dc_queuecmd(qcmd_cb, cb, cmd, L"%%a", toks, NULL));
272         } else {
273             PyErr_SetString(PyExc_TypeError, "Callback must be callable");
274             Py_DECREF(cb);
275         }
276     } else {
277         ret = PyInt_FromLong(dc_queuecmd(NULL, NULL, cmd, L"%%a", toks, NULL));
278     }
279
280 out:
281     dc_freewcsarr(toks);
282     if(cmd != NULL)
283         free(cmd);
284     return(ret);
285 }
286
287 static void login_cb(int err, wchar_t *reason, PyObject *cb)
288 {
289     char *errstr;
290     PyObject *args, *pyerr, *pyreason, *ret;
291     
292     switch(err) {
293     case DC_LOGIN_ERR_SUCCESS:
294         errstr = "success";
295         break;
296     case DC_LOGIN_ERR_NOLOGIN:
297         errstr = "nologin";
298         break;
299     case DC_LOGIN_ERR_SERVER:
300         errstr = "server";
301         break;
302     case DC_LOGIN_ERR_USER:
303         errstr = "user";
304         break;
305     case DC_LOGIN_ERR_CONV:
306         errstr = "conv";
307         break;
308     case DC_LOGIN_ERR_AUTHFAIL:
309         errstr = "authfail";
310         break;
311     }
312     pyerr = PyString_FromString(errstr);
313     if(reason == NULL)
314         Py_INCREF(pyreason = Py_None);
315     else
316         pyreason = PyUnicode_FromWideChar(reason, wcslen(reason));
317     args = PyTuple_Pack(2, pyerr, pyreason);
318     Py_DECREF(pyerr); Py_DECREF(pyreason);
319     ret = PyObject_Call(cb, args, NULL);
320     Py_DECREF(cb);
321     Py_DECREF(args);
322     Py_DECREF(ret);
323 }
324
325 static PyObject *mod_loginasync(PyObject *self, PyObject *args, PyObject *kwargs)
326 {
327     int useauthless;
328     char *username;
329     PyObject *o, *cb, *conv;
330     
331     username = NULL;
332     conv = NULL;
333     useauthless = 1;
334     if(!PyArg_ParseTuple(args, "O|i", &cb, &useauthless))
335         return(NULL);
336     if(!PyCallable_Check(cb)) {
337         PyErr_SetString(PyExc_TypeError, "Callback must be callable");
338         return(NULL);
339     }
340     if(PyMapping_HasKeyString(kwargs, "username")) {
341         o = PyMapping_GetItemString(kwargs, "username");
342         username = PyString_AsString(o);
343         Py_DECREF(o);
344         if(username == NULL)
345             return(NULL);
346     }
347     if(PyMapping_HasKeyString(kwargs, "conv")) {
348         conv = PyMapping_GetItemString(kwargs, "conv");
349         if(!PyCallable_Check(conv)) {
350             PyErr_SetString(PyExc_TypeError, "Conv callback must be callable");
351             return(NULL);
352         }
353         PyErr_SetString(PyExc_NotImplementedError, "Custom conv functions are not yet supported by the Python interface");
354         return(NULL);
355     }
356     Py_INCREF(cb);
357     dc_loginasync(username, useauthless, NULL, (void (*)(int, wchar_t *, void *))login_cb, cb);
358     Py_RETURN_NONE;
359 }
360
361 static PyObject *mod_lexsexpr(PyObject *self, PyObject *args)
362 {
363     PyObject *arg, *se, *ret;
364     wchar_t **arr, **ap, *buf;
365     size_t bufsize;
366     
367     if(!PyArg_ParseTuple(args, "O", &arg))
368         return(NULL);
369     if((se = PyUnicode_FromObject(arg)) == NULL)
370         return(NULL);
371     buf = smalloc((bufsize = (PyUnicode_GetSize(se) + 1)) * sizeof(*buf));
372     buf[PyUnicode_AsWideChar((PyUnicodeObject *)se, buf, bufsize)] = L'\0';
373     arr = dc_lexsexpr(buf);
374     free(buf);
375     Py_DECREF(se);
376     ret = PyList_New(0);
377     if(arr != NULL) {
378         for(ap = arr; *ap; ap++)
379             PyList_Append(ret, PyUnicode_FromWideChar(*ap, wcslen(*ap)));
380         dc_freewcsarr(arr);
381     }
382     return(ret);
383 }
384
385 static PyObject *mod_wantwrite(PyObject *self)
386 {
387     if(dc_wantwrite())
388         Py_RETURN_TRUE;
389     else
390         Py_RETURN_FALSE;
391 }
392
393 static PyMethodDef methods[] = {
394     {"connect", mod_connect, METH_VARARGS,
395      "Connect to a Dolda Connect server"},
396     {"disconnect", mod_disconnect, METH_VARARGS,
397      "Disconnect from the server"},
398     {"connected", mod_connected, METH_VARARGS,
399      "Return a boolean indicated whether there currently is a connection to a server"},
400     {"select", mod_select, METH_VARARGS,
401      "Handle data from the server connection, optionally blocking until something happens"},
402     {"getresp", mod_getresp, METH_VARARGS,
403      "Get a queued response object"},
404     {"qcmd", (PyCFunction)mod_qcmd, METH_VARARGS | METH_KEYWORDS,
405      "Queue a command to be sent to the server"},
406     {"loginasync", (PyCFunction)mod_loginasync, METH_VARARGS | METH_KEYWORDS,
407      "Perform an asynchronous login procedure"},
408     {"lexsexpr", mod_lexsexpr, METH_VARARGS,
409      "Use a standard algorithm to lex a search expression"},
410     {"wantwrite", (PyCFunction)mod_wantwrite, METH_NOARGS,
411      "Return a boolean indicating whether there is output to be fed to the server"},
412     {NULL, NULL, 0, NULL}
413 };
414
415 PyMODINIT_FUNC initdolmod(void)
416 {
417     PyObject *m;
418     
419     if(PyType_Ready(&resptype) < 0)
420         return;
421     m = Py_InitModule("dolmod", methods);
422     Py_INCREF(&resptype);
423     PyModule_AddObject(m, "Response", (PyObject *)&resptype);
424     dc_init();
425 }