Fix UI
[kdenetwork.git] / kdict / dict.cpp
blobb36f1ac15504132dd3ed93b30d08fcbaa174a649
1 /* -------------------------------------------------------------
3 dict.cpp (part of The KDE Dictionary Client)
5 Copyright (C) 2000-2001 Christian Gebauer <gebauer@kde.org>
6 (C) by Matthias Hölzer 1998
8 This file is distributed under the Artistic License.
9 See LICENSE for details.
11 -------------------------------------------------------------
13 JobData used for data transfer between Client and Interface
14 DictAsyncClient all network related stuff happens here in asynchrous thread
15 DictInterface interface for DictAsyncClient, job management
17 ------------------------------------------------------------- */
19 #include <config.h>
21 #include "application.h"
22 #include "options.h"
23 #include "dict.h"
25 #include <qregexp.h>
26 #include <qtextcodec.h>
28 #include <klocale.h>
29 #include <kdebug.h>
30 #include <kmessagebox.h>
31 #include <kmdcodec.h>
32 #include <kextsock.h>
33 #include <ksocks.h>
35 #include <errno.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <signal.h>
39 #include <stdlib.h>
41 //********* JobData ******************************************
44 JobData::JobData(QueryType Ntype,bool NnewServer,QString const& Nserver,int Nport,
45 int NidleHold, int Ntimeout, int NpipeSize, QString const& Nencoding, bool NAuthEnabled,
46 QString const& Nuser, QString const& Nsecret, unsigned int NheadLayout)
47 : type(Ntype), error(ErrNoErr), canceled(false), numFetched(0), newServer(NnewServer),server(Nserver), port(Nport),
48 timeout(Ntimeout), pipeSize(NpipeSize), idleHold(NidleHold), encoding(Nencoding), authEnabled(NAuthEnabled),
49 user(Nuser), secret(Nsecret), headLayout(NheadLayout)
53 //********* DictAsyncClient *************************************
55 DictAsyncClient::DictAsyncClient(int NfdPipeIn, int NfdPipeOut)
56 : job(0L), inputSize(10000), fdPipeIn(NfdPipeIn),
57 fdPipeOut(NfdPipeOut), tcpSocket(-1), idleHold(0)
59 input = new char[inputSize];
63 DictAsyncClient::~DictAsyncClient()
65 if (-1!=tcpSocket)
66 doQuit();
67 delete [] input;
71 void* DictAsyncClient::startThread(void* pseudoThis)
73 DictAsyncClient* newthis = (DictAsyncClient*) (pseudoThis);
75 if (0!=pthread_setcanceltype(PTHREAD_CANCEL_ENABLE,NULL))
76 qWarning("pthread_setcanceltype failed!");
77 if (0!= pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL))
78 qWarning("pthread_setcanceltype failed!");
80 signal(SIGPIPE,SIG_IGN); // ignore sigpipe
82 newthis->waitForWork();
83 return NULL;
87 void DictAsyncClient::insertJob(JobData *newJob)
89 if (!job) // don't overwrite existing job pointer
90 job = newJob;
94 void DictAsyncClient::removeJob()
96 job = 0L;
100 void DictAsyncClient::waitForWork()
102 fd_set fdsR,fdsE;
103 timeval tv;
104 int selectRet;
105 char buf;
107 while (true) {
108 if (tcpSocket != -1) { // we are connected, hold the connection for xx secs
109 FD_ZERO(&fdsR);
110 FD_SET(fdPipeIn, &fdsR);
111 FD_SET(tcpSocket, &fdsR);
112 FD_ZERO(&fdsE);
113 FD_SET(tcpSocket, &fdsE);
114 tv.tv_sec = idleHold;
115 tv.tv_usec = 0;
116 selectRet = KSocks::self()->select(FD_SETSIZE, &fdsR, NULL, &fdsE, &tv);
117 if (selectRet == 0) {
118 doQuit(); // nothing happend...
119 } else {
120 if (((selectRet > 0)&&(!FD_ISSET(fdPipeIn,&fdsR)))||(selectRet == -1))
121 closeSocket();
125 do {
126 FD_ZERO(&fdsR);
127 FD_SET(fdPipeIn, &fdsR);
128 } while (select(FD_SETSIZE, &fdsR, NULL, NULL, NULL)<0); // don't get tricked by signals
130 clearPipe();
132 if (job) {
133 if ((tcpSocket!=-1)&&(job->newServer))
134 doQuit();
136 codec = QTextCodec::codecForName(job->encoding.latin1());
137 input[0] = 0; //terminate string
138 thisLine = input;
139 nextLine = input;
140 inputEnd = input;
141 timeout = job->timeout;
142 idleHold = job->idleHold;
144 if (tcpSocket==-1)
145 openConnection();
147 if (tcpSocket!=-1) { // connection is ready
148 switch (job->type) {
149 case JobData::TDefine :
150 define();
151 break;
152 case JobData::TGetDefinitions :
153 getDefinitions();
154 break;
155 case JobData::TMatch :
156 match();
157 break;
158 case JobData::TShowDatabases :
159 showDatabases();
160 break;
161 case JobData::TShowDbInfo :
162 showDbInfo();
163 break;
164 case JobData::TShowStrategies :
165 showStrategies();
166 break;
167 case JobData::TShowInfo :
168 showInfo();
169 break;
170 case JobData::TUpdate :
171 update();
174 clearPipe();
176 if (write(fdPipeOut,&buf,1) == -1) // emit stopped signal
177 ::perror( "waitForJobs()" );
182 void DictAsyncClient::define()
184 QString command;
186 job->defines.clear();
187 QStringList::iterator it;
188 for (it = job->databases.begin(); it != job->databases.end(); ++it) {
189 command = "define ";
190 command += *it;
191 command += " \"";
192 command += job->query;
193 command += "\"\r\n";
194 job->defines.append(command);
197 if (!getDefinitions())
198 return;
200 if (job->numFetched == 0) {
201 job->strategy = ".";
202 if (!match())
203 return;
204 job->result = QString::null;
205 if (job->numFetched == 0) {
206 resultAppend("<body>\n<p class=\"heading\">\n");
207 resultAppend(i18n("No definitions found for \'%1'.").arg(job->query));
208 resultAppend("</p>\n</html></body>");
209 } else {
210 // html header...
211 resultAppend("<body>\n<p class=\"heading\">\n");
212 resultAppend(i18n("No definitions found for \'%1\'. Perhaps you mean:").arg(job->query));
213 resultAppend("</p>\n<table width=\"100%\" cols=2>\n");
215 QString lastDb;
216 QStringList::iterator it;
217 for (it = job->matches.begin(); it != job->matches.end(); ++it) {
218 int pos = (*it).find(' ');
219 if (pos != -1) {
220 if (lastDb != (*it).left(pos)) {
221 if (lastDb.length() > 0)
222 resultAppend("</pre></td></tr>\n");
223 lastDb = (*it).left(pos);
224 resultAppend("<tr valign=top><td width=25%><pre><b>");
225 resultAppend(lastDb);
226 resultAppend(":</b></pre></td><td width=75%><pre>");
228 if ((*it).length() > (unsigned int)pos+2) {
229 resultAppend("<a href=\"http://define/");
230 resultAppend((*it).mid(pos+2, (*it).length()-pos-3));
231 resultAppend("\">");
232 resultAppend((*it).mid(pos+2, (*it).length()-pos-3));
233 resultAppend("</a> ");
237 resultAppend("\n</pre></td></tr></table>\n</body></html>");
238 job->numFetched = 0;
244 QString htmlString(const QString &raw)
246 unsigned int len=raw.length();
247 QString ret;
249 for (unsigned int i=0; i<len; i++) {
250 switch (raw[i]) {
251 case '&' : ret += "&amp"; break;
252 case '<' : ret+="&lt;"; break;
253 case '>' : ret+="&gt;"; break;
254 default : ret+=raw[i];
258 return ret;
262 QString generateDefineLink(const QString &raw)
264 QRegExp http("http://[^\\s<>()\"|\\[\\]{}]+");
265 QRegExp ftp("ftp://[^\\s<>()\"|\\[\\]{}]+");
266 int matchPos=0, matchLen=0;
267 bool httpMatch=false;
268 QString ret;
270 matchPos = http.search(raw);
271 matchLen = http.matchedLength();
272 if (-1 != matchPos) {
273 httpMatch = true;
274 } else {
275 matchPos = ftp.search(raw);
276 matchLen = ftp.matchedLength();
277 httpMatch = false;
280 if (-1 != matchPos) {
281 ret = htmlString(raw.left(matchPos));
282 ret += "<a href=\"http://";
283 if (httpMatch) {
284 ret += "realhttp/";
285 ret += raw.mid(matchPos+7, matchLen-7);
286 } else {
287 ret += "realftp/";
288 ret += raw.mid(matchPos+6, matchLen-6);
290 ret += "\">";
291 ret += htmlString(raw.mid(matchPos, matchLen));
292 ret += "</a>";
293 ret += htmlString(raw.right(raw.length()-matchLen-matchPos));
294 } else {
295 ret = "<a href=\"http://define/";
296 ret += raw;
297 ret += "\">";
298 ret += htmlString(raw);
299 ret += "</a>";
302 return ret;
306 bool DictAsyncClient::getDefinitions()
308 QCString lastDb,bracketBuff;
309 QStrList hashList;
310 char *s;
311 int defCount,response;
313 // html header...
314 resultAppend("<body>\n");
316 while (job->defines.count()>0) {
317 defCount = 0;
318 cmdBuffer = "";
319 do {
320 QStringList::iterator it = job->defines.begin();
321 cmdBuffer += codec->fromUnicode(*it);
322 defCount++;
323 job->defines.remove(it);
324 } while ((job->defines.count()>0)&&((int)cmdBuffer.length()<job->pipeSize));
326 if (!sendBuffer())
327 return false;
329 for (;defCount > 0;defCount--) {
330 if (!getNextResponse(response))
331 return false;
332 switch (response) {
333 case 552: // define: 552 No match
334 break;
335 case 150: { // define: 150 n definitions retrieved - definitions follow
336 bool defineDone = false;
337 while (!defineDone) {
338 if (!getNextResponse(response))
339 return false;
340 switch (response) {
341 case 151: { // define: 151 word database name - text follows
342 char *db = strchr(thisLine, '\"');
343 if (db)
344 db = strchr(db+1, '\"');
345 char *dbdes = 0;
346 if (db) {
347 db+=2; // db points now on database name
348 dbdes = strchr(db,' ');
349 if (dbdes) {
350 dbdes[0] = 0; // terminate database name
351 dbdes+=2; // dbdes points now on database description
353 } else {
354 job->error = JobData::ErrServerError;
355 job->result = QString::null;
356 resultAppend(thisLine);
357 doQuit();
358 return false;
361 int oldResPos = job->result.length();
363 if (((job->headLayout<2)&&(lastDb!=db))||(job->headLayout==2)) {
364 lastDb = db;
365 resultAppend("<p class=\"heading\">\n");
366 if (dbdes)
367 resultAppend(codec->toUnicode(dbdes,strlen(dbdes)-1));
368 resultAppend(" [<a href=\"http://dbinfo/");
369 resultAppend(db);
370 resultAppend("\">");
371 resultAppend(db);
372 resultAppend("</a>]</p>\n");
373 } else
374 if (job->headLayout==1)
375 resultAppend("<hr>\n");
377 resultAppend("<pre><p class=\"definition\">\n");
379 KMD5 context;
380 bool bodyDone = false;
381 while (!bodyDone) {
382 if (!getNextLine())
383 return false;
384 char *line = thisLine;
385 if (line[0]=='.') {
386 if (line[1]=='.')
387 line++; // collapse double periode into one
388 else
389 if (line[1]==0)
390 bodyDone = true;
392 if (!bodyDone) {
393 context.update(QCString(line));
394 if (!bracketBuff.isEmpty()) {
395 s = strchr(line,'}');
396 if (!s)
397 resultAppend(bracketBuff.data());
398 else {
399 s[0] = 0;
400 bracketBuff.remove(0,1); // remove '{'
401 bracketBuff += line;
402 line = s+1;
403 resultAppend(generateDefineLink(codec->toUnicode(bracketBuff)));
405 bracketBuff = "";
407 s = strchr(line,'{');
408 while (s) {
409 resultAppend(htmlString(codec->toUnicode(line,s-line)));
410 line = s;
411 s = strchr(line,'}');
412 if (s) {
413 s[0] = 0;
414 line++;
415 resultAppend(generateDefineLink(codec->toUnicode(line)));
416 line = s+1;
417 s = strchr(line,'{');
418 } else {
419 bracketBuff = line;
420 bracketBuff += "\n";
421 line = 0;
422 s = 0;
425 if (line) {
426 resultAppend(htmlString(codec->toUnicode(line)));
427 resultAppend("\n");
431 resultAppend("</p></pre>\n");
433 if (hashList.find(context.hexDigest())>=0) // duplicate??
434 job->result.truncate(oldResPos); // delete the whole definition
435 else {
436 hashList.append(context.hexDigest());
437 job->numFetched++;
440 break; }
441 case 250: { // define: 250 ok (optional timing information here)
442 defineDone = true;
443 break; }
444 default: {
445 handleErrors();
446 return false; }
449 break; }
450 default:
451 handleErrors();
452 return false;
457 resultAppend("</body></html>\n");
458 return true;
462 bool DictAsyncClient::match()
464 QStringList::iterator it = job->databases.begin();
465 int response;
466 cmdBuffer = "";
468 while (it != job->databases.end()) {
469 int send = 0;
470 do {
471 cmdBuffer += "match ";
472 cmdBuffer += codec->fromUnicode(*(it));
473 cmdBuffer += " ";
474 cmdBuffer += codec->fromUnicode(job->strategy);
475 cmdBuffer += " \"";
476 cmdBuffer += codec->fromUnicode(job->query);
477 cmdBuffer += "\"\r\n";
478 send++;
479 ++it;
480 } while ((it != job->databases.end())&&((int)cmdBuffer.length()<job->pipeSize));
482 if (!sendBuffer())
483 return false;
485 for (;send > 0;send--) {
486 if (!getNextResponse(response))
487 return false;
488 switch (response) {
489 case 552: // match: 552 No match
490 break;
491 case 152: { // match: 152 n matches found - text follows
492 bool matchDone = false;
493 while (!matchDone) {
494 if (!getNextLine())
495 return false;
496 char *line = thisLine;
497 if (line[0]=='.') {
498 if (line[1]=='.')
499 line++; // collapse double period into one
500 else
501 if (line[1]==0)
502 matchDone = true;
504 if (!matchDone) {
505 job->numFetched++;
506 job->matches.append(codec->toUnicode(line));
509 if (!nextResponseOk(250)) // match: "250 ok ..."
510 return false;
511 break; }
512 default:
513 handleErrors();
514 return false;
519 return true;
523 void DictAsyncClient::showDatabases()
525 cmdBuffer = "show db\r\n";
527 if (!sendBuffer())
528 return;
530 if (!nextResponseOk(110)) // show db: "110 n databases present - text follows "
531 return;
533 // html header...
534 resultAppend("<body>\n<p class=\"heading\">\n");
535 resultAppend(i18n("Available Databases:"));
536 resultAppend("\n</p>\n<table width=\"100%\" cols=2>\n");
538 bool done(false);
539 char *line;
540 while (!done) {
541 if (!getNextLine())
542 return;
543 line = thisLine;
544 if (line[0]=='.') {
545 if (line[1]=='.')
546 line++; // collapse double periode into one
547 else
548 if (line[1]==0)
549 done = true;
551 if (!done) {
552 resultAppend("<tr valign=top><td width=25%><pre><a href=\"http://dbinfo/");
553 char *space = strchr(line,' ');
554 if (space) {
555 resultAppend(codec->toUnicode(line,space-line));
556 resultAppend("\">");
557 resultAppend(codec->toUnicode(line,space-line));
558 resultAppend("</a></pre></td><td width=75%><pre>");
559 line = space+1;
560 if (line[0]=='"') {
561 line++; // remove double quote
562 char *quote = strchr(line, '\"');
563 if (quote)
564 quote[0]=0;
566 } else { // hmmm, malformated line...
567 resultAppend("\"></a></pre></td><td width=75%>");
569 resultAppend(line);
570 resultAppend("</pre></td></tr>\n");
573 resultAppend("</table>\n</body></html>");
575 if (!nextResponseOk(250)) // end of transmission: "250 ok ..."
576 return;
580 void DictAsyncClient::showDbInfo()
582 cmdBuffer = "show info ";
583 cmdBuffer += codec->fromUnicode(job->query);
584 cmdBuffer += "\r\n";
586 if (!sendBuffer())
587 return;
589 if (!nextResponseOk(112)) // show info db: "112 database information follows"
590 return;
592 // html header...
593 resultAppend("<body>\n<p class=\"heading\">\n");
594 resultAppend(i18n("Database Information [%1]:").arg(job->query));
595 resultAppend("</p>\n<pre><p class=\"definition\">\n");
597 bool done(false);
598 char *line;
599 while (!done) {
600 if (!getNextLine())
601 return;
602 line = thisLine;
603 if (line[0]=='.') {
604 if (line[1]=='.')
605 line++; // collapse double periode into one
606 else
607 if (line[1]==0)
608 done = true;
610 if (!done) {
611 resultAppend(line);
612 resultAppend("\n");
616 resultAppend("</p></pre>\n</body></html>");
618 if (!nextResponseOk(250)) // end of transmission: "250 ok ..."
619 return;
623 void DictAsyncClient::showStrategies()
625 cmdBuffer = "show strat\r\n";
627 if (!sendBuffer())
628 return;
630 if (!nextResponseOk(111)) // show strat: "111 n strategies present - text follows "
631 return;
633 // html header...
634 resultAppend("<body>\n<p class=\"heading\">\n");
635 resultAppend(i18n("Available Strategies:"));
636 resultAppend("\n</p>\n<table width=\"100%\" cols=2>\n");
638 bool done(false);
639 char *line;
640 while (!done) {
641 if (!getNextLine())
642 return;
643 line = thisLine;
644 if (line[0]=='.') {
645 if (line[1]=='.')
646 line++; // collapse double periode into one
647 else
648 if (line[1]==0)
649 done = true;
651 if (!done) {
652 resultAppend("<tr valign=top><td width=25%><pre>");
653 char *space = strchr(line,' ');
654 if (space) {
655 resultAppend(codec->toUnicode(line,space-line));
656 resultAppend("</pre></td><td width=75%><pre>");
657 line = space+1;
658 if (line[0]=='"') {
659 line++; // remove double quote
660 char *quote = strchr(line, '\"');
661 if (quote)
662 quote[0]=0;
664 } else { // hmmm, malformated line...
665 resultAppend("</pre></td><td width=75%><pre>");
667 resultAppend(line);
668 resultAppend("</pre></td></tr>\n");
671 resultAppend("</table>\n</body></html>");
673 if (!nextResponseOk(250)) // end of transmission: "250 ok ..."
674 return;
678 void DictAsyncClient::showInfo()
680 cmdBuffer = "show server\r\n";
682 if (!sendBuffer())
683 return;
685 if (!nextResponseOk(114)) // show server: "114 server information follows"
686 return;
688 // html header...
689 resultAppend("<body>\n<p class=\"heading\">\n");
690 resultAppend(i18n("Server Information:"));
691 resultAppend("\n</p>\n<pre><p class=\"definition\">\n");
693 bool done(false);
694 char *line;
695 while (!done) {
696 if (!getNextLine())
697 return;
698 line = thisLine;
699 if (line[0]=='.') {
700 if (line[1]=='.')
701 line++; // collapse double periode into one
702 else
703 if (line[1]==0)
704 done = true;
706 if (!done) {
707 resultAppend(line);
708 resultAppend("\n");
712 resultAppend("</p></pre>\n</body></html>");
714 if (!nextResponseOk(250)) // end of transmission: "250 ok ..."
715 return;
719 void DictAsyncClient::update()
721 cmdBuffer = "show strat\r\nshow db\r\n";
723 if (!sendBuffer())
724 return;
726 if (!nextResponseOk(111)) // show strat: "111 n strategies present - text follows "
727 return;
729 bool done(false);
730 char *line;
731 while (!done) {
732 if (!getNextLine())
733 return;
734 line = thisLine;
735 if (line[0]=='.') {
736 if (line[1]=='.')
737 line++; // collapse double periode into one
738 else
739 if (line[1]==0)
740 done = true;
742 if (!done) {
743 char *space = strchr(line,' ');
744 if (space) space[0] = 0; // terminate string, hack ;-)
745 job->strategies.append(codec->toUnicode(line));
749 if (!nextResponseOk(250)) // end of transmission: "250 ok ..."
750 return;
752 if (!nextResponseOk(110)) // show db: "110 n databases present - text follows "
753 return;
755 done = false;
756 while (!done) {
757 if (!getNextLine())
758 return;
759 line = thisLine;
760 if (line[0]=='.') {
761 if (line[1]=='.')
762 line++; // collapse double periode into one
763 else
764 if (line[1]==0)
765 done = true;
767 if (!done) {
768 char *space = strchr(line,' ');
769 if (space) space[0] = 0; // terminate string, hack ;-)
770 job->databases.append(codec->toUnicode(line));
774 if (!nextResponseOk(250)) // end of transmission: "250 ok ..."
775 return;
779 // connect, handshake and authorization
780 void DictAsyncClient::openConnection()
782 if (job->server.isEmpty()) {
783 job->error = JobData::ErrBadHost;
784 return;
787 KExtendedSocket ks;
789 ks.setAddress(job->server, job->port);
790 ks.setTimeout(job->timeout);
791 if (ks.connect() < 0) {
792 if (ks.status() == IO_LookupError)
793 job->error = JobData::ErrBadHost;
794 else if (ks.status() == IO_ConnectError) {
795 job->result = QString::null;
796 resultAppend(KExtendedSocket::strError(ks.status(), errno));
797 job->error = JobData::ErrConnect;
798 } else if (ks.status() == IO_TimeOutError)
799 job->error = JobData::ErrTimeout;
800 else {
801 job->result = QString::null;
802 resultAppend(KExtendedSocket::strError(ks.status(), errno));
803 job->error = JobData::ErrCommunication;
806 closeSocket();
807 return;
809 tcpSocket = ks.fd();
810 ks.release();
812 if (!nextResponseOk(220)) // connect: "220 text capabilities msg-id"
813 return;
815 cmdBuffer = "client \"Kdict ";
816 cmdBuffer += KDICT_VERSION;
817 cmdBuffer += "\"\r\n";
819 if (job->authEnabled)
820 if (strstr(thisLine,"auth")) { // skip auth if not supported
821 char *msgId = strrchr(thisLine,'<');
823 if ((!msgId)||(!job->user.length())) {
824 job->error = JobData::ErrAuthFailed;
825 closeSocket();
826 return;
829 KMD5 context;
830 context.update(QCString(msgId));
831 context.update(job->secret.local8Bit());
833 cmdBuffer += "auth " + job->user.local8Bit() + " ";
834 cmdBuffer += context.hexDigest();
835 cmdBuffer += "\r\n";
838 if (!sendBuffer())
839 return;
841 if (!nextResponseOk(250)) // client: "250 ok ..."
842 return;
844 if (job->authEnabled)
845 if (!nextResponseOk(230)) // auth: "230 Authentication successful"
846 return;
850 void DictAsyncClient::closeSocket()
852 if (-1 != tcpSocket) {
853 ::close(tcpSocket);
854 tcpSocket = -1;
859 // send "quit" without timeout, without checks, close connection
860 void DictAsyncClient::doQuit()
862 fd_set fdsW;
863 timeval tv;
865 FD_ZERO(&fdsW);
866 FD_SET(tcpSocket, &fdsW);
867 tv.tv_sec = 0;
868 tv.tv_usec = 0;
869 int ret = KSocks::self()->select(FD_SETSIZE, NULL, &fdsW, NULL, &tv);
871 if (ret > 0) { // we can write...
872 cmdBuffer = "quit\r\n";
873 int todo = cmdBuffer.length();
874 KSocks::self()->write(tcpSocket,&cmdBuffer.data()[0],todo);
876 closeSocket();
880 // used by getNextLine()
881 bool DictAsyncClient::waitForRead()
883 fd_set fdsR,fdsE;
884 timeval tv;
886 int ret;
887 do {
888 FD_ZERO(&fdsR);
889 FD_SET(fdPipeIn, &fdsR);
890 FD_SET(tcpSocket, &fdsR);
891 FD_ZERO(&fdsE);
892 FD_SET(tcpSocket, &fdsE);
893 FD_SET(fdPipeIn, &fdsE);
894 tv.tv_sec = timeout;
895 tv.tv_usec = 0;
896 ret = KSocks::self()->select(FD_SETSIZE, &fdsR, NULL, &fdsE, &tv);
897 } while ((ret<0)&&(errno==EINTR)); // don't get tricked by signals
899 if (ret == -1) { // select failed
900 if (job) {
901 job->result = QString::null;
902 resultAppend(strerror(errno));
903 job->error = JobData::ErrCommunication;
905 closeSocket();
906 return false;
908 if (ret == 0) { // Nothing happend, timeout
909 if (job)
910 job->error = JobData::ErrTimeout;
911 doQuit();
912 return false;
914 if (ret > 0) {
915 if (FD_ISSET(fdPipeIn,&fdsR)) { // stop signal
916 doQuit();
917 return false;
919 if (FD_ISSET(tcpSocket,&fdsE)||FD_ISSET(fdPipeIn,&fdsE)) { // broken pipe, etc
920 if (job) {
921 job->result = QString::null;
922 resultAppend(i18n("The connection is broken."));
923 job->error = JobData::ErrCommunication;
925 closeSocket();
926 return false;
928 if (FD_ISSET(tcpSocket,&fdsR)) // all ok
929 return true;
932 if (job) {
933 job->result = QString::null;
934 job->error = JobData::ErrCommunication;
936 closeSocket();
937 return false;
941 // used by sendBuffer() & connect()
942 bool DictAsyncClient::waitForWrite()
944 fd_set fdsR,fdsW,fdsE;
945 timeval tv;
947 int ret;
948 do {
949 FD_ZERO(&fdsR);
950 FD_SET(fdPipeIn, &fdsR);
951 FD_SET(tcpSocket, &fdsR);
952 FD_ZERO(&fdsW);
953 FD_SET(tcpSocket, &fdsW);
954 FD_ZERO(&fdsE);
955 FD_SET(tcpSocket, &fdsE);
956 FD_SET(fdPipeIn, &fdsE);
957 tv.tv_sec = timeout;
958 tv.tv_usec = 0;
959 ret = KSocks::self()->select(FD_SETSIZE, &fdsR, &fdsW, &fdsE, &tv);
960 } while ((ret<0)&&(errno==EINTR)); // don't get tricked by signals
962 if (ret == -1) { // select failed
963 if (job) {
964 job->result = QString::null;
965 resultAppend(strerror(errno));
966 job->error = JobData::ErrCommunication;
968 closeSocket();
969 return false;
971 if (ret == 0) { // nothing happend, timeout
972 if (job)
973 job->error = JobData::ErrTimeout;
974 closeSocket();
975 return false;
977 if (ret > 0) {
978 if (FD_ISSET(fdPipeIn,&fdsR)) { // stop signal
979 doQuit();
980 return false;
982 if (FD_ISSET(tcpSocket,&fdsR)||FD_ISSET(tcpSocket,&fdsE)||FD_ISSET(fdPipeIn,&fdsE)) { // broken pipe, etc
983 if (job) {
984 job->result = QString::null;
985 resultAppend(i18n("The connection is broken."));
986 job->error = JobData::ErrCommunication;
988 closeSocket();
989 return false;
991 if (FD_ISSET(tcpSocket,&fdsW)) // all ok
992 return true;
994 if (job) {
995 job->result = QString::null;
996 job->error = JobData::ErrCommunication;
998 closeSocket();
999 return false;
1003 // remove start/stop signal
1004 void DictAsyncClient::clearPipe()
1006 fd_set fdsR;
1007 timeval tv;
1008 int selectRet;
1009 char buf;
1011 tv.tv_sec = 0;
1012 tv.tv_usec = 0;
1013 do {
1014 FD_ZERO(&fdsR);
1015 FD_SET(fdPipeIn,&fdsR);
1016 if (1==(selectRet=select(FD_SETSIZE,&fdsR,NULL,NULL,&tv)))
1017 if ( ::read(fdPipeIn, &buf, 1 ) == -1 )
1018 ::perror( "clearPipe()" );
1019 } while (selectRet == 1);
1023 bool DictAsyncClient::sendBuffer()
1025 int ret;
1026 int todo = cmdBuffer.length();
1027 int done = 0;
1029 while (todo > 0) {
1030 if (!waitForWrite())
1031 return false;
1032 ret = KSocks::self()->write(tcpSocket,&cmdBuffer.data()[done],todo);
1033 if (ret <= 0) {
1034 if (job) {
1035 job->result = QString::null;
1036 resultAppend(strerror(errno));
1037 job->error = JobData::ErrCommunication;
1039 closeSocket();
1040 return false;
1041 } else {
1042 done += ret;
1043 todo -= ret;
1046 return true;
1050 // set thisLine to next complete line of input
1051 bool DictAsyncClient::getNextLine()
1053 thisLine = nextLine;
1054 nextLine = strstr(thisLine,"\r\n");
1055 if (nextLine) { // there is another full line in the inputbuffer
1056 nextLine[0] = 0; // terminate string
1057 nextLine[1] = 0;
1058 nextLine+=2;
1059 return true;
1061 unsigned int div = inputEnd-thisLine+1; // hmmm, I need to fetch more input from the server...
1062 memmove(input,thisLine,div); // save last, incomplete line
1063 thisLine = input;
1064 inputEnd = input+div-1;
1065 do {
1066 if ((inputEnd-input) > 9000) {
1067 job->error = JobData::ErrMsgTooLong;
1068 closeSocket();
1069 return false;
1071 if (!waitForRead())
1072 return false;
1074 int received;
1075 do {
1076 received = KSocks::self()->read(tcpSocket, inputEnd, inputSize-(inputEnd-input)-1);
1077 } while ((received<0)&&(errno==EINTR)); // don't get tricked by signals
1079 if (received <= 0) {
1080 job->result = QString::null;
1081 resultAppend(i18n("The connection is broken."));
1082 job->error = JobData::ErrCommunication;
1083 closeSocket();
1084 return false;
1086 inputEnd += received;
1087 inputEnd[0] = 0; // terminate *char
1088 } while (!(nextLine = strstr(thisLine,"\r\n")));
1089 nextLine[0] = 0; // terminate string
1090 nextLine[1] = 0;
1091 nextLine+=2;
1092 return true;
1096 // reads next line and checks the response code
1097 bool DictAsyncClient::nextResponseOk(int code)
1099 if (!getNextLine())
1100 return false;
1101 if (strtol(thisLine,0L,0)!=code) {
1102 handleErrors();
1103 return false;
1105 return true;
1109 // reads next line and returns the response code
1110 bool DictAsyncClient::getNextResponse(int &code)
1112 if (!getNextLine())
1113 return false;
1114 code = strtol(thisLine,0L,0);
1115 return true;
1119 void DictAsyncClient::handleErrors()
1121 int len = strlen(thisLine);
1122 if (len>80)
1123 len = 80;
1124 job->result = QString::null;
1125 resultAppend(codec->toUnicode(thisLine,len));
1127 switch (strtol(thisLine,0L,0)) {
1128 case 420:
1129 case 421:
1130 job->error = JobData::ErrNotAvailable; // server unavailable
1131 break;
1132 case 500:
1133 case 501:
1134 job->error = JobData::ErrSyntax; // syntax error
1135 break;
1136 case 502:
1137 case 503:
1138 job->error = JobData::ErrCommandNotImplemented; // command not implemented
1139 break;
1140 case 530:
1141 job->error = JobData::ErrAccessDenied; // access denied
1142 break;
1143 case 531:
1144 job->error = JobData::ErrAuthFailed; // authentication failed
1145 break;
1146 case 550:
1147 case 551:
1148 job->error = JobData::ErrInvalidDbStrat; // invalid strategy/database
1149 break;
1150 case 554:
1151 job->error = JobData::ErrNoDatabases; // no databases
1152 break;
1153 case 555:
1154 job->error = JobData::ErrNoStrategies; // no strategies
1155 break;
1156 default:
1157 job->error = JobData::ErrServerError;
1159 doQuit();
1163 void DictAsyncClient::resultAppend(const char* str)
1165 if (job)
1166 job->result += codec->toUnicode(str);
1170 void DictAsyncClient::resultAppend(QString str)
1172 if (job)
1173 job->result += str;
1178 //********* DictInterface ******************************************
1180 DictInterface::DictInterface()
1181 : newServer(false), clientDoneInProgress(false)
1183 if (::pipe(fdPipeIn ) == -1 ) {
1184 perror( "Creating in pipe" );
1185 KMessageBox::error(global->topLevel, i18n("Internal error:\nFailed to open pipes for internal communication."));
1186 kapp->exit(1);
1188 if (::pipe(fdPipeOut ) == -1 ) {
1189 perror( "Creating out pipe" );
1190 KMessageBox::error(global->topLevel, i18n("Internal error:\nFailed to open pipes for internal communication."));
1191 kapp->exit(1);
1194 if (-1 == fcntl(fdPipeIn[0],F_SETFL,O_NONBLOCK)) { // make socket non-blocking
1195 perror("fcntl()");
1196 KMessageBox::error(global->topLevel, i18n("Internal error:\nFailed to open pipes for internal communication."));
1197 kapp->exit(1);
1200 if (-1 == fcntl(fdPipeOut[0],F_SETFL,O_NONBLOCK)) { // make socket non-blocking
1201 perror("fcntl()");
1202 KMessageBox::error(global->topLevel, i18n("Internal error:\nFailed to open pipes for internal communication."));
1203 kapp->exit(1);
1206 notifier = new QSocketNotifier(fdPipeIn[0],QSocketNotifier::Read,this);
1207 connect(notifier,SIGNAL(activated(int)),this,SLOT(clientDone()));
1209 // initialize the KSocks stuff in the main thread, otherwise we get
1210 // strange effects on FreeBSD
1211 (void) KSocks::self();
1213 client = new DictAsyncClient(fdPipeOut[0],fdPipeIn[1]);
1214 if (0!=pthread_create(&threadID,0,&(client->startThread),client)) {
1215 KMessageBox::error(global->topLevel, i18n("Internal error:\nUnable to create thread."));
1216 kapp->exit(1);
1219 jobList.setAutoDelete(true);
1223 DictInterface::~DictInterface()
1225 disconnect(notifier,SIGNAL(activated(int)),this,SLOT(clientDone()));
1227 if (0!=pthread_cancel(threadID))
1228 kdWarning() << "pthread_cancel failed!" << endl;
1229 if (0!=pthread_join(threadID,NULL))
1230 kdWarning() << "pthread_join failed!" << endl;
1231 delete client;
1233 if ( ::close( fdPipeIn[0] ) == -1 ) {
1234 perror( "Closing fdPipeIn[0]" );
1236 if ( ::close( fdPipeIn[1] ) == -1 ) {
1237 perror( "Closing fdPipeIn[1]" );
1239 if ( ::close( fdPipeOut[0] ) == -1 ) {
1240 perror( "Closing fdPipeOut[0]" );
1242 if ( ::close( fdPipeOut[1] ) == -1 ) {
1243 perror( "Closing fdPipeOut[1]" );
1248 // inform the client when server settings get changed
1249 void DictInterface::serverChanged()
1251 newServer = true;
1255 // cancel all pending jobs
1256 void DictInterface::stop()
1258 if (jobList.isEmpty()) {
1259 return;
1260 } else {
1261 while (jobList.count()>1) // not yet started jobs can be deleted directly
1262 jobList.removeLast();
1264 if (!clientDoneInProgress) {
1265 jobList.getFirst()->canceled = true; // clientDone() now ignores the results of this job
1266 char buf; // write one char in the pipe to the async thread
1267 if (::write(fdPipeOut[1],&buf,1) == -1)
1268 ::perror( "stop()" );
1274 void DictInterface::define(const QString &query)
1276 JobData *newJob = generateQuery(JobData::TDefine,query);
1277 if (newJob)
1278 insertJob(newJob);
1282 void DictInterface::getDefinitions(QStringList query)
1284 JobData *newjob = new JobData(JobData::TGetDefinitions,newServer,global->server,global->port,
1285 global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled,
1286 global->user,global->secret,global->headLayout);
1287 newjob->defines = query;
1288 newServer = false;
1289 insertJob(newjob);
1293 void DictInterface::match(const QString &query)
1295 JobData *newJob = generateQuery(JobData::TMatch,query);
1297 if (newJob) {
1298 if (global->currentStrategy == 0)
1299 newJob->strategy = "."; // spell check strategy
1300 else
1301 newJob->strategy = global->strategies[global->currentStrategy].utf8();
1303 insertJob(newJob);
1308 // fetch detailed db info
1309 void DictInterface::showDbInfo(const QString &db)
1311 QString ndb = db.simplifyWhiteSpace(); // cleanup query string
1312 if (ndb.isEmpty())
1313 return;
1314 if (ndb.length()>100) // shorten if necessary
1315 ndb.truncate(100);
1316 JobData *newjob = new JobData(JobData::TShowDbInfo,newServer,global->server,global->port,
1317 global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled,
1318 global->user,global->secret,global->headLayout);
1319 newServer = false;
1320 newjob->query = ndb; // construct job...
1321 insertJob(newjob);
1325 void DictInterface::showDatabases()
1327 insertJob( new JobData(JobData::TShowDatabases,newServer,global->server,global->port,
1328 global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled,
1329 global->user,global->secret,global->headLayout));
1330 newServer = false;
1334 void DictInterface::showStrategies()
1336 insertJob( new JobData(JobData::TShowStrategies,newServer,global->server,global->port,
1337 global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled,
1338 global->user,global->secret,global->headLayout));
1339 newServer = false;
1343 void DictInterface::showInfo()
1345 insertJob( new JobData(JobData::TShowInfo,newServer,global->server,global->port,
1346 global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled,
1347 global->user,global->secret,global->headLayout));
1348 newServer = false;
1352 // get info about databases & stratgies the server knows
1353 void DictInterface::updateServer()
1355 insertJob( new JobData(JobData::TUpdate,newServer,global->server,global->port,
1356 global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled,
1357 global->user,global->secret,global->headLayout));
1358 newServer = false;
1362 // client-thread ended
1363 void DictInterface::clientDone()
1365 QString message;
1367 cleanPipes(); // read from pipe so that notifier doesn´t fire again
1369 if (jobList.isEmpty()) {
1370 kdDebug(5004) << "This shouldn´t happen, the client-thread signaled termination, but the job list is empty" << endl;
1371 return; // strange..
1374 clientDoneInProgress = true;
1375 QStringList::iterator it;
1376 JobData* job = jobList.getFirst();
1377 if (!job->canceled) { // non-interupted job?
1378 if (JobData::ErrNoErr == job->error) {
1379 switch (job->type) {
1380 case JobData::TUpdate :
1381 global->serverDatabases.clear();
1382 for (it = job->databases.begin(); it != job->databases.end(); ++it)
1383 global->serverDatabases.append(*it);
1384 global->databases = global->serverDatabases;
1385 for (int i = global->databaseSets.count()-1;i>=0;i--)
1386 global->databases.prepend(global->databaseSets.at(i)->first());
1387 global->databases.prepend(i18n("All Databases"));
1388 global->currentDatabase = 0;
1390 global->strategies.clear();
1391 for (it = job->strategies.begin(); it != job->strategies.end(); ++it)
1392 global->strategies.append(*it);
1393 global->strategies.prepend(i18n("Spell Check"));
1394 global->currentStrategy = 0;
1395 message = i18n(" Received database/strategy list ");
1396 emit stopped(message);
1397 emit infoReady();
1398 break;
1399 case JobData::TDefine:
1400 case JobData::TGetDefinitions:
1401 if (job->type == JobData::TDefine) {
1402 switch (job->numFetched) {
1403 case 0:
1404 message = i18n("No definitions found");
1405 break;
1406 case 1:
1407 message = i18n("One definition found");
1408 break;
1409 default:
1410 message = i18n("%1 definitions found").arg(job->numFetched);
1412 } else {
1413 switch (job->numFetched) {
1414 case 0:
1415 message = i18n(" No definitions fetched ");
1416 break;
1417 case 1:
1418 message = i18n(" One definition fetched ");
1419 break;
1420 default:
1421 message = i18n(" %1 definitions fetched ").arg(job->numFetched);
1424 emit stopped(message);
1425 emit resultReady(job->result, job->query);
1426 break;
1427 case JobData::TMatch:
1428 switch (job->numFetched) {
1429 case 0:
1430 message = i18n(" No matching definitions found ");
1431 break;
1432 case 1:
1433 message = i18n(" One matching definition found ");
1434 break;
1435 default:
1436 message = i18n(" %1 matching definitions found ").arg(job->numFetched);
1438 emit stopped(message);
1439 emit matchReady(job->matches);
1440 break;
1441 default :
1442 message = i18n(" Received information ");
1443 emit stopped(message);
1444 emit resultReady(job->result, job->query);
1446 } else {
1447 QString errMsg;
1448 switch (job->error) {
1449 case JobData::ErrCommunication:
1450 errMsg = i18n("Communication error:\n\n");
1451 errMsg += job->result;
1452 break;
1453 case JobData::ErrTimeout:
1454 errMsg = i18n("A delay occurred which exceeded the\ncurrent timeout limit of %1 seconds.\nYou can modify this limit in the Preferences Dialog.").arg(global->timeout);
1455 break;
1456 case JobData::ErrBadHost:
1457 errMsg = i18n("Unable to connect to:\n%1:%2\n\nCannot resolve hostname.").arg(job->server).arg(job->port);
1458 break;
1459 case JobData::ErrConnect:
1460 errMsg = i18n("Unable to connect to:\n%1:%2\n\n").arg(job->server).arg(job->port);
1461 errMsg += job->result;
1462 break;
1463 case JobData::ErrRefused:
1464 errMsg = i18n("Unable to connect to:\n%1:%2\n\nThe server refused the connection.").arg(job->server).arg(job->port);
1465 break;
1466 case JobData::ErrNotAvailable:
1467 errMsg = i18n("The server is temporarily unavailable.");
1468 break;
1469 case JobData::ErrSyntax:
1470 errMsg = i18n("The server reported a syntax error.\nThis shouldn't happen -- please consider\nwriting a bug report.");
1471 break;
1472 case JobData::ErrCommandNotImplemented:
1473 errMsg = i18n("A command that Kdict needs isn't\nimplemented on the server.");
1474 break;
1475 case JobData::ErrAccessDenied:
1476 errMsg = i18n("Access denied.\nThis host is not allowed to connect.");
1477 break;
1478 case JobData::ErrAuthFailed:
1479 errMsg = i18n("Authentication failed.\nPlease enter a valid username and password.");
1480 break;
1481 case JobData::ErrInvalidDbStrat:
1482 errMsg = i18n("Invalid database/strategy.\nYou probably need to use Server->Get Capabilities.");
1483 break;
1484 case JobData::ErrNoDatabases:
1485 errMsg = i18n("No databases available.\nIt is possible that you need to authenticate\nwith a valid username/password combination to\ngain access to any databases.");
1486 break;
1487 case JobData::ErrNoStrategies:
1488 errMsg = i18n("No strategies available.");
1489 break;
1490 case JobData::ErrServerError:
1491 errMsg = i18n("The server sent an unexpected reply:\n\"%1\"\nThis shouldn't happen, please consider\nwriting a bug report").arg(job->result);
1492 break;
1493 case JobData::ErrMsgTooLong:
1494 errMsg = i18n("The server sent a response with a text line\nthat was too long.\n(RFC 2229: max. 1024 characters/6144 octets)");
1495 break;
1496 case JobData::ErrNoErr: // make compiler happy
1497 errMsg = i18n("No Errors");
1499 message = i18n(" Error ");
1500 emit stopped(message);
1501 KMessageBox::error(global->topLevel, errMsg);
1503 } else {
1504 message = i18n(" Stopped ");
1505 emit stopped(message);
1508 clientDoneInProgress = false;
1510 client->removeJob();
1511 jobList.removeFirst(); // this job is now history
1512 if (!jobList.isEmpty()) // work to be done?
1513 startClient(); // => restart client
1517 JobData* DictInterface::generateQuery(JobData::QueryType type, QString query)
1519 query = query.simplifyWhiteSpace(); // cleanup query string
1520 if (query.isEmpty())
1521 return 0L;
1522 if (query.length()>300) // shorten if necessary
1523 query.truncate(300);
1524 query = query.replace(QRegExp("[\"\\]"), ""); // remove remaining illegal chars...
1525 if (query.isEmpty())
1526 return 0L;
1528 JobData *newjob = new JobData(type,newServer,global->server,global->port,
1529 global->idleHold,global->timeout,global->pipeSize, global->encoding, global->authEnabled,
1530 global->user,global->secret,global->headLayout);
1531 newServer = false;
1532 newjob->query = query; // construct job...
1534 if (global->currentDatabase == 0) // all databases
1535 newjob->databases.append("*");
1536 else {
1537 if ((global->currentDatabase > 0)&& // database set
1538 (global->currentDatabase < global->databaseSets.count()+1)) {
1539 for (int i = 0;i<(int)global->serverDatabases.count();i++)
1540 if ((global->databaseSets.at(global->currentDatabase-1))->findIndex(global->serverDatabases[i])>0)
1541 newjob->databases.append(global->serverDatabases[i].utf8().data());
1542 if (newjob->databases.count()==0) {
1543 KMessageBox::sorry(global->topLevel, i18n("Please select at least one database."));
1544 delete newjob;
1545 return 0L;
1547 } else { // one database
1548 newjob->databases.append(global->databases[global->currentDatabase].utf8().data());
1552 return newjob;
1556 void DictInterface::insertJob(JobData* job)
1558 if (jobList.isEmpty()) { // Client has nothing to do, start directly
1559 jobList.append(job);
1560 startClient();
1561 } else { // there are other pending jobs...
1562 stop();
1563 jobList.append(job);
1568 // start client-thread
1569 void DictInterface::startClient()
1571 cleanPipes();
1572 if (jobList.isEmpty()) {
1573 kdDebug(5004) << "This shouldn´t happen, startClient called, but clientList is empty" << endl;
1574 return;
1577 client->insertJob(jobList.getFirst());
1578 char buf; // write one char in the pipe to the async thread
1579 if (::write(fdPipeOut[1],&buf,1) == -1)
1580 ::perror( "startClient()" );
1582 QString message;
1583 switch (jobList.getFirst()->type) {
1584 case JobData::TDefine:
1585 case JobData::TGetDefinitions:
1586 case JobData::TMatch:
1587 message = i18n(" Querying server... ");
1588 break;
1589 case JobData::TShowDatabases:
1590 case JobData::TShowStrategies:
1591 case JobData::TShowInfo:
1592 case JobData::TShowDbInfo:
1593 message = i18n(" Fetching information... ");
1594 break;
1595 case JobData::TUpdate:
1596 message = i18n(" Updating server information... ");
1597 break;
1599 emit started(message);
1603 // empty the pipes, so that notifier stops firing
1604 void DictInterface::cleanPipes()
1606 fd_set rfds;
1607 struct timeval tv;
1608 int ret;
1609 char buf;
1610 tv.tv_sec = 0;
1611 tv.tv_usec = 0;
1613 do {
1614 FD_ZERO(&rfds);
1615 FD_SET(fdPipeIn[0],&rfds);
1616 if (1==(ret=select(FD_SETSIZE,&rfds,NULL,NULL,&tv)))
1617 if ( ::read(fdPipeIn[0], &buf, 1 ) == -1 )
1618 ::perror( "cleanPipes" );
1619 } while (ret == 1);
1621 do {
1622 FD_ZERO(&rfds);
1623 FD_SET(fdPipeOut[0],&rfds);
1624 if (1==(ret=select(FD_SETSIZE,&rfds,NULL,NULL,&tv)))
1625 if ( ::read(fdPipeOut[0], &buf, 1 ) == -1 )
1626 ::perror( "cleanPipes" );
1627 } while (ret == 1);
1630 //--------------------------------
1632 #include "dict.moc"