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