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; | |
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())) { | |
48cff6fe FT |
192 | fd = -1; |
193 | if(errno == 0) | |
fbe30a6d | 194 | Py_RETURN_FALSE; |
fbe30a6d | 195 | PyErr_SetFromErrno(PyExc_OSError); |
48cff6fe | 196 | return(NULL); |
fbe30a6d | 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 | } |