Add checks in mod_qcmd.
[doldaconnect.git] / lib / python / dolmod.c
CommitLineData
fbe30a6d 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
26struct respobj {
27 PyObject_HEAD
28 struct dc_response *resp;
29};
30
31static int fd = -1;
32
33static void resptype_del(struct respobj *self)
34{
35 dc_freeresp(self->resp);
36 self->ob_type->tp_free((PyObject *)self);
37}
38
39static PyObject *resp_getcode(struct respobj *self)
40{
41 return(Py_BuildValue("i", self->resp->code));
42}
43
44static PyObject *resp_gettag(struct respobj *self)
45{
46 return(Py_BuildValue("i", self->resp->tag));
47}
48
49static PyObject *resp_getcmd(struct respobj *self)
50{
51 return(PyUnicode_FromWideChar(self->resp->cmdname, wcslen(self->resp->cmdname)));
52}
53
54static 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
72static 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
101static 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
115static 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
125static 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
134static 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
151static 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
160static PyObject *mod_connected(PyObject *self, PyObject *args)
161{
162 if(fd == -1)
163 Py_RETURN_FALSE;
164 else
165 Py_RETURN_TRUE;
166}
167
168static 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
204static 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
221static 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
234static 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;
277c2137 244 ret = NULL;
fbe30a6d 245 for(i = 0; i < PySequence_Size(args); i++) {
277c2137 246 if((c = PySequence_GetItem(args, i)) == NULL)
247 goto out;
fbe30a6d 248 if(!PyUnicode_Check(c)) {
249 n = PyUnicode_FromObject(c);
250 Py_DECREF(c);
277c2137 251 if((c = n) == NULL)
252 goto out;
fbe30a6d 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");
277c2137 264 goto out;
fbe30a6d 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 }
277c2137 279
280out:
fbe30a6d 281 dc_freewcsarr(toks);
277c2137 282 if(cmd != NULL)
283 free(cmd);
fbe30a6d 284 return(ret);
285}
286
287static 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
325static 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
361static 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
385static PyMethodDef methods[] = {
386 {"connect", mod_connect, METH_VARARGS,
387 "Connect to a Dolda Connect server"},
388 {"disconnect", mod_disconnect, METH_VARARGS,
389 "Disconnect from the server"},
390 {"connected", mod_connected, METH_VARARGS,
391 "Return a boolean indicated whether there currently is a connection to a server"},
392 {"select", mod_select, METH_VARARGS,
393 "Handle data from the server connection, optionally blocking until something happens"},
394 {"getresp", mod_getresp, METH_VARARGS,
395 "Get a queued response object"},
396 {"qcmd", (PyCFunction)mod_qcmd, METH_VARARGS | METH_KEYWORDS,
397 "Queue a command to be sent to the server"},
398 {"loginasync", (PyCFunction)mod_loginasync, METH_VARARGS | METH_KEYWORDS,
399 "Perform an asynchronous login procedure"},
400 {"lexsexpr", mod_lexsexpr, METH_VARARGS,
401 "Use a standard algorithm to lex a search expression"},
402 {NULL, NULL, 0, NULL}
403};
404
405PyMODINIT_FUNC initdolmod(void)
406{
407 PyObject *m;
408
409 if(PyType_Ready(&resptype) < 0)
410 return;
411 m = Py_InitModule("dolmod", methods);
412 Py_INCREF(&resptype);
413 PyModule_AddObject(m, "Response", (PyObject *)&resptype);
414 dc_init();
415}