|
|
/* -------------------------------------------------------------
|
|
|
|
|
|
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 <qregexp.h>
|
|
|
#include <qtextcodec.h>
|
|
|
|
|
|
#include <klocale.h>
|
|
|
#include <kdebug.h>
|
|
|
#include <kmessagebox.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,QString const& Nserver,int Nport,
|
|
|
int NidleHold, int Ntimeout, int NpipeSize, QString const& Nencoding, bool NAuthEnabled,
|
|
|
QString const& Nuser, QString 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))
|
|
|
qWarning("pthread_setcanceltype failed!");
|
|
|
if (0!= pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL))
|
|
|
qWarning("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 = QTextCodec::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()
|
|
|
{
|
|
|
QString command;
|
|
|
|
|
|
job->defines.clear();
|
|
|
QStringList::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 = QString::null;
|
|
|
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");
|
|
|
|
|
|
QString lastDb;
|
|
|
QStringList::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;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
QString htmlString(const QString &raw)
|
|
|
{
|
|
|
unsigned int len=raw.length();
|
|
|
QString 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;
|
|
|
}
|
|
|
|
|
|
|
|
|
QString generateDefineLink(const QString &raw)
|
|
|
{
|
|
|
QRegExp http("http://[^\\s<>()\"|\\[\\]{}]+");
|
|
|
QRegExp ftp("ftp://[^\\s<>()\"|\\[\\]{}]+");
|
|
|
int matchPos=0, matchLen=0;
|
|
|
bool httpMatch=false;
|
|
|
QString 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()
|
|
|
{
|
|
|
QCString lastDb,bracketBuff;
|
|
|
QStrList hashList;
|
|
|
char *s;
|
|
|
int defCount,response;
|
|
|
|
|
|
// html header...
|
|
|
resultAppend("<body>\n");
|
|
|
|
|
|
while (job->defines.count()>0) {
|
|
|
defCount = 0;
|
|
|
cmdBuffer = "";
|
|
|
do {
|
|
|
QStringList::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 = QString::null;
|
|
|
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(QCString(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()
|
|
|
{
|
|
|
QStringList::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 = QString::null;
|
|
|
resultAppend(KExtendedSocket::strError(ks.status(), errno));
|
|
|
job->error = JobData::ErrConnect;
|
|
|
} else if (ks.status() == IO_TimeOutError)
|
|
|
job->error = JobData::ErrTimeout;
|
|
|
else {
|
|
|
job->result = QString::null;
|
|
|
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(QCString(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 = QString::null;
|
|
|
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 = QString::null;
|
|
|
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 = QString::null;
|
|
|
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 = QString::null;
|
|
|
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 = QString::null;
|
|
|
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 = QString::null;
|
|
|
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 = QString::null;
|
|
|
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 = QString::null;
|
|
|
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 = QString::null;
|
|
|
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(QString 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 QSocketNotifier(fdPipeIn[0],QSocketNotifier::Read,this);
|
|
|
connect(notifier,SIGNAL(activated(int)),this,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,SIGNAL(activated(int)),this,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 QString &query)
|
|
|
{
|
|
|
JobData *newJob = generateQuery(JobData::TDefine,query);
|
|
|
if (newJob)
|
|
|
insertJob(newJob);
|
|
|
}
|
|
|
|
|
|
|
|
|
void DictInterface::getDefinitions(QStringList 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 QString &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 QString &db)
|
|
|
{
|
|
|
QString 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()
|
|
|
{
|
|
|
QString 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;
|
|
|
QStringList::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 {
|
|
|
QString 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, QString 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(QRegExp("[\"\\]"), ""); // 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()" );
|
|
|
|
|
|
QString 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"
|