Ported the Python module for usage of 64-bit numbers.
[doldaconnect.git] / lib / python / dolmod.c
CommitLineData
fbe30a6d 1/*
2 * Dolda Connect - Modular multiuser Direct Connect-style client
302a2600 3 * Copyright (C) 2004 Fredrik Tolf <fredrik@dolda2000.com>
fbe30a6d 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;
32a93257
FT
93 case 4:
94 PyList_SetItem(sl, i, PyLong_FromLongLong(ires->argv[i].val.lnum));
95 break;
fbe30a6d 96 }
97 }
98 dc_freeires(ires);
99 PyList_Append(ret, sl);
100 }
101 return(ret);
102}
103
104static PyMethodDef respmethods[] = {
105 {"getcode", (PyCFunction)resp_getcode, METH_NOARGS,
106 "Get the numerical code from the response"},
107 {"gettag", (PyCFunction)resp_gettag, METH_NOARGS,
108 "Get the tag from the response"},
109 {"getcmd", (PyCFunction)resp_getcmd, METH_NOARGS,
110 "Get the command name from the response"},
111 {"extract", (PyCFunction)resp_extract, METH_NOARGS,
112 "Extract the lines and columns from the response"},
113 {"intresp", (PyCFunction)resp_intresp, METH_NOARGS,
114 "Interpret all the lines from the response with native datatypes"},
115 {NULL, NULL, 0, NULL}
116};
117
118static PyTypeObject resptype = {
119 PyObject_HEAD_INIT(NULL)
120 .tp_name = "dolcon.Response",
121 .tp_basicsize = sizeof(struct respobj),
122 .tp_flags = Py_TPFLAGS_DEFAULT,
123 .tp_doc = "Dolda Connect response objects",
124 .tp_dealloc = (destructor)resptype_del,
125 .tp_methods = respmethods,
126};
127
128static struct respobj *makeresp(struct dc_response *resp)
129{
130 struct respobj *new;
131
132 new = (struct respobj *)resptype.tp_alloc(&resptype, 0);
133 new->resp = resp;
134 return(new);
135}
136
137static PyObject *mod_connect(PyObject *self, PyObject *args)
138{
139 char *host;
fbe30a6d 140
12383d48 141 host = NULL;
142 if(!PyArg_ParseTuple(args, "|s", &host))
fbe30a6d 143 return(NULL);
144 if(fd >= 0)
145 dc_disconnect();
12383d48 146 if((fd = dc_connect(host)) < 0) {
fbe30a6d 147 PyErr_SetFromErrno(PyExc_OSError);
148 return(NULL);
149 }
150 return(Py_BuildValue("i", fd));
151}
152
153static PyObject *mod_disconnect(PyObject *self, PyObject *args)
154{
155 if(fd != -1) {
156 dc_disconnect();
157 fd = -1;
158 }
159 Py_RETURN_NONE;
160}
161
162static PyObject *mod_connected(PyObject *self, PyObject *args)
163{
164 if(fd == -1)
165 Py_RETURN_FALSE;
166 else
167 Py_RETURN_TRUE;
168}
169
170static PyObject *mod_select(PyObject *self, PyObject *args)
171{
172 struct pollfd pfd;
173 int timeout, ret;
174
175 timeout = -1;
176 if(!PyArg_ParseTuple(args, "|i", &timeout))
177 return(NULL);
178 if(fd < 0) {
179 PyErr_SetString(PyExc_RuntimeError, "Not connected");
180 return(NULL);
181 }
182 pfd.fd = fd;
183 pfd.events = POLLIN;
184 if(dc_wantwrite())
185 pfd.events |= POLLOUT;
186 if((ret = poll(&pfd, 1, timeout)) < 0) {
187 if(errno == EINTR)
188 Py_RETURN_FALSE;
189 dc_disconnect();
190 fd = -1;
191 PyErr_SetFromErrno(PyExc_OSError);
192 return(NULL);
193 }
194 if(((pfd.revents & POLLIN) && dc_handleread()) || ((pfd.revents & POLLOUT) && dc_handlewrite())) {
48cff6fe
FT
195 fd = -1;
196 if(errno == 0)
fbe30a6d 197 Py_RETURN_FALSE;
fbe30a6d 198 PyErr_SetFromErrno(PyExc_OSError);
48cff6fe 199 return(NULL);
fbe30a6d 200 }
201 if(ret > 0)
202 Py_RETURN_TRUE;
203 Py_RETURN_FALSE;
204}
205
206static PyObject *mod_getresp(PyObject *self, PyObject *args)
207{
208 int tag;
209 struct dc_response *resp;
210
211 tag = -1;
212 if(!PyArg_ParseTuple(args, "|i", &tag))
213 return(NULL);
214 if(tag == -1)
215 resp = dc_getresp();
216 else
217 resp = dc_gettaggedresp(tag);
218 if(resp == NULL)
219 Py_RETURN_NONE;
220 return((PyObject *)makeresp(resp));
221}
222
223static int qcmd_cb(struct dc_response *resp)
224{
225 PyObject *pycb, *args, *ret;
226
227 pycb = resp->data;
228 args = Py_BuildValue("(N)", makeresp(resp));
229 ret = PyObject_Call(pycb, args, NULL);
230 Py_DECREF(args);
231 Py_DECREF(ret);
232 Py_DECREF(pycb);
233 return(2);
234}
235
236static PyObject *mod_qcmd(PyObject *self, PyObject *args, PyObject *kwargs)
237{
238 int i, tag;
239 wchar_t **toks, *tok, *cmd;
240 size_t tokssize, toksdata, toksize;
241 PyObject *c, *n, *cb, *ret;
242
243 toks = NULL;
244 tokssize = toksdata = 0;
245 cmd = NULL;
277c2137 246 ret = NULL;
fbe30a6d 247 for(i = 0; i < PySequence_Size(args); i++) {
277c2137 248 if((c = PySequence_GetItem(args, i)) == NULL)
249 goto out;
fbe30a6d 250 if(!PyUnicode_Check(c)) {
251 n = PyUnicode_FromObject(c);
252 Py_DECREF(c);
277c2137 253 if((c = n) == NULL)
254 goto out;
fbe30a6d 255 }
256 tok = smalloc((toksize = (PyUnicode_GetSize(c) + 1)) * sizeof(*tok));
257 tok[PyUnicode_AsWideChar((PyUnicodeObject *)c, tok, toksize)] = L'\0';
258 Py_DECREF(c);
259 if(cmd == NULL)
260 cmd = tok;
261 else
262 addtobuf(toks, tok);
263 }
264 if(cmd == NULL) {
265 PyErr_SetString(PyExc_TypeError, "qcmd needs at least 1 argument");
277c2137 266 goto out;
fbe30a6d 267 }
268 addtobuf(toks, NULL);
269 ret = NULL;
270 if(PyMapping_HasKeyString(kwargs, "cb")) {
271 cb = PyMapping_GetItemString(kwargs, "cb");
272 if(PyCallable_Check(cb)) {
0931eb36 273 ret = PyInt_FromLong(dc_queuecmd(qcmd_cb, cb, cmd, L"%a", toks, NULL));
fbe30a6d 274 } else {
275 PyErr_SetString(PyExc_TypeError, "Callback must be callable");
276 Py_DECREF(cb);
277 }
278 } else {
0931eb36 279 ret = PyInt_FromLong(dc_queuecmd(NULL, NULL, cmd, L"%a", toks, NULL));
fbe30a6d 280 }
277c2137 281
282out:
fbe30a6d 283 dc_freewcsarr(toks);
277c2137 284 if(cmd != NULL)
285 free(cmd);
fbe30a6d 286 return(ret);
287}
288
289static void login_cb(int err, wchar_t *reason, PyObject *cb)
290{
291 char *errstr;
292 PyObject *args, *pyerr, *pyreason, *ret;
293
294 switch(err) {
295 case DC_LOGIN_ERR_SUCCESS:
296 errstr = "success";
297 break;
298 case DC_LOGIN_ERR_NOLOGIN:
299 errstr = "nologin";
300 break;
301 case DC_LOGIN_ERR_SERVER:
302 errstr = "server";
303 break;
304 case DC_LOGIN_ERR_USER:
305 errstr = "user";
306 break;
307 case DC_LOGIN_ERR_CONV:
308 errstr = "conv";
309 break;
310 case DC_LOGIN_ERR_AUTHFAIL:
311 errstr = "authfail";
312 break;
313 }
314 pyerr = PyString_FromString(errstr);
315 if(reason == NULL)
316 Py_INCREF(pyreason = Py_None);
317 else
318 pyreason = PyUnicode_FromWideChar(reason, wcslen(reason));
319 args = PyTuple_Pack(2, pyerr, pyreason);
320 Py_DECREF(pyerr); Py_DECREF(pyreason);
321 ret = PyObject_Call(cb, args, NULL);
322 Py_DECREF(cb);
323 Py_DECREF(args);
324 Py_DECREF(ret);
325}
326
327static PyObject *mod_loginasync(PyObject *self, PyObject *args, PyObject *kwargs)
328{
329 int useauthless;
330 char *username;
331 PyObject *o, *cb, *conv;
332
333 username = NULL;
334 conv = NULL;
335 useauthless = 1;
336 if(!PyArg_ParseTuple(args, "O|i", &cb, &useauthless))
337 return(NULL);
338 if(!PyCallable_Check(cb)) {
339 PyErr_SetString(PyExc_TypeError, "Callback must be callable");
340 return(NULL);
341 }
342 if(PyMapping_HasKeyString(kwargs, "username")) {
343 o = PyMapping_GetItemString(kwargs, "username");
344 username = PyString_AsString(o);
345 Py_DECREF(o);
346 if(username == NULL)
347 return(NULL);
348 }
349 if(PyMapping_HasKeyString(kwargs, "conv")) {
350 conv = PyMapping_GetItemString(kwargs, "conv");
351 if(!PyCallable_Check(conv)) {
352 PyErr_SetString(PyExc_TypeError, "Conv callback must be callable");
353 return(NULL);
354 }
355 PyErr_SetString(PyExc_NotImplementedError, "Custom conv functions are not yet supported by the Python interface");
356 return(NULL);
357 }
358 Py_INCREF(cb);
359 dc_loginasync(username, useauthless, NULL, (void (*)(int, wchar_t *, void *))login_cb, cb);
360 Py_RETURN_NONE;
361}
362
363static PyObject *mod_lexsexpr(PyObject *self, PyObject *args)
364{
365 PyObject *arg, *se, *ret;
366 wchar_t **arr, **ap, *buf;
367 size_t bufsize;
368
369 if(!PyArg_ParseTuple(args, "O", &arg))
370 return(NULL);
371 if((se = PyUnicode_FromObject(arg)) == NULL)
372 return(NULL);
373 buf = smalloc((bufsize = (PyUnicode_GetSize(se) + 1)) * sizeof(*buf));
374 buf[PyUnicode_AsWideChar((PyUnicodeObject *)se, buf, bufsize)] = L'\0';
375 arr = dc_lexsexpr(buf);
376 free(buf);
377 Py_DECREF(se);
378 ret = PyList_New(0);
379 if(arr != NULL) {
380 for(ap = arr; *ap; ap++)
381 PyList_Append(ret, PyUnicode_FromWideChar(*ap, wcslen(*ap)));
382 dc_freewcsarr(arr);
383 }
384 return(ret);
385}
386
c0fe4f45 387static PyObject *mod_wantwrite(PyObject *self)
388{
389 if(dc_wantwrite())
390 Py_RETURN_TRUE;
391 else
392 Py_RETURN_FALSE;
393}
394
9cbeb60c 395static PyObject *mod_checkproto(PyObject *self, PyObject *args)
396{
397 PyObject *tmp;
398 struct respobj *resp;
399 int version;
400
401 version = DC_LATEST;
402 if(!PyArg_ParseTuple(args, "O|i", &tmp, &version))
403 return(NULL);
404 if(!PyObject_TypeCheck(tmp, &resptype)) {
405 PyErr_SetString(PyExc_TypeError, "first argument must be a response object");
406 return(NULL);
407 }
071ecf13 408 resp = (struct respobj *)tmp;
9cbeb60c 409 if(dc_checkprotocol(resp->resp, version))
410 Py_RETURN_FALSE;
411 else
412 Py_RETURN_TRUE;
413}
414
fbe30a6d 415static PyMethodDef methods[] = {
416 {"connect", mod_connect, METH_VARARGS,
417 "Connect to a Dolda Connect server"},
418 {"disconnect", mod_disconnect, METH_VARARGS,
419 "Disconnect from the server"},
420 {"connected", mod_connected, METH_VARARGS,
421 "Return a boolean indicated whether there currently is a connection to a server"},
422 {"select", mod_select, METH_VARARGS,
423 "Handle data from the server connection, optionally blocking until something happens"},
424 {"getresp", mod_getresp, METH_VARARGS,
425 "Get a queued response object"},
426 {"qcmd", (PyCFunction)mod_qcmd, METH_VARARGS | METH_KEYWORDS,
427 "Queue a command to be sent to the server"},
428 {"loginasync", (PyCFunction)mod_loginasync, METH_VARARGS | METH_KEYWORDS,
429 "Perform an asynchronous login procedure"},
430 {"lexsexpr", mod_lexsexpr, METH_VARARGS,
431 "Use a standard algorithm to lex a search expression"},
c0fe4f45 432 {"wantwrite", (PyCFunction)mod_wantwrite, METH_NOARGS,
433 "Return a boolean indicating whether there is output to be fed to the server"},
9cbeb60c 434 {"checkproto", (PyCFunction)mod_checkproto, METH_VARARGS,
435 "Check so that the connect stanza returned by the server indicates support for the correct revision of the protocol"},
fbe30a6d 436 {NULL, NULL, 0, NULL}
437};
438
439PyMODINIT_FUNC initdolmod(void)
440{
441 PyObject *m;
442
443 if(PyType_Ready(&resptype) < 0)
444 return;
445 m = Py_InitModule("dolmod", methods);
446 Py_INCREF(&resptype);
447 PyModule_AddObject(m, "Response", (PyObject *)&resptype);
9cbeb60c 448 PyModule_AddObject(m, "latest", Py_BuildValue("i", DC_LATEST));
fbe30a6d 449 dc_init();
450}