goldendict-ng/src/dict/dictserver.cc

971 lines
26 KiB
C++
Raw Normal View History

2014-04-30 12:55:53 +00:00
/* This file is (c) 2014 Abs62
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include "dictserver.hh"
#include "wstring_qt.hh"
#include <QUrl>
#include <QTcpSocket>
#include <QString>
#include <list>
#include "gddebug.hh"
#include "htmlescape.hh"
#include <QRegularExpression>
2014-04-30 12:55:53 +00:00
namespace DictServer {
using namespace Dictionary;
enum {
DefaultPort = 2628
};
namespace {
#define MAX_MATCHES_COUNT 60
2014-04-30 12:55:53 +00:00
bool readLine( QTcpSocket & socket, QString & line,
QString & errorString, QAtomicInt & isCancelled )
{
line.clear();
errorString.clear();
if( socket.state() != QTcpSocket::ConnectedState )
return false;
for( ; ; )
{
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
return false;
if( socket.canReadLine() )
{
QByteArray reply = socket.readLine();
line = QString::fromUtf8( reply.data(), reply.size() );
return true;
}
if( !socket.waitForReadyRead( 2000 ) )
{
errorString = "Data reading error: socket error " + QString::number( socket.error() )
+ ": \"" + socket.errorString() + "\"";
2014-04-30 12:55:53 +00:00
break;
}
}
return false;
}
bool connectToServer( QTcpSocket & socket, QString const & url,
QString & errorString, QAtomicInt & isCancelled )
{
QUrl serverUrl( url );
quint16 port = serverUrl.port( DefaultPort );
QString reply;
for( ; ; )
{
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
return false;
socket.connectToHost( serverUrl.host(), port );
if( socket.state() != QTcpSocket::ConnectedState )
{
if( !socket.waitForConnected( 5000 ) )
break;
}
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
return false;
if( !readLine( socket, reply, errorString, isCancelled ) )
break;
if( !reply.isEmpty() && reply.left( 3 ) != "220" )
{
errorString = "Server refuse connection: " + reply;
return false;
}
2018-10-21 19:58:44 +00:00
QString msgId = reply.mid( reply.lastIndexOf(" ") ).trimmed();
2014-04-30 12:55:53 +00:00
socket.write( QByteArray( "CLIENT GoldenDict\r\n") );
if( !socket.waitForBytesWritten( 1000 ) )
break;
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
return false;
if( !readLine( socket, reply, errorString, isCancelled ) )
break;
if( !serverUrl.userInfo().isEmpty() )
{
QString authCommand = QString( "AUTH " );
2018-10-21 19:58:44 +00:00
QString authString = msgId;
2014-04-30 12:55:53 +00:00
2022-02-27 05:17:37 +00:00
int pos = serverUrl.userInfo().indexOf( QRegularExpression( "[:;]" ) );
2014-04-30 12:55:53 +00:00
if( pos > 0 )
2018-10-21 19:58:44 +00:00
{
authCommand += serverUrl.userInfo().left( pos );
authString += serverUrl.userInfo().mid( pos + 1 );
}
2014-04-30 12:55:53 +00:00
else
authCommand += serverUrl.userInfo();
2018-10-21 19:58:44 +00:00
authCommand += " ";
authCommand += QCryptographicHash::hash( authString.toUtf8(), QCryptographicHash::Md5 ).toHex();
2014-04-30 12:55:53 +00:00
authCommand += "\r\n";
socket.write( authCommand.toUtf8() );
2018-10-21 19:58:44 +00:00
if( !socket.waitForBytesWritten( 1000 ) )
2014-04-30 12:55:53 +00:00
break;
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
return false;
2018-10-21 19:58:44 +00:00
if( !readLine( socket, reply, errorString, isCancelled ) )
2014-04-30 12:55:53 +00:00
break;
if( reply.left( 3 ) != "230" )
{
errorString = "Authentication error: " + reply;
return false;
}
}
2018-10-20 19:49:41 +00:00
socket.write( QByteArray( "OPTION MIME\r\n" ) );
if( !socket.waitForBytesWritten( 1000 ) )
break;
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2018-10-20 19:49:41 +00:00
return false;
if( !readLine( socket, reply, errorString, isCancelled ) )
break;
if( reply.left( 3 ) != "250" )
{
// RFC 2229, 3.10.1.1:
// OPTION MIME is a REQUIRED server capability,
// all DICT servers MUST implement this command.
errorString = "Server doesn't support mime capability: " + reply;
return false;
}
2014-04-30 12:55:53 +00:00
return true;
}
if( !Utils::AtomicInt::loadAcquire( isCancelled ) )
errorString = QString( "Server connection fault, socket error %1: \"%2\"" )
.arg( QString::number( socket.error() ) )
.arg( socket.errorString() );
2014-04-30 12:55:53 +00:00
return false;
}
void disconnectFromServer( QTcpSocket & socket )
{
if( socket.state() == QTcpSocket::ConnectedState )
socket.write( QByteArray( "QUIT\r\n" ) );
socket.disconnectFromHost();
}
class DictServerDictionary: public Dictionary::Class
{
string name;
QString url, icon;
quint32 langId;
QString errorString;
QTcpSocket socket;
QStringList databases;
QStringList strategies;
QStringList serverDatabases;
2014-04-30 12:55:53 +00:00
public:
DictServerDictionary( string const & id, string const & name_,
QString const & url_,
QString const & database_,
QString const & strategies_,
2014-04-30 12:55:53 +00:00
QString const & icon_ ):
Dictionary::Class( id, vector< string >() ),
name( name_ ),
url( url_ ),
icon( icon_ ),
langId( 0 )
{
int pos = url.indexOf( "://" );
if( pos < 0 )
url = "dict://" + url;
2022-02-27 05:17:37 +00:00
databases = database_.split( QRegularExpression( "[ ,;]" ), Qt::SkipEmptyParts );
2014-04-30 12:55:53 +00:00
if( databases.isEmpty() )
databases.append( "*" );
2022-02-27 05:17:37 +00:00
strategies = strategies_.split( QRegularExpression( "[ ,;]" ), Qt::SkipEmptyParts );
if( strategies.isEmpty() )
strategies.append( "prefix" );
2014-04-30 12:55:53 +00:00
}
string getName() noexcept override
2014-04-30 12:55:53 +00:00
{ return name; }
map< Property, string > getProperties() noexcept override
2014-04-30 12:55:53 +00:00
{ return map< Property, string >(); }
unsigned long getArticleCount() noexcept override
2014-04-30 12:55:53 +00:00
{ return 0; }
unsigned long getWordCount() noexcept override
2014-04-30 12:55:53 +00:00
{ return 0; }
sptr< WordSearchRequest > prefixMatch( wstring const &,
unsigned long maxResults ) override ;
2014-04-30 12:55:53 +00:00
sptr< DataRequest > getArticle( wstring const &, vector< wstring > const & alts,
wstring const &, bool ) override
2022-01-09 08:35:07 +00:00
;
2014-04-30 12:55:53 +00:00
quint32 getLangFrom() const override
2014-04-30 12:55:53 +00:00
{ return langId; }
quint32 getLangTo() const override
2014-04-30 12:55:53 +00:00
{ return langId; }
QString const & getDescription() override;
2014-04-30 12:55:53 +00:00
protected:
void loadIcon() noexcept override;
2014-04-30 12:55:53 +00:00
void getServerDatabases();
2014-04-30 12:55:53 +00:00
friend class DictServerWordSearchRequest;
friend class DictServerArticleRequest;
};
void DictServerDictionary::loadIcon() noexcept
2014-04-30 12:55:53 +00:00
{
if ( dictionaryIconLoaded )
return;
if( !icon.isEmpty() )
{
QFileInfo fInfo( QDir( Config::getConfigDir() ), icon );
if( fInfo.isFile() )
loadIconFromFile( fInfo.absoluteFilePath(), true );
}
if( dictionaryIcon.isNull() )
dictionaryIcon = dictionaryNativeIcon = QIcon(":/icons/network.svg");
2014-04-30 12:55:53 +00:00
dictionaryIconLoaded = true;
}
QString const & DictServerDictionary::getDescription()
{
if( serverDatabases.isEmpty() )
{
dictionaryDescription.clear();
getServerDatabases();
}
2014-04-30 12:55:53 +00:00
if( dictionaryDescription.isEmpty() )
{
dictionaryDescription = QCoreApplication::translate( "DictServer", "Url: " ) + url + "\n";
dictionaryDescription += QCoreApplication::translate( "DictServer", "Databases: " ) + databases.join( ", " ) + "\n";
dictionaryDescription += QCoreApplication::translate( "DictServer", "Search strategies: " ) + strategies.join( ", " );
if( !serverDatabases.isEmpty() )
{
dictionaryDescription += "\n\n";
dictionaryDescription += QCoreApplication::translate( "DictServer", "Server databases" )
+ " (" + QString::number( serverDatabases.size()) + "):";
for( QStringList::const_iterator i = serverDatabases.begin(); i != serverDatabases.end(); ++i )
dictionaryDescription += "\n" + *i;
}
2014-04-30 12:55:53 +00:00
}
return dictionaryDescription;
}
void DictServerDictionary::getServerDatabases()
{
QAtomicInt isCancelled;
QTcpSocket * socket = new QTcpSocket;
if( !socket )
return;
if( connectToServer( *socket, url, errorString, isCancelled ) )
{
for( ; ; )
{
QString req = QString( "SHOW DB\r\n" );
socket->write( req.toUtf8() );
socket->waitForBytesWritten( 1000 );
QString reply;
if( !readLine( *socket, reply, errorString, isCancelled ) )
return;
if( reply.left( 3 ) == "110" )
{
int countPos = reply.indexOf( ' ', 4 );
// Get databases count
int count = reply.mid( 4, countPos > 4 ? countPos - 4 : -1 ).toInt();
// Read databases
for( int x = 0; x < count; x++ )
{
if( !readLine( *socket, reply, errorString, isCancelled ) )
break;
if( reply[ 0 ] == '.' )
break;
while( reply.endsWith( '\r' ) || reply.endsWith( '\n' ) )
reply.chop( 1 );
if( !reply.isEmpty() )
serverDatabases.append( reply );
}
break;
}
else
{
gdWarning( "Retrieving databases from \"%s\" fault: %s\n",
getName().c_str(), reply.toUtf8().data() );
break;
}
}
disconnectFromServer( *socket );
}
if( !errorString.isEmpty() )
gdWarning( "Retrieving databases from \"%s\" fault: %s\n",
getName().c_str(), errorString.toUtf8().data() );
delete socket;
}
2014-04-30 12:55:53 +00:00
class DictServerWordSearchRequest;
class DictServerWordSearchRequestRunnable: public QRunnable
{
DictServerWordSearchRequest & r;
QSemaphore & hasExited;
public:
DictServerWordSearchRequestRunnable( DictServerWordSearchRequest & r_,
QSemaphore & hasExited_ ): r( r_ ),
hasExited( hasExited_ )
{}
~DictServerWordSearchRequestRunnable()
{
hasExited.release();
}
void run() override;
2014-04-30 12:55:53 +00:00
};
class DictServerWordSearchRequest: public Dictionary::WordSearchRequest
{
QAtomicInt isCancelled;
wstring word;
QString errorString;
QSemaphore hasExited;
DictServerDictionary & dict;
QTcpSocket * socket;
public:
DictServerWordSearchRequest( wstring const & word_,
DictServerDictionary & dict_ ) :
word( word_ ),
dict( dict_ ),
socket( 0 )
{
QThreadPool::globalInstance()->start(
new DictServerWordSearchRequestRunnable( *this, hasExited ) );
}
void run();
~DictServerWordSearchRequest()
{
hasExited.acquire();
}
void cancel() override;
2014-04-30 12:55:53 +00:00
};
void DictServerWordSearchRequestRunnable::run()
{
r.run();
}
void DictServerWordSearchRequest::run()
{
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
{
finish();
return;
}
socket = new QTcpSocket;
if( !socket )
{
finish();
return;
}
if( connectToServer( *socket, dict.url, errorString, isCancelled ) )
{
QStringList matchesList;
for( int ns = 0; ns < dict.strategies.size(); ns++ )
2014-04-30 12:55:53 +00:00
{
for( int i = 0; i < dict.databases.size(); i++ )
{
QString matchReq = QString( "MATCH " )
+ dict.databases.at( i )
+ " " + dict.strategies.at( ns )
+ " \"" + QString::fromStdU32String( word )
+ "\"\r\n";
socket->write( matchReq.toUtf8() );
socket->waitForBytesWritten( 1000 );
2014-04-30 12:55:53 +00:00
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
break;
2014-04-30 12:55:53 +00:00
QString reply;
2014-04-30 12:55:53 +00:00
if( !readLine( *socket, reply, errorString, isCancelled ) )
break;
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
break;
if( reply.left( 3 ) == "250" )
{
// "OK" reply - matches info will be later
if( !readLine( *socket, reply, errorString, isCancelled ) )
break;
2014-04-30 12:55:53 +00:00
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
break;
}
2014-04-30 12:55:53 +00:00
if( reply.left( 3 ) == "552" )
{
// No matches
continue;
}
2014-04-30 12:55:53 +00:00
if( reply[ 0 ] == '5' || reply[ 0 ] == '4' )
{
// Database error
gdWarning( "Find matches in \"%s\", database \"%s\", strategy \"%s\" fault: %s\n",
dict.getName().c_str(), dict.databases.at( i ).toUtf8().data(),
dict.strategies.at( ns ).toUtf8().data(), reply.toUtf8().data() );
continue;
}
2014-04-30 12:55:53 +00:00
if( reply.left( 3 ) == "152" )
2014-04-30 12:55:53 +00:00
{
// Matches found
int countPos = reply.indexOf( ' ', 4 );
2014-04-30 12:55:53 +00:00
// Get matches count
int count = reply.mid( 4, countPos > 4 ? countPos - 4 : -1 ).toInt();
2014-04-30 12:55:53 +00:00
// Read matches
for( int x = 0; x <= count; x++ )
{
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
break;
2014-04-30 12:55:53 +00:00
if( !readLine( *socket, reply, errorString, isCancelled ) )
break;
2014-04-30 12:55:53 +00:00
if( reply[ 0 ] == '.' )
break;
2014-04-30 12:55:53 +00:00
while( reply.endsWith( '\r' ) || reply.endsWith( '\n' ) )
reply.chop( 1 );
int pos = reply.indexOf( ' ' );
if( pos >= 0 )
{
QString word = reply.mid( pos + 1 );
if( word.endsWith( '\"') )
word.chop( 1 );
if( word[ 0 ] == '\"' )
word = word.mid( 1 );
matchesList.append( word );
}
2014-04-30 12:55:53 +00:00
}
if( Utils::AtomicInt::loadAcquire( isCancelled ) || !errorString.isEmpty() )
break;
2014-04-30 12:55:53 +00:00
}
}
2014-04-30 12:55:53 +00:00
if( Utils::AtomicInt::loadAcquire( isCancelled ) || !errorString.isEmpty() )
break;
2014-04-30 12:55:53 +00:00
matchesList.removeDuplicates();
if( matchesList.size() >= MAX_MATCHES_COUNT )
break;
2014-04-30 12:55:53 +00:00
}
if( !Utils::AtomicInt::loadAcquire( isCancelled ) && errorString.isEmpty() )
2014-04-30 12:55:53 +00:00
{
matchesList.removeDuplicates();
int count = matchesList.size();
if( count > MAX_MATCHES_COUNT )
count = MAX_MATCHES_COUNT;
if( count )
{
Mutex::Lock _( dataMutex );
for( int x = 0; x < count; x++ )
matches.push_back( gd::toWString( matchesList.at( x ) ) );
}
}
}
if( !errorString.isEmpty() )
gdWarning( "Prefix find in \"%s\" fault: %s\n", dict.getName().c_str(),
errorString.toUtf8().data() );
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
socket->abort();
else
disconnectFromServer( *socket );
delete socket;
if( !Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
finish();
}
void DictServerWordSearchRequest::cancel()
{
isCancelled.ref();
Mutex::Lock _( dataMutex );
finish();
}
class DictServerArticleRequest;
class DictServerArticleRequestRunnable: public QRunnable
{
DictServerArticleRequest & r;
QSemaphore & hasExited;
public:
DictServerArticleRequestRunnable( DictServerArticleRequest & r_,
QSemaphore & hasExited_ ): r( r_ ),
hasExited( hasExited_ )
{}
~DictServerArticleRequestRunnable()
{
hasExited.release();
}
void run() override;
2014-04-30 12:55:53 +00:00
};
class DictServerArticleRequest: public Dictionary::DataRequest
{
QAtomicInt isCancelled;
wstring word;
QString errorString;
QSemaphore hasExited;
DictServerDictionary & dict;
QTcpSocket * socket;
public:
DictServerArticleRequest( wstring const & word_,
DictServerDictionary & dict_ ) :
word( word_ ),
dict( dict_ ),
socket( 0 )
{
QThreadPool::globalInstance()->start(
new DictServerArticleRequestRunnable( *this, hasExited ) );
}
void run();
~DictServerArticleRequest()
{
hasExited.acquire();
}
void cancel() override;
2014-04-30 12:55:53 +00:00
};
void DictServerArticleRequestRunnable::run()
{
r.run();
}
void DictServerArticleRequest::run()
{
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
{
finish();
return;
}
socket = new QTcpSocket;
if( !socket )
{
finish();
return;
}
if( connectToServer( *socket, dict.url, errorString, isCancelled ) )
{
string articleData;
for( int i = 0; i < dict.databases.size(); i++ )
{
2018-10-20 19:49:41 +00:00
QString defineReq = QString( "DEFINE " )
2014-04-30 12:55:53 +00:00
+ dict.databases.at( i )
+ " \"" + QString::fromStdU32String( word ) + "\"\r\n";
2018-10-20 19:49:41 +00:00
socket->write( defineReq.toUtf8() );
2014-04-30 12:55:53 +00:00
socket->waitForBytesWritten( 1000 );
QString reply;
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
break;
if( !readLine( *socket, reply, errorString, isCancelled ) )
break;
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
break;
if( reply.left( 3 ) == "250" )
{
// "OK" reply - matches info will be later
if( !readLine( *socket, reply, errorString, isCancelled ) )
break;
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
break;
}
if( reply.left( 3 ) == "552" )
{
// No matches found
continue;
}
if( reply[ 0 ] == '5' || reply[ 0 ] == '4' )
{
// Database error
gdWarning( "Articles request from \"%s\", database \"%s\" fault: %s\n",
dict.getName().c_str(), dict.databases.at( i ).toUtf8().data(),
reply.toUtf8().data() );
continue;
}
if( reply.left( 3 ) == "150" )
{
// Articles found
int countPos = reply.indexOf( ' ', 4 );
QString articleText;
// Get articles count
int count = reply.mid( 4, countPos > 4 ? countPos - 4 : -1 ).toInt();
// Read articles
for( int x = 0; x < count; x++ )
{
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
break;
if( !readLine( *socket, reply, errorString, isCancelled ) )
break;
if( reply.left( 3 ) == "250" )
break;
if( reply.left( 3 ) == "151" )
{
int pos = 4;
int endPos;
// Skip requested word
if( reply[ pos ] == '\"' )
endPos = reply.indexOf( '\"', pos + 1 ) + 1;
else
endPos = reply.indexOf( ' ', pos );
if( endPos < pos )
{
// It seems mailformed string
break;
}
pos = endPos + 1;
QString dbID, dbName;
// Retrieve database ID
endPos = reply.indexOf( ' ', pos );
if( endPos < pos )
{
// It seems mailformed string
break;
}
dbID = reply.mid( pos, endPos - pos );
// Retrieve database ID
pos = endPos + 1;
endPos = reply.indexOf( ' ', pos );
if( reply[ pos ] == '\"' )
endPos = reply.indexOf( '\"', pos + 1 ) + 1;
else
endPos = reply.indexOf( ' ', pos );
if( endPos < pos )
{
// It seems mailformed string
break;
}
dbName = reply.mid( pos, endPos - pos );
if( dbName.endsWith( '\"' ) )
dbName.chop( 1 );
if( dbName[ 0 ] == '\"' )
dbName = dbName.mid( 1 );
articleData += string( "<div class=\"dictserver_from\">From " )
+ dbName.toUtf8().data()
+ " [" + dbID.toUtf8().data() + "]:"
+ "</div>";
2018-10-20 19:49:41 +00:00
// Retreive MIME headers if any
static QRegularExpression contentTypeExpr( "Content-Type\\s*:\\s*text/html",
QRegularExpression::CaseInsensitiveOption );
2021-11-19 13:47:22 +00:00
bool contentInHtml = false;
2018-10-20 19:49:41 +00:00
for( ; ; )
{
if( !readLine( *socket, reply, errorString, isCancelled ) )
break;
if( reply == "\r\n" )
break;
QRegularExpressionMatch match = contentTypeExpr.match( reply );
if( match.hasMatch() )
contentInHtml = true;
2018-10-20 19:49:41 +00:00
}
2014-04-30 12:55:53 +00:00
// Retrieve article text
articleText.clear();
for( ; ; )
{
if( !readLine( *socket, reply, errorString, isCancelled ) )
break;
if( reply == ".\r\n" )
break;
articleText += reply;
}
if( Utils::AtomicInt::loadAcquire( isCancelled ) || !errorString.isEmpty() )
2014-04-30 12:55:53 +00:00
break;
static QRegularExpression phonetic( R"(\\([^\\]+)\\)",
QRegularExpression::CaseInsensitiveOption ); // phonetics: \stuff\ ...
static QRegularExpression divs_inside_phonetic( "</div([^>]*)><div([^>]*)>",
QRegularExpression::CaseInsensitiveOption );
static QRegularExpression refs( R"(\{([^\{\}]+)\})",
QRegularExpression::CaseInsensitiveOption ); // links: {stuff}
static QRegularExpression links( "<a href=\"gdlookup://localhost/([^\"]*)\">",
QRegularExpression::CaseInsensitiveOption );
static QRegularExpression tags( "<[^>]*>",
QRegularExpression::CaseInsensitiveOption );
2021-11-19 13:47:22 +00:00
2018-10-20 19:49:41 +00:00
string articleStr;
if( contentInHtml )
2018-10-20 19:49:41 +00:00
articleStr = articleText.toUtf8().data();
else
articleStr = Html::preformat( articleText.toUtf8().data() );
2018-10-20 19:49:41 +00:00
2021-01-15 09:47:18 +00:00
articleText = QString::fromUtf8( articleStr.c_str(), articleStr.size() );
if( !contentInHtml )
{
articleText = articleText.replace(refs, R"(<a href="gdlookup://localhost/\1">\1</a>)" );
2021-01-15 09:47:18 +00:00
pos = 0;
QString articleNewText;
// Handle phonetics
QRegularExpressionMatchIterator it = phonetic.globalMatch( articleText );
while( it.hasNext() )
{
QRegularExpressionMatch match = it.next();
2022-02-27 05:17:37 +00:00
articleNewText += articleText.mid( pos, match.capturedStart() - pos );
2021-01-15 09:47:18 +00:00
pos = match.capturedEnd();
QString phonetic_text = match.captured( 1 );
phonetic_text.replace( divs_inside_phonetic, R"(</span></div\1><div\2><span class="dictd_phonetic">)" );
2021-01-15 09:47:18 +00:00
articleNewText += "<span class=\"dictd_phonetic\">" + phonetic_text + "</span>";
}
if( pos )
{
2022-02-27 05:17:37 +00:00
articleNewText += articleText.mid( pos );
2021-01-15 09:47:18 +00:00
articleText = articleNewText;
articleNewText.clear();
}
// Handle links
pos = 0;
it = links.globalMatch( articleText );
while( it.hasNext() )
{
QRegularExpressionMatch match = it.next();
2022-02-27 05:17:37 +00:00
articleNewText += articleText.mid( pos, match.capturedStart() - pos );
2021-01-15 09:47:18 +00:00
pos = match.capturedEnd();
QString link = match.captured( 1 );
link.replace( tags, " " );
link.replace( "&nbsp;", " " );
QString newLink = match.captured();
newLink.replace( 30, match.capturedLength( 1 ),
QString::fromUtf8( QUrl::toPercentEncoding( link.simplified() ) ) );
articleNewText += newLink;
}
if( pos )
{
2022-02-27 05:17:37 +00:00
articleNewText += articleText.mid( pos );
2021-01-15 09:47:18 +00:00
articleText = articleNewText;
articleNewText.clear();
}
}
2014-04-30 12:55:53 +00:00
articleData += string( "<div class=\"dictd_article\">" )
+ articleText.toUtf8().data()
+ "<br></div>";
}
if( Utils::AtomicInt::loadAcquire( isCancelled ) || !errorString.isEmpty() )
2014-04-30 12:55:53 +00:00
break;
}
}
}
if( !Utils::AtomicInt::loadAcquire( isCancelled ) && errorString.isEmpty() && !articleData.empty() )
2014-04-30 12:55:53 +00:00
{
Mutex::Lock _( dataMutex );
data.resize( articleData.size() );
memcpy( &data.front(), articleData.data(), articleData.size() );
hasAnyData = true;
}
}
if( !errorString.isEmpty() )
gdWarning( "Articles request from \"%s\" fault: %s\n", dict.getName().c_str(),
errorString.toUtf8().data() );
if( Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
socket->abort();
else
disconnectFromServer( *socket );
delete socket;
if( !Utils::AtomicInt::loadAcquire( isCancelled ) )
2014-04-30 12:55:53 +00:00
finish();
}
void DictServerArticleRequest::cancel()
{
isCancelled.ref();
Mutex::Lock _( dataMutex );
finish();
}
sptr< WordSearchRequest > DictServerDictionary::prefixMatch( wstring const & word,
unsigned long maxResults )
2022-01-09 08:35:07 +00:00
2014-04-30 12:55:53 +00:00
{
(void) maxResults;
if ( word.size() > 80 )
{
// Don't make excessively large queries -- they're fruitless anyway
return std::make_shared<WordSearchRequestInstant>();
2014-04-30 12:55:53 +00:00
}
else
return std::make_shared<DictServerWordSearchRequest>( word, *this );
2014-04-30 12:55:53 +00:00
}
sptr< DataRequest > DictServerDictionary::getArticle( wstring const & word,
vector< wstring > const &,
wstring const &, bool )
2022-01-09 08:35:07 +00:00
2014-04-30 12:55:53 +00:00
{
if ( word.size() > 80 )
{
// Don't make excessively large queries -- they're fruitless anyway
return std::make_shared<DataRequestInstant>( false );
2014-04-30 12:55:53 +00:00
}
else
return std::make_shared<DictServerArticleRequest>( word, *this );
2014-04-30 12:55:53 +00:00
}
} // Anonimuos namespace
vector< sptr< Dictionary::Class > > makeDictionaries( Config::DictServers const & servers )
2022-01-09 08:35:07 +00:00
2014-04-30 12:55:53 +00:00
{
vector< sptr< Dictionary::Class > > result;
for( int x = 0; x < servers.size(); ++x )
{
if ( servers[ x ].enabled )
result.push_back( std::make_shared<DictServerDictionary>( servers[ x ].id.toStdString(),
2014-04-30 12:55:53 +00:00
servers[ x ].name.toUtf8().data(),
servers[ x ].url,
servers[ x ].databases,
servers[ x ].strategies,
2014-04-30 12:55:53 +00:00
servers[ x ].iconFilename ) );
}
return result;
}
} // namespace DictServer