Commit | Line | Data |
---|---|---|
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; | |
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 | ||
104 | static 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 | ||
118 | static 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 | ||
128 | static 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 | ||
137 | static 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 | ||
153 | static 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 | ||
162 | static PyObject *mod_connected(PyObject *self, PyObject *args) | |
163 | { | |
164 | if(fd == -1) | |
165 | Py_RETURN_FALSE; | |
166 | else | |
167 | Py_RETURN_TRUE; | |
168 | } | |
169 | ||
170 | static 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 | ||
206 | static 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 | ||
223 | static 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 | ||
236 | static PyObject *mod_qcmd(PyObject *self, PyObject *args, PyObject *kwargs) | |
237 | { | |
b23dedf9 | 238 | int i; |
fbe30a6d | 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 | |
282 | out: | |
fbe30a6d | 283 | dc_freewcsarr(toks); |
277c2137 | 284 | if(cmd != NULL) |
285 | free(cmd); | |
fbe30a6d | 286 | return(ret); |
287 | } | |
288 | ||
289 | static 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; | |
b23dedf9 FT |
313 | default: |
314 | errstr = "unknown"; | |
315 | break; | |
fbe30a6d | 316 | } |
317 | pyerr = PyString_FromString(errstr); | |
318 | if(reason == NULL) | |
319 | Py_INCREF(pyreason = Py_None); | |
320 | else | |
321 | pyreason = PyUnicode_FromWideChar(reason, wcslen(reason)); | |
322 | args = PyTuple_Pack(2, pyerr, pyreason); | |
323 | Py_DECREF(pyerr); Py_DECREF(pyreason); | |
324 | ret = PyObject_Call(cb, args, NULL); | |
325 | Py_DECREF(cb); | |
326 | Py_DECREF(args); | |
327 | Py_DECREF(ret); | |
328 | } | |
329 | ||
330 | static PyObject *mod_loginasync(PyObject *self, PyObject *args, PyObject *kwargs) | |
331 | { | |
332 | int useauthless; | |
333 | char *username; | |
334 | PyObject *o, *cb, *conv; | |
335 | ||
336 | username = NULL; | |
337 | conv = NULL; | |
338 | useauthless = 1; | |
339 | if(!PyArg_ParseTuple(args, "O|i", &cb, &useauthless)) | |
340 | return(NULL); | |
341 | if(!PyCallable_Check(cb)) { | |
342 | PyErr_SetString(PyExc_TypeError, "Callback must be callable"); | |
343 | return(NULL); | |
344 | } | |
345 | if(PyMapping_HasKeyString(kwargs, "username")) { | |
346 | o = PyMapping_GetItemString(kwargs, "username"); | |
347 | username = PyString_AsString(o); | |
348 | Py_DECREF(o); | |
349 | if(username == NULL) | |
350 | return(NULL); | |
351 | } | |
352 | if(PyMapping_HasKeyString(kwargs, "conv")) { | |
353 | conv = PyMapping_GetItemString(kwargs, "conv"); | |
354 | if(!PyCallable_Check(conv)) { | |
355 | PyErr_SetString(PyExc_TypeError, "Conv callback must be callable"); | |
356 | return(NULL); | |
357 | } | |
358 | PyErr_SetString(PyExc_NotImplementedError, "Custom conv functions are not yet supported by the Python interface"); | |
359 | return(NULL); | |
360 | } | |
361 | Py_INCREF(cb); | |
362 | dc_loginasync(username, useauthless, NULL, (void (*)(int, wchar_t *, void *))login_cb, cb); | |
363 | Py_RETURN_NONE; | |
364 | } | |
365 | ||
366 | static PyObject *mod_lexsexpr(PyObject *self, PyObject *args) | |
367 | { | |
368 | PyObject *arg, *se, *ret; | |
369 | wchar_t **arr, **ap, *buf; | |
370 | size_t bufsize; | |
371 | ||
372 | if(!PyArg_ParseTuple(args, "O", &arg)) | |
373 | return(NULL); | |
374 | if((se = PyUnicode_FromObject(arg)) == NULL) | |
375 | return(NULL); | |
376 | buf = smalloc((bufsize = (PyUnicode_GetSize(se) + 1)) * sizeof(*buf)); | |
377 | buf[PyUnicode_AsWideChar((PyUnicodeObject *)se, buf, bufsize)] = L'\0'; | |
378 | arr = dc_lexsexpr(buf); | |
379 | free(buf); | |
380 | Py_DECREF(se); | |
381 | ret = PyList_New(0); | |
382 | if(arr != NULL) { | |
383 | for(ap = arr; *ap; ap++) | |
384 | PyList_Append(ret, PyUnicode_FromWideChar(*ap, wcslen(*ap))); | |
385 | dc_freewcsarr(arr); | |
386 | } | |
387 | return(ret); | |
388 | } | |
389 | ||
c0fe4f45 | 390 | static PyObject *mod_wantwrite(PyObject *self) |
391 | { | |
392 | if(dc_wantwrite()) | |
393 | Py_RETURN_TRUE; | |
394 | else | |
395 | Py_RETURN_FALSE; | |
396 | } | |
397 | ||
9cbeb60c | 398 | static PyObject *mod_checkproto(PyObject *self, PyObject *args) |
399 | { | |
400 | PyObject *tmp; | |
401 | struct respobj *resp; | |
402 | int version; | |
403 | ||
404 | version = DC_LATEST; | |
405 | if(!PyArg_ParseTuple(args, "O|i", &tmp, &version)) | |
406 | return(NULL); | |
407 | if(!PyObject_TypeCheck(tmp, &resptype)) { | |
408 | PyErr_SetString(PyExc_TypeError, "first argument must be a response object"); | |
409 | return(NULL); | |
410 | } | |
071ecf13 | 411 | resp = (struct respobj *)tmp; |
9cbeb60c | 412 | if(dc_checkprotocol(resp->resp, version)) |
413 | Py_RETURN_FALSE; | |
414 | else | |
415 | Py_RETURN_TRUE; | |
416 | } | |
417 | ||
fbe30a6d | 418 | static PyMethodDef methods[] = { |
419 | {"connect", mod_connect, METH_VARARGS, | |
420 | "Connect to a Dolda Connect server"}, | |
421 | {"disconnect", mod_disconnect, METH_VARARGS, | |
422 | "Disconnect from the server"}, | |
423 | {"connected", mod_connected, METH_VARARGS, | |
424 | "Return a boolean indicated whether there currently is a connection to a server"}, | |
425 | {"select", mod_select, METH_VARARGS, | |
426 | "Handle data from the server connection, optionally blocking until something happens"}, | |
427 | {"getresp", mod_getresp, METH_VARARGS, | |
428 | "Get a queued response object"}, | |
429 | {"qcmd", (PyCFunction)mod_qcmd, METH_VARARGS | METH_KEYWORDS, | |
430 | "Queue a command to be sent to the server"}, | |
431 | {"loginasync", (PyCFunction)mod_loginasync, METH_VARARGS | METH_KEYWORDS, | |
432 | "Perform an asynchronous login procedure"}, | |
433 | {"lexsexpr", mod_lexsexpr, METH_VARARGS, | |
434 | "Use a standard algorithm to lex a search expression"}, | |
c0fe4f45 | 435 | {"wantwrite", (PyCFunction)mod_wantwrite, METH_NOARGS, |
436 | "Return a boolean indicating whether there is output to be fed to the server"}, | |
9cbeb60c | 437 | {"checkproto", (PyCFunction)mod_checkproto, METH_VARARGS, |
438 | "Check so that the connect stanza returned by the server indicates support for the correct revision of the protocol"}, | |
fbe30a6d | 439 | {NULL, NULL, 0, NULL} |
440 | }; | |
441 | ||
442 | PyMODINIT_FUNC initdolmod(void) | |
443 | { | |
444 | PyObject *m; | |
445 | ||
446 | if(PyType_Ready(&resptype) < 0) | |
447 | return; | |
448 | m = Py_InitModule("dolmod", methods); | |
449 | Py_INCREF(&resptype); | |
450 | PyModule_AddObject(m, "Response", (PyObject *)&resptype); | |
9cbeb60c | 451 | PyModule_AddObject(m, "latest", Py_BuildValue("i", DC_LATEST)); |
fbe30a6d | 452 | dc_init(); |
453 | } |