/* ------------------------------------------------------------- dict.cpp (part of The KDE Dictionary Client) Copyright (C) 2000-2001 Christian Gebauer <gebauer@kde.org> (C) by Matthias H�lzer 1998 This file is distributed under the Artistic License. See LICENSE for details. ------------------------------------------------------------- JobData used for data transfer between Client and Interface DictAsyncClient all network related stuff happens here in asynchrous thread DictInterface interface for DictAsyncClient, job management ------------------------------------------------------------- */ #include <config.h> #include "application.h" #include "options.h" #include "dict.h" #include <tqregexp.h> #include <tqtextcodec.h> #include <tdelocale.h> #include <kdebug.h> #include <tdemessagebox.h> #include <kmdcodec.h> #include <kextsock.h> #include <ksocks.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #include <signal.h> #include <stdlib.h> //********* JobData ****************************************** JobData::JobData(QueryType Ntype,bool NnewServer,TQString const& Nserver,int Nport, int NidleHold, int Ntimeout, int NpipeSize, TQString const& Nencoding, bool NAuthEnabled, TQString const& Nuser, TQString const& Nsecret, unsigned int NheadLayout) : type(Ntype), error(ErrNoErr), canceled(false), numFetched(0), newServer(NnewServer),server(Nserver), port(Nport), timeout(Ntimeout), pipeSize(NpipeSize), idleHold(NidleHold), encoding(Nencoding), authEnabled(NAuthEnabled), user(Nuser), secret(Nsecret), headLayout(NheadLayout) {} //********* DictAsyncClient ************************************* DictAsyncClient::DictAsyncClient(int NfdPipeIn, int NfdPipeOut) : job(0L), inputSize(10000), fdPipeIn(NfdPipeIn), fdPipeOut(NfdPipeOut), tcpSocket(-1), idleHold(0) { input = new char[inputSize]; } DictAsyncClient::~DictAsyncClient() { if (-1!=tcpSocket) doQuit(); delete [] input; } void* DictAsyncClient::startThread(void* pseudoThis) { DictAsyncClient* newthis = (DictAsyncClient*) (pseudoThis); if (0!=pthread_setcanceltype(PTHREAD_CANCEL_ENABLE,NULL)) tqWarning("pthread_setcanceltype failed!"); if (0!= pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL)) tqWarning("pthread_setcanceltype failed!"); signal(SIGPIPE,SIG_IGN); // ignore sigpipe newthis->waitForWork(); return NULL; } void DictAsyncClient::insertJob(JobData *newJob) { if (!job) // don't overwrite existing job pointer job = newJob; } void DictAsyncClient::removeJob() { job = 0L; } void DictAsyncClient::waitForWork() { fd_set fdsR,fdsE; timeval tv; int selectRet; char buf; while (true) { if (tcpSocket != -1) { // we are connected, hold the connection for xx secs FD_ZERO(&fdsR); FD_SET(fdPipeIn, &fdsR); FD_SET(tcpSocket, &fdsR); FD_ZERO(&fdsE); FD_SET(tcpSocket, &fdsE); tv.tv_sec = idleHold; tv.tv_usec = 0; selectRet = KSocks::self()->select(FD_SETSIZE, &fdsR, NULL, &fdsE, &tv); if (selectRet == 0) { doQuit(); // nothing happend... } else { if (((selectRet > 0)&&(!FD_ISSET(fdPipeIn,&fdsR)))||(selectRet == -1)) closeSocket(); } } do { FD_ZERO(&fdsR); FD_SET(fdPipeIn, &fdsR); } while (select(FD_SETSIZE, &fdsR, NULL, NULL, NULL)<0); // don't get tricked by signals clearPipe(); if (job) { if ((tcpSocket!=-1)&&(job->newServer)) doQuit(); codec = TQTextCodec::codecForName(job->encoding.latin1()); input[0] = 0; //terminate string thisLine = input; nextLine = input; inputEnd = input; timeout = job->timeout; idleHold = job->idleHold; if (tcpSocket==-1) openConnection(); if (tcpSocket!=-1) { // connection is ready switch (job->type) { case JobData::TDefine : define(); break; case JobData::TGetDefinitions : getDefinitions(); break; case JobData::TMatch : match(); break; case JobData::TShowDatabases : showDatabases(); break; case JobData::TShowDbInfo : showDbInfo(); break; case JobData::TShowStrategies : showStrategies(); break; case JobData::TShowInfo : showInfo(); break; case JobData::TUpdate : update(); } } clearPipe(); } if (write(fdPipeOut,&buf,1) == -1) // emit stopped signal ::perror( "waitForJobs()" ); } } void DictAsyncClient::define() { TQString command; job->defines.clear(); TQStringList::iterator it; for (it = job->databases.begin(); it != job->databases.end(); ++it) { command = "define "; command += *it; command += " \""; command += job->query; command += "\"\r\n"; job->defines.append(command); } if (!getDefinitions()) return; if (job->numFetched == 0) { job->strategy = "."; if (!match()) return; job->result = TQString(); if (job->numFetched == 0) { resultAppend("<body>\n<p class=\"heading\">\n"); resultAppend(i18n("No definitions found for \'%1'.").arg(job->query)); resultAppend("</p>\n</html></body>"); } else { // html header... resultAppend("<body>\n<p class=\"heading\">\n"); resultAppend(i18n("No definitions found for \'%1\'. Perhaps you mean:").arg(job->query)); resultAppend("</p>\n<table width=\"100%\" cols=2>\n"); TQString lastDb; TQStringList::iterator it; for (it = job->matches.begin(); it != job->matches.end(); ++it) { int pos = (*it).find(' '); if (pos != -1) { if (lastDb != (*it).left(pos)) { if (lastDb.length() > 0) resultAppend("</pre></td></tr>\n"); lastDb = (*it).left(pos); resultAppend("<tr valign=top><td width=25%><pre><b>"); resultAppend(lastDb); resultAppend(":</b></pre></td><td width=75%><pre>"); } if ((*it).length() > (unsigned int)pos+2) { resultAppend("<a href=\"http://define/"); resultAppend((*it).mid(pos+2, (*it).length()-pos-3)); resultAppend("\">"); resultAppend((*it).mid(pos+2, (*it).length()-pos-3)); resultAppend("</a> "); } } } resultAppend("\n</pre></td></tr></table>\n</body></html>"); job->numFetched = 0; } } } TQString htmlString(const TQString &raw) { unsigned int len=raw.length(); TQString ret; for (unsigned int i=0; i<len; i++) { switch (raw[i]) { case '&' : ret += "&"; break; case '<' : ret+="<"; break; case '>' : ret+=">"; break; default : ret+=raw[i]; } } return ret; } TQString generateDefineLink(const TQString &raw) { TQRegExp http("http://[^\\s<>()\"|\\[\\]{}]+"); TQRegExp ftp("ftp://[^\\s<>()\"|\\[\\]{}]+"); int matchPos=0, matchLen=0; bool httpMatch=false; TQString ret; matchPos = http.search(raw); matchLen = http.matchedLength(); if (-1 != matchPos) { httpMatch = true; } else { matchPos = ftp.search(raw); matchLen = ftp.matchedLength(); httpMatch = false; } if (-1 != matchPos) { ret = htmlString(raw.left(matchPos)); ret += "<a href=\"http://"; if (httpMatch) { ret += "realhttp/"; ret += raw.mid(matchPos+7, matchLen-7); } else { ret += "realftp/"; ret += raw.mid(matchPos+6, matchLen-6); } ret += "\">"; ret += htmlString(raw.mid(matchPos, matchLen)); ret += "</a>"; ret += htmlString(raw.right(raw.length()-matchLen-matchPos)); } else { ret = "<a href=\"http://define/"; ret += raw; ret += "\">"; ret += htmlString(raw); ret += "</a>"; } return ret; } bool DictAsyncClient::getDefinitions() { TQCString lastDb,bracketBuff; TQStrList hashList; char *s; int defCount,response; // html header... resultAppend("<body>\n"); while (job->defines.count()>0) { defCount = 0; cmdBuffer = ""; do { TQStringList::iterator it = job->defines.begin(); cmdBuffer += codec->fromUnicode(*it); defCount++; job->defines.remove(it); } while ((job->defines.count()>0)&&((int)cmdBuffer.length()<job->pipeSize)); if (!sendBuffer()) return false; for (;defCount > 0;defCount--) { if (!getNextResponse(response)) return false; switch (response) { case 552: // define: 552 No match break; case 150: { // define: 150 n definitions retrieved - definitions follow bool defineDone = false; while (!defineDone) { if (!getNextResponse(response)) return false; switch (response) { case 151: { // define: 151 word database name - text follows char *db = strchr(thisLine, '\"'); if (db) db = strchr(db+1, '\"'); char *dbdes = 0; if (db) { db+=2; // db points now on database name dbdes = strchr(db,' '); if (dbdes) { dbdes[0] = 0; // terminate database name dbdes+=2; // dbdes points now on database description } } else { job->error = JobData::ErrServerError; job->result = TQString(); resultAppend(thisLine); doQuit(); return false; } int oldResPos = job->result.length(); if (((job->headLayout<2)&&(lastDb!=db))||(job->headLayout==2)) { lastDb = db; resultAppend("<p class=\"heading\">\n"); if (dbdes) resultAppend(codec->toUnicode(dbdes,strlen(dbdes)-1)); resultAppend(" [<a href=\"http://dbinfo/"); resultAppend(db); resultAppend("\">"); resultAppend(db); resultAppend("</a>]</p>\n"); } else if (job->headLayout==1) resultAppend("<hr>\n"); resultAppend("<pre><p class=\"definition\">\n"); KMD5 context; bool bodyDone = false; while (!bodyDone) { if (!getNextLine()) return false; char *line = thisLine; if (line[0]=='.') { if (line[1]=='.') line++; // collapse double periode into one else if (line[1]==0) bodyDone = true; } if (!bodyDone) { context.update(TQCString(line)); if (!bracketBuff.isEmpty()) { s = strchr(line,'}'); if (!s) resultAppend(bracketBuff.data()); else { s[0] = 0; bracketBuff.remove(0,1); // remove '{' bracketBuff += line; line = s+1; resultAppend(generateDefineLink(codec->toUnicode(bracketBuff))); } bracketBuff = ""; } s = strchr(line,'{'); while (s) { resultAppend(htmlString(codec->toUnicode(line,s-line))); line = s; s = strchr(line,'}'); if (s) { s[0] = 0; line++; resultAppend(generateDefineLink(codec->toUnicode(line))); line = s+1; s = strchr(line,'{'); } else { bracketBuff = line; bracketBuff += "\n"; line = 0; s = 0; } } if (line) { resultAppend(htmlString(codec->toUnicode(line))); resultAppend("\n"); } } } resultAppend("</p></pre>\n"); if (hashList.find(context.hexDigest())>=0) // duplicate?? job->result.truncate(oldResPos); // delete the whole definition else { hashList.append(context.hexDigest()); job->numFetched++; } break; } case 250: { // define: 250 ok (optional timing information here) defineDone = true; break; } default: { handleErrors(); return false; } } } break; } default: handleErrors(); return false; } } } resultAppend("</body></html>\n"); return true; } bool DictAsyncClient::match() { TQStringList::iterator it = job->databases.begin(); int response; cmdBuffer = ""; while (it != job->databases.end()) { int send = 0; do { cmdBuffer += "match "; cmdBuffer += codec->fromUnicode(*(it)); cmdBuffer += " "; cmdBuffer += codec->fromUnicode(job->strategy); cmdBuffer += " \""; cmdBuffer += codec->fromUnicode(job->query); cmdBuffer += "\"\r\n"; send++; ++it; } while ((it != job->databases.end())&&((int)cmdBuffer.length()<job->pipeSize)); if (!sendBuffer()) return false; for (;send > 0;send--) { if (!getNextResponse(response)) return false; switch (response) { case 552: // match: 552 No match break; case 152: { // match: 152 n matches found - text follows bool matchDone = false; while (!matchDone) { if (!getNextLine()) return false; char *line = thisLine; if (line[0]=='.') { if (line[1]=='.') line++; // collapse double period into one else if (line[1]==0) matchDone = true; } if (!matchDone) { job->numFetched++; job->matches.append(codec->toUnicode(line)); } } if (!nextResponseOk(250)) // match: "250 ok ..." return false; break; } default: handleErrors(); return false; } } } return true; } void DictAsyncClient::showDatabases() { cmdBuffer = "show db\r\n"; if (!sendBuffer()) return; if (!nextResponseOk(110)) // show db: "110 n databases present - text follows " return; // html header... resultAppend("<body>\n<p class=\"heading\">\n"); resultAppend(i18n("Available Databases:")); resultAppend("\n</p>\n<table width=\"100%\" cols=2>\n"); bool done(false); char *line; while (!done) { if (!getNextLine()) return; line = thisLine; if (line[0]=='.') { if (line[1]=='.') line++; // collapse double periode into one else if (line[1]==0) done = true; } if (!done) { resultAppend("<tr valign=top><td width=25%><pre><a href=\"http://dbinfo/"); char *space = strchr(line,' '); if (space) { resultAppend(codec->toUnicode(line,space-line)); resultAppend("\">"); resultAppend(codec->toUnicode(line,space-line)); resultAppend("</a></pre></td><td width=75%><pre>"); line = space+1; if (line[0]=='"') { line++; // remove double quote char *quote = strchr(line, '\"'); if (quote) quote[0]=0; } } else { // hmmm, malformated line... resultAppend("\"></a></pre></td><td width=75%>"); } resultAppend(line); resultAppend("</pre></td></tr>\n"); } } resultAppend("</table>\n</body></html>"); if (!nextResponseOk(250)) // end of transmission: "250 ok ..." return; } void DictAsyncClient::showDbInfo() { cmdBuffer = "show info "; cmdBuffer += codec->fromUnicode(job->query); cmdBuffer += "\r\n"; if (!sendBuffer()) return; if (!nextResponseOk(112)) // show info db: "112 database information follows" return; // html header... resultAppend("<body>\n<p class=\"heading\">\n"); resultAppend(i18n("Database Information [%1]:").arg(job->query)); resultAppend("</p>\n<pre><p class=\"definition\">\n"); bool done(false); char *line; while (!done) { if (!getNextLine()) return; line = thisLine; if (line[0]=='.') { if (line[1]=='.') line++; // collapse double periode into one else if (line[1]==0) done = true; } if (!done) { resultAppend(line); resultAppend("\n"); } } resultAppend("</p></pre>\n</body></html>"); if (!nextResponseOk(250)) // end of transmission: "250 ok ..." return; } void DictAsyncClient::showStrategies() { cmdBuffer = "show strat\r\n"; if (!sendBuffer()) return; if (!nextResponseOk(111)) // show strat: "111 n strategies present - text follows " return; // html header... resultAppend("<body>\n<p class=\"heading\">\n"); resultAppend(i18n("Available Strategies:")); resultAppend("\n</p>\n<table width=\"100%\" cols=2>\n"); bool done(false); char *line; while (!done) { if (!getNextLine()) return; line = thisLine; if (line[0]=='.') { if (line[1]=='.') line++; // collapse double periode into one else if (line[1]==0) done = true; } if (!done) { resultAppend("<tr valign=top><td width=25%><pre>"); char *space = strchr(line,' '); if (space) { resultAppend(codec->toUnicode(line,space-line)); resultAppend("</pre></td><td width=75%><pre>"); line = space+1; if (line[0]=='"') { line++; // remove double quote char *quote = strchr(line, '\"'); if (quote) quote[0]=0; } } else { // hmmm, malformated line... resultAppend("</pre></td><td width=75%><pre>"); } resultAppend(line); resultAppend("</pre></td></tr>\n"); } } resultAppend("</table>\n</body></html>"); if (!nextResponseOk(250)) // end of transmission: "250 ok ..." return; } void DictAsyncClient::showInfo() { cmdBuffer = "show server\r\n"; if (!sendBuffer()) return; if (!nextResponseOk(114)) // show server: "114 server information follows" return; // html header... resultAppend("<body>\n<p class=\"heading\">\n"); resultAppend(i18n("Server Information:")); resultAppend("\n</p>\n<pre><p class=\"definition\">\n"); bool done(false); char *line; while (!done) { if (!getNextLine()) return; line = thisLine; if (line[0]=='.') { if (line[1]=='.') line++; // collapse double periode into one else if (line[1]==0) done = true; } if (!done) { resultAppend(line); resultAppend("\n"); } } resultAppend("</p></pre>\n</body></html>"); if (!nextResponseOk(250)) // end of transmission: "250 ok ..." return; } void DictAsyncClient::update() { cmdBuffer = "show strat\r\nshow db\r\n"; if (!sendBuffer()) return; if (!nextResponseOk(111)) // show strat: "111 n strategies present - text follows " return; bool done(false); char *line; while (!done) { if (!getNextLine()) return; line = thisLine; if (line[0]=='.') { if (line[1]=='.') line++; // collapse double periode into one else if (line[1]==0) done = true; } if (!done) { char *space = strchr(line,' '); if (space) space[0] = 0; // terminate string, hack ;-) job->strategies.append(codec->toUnicode(line)); } } if (!nextResponseOk(250)) // end of transmission: "250 ok ..." return; if (!nextResponseOk(110)) // show db: "110 n databases present - text follows " return; done = false; while (!done) { if (!getNextLine()) return; line = thisLine; if (line[0]=='.') { if (line[1]=='.') line++; // collapse double periode into one else if (line[1]==0) done = true; } if (!done) { char *space = strchr(line,' '); if (space) space[0] = 0; // terminate string, hack ;-) job->databases.append(codec->toUnicode(line)); } } if (!nextResponseOk(250)) // end of transmission: "250 ok ..." return; } // connect, handshake and authorization void DictAsyncClient::openConnection() { if (job->server.isEmpty()) { job->error = JobData::ErrBadHost; return; } KExtendedSocket ks; ks.setAddress(job->server, job->port); ks.setTimeout(job->timeout); if (ks.connect() < 0) { if (ks.status() == IO_LookupError) job->error = JobData::ErrBadHost; else if (ks.status() == IO_ConnectError) { job->result = TQString(); resultAppend(KExtendedSocket::strError(ks.status(), errno)); job->error = JobData::ErrConnect; } else if (ks.status() == IO_TimeOutError) job->error = JobData::ErrTimeout; else { job->result = TQString(); resultAppend(KExtendedSocket::strError(ks.status(), errno)); job->error = JobData::ErrCommunication; } closeSocket(); return; } tcpSocket = ks.fd(); ks.release(); if (!nextResponseOk(220)) // connect: "220 text capabilities msg-id" return; cmdBuffer = "client \"Kdict "; cmdBuffer += KDICT_VERSION; cmdBuffer += "\"\r\n"; if (job->authEnabled) if (strstr(thisLine,"auth")) { // skip auth if not supported char *msgId = strrchr(thisLine,'<'); if ((!msgId)||(!job->user.length())) { job->error = JobData::ErrAuthFailed; closeSocket(); return; } KMD5 context; context.update(TQCString(msgId)); context.update(job->secret.local8Bit()); cmdBuffer += "auth " + job->user.local8Bit() + " "; cmdBuffer += context.hexDigest(); cmdBuffer += "\r\n"; } if (!sendBuffer()) return; if (!nextResponseOk(250)) // client: "250 ok ..." return; if (job->authEnabled) if (!nextResponseOk(230)) // auth: "230 Authentication successful" return; } void DictAsyncClient::closeSocket() { if (-1 != tcpSocket) { ::close(tcpSocket); tcpSocket = -1; } } // send "quit" without timeout, without checks, close connection void DictAsyncClient::doQuit() { fd_set fdsW; timeval tv; FD_ZERO(&fdsW); FD_SET(tcpSocket, &fdsW); tv.tv_sec = 0; tv.tv_usec = 0; int ret = KSocks::self()->select(FD_SETSIZE, NULL, &fdsW, NULL, &tv); if (ret > 0) { // we can write... cmdBuffer = "quit\r\n"; int todo = cmdBuffer.length(); KSocks::self()->write(tcpSocket,&cmdBuffer.data()[0],todo); } closeSocket(); } // used by getNextLine() bool DictAsyncClient::waitForRead() { fd_set fdsR,fdsE; timeval tv; int ret; do { FD_ZERO(&fdsR); FD_SET(fdPipeIn, &fdsR); FD_SET(tcpSocket, &fdsR); FD_ZERO(&fdsE); FD_SET(tcpSocket, &fdsE); FD_SET(fdPipeIn, &fdsE); tv.tv_sec = timeout; tv.tv_usec = 0; ret = KSocks::self()->select(FD_SETSIZE, &fdsR, NULL, &fdsE, &tv); } while ((ret<0)&&(errno==EINTR)); // don't get tricked by signals if (ret == -1) { // select failed if (job) { job->result = TQString(); resultAppend(strerror(errno)); job->error = JobData::ErrCommunication; } closeSocket(); return false; } if (ret == 0) { // Nothing happend, timeout if (job) job->error = JobData::ErrTimeout; doQuit(); return false; } if (ret > 0) { if (FD_ISSET(fdPipeIn,&fdsR)) { // stop signal doQuit(); return false; } if (FD_ISSET(tcpSocket,&fdsE)||FD_ISSET(fdPipeIn,&fdsE)) { // broken pipe, etc if (job) { job->result = TQString(); resultAppend(i18n("The connection is broken.")); job->error = JobData::ErrCommunication; } closeSocket(); return false; } if (FD_ISSET(tcpSocket,&fdsR)) // all ok return true; } if (job) { job->result = TQString(); job->error = JobData::ErrCommunication; } closeSocket(); return false; } // used by sendBuffer() & connect() bool DictAsyncClient::waitForWrite() { fd_set fdsR,fdsW,fdsE; timeval tv; int ret; do { FD_ZERO(&fdsR); FD_SET(fdPipeIn, &fdsR); FD_SET(tcpSocket, &fdsR); FD_ZERO(&fdsW); FD_SET(tcpSocket, &fdsW); FD_ZERO(&fdsE); FD_SET(tcpSocket, &fdsE); FD_SET(fdPipeIn, &fdsE); tv.tv_sec = timeout; tv.tv_usec = 0; ret = KSocks::self()->select(FD_SETSIZE, &fdsR, &fdsW, &fdsE, &tv); } while ((ret<0)&&(errno==EINTR)); // don't get tricked by signals if (ret == -1) { // select failed if (job) { job->result = TQString(); resultAppend(strerror(errno)); job->error = JobData::ErrCommunication; } closeSocket(); return false; } if (ret == 0) { // nothing happend, timeout if (job) job->error = JobData::ErrTimeout; closeSocket(); return false; } if (ret > 0) { if (FD_ISSET(fdPipeIn,&fdsR)) { // stop signal doQuit(); return false; } if (FD_ISSET(tcpSocket,&fdsR)||FD_ISSET(tcpSocket,&fdsE)||FD_ISSET(fdPipeIn,&fdsE)) { // broken pipe, etc if (job) { job->result = TQString(); resultAppend(i18n("The connection is broken.")); job->error = JobData::ErrCommunication; } closeSocket(); return false; } if (FD_ISSET(tcpSocket,&fdsW)) // all ok return true; } if (job) { job->result = TQString(); job->error = JobData::ErrCommunication; } closeSocket(); return false; } // remove start/stop signal void DictAsyncClient::clearPipe() { fd_set fdsR; timeval tv; int selectRet; char buf; tv.tv_sec = 0; tv.tv_usec = 0; do { FD_ZERO(&fdsR); FD_SET(fdPipeIn,&fdsR); if (1==(selectRet=select(FD_SETSIZE,&fdsR,NULL,NULL,&tv))) if ( ::read(fdPipeIn, &buf, 1 ) == -1 ) ::perror( "clearPipe()" ); } while (selectRet == 1); } bool DictAsyncClient::sendBuffer() { int ret; int todo = cmdBuffer.length(); int done = 0; while (todo > 0) { if (!waitForWrite()) return false; ret = KSocks::self()->write(tcpSocket,&cmdBuffer.data()[done],todo); if (ret <= 0) { if (job) { job->result = TQString(); resultAppend(strerror(errno)); job->error = JobData::ErrCommunication; } closeSocket(); return false; } else { done += ret; todo -= ret; } } return true; } // set thisLine to next complete line of input bool DictAsyncClient::getNextLine() { thisLine = nextLine; nextLine = strstr(thisLine,"\r\n"); if (nextLine) { // there is another full line in the inputbuffer nextLine[0] = 0; // terminate string nextLine[1] = 0; nextLine+=2; return true; } unsigned int div = inputEnd-thisLine+1; // hmmm, I need to fetch more input from the server... memmove(input,thisLine,div); // save last, incomplete line thisLine = input; inputEnd = input+div-1; do { if ((inputEnd-input) > 9000) { job->error = JobData::ErrMsgTooLong; closeSocket(); return false; } if (!waitForRead()) return false; int received; do { received = KSocks::self()->read(tcpSocket, inputEnd, inputSize-(inputEnd-input)-1); } while ((received<0)&&(errno==EINTR)); // don't get tricked by signals if (received <= 0) { job->result = TQString(); resultAppend(i18n("The connection is broken.")); job->error = JobData::ErrCommunication; closeSocket(); return false; } inputEnd += received; inputEnd[0] = 0; // terminate *char } while (!(nextLine = strstr(thisLine,"\r\n"))); nextLine[0] = 0; // terminate string nextLine[1] = 0; nextLine+=2; return true; } // reads next line and checks the response code bool DictAsyncClient::nextResponseOk(int code) { if (!getNextLine()) return false; if (strtol(thisLine,0L,0)!=code) { handleErrors(); return false; } return true; } // reads next line and returns the response code bool DictAsyncClient::getNextResponse(int &code) { if (!getNextLine()) return false; code = strtol(thisLine,0L,0); return true; } void DictAsyncClient::handleErrors() { int len = strlen(thisLine); if (len>80) len = 80; job->result = TQString(); resultAppend(codec->toUnicode(thisLine,len)); switch (strtol(thisLine,0L,0)) { case 420: case 421: job->error = JobData::ErrNotAvailable; // server unavailable break; case 500: case 501: job->error = JobData::ErrSyntax; // syntax error break; case 502: case 503: job->error = JobData::ErrCommandNotImplemented; // command not implemented break; case 530: job->error = JobData::ErrAccessDenied; // access denied break; case 531: job->error = JobData::ErrAuthFailed; // authentication failed break; case 550: case 551: job->error = JobData::ErrInvalidDbStrat; // invalid strategy/database break; case 554: job->error = JobData::ErrNoDatabases; // no databases break; case 555: job->error = JobData::ErrNoStrategies; // no strategies break; default: job->error = JobData::ErrServerError; } doQuit(); } void DictAsyncClient::resultAppend(const char* str) { if (job) job->result += codec->toUnicode(str); } void DictAsyncClient::resultAppend(TQString str) { if (job) job->result += str; } //********* DictInterface ****************************************** DictInterface::DictInterface() : newServer(false), clientDoneInProgress(false) { if (::pipe(fdPipeIn ) == -1 ) { perror( "Creating in pipe" ); KMessageBox::error(global->topLevel, i18n("Internal error:\nFailed to open pipes for internal communication.")); kapp->exit(1); } if (::pipe(fdPipeOut ) == -1 ) { perror( "Creating out pipe" ); KMessageBox::error(global->topLevel, i18n("Internal error:\nFailed to open pipes for internal communication.")); kapp->exit(1); } if (-1 == fcntl(fdPipeIn[0],F_SETFL,O_NONBLOCK)) { // make socket non-blocking perror("fcntl()"); KMessageBox::error(global->topLevel, i18n("Internal error:\nFailed to open pipes for internal communication.")); kapp->exit(1); } if (-1 == fcntl(fdPipeOut[0],F_SETFL,O_NONBLOCK)) { // make socket non-blocking perror("fcntl()"); KMessageBox::error(global->topLevel, i18n("Internal error:\nFailed to open pipes for internal communication.")); kapp->exit(1); } notifier = new TQSocketNotifier(fdPipeIn[0],TQSocketNotifier::Read,this); connect(notifier,TQT_SIGNAL(activated(int)),this,TQT_SLOT(clientDone())); // initialize the KSocks stuff in the main thread, otherwise we get // strange effects on FreeBSD (void) KSocks::self(); client = new DictAsyncClient(fdPipeOut[0],fdPipeIn[1]); if (0!=pthread_create(&threadID,0,&(client->startThread),client)) { KMessageBox::error(global->topLevel, i18n("Internal error:\nUnable to create thread.")); kapp->exit(1); } jobList.setAutoDelete(true); } DictInterface::~DictInterface() { disconnect(notifier,TQT_SIGNAL(activated(int)),this,TQT_SLOT(clientDone())); if (0!=pthread_cancel(threadID)) kdWarning() << "pthread_cancel failed!" << endl; if (0!=pthread_join(threadID,NULL)) kdWarning() << "pthread_join failed!" << endl; delete client; if ( ::close( fdPipeIn[0] ) == -1 ) { perror( "Closing fdPipeIn[0]" ); } if ( ::close( fdPipeIn[1] ) == -1 ) { perror( "Closing fdPipeIn[1]" ); } if ( ::close( fdPipeOut[0] ) == -1 ) { perror( "Closing fdPipeOut[0]" ); } if ( ::close( fdPipeOut[1] ) == -1 ) { perror( "Closing fdPipeOut[1]" ); } } // inform the client when server settings get changed void DictInterface::serverChanged() { newServer = true; } // cancel all pending jobs void DictInterface::stop() { if (jobList.isEmpty()) { return; } else { while (jobList.count()>1) // not yet started jobs can be deleted directly jobList.removeLast(); if (!clientDoneInProgress) { jobList.getFirst()->canceled = true; // clientDone() now ignores the results of this job char buf; // write one char in the pipe to the async thread if (::write(fdPipeOut[1],&buf,1) == -1) ::perror( "stop()" ); } } } void DictInterface::define(const TQString &query) { JobData *newJob = generateQuery(JobData::TDefine,query); if (newJob) insertJob(newJob); } void DictInterface::getDefinitions(TQStringList query) { JobData *newjob = new JobData(JobData::TGetDefinitions,newServer,global->server,global->port, global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled, global->user,global->secret,global->headLayout); newjob->defines = query; newServer = false; insertJob(newjob); } void DictInterface::match(const TQString &query) { JobData *newJob = generateQuery(JobData::TMatch,query); if (newJob) { if (global->currentStrategy == 0) newJob->strategy = "."; // spell check strategy else newJob->strategy = global->strategies[global->currentStrategy].utf8(); insertJob(newJob); } } // fetch detailed db info void DictInterface::showDbInfo(const TQString &db) { TQString ndb = db.simplifyWhiteSpace(); // cleanup query string if (ndb.isEmpty()) return; if (ndb.length()>100) // shorten if necessary ndb.truncate(100); JobData *newjob = new JobData(JobData::TShowDbInfo,newServer,global->server,global->port, global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled, global->user,global->secret,global->headLayout); newServer = false; newjob->query = ndb; // construct job... insertJob(newjob); } void DictInterface::showDatabases() { insertJob( new JobData(JobData::TShowDatabases,newServer,global->server,global->port, global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled, global->user,global->secret,global->headLayout)); newServer = false; } void DictInterface::showStrategies() { insertJob( new JobData(JobData::TShowStrategies,newServer,global->server,global->port, global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled, global->user,global->secret,global->headLayout)); newServer = false; } void DictInterface::showInfo() { insertJob( new JobData(JobData::TShowInfo,newServer,global->server,global->port, global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled, global->user,global->secret,global->headLayout)); newServer = false; } // get info about databases & stratgies the server knows void DictInterface::updateServer() { insertJob( new JobData(JobData::TUpdate,newServer,global->server,global->port, global->idleHold,global->timeout,global->pipeSize, global->encoding,global->authEnabled, global->user,global->secret,global->headLayout)); newServer = false; } // client-thread ended void DictInterface::clientDone() { TQString message; cleanPipes(); // read from pipe so that notifier doesn�t fire again if (jobList.isEmpty()) { kdDebug(5004) << "This shouldn�t happen, the client-thread signaled termination, but the job list is empty" << endl; return; // strange.. } clientDoneInProgress = true; TQStringList::iterator it; JobData* job = jobList.getFirst(); if (!job->canceled) { // non-interupted job? if (JobData::ErrNoErr == job->error) { switch (job->type) { case JobData::TUpdate : global->serverDatabases.clear(); for (it = job->databases.begin(); it != job->databases.end(); ++it) global->serverDatabases.append(*it); global->databases = global->serverDatabases; for (int i = global->databaseSets.count()-1;i>=0;i--) global->databases.prepend(global->databaseSets.at(i)->first()); global->databases.prepend(i18n("All Databases")); global->currentDatabase = 0; global->strategies.clear(); for (it = job->strategies.begin(); it != job->strategies.end(); ++it) global->strategies.append(*it); global->strategies.prepend(i18n("Spell Check")); global->currentStrategy = 0; message = i18n(" Received database/strategy list "); emit stopped(message); emit infoReady(); break; case JobData::TDefine: case JobData::TGetDefinitions: if (job->type == JobData::TDefine) { switch (job->numFetched) { case 0: message = i18n("No definitions found"); break; case 1: message = i18n("One definition found"); break; default: message = i18n("%1 definitions found").arg(job->numFetched); } } else { switch (job->numFetched) { case 0: message = i18n(" No definitions fetched "); break; case 1: message = i18n(" One definition fetched "); break; default: message = i18n(" %1 definitions fetched ").arg(job->numFetched); } } emit stopped(message); emit resultReady(job->result, job->query); break; case JobData::TMatch: switch (job->numFetched) { case 0: message = i18n(" No matching definitions found "); break; case 1: message = i18n(" One matching definition found "); break; default: message = i18n(" %1 matching definitions found ").arg(job->numFetched); } emit stopped(message); emit matchReady(job->matches); break; default : message = i18n(" Received information "); emit stopped(message); emit resultReady(job->result, job->query); } } else { TQString errMsg; switch (job->error) { case JobData::ErrCommunication: errMsg = i18n("Communication error:\n\n"); errMsg += job->result; break; case JobData::ErrTimeout: 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); break; case JobData::ErrBadHost: errMsg = i18n("Unable to connect to:\n%1:%2\n\nCannot resolve hostname.").arg(job->server).arg(job->port); break; case JobData::ErrConnect: errMsg = i18n("Unable to connect to:\n%1:%2\n\n").arg(job->server).arg(job->port); errMsg += job->result; break; case JobData::ErrRefused: errMsg = i18n("Unable to connect to:\n%1:%2\n\nThe server refused the connection.").arg(job->server).arg(job->port); break; case JobData::ErrNotAvailable: errMsg = i18n("The server is temporarily unavailable."); break; case JobData::ErrSyntax: errMsg = i18n("The server reported a syntax error.\nThis shouldn't happen -- please consider\nwriting a bug report."); break; case JobData::ErrCommandNotImplemented: errMsg = i18n("A command that Kdict needs isn't\nimplemented on the server."); break; case JobData::ErrAccessDenied: errMsg = i18n("Access denied.\nThis host is not allowed to connect."); break; case JobData::ErrAuthFailed: errMsg = i18n("Authentication failed.\nPlease enter a valid username and password."); break; case JobData::ErrInvalidDbStrat: errMsg = i18n("Invalid database/strategy.\nYou probably need to use Server->Get Capabilities."); break; case JobData::ErrNoDatabases: 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."); break; case JobData::ErrNoStrategies: errMsg = i18n("No strategies available."); break; case JobData::ErrServerError: errMsg = i18n("The server sent an unexpected reply:\n\"%1\"\nThis shouldn't happen, please consider\nwriting a bug report").arg(job->result); break; case JobData::ErrMsgTooLong: errMsg = i18n("The server sent a response with a text line\nthat was too long.\n(RFC 2229: max. 1024 characters/6144 octets)"); break; case JobData::ErrNoErr: // make compiler happy errMsg = i18n("No Errors"); } message = i18n(" Error "); emit stopped(message); KMessageBox::error(global->topLevel, errMsg); } } else { message = i18n(" Stopped "); emit stopped(message); } clientDoneInProgress = false; client->removeJob(); jobList.removeFirst(); // this job is now history if (!jobList.isEmpty()) // work to be done? startClient(); // => restart client } JobData* DictInterface::generateQuery(JobData::QueryType type, TQString query) { query = query.simplifyWhiteSpace(); // cleanup query string if (query.isEmpty()) return 0L; if (query.length()>300) // shorten if necessary query.truncate(300); query = query.replace(TQRegExp("[\"\\]"), ""); // remove remaining illegal chars... if (query.isEmpty()) return 0L; JobData *newjob = new JobData(type,newServer,global->server,global->port, global->idleHold,global->timeout,global->pipeSize, global->encoding, global->authEnabled, global->user,global->secret,global->headLayout); newServer = false; newjob->query = query; // construct job... if (global->currentDatabase == 0) // all databases newjob->databases.append("*"); else { if ((global->currentDatabase > 0)&& // database set (global->currentDatabase < global->databaseSets.count()+1)) { for (int i = 0;i<(int)global->serverDatabases.count();i++) if ((global->databaseSets.at(global->currentDatabase-1))->findIndex(global->serverDatabases[i])>0) newjob->databases.append(global->serverDatabases[i].utf8().data()); if (newjob->databases.count()==0) { KMessageBox::sorry(global->topLevel, i18n("Please select at least one database.")); delete newjob; return 0L; } } else { // one database newjob->databases.append(global->databases[global->currentDatabase].utf8().data()); } } return newjob; } void DictInterface::insertJob(JobData* job) { if (jobList.isEmpty()) { // Client has nothing to do, start directly jobList.append(job); startClient(); } else { // there are other pending jobs... stop(); jobList.append(job); } } // start client-thread void DictInterface::startClient() { cleanPipes(); if (jobList.isEmpty()) { kdDebug(5004) << "This shouldn�t happen, startClient called, but clientList is empty" << endl; return; } client->insertJob(jobList.getFirst()); char buf; // write one char in the pipe to the async thread if (::write(fdPipeOut[1],&buf,1) == -1) ::perror( "startClient()" ); TQString message; switch (jobList.getFirst()->type) { case JobData::TDefine: case JobData::TGetDefinitions: case JobData::TMatch: message = i18n(" Querying server... "); break; case JobData::TShowDatabases: case JobData::TShowStrategies: case JobData::TShowInfo: case JobData::TShowDbInfo: message = i18n(" Fetching information... "); break; case JobData::TUpdate: message = i18n(" Updating server information... "); break; } emit started(message); } // empty the pipes, so that notifier stops firing void DictInterface::cleanPipes() { fd_set rfds; struct timeval tv; int ret; char buf; tv.tv_sec = 0; tv.tv_usec = 0; do { FD_ZERO(&rfds); FD_SET(fdPipeIn[0],&rfds); if (1==(ret=select(FD_SETSIZE,&rfds,NULL,NULL,&tv))) if ( ::read(fdPipeIn[0], &buf, 1 ) == -1 ) ::perror( "cleanPipes" ); } while (ret == 1); do { FD_ZERO(&rfds); FD_SET(fdPipeOut[0],&rfds); if (1==(ret=select(FD_SETSIZE,&rfds,NULL,NULL,&tv))) if ( ::read(fdPipeOut[0], &buf, 1 ) == -1 ) ::perror( "cleanPipes" ); } while (ret == 1); } //-------------------------------- #include "dict.moc"