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 ------------------------------------------------------------- */
21 #include "application.h"
26 #include <qtextcodec.h>
30 #include <kmessagebox.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()
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();
87 void DictAsyncClient::insertJob(JobData
*newJob
)
89 if (!job
) // don't overwrite existing job pointer
94 void DictAsyncClient::removeJob()
100 void DictAsyncClient::waitForWork()
108 if (tcpSocket
!= -1) { // we are connected, hold the connection for xx secs
110 FD_SET(fdPipeIn
, &fdsR
);
111 FD_SET(tcpSocket
, &fdsR
);
113 FD_SET(tcpSocket
, &fdsE
);
114 tv
.tv_sec
= idleHold
;
116 selectRet
= KSocks::self()->select(FD_SETSIZE
, &fdsR
, NULL
, &fdsE
, &tv
);
117 if (selectRet
== 0) {
118 doQuit(); // nothing happend...
120 if (((selectRet
> 0)&&(!FD_ISSET(fdPipeIn
,&fdsR
)))||(selectRet
== -1))
127 FD_SET(fdPipeIn
, &fdsR
);
128 } while (select(FD_SETSIZE
, &fdsR
, NULL
, NULL
, NULL
)<0); // don't get tricked by signals
133 if ((tcpSocket
!=-1)&&(job
->newServer
))
136 codec
= QTextCodec::codecForName(job
->encoding
.latin1());
137 input
[0] = 0; //terminate string
141 timeout
= job
->timeout
;
142 idleHold
= job
->idleHold
;
147 if (tcpSocket
!=-1) { // connection is ready
149 case JobData::TDefine
:
152 case JobData::TGetDefinitions
:
155 case JobData::TMatch
:
158 case JobData::TShowDatabases
:
161 case JobData::TShowDbInfo
:
164 case JobData::TShowStrategies
:
167 case JobData::TShowInfo
:
170 case JobData::TUpdate
:
176 if (write(fdPipeOut
,&buf
,1) == -1) // emit stopped signal
177 ::perror( "waitForJobs()" );
182 void DictAsyncClient::define()
186 job
->defines
.clear();
187 QStringList::iterator it
;
188 for (it
= job
->databases
.begin(); it
!= job
->databases
.end(); ++it
) {
192 command
+= job
->query
;
194 job
->defines
.append(command
);
197 if (!getDefinitions())
200 if (job
->numFetched
== 0) {
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>");
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");
216 QStringList::iterator it
;
217 for (it
= job
->matches
.begin(); it
!= job
->matches
.end(); ++it
) {
218 int pos
= (*it
).find(' ');
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));
232 resultAppend((*it
).mid(pos
+2, (*it
).length()-pos
-3));
233 resultAppend("</a> ");
237 resultAppend("\n</pre></td></tr></table>\n</body></html>");
244 QString
htmlString(const QString
&raw
)
246 unsigned int len
=raw
.length();
249 for (unsigned int i
=0; i
<len
; i
++) {
251 case '&' : ret
+= "&"; break;
252 case '<' : ret
+="<"; break;
253 case '>' : ret
+=">"; break;
254 default : ret
+=raw
[i
];
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;
270 matchPos
= http
.search(raw
);
271 matchLen
= http
.matchedLength();
272 if (-1 != matchPos
) {
275 matchPos
= ftp
.search(raw
);
276 matchLen
= ftp
.matchedLength();
280 if (-1 != matchPos
) {
281 ret
= htmlString(raw
.left(matchPos
));
282 ret
+= "<a href=\"http://";
285 ret
+= raw
.mid(matchPos
+7, matchLen
-7);
288 ret
+= raw
.mid(matchPos
+6, matchLen
-6);
291 ret
+= htmlString(raw
.mid(matchPos
, matchLen
));
293 ret
+= htmlString(raw
.right(raw
.length()-matchLen
-matchPos
));
295 ret
= "<a href=\"http://define/";
298 ret
+= htmlString(raw
);
306 bool DictAsyncClient::getDefinitions()
308 QCString lastDb
,bracketBuff
;
311 int defCount
,response
;
314 resultAppend("<body>\n");
316 while (job
->defines
.count()>0) {
320 QStringList::iterator it
= job
->defines
.begin();
321 cmdBuffer
+= codec
->fromUnicode(*it
);
323 job
->defines
.remove(it
);
324 } while ((job
->defines
.count()>0)&&((int)cmdBuffer
.length()<job
->pipeSize
));
329 for (;defCount
> 0;defCount
--) {
330 if (!getNextResponse(response
))
333 case 552: // define: 552 No match
335 case 150: { // define: 150 n definitions retrieved - definitions follow
336 bool defineDone
= false;
337 while (!defineDone
) {
338 if (!getNextResponse(response
))
341 case 151: { // define: 151 word database name - text follows
342 char *db
= strchr(thisLine
, '\"');
344 db
= strchr(db
+1, '\"');
347 db
+=2; // db points now on database name
348 dbdes
= strchr(db
,' ');
350 dbdes
[0] = 0; // terminate database name
351 dbdes
+=2; // dbdes points now on database description
354 job
->error
= JobData::ErrServerError
;
355 job
->result
= QString::null
;
356 resultAppend(thisLine
);
361 int oldResPos
= job
->result
.length();
363 if (((job
->headLayout
<2)&&(lastDb
!=db
))||(job
->headLayout
==2)) {
365 resultAppend("<p class=\"heading\">\n");
367 resultAppend(codec
->toUnicode(dbdes
,strlen(dbdes
)-1));
368 resultAppend(" [<a href=\"http://dbinfo/");
372 resultAppend("</a>]</p>\n");
374 if (job
->headLayout
==1)
375 resultAppend("<hr>\n");
377 resultAppend("<pre><p class=\"definition\">\n");
380 bool bodyDone
= false;
384 char *line
= thisLine
;
387 line
++; // collapse double periode into one
393 context
.update(QCString(line
));
394 if (!bracketBuff
.isEmpty()) {
395 s
= strchr(line
,'}');
397 resultAppend(bracketBuff
.data());
400 bracketBuff
.remove(0,1); // remove '{'
403 resultAppend(generateDefineLink(codec
->toUnicode(bracketBuff
)));
407 s
= strchr(line
,'{');
409 resultAppend(htmlString(codec
->toUnicode(line
,s
-line
)));
411 s
= strchr(line
,'}');
415 resultAppend(generateDefineLink(codec
->toUnicode(line
)));
417 s
= strchr(line
,'{');
426 resultAppend(htmlString(codec
->toUnicode(line
)));
431 resultAppend("</p></pre>\n");
433 if (hashList
.find(context
.hexDigest())>=0) // duplicate??
434 job
->result
.truncate(oldResPos
); // delete the whole definition
436 hashList
.append(context
.hexDigest());
441 case 250: { // define: 250 ok (optional timing information here)
457 resultAppend("</body></html>\n");
462 bool DictAsyncClient::match()
464 QStringList::iterator it
= job
->databases
.begin();
468 while (it
!= job
->databases
.end()) {
471 cmdBuffer
+= "match ";
472 cmdBuffer
+= codec
->fromUnicode(*(it
));
474 cmdBuffer
+= codec
->fromUnicode(job
->strategy
);
476 cmdBuffer
+= codec
->fromUnicode(job
->query
);
477 cmdBuffer
+= "\"\r\n";
480 } while ((it
!= job
->databases
.end())&&((int)cmdBuffer
.length()<job
->pipeSize
));
485 for (;send
> 0;send
--) {
486 if (!getNextResponse(response
))
489 case 552: // match: 552 No match
491 case 152: { // match: 152 n matches found - text follows
492 bool matchDone
= false;
496 char *line
= thisLine
;
499 line
++; // collapse double period into one
506 job
->matches
.append(codec
->toUnicode(line
));
509 if (!nextResponseOk(250)) // match: "250 ok ..."
523 void DictAsyncClient::showDatabases()
525 cmdBuffer
= "show db\r\n";
530 if (!nextResponseOk(110)) // show db: "110 n databases present - text follows "
534 resultAppend("<body>\n<p class=\"heading\">\n");
535 resultAppend(i18n("Available Databases:"));
536 resultAppend("\n</p>\n<table width=\"100%\" cols=2>\n");
546 line
++; // collapse double periode into one
552 resultAppend("<tr valign=top><td width=25%><pre><a href=\"http://dbinfo/");
553 char *space
= strchr(line
,' ');
555 resultAppend(codec
->toUnicode(line
,space
-line
));
557 resultAppend(codec
->toUnicode(line
,space
-line
));
558 resultAppend("</a></pre></td><td width=75%><pre>");
561 line
++; // remove double quote
562 char *quote
= strchr(line
, '\"');
566 } else { // hmmm, malformated line...
567 resultAppend("\"></a></pre></td><td width=75%>");
570 resultAppend("</pre></td></tr>\n");
573 resultAppend("</table>\n</body></html>");
575 if (!nextResponseOk(250)) // end of transmission: "250 ok ..."
580 void DictAsyncClient::showDbInfo()
582 cmdBuffer
= "show info ";
583 cmdBuffer
+= codec
->fromUnicode(job
->query
);
589 if (!nextResponseOk(112)) // show info db: "112 database information follows"
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");
605 line
++; // collapse double periode into one
616 resultAppend("</p></pre>\n</body></html>");
618 if (!nextResponseOk(250)) // end of transmission: "250 ok ..."
623 void DictAsyncClient::showStrategies()
625 cmdBuffer
= "show strat\r\n";
630 if (!nextResponseOk(111)) // show strat: "111 n strategies present - text follows "
634 resultAppend("<body>\n<p class=\"heading\">\n");
635 resultAppend(i18n("Available Strategies:"));
636 resultAppend("\n</p>\n<table width=\"100%\" cols=2>\n");
646 line
++; // collapse double periode into one
652 resultAppend("<tr valign=top><td width=25%><pre>");
653 char *space
= strchr(line
,' ');
655 resultAppend(codec
->toUnicode(line
,space
-line
));
656 resultAppend("</pre></td><td width=75%><pre>");
659 line
++; // remove double quote
660 char *quote
= strchr(line
, '\"');
664 } else { // hmmm, malformated line...
665 resultAppend("</pre></td><td width=75%><pre>");
668 resultAppend("</pre></td></tr>\n");
671 resultAppend("</table>\n</body></html>");
673 if (!nextResponseOk(250)) // end of transmission: "250 ok ..."
678 void DictAsyncClient::showInfo()
680 cmdBuffer
= "show server\r\n";
685 if (!nextResponseOk(114)) // show server: "114 server information follows"
689 resultAppend("<body>\n<p class=\"heading\">\n");
690 resultAppend(i18n("Server Information:"));
691 resultAppend("\n</p>\n<pre><p class=\"definition\">\n");
701 line
++; // collapse double periode into one
712 resultAppend("</p></pre>\n</body></html>");
714 if (!nextResponseOk(250)) // end of transmission: "250 ok ..."
719 void DictAsyncClient::update()
721 cmdBuffer
= "show strat\r\nshow db\r\n";
726 if (!nextResponseOk(111)) // show strat: "111 n strategies present - text follows "
737 line
++; // collapse double periode into one
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 ..."
752 if (!nextResponseOk(110)) // show db: "110 n databases present - text follows "
762 line
++; // collapse double periode into one
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 ..."
779 // connect, handshake and authorization
780 void DictAsyncClient::openConnection()
782 if (job
->server
.isEmpty()) {
783 job
->error
= JobData::ErrBadHost
;
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
;
801 job
->result
= QString::null
;
802 resultAppend(KExtendedSocket::strError(ks
.status(), errno
));
803 job
->error
= JobData::ErrCommunication
;
812 if (!nextResponseOk(220)) // connect: "220 text capabilities msg-id"
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
;
830 context
.update(QCString(msgId
));
831 context
.update(job
->secret
.local8Bit());
833 cmdBuffer
+= "auth " + job
->user
.local8Bit() + " ";
834 cmdBuffer
+= context
.hexDigest();
841 if (!nextResponseOk(250)) // client: "250 ok ..."
844 if (job
->authEnabled
)
845 if (!nextResponseOk(230)) // auth: "230 Authentication successful"
850 void DictAsyncClient::closeSocket()
852 if (-1 != tcpSocket
) {
859 // send "quit" without timeout, without checks, close connection
860 void DictAsyncClient::doQuit()
866 FD_SET(tcpSocket
, &fdsW
);
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
);
880 // used by getNextLine()
881 bool DictAsyncClient::waitForRead()
889 FD_SET(fdPipeIn
, &fdsR
);
890 FD_SET(tcpSocket
, &fdsR
);
892 FD_SET(tcpSocket
, &fdsE
);
893 FD_SET(fdPipeIn
, &fdsE
);
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
901 job
->result
= QString::null
;
902 resultAppend(strerror(errno
));
903 job
->error
= JobData::ErrCommunication
;
908 if (ret
== 0) { // Nothing happend, timeout
910 job
->error
= JobData::ErrTimeout
;
915 if (FD_ISSET(fdPipeIn
,&fdsR
)) { // stop signal
919 if (FD_ISSET(tcpSocket
,&fdsE
)||FD_ISSET(fdPipeIn
,&fdsE
)) { // broken pipe, etc
921 job
->result
= QString::null
;
922 resultAppend(i18n("The connection is broken."));
923 job
->error
= JobData::ErrCommunication
;
928 if (FD_ISSET(tcpSocket
,&fdsR
)) // all ok
933 job
->result
= QString::null
;
934 job
->error
= JobData::ErrCommunication
;
941 // used by sendBuffer() & connect()
942 bool DictAsyncClient::waitForWrite()
944 fd_set fdsR
,fdsW
,fdsE
;
950 FD_SET(fdPipeIn
, &fdsR
);
951 FD_SET(tcpSocket
, &fdsR
);
953 FD_SET(tcpSocket
, &fdsW
);
955 FD_SET(tcpSocket
, &fdsE
);
956 FD_SET(fdPipeIn
, &fdsE
);
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
964 job
->result
= QString::null
;
965 resultAppend(strerror(errno
));
966 job
->error
= JobData::ErrCommunication
;
971 if (ret
== 0) { // nothing happend, timeout
973 job
->error
= JobData::ErrTimeout
;
978 if (FD_ISSET(fdPipeIn
,&fdsR
)) { // stop signal
982 if (FD_ISSET(tcpSocket
,&fdsR
)||FD_ISSET(tcpSocket
,&fdsE
)||FD_ISSET(fdPipeIn
,&fdsE
)) { // broken pipe, etc
984 job
->result
= QString::null
;
985 resultAppend(i18n("The connection is broken."));
986 job
->error
= JobData::ErrCommunication
;
991 if (FD_ISSET(tcpSocket
,&fdsW
)) // all ok
995 job
->result
= QString::null
;
996 job
->error
= JobData::ErrCommunication
;
1003 // remove start/stop signal
1004 void DictAsyncClient::clearPipe()
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()
1026 int todo
= cmdBuffer
.length();
1030 if (!waitForWrite())
1032 ret
= KSocks::self()->write(tcpSocket
,&cmdBuffer
.data()[done
],todo
);
1035 job
->result
= QString::null
;
1036 resultAppend(strerror(errno
));
1037 job
->error
= JobData::ErrCommunication
;
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
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
1064 inputEnd
= input
+div
-1;
1066 if ((inputEnd
-input
) > 9000) {
1067 job
->error
= JobData::ErrMsgTooLong
;
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
;
1086 inputEnd
+= received
;
1087 inputEnd
[0] = 0; // terminate *char
1088 } while (!(nextLine
= strstr(thisLine
,"\r\n")));
1089 nextLine
[0] = 0; // terminate string
1096 // reads next line and checks the response code
1097 bool DictAsyncClient::nextResponseOk(int code
)
1101 if (strtol(thisLine
,0L,0)!=code
) {
1109 // reads next line and returns the response code
1110 bool DictAsyncClient::getNextResponse(int &code
)
1114 code
= strtol(thisLine
,0L,0);
1119 void DictAsyncClient::handleErrors()
1121 int len
= strlen(thisLine
);
1124 job
->result
= QString::null
;
1125 resultAppend(codec
->toUnicode(thisLine
,len
));
1127 switch (strtol(thisLine
,0L,0)) {
1130 job
->error
= JobData::ErrNotAvailable
; // server unavailable
1134 job
->error
= JobData::ErrSyntax
; // syntax error
1138 job
->error
= JobData::ErrCommandNotImplemented
; // command not implemented
1141 job
->error
= JobData::ErrAccessDenied
; // access denied
1144 job
->error
= JobData::ErrAuthFailed
; // authentication failed
1148 job
->error
= JobData::ErrInvalidDbStrat
; // invalid strategy/database
1151 job
->error
= JobData::ErrNoDatabases
; // no databases
1154 job
->error
= JobData::ErrNoStrategies
; // no strategies
1157 job
->error
= JobData::ErrServerError
;
1163 void DictAsyncClient::resultAppend(const char* str
)
1166 job
->result
+= codec
->toUnicode(str
);
1170 void DictAsyncClient::resultAppend(QString 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."));
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."));
1194 if (-1 == fcntl(fdPipeIn
[0],F_SETFL
,O_NONBLOCK
)) { // make socket non-blocking
1196 KMessageBox::error(global
->topLevel
, i18n("Internal error:\nFailed to open pipes for internal communication."));
1200 if (-1 == fcntl(fdPipeOut
[0],F_SETFL
,O_NONBLOCK
)) { // make socket non-blocking
1202 KMessageBox::error(global
->topLevel
, i18n("Internal error:\nFailed to open pipes for internal communication."));
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."));
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
;
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()
1255 // cancel all pending jobs
1256 void DictInterface::stop()
1258 if (jobList
.isEmpty()) {
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
);
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
;
1293 void DictInterface::match(const QString
&query
)
1295 JobData
*newJob
= generateQuery(JobData::TMatch
,query
);
1298 if (global
->currentStrategy
== 0)
1299 newJob
->strategy
= "."; // spell check strategy
1301 newJob
->strategy
= global
->strategies
[global
->currentStrategy
].utf8();
1308 // fetch detailed db info
1309 void DictInterface::showDbInfo(const QString
&db
)
1311 QString ndb
= db
.simplifyWhiteSpace(); // cleanup query string
1314 if (ndb
.length()>100) // shorten if necessary
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
);
1320 newjob
->query
= ndb
; // construct job...
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
));
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
));
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
));
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
));
1362 // client-thread ended
1363 void DictInterface::clientDone()
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
);
1399 case JobData::TDefine
:
1400 case JobData::TGetDefinitions
:
1401 if (job
->type
== JobData::TDefine
) {
1402 switch (job
->numFetched
) {
1404 message
= i18n("No definitions found");
1407 message
= i18n("One definition found");
1410 message
= i18n("%1 definitions found").arg(job
->numFetched
);
1413 switch (job
->numFetched
) {
1415 message
= i18n(" No definitions fetched ");
1418 message
= i18n(" One definition fetched ");
1421 message
= i18n(" %1 definitions fetched ").arg(job
->numFetched
);
1424 emit
stopped(message
);
1425 emit
resultReady(job
->result
, job
->query
);
1427 case JobData::TMatch
:
1428 switch (job
->numFetched
) {
1430 message
= i18n(" No matching definitions found ");
1433 message
= i18n(" One matching definition found ");
1436 message
= i18n(" %1 matching definitions found ").arg(job
->numFetched
);
1438 emit
stopped(message
);
1439 emit
matchReady(job
->matches
);
1442 message
= i18n(" Received information ");
1443 emit
stopped(message
);
1444 emit
resultReady(job
->result
, job
->query
);
1448 switch (job
->error
) {
1449 case JobData::ErrCommunication
:
1450 errMsg
= i18n("Communication error:\n\n");
1451 errMsg
+= job
->result
;
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
);
1456 case JobData::ErrBadHost
:
1457 errMsg
= i18n("Unable to connect to:\n%1:%2\n\nCannot resolve hostname.").arg(job
->server
).arg(job
->port
);
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
;
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
);
1466 case JobData::ErrNotAvailable
:
1467 errMsg
= i18n("The server is temporarily unavailable.");
1469 case JobData::ErrSyntax
:
1470 errMsg
= i18n("The server reported a syntax error.\nThis shouldn't happen -- please consider\nwriting a bug report.");
1472 case JobData::ErrCommandNotImplemented
:
1473 errMsg
= i18n("A command that Kdict needs isn't\nimplemented on the server.");
1475 case JobData::ErrAccessDenied
:
1476 errMsg
= i18n("Access denied.\nThis host is not allowed to connect.");
1478 case JobData::ErrAuthFailed
:
1479 errMsg
= i18n("Authentication failed.\nPlease enter a valid username and password.");
1481 case JobData::ErrInvalidDbStrat
:
1482 errMsg
= i18n("Invalid database/strategy.\nYou probably need to use Server->Get Capabilities.");
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.");
1487 case JobData::ErrNoStrategies
:
1488 errMsg
= i18n("No strategies available.");
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
);
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)");
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
);
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())
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())
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
);
1532 newjob
->query
= query
; // construct job...
1534 if (global
->currentDatabase
== 0) // all databases
1535 newjob
->databases
.append("*");
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."));
1547 } else { // one database
1548 newjob
->databases
.append(global
->databases
[global
->currentDatabase
].utf8().data());
1556 void DictInterface::insertJob(JobData
* job
)
1558 if (jobList
.isEmpty()) { // Client has nothing to do, start directly
1559 jobList
.append(job
);
1561 } else { // there are other pending jobs...
1563 jobList
.append(job
);
1568 // start client-thread
1569 void DictInterface::startClient()
1572 if (jobList
.isEmpty()) {
1573 kdDebug(5004) << "This shouldn´t happen, startClient called, but clientList is empty" << endl
;
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()" );
1583 switch (jobList
.getFirst()->type
) {
1584 case JobData::TDefine
:
1585 case JobData::TGetDefinitions
:
1586 case JobData::TMatch
:
1587 message
= i18n(" Querying server... ");
1589 case JobData::TShowDatabases
:
1590 case JobData::TShowStrategies
:
1591 case JobData::TShowInfo
:
1592 case JobData::TShowDbInfo
:
1593 message
= i18n(" Fetching information... ");
1595 case JobData::TUpdate
:
1596 message
= i18n(" Updating server information... ");
1599 emit
started(message
);
1603 // empty the pipes, so that notifier stops firing
1604 void DictInterface::cleanPipes()
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" );
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" );
1630 //--------------------------------